Refactor cpuif classes to use Interface abstraction (#14)

* Initial plan

* Refactor cpuif classes to use Interface abstraction

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

* Fix type annotation consistency in Interface.signal()

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

* Add runtime validation and documentation for indexer types

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

* Remove unused variable in SVInterface.signal()

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

* Fix master port directions in APB3 and APB4 flat interfaces

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

* Fix AXI4LiteCpuifFlat and apply code formatting

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

* PSELx -> PSEL

* cleanup marker warnings

---------

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:
Copilot
2025-10-26 18:47:11 -07:00
committed by GitHub
parent 1eababe1ab
commit 95fda3abaa
14 changed files with 539 additions and 258 deletions

View File

@@ -1,92 +1,86 @@
from typing import overload
from typing import TYPE_CHECKING, overload
from systemrdl.node import AddressableNode
from .axi4_lite_cpuif import AXI4LiteCpuif
from ...utils import get_indexed_path
from ..base_cpuif import BaseCpuif
from .axi4lite_interface import AXI4LiteFlatInterface
if TYPE_CHECKING:
from ...exporter import BusDecoderExporter
class AXI4LiteCpuifFlat(AXI4LiteCpuif):
class AXI4LiteCpuifFlat(BaseCpuif):
"""Verilator-friendly variant that flattens the AXI4-Lite interface ports."""
template_path = "axi4lite_tmpl.sv"
is_interface = False
def _port_declaration(self, child: AddressableNode) -> list[str]:
return [
f"input logic {self.signal('AWREADY', child)}",
f"output logic {self.signal('AWVALID', child)}",
f"output logic [{self.addr_width - 1}:0] {self.signal('AWADDR', child)}",
f"output logic [2:0] {self.signal('AWPROT', child)}",
f"input logic {self.signal('WREADY', child)}",
f"output logic {self.signal('WVALID', child)}",
f"output logic [{self.data_width - 1}:0] {self.signal('WDATA', child)}",
f"output logic [{self.data_width_bytes - 1}:0] {self.signal('WSTRB', child)}",
f"output logic {self.signal('BREADY', child)}",
f"input logic {self.signal('BVALID', child)}",
f"input logic [1:0] {self.signal('BRESP', child)}",
f"input logic {self.signal('ARREADY', child)}",
f"output logic {self.signal('ARVALID', child)}",
f"output logic [{self.addr_width - 1}:0] {self.signal('ARADDR', child)}",
f"output logic [2:0] {self.signal('ARPROT', child)}",
f"output logic {self.signal('RREADY', child)}",
f"input logic {self.signal('RVALID', child)}",
f"input logic [{self.data_width - 1}:0] {self.signal('RDATA', child)}",
f"input logic [1:0] {self.signal('RRESP', child)}",
]
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:
slave_ports: list[str] = [
f"input logic {self.signal('ACLK')}",
f"input logic {self.signal('ARESETn')}",
f"output logic {self.signal('AWREADY')}",
f"input logic {self.signal('AWVALID')}",
f"input logic [{self.addr_width - 1}:0] {self.signal('AWADDR')}",
f"input logic [2:0] {self.signal('AWPROT')}",
f"output logic {self.signal('WREADY')}",
f"input logic {self.signal('WVALID')}",
f"input logic [{self.data_width - 1}:0] {self.signal('WDATA')}",
f"input logic [{self.data_width_bytes - 1}:0] {self.signal('WSTRB')}",
f"input logic {self.signal('BREADY')}",
f"output logic {self.signal('BVALID')}",
f"output logic [1:0] {self.signal('BRESP')}",
f"output logic {self.signal('ARREADY')}",
f"input logic {self.signal('ARVALID')}",
f"input logic [{self.addr_width - 1}:0] {self.signal('ARADDR')}",
f"input logic [2:0] {self.signal('ARPROT')}",
f"input logic {self.signal('RREADY')}",
f"output logic {self.signal('RVALID')}",
f"output logic [{self.data_width - 1}:0] {self.signal('RDATA')}",
f"output logic [1:0] {self.signal('RRESP')}",
]
master_ports: list[str] = []
for child in self.addressable_children:
master_ports.extend(self._port_declaration(child))
return ",\n".join(slave_ports + master_ports)
"""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 signal(
self,
signal: str,
node: AddressableNode | None = None,
indexer: str | None = None,
) -> str:
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:
return f"s_axil_{signal}"
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]"
base = f"m_axil_{node.inst_name}_{signal}"
if not self.check_is_array(node):
if node.current_idx is not None:
return f"{base}_{'_'.join(map(str, node.current_idx))}"
return base
return "\n".join(f"{lhs} = {rhs};" for lhs, rhs in fanin.items())
if indexer is None:
return f"{base}[N_{node.inst_name.upper()}S]"
return f"{base}[{indexer}]"
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())