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/ethernet/MiiAdapter.lvt

namespace Livt.IO

/**
 * Full-duplex MII media adapter: 50 MHz MAC context and independent PHY-derived
 * RX/TX contexts (25 MHz at 100 Mb/s, 2.5 MHz at 10 Mb/s). Both rates use identical
 * nibble logic. Owns two block-memory FIFOs. TX buffers one complete wire frame
 * before commit; completion means TX_EN has fallen, not merely FIFO admission.
 * All three resets must assert together, asynchronously, and release locally.
 * Board constraints must cover FIFO Gray buses and mailbox synchronizers.
 * Committed discard metadata remains immutable until acknowledgement; a late
 * abort changes only the MAC-domain completion result.
 */
@Context(ClockFrequency = 50MHz, ResetStyle = ResetStyle.Asynchronous)
component MiiAdapter : IEthernetMedia
{
	// FIFO token flag marking the final TX byte or RX terminator.
	const FRAME_END: logic[11] = 0x200

	// FIFO token flag marking the first RX byte.
	const FRAME_START: logic[11] = 0x100

	// FIFO token flag marking a malformed RX frame.
	const FRAME_ERROR: logic[11] = 0x400

	// Reserve one of 2048 FIFO entries when committing or aborting a frame.
	const LAST_BUFFER_INDEX: logic[11] = 2046

	// Borrowed PHY clock contexts; both resets assert with the MAC reset.
	@Context(ResetStyle = ResetStyle.Asynchronous)
	receiveDomain: IContext

	// Borrowed PHY clock contexts; both resets assert with the MAC reset.
	@Context(ResetStyle = ResetStyle.Asynchronous)
	transmitDomain: IContext

	// Borrowed physical pins and coherent link status.
	pins: IMiiPins
	link: IEthernetLinkStatus

	// Owned receive/serialize engines and block-memory clock bridges.
	receive: MiiReceiver
	transmit: MiiTransmitter
	completion: MiiCompletionReceiver
	rxQueue: MiiClockBridge<2048>
	txQueue: MiiClockBridge<2048>

	// Complete-frame mailbox and producer occupancy.
	commit: bool
	discard: bool
	busy: bool
	count: logic[11]

	// MAC-domain failure after commit; never sampled by the PHY domain.
	lateAbort: bool

	// Four application cycles preserve the existing MAC synchronous-RAM byte cadence.
	byteSlot: logic[2]

	// Coherent completion mailbox and one-cycle MAC terminal events.
	terminal: bool
	terminalFault: bool

	// Combinational MAC input aliases and FIFO admission signals.
	inputData: byte
	inputValid: bool
	inputFirst: bool
	inputLast: bool
	inputAbort: bool
	accept: bool
	abortFrame: bool
	enabled: bool
	push: bool
	token: logic[11]

	/**
	 * Borrows pins, link status, and media clocks; keeps MAC work in this context.
	 */
	new(macContext: IContext, receiveContext: IContext, transmitContext: IContext,
		pins: IMiiPins, link: IEthernetLinkStatus)
	{
		context = macContext
		receiveDomain = receiveContext
		transmitDomain = transmitContext
		this.pins = pins
		this.link = link
		rxQueue = new MiiClockBridge<2048>(receiveDomain, context, receive.valid, receive.data, true)
		txQueue = new MiiClockBridge<2048>(context, transmitDomain, push, token, transmit.ready)
		receive = new MiiReceiver(receiveDomain, pins.rxData, pins.rxValid, pins.rxError, rxQueue.writeReady)
		transmit = new MiiTransmitter(transmitDomain, commit, discard,
			txQueue.readValid, txQueue.readData)
		completion = new MiiCompletionReceiver(context, transmit)
	}

	/**
	 * Wires FIFO transfers and exposes one-cycle terminal events in the MAC domain.
	 */
	process Ports[]()
	{
		pins.txData = transmit.txData
		pins.txEnable = transmit.txEnable
		inputData = txData
		inputValid = txValid
		inputFirst = txFirst
		inputLast = txLast
		inputAbort = txAbort
		enabled = link.ready && (link.speed == EthernetSpeed.Mbps100
			|| link.speed == EthernetSpeed.Mbps10)
		txReady = enabled && !busy && !inputAbort && txQueue.writeReady && byteSlot == 0
		accept = enabled && !busy && !inputAbort && txQueue.writeReady && byteSlot == 0 && inputValid
		abortFrame = !busy && count != 0 && (inputAbort || !enabled)
		push = accept || (abortFrame && txQueue.writeReady)
		var nextToken = inputData as logic[11]

		if (inputLast || abortFrame || count == LAST_BUFFER_INDEX) {
			nextToken = nextToken | FRAME_END
		}

		token = nextToken
		txDone = terminal && !terminalFault
		txFault = terminal && terminalFault
		rxData = rxQueue.readData as byte
		rxValid = enabled && rxQueue.readValid && (rxQueue.readData & FRAME_END) == 0
		rxStart = enabled && rxQueue.readValid && (rxQueue.readData & FRAME_START) != 0
		rxEnd = rxQueue.readValid && (rxQueue.readData & FRAME_END) != 0
		rxError = rxQueue.readValid && ((rxQueue.readData & FRAME_ERROR) != 0 || !enabled)
	}

	/**
	 * Commits complete frames and retains metadata until the remote acknowledgement.
	 */
	process Frames()
	{
		state {
			byteSlot++
			terminal = false

			if (busy) {
				// Keep discard frozen while the PHY consumes the committed frame.
				if (!enabled || inputAbort) {
					lateAbort = true
				}

				if (completion.acknowledged == commit) {
					busy = false
					count = 0
					terminal = true
					terminalFault = completion.fault || discard || lateAbort
					discard = false
					lateAbort = false
				}
			} elif (abortFrame && txQueue.writeReady) {
				discard = true
				commit = !commit
				busy = true
			} elif (accept) {
				if ((count == 0 && !inputFirst) || (count != 0 && inputFirst)) {
					discard = true
				}

				if (inputLast || count == LAST_BUFFER_INDEX) {
					discard = discard || !inputLast
					commit = !commit
					busy = true
				}

				count++
			}

		}
	}
}