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>
This commit is contained in:
@@ -52,8 +52,16 @@ class APB3Cpuif(BaseCpuif):
|
||||
fanin["cpuif_rd_ack"] = "'0"
|
||||
fanin["cpuif_rd_err"] = "'0"
|
||||
else:
|
||||
fanin["cpuif_rd_ack"] = self.signal("PREADY", node, "i")
|
||||
fanin["cpuif_rd_err"] = self.signal("PSLVERR", node, "i")
|
||||
# Use intermediate signals for interface arrays to avoid
|
||||
# non-constant indexing of interface arrays in procedural blocks
|
||||
if self.is_interface and node.is_array and node.array_dimensions:
|
||||
# Generate array index string [i0][i1]... for the intermediate signal
|
||||
array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
|
||||
fanin["cpuif_rd_ack"] = f"{node.inst_name}_fanin_ready{array_idx}"
|
||||
fanin["cpuif_rd_err"] = f"{node.inst_name}_fanin_err{array_idx}"
|
||||
else:
|
||||
fanin["cpuif_rd_ack"] = self.signal("PREADY", node, "i")
|
||||
fanin["cpuif_rd_err"] = self.signal("PSLVERR", node, "i")
|
||||
|
||||
return "\n".join(map(lambda kv: f"{kv[0]} = {kv[1]};", fanin.items()))
|
||||
|
||||
@@ -62,6 +70,23 @@ class APB3Cpuif(BaseCpuif):
|
||||
if node is None:
|
||||
fanin["cpuif_rd_data"] = "'0"
|
||||
else:
|
||||
fanin["cpuif_rd_data"] = self.signal("PRDATA", node, "i")
|
||||
# Use intermediate signals for interface arrays to avoid
|
||||
# non-constant indexing of interface arrays in procedural blocks
|
||||
if self.is_interface and node.is_array and node.array_dimensions:
|
||||
# Generate array index string [i0][i1]... for the intermediate signal
|
||||
array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
|
||||
fanin["cpuif_rd_data"] = f"{node.inst_name}_fanin_data{array_idx}"
|
||||
else:
|
||||
fanin["cpuif_rd_data"] = self.signal("PRDATA", node, "i")
|
||||
|
||||
return "\n".join(map(lambda kv: f"{kv[0]} = {kv[1]};", fanin.items()))
|
||||
|
||||
def fanin_intermediate_assignments(
|
||||
self, node: AddressableNode, inst_name: str, array_idx: str, master_prefix: str, indexed_path: str
|
||||
) -> list[str]:
|
||||
"""Generate intermediate signal assignments for APB3 interface arrays."""
|
||||
return [
|
||||
f"assign {inst_name}_fanin_ready{array_idx} = {master_prefix}{indexed_path}.PREADY;",
|
||||
f"assign {inst_name}_fanin_err{array_idx} = {master_prefix}{indexed_path}.PSLVERR;",
|
||||
f"assign {inst_name}_fanin_data{array_idx} = {master_prefix}{indexed_path}.PRDATA;",
|
||||
]
|
||||
|
||||
@@ -26,6 +26,13 @@ assign {{cpuif.signal("PSLVERR")}} = cpuif_rd_err | cpuif_rd_sel.cpuif_err | cpu
|
||||
// Fanout CPU Bus interface signals
|
||||
//--------------------------------------------------------------------------
|
||||
{{fanout|walk(cpuif=cpuif)}}
|
||||
{%- if cpuif.is_interface %}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Intermediate signals for interface array fanin
|
||||
//--------------------------------------------------------------------------
|
||||
{{fanin_intermediate|walk(cpuif=cpuif)}}
|
||||
{%- endif %}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Fanin CPU Bus interface signals
|
||||
|
||||
@@ -55,8 +55,16 @@ class APB4Cpuif(BaseCpuif):
|
||||
fanin["cpuif_rd_ack"] = "'0"
|
||||
fanin["cpuif_rd_err"] = "'0"
|
||||
else:
|
||||
fanin["cpuif_rd_ack"] = self.signal("PREADY", node, "i")
|
||||
fanin["cpuif_rd_err"] = self.signal("PSLVERR", node, "i")
|
||||
# Use intermediate signals for interface arrays to avoid
|
||||
# non-constant indexing of interface arrays in procedural blocks
|
||||
if self.is_interface and node.is_array and node.array_dimensions:
|
||||
# Generate array index string [i0][i1]... for the intermediate signal
|
||||
array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
|
||||
fanin["cpuif_rd_ack"] = f"{node.inst_name}_fanin_ready{array_idx}"
|
||||
fanin["cpuif_rd_err"] = f"{node.inst_name}_fanin_err{array_idx}"
|
||||
else:
|
||||
fanin["cpuif_rd_ack"] = self.signal("PREADY", node, "i")
|
||||
fanin["cpuif_rd_err"] = self.signal("PSLVERR", node, "i")
|
||||
|
||||
return "\n".join(map(lambda kv: f"{kv[0]} = {kv[1]};", fanin.items()))
|
||||
|
||||
@@ -65,6 +73,23 @@ class APB4Cpuif(BaseCpuif):
|
||||
if node is None:
|
||||
fanin["cpuif_rd_data"] = "'0"
|
||||
else:
|
||||
fanin["cpuif_rd_data"] = self.signal("PRDATA", node, "i")
|
||||
# Use intermediate signals for interface arrays to avoid
|
||||
# non-constant indexing of interface arrays in procedural blocks
|
||||
if self.is_interface and node.is_array and node.array_dimensions:
|
||||
# Generate array index string [i0][i1]... for the intermediate signal
|
||||
array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
|
||||
fanin["cpuif_rd_data"] = f"{node.inst_name}_fanin_data{array_idx}"
|
||||
else:
|
||||
fanin["cpuif_rd_data"] = self.signal("PRDATA", node, "i")
|
||||
|
||||
return "\n".join(map(lambda kv: f"{kv[0]} = {kv[1]};", fanin.items()))
|
||||
|
||||
def fanin_intermediate_assignments(
|
||||
self, node: AddressableNode, inst_name: str, array_idx: str, master_prefix: str, indexed_path: str
|
||||
) -> list[str]:
|
||||
"""Generate intermediate signal assignments for APB4 interface arrays."""
|
||||
return [
|
||||
f"assign {inst_name}_fanin_ready{array_idx} = {master_prefix}{indexed_path}.PREADY;",
|
||||
f"assign {inst_name}_fanin_err{array_idx} = {master_prefix}{indexed_path}.PSLVERR;",
|
||||
f"assign {inst_name}_fanin_data{array_idx} = {master_prefix}{indexed_path}.PRDATA;",
|
||||
]
|
||||
|
||||
@@ -29,6 +29,13 @@ assign {{cpuif.signal("PSLVERR")}} = cpuif_rd_err | cpuif_rd_sel.cpuif_err | cpu
|
||||
// Fanout CPU Bus interface signals
|
||||
//--------------------------------------------------------------------------
|
||||
{{fanout|walk(cpuif=cpuif)}}
|
||||
{%- if cpuif.is_interface %}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Intermediate signals for interface array fanin
|
||||
//--------------------------------------------------------------------------
|
||||
{{fanin_intermediate|walk(cpuif=cpuif)}}
|
||||
{%- endif %}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Fanin CPU Bus interface signals
|
||||
|
||||
@@ -4,14 +4,14 @@ from systemrdl.node import AddressableNode
|
||||
|
||||
from ...utils import get_indexed_path
|
||||
from ..base_cpuif import BaseCpuif
|
||||
from .axi4lite_interface import AXI4LiteSVInterface
|
||||
from .axi4_lite_interface import AXI4LiteSVInterface
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...exporter import BusDecoderExporter
|
||||
|
||||
|
||||
class AXI4LiteCpuif(BaseCpuif):
|
||||
template_path = "axi4lite_tmpl.sv"
|
||||
template_path = "axi4_lite_tmpl.sv"
|
||||
|
||||
def __init__(self, exp: "BusDecoderExporter") -> None:
|
||||
super().__init__(exp)
|
||||
@@ -68,9 +68,17 @@ class AXI4LiteCpuif(BaseCpuif):
|
||||
fanin["cpuif_rd_ack"] = "'0"
|
||||
fanin["cpuif_rd_err"] = "'0"
|
||||
else:
|
||||
# Read side: ack comes from RVALID; err if RRESP[1] is set (SLVERR/DECERR)
|
||||
fanin["cpuif_rd_ack"] = self.signal("RVALID", node, "i")
|
||||
fanin["cpuif_rd_err"] = f"{self.signal('RRESP', node, 'i')}[1]"
|
||||
# Use intermediate signals for interface arrays to avoid
|
||||
# non-constant indexing of interface arrays in procedural blocks
|
||||
if self.is_interface and node.is_array and node.array_dimensions:
|
||||
# Generate array index string [i0][i1]... for the intermediate signal
|
||||
array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
|
||||
fanin["cpuif_rd_ack"] = f"{node.inst_name}_fanin_ready{array_idx}"
|
||||
fanin["cpuif_rd_err"] = f"{node.inst_name}_fanin_err{array_idx}"
|
||||
else:
|
||||
# Read side: ack comes from RVALID; err if RRESP[1] is set (SLVERR/DECERR)
|
||||
fanin["cpuif_rd_ack"] = self.signal("RVALID", node, "i")
|
||||
fanin["cpuif_rd_err"] = f"{self.signal('RRESP', node, 'i')}[1]"
|
||||
|
||||
return "\n".join(f"{lhs} = {rhs};" for lhs, rhs in fanin.items())
|
||||
|
||||
@@ -79,6 +87,23 @@ class AXI4LiteCpuif(BaseCpuif):
|
||||
if node is None:
|
||||
fanin["cpuif_rd_data"] = "'0"
|
||||
else:
|
||||
fanin["cpuif_rd_data"] = self.signal("RDATA", node, "i")
|
||||
# Use intermediate signals for interface arrays to avoid
|
||||
# non-constant indexing of interface arrays in procedural blocks
|
||||
if self.is_interface and node.is_array and node.array_dimensions:
|
||||
# Generate array index string [i0][i1]... for the intermediate signal
|
||||
array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
|
||||
fanin["cpuif_rd_data"] = f"{node.inst_name}_fanin_data{array_idx}"
|
||||
else:
|
||||
fanin["cpuif_rd_data"] = self.signal("RDATA", node, "i")
|
||||
|
||||
return "\n".join(f"{lhs} = {rhs};" for lhs, rhs in fanin.items())
|
||||
|
||||
def fanin_intermediate_assignments(
|
||||
self, node: AddressableNode, inst_name: str, array_idx: str, master_prefix: str, indexed_path: str
|
||||
) -> list[str]:
|
||||
"""Generate intermediate signal assignments for AXI4-Lite interface arrays."""
|
||||
return [
|
||||
f"assign {inst_name}_fanin_ready{array_idx} = {master_prefix}{indexed_path}.RVALID;",
|
||||
f"assign {inst_name}_fanin_err{array_idx} = {master_prefix}{indexed_path}.RRESP[1];",
|
||||
f"assign {inst_name}_fanin_data{array_idx} = {master_prefix}{indexed_path}.RDATA;",
|
||||
]
|
||||
|
||||
@@ -4,7 +4,7 @@ from systemrdl.node import AddressableNode
|
||||
|
||||
from ...utils import get_indexed_path
|
||||
from ..base_cpuif import BaseCpuif
|
||||
from .axi4lite_interface import AXI4LiteFlatInterface
|
||||
from .axi4_lite_interface import AXI4LiteFlatInterface
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...exporter import BusDecoderExporter
|
||||
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
|
||||
class AXI4LiteCpuifFlat(BaseCpuif):
|
||||
"""Verilator-friendly variant that flattens the AXI4-Lite interface ports."""
|
||||
|
||||
template_path = "axi4lite_tmpl.sv"
|
||||
template_path = "axi4_lite_tmpl.sv"
|
||||
|
||||
def __init__(self, exp: "BusDecoderExporter") -> None:
|
||||
super().__init__(exp)
|
||||
|
||||
@@ -53,6 +53,13 @@ assign {{cpuif.signal("BRESP")}} = (cpuif_wr_err | cpuif_wr_sel.cpuif_err | cpu
|
||||
// Fanout CPU Bus interface signals
|
||||
//--------------------------------------------------------------------------
|
||||
{{fanout|walk(cpuif=cpuif)}}
|
||||
{%- if cpuif.is_interface %}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Intermediate signals for interface array fanin
|
||||
//--------------------------------------------------------------------------
|
||||
{{fanin_intermediate|walk(cpuif=cpuif)}}
|
||||
{%- endif %}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Fanin CPU Bus interface signals
|
||||
@@ -7,6 +7,7 @@ from systemrdl.node import AddressableNode
|
||||
|
||||
from ..utils import clog2, get_indexed_path, is_pow2, roundup_pow2
|
||||
from .fanin_gen import FaninGenerator
|
||||
from .fanin_intermediate_gen import FaninIntermediateGenerator
|
||||
from .fanout_gen import FanoutGenerator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -97,6 +98,7 @@ class BaseCpuif:
|
||||
"ds": self.exp.ds,
|
||||
"fanout": FanoutGenerator,
|
||||
"fanin": FaninGenerator,
|
||||
"fanin_intermediate": FaninIntermediateGenerator,
|
||||
}
|
||||
|
||||
template = jj_env.get_template(self.template_path)
|
||||
@@ -116,3 +118,24 @@ class BaseCpuif:
|
||||
|
||||
def readback(self, node: AddressableNode | None = None) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def fanin_intermediate_assignments(
|
||||
self, node: AddressableNode, inst_name: str, array_idx: str, master_prefix: str, indexed_path: str
|
||||
) -> list[str]:
|
||||
"""Generate intermediate signal assignments for interface array fanin.
|
||||
|
||||
This method should be implemented by cpuif classes that use interfaces.
|
||||
It returns a list of assignment strings that copy signals from interface
|
||||
arrays to intermediate unpacked arrays using constant (genvar) indexing.
|
||||
|
||||
Args:
|
||||
node: The addressable node
|
||||
inst_name: Instance name for the intermediate signals
|
||||
array_idx: Array index string (e.g., "[gi0][gi1]")
|
||||
master_prefix: Master interface prefix
|
||||
indexed_path: Indexed path to the interface element
|
||||
|
||||
Returns:
|
||||
List of assignment strings
|
||||
"""
|
||||
return [] # Default: no intermediate assignments needed
|
||||
|
||||
142
src/peakrdl_busdecoder/cpuif/fanin_intermediate_gen.py
Normal file
142
src/peakrdl_busdecoder/cpuif/fanin_intermediate_gen.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""Generator for intermediate signals needed for interface array fanin.
|
||||
|
||||
When using SystemVerilog interface arrays, we cannot use variable indices
|
||||
in procedural blocks (like always_comb). This generator creates intermediate
|
||||
signals that copy from interface arrays using generate loops, which can then
|
||||
be safely accessed with variable indices in the fanin logic.
|
||||
"""
|
||||
|
||||
from collections import deque
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from systemrdl.node import AddressableNode
|
||||
from systemrdl.walker import WalkerAction
|
||||
|
||||
from ..body import Body, ForLoopBody
|
||||
from ..design_state import DesignState
|
||||
from ..listener import BusDecoderListener
|
||||
from ..utils import get_indexed_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .base_cpuif import BaseCpuif
|
||||
|
||||
|
||||
class FaninIntermediateGenerator(BusDecoderListener):
|
||||
"""Generates intermediate signals for interface array fanin."""
|
||||
|
||||
def __init__(self, ds: DesignState, cpuif: "BaseCpuif") -> None:
|
||||
super().__init__(ds)
|
||||
self._cpuif = cpuif
|
||||
self._declarations: list[str] = []
|
||||
self._stack: deque[Body] = deque()
|
||||
self._stack.append(Body())
|
||||
|
||||
def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
|
||||
action = super().enter_AddressableComponent(node)
|
||||
|
||||
# Only generate intermediates for interface arrays
|
||||
# Check if cpuif has is_interface attribute (some implementations don't)
|
||||
is_interface = getattr(self._cpuif, "is_interface", False)
|
||||
if not is_interface or not node.array_dimensions:
|
||||
return action
|
||||
|
||||
# Generate intermediate signal declarations
|
||||
self._generate_intermediate_declarations(node)
|
||||
|
||||
# Generate assignment logic using generate loops
|
||||
if node.array_dimensions:
|
||||
for i, dim in enumerate(node.array_dimensions):
|
||||
fb = ForLoopBody(
|
||||
"genvar",
|
||||
f"gi{i}",
|
||||
dim,
|
||||
)
|
||||
self._stack.append(fb)
|
||||
|
||||
# Generate assignments from interface array to intermediates
|
||||
self._stack[-1] += self._generate_intermediate_assignments(node)
|
||||
|
||||
return action
|
||||
|
||||
def exit_AddressableComponent(self, node: AddressableNode) -> None:
|
||||
is_interface = getattr(self._cpuif, "is_interface", False)
|
||||
if is_interface and node.array_dimensions:
|
||||
for _ in node.array_dimensions:
|
||||
b = self._stack.pop()
|
||||
if not b:
|
||||
continue
|
||||
self._stack[-1] += b
|
||||
|
||||
super().exit_AddressableComponent(node)
|
||||
|
||||
def _generate_intermediate_declarations(self, node: AddressableNode) -> None:
|
||||
"""Generate intermediate signal declarations for a node."""
|
||||
inst_name = node.inst_name
|
||||
|
||||
# Array dimensions should be checked before calling this function
|
||||
if not node.array_dimensions:
|
||||
return
|
||||
|
||||
# Calculate total array size
|
||||
array_size = 1
|
||||
for dim in node.array_dimensions:
|
||||
array_size *= dim
|
||||
|
||||
# Create array dimension string
|
||||
array_str = "".join(f"[{dim}]" for dim in node.array_dimensions)
|
||||
|
||||
# Generate declarations for each fanin signal
|
||||
# For APB3/4: PREADY, PSLVERR, PRDATA
|
||||
# These are the signals read in fanin
|
||||
self._declarations.append(f"logic {inst_name}_fanin_ready{array_str};")
|
||||
self._declarations.append(f"logic {inst_name}_fanin_err{array_str};")
|
||||
self._declarations.append(
|
||||
f"logic [{self._cpuif.data_width - 1}:0] {inst_name}_fanin_data{array_str};"
|
||||
)
|
||||
|
||||
def _generate_intermediate_assignments(self, node: AddressableNode) -> str:
|
||||
"""Generate assignments from interface array to intermediate signals."""
|
||||
inst_name = node.inst_name
|
||||
indexed_path = get_indexed_path(node.parent, node, "gi", skip_kw_filter=True)
|
||||
|
||||
# Get master prefix - use getattr to avoid type errors
|
||||
interface = getattr(self._cpuif, "_interface", None)
|
||||
if interface is None:
|
||||
return ""
|
||||
master_prefix = interface.get_master_prefix()
|
||||
|
||||
# Array dimensions should be checked before calling this function
|
||||
if not node.array_dimensions:
|
||||
return ""
|
||||
|
||||
# Create indexed signal names for left-hand side
|
||||
array_idx = "".join(f"[gi{i}]" for i in range(len(node.array_dimensions)))
|
||||
|
||||
# Delegate to cpuif to get the appropriate assignments for this interface type
|
||||
assignments = self._cpuif.fanin_intermediate_assignments(
|
||||
node, inst_name, array_idx, master_prefix, indexed_path
|
||||
)
|
||||
|
||||
return "\n".join(assignments)
|
||||
|
||||
def get_declarations(self) -> str:
|
||||
"""Get all intermediate signal declarations."""
|
||||
if not self._declarations:
|
||||
return ""
|
||||
return "\n".join(self._declarations)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get all intermediate signal declarations and assignments."""
|
||||
if not self._declarations:
|
||||
return ""
|
||||
|
||||
# Output declarations first
|
||||
output = "\n".join(self._declarations)
|
||||
output += "\n\n"
|
||||
|
||||
# Then output assignments
|
||||
body_str = "\n".join(map(str, self._stack))
|
||||
if body_str and body_str.strip():
|
||||
output += body_str
|
||||
|
||||
return output
|
||||
@@ -51,7 +51,7 @@ module {{ds.module_name}}
|
||||
//--------------------------------------------------------------------------
|
||||
always_comb begin
|
||||
// Default all write select signals to 0
|
||||
cpuif_wr_sel = '0;
|
||||
cpuif_wr_sel = '{default: '0};
|
||||
|
||||
if (cpuif_req && cpuif_wr_en) begin
|
||||
// A write request is pending
|
||||
@@ -66,7 +66,7 @@ module {{ds.module_name}}
|
||||
//--------------------------------------------------------------------------
|
||||
always_comb begin
|
||||
// Default all read select signals to 0
|
||||
cpuif_rd_sel = '0;
|
||||
cpuif_rd_sel = '{default: '0};
|
||||
|
||||
if (cpuif_req && cpuif_rd_en) begin
|
||||
// A read request is pending
|
||||
|
||||
@@ -17,7 +17,7 @@ class StructGenerator(BusDecoderListener):
|
||||
super().__init__(ds)
|
||||
|
||||
self._stack: deque[Body] = deque()
|
||||
self._stack.append(StructBody("cpuif_sel_t", True, True))
|
||||
self._stack.append(StructBody("cpuif_sel_t", True, False))
|
||||
|
||||
def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
|
||||
action = super().enter_AddressableComponent(node)
|
||||
@@ -28,7 +28,7 @@ class StructGenerator(BusDecoderListener):
|
||||
|
||||
if node.children():
|
||||
# Push new body onto stack
|
||||
body = StructBody(f"cpuif_sel_{node.inst_name}_t", True, True)
|
||||
body = StructBody(f"cpuif_sel_{node.inst_name}_t", True, False)
|
||||
self._stack.append(body)
|
||||
|
||||
return action
|
||||
@@ -46,7 +46,7 @@ class StructGenerator(BusDecoderListener):
|
||||
|
||||
if node.array_dimensions:
|
||||
for dim in node.array_dimensions:
|
||||
name = f"[{dim - 1}:0]{name}"
|
||||
name = f"{name}[{dim}]"
|
||||
|
||||
self._stack[-1] += f"{type} {name};"
|
||||
|
||||
|
||||
131
tests/generator/test_questa_compatibility.py
Normal file
131
tests/generator/test_questa_compatibility.py
Normal file
@@ -0,0 +1,131 @@
|
||||
"""Test Questa simulator compatibility for instance arrays."""
|
||||
|
||||
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_instance_array_questa_compatibility(compile_rdl: Callable[..., AddrmapNode]) -> None:
|
||||
"""Test that instance arrays generate Questa-compatible code.
|
||||
|
||||
This test ensures that:
|
||||
- Struct members for arrays use unpacked array syntax (name[dim])
|
||||
- NOT packed bit-vector syntax ([dim-1:0]name)
|
||||
- Struct is unpacked (not packed)
|
||||
- Array indexing with loop variables works correctly
|
||||
|
||||
This fixes the error: "Nonconstant index into instance array"
|
||||
"""
|
||||
rdl_source = """
|
||||
addrmap test_map {
|
||||
reg {
|
||||
field {
|
||||
sw=rw;
|
||||
hw=r;
|
||||
} data[31:0];
|
||||
} my_reg[4] @ 0x0 += 0x10;
|
||||
};
|
||||
"""
|
||||
top = compile_rdl(rdl_source, top="test_map")
|
||||
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
exporter = BusDecoderExporter()
|
||||
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif)
|
||||
|
||||
# Read the generated module
|
||||
module_file = Path(tmpdir) / "test_map.sv"
|
||||
content = module_file.read_text()
|
||||
|
||||
# Should use unpacked struct
|
||||
assert "typedef struct {" in content
|
||||
assert "typedef struct packed" not in content
|
||||
|
||||
# Should use unpacked array syntax for array members
|
||||
assert "logic my_reg[4];" in content
|
||||
|
||||
# Should NOT use packed bit-vector syntax
|
||||
assert "[3:0]my_reg" not in content
|
||||
|
||||
# Should have proper array indexing in decode logic
|
||||
assert "cpuif_wr_sel.my_reg[i0] = 1'b1;" in content
|
||||
assert "cpuif_rd_sel.my_reg[i0] = 1'b1;" in content
|
||||
|
||||
# Should have proper array indexing in fanout/fanin logic
|
||||
assert "cpuif_wr_sel.my_reg[gi0]" in content or "cpuif_rd_sel.my_reg[gi0]" in content
|
||||
assert "cpuif_wr_sel.my_reg[i0]" in content or "cpuif_rd_sel.my_reg[i0]" in content
|
||||
|
||||
|
||||
def test_multidimensional_array_questa_compatibility(compile_rdl: Callable[..., AddrmapNode]) -> None:
|
||||
"""Test that multidimensional instance arrays generate Questa-compatible code."""
|
||||
rdl_source = """
|
||||
addrmap test_map {
|
||||
reg {
|
||||
field {
|
||||
sw=rw;
|
||||
hw=r;
|
||||
} data[31:0];
|
||||
} my_reg[2][3] @ 0x0 += 0x10;
|
||||
};
|
||||
"""
|
||||
top = compile_rdl(rdl_source, top="test_map")
|
||||
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
exporter = BusDecoderExporter()
|
||||
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif)
|
||||
|
||||
# Read the generated module
|
||||
module_file = Path(tmpdir) / "test_map.sv"
|
||||
content = module_file.read_text()
|
||||
|
||||
# Should use unpacked struct with multidimensional array
|
||||
assert "typedef struct {" in content
|
||||
|
||||
# Should use unpacked array syntax for multidimensional arrays
|
||||
assert "logic my_reg[2][3];" in content
|
||||
|
||||
# Should NOT use packed bit-vector syntax
|
||||
assert "[1:0][2:0]my_reg" not in content
|
||||
assert "[5:0]my_reg" not in content
|
||||
|
||||
|
||||
def test_nested_instance_array_questa_compatibility(compile_rdl: Callable[..., AddrmapNode]) -> None:
|
||||
"""Test that nested instance arrays generate Questa-compatible code."""
|
||||
rdl_source = """
|
||||
addrmap inner_map {
|
||||
reg {
|
||||
field {
|
||||
sw=rw;
|
||||
hw=r;
|
||||
} data[31:0];
|
||||
} inner_reg[2] @ 0x0 += 0x10;
|
||||
};
|
||||
|
||||
addrmap outer_map {
|
||||
inner_map inner[3] @ 0x0 += 0x100;
|
||||
};
|
||||
"""
|
||||
top = compile_rdl(rdl_source, top="outer_map")
|
||||
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
exporter = BusDecoderExporter()
|
||||
exporter.export(top, tmpdir, cpuif_cls=APB4Cpuif)
|
||||
|
||||
# Read the generated module
|
||||
module_file = Path(tmpdir) / "outer_map.sv"
|
||||
content = module_file.read_text()
|
||||
|
||||
# Should use unpacked struct
|
||||
assert "typedef struct {" in content
|
||||
|
||||
# Inner should be an array
|
||||
# The exact syntax may vary, but it should be unpacked
|
||||
# Look for the pattern of unpacked arrays, not packed bit-vectors
|
||||
assert "inner[3]" in content or "logic inner" in content
|
||||
|
||||
# Should NOT use packed bit-vector syntax like [2:0]inner
|
||||
assert "[2:0]inner" not in content
|
||||
@@ -96,3 +96,10 @@ class TestStructGenerator:
|
||||
assert "[" in result and "]" in result
|
||||
# Should reference the register
|
||||
assert "my_regs" in result
|
||||
# Should use unpacked array syntax (name[size]), not packed bit-vector ([size:0]name)
|
||||
assert "my_regs[4]" in result
|
||||
# Should NOT use packed bit-vector syntax
|
||||
assert "[3:0]my_regs" not in result
|
||||
# Should be unpacked struct, not packed
|
||||
assert "typedef struct {" in result
|
||||
assert "typedef struct packed" not in result
|
||||
|
||||
@@ -41,9 +41,9 @@ def test_external_nested_components_generate_correct_decoder(external_nested_rdl
|
||||
assert "cpuif_rd_sel.multicast.response" not in content
|
||||
|
||||
# Verify struct is flat (no nested structs for external children)
|
||||
assert "typedef struct packed" in content
|
||||
assert "typedef struct" in content
|
||||
assert "logic multicast;" in content
|
||||
assert "logic [15:0]port;" in content
|
||||
assert "logic port[16];" in content
|
||||
|
||||
|
||||
def test_external_nested_components_generate_correct_interfaces(external_nested_rdl: AddrmapNode) -> None:
|
||||
|
||||
Reference in New Issue
Block a user