tests: add tb for axi4l_reg_map [WIP, 1 test broken]
This commit is contained in:
164
axi/tb/axil_reg_cocotb/test_axi4l_reg_map.py
Normal file
164
axi/tb/axil_reg_cocotb/test_axi4l_reg_map.py
Normal file
@ -0,0 +1,164 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge
|
||||
from cocotbext.axi import AxiLiteBus, AxiLiteMaster
|
||||
|
||||
OKAY = 0
|
||||
SLVERR = 2
|
||||
|
||||
REG0_CTRL = 0x00
|
||||
REG1_STATUS = 0x04
|
||||
REG2_CONFIG = 0x08
|
||||
REG_INVALID = 0x0C # N_REGS=3, so index 3 is bad
|
||||
|
||||
|
||||
def _resp_code(resp):
|
||||
"""Return integer AXI response code from a cocotbext-axi response object."""
|
||||
if resp is None:
|
||||
return OKAY
|
||||
|
||||
value = getattr(resp, "resp", OKAY)
|
||||
|
||||
# common guards
|
||||
try:
|
||||
return int(value)
|
||||
except TypeError:
|
||||
return int(value.integer)
|
||||
|
||||
|
||||
def _read_data(resp):
|
||||
return int.from_bytes(bytes(resp.data), "little")
|
||||
|
||||
|
||||
class TB:
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
self.axil = AxiLiteMaster(AxiLiteBus.from_prefix(dut, "s_axil"), dut.clk, dut.rst)
|
||||
|
||||
async def reset(self):
|
||||
self.dut.rst.value = 1
|
||||
self.dut.busy_i.value = 0
|
||||
self.dut.error_code_i.value = 0
|
||||
|
||||
for _ in range(5):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
self.dut.rst.value = 0
|
||||
|
||||
for _ in range(5):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
# shortcuts sorta
|
||||
async def write32(self, addr, value, expected_resp=OKAY):
|
||||
resp = await self.axil.write(addr, value.to_bytes(4, "little"))
|
||||
assert _resp_code(resp) == expected_resp, (
|
||||
f"write addr=0x{addr:08x}: expected resp {expected_resp}, got {_resp_code(resp)}"
|
||||
)
|
||||
return resp
|
||||
|
||||
async def read32(self, addr, expected_resp=OKAY):
|
||||
resp = await self.axil.read(addr, 4)
|
||||
assert _resp_code(resp) == expected_resp, (
|
||||
f"read addr=0x{addr:08x}: expected resp {expected_resp}, got {_resp_code(resp)}"
|
||||
)
|
||||
return _read_data(resp)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_reset_and_basic_reads(dut):
|
||||
"""Check reset values and RO status path via reg_i."""
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
tb = TB(dut)
|
||||
await tb.reset()
|
||||
|
||||
# REG0 reset: W1C sticky bit set. W1S reads as 0, RW reads from reg_o
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000004
|
||||
|
||||
# REG2 is a normal RW config register with non-zero reset value
|
||||
assert await tb.read32(REG2_CONFIG) == 0x12345678
|
||||
|
||||
# Drive external status and read it through RO bits
|
||||
dut.busy_i.value = 1
|
||||
dut.error_code_i.value = 0x5A
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert await tb.read32(REG1_STATUS) == 0x00005A01
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_rw_and_w1c_bits(dut):
|
||||
"""Check RW bit update and W1C sticky clear behavior."""
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
tb = TB(dut)
|
||||
await tb.reset()
|
||||
|
||||
# Initial REG0: bit2 W1C is set, bit1 RW is clear, bit0 W1S reads as 0
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000004
|
||||
|
||||
# Set RW bit1 while preserving W1C bit2
|
||||
await tb.write32(REG0_CTRL, 0x00000006)
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000006
|
||||
|
||||
# Write 1 to W1C bit2, keep RW bit1 set
|
||||
await tb.write32(REG0_CTRL, 0x00000006)
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000002
|
||||
|
||||
# Write zero clears the RW bit because REG0 is a normal word write for RW fields
|
||||
await tb.write32(REG0_CTRL, 0x00000000)
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000000
|
||||
|
||||
# Full RW register write/read
|
||||
await tb.write32(REG2_CONFIG, 0xDEADBEEF)
|
||||
assert await tb.read32(REG2_CONFIG) == 0xDEADBEEF
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_w1s_seen_on_reg_o_probe(dut):
|
||||
"""Check that a W1S write creates a short-lived internal reg_o bit."""
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
tb = TB(dut)
|
||||
await tb.reset()
|
||||
|
||||
seen = False
|
||||
|
||||
async def monitor_w1s_bit():
|
||||
nonlocal seen
|
||||
for _ in range(20):
|
||||
await RisingEdge(dut.clk)
|
||||
if int(dut.reg0_o.value) & 0x1:
|
||||
seen = True
|
||||
|
||||
mon = cocotb.start_soon(monitor_w1s_bit())
|
||||
await tb.write32(REG0_CTRL, 0x00000001)
|
||||
await mon
|
||||
|
||||
assert seen, "W1S bit was never observed on reg0_o[0]"
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000004 # W1S reads as 0, W1C reset bit remains set
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_write_to_ro_returns_slverr(dut):
|
||||
"""Writing a RO bit should return SLVERR and should not change status."""
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
tb = TB(dut)
|
||||
await tb.reset()
|
||||
|
||||
dut.busy_i.value = 1
|
||||
dut.error_code_i.value = 0x33
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await tb.write32(REG1_STATUS, 0x00000101, expected_resp=SLVERR)
|
||||
assert await tb.read32(REG1_STATUS) == 0x00003301
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_invalid_address_returns_slverr(dut):
|
||||
"""N_REGS=3, so address 0x0C selects index 3 and must fail."""
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
tb = TB(dut)
|
||||
await tb.reset()
|
||||
|
||||
await tb.write32(REG_INVALID, 0x11223344, expected_resp=SLVERR)
|
||||
assert await tb.read32(REG_INVALID, expected_resp=SLVERR) == 0x00000000
|
||||
Reference in New Issue
Block a user