Refactor tests (better grouping + cocotb support) (#15)

* initial refactor

* fix cocotb tests

* fix typecheck

* install verilator
This commit is contained in:
Arnav Sacheti
2025-10-26 17:56:35 -07:00
committed by GitHub
parent 93276ff616
commit b1f1bf983a
66 changed files with 1734 additions and 2963 deletions

View File

@@ -1 +0,0 @@
"""Unit test package for PeakRDL BusDecoder."""

View File

@@ -5,52 +5,71 @@
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import Iterable, Mapping, Optional
from collections.abc import Callable
import pytest
from systemrdl import RDLCompileError, RDLCompiler
from systemrdl.node import AddrmapNode
@pytest.fixture
def compile_rdl(tmp_path: Path):
"""Compile inline SystemRDL source and return the elaborated root node.
def external_nested_rdl(compile_rdl: Callable[..., AddrmapNode]) -> AddrmapNode:
"""Create an RDL design with external nested addressable components.
Parameters
----------
tmp_path:
Temporary directory provided by pytest.
This tests the scenario where an addrmap contains external children
that themselves have external addressable children.
The decoder should only generate select signals for the top-level
external children, not their internal structure.
"""
rdl_source = """
mem queue_t {
name = "Queue";
mementries = 1024;
memwidth = 64;
};
def _compile(
source: str,
*,
top: Optional[str] = None,
defines: Optional[Mapping[str, object]] = None,
include_paths: Optional[Iterable[Path | str]] = None,
):
compiler = RDLCompiler()
addrmap port_t {
name = "Port";
desc = "";
for key, value in (defines or {}).items():
compiler.define(key, value)
external queue_t common[3] @ 0x0 += 0x2000;
external queue_t response @ 0x6000;
};
for include_path in include_paths or ():
compiler.add_include_path(str(include_path))
addrmap buffer_t {
name = "Buffer";
desc = "";
# Use delete=False to keep the file around after closing
with NamedTemporaryFile("w", suffix=".rdl", dir=tmp_path, delete=False) as tmp_file:
tmp_file.write(source)
tmp_file.flush()
port_t multicast @ 0x0;
port_t port [16] @ 0x8000 += 0x8000;
};
"""
return compile_rdl(rdl_source, top="buffer_t")
try:
compiler.compile_file(tmp_file.name)
if top is not None:
root = compiler.elaborate(top)
return root.top
root = compiler.elaborate()
return root.top
except RDLCompileError:
# Print error messages if available
if hasattr(compiler, "print_messages"):
compiler.print_messages()
raise
return _compile
@pytest.fixture
def nested_addrmap_rdl(compile_rdl: Callable[..., AddrmapNode]) -> AddrmapNode:
"""Create an RDL design with nested non-external addrmaps for testing depth control."""
rdl_source = """
addrmap level2 {
reg {
field { sw=rw; hw=r; } data2[31:0];
} reg2 @ 0x0;
reg {
field { sw=rw; hw=r; } data2b[31:0];
} reg2b @ 0x4;
};
addrmap level1 {
reg {
field { sw=rw; hw=r; } data1[31:0];
} reg1 @ 0x0;
level2 inner2 @ 0x10;
};
addrmap level0 {
level1 inner1 @ 0x0;
};
"""
return compile_rdl(rdl_source, top="level0")

View File

@@ -1,285 +0,0 @@
"""Tests for body classes used in code generation."""
import pytest
from peakrdl_busdecoder.body import (
Body,
CombinationalBody,
ForLoopBody,
IfBody,
StructBody,
)
class TestBody:
"""Test the base Body class."""
def test_empty_body(self):
"""Test empty body returns empty string."""
body = Body()
assert str(body) == ""
assert not body # Should be falsy when empty
def test_add_single_line(self):
"""Test adding a single line to body."""
body = Body()
body += "line1"
assert str(body) == "line1"
assert body # Should be truthy when not empty
def test_add_multiple_lines(self):
"""Test adding multiple lines to body."""
body = Body()
body += "line1"
body += "line2"
body += "line3"
expected = "line1\nline2\nline3"
assert str(body) == expected
def test_add_returns_self(self):
"""Test that add operation returns self for chaining."""
body = Body()
body += "line1"
body += "line2"
# Chaining works because += returns self
assert len(body.lines) == 2
def test_add_nested_body(self):
"""Test adding another body as a line."""
outer = Body()
inner = Body()
inner += "inner1"
inner += "inner2"
outer += "outer1"
outer += inner
outer += "outer2"
expected = "outer1\ninner1\ninner2\nouter2"
assert str(outer) == expected
class TestForLoopBody:
"""Test the ForLoopBody class."""
def test_genvar_for_loop(self):
"""Test genvar-style for loop."""
body = ForLoopBody("genvar", "i", 4)
body += "statement1;"
body += "statement2;"
result = str(body)
assert "for (genvar i = 0; i < 4; i++)" in result
assert "statement1;" in result
assert "statement2;" in result
assert "end" in result
def test_int_for_loop(self):
"""Test int-style for loop."""
body = ForLoopBody("int", "j", 8)
body += "assignment = value;"
result = str(body)
assert "for (int j = 0; j < 8; j++)" in result
assert "assignment = value;" in result
assert "end" in result
def test_empty_for_loop(self):
"""Test empty for loop."""
body = ForLoopBody("genvar", "k", 2)
result = str(body)
# Empty for loop should still have structure
assert "for (genvar k = 0; k < 2; k++)" in result
def test_nested_for_loops(self):
"""Test nested for loops."""
outer = ForLoopBody("genvar", "i", 3)
inner = ForLoopBody("genvar", "j", 2)
inner += "nested_statement;"
outer += inner
result = str(outer)
assert "for (genvar i = 0; i < 3; i++)" in result
assert "for (genvar j = 0; j < 2; j++)" in result
assert "nested_statement;" in result
class TestIfBody:
"""Test the IfBody class."""
def test_simple_if(self):
"""Test simple if statement."""
body = IfBody()
with body.cm("condition1") as b:
b += "statement1;"
result = str(body)
assert "if (condition1)" in result
assert "statement1;" in result
assert "end" in result
def test_if_else(self):
"""Test if-else statement."""
body = IfBody()
with body.cm("condition1") as b:
b += "if_statement;"
with body.cm(None) as b: # None for else
b += "else_statement;"
result = str(body)
assert "if (condition1)" in result
assert "if_statement;" in result
assert "else" in result
assert "else_statement;" in result
def test_if_elif_else(self):
"""Test if-elif-else chain."""
body = IfBody()
with body.cm("condition1") as b:
b += "statement1;"
with body.cm("condition2") as b:
b += "statement2;"
with body.cm(None) as b: # None for else
b += "statement3;"
result = str(body)
assert "if (condition1)" in result
assert "statement1;" in result
assert "else if (condition2)" in result
assert "statement2;" in result
assert "else" in result
assert "statement3;" in result
def test_multiple_elif(self):
"""Test multiple elif statements."""
body = IfBody()
with body.cm("cond1") as b:
b += "stmt1;"
with body.cm("cond2") as b:
b += "stmt2;"
with body.cm("cond3") as b:
b += "stmt3;"
result = str(body)
assert "if (cond1)" in result
assert "else if (cond2)" in result
assert "else if (cond3)" in result
def test_empty_if_branches(self):
"""Test if statement with empty branches."""
body = IfBody()
with body.cm("condition"):
pass
result = str(body)
assert "if (condition)" in result
def test_nested_if(self):
"""Test nested if statements."""
outer = IfBody()
with outer.cm("outer_cond") as outer_body:
inner = IfBody()
with inner.cm("inner_cond") as inner_body:
inner_body += "nested_statement;"
outer_body += inner
result = str(outer)
assert "if (outer_cond)" in result
assert "if (inner_cond)" in result
assert "nested_statement;" in result
class TestCombinationalBody:
"""Test the CombinationalBody class."""
def test_simple_combinational_block(self):
"""Test simple combinational block."""
body = CombinationalBody()
body += "assign1 = value1;"
body += "assign2 = value2;"
result = str(body)
assert "always_comb" in result
assert "begin" in result
assert "assign1 = value1;" in result
assert "assign2 = value2;" in result
assert "end" in result
def test_empty_combinational_block(self):
"""Test empty combinational block."""
body = CombinationalBody()
result = str(body)
assert "always_comb" in result
assert "begin" in result
assert "end" in result
def test_combinational_with_if_statement(self):
"""Test combinational block with if statement."""
cb = CombinationalBody()
ifb = IfBody()
with ifb.cm("condition") as b:
b += "assignment = value;"
cb += ifb
result = str(cb)
assert "always_comb" in result
assert "if (condition)" in result
assert "assignment = value;" in result
class TestStructBody:
"""Test the StructBody class."""
def test_simple_struct(self):
"""Test simple struct definition."""
body = StructBody("my_struct_t", packed=True, typedef=True)
body += "logic [7:0] field1;"
body += "logic field2;"
result = str(body)
assert "typedef struct packed" in result
assert "my_struct_t" in result
assert "logic [7:0] field1;" in result
assert "logic field2;" in result
def test_unpacked_struct(self):
"""Test unpacked struct definition."""
body = StructBody("unpacked_t", packed=False, typedef=True)
body += "int field1;"
result = str(body)
assert "typedef struct" in result
assert "packed" not in result or "typedef struct {" in result
assert "unpacked_t" in result
def test_struct_without_typedef(self):
"""Test struct without typedef."""
body = StructBody("my_struct", packed=True, typedef=False)
body += "logic field;"
result = str(body)
# When typedef=False, packed is not used
assert "struct {" in result
assert "typedef" not in result
assert "my_struct" in result
def test_empty_struct(self):
"""Test empty struct."""
body = StructBody("empty_t", packed=True, typedef=True)
result = str(body)
assert "typedef struct packed" in result
assert "empty_t" in result
def test_nested_struct(self):
"""Test struct with nested struct."""
outer = StructBody("outer_t", packed=True, typedef=True)
inner = StructBody("inner_t", packed=True, typedef=True)
inner += "logic field1;"
outer += "logic field2;"
outer += str(inner) # Include inner struct as a string
result = str(outer)
assert "outer_t" in result
assert "field2;" in result
# Inner struct should appear in the string
assert "inner_t" in result

View File

@@ -1,329 +0,0 @@
"""Integration tests for the BusDecoderExporter."""
import os
from pathlib import Path
import pytest
from peakrdl_busdecoder.cpuif.apb4 import APB4Cpuif
from peakrdl_busdecoder.exporter import BusDecoderExporter
class TestBusDecoderExporter:
"""Test the top-level BusDecoderExporter."""
def test_simple_register_export(self, compile_rdl, tmp_path):
"""Test exporting a simple register."""
rdl_source = """
addrmap simple_reg {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="simple_reg")
exporter = BusDecoderExporter()
output_dir = str(tmp_path)
exporter.export(top, output_dir, cpuif_cls=APB4Cpuif)
# Check that output files are created
module_file = tmp_path / "simple_reg.sv"
package_file = tmp_path / "simple_reg_pkg.sv"
assert module_file.exists()
assert package_file.exists()
# Check basic content
module_content = module_file.read_text()
assert "module simple_reg" in module_content
assert "my_reg" in module_content
package_content = package_file.read_text()
assert "package simple_reg_pkg" in package_content
def test_register_array_export(self, compile_rdl, tmp_path):
"""Test exporting a register array."""
rdl_source = """
addrmap reg_array {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_regs[4] @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="reg_array")
exporter = BusDecoderExporter()
output_dir = str(tmp_path)
exporter.export(top, output_dir, cpuif_cls=APB4Cpuif)
# Check that output files are created
module_file = tmp_path / "reg_array.sv"
assert module_file.exists()
module_content = module_file.read_text()
assert "module reg_array" in module_content
assert "my_regs" in module_content
def test_nested_addrmap_export(self, compile_rdl, tmp_path):
"""Test exporting nested addrmaps."""
rdl_source = """
addrmap inner_block {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} inner_reg @ 0x0;
};
addrmap outer_block {
inner_block inner @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="outer_block")
exporter = BusDecoderExporter()
output_dir = str(tmp_path)
exporter.export(top, output_dir, cpuif_cls=APB4Cpuif)
# Check that output files are created
module_file = tmp_path / "outer_block.sv"
assert module_file.exists()
module_content = module_file.read_text()
assert "module outer_block" in module_content
assert "inner" in module_content
assert "inner_reg" in module_content
def test_custom_module_name(self, compile_rdl, tmp_path):
"""Test exporting with custom module name."""
rdl_source = """
addrmap my_addrmap {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="my_addrmap")
exporter = BusDecoderExporter()
output_dir = str(tmp_path)
exporter.export(top, output_dir, module_name="custom_module", cpuif_cls=APB4Cpuif)
# Check that output files use custom name
module_file = tmp_path / "custom_module.sv"
package_file = tmp_path / "custom_module_pkg.sv"
assert module_file.exists()
assert package_file.exists()
module_content = module_file.read_text()
assert "module custom_module" in module_content
def test_custom_package_name(self, compile_rdl, tmp_path):
"""Test exporting with custom package name."""
rdl_source = """
addrmap my_addrmap {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="my_addrmap")
exporter = BusDecoderExporter()
output_dir = str(tmp_path)
exporter.export(top, output_dir, package_name="custom_pkg", cpuif_cls=APB4Cpuif)
# Check that output files use custom package name
package_file = tmp_path / "custom_pkg.sv"
assert package_file.exists()
package_content = package_file.read_text()
assert "package custom_pkg" in package_content
def test_multiple_registers(self, compile_rdl, tmp_path):
"""Test exporting 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;
};
"""
top = compile_rdl(rdl_source, top="multi_reg")
exporter = BusDecoderExporter()
output_dir = str(tmp_path)
exporter.export(top, output_dir, cpuif_cls=APB4Cpuif)
module_file = tmp_path / "multi_reg.sv"
assert module_file.exists()
module_content = module_file.read_text()
assert "module multi_reg" in module_content
assert "reg1" in module_content
assert "reg2" in module_content
assert "reg3" in module_content
class TestAPB4Interface:
"""Test APB4 CPU interface generation."""
def test_apb4_port_declaration(self, compile_rdl, tmp_path):
"""Test that APB4 interface ports are generated."""
rdl_source = """
addrmap apb_test {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="apb_test")
exporter = BusDecoderExporter()
output_dir = str(tmp_path)
exporter.export(top, output_dir, cpuif_cls=APB4Cpuif)
module_file = tmp_path / "apb_test.sv"
module_content = module_file.read_text()
# Check for APB4 signals
assert "PSEL" in module_content or "psel" in module_content
assert "PENABLE" in module_content or "penable" in module_content
assert "PWRITE" in module_content or "pwrite" in module_content
assert "PADDR" in module_content or "paddr" in module_content
assert "PWDATA" in module_content or "pwdata" in module_content
assert "PRDATA" in module_content or "prdata" in module_content
assert "PREADY" in module_content or "pready" in module_content
def test_apb4_read_write_logic(self, compile_rdl, tmp_path):
"""Test that APB4 read/write logic is generated."""
rdl_source = """
addrmap apb_rw {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="apb_rw")
exporter = BusDecoderExporter()
output_dir = str(tmp_path)
exporter.export(top, output_dir, cpuif_cls=APB4Cpuif)
module_file = tmp_path / "apb_rw.sv"
module_content = module_file.read_text()
# Basic sanity checks for logic generation
assert "always" in module_content or "assign" in module_content
assert "my_reg" in module_content
def test_nested_addrmap_with_array_stride(self, compile_rdl, tmp_path):
"""Test that nested addrmaps with arrays use correct stride values."""
rdl_source = """
addrmap inner_block {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} inner_reg @ 0x0;
};
addrmap outer_block {
inner_block inner[4] @ 0x0 += 0x100;
reg {
field {
sw=rw;
hw=r;
} outer_data[31:0];
} outer_reg @ 0x400;
};
"""
top = compile_rdl(rdl_source, top="outer_block")
exporter = BusDecoderExporter()
output_dir = str(tmp_path)
exporter.export(top, output_dir, cpuif_cls=APB4Cpuif)
module_file = tmp_path / "outer_block.sv"
module_content = module_file.read_text()
# Check that the generated code uses the correct stride (0x100 = 256)
# not the array dimension (4)
# The decode logic should contain something like: i0)*11'h100 or i0)*256
assert "i0)*11'h100" in module_content or "i0)*'h100" in module_content, \
"Array stride should be 0x100 (256), not the dimension value (4)"
# Ensure it's NOT using the incorrect dimension value
assert (
"i0)*11'h4" not in module_content
and "i0)*4" not in module_content
), "Should not use array dimension (4) as stride"
def test_multidimensional_array_strides(self, compile_rdl, tmp_path):
"""Test that multidimensional arrays calculate correct strides for each dimension."""
rdl_source = """
addrmap test_block {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg[2][3] @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="test_block")
exporter = BusDecoderExporter()
output_dir = str(tmp_path)
exporter.export(top, output_dir, cpuif_cls=APB4Cpuif)
module_file = tmp_path / "test_block.sv"
module_content = module_file.read_text()
# For a [2][3] array where each register is 4 bytes:
# i0 (leftmost/slowest) should have stride = 3 * 4 = 12 (0xc)
# i1 (rightmost/fastest) should have stride = 4 (0x4)
assert ("i0)*5'hc" in module_content or "i0)*12" in module_content), \
"i0 should use stride 12 (0xc) for [2][3] array"
assert ("i1)*5'h4" in module_content or "i1)*4" in module_content), \
"i1 should use stride 4 for [2][3] array"

View File

@@ -1,79 +1,16 @@
"""Test handling of external nested addressable components."""
from collections.abc import Callable
from pathlib import Path
from tempfile import TemporaryDirectory
import pytest
from systemrdl.node import AddrmapNode
from peakrdl_busdecoder import BusDecoderExporter
from peakrdl_busdecoder.cpuif.apb4 import APB4Cpuif
@pytest.fixture
def external_nested_rdl(compile_rdl):
"""Create an RDL design with external nested addressable components.
This tests the scenario where an addrmap contains external children
that themselves have external addressable children.
The decoder should only generate select signals for the top-level
external children, not their internal structure.
"""
rdl_source = """
mem queue_t {
name = "Queue";
mementries = 1024;
memwidth = 64;
};
addrmap port_t {
name = "Port";
desc = "";
external queue_t common[3] @ 0x0 += 0x2000;
external queue_t response @ 0x6000;
};
addrmap buffer_t {
name = "Buffer";
desc = "";
port_t multicast @ 0x0;
port_t port [16] @ 0x8000 += 0x8000;
};
"""
return compile_rdl(rdl_source, top="buffer_t")
@pytest.fixture
def nested_addrmap_rdl(compile_rdl):
"""Create an RDL design with nested non-external addrmaps for testing depth control."""
rdl_source = """
addrmap level2 {
reg {
field { sw=rw; hw=r; } data2[31:0];
} reg2 @ 0x0;
reg {
field { sw=rw; hw=r; } data2b[31:0];
} reg2b @ 0x4;
};
addrmap level1 {
reg {
field { sw=rw; hw=r; } data1[31:0];
} reg1 @ 0x0;
level2 inner2 @ 0x10;
};
addrmap level0 {
level1 inner1 @ 0x0;
};
"""
return compile_rdl(rdl_source, top="level0")
def test_external_nested_components_generate_correct_decoder(external_nested_rdl):
def test_external_nested_components_generate_correct_decoder(external_nested_rdl: AddrmapNode) -> None:
"""Test that external nested components generate correct decoder logic.
The decoder should:
@@ -109,7 +46,7 @@ def test_external_nested_components_generate_correct_decoder(external_nested_rdl
assert "logic [15:0]port;" in content
def test_external_nested_components_generate_correct_interfaces(external_nested_rdl):
def test_external_nested_components_generate_correct_interfaces(external_nested_rdl: AddrmapNode) -> None:
"""Test that external nested components generate correct interface ports.
The module should have:
@@ -140,7 +77,7 @@ def test_external_nested_components_generate_correct_interfaces(external_nested_
assert "m_apb_response" not in content
def test_non_external_nested_components_are_descended(compile_rdl):
def test_non_external_nested_components_are_descended(compile_rdl: Callable[..., AddrmapNode]) -> None:
"""Test that non-external nested components are still descended into.
This is a regression test to ensure we didn't break normal nested
@@ -175,7 +112,7 @@ def test_non_external_nested_components_are_descended(compile_rdl):
assert "inner_reg" in content
def test_max_decode_depth_parameter_exists(compile_rdl):
def test_max_decode_depth_parameter_exists(compile_rdl: Callable[..., AddrmapNode]) -> None:
"""Test that max_decode_depth parameter can be set."""
rdl_source = """
addrmap simple {

View File

@@ -1,312 +0,0 @@
"""Tests for code generation classes."""
import pytest
from peakrdl_busdecoder.cpuif.apb4 import APB4Cpuif
from peakrdl_busdecoder.decode_logic_gen import DecodeLogicFlavor, DecodeLogicGenerator
from peakrdl_busdecoder.design_state import DesignState
from peakrdl_busdecoder.exporter import BusDecoderExporter
from peakrdl_busdecoder.struct_gen import StructGenerator
class TestDecodeLogicGenerator:
"""Test the DecodeLogicGenerator."""
def test_decode_logic_read(self, compile_rdl):
"""Test decode logic generation for read operations."""
rdl_source = """
addrmap test {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="test")
ds = DesignState(top, {})
gen = DecodeLogicGenerator(ds, DecodeLogicFlavor.READ)
# Basic sanity check - it should initialize
assert gen is not None
assert gen._flavor == DecodeLogicFlavor.READ
def test_decode_logic_write(self, compile_rdl):
"""Test decode logic generation for write operations."""
rdl_source = """
addrmap test {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="test")
ds = DesignState(top, {})
gen = DecodeLogicGenerator(ds, DecodeLogicFlavor.WRITE)
assert gen is not None
assert gen._flavor == DecodeLogicFlavor.WRITE
def test_cpuif_addr_predicate(self, compile_rdl):
"""Test address predicate generation."""
rdl_source = """
addrmap test {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x100;
};
"""
top = compile_rdl(rdl_source, top="test")
ds = DesignState(top, {})
gen = DecodeLogicGenerator(ds, DecodeLogicFlavor.READ)
# Get the register node
reg_node = None
for child in top.children():
if child.inst_name == "my_reg":
reg_node = child
break
assert reg_node is not None
predicates = gen.cpuif_addr_predicate(reg_node)
# Should return a list of conditions
assert isinstance(predicates, list)
assert len(predicates) > 0
# Should check address bounds
for pred in predicates:
assert "cpuif_rd_addr" in pred or ">=" in pred or "<" in pred
def test_decode_logic_flavor_enum(self):
"""Test DecodeLogicFlavor enum values."""
assert DecodeLogicFlavor.READ.value == "rd"
assert DecodeLogicFlavor.WRITE.value == "wr"
assert DecodeLogicFlavor.READ.cpuif_address == "cpuif_rd_addr"
assert DecodeLogicFlavor.WRITE.cpuif_address == "cpuif_wr_addr"
assert DecodeLogicFlavor.READ.cpuif_select == "cpuif_rd_sel"
assert DecodeLogicFlavor.WRITE.cpuif_select == "cpuif_wr_sel"
class TestStructGenerator:
"""Test the StructGenerator."""
def test_simple_struct_generation(self, compile_rdl):
"""Test struct generation for simple register."""
rdl_source = """
addrmap test {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="test")
ds = DesignState(top, {})
gen = StructGenerator(ds)
# Should generate struct definition
assert gen is not None
result = str(gen)
# Should contain struct declaration
assert "struct" in result or "typedef" in result
def test_nested_struct_generation(self, compile_rdl):
"""Test struct generation for nested addrmaps."""
rdl_source = """
addrmap inner {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} inner_reg @ 0x0;
};
addrmap outer {
inner my_inner @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="outer")
ds = DesignState(top, {})
gen = StructGenerator(ds)
# Walk the tree to generate structs
from systemrdl.walker import RDLWalker
walker = RDLWalker()
walker.walk(top, gen, skip_top=True)
result = str(gen)
# Should contain struct declaration
assert "struct" in result or "typedef" in result
# The struct should reference the inner component
assert "my_inner" in result
def test_array_struct_generation(self, compile_rdl):
"""Test struct generation for register arrays."""
rdl_source = """
addrmap test {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_regs[4] @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="test")
ds = DesignState(top, {})
gen = StructGenerator(ds)
# Walk the tree to generate structs
from systemrdl.walker import RDLWalker
walker = RDLWalker()
walker.walk(top, gen, skip_top=True)
result = str(gen)
# Should contain array notation
assert "[" in result and "]" in result
# Should reference the register
assert "my_regs" in result
class TestDesignState:
"""Test the DesignState class."""
def test_design_state_basic(self, compile_rdl):
"""Test basic DesignState initialization."""
rdl_source = """
addrmap test {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="test")
ds = DesignState(top, {})
assert ds.top_node == top
assert ds.module_name == "test"
assert ds.package_name == "test_pkg"
assert ds.cpuif_data_width == 32 # Should infer from 32-bit field
assert ds.addr_width > 0
def test_design_state_custom_module_name(self, compile_rdl):
"""Test DesignState with custom module name."""
rdl_source = """
addrmap test {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="test")
ds = DesignState(top, {"module_name": "custom_module"})
assert ds.module_name == "custom_module"
assert ds.package_name == "custom_module_pkg"
def test_design_state_custom_package_name(self, compile_rdl):
"""Test DesignState with custom package name."""
rdl_source = """
addrmap test {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="test")
ds = DesignState(top, {"package_name": "custom_pkg"})
assert ds.package_name == "custom_pkg"
def test_design_state_custom_address_width(self, compile_rdl):
"""Test DesignState with custom address width."""
rdl_source = """
addrmap test {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="test")
ds = DesignState(top, {"address_width": 16})
assert ds.addr_width == 16
def test_design_state_unroll_arrays(self, compile_rdl):
"""Test DesignState with cpuif_unroll option."""
rdl_source = """
addrmap test {
reg {
field {
sw=rw;
hw=r;
} data[31:0];
} my_regs[4] @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="test")
ds = DesignState(top, {"cpuif_unroll": True})
assert ds.cpuif_unroll is True
def test_design_state_64bit_registers(self, compile_rdl):
"""Test DesignState with wider data width."""
rdl_source = """
addrmap test {
reg {
regwidth = 32;
field {
sw=rw;
hw=r;
} data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="test")
ds = DesignState(top, {})
# Should infer 32-bit data width from field
assert ds.cpuif_data_width == 32

View File

@@ -1,163 +0,0 @@
"""Test the --unroll CLI argument functionality."""
from pathlib import Path
from tempfile import TemporaryDirectory
import pytest
from peakrdl_busdecoder import BusDecoderExporter
from peakrdl_busdecoder.cpuif.apb3 import APB3Cpuif
from peakrdl_busdecoder.cpuif.apb4 import APB4Cpuif
@pytest.fixture
def sample_rdl(compile_rdl):
"""Create a simple RDL design with an array."""
rdl_source = """
addrmap top {
reg my_reg {
field {
sw=rw;
hw=r;
} data[31:0];
};
my_reg regs[4] @ 0x0 += 0x4;
};
"""
return compile_rdl(rdl_source)
def test_unroll_disabled_creates_array_interface(sample_rdl):
"""Test that with unroll=False, array nodes are kept as arrays."""
with TemporaryDirectory() as tmpdir:
exporter = BusDecoderExporter()
exporter.export(
sample_rdl,
tmpdir,
cpuif_cls=APB4Cpuif,
cpuif_unroll=False,
)
# Read the generated module
module_file = Path(tmpdir) / "top.sv"
content = module_file.read_text()
# Should have a single array interface with [4] dimension
assert "m_apb_regs [4]" in content
# Should have a parameter for array size
assert "N_REGSS = 4" in content
# Should NOT have individual indexed interfaces
assert "m_apb_regs_0" not in content
assert "m_apb_regs_1" not in content
assert "m_apb_regs_2" not in content
assert "m_apb_regs_3" not in content
def test_unroll_enabled_creates_individual_interfaces(sample_rdl):
"""Test that with unroll=True, array elements are unrolled into separate instances."""
with TemporaryDirectory() as tmpdir:
exporter = BusDecoderExporter()
exporter.export(
sample_rdl,
tmpdir,
cpuif_cls=APB4Cpuif,
cpuif_unroll=True,
)
# Read the generated module
module_file = Path(tmpdir) / "top.sv"
content = module_file.read_text()
# Should have individual interfaces without array dimensions
assert "m_apb_regs_0," in content or "m_apb_regs_0\n" in content
assert "m_apb_regs_1," in content or "m_apb_regs_1\n" in content
assert "m_apb_regs_2," in content or "m_apb_regs_2\n" in content
assert "m_apb_regs_3" in content
# Should NOT have array interface
assert "m_apb_regs [4]" not in content
# Should NOT have individual interfaces with array dimensions (the bug we're fixing)
assert "m_apb_regs_0 [4]" not in content
assert "m_apb_regs_1 [4]" not in content
assert "m_apb_regs_2 [4]" not in content
assert "m_apb_regs_3 [4]" not in content
# Should NOT have array size parameter when unrolled
assert "N_REGSS" not in content
def test_unroll_with_apb3(sample_rdl):
"""Test that unroll works correctly with APB3 interface."""
with TemporaryDirectory() as tmpdir:
exporter = BusDecoderExporter()
exporter.export(
sample_rdl,
tmpdir,
cpuif_cls=APB3Cpuif,
cpuif_unroll=True,
)
# Read the generated module
module_file = Path(tmpdir) / "top.sv"
content = module_file.read_text()
# Should have individual APB3 interfaces
assert "m_apb_regs_0," in content or "m_apb_regs_0\n" in content
assert "m_apb_regs_1," in content or "m_apb_regs_1\n" in content
assert "m_apb_regs_2," in content or "m_apb_regs_2\n" in content
assert "m_apb_regs_3" in content
# Should NOT have array dimensions on unrolled interfaces
assert "m_apb_regs_0 [4]" not in content
@pytest.fixture
def multidim_array_rdl(compile_rdl):
"""Create an RDL design with a multi-dimensional array."""
rdl_source = """
addrmap top {
reg my_reg {
field {
sw=rw;
hw=r;
} data[31:0];
};
my_reg matrix[2][3] @ 0x0 += 0x4;
};
"""
return compile_rdl(rdl_source)
def test_unroll_multidimensional_array(multidim_array_rdl):
"""Test that unroll works correctly with multi-dimensional arrays."""
with TemporaryDirectory() as tmpdir:
exporter = BusDecoderExporter()
exporter.export(
multidim_array_rdl,
tmpdir,
cpuif_cls=APB4Cpuif,
cpuif_unroll=True,
)
# Read the generated module
module_file = Path(tmpdir) / "top.sv"
content = module_file.read_text()
# Should have individual interfaces for each element in the 2x3 array
# Format should be m_apb_matrix_0_0, m_apb_matrix_0_1, ..., m_apb_matrix_1_2
assert "m_apb_matrix_0_0" in content
assert "m_apb_matrix_0_1" in content
assert "m_apb_matrix_0_2" in content
assert "m_apb_matrix_1_0" in content
assert "m_apb_matrix_1_1" in content
assert "m_apb_matrix_1_2" in content
# Should NOT have array dimensions on any of the unrolled interfaces
for i in range(2):
for j in range(3):
assert f"m_apb_matrix_{i}_{j} [" not in content

View File

@@ -1,262 +0,0 @@
"""Tests for utility functions."""
import pytest
from systemrdl import RDLCompiler
from systemrdl.node import AddrmapNode
from peakrdl_busdecoder.utils import clog2, get_indexed_path, is_pow2, roundup_pow2
class TestMathUtils:
"""Test mathematical utility functions."""
def test_clog2_basic(self):
"""Test clog2 function with basic values."""
assert clog2(1) == 0
assert clog2(2) == 1
assert clog2(3) == 2
assert clog2(4) == 2
assert clog2(5) == 3
assert clog2(8) == 3
assert clog2(9) == 4
assert clog2(16) == 4
assert clog2(17) == 5
assert clog2(32) == 5
assert clog2(33) == 6
assert clog2(64) == 6
assert clog2(128) == 7
assert clog2(256) == 8
assert clog2(1024) == 10
def test_is_pow2_true_cases(self):
"""Test is_pow2 returns True for powers of 2."""
assert is_pow2(1) is True
assert is_pow2(2) is True
assert is_pow2(4) is True
assert is_pow2(8) is True
assert is_pow2(16) is True
assert is_pow2(32) is True
assert is_pow2(64) is True
assert is_pow2(128) is True
assert is_pow2(256) is True
assert is_pow2(512) is True
assert is_pow2(1024) is True
def test_is_pow2_false_cases(self):
"""Test is_pow2 returns False for non-powers of 2."""
assert is_pow2(0) is False
assert is_pow2(3) is False
assert is_pow2(5) is False
assert is_pow2(6) is False
assert is_pow2(7) is False
assert is_pow2(9) is False
assert is_pow2(10) is False
assert is_pow2(15) is False
assert is_pow2(17) is False
assert is_pow2(100) is False
assert is_pow2(255) is False
assert is_pow2(1000) is False
def test_roundup_pow2_already_power_of_2(self):
"""Test roundup_pow2 with values that are already powers of 2."""
assert roundup_pow2(1) == 1
assert roundup_pow2(2) == 2
assert roundup_pow2(4) == 4
assert roundup_pow2(8) == 8
assert roundup_pow2(16) == 16
assert roundup_pow2(32) == 32
assert roundup_pow2(64) == 64
assert roundup_pow2(128) == 128
assert roundup_pow2(256) == 256
def test_roundup_pow2_non_power_of_2(self):
"""Test roundup_pow2 with values that are not powers of 2."""
assert roundup_pow2(3) == 4
assert roundup_pow2(5) == 8
assert roundup_pow2(6) == 8
assert roundup_pow2(7) == 8
assert roundup_pow2(9) == 16
assert roundup_pow2(15) == 16
assert roundup_pow2(17) == 32
assert roundup_pow2(31) == 32
assert roundup_pow2(33) == 64
assert roundup_pow2(100) == 128
assert roundup_pow2(255) == 256
assert roundup_pow2(257) == 512
class TestGetIndexedPath:
"""Test get_indexed_path function."""
def test_simple_path(self, compile_rdl):
"""Test simple path without arrays."""
rdl_source = """
addrmap my_addrmap {
reg {
field {} data;
} my_reg;
};
"""
top = compile_rdl(rdl_source, top="my_addrmap")
# Get the register node by iterating through children
reg_node = None
for child in top.children():
if child.inst_name == "my_reg":
reg_node = child
break
assert reg_node is not None
path = get_indexed_path(top, reg_node)
assert path == "my_reg"
def test_nested_path(self, compile_rdl):
"""Test nested path without arrays."""
rdl_source = """
addrmap inner_map {
reg {
field {} data;
} my_reg;
};
addrmap my_addrmap {
inner_map inner;
};
"""
top = compile_rdl(rdl_source, top="my_addrmap")
# Navigate to the nested register
inner_node = None
for child in top.children():
if child.inst_name == "inner":
inner_node = child
break
assert inner_node is not None
reg_node = None
for child in inner_node.children():
if child.inst_name == "my_reg":
reg_node = child
break
assert reg_node is not None
path = get_indexed_path(top, reg_node)
assert path == "inner.my_reg"
def test_array_path(self, compile_rdl):
"""Test path with array indices."""
rdl_source = """
addrmap my_addrmap {
reg {
field {} data;
} my_reg[4];
};
"""
top = compile_rdl(rdl_source, top="my_addrmap")
reg_node = None
for child in top.children():
if child.inst_name == "my_reg":
reg_node = child
break
assert reg_node is not None
path = get_indexed_path(top, reg_node)
assert path == "my_reg[i0]"
def test_multidimensional_array_path(self, compile_rdl):
"""Test path with multidimensional arrays."""
rdl_source = """
addrmap my_addrmap {
reg {
field {} data;
} my_reg[2][3];
};
"""
top = compile_rdl(rdl_source, top="my_addrmap")
reg_node = None
for child in top.children():
if child.inst_name == "my_reg":
reg_node = child
break
assert reg_node is not None
path = get_indexed_path(top, reg_node)
assert path == "my_reg[i0][i1]"
def test_nested_array_path(self, compile_rdl):
"""Test path with nested arrays."""
rdl_source = """
addrmap inner_map {
reg {
field {} data;
} my_reg[2];
};
addrmap my_addrmap {
inner_map inner[3];
};
"""
top = compile_rdl(rdl_source, top="my_addrmap")
# Navigate to the nested register
inner_node = None
for child in top.children():
if child.inst_name == "inner":
inner_node = child
break
assert inner_node is not None
reg_node = None
for child in inner_node.children():
if child.inst_name == "my_reg":
reg_node = child
break
assert reg_node is not None
path = get_indexed_path(top, reg_node)
assert path == "inner[i0].my_reg[i1]"
def test_custom_indexer(self, compile_rdl):
"""Test path with custom indexer name."""
rdl_source = """
addrmap my_addrmap {
reg {
field {} data;
} my_reg[4];
};
"""
top = compile_rdl(rdl_source, top="my_addrmap")
reg_node = None
for child in top.children():
if child.inst_name == "my_reg":
reg_node = child
break
assert reg_node is not None
path = get_indexed_path(top, reg_node, indexer="idx")
assert path == "my_reg[idx0]"
def test_skip_kw_filter(self, compile_rdl):
"""Test path with keyword filtering skipped."""
rdl_source = """
addrmap my_addrmap {
reg {
field {} data;
} always_reg;
};
"""
top = compile_rdl(rdl_source, top="my_addrmap")
reg_node = None
for child in top.children():
if child.inst_name == "always_reg":
reg_node = child
break
assert reg_node is not None
# With keyword filter (default) - SystemRDL identifiers can use keywords but SV can't
path = get_indexed_path(top, reg_node)
# The path should contain always_reg
assert "always_reg" in path
# Without keyword filter
path = get_indexed_path(top, reg_node, skip_kw_filter=True)
assert path == "always_reg"