Files
PeakRDL-BusDecoder/tests/unit/test_external_nested.py
Copilot 3d823572cc Fix nonconstant index errors in Questa by using unpacked structs and interface array intermediates (#17)
* Initial plan

* Fix nonconstant index error by using unpacked structs and arrays

Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com>

* Add comprehensive tests for Questa compatibility with instance arrays

Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com>

* Add intermediate signals for interface array fanin to fix Questa compatibility

Questa simulator rejects indexing interface arrays with non-constant variables in procedural blocks (always_comb). This fix creates intermediate unpacked array signals that are assigned from interface arrays using generate loops (with genvar), then references those intermediates in the fanin logic.

Changes:
- Added fanin_intermediate_gen.py to generate intermediate signals
- Modified APB3/APB4/AXI4-Lite cpuif classes to use intermediates for interface arrays
- Updated templates to include intermediate signal section
- Intermediate signals use genvar indexing (legal) instead of variable indexing (illegal in Questa)

Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com>

* Fix type checking and formatting issues in fanin intermediate generator

- Added proper null checks for node.array_dimensions to avoid type errors
- Used getattr() to safely access is_interface and _interface attributes
- Added early returns when interface is None to prevent errors
- Fixed formatting issues in fanin_intermediate_gen.py
- All type checks now pass (only pre-existing errors remain in other files)
- All 67 tests still pass

Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com>

* Fix test collection when cocotb is not installed

The cocotb test files (test_runner.py) have imports that fail at module level when cocotb is not installed, causing pytest collection to fail. This prevents running tests in environments without cocotb dependencies.

Solution: Updated conftest.py to conditionally ignore cocotb tests when cocotb is not available, allowing tests to pass in both environments (with and without cocotb).

- When cocotb is available: only ignore test_register_access.py files (existing behavior)
- When cocotb is not available: ignore all files under cocotb/** directories

This allows CI to run all 67 non-simulation tests successfully without requiring cocotb/verilator dependencies for basic testing.

Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com>

* add default for unpacked struct

* cocotb is test dependancy

* Refactor fanin intermediate signal logic to cpuif level

Moved the interface-specific signal assignment logic from fanin_intermediate_gen.py to individual cpuif classes (APB3Cpuif, APB4Cpuif, AXI4LiteCpuif). This follows better architecture principles where each cpuif knows which signals it needs.

Changes:
- Added fanin_intermediate_assignments() method to BaseCpuif
- Implemented fanin_intermediate_assignments() in APB3Cpuif, APB4Cpuif, and AXI4LiteCpuif
- Updated FaninIntermediateGenerator to call the cpuif method instead of checking interface type
- Removed interface type checking logic from fanin_intermediate_gen.py

This makes the code more maintainable and follows the single responsibility principle - each cpuif class knows its own signal requirements.

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-28 22:03:57 -07:00

140 lines
4.6 KiB
Python

"""Test handling of external nested addressable components."""
from collections.abc import Callable
from pathlib import Path
from tempfile import TemporaryDirectory
from systemrdl.node import AddrmapNode
from peakrdl_busdecoder import BusDecoderExporter
from peakrdl_busdecoder.cpuif.apb4 import APB4Cpuif
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:
- Generate select signals for multicast and port[16]
- NOT generate select signals for multicast.common[] or multicast.response
- NOT generate invalid paths like multicast.common[i0]
"""
with TemporaryDirectory() as tmpdir:
exporter = BusDecoderExporter()
exporter.export(
external_nested_rdl,
tmpdir,
cpuif_cls=APB4Cpuif,
)
# Read the generated module
module_file = Path(tmpdir) / "buffer_t.sv"
content = module_file.read_text()
# Should have correct select signals
assert "cpuif_wr_sel.multicast = 1'b1;" in content
assert "cpuif_wr_sel.port[i0] = 1'b1;" in content
# Should NOT have invalid nested paths
assert "cpuif_wr_sel.multicast.common" not in content
assert "cpuif_wr_sel.multicast.response" not in content
assert "cpuif_rd_sel.multicast.common" not in content
assert "cpuif_rd_sel.multicast.response" not in content
# Verify struct is flat (no nested structs for external children)
assert "typedef struct" in content
assert "logic multicast;" in content
assert "logic port[16];" in content
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:
- One master interface for multicast
- Array of 16 master interfaces for port[]
- NO interfaces for internal components like common[] or response
"""
with TemporaryDirectory() as tmpdir:
exporter = BusDecoderExporter()
exporter.export(
external_nested_rdl,
tmpdir,
cpuif_cls=APB4Cpuif,
)
# Read the generated module
module_file = Path(tmpdir) / "buffer_t.sv"
content = module_file.read_text()
# Should have master interfaces for top-level external children
assert "m_apb_multicast" in content
assert "m_apb_port [16]" in content or "m_apb_port[16]" in content
# Should NOT have interfaces for nested external children
assert "m_apb_multicast_common" not in content
assert "m_apb_multicast_response" not in content
assert "m_apb_common" not in content
assert "m_apb_response" not in content
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
component handling.
"""
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")
with TemporaryDirectory() as tmpdir:
exporter = BusDecoderExporter()
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif)
# Read the generated module
module_file = Path(tmpdir) / "outer_block.sv"
content = module_file.read_text()
# Should descend into inner and reference inner_reg
assert "inner" in content
assert "inner_reg" in content
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 {
reg {
field { sw=rw; hw=r; } data[31:0];
} my_reg @ 0x0;
};
"""
top = compile_rdl(rdl_source, top="simple")
with TemporaryDirectory() as tmpdir:
exporter = BusDecoderExporter()
# Should not raise an exception
exporter.export(
top,
tmpdir,
cpuif_cls=APB4Cpuif,
max_decode_depth=2,
)
# Verify output was generated
module_file = Path(tmpdir) / "simple.sv"
assert module_file.exists()