Livt.IO
Version 1.8.1
Memory, UART, I2C, SPI, and Ethernet input/output components for Livt hardware designs.
Livt.IO
Livt.IO provides reusable input/output components for the Livt base library.
It combines byte- and word-addressable memory, read-only block-device contracts,
UART serial I/O, Ethernet, and protocol bus helpers into one package so applications
can depend on Livt.IO instead of separate Ram or Uart packages.
See CHANGELOG.md for release notes and migration details.
Version 1.8.1 provides the following hardware components and contracts:
Livt.IO.IReadOnlyBlockDevice/BlockReadState: transport-independent, retained block reads with explicit cancellation and release.Livt.IO.SequentialRam<T, CAPACITY, STYLE>: compact scheduled storage requiring sequential calls; no internal arbitration. Defaults to block RAM.Livt.IO.Ram<T, CAPACITY, STYLE>: scheduled element-addressable memory implementingIRam<T>.Livt.IO.BlockRam<T, CAPACITY>/DistributedRam<T, CAPACITY>: inherited storage-style specializations.Livt.IO.SynchronousRam<T, ADDRESS, CAPACITY, STYLE>: one-edge read/write ports.Livt.IO.AsynchronousRam<T, ADDRESS, CAPACITY, STYLE>: combinational reads and clocked writes.Livt.IO.AsynchronousDistributedRam<T, ADDRESS, CAPACITY>: inherited distributed-style port core.ISynchronousRam/IAsynchronousRam: separate timing contracts;RamAccessaccepts a custom synchronous provider.Livt.IO.ArrayRam<T, CAPACITY>: scheduled, resettable logic-array storage implementingIRam<T>.Livt.IO.Ram16/Ram32: 2048-element block-style specializations.Livt.IO.AsynchronousDistributedRam32x16,AsynchronousDistributedRam32x32,AsynchronousDistributedRam8x64: inherited asynchronous specializations.Livt.IO.UartReceiver: compile-time-configurable UART receive block.Livt.IO.UartTransmitter: compile-time-configurable UART transmit block.Livt.IO.Uart: low-level combined RX/TX UART block with explicit signals.Livt.IO.IBufferedUart: common scheduled contract for buffered UART implementations.Livt.IO.BufferedUart: FIFO-backed application UART with configurable TX/RX capacities.Livt.IO.RtsCtsBufferedUart: buffered UART with active-low RTS/CTS flow control.Livt.IO.LoopbackUart: serial loopback wrapper that connects TX to RX.Livt.IO.I2CBus: open-drain I2C bus contract.Livt.IO.I2COpenDrainPins: adapter from physicalinoutpins toI2CBus.Livt.IO.I2CBusCombiner: wired-AND combiner for one controller and one target.Livt.IO.I2CMaster: byte-level standard-mode I2C master. Version 1.3.0 adds consumer-selected frequency and clock-stretch timeout; see I2C timing and recovery.Livt.IO.I2CSlave: byte-event standard-mode I2C target.Livt.IO.I2CRegisterSlave: 256-byte register-file helper for I2C targets.Livt.IO.EthernetReceiver/EthernetTransmitter: RAM-backed Ethernet frame reception and transmission.Livt.IO.RmiiAdapter<ENABLE_10>: full-duplex RMII media adapter; 100 Mbit/s by default, optional 10 Mbit/s.Livt.IO.MiiAdapter: full-duplex 10/100 Mbit/s MII with independent RX/TX clocks.Livt.IO.MiiCompletionReceiver: user-implemented PHY-to-MAC completion CDC using Livt'scrossmarker.Livt.IO.Phy.Dp83848Phy: DP83848 identification, MII configuration, and 10/100-full link management.Livt.IO.MdioController: serialized Clause 22 PHY register access.Livt.IO.Phy.Rtl8201Phy/Rtl8211Phy: PHY configuration and link monitoring.Livt.IO.SPIBus: push-pull, single-data-lane SPI bus contract.Livt.IO.SPIMaster: context-timed, byte-level SPI Mode 0 controller.Livt.IO.ISPITransactions/SPITransactions: scheduled, exclusive SPI transactions.Livt.IO.ISPIFlash/SPIFlash: SPI NOR identification, reads, page programming and sector erase with selectable staging storage.
📦 Package
[dependencies]
"Livt.IO" = "1.8.1"
Livt.IO is part of the official Livt base library package set. New packages
should depend on Livt.IO; Livt.IO supersedes the standalone Ram and Uart packages for new code.
New in 1.8.0
MiiCompletionReceiver encapsulates the MII transmitter-to-MAC completion
crossing. It uses Livt's cross keyword at the two intentional raw boundaries,
then implements the completion protocol in Livt: the acknowledgement passes
through two MAC-domain sampling stages and the matching held fault payload is
published with it on the following edge.
MiiAdapter owns this receiver, so applications continue to use the existing
media interface without handling the crossing themselves. The compiler does not
insert a second synchronization transaction for cross; physical placement,
timing constraints and CDC sign-off remain integration responsibilities. See
MII integration for the protocol and reset contract.
Existing consumer APIs are unchanged. Version 1.8.1 updates the
Livt.Collections dependency to 1.4.2 and publishes package documentation and
discovery metadata. Building this release requires a Livt compiler with cross
support.
📚 Namespaces
Livt.IO keeps public components in the root namespace for short, compatible
call sites. Protocol components use readable prefixes such as I2CMaster and
SPIMaster rather than nested protocol namespaces. Device-specific Ethernet PHY
managers live in Livt.IO.Phy; generic MAC, media and MDIO components remain
in Livt.IO.
| Component | Synthesizable | Purpose |
|---|---|---|
IReadOnlyBlockDevice / BlockReadState |
Contract | Read-only block lifecycle independent of transport and buffering |
SynchronousRam<T, ADDRESS, CAPACITY, STYLE> |
Yes | One enabled read or write per clock; registered read response |
AsynchronousRam<T, ADDRESS, CAPACITY, STYLE> |
Yes | Combinational read and one clocked write port |
AsynchronousDistributedRam<T, ADDRESS, CAPACITY> |
Yes | Inherited combinational-read core with Distributed intent |
Ram<T, CAPACITY, STYLE> |
Yes | Scheduled Read/Write over one portable storage port |
SequentialRam<T, CAPACITY, STYLE> |
Yes | Compact scheduled access when the caller serializes all operations |
BlockRam<T, CAPACITY>, DistributedRam<T, CAPACITY> |
Yes | Compile-time style specializations |
ArrayRam<T, CAPACITY> |
Yes | Scheduled logic-array storage with zeroed payload on reset |
RamAccess<T, ADDRESS, ELEMENT_COUNT, STORAGE> |
Yes | Scheduled access over an injected synchronous provider |
Ram16 |
Yes | Fixed 2048-word memory with 16-bit reads and writes |
Ram32 |
Yes | Fixed 2048-word memory with 32-bit reads and writes |
AsynchronousDistributedRam32x16 |
Yes | 16-word, 32-bit single-port distributed RAM |
AsynchronousDistributedRam32x32 |
Yes | 32-word, 32-bit single-port distributed RAM |
AsynchronousDistributedRam8x64 |
Yes | 64-byte single-port distributed RAM |
UartReceiver |
Yes | Serial RX with configurable data width, parity, and stop bits |
UartTransmitter |
Yes | Serial TX with configurable data width, parity, and stop bits |
Uart |
Yes | Combined RX/TX block with explicit handshake signals |
IBufferedUart |
Yes | Shared scheduled contract for buffered UART implementations |
BufferedUart |
Yes | FIFO-backed UART with configurable transmit and receive capacities |
RtsCtsBufferedUart |
Yes | Buffered UART with active-low RTS/CTS flow control |
LoopbackUart |
Yes | Buffered UART wrapper with internal TX-to-RX loopback |
I2CBus |
Yes | Open-drain I2C bus interface |
I2COpenDrainPins |
Yes | Physical scl/sda pin adapter |
I2CBusCombiner |
Yes | Combines controller and target drive-low requests |
I2CMaster |
Yes | Byte-level standard-mode I2C master |
I2CSlave |
Yes | Byte-event standard-mode I2C target |
I2CRegisterSlave |
Yes | 256-byte register-file helper built on I2CSlave |
EthernetReceiver<CAPACITY, PROMISCUOUS> |
Yes | Buffered frame reception, FCS validation, filtering and discard counters |
EthernetTransmitter<CAPACITY> |
Yes | Buffered frame transmission, padding, FCS and retained completion |
RmiiAdapter<ENABLE_10> |
Yes | RMII serialization and byte assembly; optional 10 Mbit/s mode |
MiiAdapter |
Yes | 10/100 Mbit/s full-duplex MII with complete-frame TX buffering and asynchronous FIFOs |
MiiCompletionReceiver |
Yes | Coherent user-implemented crossing of the held MII completion toggle and fault payload into the MAC context |
Phy.Dp83848Phy<PHY_ADDRESS, POLL> |
Yes | DP83848 identification, MII selection, negotiation and link monitoring |
MdioController |
Yes | Clause 22 MDIO register reads and writes |
Phy.Rtl8201Phy<PHY_ADDRESS, POLL> |
Yes | RTL8201F configuration and 100-full link monitoring |
Phy.Rtl8211Phy<PHY_ADDRESS, POLL> |
Yes | RTL8211F configuration, restart and 100-full link monitoring |
SPIBus |
Yes | Push-pull, single-data-lane SPI bus interface |
SPIMaster |
Yes | Byte-level SPI Mode 0 master with context-derived timing |
ISPITransactions / SPITransactions |
Yes | Scheduled select, byte transfer and deselect operations |
ISPIFlash / SPIFlash |
Yes | Single-lane SPI NOR commands with configurable geometry and staging storage |
🔌 API Overview
Read-only block storage
Bind an IReadOnlyBlockDevice provider through the consumer's constructor. Wait
for readiness, submit an in-range LBA with TryReadBlock(), and poll
GetReadState(). On Complete, read bytes with TryReadByte(); on Complete or
Failed, call ReleaseRead() before submitting another request.
CancelRead() requests cancellation and does not imply immediate completion.
Disconnect invalidates retained bytes. Media geometry comes from GetBlockCount()
and GetBlockSize(); the interface does not assume 512-byte sectors. Filesystems
and transport-specific drivers remain separate from this common contract. See
block storage for ownership, reset and provider rules.
Memory
Ram<byte, 64> is the ordinary scheduled API: Read(address),
Write(address, value), and IsValidAddress(address), through IRam<T>.
Capacity counts elements and must be positive. Invalid reads return zero;
invalid writes do nothing. Default capacity is 64. BlockRam<T> defaults to
2048 elements; DistributedRam<T> defaults to 64. Ram16 and Ram32
inherit 2048-element block-style RAM with 16-/32-bit logic-vector payloads.
For a resettable logic-array backend, use ArrayRam<T, CAPACITY> (default
capacity 64). It implements the same IRam<T> API and zeroes every cell on reset.
See memory storage choices.
The inferred RAM implementations are Livt source. Their cells are unspecified until written and survive reset. Reset cancels scheduled calls; it does not erase memory. Applications needing zeros must explicitly write them first.
Storage uses @Memory(Style=STYLE) and @UninitializedStorage.
Auto emits no placement hint; Block and Distributed emit direct synthesis
attributes. These are requests, not guarantees of physical RAM allocation.
A style choice does not change read timing.
For one-command-per-clock datapaths, use SynchronousRam directly. For
combinational reads, use AsynchronousRam. Their separate interfaces include
the payload type, explicit unsigned address type, and RamGeometry<CAPACITY>
identity. The scheduled Ram derives its own narrow address width; low-level
users supply an address type large enough for every index.
The fixed AsynchronousDistributedRam32x16, AsynchronousDistributedRam32x32,
and AsynchronousDistributedRam8x64 inherit AsynchronousDistributedRam,
which binds the Distributed hint on AsynchronousRam. They expose address,
boolean writeEnable, writeData, and readData directly; there is no
compatibility adapter. Wire ports in a combinational process when scheduled
code needs live observations.
BlockRam and DistributedRam remain scheduled APIs over synchronous storage.
“Asynchronous” in the new names identifies read timing; “Distributed” identifies
storage intent. Inheritance binds configuration without changing the access contract.
See usage and migration and
memory regression tests.
The former nongeneric Ram.ReadByte/WriteByte API and opaque InternalRam*
primitives are removed; this is an explicit API/startup migration.
Ethernet
Version 1.4.0 adds MiiAdapter and Phy.Dp83848Phy for 10/100 Mbit/s full-duplex
Ethernet. Existing MAC engines retain their frame APIs; the media adapter owns
nibble serialization and clock crossings. MII uses a 50 MHz MAC context and
separate PHY clocks at 25 MHz or 2.5 MHz. The board provides clock/reset wiring,
MDIO pads, and timing/CDC constraints.
See Ethernet contracts and MII integration for construction, ownership, reset and physical integration requirements.
UART
UART components derive bit timing from their component context. Every UART
component has a final BAUD: Frequency = 115200Hz value parameter, including
buffered, RTS/CTS, and loopback variants. Baud is structural configuration, so
it is part of the component type rather than a runtime constructor input:
new BufferedUart(rx, tx) // 115200 baud in the effective clock context
new BufferedUart<64, 64, UartDataBits.Eight, UartParity.None,
UartStopBits.One, 921600Hz>(rx, tx)
Frame format is also selected at compile time. The defaults remain 8-N-1. Data
width accepts UartDataBits.Five, Six, Seven, or Eight; parity accepts
UartParity.None, Even, or Odd; and stop width accepts
UartStopBits.One or Two:
uart: BufferedUart<64, 64, UartDataBits.Seven, UartParity.Even, UartStopBits.Two>
Formats narrower than eight bits transmit only the least-significant selected bits and zero-fill the unused most-significant bits on receive. Because these are component value parameters, static specialization removes unselected frame branches instead of adding runtime format selectors.
Named constants and forwarded const parameters are supported; runtime baud changes are not. See UART hardware notes and the baud configuration tests for timing guidance and executable examples.
UartReceiver pulses rx_dv for one cycle after a valid byte and pulses
rx_frame_error for one cycle after an invalid parity or stop bit. UartTransmitter
starts when tx_dv is pulsed, keeps tx_active high while a frame is in
flight, and pulses tx_done when transmission completes.
All buffered variants accept compile-time capacities, defaulting to 64 entries
each. BufferedUart owns the signal-level Uart plus two
Livt.Collections.Fifo<byte, CAPACITY> instances. LoopbackUart and
RtsCtsBufferedUart compose it and implement IBufferedUart, which gives the
compiler one common implementation contract. The wrappers delegate scheduled
methods with IBufferedUart by buffered. Applications can hold an IBufferedUart
reference when substitution is useful, or use a concrete component type directly.
Uart remains available for custom unbuffered compositions. The shared FIFO
dependency is declared in livt.toml as Livt.Collections version 1.4.2.
TryTransmit(data) boolreports FIFO acceptance, not wire completion.TryReceive(data: out byte) boolatomically removes a byte. Failure assigns zero; a successful zero byte remains distinguishable from an empty queue.IsTransmitting()reports an active physical frame.HasPendingTransmit()includes pending API requests, queued bytes, committed launches, and active frames.IsTransmitIdle()means no transmit work remains.GetReceiveCount()andGetTransmitSpace()are snapshots, not reservations.Send(data) intaccepts a prefix and returns its length. It stops at the first rejection; other producers may interleave. It is not an atomic message send.ClearReceiveBuffer()discards queued receive data.ClearTransmitBuffer()discards queued bytes, preserving an active frame or a byte already committed to launch.GetFrameErrorCount(),GetReceiveOverflowCount(), andClearErrors()expose and clear receive errors.
These are scheduled methods, not one-clock operations. They form the
IBufferedUart contract, including Send(byte[]); compile-time interface
delegation preserves its inferred maximum capacity and per-call logical length.
Use Uart for cycle-sensitive or custom unbuffered applications. See
hardware contracts.
RTS/CTS wrappers synchronize active-low ctsN before granting launch permission.
A committed or active frame finishes even if CTS changes. Active-low rtsN
uses capacity-derived hysteresis: reserve ceil(RX_CAPACITY / 8) entries,
stop at capacity minus that reserve, and resume one reserve below the stop level.
Defaults remain 56/48; receive capacity must be at least two for RTS/CTS.
Small buffers require a correspondingly prompt peer. Basic UART ties permission
active; elimination of unused flow-control logic still needs synthesis evidence.
Ethernet components
EthernetReceiver and EthernetTransmitter provide vendor-neutral buffered
Ethernet II framing. Each owns a synchronous RAM with configurable capacity
(default 1514 bytes). Reception validates length and FCS, filters destination
addresses and records discard reasons. Transmission supplies minimum-frame
padding, FCS and the inter-frame gap, then retains completion until acknowledged.
Application frame bytes exclude preamble, SFD and FCS.
IEthernetMedia separates the MAC engines from physical media adaptation.
RmiiAdapter implements 100 Mbit/s full-duplex RMII in a shared 50 MHz context.
MdioController provides serialized Clause 22 register access through split
input/output/output-enable pins. The integrating application supplies pads,
clocks, PHY reset sequencing and timing constraints.
Livt.IO.Phy.Rtl8201Phy and Livt.IO.Phy.Rtl8211Phy configure their respective
PHYs and expose readiness and speed through IEthernetLinkStatus. Both advertise
100 Mbit/s full duplex only. RTL8211F management also supports a restart toggle,
enables RGMII RX delay and disables TX delay; the external transmit path supplies
clock/data skew. PHY management does not include an RGMII media adapter.
RX and TX each have one frame slot, so link rate does not imply sustained application throughput. 10 Mbit/s operation, half-duplex, VLAN-tagged frames and jumbo frames are not supported end-to-end. IP and transport protocols belong to higher layers; Livt.Net provides adapters for these frame APIs.
See Ethernet contracts and constructors and RTL8211F management for ownership, configuration and integration requirements.
I2C
I2C support is byte-level and standard-mode. The 1.3.0 master accepts
I2CMaster<BUS_FREQUENCY: Frequency = 100000Hz, STRETCH_TIMEOUT: Time = 1ms>.
Timing derives from the consumer clock context; fixed CLOCK_HZ / I2C_HZ
constants are replaced by configuration and HalfPeriodTicks(). Version 1.2.0 used fixed 100 MHz / 100 kHz timing.
Clock-stretch timeout releases both lines and completes with HasTimedOut();
callers must check it before ACK/NACK or byte data. A new accepted command clears
the timeout. Automatic recovery clock pulses are not provided.
I2CBus models an open-drain bus attachment. The provider exposes observed
scl and sda levels, while devices request low drive through
scl_drive_low and sda_drive_low. I2CBusCombiner owns public
controller and target endpoints and wires them into one upstream adapter.
I2CMaster exposes asynchronous byte commands:
BeginStart(),BeginStop()BeginWriteByte(data)BeginWriteAddress(address),BeginReadAddress(address)BeginReadByte(sendAck)IsBusy(),HasResult(),HasTimedOut(),ClearResult()WasAckReceived(),WasNackReceived(),GetReadByte()
Address helpers take unshifted 7-bit addresses in the range 0x00..0x7F and
return false for invalid addresses.
I2CSlave exposes byte events:
HasReceivedByte(),GetReceivedByte(),ClearReceivedByte()SetTransmitByte(value)IsReadRequested(),ClearReadRequested()HasAddressMatch(),ClearAddressMatch()WasReadAddressed(),WasWriteAddressed()HasStopDetected(),ClearStopDetected()HasTransmittedByte(),WasTransmitAcked(),ClearTransmittedByte()
I2CRegisterSlave wraps I2CSlave with a 256-byte register map. The first
write byte selects the register pointer; following write bytes store values and
auto-increment the pointer. Read requests load the current register value, and
ACKed transmitted bytes advance the pointer for repeated multi-byte reads.
SetRegister(address, value),GetRegister(address)SetPointer(address),GetPointer(),HasPointer(),ClearPointer()AcceptWriteByte(value),PrepareReadByte(),GetCurrentRegister()HandleTransmittedByte(acked)HasWrittenRegister(),GetWrittenRegister(),GetWrittenValue(),ClearWrittenRegister()
SPI
SPIMaster implements single-data-lane SPI Mode 0 with MSB-first, full-duplex
byte transfers. Chip select is controlled separately so a command, address,
and payload can remain in one transaction:
BeginSelect()asserts the active-low chip select.BeginTransfer(data)exchanges one byte while chip select remains asserted.BeginDeselect()releases chip select after its hold interval.IsBusy(),IsSelected(),HasResult(), andClearResult()expose state.GetReceivedByte()returns the byte sampled during the last transfer.HalfPeriodTicks()returns the context-derived SCLK half-period.
Accepted commands complete asynchronously. A Begin* call returns false
when its preconditions are not satisfied and leaves the controller unchanged.
The default SCLK half-period is 50 ns. Pass a positive compile-time duration
when more timing margin is needed, for example new SPIMaster(bus, 100ns) for
a clock no faster than 5 MHz. SPIMaster converts the duration with
this.context.TicksFor(halfPeriod) during construction; it rounds up to whole
parent-context ticks. Account for device timing and registered adapter latency
when choosing the half-period. MISO is
sampled on the Mode-0 rising edge; the flash may begin changing it on the
following falling edge.
At startup and reset, the controller is idle and deselected: SCLK and MOSI are
low, chip select is high, and HasResult() is false. The first received byte is
0x00.
SPI NOR flash
SPITransactions owns an SPIMaster and provides scheduled Select(),
Transfer(value) and Deselect() calls through ISPITransactions. One caller
serializes complete transactions. Its default half-period is 100 ns and must
cover at least five ticks of the inherited context.
SPIFlash borrows a transaction provider through ISPITransactions and exposes
ISPIFlash: ReadJedecId(), Read(), SetByte(), ProgramPage(),
EraseSector() and GetByte(). Configure the device capacity (up to 16 MiB),
page/sector geometry and readiness timeout to match the NOR device.
Page staging defaults to block-style RAM. Select distributed storage through
MemoryStyle, or supply an IRam<byte> implementation such as ArrayRam for
resettable logic-array storage. Staging is initialized to zero on first use
after reset. Applications own partition boundaries, recovery and read-back
verification; a completed program operation alone does not verify stored bytes.
See SPI NOR flash for generic parameters, storage examples, timing requirements and limitations.
🧪 Build and Test
livt test
To force a clean regeneration without removing dependencies:
livt clean
livt test
Short examples live in docs/usage.md. Protocol details and
caveats live in Ethernet, RTL8211F management,
I2C, SPI, SPI NOR flash and
read-only block storage.
Hardware and synthesis notes live in
docs/hardware-notes.md.
🛠️ Development Notes
- Keep generic public components in
namespace Livt.IO; device-specific PHY managers belong innamespace Livt.IO.Phy. - Prefix protocol components with the protocol acronym, for example
I2CMasterandSPIMaster. - Keep tests in
namespace Livt.IO.Tests. - Use
bytefor byte-oriented public APIs. - Keep implementation notes and hardware caveats in
docs/hardware-notes.md.
🚧 Outlook
Future additions may include partial-word write APIs, dual-port RAM,
SPI modes 1 through 3, multiple chip
selects, quad-SPI transfers, and SPISlave.
📄 License
This project is licensed under the MIT License. See LICENSE.