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/host/TransactionEngine.lvt
namespace Livt.Usb.Host
using Livt.IO
using Livt.Usb
using Livt.Usb.Packets
/**
* Cycle-level transaction and frame arbiter, independent of software-style calls.
* clearRequest, loadRequest and request are toggle mailboxes; hold each payload
* until the matching Accepted output equals its request. Stage OUT bytes first;
* every attempt consumes staging. completed is retained until the next acceptance.
* Valid IN data is ACKed here before any scheduled payload access; duplicates are
* ACKed but reported separately. One retained RX slot is released on next request.
* All bindings share one clock/reset context. PIPELINE_LINK adds coherent receive,
* ACK, frame-admission and transmit stages; false selects the direct path.
* frameRemaining counts down once per edge and wraps at frameTick. The PHY must
* leave at least 64 clocks between byte acceptances in either configuration.
*/
component TransactionEngine<STYLE: MemoryStyle = MemoryStyle.Block,
B: ISynchronousRam<byte, uint, RamGeometry<64>> & new() = SynchronousRam<byte, uint, 64, STYLE>,
PIPELINE_LINK: bool = true>
{
// Physical durations use exact context rates to avoid TicksFor clock-period rounding.
const CLOCK_HZ: uint = this.context.TicksPer(1Hz)
const FULL_BIT_TICKS: uint = this.context.TicksPer(12MHz)
const LOW_BIT_TICKS: uint = this.context.TicksPer(1500KHz)
const MICROSECOND_TICKS: uint = this.context.TicksPer(1MHz)
const FRAME_GUARD_TICKS: uint = MICROSECOND_TICKS * 200
const RESPONSE_TICKS: uint = MICROSECOND_TICKS * 128
const HANDSHAKE_TICKS: uint = LOW_BIT_TICKS * 32
const GAP_TICKS: uint = MICROSECOND_TICKS * 2
const RESPONSE_BITS: int = Bits.RequiredFor(RESPONSE_TICKS)
const GAP_BITS: int = Bits.RequiredFor(GAP_TICKS)
// Independent packet stores and cycle-level link context.
rx: PacketReceiver<64, STYLE, B>
tx: PacketTransmitter<64, STYLE, B, PIPELINE_LINK>
public speed: in UsbSpeed
public connected: in bool
public cancel: in bool
public frameTick: in bool
public frame: in logic[16]
public frameRemaining: in uint
public phyReady: in bool
public phyBusy: in bool
public rxActive: in bool
public rxValid: in bool
public rxData: in byte
public rxError: in bool
public txValid: out bool
public txData: out byte
public keepAlive: out bool
public keepReady: in bool
// Client payload and command handshakes, held stable through acceptance.
public clearRequest: in bool
public clearAccepted: out bool
clearSeen: bool
public loadRequest: in bool
public loadAccepted: out bool
loadSeen: bool
public loadData: in byte
public loadReady: out bool
public request: in bool
public requestAccepted: out bool
public phaseCode: out byte
acceptedRequest: bool
public tokenPid: in byte
public token: in logic[11]
public dataOne: in bool
public maxLength: in uint
public ready: out bool
public completed: out bool
public acknowledged: out bool
public result: out TransactionResult
public length: out uint
public readRequest: in bool
public readAddress: in uint
public readData: out byte
public readValid: out bool
// Exchange phases and retained command; one owner arbitrates all transmit work.
const IDLE: logic[4] = 0
const TOKEN: logic[4] = 1
const DATA: logic[4] = 2
const RESPONSE: logic[4] = 3
const ACK: logic[4] = 4
const FRAME: logic[4] = 5
const KEEP: logic[4] = 6
const KEEP_WAIT: logic[4] = 7
const ACK_REQUEST: logic[4] = 8
phase: logic[4]
savedPid: byte
savedOne: bool
savedLimit: uint
responseCycles: logic[RESPONSE_BITS]
responseStarted: bool
startLimit: uint
pendingFrame: bool
gap: logic[GAP_BITS]
done: bool
outcome: TransactionResult
receivedLength: uint
// Optional coherent ingress, RX-slot release, frame admission and TX feedback.
linkSpeed: UsbSpeed
linkCancel: bool
linkActive: bool
linkValid: bool
linkData: byte
linkError: bool
releasePending: bool
frameOpen: bool
linkAccepted: bool
linkBusy: bool
/**
* Creates two 64-byte packet slots; the owner supplies a PHY in the same clock context.
*/
new()
{
assert CLOCK_HZ % 12000000 == 0, "USB transaction clock must be an exact multiple of 12 MHz"
rx = new PacketReceiver<64, STYLE, B>()
tx = new PacketTransmitter<64, STYLE, B, PIPELINE_LINK>()
}
/**
* Connects packets and immediate response requests. A 200 us frame guard
* admits only exchanges that fit before the next 1 ms frame boundary.
*/
process Ports[]()
{
static if (PIPELINE_LINK) {
rx.speed = linkSpeed
rx.cancel = linkCancel
rx.active = linkActive
rx.valid = linkValid
rx.data = linkData
rx.physicalError = linkError
rx.release = releasePending
} else {
rx.speed = speed
rx.cancel = cancel || !connected
rx.active = rxActive
rx.valid = rxValid
rx.data = rxData
rx.physicalError = rxError
rx.release = request != acceptedRequest && ready
}
rx.readRequest = readRequest
rx.readAddress = readAddress
readData = rx.readData
readValid = rx.readValid && !cancel
tx.speed = speed
tx.cancel = cancel
static if (PIPELINE_LINK) {
tx.phyReady = linkAccepted
tx.phyBusy = linkBusy
} else {
tx.phyReady = phyReady
tx.phyBusy = phyBusy
}
tx.clear = clearRequest != clearSeen && phase == IDLE && !tx.busy
tx.loadValid = loadRequest != loadSeen && phase == IDLE
tx.loadData = loadData
loadReady = tx.loadReady && phase == IDLE
startLimit = FULL_BIT_TICKS * 18
if (speed == UsbSpeed.Low) {
startLimit = LOW_BIT_TICKS * 18
}
var admitFrame: bool
static if (PIPELINE_LINK) {
admitFrame = frameOpen
} else {
admitFrame = frameRemaining > FRAME_GUARD_TICKS
}
ready = connected && !cancel && phase == IDLE && !pendingFrame
&& !frameTick && admitFrame && gap == 0 && tx.ready && !rxActive
tx.request = false
tx.pid = tokenPid
tx.token = token
keepAlive = phase == KEEP && !cancel
var ackCommand: bool
static if (PIPELINE_LINK) {
ackCommand = phase == ACK_REQUEST
} else {
ackCommand = phase == RESPONSE && rx.accepted && savedPid == PacketPid.IN
&& PacketPid.IsData(rx.pid) && rx.length <= savedLimit
}
if (phase == IDLE && pendingFrame && gap == 0 && tx.ready && !rxActive) {
if (speed == UsbSpeed.Full) {
tx.request = true
tx.pid = PacketPid.SOF
tx.token = frame as logic[11]
}
} elif (request != acceptedRequest && ready) {
tx.request = true
} elif (phase == TOKEN && tx.completed && savedPid != PacketPid.IN) {
tx.request = true
tx.pid = PacketPid.DATA0
if (savedOne) {
tx.pid = PacketPid.DATA1
}
} elif (ackCommand) {
tx.request = true
tx.pid = PacketPid.ACK
}
txValid = tx.txValid
txData = tx.txData
clearAccepted = clearSeen
loadAccepted = loadSeen
requestAccepted = acceptedRequest
phaseCode = phase as byte
completed = done
acknowledged = done && outcome == TransactionResult.Success && acceptedRequest == request
result = outcome
length = receivedLength
}
/**
* Samples coherent receive signals, accepted transmit bytes and frame admission.
* The frame countdown reserves one edge for sampling. Bypassed registers are
* unreferenced when PIPELINE_LINK is false and are removed by synthesis.
*/
process CaptureLink()
{
state {
linkSpeed = speed
linkCancel = cancel || !connected
linkActive = rxActive
linkValid = rxValid
linkData = rxData
linkError = rxError
// Ready may remain high while idle; retain only a real byte acceptance.
linkAccepted = phyReady && tx.txValid
linkBusy = phyBusy
// The root-port counter falls by one per edge; reserve that sampling edge.
frameOpen = frameRemaining > FRAME_GUARD_TICKS + 1
}
}
/**
* Bounds a response including wire payload to 128 us and keeps frame traffic
* alive while the scheduled client drains its retained completion.
*/
process Exchange()
{
state {
releasePending = false
if (cancel) {
// Cancellation cannot undo a completed wire ACK. Preserve its commit
// observation until the next command, including an ACK completing now.
if (phase == ACK && tx.completed && outcome == TransactionResult.Success) {
done = true
}
if (phase == RESPONSE && savedPid != PacketPid.IN && rx.completed && rx.accepted && rx.pid == PacketPid.ACK) {
outcome = TransactionResult.Success
done = true
}
if (!done || acceptedRequest != request) {
outcome = TransactionResult.Disconnected
receivedLength = 0
}
clearSeen = clearRequest
loadSeen = loadRequest
acceptedRequest = request
phase = IDLE
pendingFrame = false
done = true
gap = 0
} else {
if (tx.clear) {
clearSeen = clearRequest
}
if (tx.loadValid && tx.loadReady) {
loadSeen = loadRequest
}
match phase
{
IDLE =>
{
if (pendingFrame && gap == 0 && tx.ready && !rxActive) {
phase = FRAME
if (speed == UsbSpeed.Low) {
phase = KEEP
}
pendingFrame = false
} elif (request != acceptedRequest && ready) {
releasePending = true
acceptedRequest = request
savedPid = tokenPid
savedOne = dataOne
savedLimit = maxLength
done = false
outcome = TransactionResult.Pending
receivedLength = 0
phase = TOKEN
}
}
TOKEN =>
{
if (tx.completed) {
phase = DATA
if (savedPid == PacketPid.IN) {
phase = RESPONSE
}
responseCycles = 0
responseStarted = false
}
}
DATA =>
{
if (tx.completed) {
phase = RESPONSE
responseCycles = 0
responseStarted = false
}
}
RESPONSE =>
{
responseCycles++
if (rxActive) {
responseStarted = true
}
if (rx.completed) {
outcome = TransactionResult.ProtocolError
if (rx.accepted) {
if (rx.pid == PacketPid.NAK) {
outcome = TransactionResult.Nak
} elif (rx.pid == PacketPid.STALL) {
outcome = TransactionResult.Stall
} elif (savedPid != PacketPid.IN && rx.pid == PacketPid.ACK) {
outcome = TransactionResult.Success
} elif (savedPid == PacketPid.IN && PacketPid.IsData(rx.pid) && rx.length <= savedLimit) {
outcome = TransactionResult.Success
if ((rx.pid == PacketPid.DATA1) != savedOne) {
outcome = TransactionResult.Duplicate
}
receivedLength = rx.length
static if (PIPELINE_LINK) {
phase = ACK_REQUEST
} else {
phase = ACK
}
}
}
if (phase != ACK && phase != ACK_REQUEST) {
done = true
phase = IDLE
gap = (GAP_TICKS as int) as logic[GAP_BITS]
}
} elif ((!responseStarted && (responseCycles as uint) >= startLimit)
|| (savedPid == PacketPid.IN && (responseCycles as uint) >= RESPONSE_TICKS)
|| (savedPid != PacketPid.IN && (responseCycles as uint) >= HANDSHAKE_TICKS)) {
outcome = TransactionResult.Timeout
done = true
phase = IDLE
gap = (GAP_TICKS as int) as logic[GAP_BITS]
}
}
ACK_REQUEST =>
{
if (tx.ready) {
phase = ACK
}
}
ACK =>
{
if (tx.completed) {
done = true
phase = IDLE
gap = (GAP_TICKS as int) as logic[GAP_BITS]
}
}
FRAME =>
{
if (tx.completed) {
phase = IDLE;
gap = (GAP_TICKS as int) as logic[GAP_BITS]
}
}
KEEP =>
{
if (keepReady) {
phase = KEEP_WAIT
}
}
KEEP_WAIT =>
{
if (!phyBusy) {
phase = IDLE;
gap = (GAP_TICKS as int) as logic[GAP_BITS]
}
}
_ =>
{
phase = IDLE
}
}
}
if (frameTick && !cancel) {
pendingFrame = true
}
if (gap != 0) {
gap--
}
}
}
}