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
	}
}