Files
PeakRDL-BusDecoder/tests/cocotb/testbenches/test_apb4_runner.py
Copilot 4dc61d24ca 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>
2025-10-23 23:46:51 -07:00

261 lines
6.6 KiB
Python

"""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"),
)