Add cocotb testbench for validating generated bus decoder RTL across APB3, APB4, and AXI4-Lite interfaces (#9)
* Initial plan * Add cocotb test infrastructure and testbenches for APB3, APB4, and AXI4-Lite Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * Add integration tests, examples, and documentation for cocotb testbenches Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * Address code review feedback: use relative imports and update installation docs Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * Add implementation summary document Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * Merge cocotb dependencies into test group Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * Add optional cocotb simulation workflow with Icarus Verilog Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com>
This commit is contained in:
1
tests/cocotb/testbenches/__init__.py
Normal file
1
tests/cocotb/testbenches/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Cocotb testbenches package."""
|
||||
184
tests/cocotb/testbenches/test_apb3_decoder.py
Normal file
184
tests/cocotb/testbenches/test_apb3_decoder.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""Cocotb tests for APB3 bus decoder."""
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, Timer
|
||||
|
||||
|
||||
class APB3Master:
|
||||
"""APB3 Master Bus Functional Model (no PSTRB support)."""
|
||||
|
||||
def __init__(self, dut, name, clock):
|
||||
self.dut = dut
|
||||
self.clock = clock
|
||||
self.name = name
|
||||
self.psel = getattr(dut, f"{name}_PSEL")
|
||||
self.penable = getattr(dut, f"{name}_PENABLE")
|
||||
self.pwrite = getattr(dut, f"{name}_PWRITE")
|
||||
self.paddr = getattr(dut, f"{name}_PADDR")
|
||||
self.pwdata = getattr(dut, f"{name}_PWDATA")
|
||||
self.prdata = getattr(dut, f"{name}_PRDATA")
|
||||
self.pready = getattr(dut, f"{name}_PREADY")
|
||||
self.pslverr = getattr(dut, f"{name}_PSLVERR")
|
||||
|
||||
def reset(self):
|
||||
"""Reset the bus to idle state."""
|
||||
self.psel.value = 0
|
||||
self.penable.value = 0
|
||||
self.pwrite.value = 0
|
||||
self.paddr.value = 0
|
||||
self.pwdata.value = 0
|
||||
|
||||
async def write(self, addr, data):
|
||||
"""Perform APB3 write transaction."""
|
||||
await RisingEdge(self.clock)
|
||||
self.psel.value = 1
|
||||
self.penable.value = 0
|
||||
self.pwrite.value = 1
|
||||
self.paddr.value = addr
|
||||
self.pwdata.value = data
|
||||
await RisingEdge(self.clock)
|
||||
self.penable.value = 1
|
||||
while True:
|
||||
await RisingEdge(self.clock)
|
||||
if self.pready.value == 1:
|
||||
error = self.pslverr.value == 1
|
||||
break
|
||||
self.psel.value = 0
|
||||
self.penable.value = 0
|
||||
return not error
|
||||
|
||||
async def read(self, addr):
|
||||
"""Perform APB3 read transaction."""
|
||||
await RisingEdge(self.clock)
|
||||
self.psel.value = 1
|
||||
self.penable.value = 0
|
||||
self.pwrite.value = 0
|
||||
self.paddr.value = addr
|
||||
await RisingEdge(self.clock)
|
||||
self.penable.value = 1
|
||||
while True:
|
||||
await RisingEdge(self.clock)
|
||||
if self.pready.value == 1:
|
||||
data = self.prdata.value.integer
|
||||
error = self.pslverr.value == 1
|
||||
break
|
||||
self.psel.value = 0
|
||||
self.penable.value = 0
|
||||
return data, error
|
||||
|
||||
|
||||
class APB3SlaveResponder:
|
||||
"""Simple APB3 Slave responder."""
|
||||
|
||||
def __init__(self, dut, name, clock):
|
||||
self.dut = dut
|
||||
self.clock = clock
|
||||
self.name = name
|
||||
self.psel = getattr(dut, f"{name}_PSEL")
|
||||
self.penable = getattr(dut, f"{name}_PENABLE")
|
||||
self.pwrite = getattr(dut, f"{name}_PWRITE")
|
||||
self.paddr = getattr(dut, f"{name}_PADDR")
|
||||
self.pwdata = getattr(dut, f"{name}_PWDATA")
|
||||
self.prdata = getattr(dut, f"{name}_PRDATA")
|
||||
self.pready = getattr(dut, f"{name}_PREADY")
|
||||
self.pslverr = getattr(dut, f"{name}_PSLVERR")
|
||||
self.storage = {}
|
||||
|
||||
async def run(self):
|
||||
"""Run the slave responder."""
|
||||
while True:
|
||||
await RisingEdge(self.clock)
|
||||
if self.psel.value == 1 and self.penable.value == 1:
|
||||
addr = self.paddr.value.integer
|
||||
if self.pwrite.value == 1:
|
||||
data = self.pwdata.value.integer
|
||||
self.storage[addr] = data
|
||||
self.pready.value = 1
|
||||
self.pslverr.value = 0
|
||||
else:
|
||||
data = self.storage.get(addr, 0)
|
||||
self.prdata.value = data
|
||||
self.pready.value = 1
|
||||
self.pslverr.value = 0
|
||||
else:
|
||||
self.pready.value = 0
|
||||
self.pslverr.value = 0
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_simple_read_write(dut):
|
||||
"""Test simple read and write operations on APB3."""
|
||||
clock = Clock(dut.clk, 10, units="ns")
|
||||
cocotb.start_soon(clock.start())
|
||||
|
||||
master = APB3Master(dut, "s_apb", dut.clk)
|
||||
slave = APB3SlaveResponder(dut, "m_apb_test_reg", dut.clk)
|
||||
|
||||
# Reset
|
||||
dut.rst.value = 1
|
||||
master.reset()
|
||||
await Timer(100, units="ns")
|
||||
await RisingEdge(dut.clk)
|
||||
dut.rst.value = 0
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
cocotb.start_soon(slave.run())
|
||||
|
||||
for _ in range(5):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# Write test
|
||||
dut._log.info("Writing 0xABCD1234 to address 0x0")
|
||||
success = await master.write(0x0, 0xABCD1234)
|
||||
assert success, "Write operation failed"
|
||||
|
||||
# Read test
|
||||
dut._log.info("Reading from address 0x0")
|
||||
data, error = await master.read(0x0)
|
||||
assert not error, "Read operation returned error"
|
||||
assert data == 0xABCD1234, f"Read data mismatch: expected 0xABCD1234, got 0x{data:08X}"
|
||||
|
||||
dut._log.info("Test passed!")
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_multiple_registers(dut):
|
||||
"""Test operations on multiple registers with APB3."""
|
||||
clock = Clock(dut.clk, 10, units="ns")
|
||||
cocotb.start_soon(clock.start())
|
||||
|
||||
master = APB3Master(dut, "s_apb", dut.clk)
|
||||
slave1 = APB3SlaveResponder(dut, "m_apb_reg1", dut.clk)
|
||||
slave2 = APB3SlaveResponder(dut, "m_apb_reg2", dut.clk)
|
||||
slave3 = APB3SlaveResponder(dut, "m_apb_reg3", dut.clk)
|
||||
|
||||
# Reset
|
||||
dut.rst.value = 1
|
||||
master.reset()
|
||||
await Timer(100, units="ns")
|
||||
await RisingEdge(dut.clk)
|
||||
dut.rst.value = 0
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
cocotb.start_soon(slave1.run())
|
||||
cocotb.start_soon(slave2.run())
|
||||
cocotb.start_soon(slave3.run())
|
||||
|
||||
for _ in range(5):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# Test each register
|
||||
test_data = [0x11111111, 0x22222222, 0x33333333]
|
||||
for i, data in enumerate(test_data):
|
||||
addr = i * 4
|
||||
dut._log.info(f"Writing 0x{data:08X} to address 0x{addr:X}")
|
||||
success = await master.write(addr, data)
|
||||
assert success, f"Write to address 0x{addr:X} failed"
|
||||
|
||||
dut._log.info(f"Reading from address 0x{addr:X}")
|
||||
read_data, error = await master.read(addr)
|
||||
assert not error, f"Read from address 0x{addr:X} returned error"
|
||||
assert read_data == data, f"Data mismatch at 0x{addr:X}: expected 0x{data:08X}, got 0x{read_data:08X}"
|
||||
|
||||
dut._log.info("Test passed!")
|
||||
264
tests/cocotb/testbenches/test_apb4_decoder.py
Normal file
264
tests/cocotb/testbenches/test_apb4_decoder.py
Normal file
@@ -0,0 +1,264 @@
|
||||
"""Cocotb tests for APB4 bus decoder."""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import cocotb
|
||||
import pytest
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, Timer
|
||||
|
||||
from peakrdl_busdecoder.cpuif.apb4 import APB4Cpuif
|
||||
|
||||
# Import the common test utilities
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
from common.utils import compile_rdl_and_export
|
||||
|
||||
|
||||
# APB4 Master BFM
|
||||
class APB4Master:
|
||||
"""APB4 Master Bus Functional Model."""
|
||||
|
||||
def __init__(self, dut, name, clock):
|
||||
self.dut = dut
|
||||
self.clock = clock
|
||||
self.name = name
|
||||
self.psel = getattr(dut, f"{name}_PSEL")
|
||||
self.penable = getattr(dut, f"{name}_PENABLE")
|
||||
self.pwrite = getattr(dut, f"{name}_PWRITE")
|
||||
self.paddr = getattr(dut, f"{name}_PADDR")
|
||||
self.pwdata = getattr(dut, f"{name}_PWDATA")
|
||||
self.pstrb = getattr(dut, f"{name}_PSTRB")
|
||||
self.pprot = getattr(dut, f"{name}_PPROT")
|
||||
self.prdata = getattr(dut, f"{name}_PRDATA")
|
||||
self.pready = getattr(dut, f"{name}_PREADY")
|
||||
self.pslverr = getattr(dut, f"{name}_PSLVERR")
|
||||
|
||||
def reset(self):
|
||||
"""Reset the bus to idle state."""
|
||||
self.psel.value = 0
|
||||
self.penable.value = 0
|
||||
self.pwrite.value = 0
|
||||
self.paddr.value = 0
|
||||
self.pwdata.value = 0
|
||||
self.pstrb.value = 0
|
||||
self.pprot.value = 0
|
||||
|
||||
async def write(self, addr, data, strb=None):
|
||||
"""Perform APB4 write transaction."""
|
||||
if strb is None:
|
||||
strb = 0xF
|
||||
await RisingEdge(self.clock)
|
||||
self.psel.value = 1
|
||||
self.penable.value = 0
|
||||
self.pwrite.value = 1
|
||||
self.paddr.value = addr
|
||||
self.pwdata.value = data
|
||||
self.pstrb.value = strb
|
||||
self.pprot.value = 0
|
||||
await RisingEdge(self.clock)
|
||||
self.penable.value = 1
|
||||
while True:
|
||||
await RisingEdge(self.clock)
|
||||
if self.pready.value == 1:
|
||||
error = self.pslverr.value == 1
|
||||
break
|
||||
self.psel.value = 0
|
||||
self.penable.value = 0
|
||||
return not error
|
||||
|
||||
async def read(self, addr):
|
||||
"""Perform APB4 read transaction."""
|
||||
await RisingEdge(self.clock)
|
||||
self.psel.value = 1
|
||||
self.penable.value = 0
|
||||
self.pwrite.value = 0
|
||||
self.paddr.value = addr
|
||||
self.pprot.value = 0
|
||||
await RisingEdge(self.clock)
|
||||
self.penable.value = 1
|
||||
while True:
|
||||
await RisingEdge(self.clock)
|
||||
if self.pready.value == 1:
|
||||
data = self.prdata.value.integer
|
||||
error = self.pslverr.value == 1
|
||||
break
|
||||
self.psel.value = 0
|
||||
self.penable.value = 0
|
||||
return data, error
|
||||
|
||||
|
||||
# APB4 Slave responder
|
||||
class APB4SlaveResponder:
|
||||
"""Simple APB4 Slave responder that acknowledges all transactions."""
|
||||
|
||||
def __init__(self, dut, name, clock):
|
||||
self.dut = dut
|
||||
self.clock = clock
|
||||
self.name = name
|
||||
self.psel = getattr(dut, f"{name}_PSEL")
|
||||
self.penable = getattr(dut, f"{name}_PENABLE")
|
||||
self.pwrite = getattr(dut, f"{name}_PWRITE")
|
||||
self.paddr = getattr(dut, f"{name}_PADDR")
|
||||
self.pwdata = getattr(dut, f"{name}_PWDATA")
|
||||
self.pstrb = getattr(dut, f"{name}_PSTRB")
|
||||
self.prdata = getattr(dut, f"{name}_PRDATA")
|
||||
self.pready = getattr(dut, f"{name}_PREADY")
|
||||
self.pslverr = getattr(dut, f"{name}_PSLVERR")
|
||||
# Storage for register values
|
||||
self.storage = {}
|
||||
|
||||
async def run(self):
|
||||
"""Run the slave responder."""
|
||||
while True:
|
||||
await RisingEdge(self.clock)
|
||||
if self.psel.value == 1 and self.penable.value == 1:
|
||||
addr = self.paddr.value.integer
|
||||
if self.pwrite.value == 1:
|
||||
# Write operation
|
||||
data = self.pwdata.value.integer
|
||||
self.storage[addr] = data
|
||||
self.pready.value = 1
|
||||
self.pslverr.value = 0
|
||||
else:
|
||||
# Read operation
|
||||
data = self.storage.get(addr, 0)
|
||||
self.prdata.value = data
|
||||
self.pready.value = 1
|
||||
self.pslverr.value = 0
|
||||
else:
|
||||
self.pready.value = 0
|
||||
self.pslverr.value = 0
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_simple_read_write(dut):
|
||||
"""Test simple read and write operations."""
|
||||
# Start clock
|
||||
clock = Clock(dut.clk, 10, units="ns")
|
||||
cocotb.start_soon(clock.start())
|
||||
|
||||
# Create master and slave
|
||||
master = APB4Master(dut, "s_apb", dut.clk)
|
||||
slave = APB4SlaveResponder(dut, "m_apb_test_reg", dut.clk)
|
||||
|
||||
# Reset
|
||||
dut.rst.value = 1
|
||||
master.reset()
|
||||
await Timer(100, units="ns")
|
||||
await RisingEdge(dut.clk)
|
||||
dut.rst.value = 0
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# Start slave responder
|
||||
cocotb.start_soon(slave.run())
|
||||
|
||||
# Wait a few cycles
|
||||
for _ in range(5):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# Write test
|
||||
dut._log.info("Writing 0xDEADBEEF to address 0x0")
|
||||
success = await master.write(0x0, 0xDEADBEEF)
|
||||
assert success, "Write operation failed"
|
||||
|
||||
# Read test
|
||||
dut._log.info("Reading from address 0x0")
|
||||
data, error = await master.read(0x0)
|
||||
assert not error, "Read operation returned error"
|
||||
assert data == 0xDEADBEEF, f"Read data mismatch: expected 0xDEADBEEF, got 0x{data:08X}"
|
||||
|
||||
dut._log.info("Test passed!")
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_multiple_registers(dut):
|
||||
"""Test operations on multiple registers."""
|
||||
# Start clock
|
||||
clock = Clock(dut.clk, 10, units="ns")
|
||||
cocotb.start_soon(clock.start())
|
||||
|
||||
# Create master and slaves
|
||||
master = APB4Master(dut, "s_apb", dut.clk)
|
||||
slave1 = APB4SlaveResponder(dut, "m_apb_reg1", dut.clk)
|
||||
slave2 = APB4SlaveResponder(dut, "m_apb_reg2", dut.clk)
|
||||
slave3 = APB4SlaveResponder(dut, "m_apb_reg3", dut.clk)
|
||||
|
||||
# Reset
|
||||
dut.rst.value = 1
|
||||
master.reset()
|
||||
await Timer(100, units="ns")
|
||||
await RisingEdge(dut.clk)
|
||||
dut.rst.value = 0
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# Start slave responders
|
||||
cocotb.start_soon(slave1.run())
|
||||
cocotb.start_soon(slave2.run())
|
||||
cocotb.start_soon(slave3.run())
|
||||
|
||||
# Wait a few cycles
|
||||
for _ in range(5):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# Test each register
|
||||
test_data = [0x12345678, 0xABCDEF00, 0xCAFEBABE]
|
||||
for i, data in enumerate(test_data):
|
||||
addr = i * 4
|
||||
dut._log.info(f"Writing 0x{data:08X} to address 0x{addr:X}")
|
||||
success = await master.write(addr, data)
|
||||
assert success, f"Write to address 0x{addr:X} failed"
|
||||
|
||||
dut._log.info(f"Reading from address 0x{addr:X}")
|
||||
read_data, error = await master.read(addr)
|
||||
assert not error, f"Read from address 0x{addr:X} returned error"
|
||||
assert read_data == data, f"Data mismatch at 0x{addr:X}: expected 0x{data:08X}, got 0x{read_data:08X}"
|
||||
|
||||
dut._log.info("Test passed!")
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_byte_strobe(dut):
|
||||
"""Test byte strobe functionality."""
|
||||
# Start clock
|
||||
clock = Clock(dut.clk, 10, units="ns")
|
||||
cocotb.start_soon(clock.start())
|
||||
|
||||
# Create master and slave
|
||||
master = APB4Master(dut, "s_apb", dut.clk)
|
||||
slave = APB4SlaveResponder(dut, "m_apb_test_reg", dut.clk)
|
||||
|
||||
# Reset
|
||||
dut.rst.value = 1
|
||||
master.reset()
|
||||
await Timer(100, units="ns")
|
||||
await RisingEdge(dut.clk)
|
||||
dut.rst.value = 0
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# Start slave responder
|
||||
cocotb.start_soon(slave.run())
|
||||
|
||||
# Wait a few cycles
|
||||
for _ in range(5):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# Write full word
|
||||
await master.write(0x0, 0x12345678, strb=0xF)
|
||||
|
||||
# Read back
|
||||
data, error = await master.read(0x0)
|
||||
assert not error
|
||||
assert data == 0x12345678
|
||||
|
||||
# Write only lower byte
|
||||
await master.write(0x0, 0x000000AB, strb=0x1)
|
||||
data, error = await master.read(0x0)
|
||||
assert not error
|
||||
assert (data & 0xFF) == 0xAB
|
||||
|
||||
dut._log.info("Test passed!")
|
||||
|
||||
260
tests/cocotb/testbenches/test_apb4_runner.py
Normal file
260
tests/cocotb/testbenches/test_apb4_runner.py
Normal file
@@ -0,0 +1,260 @@
|
||||
"""Pytest test runner for APB4 cocotb tests."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from peakrdl_busdecoder.cpuif.apb4 import APB4Cpuif
|
||||
|
||||
# Import the common test utilities
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
from common.utils import compile_rdl_and_export
|
||||
|
||||
|
||||
# Check if Icarus Verilog is available
|
||||
def is_simulator_available():
|
||||
"""Check if Icarus Verilog simulator is available."""
|
||||
return shutil.which("iverilog") is not None
|
||||
|
||||
|
||||
# Skip tests if simulator is not available
|
||||
skip_if_no_simulator = pytest.mark.skipif(
|
||||
not is_simulator_available(),
|
||||
reason="Requires Icarus Verilog or other simulator to be installed"
|
||||
)
|
||||
|
||||
|
||||
def generate_testbench_wrapper(top_name, slave_ports, tmpdir_path):
|
||||
"""
|
||||
Generate a testbench wrapper that exposes interface signals.
|
||||
|
||||
Args:
|
||||
top_name: Name of the top-level module
|
||||
slave_ports: List of slave port names
|
||||
tmpdir_path: Path to temporary directory
|
||||
|
||||
Returns:
|
||||
Path to generated testbench file
|
||||
"""
|
||||
tb_path = tmpdir_path / f"tb_{top_name}.sv"
|
||||
with open(tb_path, "w") as f:
|
||||
f.write(f"""
|
||||
module tb_{top_name} (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
// Instantiate APB4 interfaces
|
||||
apb4_intf #(
|
||||
.DATA_WIDTH(32),
|
||||
.ADDR_WIDTH(32)
|
||||
) s_apb ();
|
||||
|
||||
""")
|
||||
# Create interface instances for each slave port
|
||||
for port in slave_ports:
|
||||
f.write(f"""
|
||||
apb4_intf #(
|
||||
.DATA_WIDTH(32),
|
||||
.ADDR_WIDTH(32)
|
||||
) {port} ();
|
||||
""")
|
||||
|
||||
# Wire master signals
|
||||
f.write("""
|
||||
// Wire master signals from interface to top level for cocotb access
|
||||
logic s_apb_PSEL;
|
||||
logic s_apb_PENABLE;
|
||||
logic s_apb_PWRITE;
|
||||
logic [31:0] s_apb_PADDR;
|
||||
logic [31:0] s_apb_PWDATA;
|
||||
logic [3:0] s_apb_PSTRB;
|
||||
logic [2:0] s_apb_PPROT;
|
||||
logic [31:0] s_apb_PRDATA;
|
||||
logic s_apb_PREADY;
|
||||
logic s_apb_PSLVERR;
|
||||
|
||||
assign s_apb.PSEL = s_apb_PSEL;
|
||||
assign s_apb.PENABLE = s_apb_PENABLE;
|
||||
assign s_apb.PWRITE = s_apb_PWRITE;
|
||||
assign s_apb.PADDR = s_apb_PADDR;
|
||||
assign s_apb.PWDATA = s_apb_PWDATA;
|
||||
assign s_apb.PSTRB = s_apb_PSTRB;
|
||||
assign s_apb.PPROT = s_apb_PPROT;
|
||||
assign s_apb_PRDATA = s_apb.PRDATA;
|
||||
assign s_apb_PREADY = s_apb.PREADY;
|
||||
assign s_apb_PSLVERR = s_apb.PSLVERR;
|
||||
|
||||
""")
|
||||
|
||||
# Wire slave signals
|
||||
for port in slave_ports:
|
||||
f.write(f"""
|
||||
logic {port}_PSEL;
|
||||
logic {port}_PENABLE;
|
||||
logic {port}_PWRITE;
|
||||
logic [31:0] {port}_PADDR;
|
||||
logic [31:0] {port}_PWDATA;
|
||||
logic [3:0] {port}_PSTRB;
|
||||
logic [31:0] {port}_PRDATA;
|
||||
logic {port}_PREADY;
|
||||
logic {port}_PSLVERR;
|
||||
|
||||
assign {port}_PSEL = {port}.PSEL;
|
||||
assign {port}_PENABLE = {port}.PENABLE;
|
||||
assign {port}_PWRITE = {port}.PWRITE;
|
||||
assign {port}_PADDR = {port}.PADDR;
|
||||
assign {port}_PWDATA = {port}.PWDATA;
|
||||
assign {port}_PSTRB = {port}.PSTRB;
|
||||
assign {port}.PRDATA = {port}_PRDATA;
|
||||
assign {port}.PREADY = {port}_PREADY;
|
||||
assign {port}.PSLVERR = {port}_PSLVERR;
|
||||
|
||||
""")
|
||||
|
||||
# Instantiate DUT
|
||||
f.write(f"""
|
||||
// Instantiate DUT
|
||||
{top_name} dut (
|
||||
.s_apb(s_apb)""")
|
||||
|
||||
for port in slave_ports:
|
||||
f.write(f",\n .{port}({port})")
|
||||
|
||||
f.write("""
|
||||
);
|
||||
|
||||
// Dump waves
|
||||
initial begin
|
||||
$dumpfile("dump.vcd");
|
||||
$dumpvars(0, tb_{top_name});
|
||||
end
|
||||
endmodule
|
||||
""".format(top_name=top_name))
|
||||
|
||||
return tb_path
|
||||
|
||||
|
||||
@skip_if_no_simulator
|
||||
def test_apb4_simple_register():
|
||||
"""Test APB4 decoder with a simple register."""
|
||||
rdl_source = """
|
||||
addrmap simple_test {
|
||||
reg {
|
||||
field {
|
||||
sw=rw;
|
||||
hw=r;
|
||||
} data[31:0];
|
||||
} test_reg @ 0x0;
|
||||
};
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmpdir_path = Path(tmpdir)
|
||||
|
||||
# Compile RDL and export SystemVerilog
|
||||
module_path, package_path = compile_rdl_and_export(
|
||||
rdl_source, "simple_test", str(tmpdir_path), APB4Cpuif
|
||||
)
|
||||
|
||||
# Generate testbench wrapper
|
||||
tb_path = generate_testbench_wrapper(
|
||||
"simple_test", ["m_apb_test_reg"], tmpdir_path
|
||||
)
|
||||
|
||||
# Get HDL source directory
|
||||
hdl_src_dir = Path(__file__).parent.parent.parent.parent / "hdl-src"
|
||||
|
||||
# Run simulation using cocotb.runner
|
||||
from cocotb.runner import get_runner
|
||||
|
||||
runner = get_runner("icarus")
|
||||
runner.build(
|
||||
verilog_sources=[
|
||||
str(hdl_src_dir / "apb4_intf.sv"),
|
||||
str(package_path),
|
||||
str(module_path),
|
||||
str(tb_path),
|
||||
],
|
||||
hdl_toplevel="tb_simple_test",
|
||||
always=True,
|
||||
build_dir=str(tmpdir_path / "sim_build"),
|
||||
)
|
||||
|
||||
runner.test(
|
||||
hdl_toplevel="tb_simple_test",
|
||||
test_module="test_apb4_decoder",
|
||||
build_dir=str(tmpdir_path / "sim_build"),
|
||||
)
|
||||
|
||||
|
||||
@skip_if_no_simulator
|
||||
def test_apb4_multiple_registers():
|
||||
"""Test APB4 decoder with multiple registers."""
|
||||
rdl_source = """
|
||||
addrmap multi_reg {
|
||||
reg {
|
||||
field {
|
||||
sw=rw;
|
||||
hw=r;
|
||||
} data[31:0];
|
||||
} reg1 @ 0x0;
|
||||
|
||||
reg {
|
||||
field {
|
||||
sw=r;
|
||||
hw=w;
|
||||
} status[15:0];
|
||||
} reg2 @ 0x4;
|
||||
|
||||
reg {
|
||||
field {
|
||||
sw=rw;
|
||||
hw=r;
|
||||
} control[7:0];
|
||||
} reg3 @ 0x8;
|
||||
};
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmpdir_path = Path(tmpdir)
|
||||
|
||||
# Compile RDL and export SystemVerilog
|
||||
module_path, package_path = compile_rdl_and_export(
|
||||
rdl_source, "multi_reg", str(tmpdir_path), APB4Cpuif
|
||||
)
|
||||
|
||||
# Generate testbench wrapper
|
||||
tb_path = generate_testbench_wrapper(
|
||||
"multi_reg", ["m_apb_reg1", "m_apb_reg2", "m_apb_reg3"], tmpdir_path
|
||||
)
|
||||
|
||||
# Get HDL source directory
|
||||
hdl_src_dir = Path(__file__).parent.parent.parent.parent / "hdl-src"
|
||||
|
||||
# Run simulation
|
||||
from cocotb.runner import get_runner
|
||||
|
||||
runner = get_runner("icarus")
|
||||
runner.build(
|
||||
verilog_sources=[
|
||||
str(hdl_src_dir / "apb4_intf.sv"),
|
||||
str(package_path),
|
||||
str(module_path),
|
||||
str(tb_path),
|
||||
],
|
||||
hdl_toplevel="tb_multi_reg",
|
||||
always=True,
|
||||
build_dir=str(tmpdir_path / "sim_build"),
|
||||
)
|
||||
|
||||
runner.test(
|
||||
hdl_toplevel="tb_multi_reg",
|
||||
test_module="test_apb4_decoder",
|
||||
test_args=["--test-case=test_multiple_registers"],
|
||||
build_dir=str(tmpdir_path / "sim_build"),
|
||||
)
|
||||
311
tests/cocotb/testbenches/test_axi4lite_decoder.py
Normal file
311
tests/cocotb/testbenches/test_axi4lite_decoder.py
Normal file
@@ -0,0 +1,311 @@
|
||||
"""Cocotb tests for AXI4-Lite bus decoder."""
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, Timer
|
||||
|
||||
|
||||
class AXI4LiteMaster:
|
||||
"""AXI4-Lite Master Bus Functional Model."""
|
||||
|
||||
def __init__(self, dut, name, clock):
|
||||
self.dut = dut
|
||||
self.clock = clock
|
||||
self.name = name
|
||||
|
||||
# Write address channel
|
||||
self.awvalid = getattr(dut, f"{name}_AWVALID")
|
||||
self.awready = getattr(dut, f"{name}_AWREADY")
|
||||
self.awaddr = getattr(dut, f"{name}_AWADDR")
|
||||
self.awprot = getattr(dut, f"{name}_AWPROT")
|
||||
|
||||
# Write data channel
|
||||
self.wvalid = getattr(dut, f"{name}_WVALID")
|
||||
self.wready = getattr(dut, f"{name}_WREADY")
|
||||
self.wdata = getattr(dut, f"{name}_WDATA")
|
||||
self.wstrb = getattr(dut, f"{name}_WSTRB")
|
||||
|
||||
# Write response channel
|
||||
self.bvalid = getattr(dut, f"{name}_BVALID")
|
||||
self.bready = getattr(dut, f"{name}_BREADY")
|
||||
self.bresp = getattr(dut, f"{name}_BRESP")
|
||||
|
||||
# Read address channel
|
||||
self.arvalid = getattr(dut, f"{name}_ARVALID")
|
||||
self.arready = getattr(dut, f"{name}_ARREADY")
|
||||
self.araddr = getattr(dut, f"{name}_ARADDR")
|
||||
self.arprot = getattr(dut, f"{name}_ARPROT")
|
||||
|
||||
# Read data channel
|
||||
self.rvalid = getattr(dut, f"{name}_RVALID")
|
||||
self.rready = getattr(dut, f"{name}_RREADY")
|
||||
self.rdata = getattr(dut, f"{name}_RDATA")
|
||||
self.rresp = getattr(dut, f"{name}_RRESP")
|
||||
|
||||
def reset(self):
|
||||
"""Reset the bus to idle state."""
|
||||
self.awvalid.value = 0
|
||||
self.awaddr.value = 0
|
||||
self.awprot.value = 0
|
||||
self.wvalid.value = 0
|
||||
self.wdata.value = 0
|
||||
self.wstrb.value = 0
|
||||
self.bready.value = 1
|
||||
self.arvalid.value = 0
|
||||
self.araddr.value = 0
|
||||
self.arprot.value = 0
|
||||
self.rready.value = 1
|
||||
|
||||
async def write(self, addr, data, strb=None):
|
||||
"""Perform AXI4-Lite write transaction."""
|
||||
if strb is None:
|
||||
strb = 0xF
|
||||
|
||||
# Write address phase
|
||||
await RisingEdge(self.clock)
|
||||
self.awvalid.value = 1
|
||||
self.awaddr.value = addr
|
||||
self.awprot.value = 0
|
||||
|
||||
# Write data phase
|
||||
self.wvalid.value = 1
|
||||
self.wdata.value = data
|
||||
self.wstrb.value = strb
|
||||
|
||||
# Wait for address accept
|
||||
while True:
|
||||
await RisingEdge(self.clock)
|
||||
if self.awready.value == 1:
|
||||
self.awvalid.value = 0
|
||||
break
|
||||
|
||||
# Wait for data accept
|
||||
while self.wready.value != 1:
|
||||
await RisingEdge(self.clock)
|
||||
self.wvalid.value = 0
|
||||
|
||||
# Wait for write response
|
||||
self.bready.value = 1
|
||||
while self.bvalid.value != 1:
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
error = self.bresp.value != 0
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
return not error
|
||||
|
||||
async def read(self, addr):
|
||||
"""Perform AXI4-Lite read transaction."""
|
||||
# Read address phase
|
||||
await RisingEdge(self.clock)
|
||||
self.arvalid.value = 1
|
||||
self.araddr.value = addr
|
||||
self.arprot.value = 0
|
||||
|
||||
# Wait for address accept
|
||||
while True:
|
||||
await RisingEdge(self.clock)
|
||||
if self.arready.value == 1:
|
||||
self.arvalid.value = 0
|
||||
break
|
||||
|
||||
# Wait for read data
|
||||
self.rready.value = 1
|
||||
while self.rvalid.value != 1:
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
data = self.rdata.value.integer
|
||||
error = self.rresp.value != 0
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
return data, error
|
||||
|
||||
|
||||
class AXI4LiteSlaveResponder:
|
||||
"""Simple AXI4-Lite Slave responder."""
|
||||
|
||||
def __init__(self, dut, name, clock):
|
||||
self.dut = dut
|
||||
self.clock = clock
|
||||
self.name = name
|
||||
|
||||
# Get all signals
|
||||
self.awvalid = getattr(dut, f"{name}_AWVALID")
|
||||
self.awready = getattr(dut, f"{name}_AWREADY")
|
||||
self.awaddr = getattr(dut, f"{name}_AWADDR")
|
||||
self.wvalid = getattr(dut, f"{name}_WVALID")
|
||||
self.wready = getattr(dut, f"{name}_WREADY")
|
||||
self.wdata = getattr(dut, f"{name}_WDATA")
|
||||
self.wstrb = getattr(dut, f"{name}_WSTRB")
|
||||
self.bvalid = getattr(dut, f"{name}_BVALID")
|
||||
self.bready = getattr(dut, f"{name}_BREADY")
|
||||
self.bresp = getattr(dut, f"{name}_BRESP")
|
||||
self.arvalid = getattr(dut, f"{name}_ARVALID")
|
||||
self.arready = getattr(dut, f"{name}_ARREADY")
|
||||
self.araddr = getattr(dut, f"{name}_ARADDR")
|
||||
self.rvalid = getattr(dut, f"{name}_RVALID")
|
||||
self.rready = getattr(dut, f"{name}_RREADY")
|
||||
self.rdata = getattr(dut, f"{name}_RDATA")
|
||||
self.rresp = getattr(dut, f"{name}_RRESP")
|
||||
|
||||
self.storage = {}
|
||||
self.write_pending = False
|
||||
self.pending_addr = 0
|
||||
self.pending_data = 0
|
||||
|
||||
async def run(self):
|
||||
"""Run the slave responder."""
|
||||
while True:
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
# Handle write address channel
|
||||
if self.awvalid.value == 1 and not self.write_pending:
|
||||
self.awready.value = 1
|
||||
self.pending_addr = self.awaddr.value.integer
|
||||
self.write_pending = True
|
||||
else:
|
||||
self.awready.value = 0
|
||||
|
||||
# Handle write data channel
|
||||
if self.wvalid.value == 1 and self.write_pending:
|
||||
self.wready.value = 1
|
||||
self.pending_data = self.wdata.value.integer
|
||||
self.storage[self.pending_addr] = self.pending_data
|
||||
# Send write response
|
||||
self.bvalid.value = 1
|
||||
self.bresp.value = 0
|
||||
self.write_pending = False
|
||||
else:
|
||||
self.wready.value = 0
|
||||
if self.bvalid.value == 1 and self.bready.value == 1:
|
||||
self.bvalid.value = 0
|
||||
|
||||
# Handle read address channel
|
||||
if self.arvalid.value == 1:
|
||||
self.arready.value = 1
|
||||
addr = self.araddr.value.integer
|
||||
data = self.storage.get(addr, 0)
|
||||
self.rdata.value = data
|
||||
self.rvalid.value = 1
|
||||
self.rresp.value = 0
|
||||
else:
|
||||
self.arready.value = 0
|
||||
if self.rvalid.value == 1 and self.rready.value == 1:
|
||||
self.rvalid.value = 0
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_simple_read_write(dut):
|
||||
"""Test simple read and write operations on AXI4-Lite."""
|
||||
clock = Clock(dut.clk, 10, units="ns")
|
||||
cocotb.start_soon(clock.start())
|
||||
|
||||
master = AXI4LiteMaster(dut, "s_axi", dut.clk)
|
||||
slave = AXI4LiteSlaveResponder(dut, "m_axi_test_reg", dut.clk)
|
||||
|
||||
# Reset
|
||||
dut.rst.value = 1
|
||||
master.reset()
|
||||
await Timer(100, units="ns")
|
||||
await RisingEdge(dut.clk)
|
||||
dut.rst.value = 0
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
cocotb.start_soon(slave.run())
|
||||
|
||||
for _ in range(5):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# Write test
|
||||
dut._log.info("Writing 0xFEEDFACE to address 0x0")
|
||||
success = await master.write(0x0, 0xFEEDFACE)
|
||||
assert success, "Write operation failed"
|
||||
|
||||
# Read test
|
||||
dut._log.info("Reading from address 0x0")
|
||||
data, error = await master.read(0x0)
|
||||
assert not error, "Read operation returned error"
|
||||
assert data == 0xFEEDFACE, f"Read data mismatch: expected 0xFEEDFACE, got 0x{data:08X}"
|
||||
|
||||
dut._log.info("Test passed!")
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_multiple_registers(dut):
|
||||
"""Test operations on multiple registers with AXI4-Lite."""
|
||||
clock = Clock(dut.clk, 10, units="ns")
|
||||
cocotb.start_soon(clock.start())
|
||||
|
||||
master = AXI4LiteMaster(dut, "s_axi", dut.clk)
|
||||
slave1 = AXI4LiteSlaveResponder(dut, "m_axi_reg1", dut.clk)
|
||||
slave2 = AXI4LiteSlaveResponder(dut, "m_axi_reg2", dut.clk)
|
||||
slave3 = AXI4LiteSlaveResponder(dut, "m_axi_reg3", dut.clk)
|
||||
|
||||
# Reset
|
||||
dut.rst.value = 1
|
||||
master.reset()
|
||||
await Timer(100, units="ns")
|
||||
await RisingEdge(dut.clk)
|
||||
dut.rst.value = 0
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
cocotb.start_soon(slave1.run())
|
||||
cocotb.start_soon(slave2.run())
|
||||
cocotb.start_soon(slave3.run())
|
||||
|
||||
for _ in range(5):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# Test each register
|
||||
test_data = [0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC]
|
||||
for i, data in enumerate(test_data):
|
||||
addr = i * 4
|
||||
dut._log.info(f"Writing 0x{data:08X} to address 0x{addr:X}")
|
||||
success = await master.write(addr, data)
|
||||
assert success, f"Write to address 0x{addr:X} failed"
|
||||
|
||||
dut._log.info(f"Reading from address 0x{addr:X}")
|
||||
read_data, error = await master.read(addr)
|
||||
assert not error, f"Read from address 0x{addr:X} returned error"
|
||||
assert read_data == data, f"Data mismatch at 0x{addr:X}: expected 0x{data:08X}, got 0x{read_data:08X}"
|
||||
|
||||
dut._log.info("Test passed!")
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_byte_strobe(dut):
|
||||
"""Test byte strobe functionality with AXI4-Lite."""
|
||||
clock = Clock(dut.clk, 10, units="ns")
|
||||
cocotb.start_soon(clock.start())
|
||||
|
||||
master = AXI4LiteMaster(dut, "s_axi", dut.clk)
|
||||
slave = AXI4LiteSlaveResponder(dut, "m_axi_test_reg", dut.clk)
|
||||
|
||||
# Reset
|
||||
dut.rst.value = 1
|
||||
master.reset()
|
||||
await Timer(100, units="ns")
|
||||
await RisingEdge(dut.clk)
|
||||
dut.rst.value = 0
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
cocotb.start_soon(slave.run())
|
||||
|
||||
for _ in range(5):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# Write full word
|
||||
await master.write(0x0, 0x12345678, strb=0xF)
|
||||
|
||||
# Read back
|
||||
data, error = await master.read(0x0)
|
||||
assert not error
|
||||
assert data == 0x12345678
|
||||
|
||||
# Write only lower byte
|
||||
await master.write(0x0, 0x000000CD, strb=0x1)
|
||||
data, error = await master.read(0x0)
|
||||
assert not error
|
||||
assert (data & 0xFF) == 0xCD
|
||||
|
||||
dut._log.info("Test passed!")
|
||||
216
tests/cocotb/testbenches/test_integration.py
Normal file
216
tests/cocotb/testbenches/test_integration.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
Integration tests for cocotb testbench infrastructure.
|
||||
|
||||
These tests validate that the code generation and testbench setup works correctly
|
||||
without requiring an actual HDL simulator. They check:
|
||||
- RDL compilation and SystemVerilog generation
|
||||
- Generated code contains expected elements
|
||||
- Testbench utilities work correctly
|
||||
"""
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from peakrdl_busdecoder.cpuif.apb3 import APB3Cpuif
|
||||
from peakrdl_busdecoder.cpuif.apb4 import APB4Cpuif
|
||||
from peakrdl_busdecoder.cpuif.axi4lite import AXI4LiteCpuif
|
||||
|
||||
from ..common.utils import compile_rdl_and_export, get_verilog_sources
|
||||
|
||||
|
||||
class TestCodeGeneration:
|
||||
"""Test code generation for different CPU interfaces."""
|
||||
|
||||
def test_apb4_simple_register(self):
|
||||
"""Test APB4 code generation for simple register."""
|
||||
rdl_source = """
|
||||
addrmap simple_test {
|
||||
reg {
|
||||
field { sw=rw; hw=r; } data[31:0];
|
||||
} test_reg @ 0x0;
|
||||
};
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
module_path, package_path = compile_rdl_and_export(
|
||||
rdl_source, "simple_test", tmpdir, APB4Cpuif
|
||||
)
|
||||
|
||||
# Verify files exist
|
||||
assert module_path.exists()
|
||||
assert package_path.exists()
|
||||
|
||||
# Verify module content
|
||||
module_content = module_path.read_text()
|
||||
assert "module simple_test" in module_content
|
||||
assert "apb4_intf.slave s_apb" in module_content
|
||||
assert "test_reg" in module_content
|
||||
|
||||
# Verify package content
|
||||
package_content = package_path.read_text()
|
||||
assert "package simple_test_pkg" in package_content
|
||||
|
||||
def test_apb3_multiple_registers(self):
|
||||
"""Test APB3 code generation for multiple registers."""
|
||||
rdl_source = """
|
||||
addrmap multi_reg {
|
||||
reg { field { sw=rw; hw=r; } data[31:0]; } reg1 @ 0x0;
|
||||
reg { field { sw=r; hw=w; } status[15:0]; } reg2 @ 0x4;
|
||||
reg { field { sw=rw; hw=r; } control[7:0]; } reg3 @ 0x8;
|
||||
};
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
module_path, package_path = compile_rdl_and_export(
|
||||
rdl_source, "multi_reg", tmpdir, APB3Cpuif
|
||||
)
|
||||
|
||||
assert module_path.exists()
|
||||
assert package_path.exists()
|
||||
|
||||
module_content = module_path.read_text()
|
||||
assert "module multi_reg" in module_content
|
||||
assert "apb3_intf.slave s_apb" in module_content
|
||||
assert "reg1" in module_content
|
||||
assert "reg2" in module_content
|
||||
assert "reg3" in module_content
|
||||
|
||||
def test_axi4lite_nested_addrmap(self):
|
||||
"""Test AXI4-Lite code generation for nested address map."""
|
||||
rdl_source = """
|
||||
addrmap inner_block {
|
||||
reg { field { sw=rw; hw=r; } data[31:0]; } inner_reg @ 0x0;
|
||||
};
|
||||
|
||||
addrmap outer_block {
|
||||
inner_block inner @ 0x0;
|
||||
reg { field { sw=rw; hw=r; } outer_data[31:0]; } outer_reg @ 0x100;
|
||||
};
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
module_path, package_path = compile_rdl_and_export(
|
||||
rdl_source, "outer_block", tmpdir, AXI4LiteCpuif
|
||||
)
|
||||
|
||||
assert module_path.exists()
|
||||
assert package_path.exists()
|
||||
|
||||
module_content = module_path.read_text()
|
||||
assert "module outer_block" in module_content
|
||||
assert "axi4lite_intf.slave s_axi" in module_content
|
||||
assert "inner" in module_content
|
||||
assert "outer_reg" in module_content
|
||||
|
||||
def test_register_array(self):
|
||||
"""Test code generation with register arrays."""
|
||||
rdl_source = """
|
||||
addrmap array_test {
|
||||
reg { field { sw=rw; hw=r; } data[31:0]; } regs[4] @ 0x0 += 0x4;
|
||||
};
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
module_path, package_path = compile_rdl_and_export(
|
||||
rdl_source, "array_test", tmpdir, APB4Cpuif
|
||||
)
|
||||
|
||||
assert module_path.exists()
|
||||
assert package_path.exists()
|
||||
|
||||
module_content = module_path.read_text()
|
||||
assert "module array_test" in module_content
|
||||
assert "regs" in module_content
|
||||
|
||||
|
||||
class TestUtilityFunctions:
|
||||
"""Test utility functions for testbench setup."""
|
||||
|
||||
def test_get_verilog_sources(self):
|
||||
"""Test that get_verilog_sources returns correct file list."""
|
||||
hdl_src_dir = Path(__file__).parent.parent.parent.parent / "hdl-src"
|
||||
|
||||
module_path = Path("/tmp/test_module.sv")
|
||||
package_path = Path("/tmp/test_pkg.sv")
|
||||
intf_files = [
|
||||
hdl_src_dir / "apb4_intf.sv",
|
||||
hdl_src_dir / "apb3_intf.sv",
|
||||
]
|
||||
|
||||
sources = get_verilog_sources(module_path, package_path, intf_files)
|
||||
|
||||
# Verify order: interfaces first, then package, then module
|
||||
assert len(sources) == 4
|
||||
assert str(intf_files[0]) in sources[0]
|
||||
assert str(intf_files[1]) in sources[1]
|
||||
assert str(package_path) in sources[2]
|
||||
assert str(module_path) in sources[3]
|
||||
|
||||
def test_compile_rdl_and_export_with_custom_names(self):
|
||||
"""Test code generation with custom module and package names."""
|
||||
rdl_source = """
|
||||
addrmap test_map {
|
||||
reg { field { sw=rw; hw=r; } data[31:0]; } test_reg @ 0x0;
|
||||
};
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
module_path, package_path = compile_rdl_and_export(
|
||||
rdl_source,
|
||||
"test_map",
|
||||
tmpdir,
|
||||
APB4Cpuif,
|
||||
module_name="custom_module",
|
||||
package_name="custom_pkg",
|
||||
)
|
||||
|
||||
# Verify custom names
|
||||
assert module_path.name == "custom_module.sv"
|
||||
assert package_path.name == "custom_pkg.sv"
|
||||
|
||||
# Verify content uses custom names
|
||||
module_content = module_path.read_text()
|
||||
assert "module custom_module" in module_content
|
||||
|
||||
package_content = package_path.read_text()
|
||||
assert "package custom_pkg" in package_content
|
||||
|
||||
|
||||
class TestMultipleCpuInterfaces:
|
||||
"""Test that all CPU interfaces generate valid code."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cpuif_cls,intf_name",
|
||||
[
|
||||
(APB3Cpuif, "apb3_intf"),
|
||||
(APB4Cpuif, "apb4_intf"),
|
||||
(AXI4LiteCpuif, "axi4lite_intf"),
|
||||
],
|
||||
)
|
||||
def test_cpuif_generation(self, cpuif_cls, intf_name):
|
||||
"""Test code generation for each CPU interface type."""
|
||||
rdl_source = """
|
||||
addrmap test_block {
|
||||
reg {
|
||||
field { sw=rw; hw=r; } data[31:0];
|
||||
} test_reg @ 0x0;
|
||||
};
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
module_path, package_path = compile_rdl_and_export(
|
||||
rdl_source, "test_block", tmpdir, cpuif_cls
|
||||
)
|
||||
|
||||
assert module_path.exists()
|
||||
assert package_path.exists()
|
||||
|
||||
module_content = module_path.read_text()
|
||||
assert "module test_block" in module_content
|
||||
assert intf_name in module_content
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user