Livt packages

Livt.IO

Version 1.8.1

Memory, UART, I2C, SPI, and Ethernet input/output components for Livt hardware designs.

Published files (126)

src/uart/BufferedUart.lvt

namespace Livt.IO

using Livt.Collections

/**
 * FIFO-backed application UART. The owner of each FIFO is its only mutator.
 * Launch permission gates a new frame, never an active or committed frame.
 */
component BufferedUart<RX_CAPACITY: int = 64,
	TX_CAPACITY: int = 64,
	DATA_BITS: UartDataBits = UartDataBits.Eight,
	PARITY: UartParity = UartParity.None,
	STOP_BITS: UartStopBits = UartStopBits.One,
	BAUD: Frequency = 115200Hz> : IBufferedUart
{
	// Signal-level UART and the two independently owned byte stores.
	uart: Uart<DATA_BITS, PARITY, STOP_BITS, BAUD>
	receiver: Fifo<byte, RX_CAPACITY>
	transmitter: Fifo<byte, TX_CAPACITY>
	launchPermission: bool

	// Scheduled transmit and receive transactions with captured results.
	txRequest: bool
	txCompleted: bool
	txAccepted: bool
	txPayload: byte
	rxRequest: bool
	rxCompleted: bool
	rxAccepted: bool
	rxResult: byte

	// Independently serialized clear transactions.
	txClearRequest: bool
	txClearCompleted: bool
	rxClearRequest: bool
	rxClearCompleted: bool

	// Combinational requests presented to the two FIFO owners.
	txClear: bool
	rxClear: bool
	txPush: bool
	txPop: bool
	rxPush: bool
	rxPop: bool

	// FIFO acceptance, payload and occupancy observations.
	txPushAccepted: bool
	txPopAccepted: bool
	rxPushAccepted: bool
	rxPopAccepted: bool
	txHead: byte
	rxHead: byte
	rxPayload: byte
	txCount: int
	txSpace: int
	rxCount: int
	public receiveCount: out int
	rxSpace: int

	// Physical UART handshakes and committed-launch tracking.
	txValid: logic
	txData: logic[8]
	txActive: logic
	txDone: logic
	rxValid: logic
	rxData: logic[8]
	rxError: logic
	launchPending: bool

	// Sticky receive diagnostics and their serialized clear transaction.
	frameErrors: int
	overflows: int
	errorClearRequest: bool
	errorClearCompleted: bool

	/**
	 * Uses the inherited context. Optional launch permission must already be
	 * synchronized; ordinary UARTs leave it at its default value of true.
	 */
	new(
		rx: in logic,
		tx: out logic,
		launchPermission: bool = true
	)
	{
		this.launchPermission = launchPermission
		this.uart = new Uart<DATA_BITS, PARITY, STOP_BITS, BAUD>(rx, tx, this.txValid, this.txData,
			this.txActive, this.txDone, this.rxValid, this.rxData, this.rxError)
		this.receiver = new Fifo<byte, RX_CAPACITY>(this.rxClear, this.rxPush,
			this.rxPayload, this.rxPop, this.rxPushAccepted, this.rxPopAccepted,
			this.rxHead, this.rxCount, this.rxSpace)
		this.transmitter = new Fifo<byte, TX_CAPACITY>(this.txClear, this.txPush,
			this.txPayload, this.txPop, this.txPushAccepted, this.txPopAccepted,
			this.txHead, this.txCount, this.txSpace)
	}

	/**
	 * Returns acceptance into the transmit FIFO, not wire completion.
	 */
	override fn TryTransmit(data: byte) bool
	{
		txPayload = data
		txRequest = !txRequest

		while (txCompleted != txRequest) {
			state {
			}
		}

		return txAccepted
	}

	/**
	 * Enqueues a prefix, stopping at the first rejected byte. Other producers
	 * may interleave; the returned count is the number accepted by this call.
	 */
	override fn Send(data: byte[]) int
	{
		var accepted = 0

		for (var index = 0; index < data.Length(); index++) {
			if (!TryTransmit(data[index])) {
				return accepted
			}

			accepted++
		}

		return accepted
	}

	/**
	 * Removes one received byte atomically; zero on failure is not valid data.
	 */
	override fn TryReceive(data: out byte) bool
	{
		rxRequest = !rxRequest

		while (rxCompleted != rxRequest) {
			state {
			}
		}

		data = rxResult
		return rxAccepted
	}

	/**
	 * Discards queued data but not a frame or byte already committed to launch.
	 */
	override fn ClearTransmitBuffer()
	{
		txClearRequest = !txClearRequest

		while (txClearCompleted != txClearRequest) {
			state {
			}
		}
	}

	/**
	 * Clear wins over reception and consumption at its transaction edge.
	 */
	override fn ClearReceiveBuffer()
	{
		rxClearRequest = !rxClearRequest

		while (rxClearCompleted != rxClearRequest) {
			state {
			}
		}
	}

	/**
	 * Reports an active physical transmit frame.
	 */
	override fn IsTransmitting() bool
	{
		return txActive == 0b1
	}

	/**
	 * Includes queued, pending API, committed-launch and physically active work.
	 */
	override fn HasPendingTransmit() bool
	{
		return txCount > 0 || launchPending || txActive == 0b1 || txRequest != txCompleted
	}

	/**
	 * Reports that no transmit work remains.
	 */
	override fn IsTransmitIdle() bool
	{
		return txCount == 0 && !launchPending && txActive == 0b0 && txRequest == txCompleted
	}

	/**
	 * Returns a receive occupancy snapshot, not a reservation.
	 */
	override fn GetReceiveCount() int
	{
		return rxCount
	}

	/**
	 * Returns a transmit free-space snapshot, not a reservation.
	 */
	override fn GetTransmitSpace() int
	{
		return txSpace
	}

	/**
	 * Returns the number of invalid parity or stop bits observed since error clear.
	 */
	override fn GetFrameErrorCount() int
	{
		return frameErrors
	}

	/**
	 * Returns valid received frames rejected because the FIFO was full.
	 */
	override fn GetReceiveOverflowCount() int
	{
		return overflows
	}

	/**
	 * Clears both error counters; clear wins over events on its edge.
	 */
	override fn ClearErrors()
	{
		errorClearRequest = !errorClearRequest

		while (errorClearCompleted != errorClearRequest) {
			state {
			}
		}
	}

	/**
	 * Presents combinational FIFO requests; clear suppresses other operations.
	 */
	process Requests[]()
	{
		this.receiveCount = rxCount
		txClear = txClearRequest != txClearCompleted
		rxClear = rxClearRequest != rxClearCompleted
		txPush = !txClear && txRequest != txCompleted
		rxPop = !rxClear && rxRequest != rxCompleted
		rxPush = !rxClear && rxValid == 0b1
		rxPayload = rxData as byte
		txPop = !txClear && launchPermission && !launchPending && txActive == 0b0 && txCount > 0
	}

	/**
	 * Captures API outcomes and launch bytes exactly at the FIFO transfer edge.
	 */
	process Transfers()
	{
		state Tick {
			if (txClear) {
				txClearCompleted = txClearRequest
			} elif (txPush) {
				txAccepted = txPushAccepted
				txCompleted = txRequest
			}

			if (rxClear) {
				rxClearCompleted = rxClearRequest
			} elif (rxPop) {
				rxAccepted = rxPopAccepted
				rxResult = rxHead
				rxCompleted = rxRequest
			}

			txValid = 0b0

			if (txPopAccepted) {
				txData = txHead as logic[8]
				txValid = 0b1
				launchPending = true
			} elif (txActive == 0b1) {
				launchPending = false
			}

			goto Tick
		}
	}

	/**
	 * Owns both counters; rejected reception during explicit clear is intentional.
	 */
	process Errors()
	{
		state Tick {
			if (errorClearRequest != errorClearCompleted) {
				frameErrors = 0
				overflows = 0
				errorClearCompleted = errorClearRequest
			} else {
				if (rxError == 0b1) {
					frameErrors++
				}

				if (rxPush && !rxPushAccepted) {
					overflows++
				}
			}

			goto Tick
		}
	}
}