Driver Cookbook
Copy-paste recipes for common protocol verification scenarios using RouteRTL's native BFM drivers. Each recipe is a complete, runnable cocotb test.
Recipe 1: SPI Sensor Register Read
Scenario: Your design has an SPI master that reads a register from a sensor. You need to verify the master sends the correct command and captures the response.
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import ClockCycles, RisingEdge
from routertl.sim import Tb
from routertl.sim.cocotb.tb.drivers.spi_master import (
SpiBus, SpiConfig, SpiMaster, SpiSlave,
)
@cocotb.test()
async def test_spi_sensor_read(dut):
"""Read WHO_AM_I register from SPI sensor."""
tb = Tb(dut)
await tb.start_clock()
await tb.reset()
# ── Setup SPI bus ──────────────────────────────────────────────
bus = SpiBus.from_entity(dut, prefix="SPI_")
config = SpiConfig(
word_width=8,
sclk_freq=1_000_000, # 1 MHz
cpol=False, # Mode 0
cpha=False,
)
master = SpiMaster(bus, config)
# ── Setup SPI slave (simulated sensor) ─────────────────────────
slave = SpiSlave(bus, config)
slave.load_response([0x68]) # WHO_AM_I = 0x68 (like MPU-6050)
cocotb.start_soon(slave.start())
# ── Perform read transaction ───────────────────────────────────
# Send read command (bit 7 = 1 for read) + register address 0x75
rx_data = await master.write([0xF5, 0x00])
# First byte is command echo, second byte is sensor response
sensor_id = rx_data[1]
tb.log.info(f"Sensor WHO_AM_I = 0x{sensor_id:02X}")
assert sensor_id == 0x68, f"Expected 0x68, got 0x{sensor_id:02X}"
slave.stop()
tb.log.info("✅ SPI sensor read verified")
Recipe 2: UART Loopback at 460800 Baud
Scenario: Your design echoes received UART bytes back. Verify it works at high baud rates with multiple bytes.
import cocotb
from routertl.sim import Tb, UartSource
from routertl.sim.cocotb.tb.drivers.uart_master import UartSink
@cocotb.test()
async def test_uart_loopback_460800(dut):
"""Verify UART loopback at 460800 baud."""
tb = Tb(dut)
await tb.start_clock(period_ns=10) # 100 MHz
await tb.reset()
BAUD = 460_800
# ── Create UART source (drives DUT's RX pin) ──────────────────
uart_tx = UartSource(dut.UART_RX, baud=BAUD, bits=8, parity="none")
# ── Create UART sink (captures DUT's TX pin) ──────────────────
uart_rx = UartSink(dut.UART_TX, baud=BAUD, bits=8, parity="none")
cocotb.start_soon(uart_rx.start())
# ── Send test pattern ─────────────────────────────────────────
test_data = [0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE]
await uart_tx.write_bytes(test_data)
# ── Verify echoed bytes ───────────────────────────────────────
for i, expected in enumerate(test_data):
received = await uart_rx.read(timeout_us=500)
assert received is not None, f"Timeout waiting for byte {i}"
assert received == expected, \
f"Byte {i}: expected 0x{expected:02X}, got 0x{received:02X}"
tb.log.info(f"✅ Loopback verified: {len(test_data)} bytes at {BAUD} baud")
Recipe 3: AXI-Lite Register Bank Smoke Test
Scenario: Your design exposes a register bank over AXI-Lite. Verify write/read-back for all registers.
import cocotb
from routertl.sim import Tb
from routertl.sim.cocotb.tb.drivers.axi_lite import AxiLiteMaster
@cocotb.test()
async def test_regbank_smoke(dut):
"""Write/read-back all registers in the AXI-Lite register bank."""
tb = Tb(dut)
await tb.start_clock()
await tb.reset()
master = AxiLiteMaster(tb, prefix="S_AXI")
# ── Define register map ───────────────────────────────────────
registers = {
0x00: ("CTRL", 0x0000_0001), # Enable bit
0x04: ("STATUS", None), # Read-only, skip write
0x08: ("CONFIG", 0xDEAD_BEEF),
0x0C: ("THRESH", 0x0000_00FF),
}
# ── Write all writable registers ──────────────────────────────
for addr, (name, value) in registers.items():
if value is not None:
await master.write(addr, value)
tb.log.info(f" Wrote {name} (0x{addr:02X}) = 0x{value:08X}")
# ── Read back and verify ──────────────────────────────────────
for addr, (name, value) in registers.items():
if value is not None:
readback, resp = await master.read(addr)
assert readback == value, \
f"{name}: expected 0x{value:08X}, got 0x{readback:08X}"
tb.log.info(f" ✅ {name} readback OK")
tb.log.info("✅ Register bank smoke test passed")
Recipe 4: I²C Temperature Sensor Read
Scenario: Your design has an I²C master that reads a 16-bit
temperature register from a sensor (e.g. TMP102 at address 0x48).
You need to verify the I²C address byte, register pointer write,
and two-byte read-back.
import cocotb
from routertl.sim import Tb
from routertl.sim.cocotb.tb.drivers.i2c_master import (
I2cMasterDriver, I2cTiming,
)
@cocotb.test()
async def test_i2c_temp_read(dut):
"""Read temperature register from I2C sensor (TMP102 @ 0x48)."""
tb = Tb(dut)
await tb.start_clock()
await tb.reset()
# ── Setup I2C master ──────────────────────────────────────────
i2c = I2cMasterDriver(
tb,
sda=dut.I2C_SDA,
scl=dut.I2C_SCL,
timing=I2cTiming(period_ns=10_000), # 100 kHz standard mode
)
await i2c.initialize()
SENSOR_ADDR = 0x48
TEMP_REG = 0x00 # Temperature register pointer
# ── Phase 1: Write register pointer ───────────────────────────
acks = await i2c.write_transaction(
addr=SENSOR_ADDR, data=[TEMP_REG],
)
assert all(acks), f"Sensor NACK during pointer write: {acks}"
tb.log.info(f"✅ Register pointer 0x{TEMP_REG:02X} written")
# ── Phase 2: Read 2 bytes (temperature) ───────────────────────
await i2c.start() # Repeated START
addr_ack = await i2c.write_byte((SENSOR_ADDR << 1) | 1) # Read bit
assert addr_ack, "Sensor NACK on read address"
msb = await i2c.read_byte(send_ack=True) # First byte, ACK
lsb = await i2c.read_byte(send_ack=False) # Last byte, NACK
await i2c.stop()
temp_raw = (msb << 8) | lsb
tb.log.info(f"Temperature raw = 0x{temp_raw:04X} (MSB=0x{msb:02X}, LSB=0x{lsb:02X})")
# ── Verify reasonable range ───────────────────────────────────
assert msb != 0xFF, "Read 0xFF — bus stuck or no sensor responding"
tb.log.info("✅ I2C temperature read verified")
Tips
- Import shorthand:
from routertl.sim import Tb, UartSourceworks for both pip-installed and submodule setups - Error injection: Use
uart_tx.inject_framing_error(0xAA)anduart_tx.inject_parity_error(0x55)to test receiver robustness - SPI modes: Change
cpolandcphainSpiConfigto test all 4 SPI modes. See the SPI driver reference - I²C glitches: Use
i2c.inject_glitch(duration_ns=20)to test sniffer error detection. See the I²C driver reference