Livt packages

Livt.Net

Version 1.4.1

Ethernet, IPv4, TCP, UDP, ARP, TFTP, and Telnet networking components for Livt hardware.

Published files (163)

src/drivers/ethernetlite/EthernetLiteDriver.lvt

namespace Livt.Net.Drivers.EthernetLite

using Livt.Net

/**
 * Polled ping/pong RX and ping TX EthernetLite implementation.
 * Owns transaction sequencing, register addresses and byte packing; borrowed
 * receiver/transmitter components own packet and submission lifetimes.
 * All components and the external slave share coordinated reset. AXI stalls
 * have no timeout: reset the whole graph to recover from a nonresponsive slave.
 */
component EthernetLiteDriver<B: IEthernetLiteRegisterAccess, S: IPacketData, RX_CAPACITY: int = 128, MAX_TX_LENGTH: int = 1514>
{
	// Device register map; standard untagged frames fit below all control registers.
	const TX_LENGTH: logic[13] = 0x07F4
	const TX_CONTROL: logic[13] = 0x07FC
	const RX_PING: int = 0x1000
	const RX_PONG: int = 0x1800
	const RX_CONTROL_OFFSET: int = 0x07FC

	// Borrowed register access and owned receive-prefix scanner.
	bus: B
	window: EthernetLiteReceiveWindow<RX_CAPACITY>
	mac: byte[6]

	// Borrowed capabilities and driver initialization/recovery state.
	receiver: EthernetLiteReceiver<RX_CAPACITY>
	transmitter: EthernetLiteTransmitter<S, MAX_TX_LENGTH>
	initialized: bool
	failed: bool
	recover: bool
	pollPong: bool

	/**
	 * Binds independent RX/TX ownership components at the application boundary.
	 * MAC programming must complete before transmission admission is enabled.
	 */
	new(bus: B, receiver: EthernetLiteReceiver<RX_CAPACITY>, transmitter: EthernetLiteTransmitter<S, MAX_TX_LENGTH>, localMac: byte[6])
	{
		this.bus = bus
		this.window = new EthernetLiteReceiveWindow<RX_CAPACITY>()
		this.receiver = receiver
		this.transmitter = transmitter
		this.mac = localMac
	}

	/**
	 * Reports completed MAC programming and absence of a latched bus failure.
	 */
	public fn IsInitialized() bool
	{
		return this.initialized && !this.failed
	}

	/**
	 * Reports a latched device/transaction failure; source failures only affect TX.
	 */
	public fn HasDeviceError() bool
	{
		return this.failed
	}

	/**
	 * Requests reinitialization after a completed error response, not an AXI abort.
	 * The owner separately acknowledges RX errors and TX completion before reuse.
	 */
	public fn TryRecover() FrameLinkResult
	{
		if (!this.failed) {
			return FrameLinkResult.NotReady
		}

		if (this.recover) {
			return FrameLinkResult.Busy
		}

		this.recover = true
		return FrameLinkResult.Accepted
	}

	/**
	 * Programs the MAC only while TX is idle, then waits for both command bits clear.
	 */
	fn Initialize() bool
	{
		var status: logic[32] = 0x00000000

		if (!this.bus.Read(TX_CONTROL, status)) {
			return false
		}

		if (status[0] == 0b1 || status[1] == 0b1) {
			return true
		}

		for (var index = 0; index < 2; index++) {
			var word: logic[32] = 0x00000000

			for (var lane = 0; lane < 4; lane++) {
				var offset = (index << 2) + lane

				if (offset < 6) {
					var octet = this.mac[offset] as logic[8]

					word = word | (((octet as uint) as logic[32]) << (lane << 3))
				}
			}

			if (!this.bus.Write((index << 2) as logic[13], word)) {
				return false
			}
		}

		if (!this.bus.Write(TX_CONTROL, 0x00000003)) {
			return false
		}

		while (true) {
			if (!this.bus.Read(TX_CONTROL, status)) {
				return false
			}

			if (status[0] == 0b0 && status[1] == 0b0) {
				break
			}
		}

		this.transmitter.SetDeviceReady(true, false)
		this.initialized = true
		return true
	}

	/**
	 * Writes initialized source bytes with zero-filled final lanes. Delivered means
	 * the start-command write received OKAY, not physical transmission or peer receipt.
	 */
	fn Transmit() bool
	{
		var status: logic[32] = 0x00000000

		if (!this.bus.Read(TX_CONTROL, status)) {
			return false
		}

		if (status[0] == 0b1 || status[1] == 0b1) {
			return true
		}

		var length = this.transmitter.GetPendingLength()

		for (var index = 0; index < length; index = index + 4) {
			var word: logic[32] = 0x00000000

			for (var lane = 0; lane < 4; lane++) {
				if (index + lane < length) {
					var value: byte = 0x00
					var result = this.transmitter.TryReadPending(index + lane, value)

					if (result != PacketDataResult.Success) {
						this.transmitter.Complete(FrameTransmitOutcome.DeviceFailure)
						return true
					}

					word = word | (((value as uint) as logic[32]) << (lane << 3))
				}
			}

			if (!this.bus.Write(index as logic[13], word)) {
				return false
			}
		}

		if (!this.bus.Write(TX_LENGTH, length as logic[32])) {
			return false
		}

		if (!this.bus.Write(TX_CONTROL, 0x00000001)) {
			return false
		}

		this.transmitter.Complete(FrameTransmitOutcome.Delivered)
		return true
	}

	/**
	 * Alternates RX banks and copies only before publication. CRC lookahead excludes
	 * short-frame FCS/stale RAM without interpreting untrusted protocol lengths.
	 */
	fn Receive() bool
	{
		if (!this.receiver.TryBeginCapture()) {
			return true
		}

		var base = RX_PING

		if (this.pollPong) {
			base = RX_PONG
		}

		this.pollPong = !this.pollPong
		var control = (base + RX_CONTROL_OFFSET) as logic[13]
		var status: logic[32] = 0x00000000

		if (!this.bus.Read(control, status)) {
			return false
		}

		if (status[0] == 0b0) {
			this.receiver.AbandonCapture()
			return true
		}

		this.window.Reset()
		var index = 0

		while (!this.window.IsFinished()) {
			var word: logic[32] = 0x00000000

			if (!this.bus.Read((base + index) as logic[13], word)) {
				return false
			}

			for (var lane = 0; lane < 4; lane++) {
				if (!this.window.IsFinished()) {
					var value = (word >> (lane << 3)) as logic[8]

					if (index + lane < RX_CAPACITY) {
						if (!this.receiver.TryAppend(value as byte)) {
							return false
						}
					}

					this.window.AddByte(value as byte)
				}
			}

			index = index + 4
		}

		if (!this.bus.Write(control, 0x00000000)) {
			return false
		}

		var length = this.window.GetLength()
		return this.receiver.TryPublish(length)
	}

	/**
	 * Serializes hardware access while RX ownership and TX completion remain independent.
	 * A device failure stops new work until explicit recovery or coordinated reset.
	 */
	process Service()
	{
		if (this.failed) {
			if (!this.recover) {
				continue
			}

			this.recover = false
			this.failed = false
			this.initialized = false
			this.transmitter.SetDeviceReady(false, false)
		}

		var success = true

		if (!this.initialized) {
			success = this.Initialize()
		} elif (this.transmitter.IsSourceBorrowed()) {
			success = this.Transmit()
		} else {
			success = this.Receive()
		}

		if (!success) {
			// Publish capability errors before the driver flag, and the TX terminal
			// result last. Observers must never see completion before error admission.
			this.transmitter.SetDeviceReady(false, true)
			this.receiver.Fail()
			this.failed = true
			this.transmitter.Complete(FrameTransmitOutcome.DeviceFailure)
		}
	}
}