Livt packages
Livt.IO
Version 1.8.1
Memory, UART, I2C, SPI, and Ethernet input/output components for Livt hardware designs.
Published files (126)
src/i2c/I2CMaster.lvt
namespace Livt.IO
/**
* Byte-level, single-controller I2C master with context-derived standard-mode timing.
* Borrows an open-drain bus and inherits the consumer clock/reset context.
* One serialized caller owns commands. Begin methods report acceptance; HasResult
* reports completion. Reset releases drive requests and clears pending results.
* A stretch timeout releases both lines without emitting recovery clock pulses.
*/
component I2CMaster<BUS_FREQUENCY: Frequency = 100000Hz,
STRETCH_TIMEOUT: Time = 1ms>
{
static assert BUS_FREQUENCY >= 1Hz && BUS_FREQUENCY <= 100000Hz,
"I2C bus frequency must be between 1 Hz and 100 kHz (standard mode)"
static assert STRETCH_TIMEOUT >= 1ns, "I2C clock-stretch timeout must be positive"
// Supported unshifted device address range.
public const MAX_7_BIT_ADDRESS: byte = 0x7F
// The consumer selects the bus rate; its inherited context supplies the clock.
public const TICKS_PER_HALF_PERIOD: int = ((this.context.TicksPer(BUS_FREQUENCY) + 1) / 2) as int
// Maximum consecutive observed-low cycles while the master releases SCL.
const STRETCH_TIMEOUT_TICKS: uint = this.context.TicksFor(STRETCH_TIMEOUT)
// Command codes consumed by the byte-transfer state machine.
const COMMAND_NONE: int = 0
const COMMAND_START: int = 1
const COMMAND_STOP: int = 2
const COMMAND_WRITE: int = 3
const COMMAND_READ: int = 4
// Borrowed open-drain bus attachment.
bus: I2CBus
// Completion status and result retained for the caller.
busy: bool
hasResult: bool
timedOut: bool
resultAck: bool
resultByte: byte
// Active byte command, bit position, and phase timing.
currentByte: byte
sendAck: bool
bitIndex: int
tickCount: int
command: int
// Sampled bus levels used for clock stretching and input data.
observedScl: logic
observedSda: logic
/**
* Borrows the open-drain bus, releases both lines, and starts idle with no result. Timing
* derives from the inherited context and BUS_FREQUENCY; use one serialized command owner.
*/
new(bus: flip I2CBus)
{
this.bus = bus
this.bus.scl_drive_low = 0b0
this.bus.sda_drive_low = 0b0
this.busy = false
this.hasResult = false
this.timedOut = false
this.resultAck = false
this.resultByte = 0x00
this.currentByte = 0x00
this.sendAck = false
this.bitIndex = 0
this.tickCount = 0
this.command = COMMAND_NONE
this.observedScl = 0b1
this.observedSda = 0b1
}
/**
* Returns the context-derived minimum phase count. State transitions and observed
* SCL synchronization can extend wire timing; BUS_FREQUENCY is a rate ceiling.
*/
public fn HalfPeriodTicks() int
{
return TICKS_PER_HALF_PERIOD
}
/**
* Accepts a START request when idle. True means accepted, not completed; poll HasResult.
* Returns false without changing the active command when busy.
*/
public fn BeginStart() bool
{
if (this.busy == true) {
return false
}
state {
this.busy = true
this.hasResult = false
this.timedOut = false
this.command = COMMAND_START
this.tickCount = 0
}
return true
}
/**
* Accepts a STOP request when idle. True means accepted, not completed; poll HasResult.
* Returns false while busy.
*/
public fn BeginStop() bool
{
if (this.busy == true) {
return false
}
state {
this.busy = true
this.hasResult = false
this.timedOut = false
this.command = COMMAND_STOP
this.tickCount = 0
}
return true
}
/**
* Accepts one outgoing data byte when idle and clears previous completion/ACK status. Poll
* HasResult before inspecting ACK; returns false while busy.
*/
public fn BeginWriteByte(data: byte) bool
{
if (this.busy == true) {
return false
}
state {
this.busy = true
this.hasResult = false
this.timedOut = false
this.resultAck = false
this.currentByte = data
this.bitIndex = 0
this.command = COMMAND_WRITE
this.tickCount = 0
}
return true
}
/**
* Accepts a seven-bit address byte with the write bit cleared. Returns false while busy or
* for addresses above 0x7F. Does not issue START; poll HasResult for completion.
*/
public fn BeginWriteAddress(address: byte) bool
{
if (this.busy == true) {
return false
}
if (address > MAX_7_BIT_ADDRESS) {
return false
}
var addressValue: int = address as int
var writeAddress: byte = (addressValue << 1) as byte
state {
this.busy = true
this.hasResult = false
this.timedOut = false
this.resultAck = false
this.currentByte = writeAddress
this.bitIndex = 0
this.command = COMMAND_WRITE
this.tickCount = 0
}
return true
}
/**
* Accepts a seven-bit address byte with the read bit set. Returns false while busy or for
* addresses above 0x7F. Does not issue START; poll HasResult for completion.
*/
public fn BeginReadAddress(address: byte) bool
{
if (this.busy == true) {
return false
}
if (address > MAX_7_BIT_ADDRESS) {
return false
}
var addressValue: int = address as int
var readAddress: byte = ((addressValue << 1) | 1) as byte
state {
this.busy = true
this.hasResult = false
this.timedOut = false
this.resultAck = false
this.currentByte = readAddress
this.bitIndex = 0
this.command = COMMAND_WRITE
this.tickCount = 0
}
return true
}
/**
* Accepts one byte-read command when idle. sendAck selects ACK versus NACK after
* reception; inspect GetReadByte only after HasResult. Returns false while busy.
*/
public fn BeginReadByte(sendAck: bool) bool
{
if (this.busy == true) {
return false
}
state {
this.busy = true
this.hasResult = false
this.timedOut = false
this.resultByte = 0x00
this.sendAck = sendAck
this.bitIndex = 0
this.command = COMMAND_READ
this.tickCount = 0
}
return true
}
/**
* Returns a snapshot of command activity; it does not reserve the command slot.
*/
public fn IsBusy() bool
{
return this.busy
}
/**
* Reports completion of the accepted command. Completion remains set until ClearResult or
* acceptance of another command.
*/
public fn HasResult() bool
{
return this.hasResult
}
/**
* Reports that the completed command exceeded STRETCH_TIMEOUT while waiting for
* released SCL to rise. Both drive requests are released; a new command clears
* this status. This does not generate recovery pulses for a stuck target.
*/
public fn HasTimedOut() bool
{
return this.timedOut
}
/**
* Returns ACK status for the completed write-byte/address command. Valid only after
* HasResult for that command.
*/
public fn WasAckReceived() bool
{
return this.resultAck
}
/**
* Reports NACK for a completed write-byte/address command. A clock-stretch timeout
* is a separate failure and returns false; inspect HasTimedOut after HasResult.
*/
public fn WasNackReceived() bool
{
return this.resultAck == false && !this.timedOut
}
/**
* Returns the retained received byte. Valid after HasResult for a read-byte command; does
* not clear completion.
*/
public fn GetReadByte() byte
{
return this.resultByte
}
/**
* Clears retained completion, timeout and ACK status without canceling the active command or
* clearing the received byte.
*/
public fn ClearResult()
{
state {
this.hasResult = false
this.timedOut = false
this.resultAck = false
}
}
/**
* Samples SCL and SDA in the component clock context for data reception and clock-stretch
* handling.
*/
process SampleBus()
{
state {
this.observedScl = this.bus.scl
this.observedSda = this.bus.sda
}
}
/**
* Executes accepted byte commands with standard-mode phase timing. Waits for released SCL
* to rise, retaining bus drive between commands. A clock-stretch timeout releases
* both lines and completes with HasTimedOut set; no recovery pulses are emitted.
*/
process Main()
{
var stretchTicks: uint = 0
var phaseTick: int = 0
var phaseBit: int = 0
var heldSclDriveLow: logic = 0b0
var heldSdaDriveLow: logic = 0b0
var commandInFlight: bool = false
state Idle {
stretchTicks = 0
phaseTick = 0
phaseBit = 0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (commandInFlight == true) {
if (this.busy == false) {
commandInFlight = false
}
goto Idle
} elif (this.busy == false) {
goto Idle
} elif (this.command == COMMAND_START) {
commandInFlight = true
goto StartRelease
} elif (this.command == COMMAND_STOP) {
commandInFlight = true
goto StopSdaLow
} elif (this.command == COMMAND_WRITE) {
commandInFlight = true
goto WriteBitLow
} elif (this.command == COMMAND_READ) {
commandInFlight = true
goto ReadBitLow
} else {
goto Idle
}
}
state StartRelease {
heldSclDriveLow = 0b0
heldSdaDriveLow = 0b0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (this.observedScl == 0b1) {
stretchTicks = 0
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
phaseTick = 0
goto StartSetup
} else {
phaseTick = phaseTick + 1
goto StartRelease
}
} else {
phaseTick = 0
stretchTicks++
if (stretchTicks >= STRETCH_TIMEOUT_TICKS) {
goto ClockTimeout
} else {
goto StartRelease
}
}
}
state StartSetup {
heldSclDriveLow = 0b0
heldSdaDriveLow = 0b1
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (this.observedScl == 0b1) {
stretchTicks = 0
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
phaseTick = 0
goto StartSclLow
} else {
phaseTick = phaseTick + 1
goto StartSetup
}
} else {
phaseTick = 0
stretchTicks++
if (stretchTicks >= STRETCH_TIMEOUT_TICKS) {
goto ClockTimeout
} else {
goto StartSetup
}
}
}
state StartSclLow {
heldSclDriveLow = 0b1
heldSdaDriveLow = 0b1
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
this.busy = false
this.hasResult = true
this.command = COMMAND_NONE
phaseTick = 0
goto Idle
} else {
phaseTick = phaseTick + 1
goto StartSclLow
}
}
state StopSdaLow {
heldSclDriveLow = 0b1
heldSdaDriveLow = 0b1
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
phaseTick = 0
goto StopSclHigh
} else {
phaseTick = phaseTick + 1
goto StopSdaLow
}
}
state StopSclHigh {
heldSclDriveLow = 0b0
heldSdaDriveLow = 0b1
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (this.observedScl == 0b1) {
stretchTicks = 0
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
phaseTick = 0
goto StopRelease
} else {
phaseTick = phaseTick + 1
goto StopSclHigh
}
} else {
phaseTick = 0
stretchTicks++
if (stretchTicks >= STRETCH_TIMEOUT_TICKS) {
goto ClockTimeout
} else {
goto StopSclHigh
}
}
}
state StopRelease {
heldSclDriveLow = 0b0
heldSdaDriveLow = 0b0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
this.busy = false
this.hasResult = true
this.command = COMMAND_NONE
phaseTick = 0
goto Idle
} else {
phaseTick = phaseTick + 1
goto StopRelease
}
}
state WriteBitLow {
heldSclDriveLow = 0b1
var byteValue: int = this.currentByte as int
var shiftAmount: int = 7 - phaseBit
var bitValue: int = (byteValue >> shiftAmount) & 1
if (bitValue == 0) {
heldSdaDriveLow = 0b1
} else {
heldSdaDriveLow = 0b0
}
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
phaseTick = 0
goto WriteBitHigh
} else {
phaseTick = phaseTick + 1
goto WriteBitLow
}
}
state WriteBitHigh {
heldSclDriveLow = 0b0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (this.observedScl == 0b1) {
stretchTicks = 0
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
phaseTick = 0
if (phaseBit == 7) {
goto WriteAckLow
} else {
phaseBit = phaseBit + 1
goto WriteBitLow
}
} else {
phaseTick = phaseTick + 1
goto WriteBitHigh
}
} else {
phaseTick = 0
stretchTicks++
if (stretchTicks >= STRETCH_TIMEOUT_TICKS) {
goto ClockTimeout
} else {
goto WriteBitHigh
}
}
}
state WriteAckLow {
heldSclDriveLow = 0b1
heldSdaDriveLow = 0b0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
phaseTick = 0
goto WriteAckHigh
} else {
phaseTick = phaseTick + 1
goto WriteAckLow
}
}
state WriteAckHigh {
heldSclDriveLow = 0b0
heldSdaDriveLow = 0b0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (this.observedScl == 0b1) {
stretchTicks = 0
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
this.resultAck = (this.observedSda == 0b0)
phaseTick = 0
goto WriteAckFinishLow
} else {
phaseTick = phaseTick + 1
goto WriteAckHigh
}
} else {
phaseTick = 0
stretchTicks++
if (stretchTicks >= STRETCH_TIMEOUT_TICKS) {
goto ClockTimeout
} else {
goto WriteAckHigh
}
}
}
state WriteAckFinishLow {
heldSclDriveLow = 0b1
heldSdaDriveLow = 0b0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
this.busy = false
this.hasResult = true
this.command = COMMAND_NONE
phaseTick = 0
goto Idle
} else {
phaseTick = phaseTick + 1
goto WriteAckFinishLow
}
}
state ReadBitLow {
heldSclDriveLow = 0b1
heldSdaDriveLow = 0b0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
phaseTick = 0
goto ReadBitHigh
} else {
phaseTick = phaseTick + 1
goto ReadBitLow
}
}
state ReadBitHigh {
heldSclDriveLow = 0b0
heldSdaDriveLow = 0b0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (this.observedScl == 0b1) {
stretchTicks = 0
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
phaseTick = 0
goto ReadBitSample
} else {
phaseTick = phaseTick + 1
goto ReadBitHigh
}
} else {
phaseTick = 0
stretchTicks++
if (stretchTicks >= STRETCH_TIMEOUT_TICKS) {
goto ClockTimeout
} else {
goto ReadBitHigh
}
}
}
state ReadBitSample {
heldSclDriveLow = 0b0
heldSdaDriveLow = 0b0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (this.observedScl == 0b1) {
stretchTicks = 0
var sampledBit: int = 0
if (this.observedSda == 0b1) {
sampledBit = 1
}
var currentValue: int = this.resultByte as int
var shiftAmount: int = 7 - phaseBit
this.resultByte = (currentValue | (sampledBit << shiftAmount)) as byte
if (phaseBit == 7) {
goto SendAckLow
} else {
phaseBit = phaseBit + 1
goto ReadBitLow
}
} else {
phaseTick = 0
stretchTicks++
if (stretchTicks >= STRETCH_TIMEOUT_TICKS) {
goto ClockTimeout
} else {
goto ReadBitHigh
}
}
}
state SendAckLow {
heldSclDriveLow = 0b1
if (this.sendAck == true) {
heldSdaDriveLow = 0b1
} else {
heldSdaDriveLow = 0b0
}
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
phaseTick = 0
goto SendAckHigh
} else {
phaseTick = phaseTick + 1
goto SendAckLow
}
}
state SendAckHigh {
heldSclDriveLow = 0b0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (this.observedScl == 0b1) {
stretchTicks = 0
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
heldSdaDriveLow = 0b0
this.bus.sda_drive_low = heldSdaDriveLow
phaseTick = 0
goto SendAckFinishLow
} else {
phaseTick = phaseTick + 1
goto SendAckHigh
}
} else {
phaseTick = 0
stretchTicks++
if (stretchTicks >= STRETCH_TIMEOUT_TICKS) {
goto ClockTimeout
} else {
goto SendAckHigh
}
}
}
state SendAckFinishLow {
heldSclDriveLow = 0b1
heldSdaDriveLow = 0b0
this.bus.scl_drive_low = heldSclDriveLow
this.bus.sda_drive_low = heldSdaDriveLow
if (phaseTick >= TICKS_PER_HALF_PERIOD) {
this.busy = false
this.hasResult = true
this.command = COMMAND_NONE
phaseTick = 0
goto Idle
} else {
phaseTick = phaseTick + 1
goto SendAckFinishLow
}
}
// One failure path releases the bus and preserves a distinct completion reason.
state ClockTimeout {
heldSclDriveLow = 0b0
heldSdaDriveLow = 0b0
this.bus.scl_drive_low = 0b0
this.bus.sda_drive_low = 0b0
this.busy = false
this.hasResult = true
this.timedOut = true
this.resultAck = false
this.resultByte = 0x00
this.command = COMMAND_NONE
goto Idle
}
}
}