Livt packages

Livt.Usb

Version 0.0.5

USB 2.0 low/full-speed PHY, host transfers, device discovery, HID keyboard, and read-only mass storage for Livt.

Published files (57)

src/packets/PacketTransmitter.lvt

namespace Livt.Usb.Packets

using Livt.IO
using Livt.Usb
using Livt.Utils.Hashing

/**
 * Builds complete packets from a staged payload or token/handshake command.
 * Single-clock signal handshakes: loadValid/loadReady appends, request/ready
 * submits. No load is accepted with clear or request asserted. Staging is frozen
 * during transmission. Completion follows PHY busy falling, not last-byte acceptance.
 * The PHY must pace bytes as IUsb2Phy does (at least 64 clocks between accepts).
 * PIPELINE_OUTPUT separates byte publication from phase advancement by one edge;
 * the default false publishes from the updated phase without that extra edge.
 */
component PacketTransmitter<CAPACITY: int = 64, STYLE: MemoryStyle = MemoryStyle.Block,
	B: ISynchronousRam<byte, uint, RamGeometry<CAPACITY>> & new() = SynchronousRam<byte, uint, CAPACITY, STYLE>,
	PIPELINE_OUTPUT: bool = false>
{
	static assert CAPACITY > 0 && CAPACITY <= 1023, "Packet capacity must be 1 through 1023"

	// Packet phases; fetch occurs before PID so an active wire stream never stalls.
	const IDLE: logic[4] = 0
	const FETCH: logic[4] = 1
	const PID: logic[4] = 2
	const PAYLOAD: logic[4] = 3
	const CRC_LOW: logic[4] = 4
	const CRC_HIGH: logic[4] = 5
	const TOKEN_LOW: logic[4] = 6
	const TOKEN_HIGH: logic[4] = 7
	const FINISH: logic[4] = 8

	// Owned storage/checksum helpers and borrowed PHY handshake.
	storage: B
	crc5: Crc<5, 0x05, true>
	crc16: Crc<16, 0x8005, true>
	public speed: in UsbSpeed
	public cancel: in bool
	public phyReady: in bool
	public phyBusy: in bool

	// Staging and in-flight state; RAM cells are not reset.
	phase: logic[4]
	count: logic[11]
	index: logic[10]
	savedPid: byte
	tokenWord: logic[16]
	checksum: logic[16] = PacketIntegrity.DATA_INITIAL
	payload: byte
	sampledSpeed: UsbSpeed

	// One owner publishes byte and valid together through live combinational outputs.
	wireData: byte
	wireValid: bool

	// Application/transaction owner controls and admission signals.
	public clear: in bool
	public loadValid: in bool
	public loadData: in byte
	public loadReady: out bool
	public stagedLength: out uint
	public request: in bool
	public pid: in byte
	public token: in logic[11]
	public ready: out bool
	public busy: out bool
	public completed: out bool
	public rejected: out bool

	// Wire byte stream: PID and CRC included, SYNC/EOP supplied by PHY.
	public txValid: out bool
	public txData: out byte

	/**
	 * Binds a shared clock; cancel must also cancel the attached PHY.
	 */
	new()
	{
		storage = new B()
		crc5 = new Crc<5, 0x05, true>()
		crc16 = new Crc<16, 0x8005, true>()
	}

	/**
	 * Drives admission, memory prefetch and a continuous stream until intentional EOP.
	 */
	process Ports[]()
	{
		var enabled = !cancel && this.context.rst == 0b0
		ready = enabled && phase == IDLE && !clear && !phyBusy
		loadReady = ready && !request && (count as uint) < (CAPACITY as uint)
		stagedLength = count as uint
		busy = phase != IDLE && enabled
		txValid = wireValid && enabled
		txData = wireData

		storage.enable = false
		storage.writeEnable = false
		storage.address = 0
		storage.writeData = loadData

		if (enabled) {
			if (loadValid && loadReady) {
				storage.enable = true
				storage.writeEnable = true
				storage.address = count as uint
			} elif (request && ready && PacketPid.IsData(pid) && count != 0) {
				storage.enable = true
			} elif ((phase == PID || phase == PAYLOAD) && PacketPid.IsData(savedPid)
				&& (index as uint) + 1 < (count as uint)) {
				storage.enable = true
				storage.address = (index as uint) + 1
			}
		}
	}

	/**
	 * Captures requests and advances CRC only on an accepted payload byte.
	 */
	process Transmit()
	{
		state {
			var outputPhase = phase
			var outputPid = savedPid
			var outputPayload = payload
			var outputChecksum = checksum
			var outputToken = tokenWord
			completed = false
			rejected = false

			if (cancel || (phase != IDLE && speed != sampledSpeed)) {
				phase = IDLE
				count = 0
			} elif (phase == IDLE) {
				if (clear) {
					count = 0
				} elif (request && ready) {
					var supported = PacketPid.IsToken(pid) || PacketPid.IsData(pid) || PacketPid.IsHandshake(pid)

					if (!supported || (speed != UsbSpeed.Full && speed != UsbSpeed.Low)
						|| (speed == UsbSpeed.Low && (pid == PacketPid.SOF || (PacketPid.IsData(pid) && (count as uint) > PacketIntegrity.LOW_SPEED_PAYLOAD_LIMIT)))) {
						rejected = true
					} else {
						savedPid = pid
						sampledSpeed = speed
						index = 0
						checksum = PacketIntegrity.DATA_INITIAL
						var check = crc5.UpdateBits(PacketIntegrity.TOKEN_INITIAL, token as uint, PacketIntegrity.TOKEN_BITS)
							^ PacketIntegrity.TOKEN_FINAL_XOR
						tokenWord = TokenCodec.Pack(token, check)
						phase = PID

						if (PacketPid.IsData(pid) && count != 0) {
							phase = FETCH
						}
					}
				} elif (loadValid && loadReady) {
					count++
				}
			} elif (phase == FETCH) {
				payload = storage.readData
				phase = PID
			} elif (phase == FINISH) {
				if (!phyBusy) {
					completed = true
					phase = IDLE

					if (PacketPid.IsData(savedPid)) {
						count = 0
					}
				}
			} elif (txValid && phyReady) {
				match phase
				{
					PID =>
					{
						phase = FINISH

						if (PacketPid.IsToken(savedPid)) {
							phase = TOKEN_LOW
						} elif (PacketPid.IsData(savedPid)) {
							phase = CRC_LOW

							if (count != 0) {
								phase = PAYLOAD
							}
						}
					}
					PAYLOAD =>
					{
						checksum = crc16.UpdateByte(checksum, payload)
						payload = storage.readData
						index++

						if ((index as uint) >= (count as uint)) {
							phase = CRC_LOW
						}
					}
					CRC_LOW =>
					{
						phase = CRC_HIGH
					}
					TOKEN_LOW =>
					{
						phase = TOKEN_HIGH
					}
					_ =>
					{
						phase = FINISH
					}
				}
			}

			static if (!PIPELINE_OUTPUT) {
				outputPhase = phase
				outputPid = savedPid
				outputPayload = payload
				outputChecksum = checksum
				outputToken = tokenWord
			}

			wireValid = !cancel && outputPhase >= PID && outputPhase <= TOKEN_HIGH
			wireData = outputPid

			match outputPhase
			{
				PAYLOAD =>
				{
					wireData = outputPayload
				}
				CRC_LOW =>
				{
					wireData = ((outputChecksum ^ PacketIntegrity.DATA_FINAL_XOR) & 0xFF) as byte
				}
				CRC_HIGH =>
				{
					wireData = ((outputChecksum ^ PacketIntegrity.DATA_FINAL_XOR) >> 8) as byte
				}
				TOKEN_LOW =>
				{
					wireData = (outputToken & 0xFF) as byte
				}
				TOKEN_HIGH =>
				{
					wireData = (outputToken >> 8) as byte
				}
				_ => {}
			}
		}
	}
}