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/RootPort.lvt
namespace Livt.Usb.Host
using Livt.Usb
/**
* One directly attached USB port with context-derived timing. Debounces for 100 ms,
* drives reset for 12 ms and permits transfers after 10 ms recovery. Frame time
* runs during recovery. SE0 lasting 2.5 us outside host drive disconnects the port.
*/
component RootPort<DEBOUNCE_US: int = 100000, RESET_US: int = 12000,
RECOVERY_US: int = 10000>
{
static assert DEBOUNCE_US > 0 && RESET_US > 0 && RECOVERY_US > 0, "Port durations must be positive microseconds"
static assert DEBOUNCE_US <= 1000000 && RESET_US <= 1000000 && RECOVERY_US <= 1000000,
"Port durations must not exceed one second"
static assert DEBOUNCE_US + RESET_US + RECOVERY_US <= 1000000, "Combined port durations must not exceed one second"
// Exact time units avoid rounded context.periodNs in TicksFor.
const CLOCK_HZ: uint = this.context.TicksPer(1Hz)
const MICROSECOND_TICKS: uint = this.context.TicksPer(1MHz)
const DEBOUNCE_TICKS: uint = MICROSECOND_TICKS * (DEBOUNCE_US as uint)
const RESET_TICKS: uint = MICROSECOND_TICKS * (RESET_US as uint)
const RECOVERY_TICKS: uint = MICROSECOND_TICKS * (RECOVERY_US as uint)
const FRAME_TICKS: uint = this.context.TicksPer(1000Hz)
const DISCONNECT_TICKS: uint = (MICROSECOND_TICKS * 5 + 1) / 2
const COUNT_BITS: int = Bits.RequiredFor(DEBOUNCE_TICKS + RESET_TICKS + RECOVERY_TICKS - 1)
const FRAME_BITS: int = Bits.RequiredFor(FRAME_TICKS - 1)
const DISCONNECT_BITS: int = Bits.RequiredFor(DISCONNECT_TICKS)
// Clock counts and physical observations; reset is a toggle mailbox.
public line: in logic[2]
public driving: in bool
public resetRequest: in bool
public speed: out UsbSpeed
public busReset: out bool
public connected: out bool
public frame: out logic[16]
public frameTick: out bool
public frameRemaining: out uint
phase: logic[3]
count: logic[COUNT_BITS]
se0: logic[DISCONNECT_BITS]
candidate: logic[2]
selected: UsbSpeed
resetSeen: bool
frameCount: logic[16]
frameCycle: logic[FRAME_BITS]
/**
* Checks the exact whole-microsecond timing scale in the inherited context.
*/
new()
{
assert CLOCK_HZ % 1000000 == 0, "Root-port timing requires an integer-MHz context"
}
/**
* Publishes link status; connected means reset recovery has finished.
*/
process Ports[]()
{
speed = selected
busReset = phase == 2
connected = phase == 4
frame = frameCount
frameRemaining = FRAME_TICKS - (frameCycle as uint)
frameTick = phase >= 3 && (frameCycle as uint) == FRAME_TICKS - 1
}
/**
* Owns attach/reset/disconnect state independently of scheduled clients.
*/
process Observe()
{
state {
if (phase >= 3) {
if ((frameCycle as uint) == FRAME_TICKS - 1) {
frameCycle = 0
frameCount++
} else {
frameCycle++
}
} else {
frameCycle = 0
frameCount = 0
}
if (phase >= 3 && !driving && line == 0) {
if ((se0 as uint) < DISCONNECT_TICKS) { se0++ }
} else {
se0 = 0
}
if (CLOCK_HZ % 1000000 != 0 || resetRequest != resetSeen || (se0 as uint) == DISCONNECT_TICKS) {
resetSeen = resetRequest
phase = 0
count = 0
selected = UsbSpeed.Unknown
} else {
match phase
{
0 =>
{
if (line == 1 || line == 2) {
candidate = line
count = 0
phase = 1
}
}
1 =>
{
if (line != candidate) {
phase = 0
} elif ((count as uint) == ((DEBOUNCE_TICKS - 1) as uint)) {
selected = UsbSpeed.Full
if (candidate == 1) { selected = UsbSpeed.Low }
phase = 2
count = 0
} else { count++ }
}
2 =>
{
if ((count as uint) == ((RESET_TICKS - 1) as uint)) {
phase = 3
count = 0
} else { count++ }
}
3 =>
{
if ((count as uint) == ((RECOVERY_TICKS - 1) as uint)) { phase = 4 } else { count++ }
}
_ => {}
}
}
}
}
}