* 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>
87 lines
3.3 KiB
Python
87 lines
3.3 KiB
Python
from typing import TYPE_CHECKING, overload
|
|
|
|
from systemrdl.node import AddressableNode
|
|
|
|
from ...utils import get_indexed_path
|
|
from ..base_cpuif import BaseCpuif
|
|
from .axi4_lite_interface import AXI4LiteFlatInterface
|
|
|
|
if TYPE_CHECKING:
|
|
from ...exporter import BusDecoderExporter
|
|
|
|
|
|
class AXI4LiteCpuifFlat(BaseCpuif):
|
|
"""Verilator-friendly variant that flattens the AXI4-Lite interface ports."""
|
|
|
|
template_path = "axi4_lite_tmpl.sv"
|
|
|
|
def __init__(self, exp: "BusDecoderExporter") -> None:
|
|
super().__init__(exp)
|
|
self._interface = AXI4LiteFlatInterface(self)
|
|
|
|
@property
|
|
def is_interface(self) -> bool:
|
|
return self._interface.is_interface
|
|
|
|
@property
|
|
def port_declaration(self) -> str:
|
|
"""Returns the port declaration for the AXI4-Lite interface."""
|
|
return self._interface.get_port_declaration("s_axil_", "m_axil_")
|
|
|
|
@overload
|
|
def signal(self, signal: str, node: None = None, indexer: None = None) -> str: ...
|
|
@overload
|
|
def signal(self, signal: str, node: AddressableNode, indexer: str | None = None) -> str: ...
|
|
def signal(self, signal: str, node: AddressableNode | None = None, indexer: str | None = None) -> str:
|
|
return self._interface.signal(signal, node, indexer)
|
|
|
|
def fanout(self, node: AddressableNode) -> str:
|
|
fanout: dict[str, str] = {}
|
|
|
|
wr_sel = f"cpuif_wr_sel.{get_indexed_path(self.exp.ds.top_node, node, 'gi')}"
|
|
rd_sel = f"cpuif_rd_sel.{get_indexed_path(self.exp.ds.top_node, node, 'gi')}"
|
|
|
|
# Write address channel
|
|
fanout[self.signal("AWVALID", node, "gi")] = wr_sel
|
|
fanout[self.signal("AWADDR", node, "gi")] = self.signal("AWADDR")
|
|
fanout[self.signal("AWPROT", node, "gi")] = self.signal("AWPROT")
|
|
|
|
# Write data channel
|
|
fanout[self.signal("WVALID", node, "gi")] = wr_sel
|
|
fanout[self.signal("WDATA", node, "gi")] = "cpuif_wr_data"
|
|
fanout[self.signal("WSTRB", node, "gi")] = "cpuif_wr_byte_en"
|
|
|
|
# Write response channel (master -> slave)
|
|
fanout[self.signal("BREADY", node, "gi")] = self.signal("BREADY")
|
|
|
|
# Read address channel
|
|
fanout[self.signal("ARVALID", node, "gi")] = rd_sel
|
|
fanout[self.signal("ARADDR", node, "gi")] = self.signal("ARADDR")
|
|
fanout[self.signal("ARPROT", node, "gi")] = self.signal("ARPROT")
|
|
|
|
# Read data channel (master -> slave)
|
|
fanout[self.signal("RREADY", node, "gi")] = self.signal("RREADY")
|
|
|
|
return "\n".join(f"assign {lhs} = {rhs};" for lhs, rhs in fanout.items())
|
|
|
|
def fanin(self, node: AddressableNode | None = None) -> str:
|
|
fanin: dict[str, str] = {}
|
|
if node is None:
|
|
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]"
|
|
|
|
return "\n".join(f"{lhs} = {rhs};" for lhs, rhs in fanin.items())
|
|
|
|
def readback(self, node: AddressableNode | None = None) -> str:
|
|
fanin: dict[str, str] = {}
|
|
if node is None:
|
|
fanin["cpuif_rd_data"] = "'0"
|
|
else:
|
|
fanin["cpuif_rd_data"] = self.signal("RDATA", node, "i")
|
|
|
|
return "\n".join(f"{lhs} = {rhs};" for lhs, rhs in fanin.items())
|