* Initial plan * Fix max_decode_depth to properly control decoder hierarchy and port generation Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * Fix test that relied on old depth behavior Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * Update documentation for max_decode_depth parameter Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * fix format * Add variable_depth RDL file and smoke tests for max_decode_depth parameter Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * Add variable depth tests for APB3 and AXI4-Lite CPUIFs Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * fix * fix * bump --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com>
257 lines
7.6 KiB
Python
257 lines
7.6 KiB
Python
"""Test max_decode_depth parameter behavior."""
|
|
|
|
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_depth_1_generates_top_level_interface_only(compile_rdl: Callable[..., AddrmapNode]) -> None:
|
|
"""Test that depth=1 generates interface only for top-level children."""
|
|
rdl_source = """
|
|
addrmap level1 {
|
|
reg {
|
|
field { sw=rw; hw=r; } data1[31:0];
|
|
} reg1 @ 0x0;
|
|
};
|
|
|
|
addrmap level0 {
|
|
level1 inner1 @ 0x0;
|
|
};
|
|
"""
|
|
top = compile_rdl(rdl_source, top="level0")
|
|
|
|
with TemporaryDirectory() as tmpdir:
|
|
exporter = BusDecoderExporter()
|
|
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif, max_decode_depth=1)
|
|
|
|
module_file = Path(tmpdir) / "level0.sv"
|
|
content = module_file.read_text()
|
|
|
|
# Should have interface for inner1 only
|
|
assert "m_apb_inner1" in content
|
|
# Should NOT have interface for reg1
|
|
assert "m_apb_reg1" not in content
|
|
|
|
# Struct should have inner1 but not nested structure
|
|
assert "logic inner1;" in content
|
|
|
|
|
|
def test_depth_2_generates_second_level_interfaces(compile_rdl: Callable[..., AddrmapNode]) -> None:
|
|
"""Test that depth=2 generates interfaces for second-level children."""
|
|
rdl_source = """
|
|
addrmap level2 {
|
|
reg {
|
|
field { sw=rw; hw=r; } data2[31:0];
|
|
} reg2 @ 0x0;
|
|
};
|
|
|
|
addrmap level1 {
|
|
reg {
|
|
field { sw=rw; hw=r; } data1[31:0];
|
|
} reg1 @ 0x0;
|
|
|
|
level2 inner2 @ 0x10;
|
|
};
|
|
|
|
addrmap level0 {
|
|
level1 inner1 @ 0x0;
|
|
};
|
|
"""
|
|
top = compile_rdl(rdl_source, top="level0")
|
|
|
|
with TemporaryDirectory() as tmpdir:
|
|
exporter = BusDecoderExporter()
|
|
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif, max_decode_depth=2)
|
|
|
|
module_file = Path(tmpdir) / "level0.sv"
|
|
content = module_file.read_text()
|
|
|
|
# Should have interfaces for reg1 and inner2
|
|
assert "m_apb_reg1" in content
|
|
assert "m_apb_inner2" in content
|
|
# Should NOT have interface for inner1 or reg2
|
|
assert "m_apb_inner1" not in content
|
|
assert "m_apb_reg2" not in content
|
|
|
|
# Struct should be hierarchical with inner1.reg1 and inner1.inner2
|
|
assert "cpuif_sel_inner1_t" in content
|
|
assert "logic reg1;" in content
|
|
assert "logic inner2;" in content
|
|
|
|
|
|
def test_depth_0_decodes_all_levels(compile_rdl: Callable[..., AddrmapNode]) -> None:
|
|
"""Test that depth=0 decodes all the way down to registers."""
|
|
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;
|
|
};
|
|
"""
|
|
top = compile_rdl(rdl_source, top="level0")
|
|
|
|
with TemporaryDirectory() as tmpdir:
|
|
exporter = BusDecoderExporter()
|
|
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif, max_decode_depth=0)
|
|
|
|
module_file = Path(tmpdir) / "level0.sv"
|
|
content = module_file.read_text()
|
|
|
|
# Should have interfaces for all leaf registers
|
|
assert "m_apb_reg1" in content
|
|
assert "m_apb_reg2" in content
|
|
assert "m_apb_reg2b" in content
|
|
# Should NOT have interfaces for addrmaps
|
|
assert "m_apb_inner1" not in content
|
|
assert "m_apb_inner2" not in content
|
|
|
|
# Struct should be fully hierarchical
|
|
assert "cpuif_sel_inner1_t" in content
|
|
assert "cpuif_sel_inner2_t" in content
|
|
|
|
|
|
def test_depth_affects_decode_logic(compile_rdl: Callable[..., AddrmapNode]) -> None:
|
|
"""Test that decode logic changes based on max_decode_depth."""
|
|
rdl_source = """
|
|
addrmap level1 {
|
|
reg {
|
|
field { sw=rw; hw=r; } data1[31:0];
|
|
} reg1 @ 0x0;
|
|
};
|
|
|
|
addrmap level0 {
|
|
level1 inner1 @ 0x0;
|
|
};
|
|
"""
|
|
top = compile_rdl(rdl_source, top="level0")
|
|
|
|
# Test depth=1: should set cpuif_wr_sel.inner1
|
|
with TemporaryDirectory() as tmpdir:
|
|
exporter = BusDecoderExporter()
|
|
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif, max_decode_depth=1)
|
|
|
|
module_file = Path(tmpdir) / "level0.sv"
|
|
content = module_file.read_text()
|
|
|
|
assert "cpuif_wr_sel.inner1 = 1'b1;" in content
|
|
assert "cpuif_wr_sel.inner1.reg1" not in content
|
|
|
|
# Test depth=2: should set cpuif_wr_sel.inner1.reg1
|
|
with TemporaryDirectory() as tmpdir:
|
|
exporter = BusDecoderExporter()
|
|
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif, max_decode_depth=2)
|
|
|
|
module_file = Path(tmpdir) / "level0.sv"
|
|
content = module_file.read_text()
|
|
|
|
assert "cpuif_wr_sel.inner1.reg1 = 1'b1;" in content
|
|
assert "cpuif_wr_sel.inner1 = 1'b1;" not in content
|
|
|
|
|
|
def test_depth_affects_fanout_fanin(compile_rdl: Callable[..., AddrmapNode]) -> None:
|
|
"""Test that fanout/fanin logic changes based on max_decode_depth."""
|
|
rdl_source = """
|
|
addrmap level1 {
|
|
reg {
|
|
field { sw=rw; hw=r; } data1[31:0];
|
|
} reg1 @ 0x0;
|
|
};
|
|
|
|
addrmap level0 {
|
|
level1 inner1 @ 0x0;
|
|
};
|
|
"""
|
|
top = compile_rdl(rdl_source, top="level0")
|
|
|
|
# Test depth=1: should have fanout for inner1
|
|
with TemporaryDirectory() as tmpdir:
|
|
exporter = BusDecoderExporter()
|
|
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif, max_decode_depth=1)
|
|
|
|
module_file = Path(tmpdir) / "level0.sv"
|
|
content = module_file.read_text()
|
|
|
|
assert "m_apb_inner1.PSEL" in content
|
|
assert "m_apb_reg1.PSEL" not in content
|
|
|
|
# Test depth=2: should have fanout for reg1
|
|
with TemporaryDirectory() as tmpdir:
|
|
exporter = BusDecoderExporter()
|
|
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif, max_decode_depth=2)
|
|
|
|
module_file = Path(tmpdir) / "level0.sv"
|
|
content = module_file.read_text()
|
|
|
|
assert "m_apb_reg1.PSEL" in content
|
|
assert "m_apb_inner1.PSEL" not in content
|
|
|
|
|
|
def test_depth_3_with_deep_hierarchy(compile_rdl: Callable[..., AddrmapNode]) -> None:
|
|
"""Test depth=3 with a 4-level deep hierarchy."""
|
|
rdl_source = """
|
|
addrmap level3 {
|
|
reg {
|
|
field { sw=rw; hw=r; } data3[31:0];
|
|
} reg3 @ 0x0;
|
|
};
|
|
|
|
addrmap level2 {
|
|
reg {
|
|
field { sw=rw; hw=r; } data2[31:0];
|
|
} reg2 @ 0x0;
|
|
|
|
level3 inner3 @ 0x10;
|
|
};
|
|
|
|
addrmap level1 {
|
|
reg {
|
|
field { sw=rw; hw=r; } data1[31:0];
|
|
} reg1 @ 0x0;
|
|
|
|
level2 inner2 @ 0x10;
|
|
};
|
|
|
|
addrmap level0 {
|
|
level1 inner1 @ 0x0;
|
|
};
|
|
"""
|
|
top = compile_rdl(rdl_source, top="level0")
|
|
|
|
with TemporaryDirectory() as tmpdir:
|
|
exporter = BusDecoderExporter()
|
|
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif, max_decode_depth=3)
|
|
|
|
module_file = Path(tmpdir) / "level0.sv"
|
|
content = module_file.read_text()
|
|
|
|
# Should have interfaces at depth 3: reg2, inner3
|
|
# (reg1 is at depth 2, not 3)
|
|
assert "m_apb_reg2" in content
|
|
assert "m_apb_inner3" in content
|
|
# Should NOT have interfaces at other depths
|
|
assert "m_apb_inner1" not in content
|
|
assert "m_apb_inner2" not in content
|
|
assert "m_apb_reg1" not in content
|
|
assert "m_apb_reg3" not in content
|