Livt packages
Livt.IO
Version 1.8.1
Memory, UART, I2C, SPI, and Ethernet input/output components for Livt hardware designs.
Published files (126)
tests/i2c/I2cSlaveTest.lvt
namespace Livt.IO.Tests
using Livt.IO
/**
* Test bus adapter that exposes observed line levels and low-drive requests without
* physical board pins.
*/
component I2CSlaveTestBus : I2CBus
{
// Shared fixture instances, signals, and observation state.
observed_scl: logic
observed_sda: logic
/**
* Initializes fixture state and constructor bindings. Test cases share the fixture
* instance rather than constructing a fresh subject for each method.
*/
new()
{
this.observed_scl = 0b1
this.observed_sda = 0b1
}
/**
* Publishes the fixture SCL/SDA levels in the test clock context.
*/
process ApplyObservedLines()
{
state {
this.scl = this.observed_scl
this.sda = this.observed_sda
}
}
/**
* Sets the SCL/SDA levels that the fixture will present to the component under test.
*/
public fn SetObservedLines(sclValue: logic, sdaValue: logic)
{
state {
this.observed_scl = sclValue
this.observed_sda = sdaValue
}
}
/**
* Returns the observed SDA low-drive request without changing the bus.
*/
public fn ReadSdaDriveLow() logic
{
return this.sda_drive_low
}
}
/**
* Exercises I2CSlave with a shared fixture instance. Each scenario establishes the inputs
* and state it needs.
*/
@Test
component I2CSlaveTest
{
// Shared fixture instances, signals, and observation state.
bus: I2CSlaveTestBus
slave: I2CSlave
/**
* Creates the shared fixture instances (I2CSlaveTestBus, I2CSlave). Tests initialize the
* state required by their scenario.
*/
new()
{
this.bus = new I2CSlaveTestBus()
this.slave = new I2CSlave(this.bus, 0x42)
}
/**
* Sets fixture bus levels and waits 40 simulation cycles for target edge detection.
*/
fn DriveLines(sclValue: logic, sdaValue: logic)
{
this.bus.SetObservedLines(sclValue, sdaValue)
Simulation.Wait(40)
}
/**
* Drives a START transition and leaves SCL low for the next fixture bit.
*/
fn SendStart()
{
this.DriveLines(0b1, 0b1)
this.DriveLines(0b1, 0b0)
this.DriveLines(0b0, 0b0)
}
/**
* Drives a STOP transition and leaves both fixture lines released high.
*/
fn SendStop()
{
this.DriveLines(0b0, 0b0)
this.DriveLines(0b1, 0b0)
this.DriveLines(0b1, 0b1)
}
/**
* Clocks one supplied SDA level through low, high, and low SCL phases.
*/
fn ClockBit(value: logic)
{
this.DriveLines(0b0, value)
this.DriveLines(0b1, value)
this.DriveLines(0b0, value)
}
/**
* Clocks eight bits most-significant first. The acknowledgment phase is driven separately.
*/
fn SendByte(value: byte)
{
var byteValue: int = value as int
for (var i = 0; i < 8; i++) {
var shiftAmount: int = 7 - i
var bitValue: int = (byteValue >> shiftAmount) & 1
if (bitValue == 0) {
this.ClockBit(0b0)
} else {
this.ClockBit(0b1)
}
}
}
/**
* Clocks the target acknowledgment phase with the controller SDA level released.
*/
fn ClockSlaveAck()
{
this.DriveLines(0b0, 0b1)
this.DriveLines(0b1, 0b1)
this.DriveLines(0b0, 0b1)
}
/**
* Clocks controller ACK when ack is true, otherwise NACK, after a received byte.
*/
fn ClockMasterAck(ack: bool)
{
if (ack) {
this.DriveLines(0b0, 0b0)
this.DriveLines(0b1, 0b0)
this.DriveLines(0b0, 0b0)
} else {
this.DriveLines(0b0, 0b1)
this.DriveLines(0b1, 0b1)
this.DriveLines(0b0, 0b1)
}
}
/**
* Clocks eight read bits with the controller SDA level released; acknowledgment is
* separate.
*/
fn ClockReadByte()
{
for (var i = 0; i < 8; i++) {
this.ClockBit(0b1)
}
}
/**
* Clears all retained target notifications before the next event scenario.
*/
fn ClearEvents()
{
this.slave.ClearAddressMatch()
this.slave.ClearReceivedByte()
this.slave.ClearReadRequested()
this.slave.ClearStopDetected()
this.slave.ClearTransmittedByte()
}
/**
* Checks the initial event state case.
*/
@Test
fn TestInitialEventState()
{
this.ClearEvents()
assert this.slave.HasReceivedByte() == false
assert this.slave.GetReceivedByte() == 0x00
assert this.slave.IsReadRequested() == false
assert this.slave.HasAddressMatch() == false
assert this.slave.HasStopDetected() == false
assert this.slave.HasTransmittedByte() == false
}
/**
* Checks the address match write direction case.
*/
@Test
fn TestAddressMatchWriteDirection()
{
this.ClearEvents()
this.SendStart()
this.SendByte(0x84)
Simulation.Wait(80)
assert this.slave.HasAddressMatch() == true
assert this.slave.WasWriteAddressed() == true
assert this.slave.WasReadAddressed() == false
assert this.bus.ReadSdaDriveLow() == 0b1
this.slave.ClearAddressMatch()
assert this.slave.HasAddressMatch() == false
this.ClockSlaveAck()
this.SendStop()
}
/**
* Checks the address match read direction case.
*/
@Test
fn TestAddressMatchReadDirection()
{
this.ClearEvents()
this.SendStart()
this.SendByte(0x85)
Simulation.Wait(80)
assert this.slave.HasAddressMatch() == true
assert this.slave.WasReadAddressed() == true
assert this.slave.WasWriteAddressed() == false
assert this.slave.IsReadRequested() == true
this.ClockSlaveAck()
this.ClockReadByte()
this.ClockMasterAck(false)
}
/**
* Checks the address miss does not ack case.
*/
@Test
fn TestAddressMissDoesNotAck()
{
this.ClearEvents()
this.SendStart()
this.SendByte(0x86)
Simulation.Wait(80)
assert this.slave.HasAddressMatch() == false
assert this.bus.ReadSdaDriveLow() == 0b0
this.SendStop()
}
/**
* Checks that Write receive byte.
*/
@Test
fn TestWriteReceiveByte()
{
this.ClearEvents()
this.SendStart()
this.SendByte(0x84)
this.ClockSlaveAck()
this.SendByte(0xA5)
Simulation.Wait(80)
assert this.slave.HasReceivedByte() == true
assert this.slave.GetReceivedByte() == 0xA5
this.slave.ClearReceivedByte()
assert this.slave.HasReceivedByte() == false
this.ClockSlaveAck()
this.SendStop()
}
/**
* Checks that Read transmit ack and nack events.
*/
@Test
fn TestReadTransmitAckAndNackEvents()
{
this.ClearEvents()
this.slave.SetTransmitByte(0x5A)
this.SendStart()
this.SendByte(0x85)
this.ClockSlaveAck()
this.slave.ClearReadRequested()
this.ClockReadByte()
this.ClockMasterAck(true)
Simulation.Wait(80)
assert this.slave.HasTransmittedByte() == true
assert this.slave.WasTransmitAcked() == true
this.slave.ClearTransmittedByte()
assert this.slave.HasTransmittedByte() == false
this.ClockReadByte()
this.ClockMasterAck(false)
Simulation.Wait(80)
assert this.slave.HasTransmittedByte() == true
assert this.slave.WasTransmitAcked() == false
}
/**
* Checks the stop detected case.
*/
@Test
fn TestStopDetected()
{
this.ClearEvents()
this.SendStart()
this.SendByte(0x84)
this.ClockSlaveAck()
this.SendStop()
Simulation.Wait(80)
assert this.slave.HasStopDetected() == true
this.slave.ClearStopDetected()
assert this.slave.HasStopDetected() == false
}
/**
* Checks the repeated start returns to address bits case.
*/
@Test
fn TestRepeatedStartReturnsToAddressBits()
{
this.ClearEvents()
this.SendStart()
this.SendByte(0x84)
this.ClockSlaveAck()
this.SendStart()
this.SendByte(0x85)
Simulation.Wait(80)
assert this.slave.HasAddressMatch() == true
assert this.slave.WasReadAddressed() == true
assert this.slave.IsReadRequested() == true
}
}