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/spi/SPIMasterTest.lvt
namespace Livt.IO.Tests
using Livt.IO
/**
* Observes controller output and provides deterministic MISO response bytes.
*/
component SPIMasterTestBus : SPIBus
{
// Compile-time dimensions, bounds, and numeric constants.
const MAX_CAPTURED_BYTES: int = 80
// Compile-time dimensions, bounds, and numeric constants.
const MAX_CLOCK_EDGES: int = 32
// Shared fixture instances, signals, and observation state.
responseByte: byte
// Shared fixture instances, signals, and observation state.
capturedByte: byte
// Shared fixture instances, signals, and observation state.
capturedBytes: byte[80]
// Shared fixture instances, signals, and observation state.
capturedByteCount: int
// Shared fixture instances, signals, and observation state.
risingEdges: int
// Shared fixture instances, signals, and observation state.
selectEdges: int
// Shared fixture instances, signals, and observation state.
deselectEdges: int
// Shared fixture instances, signals, and observation state.
clockEdgeIntervals: int[32]
// Shared fixture instances, signals, and observation state.
clockEdgeCount: int
// Shared fixture instances, signals, and observation state.
deselectHoldTicks: int
/**
* Creates an idle SPI test bus with an all-zero response byte.
*/
new()
{
this.responseByte = 0x00
this.capturedByte = 0x00
this.capturedByteCount = 0
this.risingEdges = 0
this.selectEdges = 0
this.deselectEdges = 0
this.clockEdgeCount = 0
this.deselectHoldTicks = 0
this.miso = 0b0
}
/**
* Selects the byte returned on MISO during subsequent transfers.
*/
public fn SetResponseByte(value: byte)
{
this.responseByte = value
}
/**
* Returns the most recently captured MOSI byte.
*/
public fn GetCapturedByte() byte
{
return this.capturedByte
}
/**
* Returns the number of complete MOSI bytes captured by the fixture.
*/
public fn GetCapturedByteCount() int
{
return this.capturedByteCount
}
/**
* Returns a captured MOSI byte or 0x00 when the index is invalid.
*/
public fn GetCapturedByteAt(index: int) byte
{
if (index < 0 || index >= this.capturedByteCount) {
return 0x00
}
return this.capturedBytes[index]
}
/**
* Returns the total number of observed SCLK rising edges.
*/
public fn GetRisingEdges() int
{
return this.risingEdges
}
/**
* Returns the total number of observed chip-select assertions.
*/
public fn GetSelectEdges() int
{
return this.selectEdges
}
/**
* Returns the total number of observed chip-select deassertions.
*/
public fn GetDeselectEdges() int
{
return this.deselectEdges
}
/**
* Returns whether SCLK is currently high.
*/
public fn IsClockHigh() bool
{
return this.sclk == 0b1
}
/**
* Returns whether the active-low chip select is currently asserted.
*/
public fn IsChipSelected() bool
{
return this.chip_select_n == 0b0
}
/**
* Returns the number of SCLK edges recorded since the latest selection.
*/
public fn GetClockEdgeCount() int
{
return this.clockEdgeCount
}
/**
* Returns the interval preceding an SCLK edge or zero for an invalid index.
* Index zero measures chip-select setup time before the first rising edge.
*/
public fn GetClockEdgeInterval(index: int) int
{
if (index < 0 || index >= this.clockEdgeCount) {
return 0
}
return this.clockEdgeIntervals[index]
}
/**
* Returns the interval from the latest SCLK edge to chip-select release.
*/
public fn GetDeselectHoldTicks() int
{
return this.deselectHoldTicks
}
/**
* Drives MISO and records MOSI bytes, bus edges, and timing intervals.
*/
process ObserveBus()
{
var capturedValue: int = 0
var capturedBits: int = 0
var edgeCount: int = 0
var selectedCount: int = 0
var deselectedCount: int = 0
var previousSclk: logic = 0b0
var previousChipSelect: logic = 0b1
var totalCapturedBytes: int = 0
var elapsedTicks: int = 0
var previousClockEdgeTick: int = 0
var recordedClockEdges: int = 0
var recordedDeselectHold: int = 0
state {
elapsedTicks = elapsedTicks + 1
if (this.chip_select_n == 0b0 && this.sclk == 0b0) {
var shiftedResponse = (this.responseByte as int) >> (7 - capturedBits)
if (shiftedResponse % 2 == 0) {
this.miso = 0b0
} else {
this.miso = 0b1
}
} elif (this.chip_select_n == 0b1) {
this.miso = 0b0
}
if (previousChipSelect == 0b1 && this.chip_select_n == 0b0) {
selectedCount = selectedCount + 1
capturedValue = 0
capturedBits = 0
previousClockEdgeTick = elapsedTicks
recordedClockEdges = 0
}
if (previousChipSelect == 0b0 && this.chip_select_n == 0b1) {
deselectedCount = deselectedCount + 1
recordedDeselectHold = elapsedTicks - previousClockEdgeTick
}
if (this.chip_select_n == 0b0 && previousSclk != this.sclk) {
var edgeInterval = elapsedTicks - previousClockEdgeTick
if (recordedClockEdges < MAX_CLOCK_EDGES) {
this.clockEdgeIntervals[recordedClockEdges] = edgeInterval
recordedClockEdges = recordedClockEdges + 1
}
previousClockEdgeTick = elapsedTicks
}
if (this.chip_select_n == 0b0 && previousSclk == 0b0 && this.sclk == 0b1) {
capturedValue = capturedValue << 1
if (this.mosi == 0b1) {
capturedValue = capturedValue + 1
}
capturedBits = capturedBits + 1
edgeCount = edgeCount + 1
if (capturedBits == 8) {
var completedByte = capturedValue as byte
this.capturedByte = completedByte
if (totalCapturedBytes < MAX_CAPTURED_BYTES) {
this.capturedBytes[totalCapturedBytes] = completedByte
totalCapturedBytes = totalCapturedBytes + 1
}
capturedBits = 0
capturedValue = 0
}
}
this.risingEdges = edgeCount
this.capturedByteCount = totalCapturedBytes
this.selectEdges = selectedCount
this.deselectEdges = deselectedCount
this.clockEdgeCount = recordedClockEdges
this.deselectHoldTicks = recordedDeselectHold
previousSclk = this.sclk
previousChipSelect = this.chip_select_n
}
}
}
/**
* Verifies SPI Mode 0 behavior in a 100 MHz component context.
*/
@Test
@Context(ClockFrequency = 100MHz)
component SPIMasterTest
{
// Shared fixture instances, signals, and observation state.
bus: SPIMasterTestBus
// Shared fixture instances, signals, and observation state.
master: SPIMaster
/**
* Creates the controller and its observing bus fixture.
*/
new()
{
this.bus = new SPIMasterTestBus()
this.master = new SPIMaster(this.bus)
}
/**
* Waits for the current asynchronous command to finish.
*/
fn WaitForCommand()
{
while (this.master.IsBusy()) {
state {
}
}
state {
}
}
/**
* Selects the fixture and verifies the selected state.
*/
fn SelectDevice()
{
assert this.master.BeginSelect() == true
this.WaitForCommand()
assert this.master.IsSelected() == true
}
/**
* Deselects the fixture and verifies the idle selection state.
*/
fn DeselectDevice()
{
assert this.master.BeginDeselect() == true
this.WaitForCommand()
assert this.master.IsSelected() == false
}
/**
* Exchanges one byte and returns the received value.
*/
fn TransferByte(value: byte) byte
{
assert this.master.BeginTransfer(value) == true
this.WaitForCommand()
return this.master.GetReceivedByte()
}
/**
* Verifies every SCLK half-period recorded for one byte transfer.
*/
fn AssertClockTiming(expectedHalfPeriod: int)
{
assert this.bus.GetClockEdgeCount() == 16
assert this.bus.GetClockEdgeInterval(0) >= expectedHalfPeriod
for (var edgeIndex = 1; edgeIndex < 16; edgeIndex++) {
// Low phases include two controller cycles for advancing and driving MOSI.
var expectedInterval = expectedHalfPeriod
if (edgeIndex % 2 == 0) {
expectedInterval = expectedInterval + 2
}
assert this.bus.GetClockEdgeInterval(edgeIndex) == expectedInterval
}
}
/**
* Verifies that a 100 MHz context produces five ticks per half-period.
*/
@Test
fn TestContextDerivesConservativeTiming()
{
assert this.master.HalfPeriodTicks() == 0n5
}
/**
* Verifies idle outputs, sticky completion, and rejected selection commands.
*/
@Test
fn TestIdleAndTransactionSelection()
{
assert this.bus.IsClockHigh() == false
assert this.bus.IsChipSelected() == false
assert this.master.HasResult() == false
assert this.master.GetReceivedByte() == 0x00
assert this.master.BeginDeselect() == false
assert this.master.BeginTransfer(0x55) == false
var selectEdgesBefore = this.bus.GetSelectEdges()
assert this.master.BeginSelect() == true
assert this.master.BeginSelect() == false
this.WaitForCommand()
assert this.bus.IsChipSelected() == true
assert this.bus.IsClockHigh() == false
assert this.bus.GetSelectEdges() == selectEdgesBefore + 1
assert this.master.IsSelected() == true
assert this.master.HasResult() == true
assert this.master.BeginSelect() == false
this.master.ClearResult()
assert this.master.HasResult() == false
var deselectEdgesBefore = this.bus.GetDeselectEdges()
assert this.master.BeginDeselect() == true
assert this.master.BeginDeselect() == false
this.WaitForCommand()
assert this.master.IsSelected() == false
assert this.master.HasResult() == true
assert this.bus.IsChipSelected() == false
assert this.bus.GetDeselectEdges() == deselectEdgesBefore + 1
}
/**
* Verifies full-duplex, most-significant-bit-first byte transfer behavior.
*/
@Test
fn TestTransferIsFullDuplexAndMostSignificantBitFirst()
{
this.SelectDevice()
this.bus.SetResponseByte(0x3C)
assert this.master.BeginTransfer(0xA5) == true
assert this.master.BeginTransfer(0x00) == false
this.WaitForCommand()
assert this.master.HasResult() == true
assert this.master.GetReceivedByte() == 0x3C
assert this.bus.GetCapturedByte() == 0xA5
assert this.bus.GetRisingEdges() == 8
assert this.master.IsSelected() == true
assert this.bus.IsClockHigh() == false
this.DeselectDevice()
}
/**
* Verifies five-tick SCLK phases and chip-select setup and hold intervals.
*/
@Test
fn TestWaveformTimingAtOneHundredMegahertz()
{
this.SelectDevice()
this.TransferByte(0xA5)
this.AssertClockTiming(5)
this.DeselectDevice()
assert this.bus.GetDeselectHoldTicks() >= 5
}
/**
* Verifies that consecutive bytes do not release chip select.
*/
@Test
fn TestConsecutiveTransfersKeepChipSelectAsserted()
{
this.SelectDevice()
var selectEdgesAfterSelection = this.bus.GetSelectEdges()
var risingEdgesBefore = this.bus.GetRisingEdges()
this.bus.SetResponseByte(0xFF)
assert this.master.BeginTransfer(0x03) == true
this.WaitForCommand()
assert this.master.GetReceivedByte() == 0xFF
this.master.ClearResult()
assert this.master.HasResult() == false
this.bus.SetResponseByte(0x5A)
assert this.master.BeginTransfer(0x00) == true
this.WaitForCommand()
assert this.master.GetReceivedByte() == 0x5A
assert this.bus.GetCapturedByte() == 0x00
assert this.bus.GetRisingEdges() == risingEdgesBefore + 16
assert this.bus.GetSelectEdges() == selectEdgesAfterSelection
assert this.master.IsSelected() == true
this.DeselectDevice()
}
/**
* Verifies a flash-style command, address, and data transaction.
*/
@Test
fn TestFlashReadSequenceUsesOneContinuousTransaction()
{
this.SelectDevice()
var capturedBefore = this.bus.GetCapturedByteCount()
var selectEdgesAfterSelection = this.bus.GetSelectEdges()
this.bus.SetResponseByte(0x00)
this.TransferByte(0x03)
this.TransferByte(0x12)
this.TransferByte(0x34)
this.TransferByte(0x40)
this.bus.SetResponseByte(0xDE)
var firstData = this.TransferByte(0x00)
this.bus.SetResponseByte(0xAD)
var secondData = this.TransferByte(0x00)
assert firstData == 0xDE
assert secondData == 0xAD
assert this.bus.GetCapturedByteCount() == capturedBefore + 6
assert this.bus.GetCapturedByteAt(capturedBefore) == 0x03
assert this.bus.GetCapturedByteAt(capturedBefore + 1) == 0x12
assert this.bus.GetCapturedByteAt(capturedBefore + 2) == 0x34
assert this.bus.GetCapturedByteAt(capturedBefore + 3) == 0x40
assert this.bus.GetCapturedByteAt(capturedBefore + 4) == 0x00
assert this.bus.GetCapturedByteAt(capturedBefore + 5) == 0x00
assert this.bus.GetSelectEdges() == selectEdgesAfterSelection
this.DeselectDevice()
}
}
/**
* Verifies context-derived SPI timing in a 50 MHz component context.
*/
@Test
@Context(ClockFrequency = 50MHz)
component SPIMasterSlowContextTest
{
// Shared fixture instances, signals, and observation state.
bus: SPIMasterTestBus
// Shared fixture instances, signals, and observation state.
master: SPIMaster
/**
* Creates the controller and its observing bus fixture.
*/
new()
{
this.bus = new SPIMasterTestBus()
this.master = new SPIMaster(this.bus)
}
/**
* Waits for the current asynchronous command to finish.
*/
fn WaitForCommand()
{
while (this.master.IsBusy()) {
state {
}
}
state {
}
}
/**
* Verifies three-tick SCLK phases and chip-select setup and hold intervals.
*/
@Test
fn TestHalfPeriodRoundsUpInSlowerContext()
{
assert this.master.HalfPeriodTicks() == 0n3
assert this.master.BeginSelect() == true
this.WaitForCommand()
assert this.master.BeginTransfer(0xA5) == true
this.WaitForCommand()
assert this.bus.GetClockEdgeCount() == 16
assert this.bus.GetClockEdgeInterval(0) >= 3
for (var edgeIndex = 1; edgeIndex < 16; edgeIndex++) {
// Low phases include two controller cycles for advancing and driving MOSI.
var expectedInterval = 3
if (edgeIndex % 2 == 0) {
expectedInterval = expectedInterval + 2
}
assert this.bus.GetClockEdgeInterval(edgeIndex) == expectedInterval
}
assert this.master.BeginDeselect() == true
this.WaitForCommand()
assert this.bus.GetDeselectHoldTicks() >= 3
}
}
/**
* Verifies an explicitly configured SPI half-period at 50 MHz.
*/
@Test
@Context(ClockFrequency = 50MHz)
component SPIMasterConfiguredTimingTest
{
// Shared fixture instances, signals, and observation state.
bus: SPIMasterTestBus
// Shared fixture instances, signals, and observation state.
master: SPIMaster
/**
* Creates the controller and its observing bus fixture.
*/
new()
{
this.bus = new SPIMasterTestBus()
this.master = new SPIMaster(this.bus, 100ns)
}
/**
* Waits for the current asynchronous command to finish.
*/
fn WaitForCommand()
{
while (this.master.IsBusy()) {
state {
}
}
state {
}
}
/**
* Verifies five-tick SCLK phases and chip-select setup and hold intervals.
*/
@Test
fn TestConfiguredHalfPeriodControlsTransfers()
{
assert this.master.HalfPeriodTicks() == 0n5
assert this.master.BeginSelect() == true
this.WaitForCommand()
this.bus.SetResponseByte(0x3C)
assert this.master.BeginTransfer(0xA5) == true
this.WaitForCommand()
assert this.master.GetReceivedByte() == 0x3C
assert this.bus.GetCapturedByteAt(0) == 0xA5
assert this.bus.GetClockEdgeCount() == 16
assert this.bus.GetClockEdgeInterval(0) >= 5
for (var edgeIndex = 1; edgeIndex < 16; edgeIndex++) {
// Low phases include two controller cycles for advancing and driving MOSI.
var expectedInterval = 5
if (edgeIndex % 2 == 0) {
expectedInterval = expectedInterval + 2
}
assert this.bus.GetClockEdgeInterval(edgeIndex) == expectedInterval
}
assert this.master.BeginDeselect() == true
this.WaitForCommand()
assert this.bus.GetDeselectHoldTicks() >= 5
}
}