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:
@@ -67,7 +67,7 @@ tools = ["pyrefly>=0.37.0", "ruff>=0.14.0"]
|
|||||||
[project.entry-points."peakrdl.exporters"]
|
[project.entry-points."peakrdl.exporters"]
|
||||||
busdecoder = "peakrdl_busdecoder.__peakrdl__:Exporter"
|
busdecoder = "peakrdl_busdecoder.__peakrdl__:Exporter"
|
||||||
|
|
||||||
|
# ---------------------- RUFF ----------------------
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 110
|
line-length = 110
|
||||||
target-version = "py310"
|
target-version = "py310"
|
||||||
@@ -106,3 +106,11 @@ untyped-def-behavior = "check-and-infer-return-type"
|
|||||||
|
|
||||||
project-includes = ["src/**/*"]
|
project-includes = ["src/**/*"]
|
||||||
project-excludes = ["**/__pycache__", "**/*venv/**/*"]
|
project-excludes = ["**/__pycache__", "**/*venv/**/*"]
|
||||||
|
|
||||||
|
# ---------------------- PYTEST ----------------------
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
python_files = ["test_*.py", "*_test.py"]
|
||||||
|
markers = [
|
||||||
|
"simulation: marks tests as requiring cocotb simulation (deselect with '-m \"not simulation\"')",
|
||||||
|
"verilator: marks tests as requiring verilator simulator (deselect with '-m \"not verilator\"')",
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,47 +1,36 @@
|
|||||||
from typing import overload
|
from typing import TYPE_CHECKING, overload
|
||||||
|
|
||||||
from systemrdl.node import AddressableNode
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
from ...utils import get_indexed_path
|
from ...utils import get_indexed_path
|
||||||
from ..base_cpuif import BaseCpuif
|
from ..base_cpuif import BaseCpuif
|
||||||
|
from .apb3_interface import APB3SVInterface
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ...exporter import BusDecoderExporter
|
||||||
|
|
||||||
|
|
||||||
class APB3Cpuif(BaseCpuif):
|
class APB3Cpuif(BaseCpuif):
|
||||||
template_path = "apb3_tmpl.sv"
|
template_path = "apb3_tmpl.sv"
|
||||||
is_interface = True
|
|
||||||
|
|
||||||
def _port_declaration(self, child: AddressableNode) -> str:
|
def __init__(self, exp: "BusDecoderExporter") -> None:
|
||||||
base = f"apb3_intf.master m_apb_{child.inst_name}"
|
super().__init__(exp)
|
||||||
|
self._interface = APB3SVInterface(self)
|
||||||
|
|
||||||
# When unrolled, current_idx is set - append it to the name
|
@property
|
||||||
if child.current_idx is not None:
|
def is_interface(self) -> bool:
|
||||||
base = f"{base}_{'_'.join(map(str, child.current_idx))}"
|
return self._interface.is_interface
|
||||||
|
|
||||||
# Only add array dimensions if this should be treated as an array
|
|
||||||
if self.check_is_array(child):
|
|
||||||
assert child.array_dimensions is not None
|
|
||||||
return f"{base} {''.join(f'[{dim}]' for dim in child.array_dimensions)}"
|
|
||||||
|
|
||||||
return base
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port_declaration(self) -> str:
|
def port_declaration(self) -> str:
|
||||||
slave_ports: list[str] = ["apb3_intf.slave s_apb"]
|
return self._interface.get_port_declaration("s_apb", "m_apb_")
|
||||||
master_ports: list[str] = list(map(self._port_declaration, self.addressable_children))
|
|
||||||
|
|
||||||
return ",\n".join(slave_ports + master_ports)
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def signal(self, signal: str, node: None = None, indexer: None = None) -> str: ...
|
def signal(self, signal: str, node: None = None, indexer: None = None) -> str: ...
|
||||||
@overload
|
@overload
|
||||||
def signal(self, signal: str, node: AddressableNode, indexer: str) -> str: ...
|
def signal(self, signal: str, node: AddressableNode, indexer: str) -> str: ...
|
||||||
def signal(self, signal: str, node: AddressableNode | None = None, indexer: str | None = None) -> str:
|
def signal(self, signal: str, node: AddressableNode | None = None, indexer: str | None = None) -> str:
|
||||||
if node is None or indexer is None:
|
return self._interface.signal(signal, node, indexer)
|
||||||
# Node is none, so this is a slave signal
|
|
||||||
return f"s_apb.{signal}"
|
|
||||||
|
|
||||||
# Master signal
|
|
||||||
return f"m_apb_{get_indexed_path(node.parent, node, indexer, skip_kw_filter=True)}.{signal}"
|
|
||||||
|
|
||||||
def fanout(self, node: AddressableNode) -> str:
|
def fanout(self, node: AddressableNode) -> str:
|
||||||
fanout: dict[str, str] = {}
|
fanout: dict[str, str] = {}
|
||||||
|
|||||||
@@ -1,46 +1,29 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from systemrdl.node import AddressableNode
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
from ...utils import get_indexed_path
|
from ...utils import get_indexed_path
|
||||||
from ..base_cpuif import BaseCpuif
|
from ..base_cpuif import BaseCpuif
|
||||||
|
from .apb3_interface import APB3FlatInterface
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ...exporter import BusDecoderExporter
|
||||||
|
|
||||||
|
|
||||||
class APB3CpuifFlat(BaseCpuif):
|
class APB3CpuifFlat(BaseCpuif):
|
||||||
template_path = "apb3_tmpl.sv"
|
template_path = "apb3_tmpl.sv"
|
||||||
is_interface = False
|
|
||||||
|
|
||||||
def _port_declaration(self, child: AddressableNode) -> list[str]:
|
def __init__(self, exp: "BusDecoderExporter") -> None:
|
||||||
return [
|
super().__init__(exp)
|
||||||
f"input logic {self.signal('PCLK', child)}",
|
self._interface = APB3FlatInterface(self)
|
||||||
f"input logic {self.signal('PRESETn', child)}",
|
|
||||||
f"output logic {self.signal('PSELx', child)}",
|
@property
|
||||||
f"output logic {self.signal('PENABLE', child)}",
|
def is_interface(self) -> bool:
|
||||||
f"output logic {self.signal('PWRITE', child)}",
|
return self._interface.is_interface
|
||||||
f"output logic [{self.addr_width - 1}:0] {self.signal('PADDR', child)}",
|
|
||||||
f"output logic [{self.data_width - 1}:0] {self.signal('PWDATA', child)}",
|
|
||||||
f"input logic [{self.data_width - 1}:0] {self.signal('PRDATA', child)}",
|
|
||||||
f"input logic {self.signal('PREADY', child)}",
|
|
||||||
f"input logic {self.signal('PSLVERR', child)}",
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port_declaration(self) -> str:
|
def port_declaration(self) -> str:
|
||||||
slave_ports: list[str] = [
|
return self._interface.get_port_declaration("s_apb_", "m_apb_")
|
||||||
f"input logic {self.signal('PCLK')}",
|
|
||||||
f"input logic {self.signal('PRESETn')}",
|
|
||||||
f"input logic {self.signal('PSELx')}",
|
|
||||||
f"input logic {self.signal('PENABLE')}",
|
|
||||||
f"input logic {self.signal('PWRITE')}",
|
|
||||||
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR')}",
|
|
||||||
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA')}",
|
|
||||||
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA')}",
|
|
||||||
f"output logic {self.signal('PREADY')}",
|
|
||||||
f"output logic {self.signal('PSLVERR')}",
|
|
||||||
]
|
|
||||||
master_ports: list[str] = []
|
|
||||||
for child in self.addressable_children:
|
|
||||||
master_ports.extend(self._port_declaration(child))
|
|
||||||
|
|
||||||
return ",\n".join(slave_ports + master_ports)
|
|
||||||
|
|
||||||
def signal(
|
def signal(
|
||||||
self,
|
self,
|
||||||
@@ -48,27 +31,11 @@ class APB3CpuifFlat(BaseCpuif):
|
|||||||
node: AddressableNode | None = None,
|
node: AddressableNode | None = None,
|
||||||
idx: str | int | None = None,
|
idx: str | int | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
mapped_signal = "PSELx" if signal == "PSEL" else signal
|
return self._interface.signal(signal, node, idx)
|
||||||
if node is None:
|
|
||||||
# Node is none, so this is a slave signal
|
|
||||||
return f"s_apb_{mapped_signal}"
|
|
||||||
|
|
||||||
# Master signal
|
|
||||||
base = f"m_apb_{node.inst_name}"
|
|
||||||
if not self.check_is_array(node):
|
|
||||||
# Not an array or an unrolled element
|
|
||||||
if node.current_idx is not None:
|
|
||||||
# This is a specific instance of an unrolled array
|
|
||||||
return f"{base}_{mapped_signal}_{'_'.join(map(str, node.current_idx))}"
|
|
||||||
return f"{base}_{mapped_signal}"
|
|
||||||
# Is an array
|
|
||||||
if idx is not None:
|
|
||||||
return f"{base}_{mapped_signal}[{idx}]"
|
|
||||||
return f"{base}_{mapped_signal}[N_{node.inst_name.upper()}S]"
|
|
||||||
|
|
||||||
def fanout(self, node: AddressableNode) -> str:
|
def fanout(self, node: AddressableNode) -> str:
|
||||||
fanout: dict[str, str] = {}
|
fanout: dict[str, str] = {}
|
||||||
fanout[self.signal("PSELx", node)] = (
|
fanout[self.signal("PSEL", node)] = (
|
||||||
f"cpuif_wr_sel.{get_indexed_path(self.exp.ds.top_node, node, 'i')}|cpuif_rd_sel.{get_indexed_path(self.exp.ds.top_node, node, 'i')}"
|
f"cpuif_wr_sel.{get_indexed_path(self.exp.ds.top_node, node, 'i')}|cpuif_rd_sel.{get_indexed_path(self.exp.ds.top_node, node, 'i')}"
|
||||||
)
|
)
|
||||||
fanout[self.signal("PENABLE", node)] = self.signal("PENABLE")
|
fanout[self.signal("PENABLE", node)] = self.signal("PENABLE")
|
||||||
|
|||||||
56
src/peakrdl_busdecoder/cpuif/apb3/apb3_interface.py
Normal file
56
src/peakrdl_busdecoder/cpuif/apb3/apb3_interface.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
"""APB3-specific interface implementations."""
|
||||||
|
|
||||||
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
|
from ..interface import FlatInterface, SVInterface
|
||||||
|
|
||||||
|
|
||||||
|
class APB3SVInterface(SVInterface):
|
||||||
|
"""APB3 SystemVerilog interface."""
|
||||||
|
|
||||||
|
def get_interface_type(self) -> str:
|
||||||
|
return "apb3_intf"
|
||||||
|
|
||||||
|
def get_slave_name(self) -> str:
|
||||||
|
return "s_apb"
|
||||||
|
|
||||||
|
def get_master_prefix(self) -> str:
|
||||||
|
return "m_apb_"
|
||||||
|
|
||||||
|
|
||||||
|
class APB3FlatInterface(FlatInterface):
|
||||||
|
"""APB3 flat signal interface."""
|
||||||
|
|
||||||
|
def get_slave_prefix(self) -> str:
|
||||||
|
return "s_apb_"
|
||||||
|
|
||||||
|
def get_master_prefix(self) -> str:
|
||||||
|
return "m_apb_"
|
||||||
|
|
||||||
|
def _get_slave_port_declarations(self, slave_prefix: str) -> list[str]:
|
||||||
|
return [
|
||||||
|
f"input logic {slave_prefix}PCLK",
|
||||||
|
f"input logic {slave_prefix}PRESETn",
|
||||||
|
f"input logic {slave_prefix}PSEL",
|
||||||
|
f"input logic {slave_prefix}PENABLE",
|
||||||
|
f"input logic {slave_prefix}PWRITE",
|
||||||
|
f"input logic [{self.cpuif.addr_width - 1}:0] {slave_prefix}PADDR",
|
||||||
|
f"input logic [{self.cpuif.data_width - 1}:0] {slave_prefix}PWDATA",
|
||||||
|
f"output logic [{self.cpuif.data_width - 1}:0] {slave_prefix}PRDATA",
|
||||||
|
f"output logic {slave_prefix}PREADY",
|
||||||
|
f"output logic {slave_prefix}PSLVERR",
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_master_port_declarations(self, child: AddressableNode, master_prefix: str) -> list[str]:
|
||||||
|
return [
|
||||||
|
f"output logic {self.signal('PCLK', child)}",
|
||||||
|
f"output logic {self.signal('PRESETn', child)}",
|
||||||
|
f"output logic {self.signal('PSEL', child)}",
|
||||||
|
f"output logic {self.signal('PENABLE', child)}",
|
||||||
|
f"output logic {self.signal('PWRITE', child)}",
|
||||||
|
f"output logic [{self.cpuif.addr_width - 1}:0] {self.signal('PADDR', child)}",
|
||||||
|
f"output logic [{self.cpuif.data_width - 1}:0] {self.signal('PWDATA', child)}",
|
||||||
|
f"input logic [{self.cpuif.data_width - 1}:0] {self.signal('PRDATA', child)}",
|
||||||
|
f"input logic {self.signal('PREADY', child)}",
|
||||||
|
f"input logic {self.signal('PSLVERR', child)}",
|
||||||
|
]
|
||||||
@@ -1,48 +1,37 @@
|
|||||||
from typing import overload
|
from typing import TYPE_CHECKING, overload
|
||||||
|
|
||||||
from systemrdl.node import AddressableNode
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
from ...utils import get_indexed_path
|
from ...utils import get_indexed_path
|
||||||
from ..base_cpuif import BaseCpuif
|
from ..base_cpuif import BaseCpuif
|
||||||
|
from .apb4_interface import APB4SVInterface
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ...exporter import BusDecoderExporter
|
||||||
|
|
||||||
|
|
||||||
class APB4Cpuif(BaseCpuif):
|
class APB4Cpuif(BaseCpuif):
|
||||||
template_path = "apb4_tmpl.sv"
|
template_path = "apb4_tmpl.sv"
|
||||||
is_interface = True
|
|
||||||
|
|
||||||
def _port_declaration(self, child: AddressableNode) -> str:
|
def __init__(self, exp: "BusDecoderExporter") -> None:
|
||||||
base = f"apb4_intf.master m_apb_{child.inst_name}"
|
super().__init__(exp)
|
||||||
|
self._interface = APB4SVInterface(self)
|
||||||
|
|
||||||
# When unrolled, current_idx is set - append it to the name
|
@property
|
||||||
if child.current_idx is not None:
|
def is_interface(self) -> bool:
|
||||||
base = f"{base}_{'_'.join(map(str, child.current_idx))}"
|
return self._interface.is_interface
|
||||||
|
|
||||||
# Only add array dimensions if this should be treated as an array
|
|
||||||
if self.check_is_array(child):
|
|
||||||
assert child.array_dimensions is not None
|
|
||||||
return f"{base} {''.join(f'[{dim}]' for dim in child.array_dimensions)}"
|
|
||||||
|
|
||||||
return base
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port_declaration(self) -> str:
|
def port_declaration(self) -> str:
|
||||||
"""Returns the port declaration for the APB4 interface."""
|
"""Returns the port declaration for the APB4 interface."""
|
||||||
slave_ports: list[str] = ["apb4_intf.slave s_apb"]
|
return self._interface.get_port_declaration("s_apb", "m_apb_")
|
||||||
master_ports: list[str] = list(map(self._port_declaration, self.addressable_children))
|
|
||||||
|
|
||||||
return ",\n".join(slave_ports + master_ports)
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def signal(self, signal: str, node: None = None, indexer: None = None) -> str: ...
|
def signal(self, signal: str, node: None = None, indexer: None = None) -> str: ...
|
||||||
@overload
|
@overload
|
||||||
def signal(self, signal: str, node: AddressableNode, indexer: str) -> str: ...
|
def signal(self, signal: str, node: AddressableNode, indexer: str) -> str: ...
|
||||||
def signal(self, signal: str, node: AddressableNode | None = None, indexer: str | None = None) -> str:
|
def signal(self, signal: str, node: AddressableNode | None = None, indexer: str | None = None) -> str:
|
||||||
if node is None or indexer is None:
|
return self._interface.signal(signal, node, indexer)
|
||||||
# Node is none, so this is a slave signal
|
|
||||||
return f"s_apb.{signal}"
|
|
||||||
|
|
||||||
# Master signal
|
|
||||||
return f"m_apb_{get_indexed_path(node.parent, node, indexer, skip_kw_filter=True)}.{signal}"
|
|
||||||
|
|
||||||
def fanout(self, node: AddressableNode) -> str:
|
def fanout(self, node: AddressableNode) -> str:
|
||||||
fanout: dict[str, str] = {}
|
fanout: dict[str, str] = {}
|
||||||
|
|||||||
@@ -1,50 +1,29 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from systemrdl.node import AddressableNode
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
from ...utils import get_indexed_path
|
from ...utils import get_indexed_path
|
||||||
from ..base_cpuif import BaseCpuif
|
from ..base_cpuif import BaseCpuif
|
||||||
|
from .apb4_interface import APB4FlatInterface
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ...exporter import BusDecoderExporter
|
||||||
|
|
||||||
|
|
||||||
class APB4CpuifFlat(BaseCpuif):
|
class APB4CpuifFlat(BaseCpuif):
|
||||||
template_path = "apb4_tmpl.sv"
|
template_path = "apb4_tmpl.sv"
|
||||||
is_interface = False
|
|
||||||
|
|
||||||
def _port_declaration(self, child: AddressableNode) -> list[str]:
|
def __init__(self, exp: "BusDecoderExporter") -> None:
|
||||||
return [
|
super().__init__(exp)
|
||||||
f"input logic {self.signal('PCLK', child)}",
|
self._interface = APB4FlatInterface(self)
|
||||||
f"input logic {self.signal('PRESETn', child)}",
|
|
||||||
f"output logic {self.signal('PSELx', child)}",
|
@property
|
||||||
f"output logic {self.signal('PENABLE', child)}",
|
def is_interface(self) -> bool:
|
||||||
f"output logic {self.signal('PWRITE', child)}",
|
return self._interface.is_interface
|
||||||
f"output logic [{self.addr_width - 1}:0] {self.signal('PADDR', child)}",
|
|
||||||
f"output logic [2:0] {self.signal('PPROT', child)}",
|
|
||||||
f"output logic [{self.data_width - 1}:0] {self.signal('PWDATA', child)}",
|
|
||||||
f"output logic [{self.data_width // 8 - 1}:0] {self.signal('PSTRB', child)}",
|
|
||||||
f"input logic [{self.data_width - 1}:0] {self.signal('PRDATA', child)}",
|
|
||||||
f"input logic {self.signal('PREADY', child)}",
|
|
||||||
f"input logic {self.signal('PSLVERR', child)}",
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port_declaration(self) -> str:
|
def port_declaration(self) -> str:
|
||||||
slave_ports: list[str] = [
|
return self._interface.get_port_declaration("s_apb_", "m_apb_")
|
||||||
f"input logic {self.signal('PCLK')}",
|
|
||||||
f"input logic {self.signal('PRESETn')}",
|
|
||||||
f"input logic {self.signal('PSELx')}",
|
|
||||||
f"input logic {self.signal('PENABLE')}",
|
|
||||||
f"input logic {self.signal('PWRITE')}",
|
|
||||||
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR')}",
|
|
||||||
f"input logic [2:0] {self.signal('PPROT')}",
|
|
||||||
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA')}",
|
|
||||||
f"input logic [{self.data_width // 8 - 1}:0] {self.signal('PSTRB')}",
|
|
||||||
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA')}",
|
|
||||||
f"output logic {self.signal('PREADY')}",
|
|
||||||
f"output logic {self.signal('PSLVERR')}",
|
|
||||||
]
|
|
||||||
master_ports: list[str] = []
|
|
||||||
for child in self.addressable_children:
|
|
||||||
master_ports.extend(self._port_declaration(child))
|
|
||||||
|
|
||||||
return ",\n".join(slave_ports + master_ports)
|
|
||||||
|
|
||||||
def signal(
|
def signal(
|
||||||
self,
|
self,
|
||||||
@@ -52,27 +31,11 @@ class APB4CpuifFlat(BaseCpuif):
|
|||||||
node: AddressableNode | None = None,
|
node: AddressableNode | None = None,
|
||||||
idx: str | int | None = None,
|
idx: str | int | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
mapped_signal = "PSELx" if signal == "PSEL" else signal
|
return self._interface.signal(signal, node, idx)
|
||||||
if node is None:
|
|
||||||
# Node is none, so this is a slave signal
|
|
||||||
return f"s_apb_{mapped_signal}"
|
|
||||||
|
|
||||||
# Master signal
|
|
||||||
base = f"m_apb_{node.inst_name}"
|
|
||||||
if not self.check_is_array(node):
|
|
||||||
# Not an array or an unrolled element
|
|
||||||
if node.current_idx is not None:
|
|
||||||
# This is a specific instance of an unrolled array
|
|
||||||
return f"{base}_{mapped_signal}_{'_'.join(map(str, node.current_idx))}"
|
|
||||||
return f"{base}_{mapped_signal}"
|
|
||||||
# Is an array
|
|
||||||
if idx is not None:
|
|
||||||
return f"{base}_{mapped_signal}[{idx}]"
|
|
||||||
return f"{base}_{mapped_signal}[N_{node.inst_name.upper()}S]"
|
|
||||||
|
|
||||||
def fanout(self, node: AddressableNode) -> str:
|
def fanout(self, node: AddressableNode) -> str:
|
||||||
fanout: dict[str, str] = {}
|
fanout: dict[str, str] = {}
|
||||||
fanout[self.signal("PSELx", node)] = (
|
fanout[self.signal("PSEL", node)] = (
|
||||||
f"cpuif_wr_sel.{get_indexed_path(self.exp.ds.top_node, node, 'i')}|cpuif_rd_sel.{get_indexed_path(self.exp.ds.top_node, node, 'i')}"
|
f"cpuif_wr_sel.{get_indexed_path(self.exp.ds.top_node, node, 'i')}|cpuif_rd_sel.{get_indexed_path(self.exp.ds.top_node, node, 'i')}"
|
||||||
)
|
)
|
||||||
fanout[self.signal("PENABLE", node)] = self.signal("PENABLE")
|
fanout[self.signal("PENABLE", node)] = self.signal("PENABLE")
|
||||||
|
|||||||
60
src/peakrdl_busdecoder/cpuif/apb4/apb4_interface.py
Normal file
60
src/peakrdl_busdecoder/cpuif/apb4/apb4_interface.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
"""APB4-specific interface implementations."""
|
||||||
|
|
||||||
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
|
from ..interface import FlatInterface, SVInterface
|
||||||
|
|
||||||
|
|
||||||
|
class APB4SVInterface(SVInterface):
|
||||||
|
"""APB4 SystemVerilog interface."""
|
||||||
|
|
||||||
|
def get_interface_type(self) -> str:
|
||||||
|
return "apb4_intf"
|
||||||
|
|
||||||
|
def get_slave_name(self) -> str:
|
||||||
|
return "s_apb"
|
||||||
|
|
||||||
|
def get_master_prefix(self) -> str:
|
||||||
|
return "m_apb_"
|
||||||
|
|
||||||
|
|
||||||
|
class APB4FlatInterface(FlatInterface):
|
||||||
|
"""APB4 flat signal interface."""
|
||||||
|
|
||||||
|
def get_slave_prefix(self) -> str:
|
||||||
|
return "s_apb_"
|
||||||
|
|
||||||
|
def get_master_prefix(self) -> str:
|
||||||
|
return "m_apb_"
|
||||||
|
|
||||||
|
def _get_slave_port_declarations(self, slave_prefix: str) -> list[str]:
|
||||||
|
return [
|
||||||
|
f"input logic {slave_prefix}PCLK",
|
||||||
|
f"input logic {slave_prefix}PRESETn",
|
||||||
|
f"input logic {slave_prefix}PSEL",
|
||||||
|
f"input logic {slave_prefix}PENABLE",
|
||||||
|
f"input logic {slave_prefix}PWRITE",
|
||||||
|
f"input logic [{self.cpuif.addr_width - 1}:0] {slave_prefix}PADDR",
|
||||||
|
f"input logic [2:0] {slave_prefix}PPROT",
|
||||||
|
f"input logic [{self.cpuif.data_width - 1}:0] {slave_prefix}PWDATA",
|
||||||
|
f"input logic [{self.cpuif.data_width // 8 - 1}:0] {slave_prefix}PSTRB",
|
||||||
|
f"output logic [{self.cpuif.data_width - 1}:0] {slave_prefix}PRDATA",
|
||||||
|
f"output logic {slave_prefix}PREADY",
|
||||||
|
f"output logic {slave_prefix}PSLVERR",
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_master_port_declarations(self, child: AddressableNode, master_prefix: str) -> list[str]:
|
||||||
|
return [
|
||||||
|
f"output logic {self.signal('PCLK', child)}",
|
||||||
|
f"output logic {self.signal('PRESETn', child)}",
|
||||||
|
f"output logic {self.signal('PSEL', child)}",
|
||||||
|
f"output logic {self.signal('PENABLE', child)}",
|
||||||
|
f"output logic {self.signal('PWRITE', child)}",
|
||||||
|
f"output logic [{self.cpuif.addr_width - 1}:0] {self.signal('PADDR', child)}",
|
||||||
|
f"output logic [2:0] {self.signal('PPROT', child)}",
|
||||||
|
f"output logic [{self.cpuif.data_width - 1}:0] {self.signal('PWDATA', child)}",
|
||||||
|
f"output logic [{self.cpuif.data_width // 8 - 1}:0] {self.signal('PSTRB', child)}",
|
||||||
|
f"input logic [{self.cpuif.data_width - 1}:0] {self.signal('PRDATA', child)}",
|
||||||
|
f"input logic {self.signal('PREADY', child)}",
|
||||||
|
f"input logic {self.signal('PSLVERR', child)}",
|
||||||
|
]
|
||||||
@@ -1,51 +1,37 @@
|
|||||||
from typing import overload
|
from typing import TYPE_CHECKING, overload
|
||||||
|
|
||||||
from systemrdl.node import AddressableNode
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
from ...utils import get_indexed_path
|
from ...utils import get_indexed_path
|
||||||
from ..base_cpuif import BaseCpuif
|
from ..base_cpuif import BaseCpuif
|
||||||
|
from .axi4lite_interface import AXI4LiteSVInterface
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ...exporter import BusDecoderExporter
|
||||||
|
|
||||||
|
|
||||||
class AXI4LiteCpuif(BaseCpuif):
|
class AXI4LiteCpuif(BaseCpuif):
|
||||||
template_path = "axi4lite_tmpl.sv"
|
template_path = "axi4lite_tmpl.sv"
|
||||||
is_interface = True
|
|
||||||
|
|
||||||
def _port_declaration(self, child: AddressableNode) -> list[str]:
|
def __init__(self, exp: "BusDecoderExporter") -> None:
|
||||||
base = f"axi4lite_intf.master m_axil_{child.inst_name}"
|
super().__init__(exp)
|
||||||
|
self._interface = AXI4LiteSVInterface(self)
|
||||||
|
|
||||||
# When unrolled, current_idx is set - append it to the name
|
@property
|
||||||
if child.current_idx is not None:
|
def is_interface(self) -> bool:
|
||||||
base = f"{base}_{'_'.join(map(str, child.current_idx))}"
|
return self._interface.is_interface
|
||||||
|
|
||||||
# Only add array dimensions if this should be treated as an array
|
|
||||||
if self.check_is_array(child):
|
|
||||||
assert child.array_dimensions is not None
|
|
||||||
return [f"{base} {''.join(f'[{dim}]' for dim in child.array_dimensions)}"]
|
|
||||||
|
|
||||||
return [base]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port_declaration(self) -> str:
|
def port_declaration(self) -> str:
|
||||||
"""Returns the port declaration for the AXI4-Lite interface."""
|
"""Returns the port declaration for the AXI4-Lite interface."""
|
||||||
slave_ports: list[str] = ["axi4lite_intf.slave s_axil"]
|
return self._interface.get_port_declaration("s_axil", "m_axil_")
|
||||||
|
|
||||||
master_ports: list[str] = []
|
|
||||||
for child in self.addressable_children:
|
|
||||||
master_ports.extend(self._port_declaration(child))
|
|
||||||
|
|
||||||
return ",\n".join(slave_ports + master_ports)
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def signal(self, signal: str, node: None = None, indexer: None = None) -> str: ...
|
def signal(self, signal: str, node: None = None, indexer: None = None) -> str: ...
|
||||||
@overload
|
@overload
|
||||||
def signal(self, signal: str, node: AddressableNode, indexer: str | None = None) -> str: ...
|
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:
|
def signal(self, signal: str, node: AddressableNode | None = None, indexer: str | None = None) -> str:
|
||||||
if node is None or indexer is None:
|
return self._interface.signal(signal, node, indexer)
|
||||||
# Node is none, so this is a slave signal
|
|
||||||
return f"s_axil.{signal}"
|
|
||||||
|
|
||||||
# Master signal
|
|
||||||
return f"m_axil_{get_indexed_path(node.parent, node, indexer, skip_kw_filter=True)}.{signal}"
|
|
||||||
|
|
||||||
def fanout(self, node: AddressableNode) -> str:
|
def fanout(self, node: AddressableNode) -> str:
|
||||||
fanout: dict[str, str] = {}
|
fanout: dict[str, str] = {}
|
||||||
|
|||||||
@@ -1,92 +1,86 @@
|
|||||||
from typing import overload
|
from typing import TYPE_CHECKING, overload
|
||||||
|
|
||||||
from systemrdl.node import AddressableNode
|
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."""
|
"""Verilator-friendly variant that flattens the AXI4-Lite interface ports."""
|
||||||
|
|
||||||
template_path = "axi4lite_tmpl.sv"
|
template_path = "axi4lite_tmpl.sv"
|
||||||
is_interface = False
|
|
||||||
|
|
||||||
def _port_declaration(self, child: AddressableNode) -> list[str]:
|
def __init__(self, exp: "BusDecoderExporter") -> None:
|
||||||
return [
|
super().__init__(exp)
|
||||||
f"input logic {self.signal('AWREADY', child)}",
|
self._interface = AXI4LiteFlatInterface(self)
|
||||||
f"output logic {self.signal('AWVALID', child)}",
|
|
||||||
f"output logic [{self.addr_width - 1}:0] {self.signal('AWADDR', child)}",
|
@property
|
||||||
f"output logic [2:0] {self.signal('AWPROT', child)}",
|
def is_interface(self) -> bool:
|
||||||
f"input logic {self.signal('WREADY', child)}",
|
return self._interface.is_interface
|
||||||
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)}",
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port_declaration(self) -> str:
|
def port_declaration(self) -> str:
|
||||||
slave_ports: list[str] = [
|
"""Returns the port declaration for the AXI4-Lite interface."""
|
||||||
f"input logic {self.signal('ACLK')}",
|
return self._interface.get_port_declaration("s_axil_", "m_axil_")
|
||||||
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)
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def signal(self, signal: str, node: None = None, indexer: None = None) -> str: ...
|
def signal(self, signal: str, node: None = None, indexer: None = None) -> str: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def signal(self, signal: str, node: AddressableNode, indexer: str | None = None) -> str: ...
|
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(
|
def fanout(self, node: AddressableNode) -> str:
|
||||||
self,
|
fanout: dict[str, str] = {}
|
||||||
signal: str,
|
|
||||||
node: AddressableNode | None = None,
|
wr_sel = f"cpuif_wr_sel.{get_indexed_path(self.exp.ds.top_node, node, 'gi')}"
|
||||||
indexer: str | None = None,
|
rd_sel = f"cpuif_rd_sel.{get_indexed_path(self.exp.ds.top_node, node, 'gi')}"
|
||||||
) -> str:
|
|
||||||
|
# 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:
|
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}"
|
return "\n".join(f"{lhs} = {rhs};" for lhs, rhs in fanin.items())
|
||||||
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
|
|
||||||
|
|
||||||
if indexer is None:
|
def readback(self, node: AddressableNode | None = None) -> str:
|
||||||
return f"{base}[N_{node.inst_name.upper()}S]"
|
fanin: dict[str, str] = {}
|
||||||
return f"{base}[{indexer}]"
|
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())
|
||||||
|
|||||||
84
src/peakrdl_busdecoder/cpuif/axi4lite/axi4lite_interface.py
Normal file
84
src/peakrdl_busdecoder/cpuif/axi4lite/axi4lite_interface.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
"""AXI4-Lite-specific interface implementations."""
|
||||||
|
|
||||||
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
|
from ..interface import FlatInterface, SVInterface
|
||||||
|
|
||||||
|
|
||||||
|
class AXI4LiteSVInterface(SVInterface):
|
||||||
|
"""AXI4-Lite SystemVerilog interface."""
|
||||||
|
|
||||||
|
def get_interface_type(self) -> str:
|
||||||
|
return "axi4lite_intf"
|
||||||
|
|
||||||
|
def get_slave_name(self) -> str:
|
||||||
|
return "s_axil"
|
||||||
|
|
||||||
|
def get_master_prefix(self) -> str:
|
||||||
|
return "m_axil_"
|
||||||
|
|
||||||
|
|
||||||
|
class AXI4LiteFlatInterface(FlatInterface):
|
||||||
|
"""AXI4-Lite flat signal interface."""
|
||||||
|
|
||||||
|
def get_slave_prefix(self) -> str:
|
||||||
|
return "s_axil_"
|
||||||
|
|
||||||
|
def get_master_prefix(self) -> str:
|
||||||
|
return "m_axil_"
|
||||||
|
|
||||||
|
def _get_slave_port_declarations(self, slave_prefix: str) -> list[str]:
|
||||||
|
return [
|
||||||
|
# Write address channel
|
||||||
|
f"input logic {slave_prefix}AWVALID",
|
||||||
|
f"output logic {slave_prefix}AWREADY",
|
||||||
|
f"input logic [{self.cpuif.addr_width - 1}:0] {slave_prefix}AWADDR",
|
||||||
|
f"input logic [2:0] {slave_prefix}AWPROT",
|
||||||
|
# Write data channel
|
||||||
|
f"input logic {slave_prefix}WVALID",
|
||||||
|
f"output logic {slave_prefix}WREADY",
|
||||||
|
f"input logic [{self.cpuif.data_width - 1}:0] {slave_prefix}WDATA",
|
||||||
|
f"input logic [{self.cpuif.data_width // 8 - 1}:0] {slave_prefix}WSTRB",
|
||||||
|
# Write response channel
|
||||||
|
f"output logic {slave_prefix}BVALID",
|
||||||
|
f"input logic {slave_prefix}BREADY",
|
||||||
|
f"output logic [1:0] {slave_prefix}BRESP",
|
||||||
|
# Read address channel
|
||||||
|
f"input logic {slave_prefix}ARVALID",
|
||||||
|
f"output logic {slave_prefix}ARREADY",
|
||||||
|
f"input logic [{self.cpuif.addr_width - 1}:0] {slave_prefix}ARADDR",
|
||||||
|
f"input logic [2:0] {slave_prefix}ARPROT",
|
||||||
|
# Read data channel
|
||||||
|
f"output logic {slave_prefix}RVALID",
|
||||||
|
f"input logic {slave_prefix}RREADY",
|
||||||
|
f"output logic [{self.cpuif.data_width - 1}:0] {slave_prefix}RDATA",
|
||||||
|
f"output logic [1:0] {slave_prefix}RRESP",
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_master_port_declarations(self, child: AddressableNode, master_prefix: str) -> list[str]:
|
||||||
|
return [
|
||||||
|
# Write address channel
|
||||||
|
f"output logic {self.signal('AWVALID', child)}",
|
||||||
|
f"input logic {self.signal('AWREADY', child)}",
|
||||||
|
f"output logic [{self.cpuif.addr_width - 1}:0] {self.signal('AWADDR', child)}",
|
||||||
|
f"output logic [2:0] {self.signal('AWPROT', child)}",
|
||||||
|
# Write data channel
|
||||||
|
f"output logic {self.signal('WVALID', child)}",
|
||||||
|
f"input logic {self.signal('WREADY', child)}",
|
||||||
|
f"output logic [{self.cpuif.data_width - 1}:0] {self.signal('WDATA', child)}",
|
||||||
|
f"output logic [{self.cpuif.data_width // 8 - 1}:0] {self.signal('WSTRB', child)}",
|
||||||
|
# Write response channel
|
||||||
|
f"input logic {self.signal('BVALID', child)}",
|
||||||
|
f"output logic {self.signal('BREADY', child)}",
|
||||||
|
f"input logic [1:0] {self.signal('BRESP', child)}",
|
||||||
|
# Read address channel
|
||||||
|
f"output logic {self.signal('ARVALID', child)}",
|
||||||
|
f"input logic {self.signal('ARREADY', child)}",
|
||||||
|
f"output logic [{self.cpuif.addr_width - 1}:0] {self.signal('ARADDR', child)}",
|
||||||
|
f"output logic [2:0] {self.signal('ARPROT', child)}",
|
||||||
|
# Read data channel
|
||||||
|
f"input logic {self.signal('RVALID', child)}",
|
||||||
|
f"output logic {self.signal('RREADY', child)}",
|
||||||
|
f"input logic [{self.cpuif.data_width - 1}:0] {self.signal('RDATA', child)}",
|
||||||
|
f"input logic [1:0] {self.signal('RRESP', child)}",
|
||||||
|
]
|
||||||
190
src/peakrdl_busdecoder/cpuif/interface.py
Normal file
190
src/peakrdl_busdecoder/cpuif/interface.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
"""Interface abstraction for handling flat and non-flat signal declarations."""
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .base_cpuif import BaseCpuif
|
||||||
|
|
||||||
|
|
||||||
|
class Interface(ABC):
|
||||||
|
"""Abstract base class for interface signal handling."""
|
||||||
|
|
||||||
|
def __init__(self, cpuif: "BaseCpuif") -> None:
|
||||||
|
self.cpuif = cpuif
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def is_interface(self) -> bool:
|
||||||
|
"""Whether this uses SystemVerilog interfaces."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_port_declaration(self, slave_name: str, master_prefix: str) -> str:
|
||||||
|
"""
|
||||||
|
Generate port declarations for the interface.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
slave_name: Name of the slave interface/signal prefix
|
||||||
|
master_prefix: Prefix for master interfaces/signals
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Port declarations as a string
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def signal(
|
||||||
|
self,
|
||||||
|
signal: str,
|
||||||
|
node: AddressableNode | None = None,
|
||||||
|
indexer: str | int | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Generate signal reference.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
signal: Signal name
|
||||||
|
node: Optional addressable node for master signals
|
||||||
|
indexer: Optional indexer for arrays.
|
||||||
|
For SVInterface: str like "i" or "gi" for loop indices
|
||||||
|
For FlatInterface: str or int for array subscript
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Signal reference as a string
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SVInterface(Interface):
|
||||||
|
"""SystemVerilog interface-based signal handling."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_interface(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_port_declaration(self, slave_name: str, master_prefix: str) -> str:
|
||||||
|
"""Generate SystemVerilog interface port declarations."""
|
||||||
|
slave_ports: list[str] = [f"{self.get_interface_type()}.slave {slave_name}"]
|
||||||
|
master_ports: list[str] = []
|
||||||
|
|
||||||
|
for child in self.cpuif.addressable_children:
|
||||||
|
base = f"{self.get_interface_type()}.master {master_prefix}{child.inst_name}"
|
||||||
|
|
||||||
|
# When unrolled, current_idx is set - append it to the name
|
||||||
|
if child.current_idx is not None:
|
||||||
|
base = f"{base}_{'_'.join(map(str, child.current_idx))}"
|
||||||
|
|
||||||
|
# Only add array dimensions if this should be treated as an array
|
||||||
|
if self.cpuif.check_is_array(child):
|
||||||
|
assert child.array_dimensions is not None
|
||||||
|
base = f"{base} {''.join(f'[{dim}]' for dim in child.array_dimensions)}"
|
||||||
|
|
||||||
|
master_ports.append(base)
|
||||||
|
|
||||||
|
return ",\n".join(slave_ports + master_ports)
|
||||||
|
|
||||||
|
def signal(
|
||||||
|
self,
|
||||||
|
signal: str,
|
||||||
|
node: AddressableNode | None = None,
|
||||||
|
indexer: str | int | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Generate SystemVerilog interface signal reference."""
|
||||||
|
from ..utils import get_indexed_path
|
||||||
|
|
||||||
|
# SVInterface only supports string indexers (loop variable names like "i", "gi")
|
||||||
|
if indexer is not None and not isinstance(indexer, str):
|
||||||
|
raise TypeError(f"SVInterface.signal() requires string indexer, got {type(indexer).__name__}")
|
||||||
|
|
||||||
|
if node is None or indexer is None:
|
||||||
|
# Node is none, so this is a slave signal
|
||||||
|
slave_name = self.get_slave_name()
|
||||||
|
return f"{slave_name}.{signal}"
|
||||||
|
|
||||||
|
# Master signal
|
||||||
|
master_prefix = self.get_master_prefix()
|
||||||
|
return f"{master_prefix}{get_indexed_path(node.parent, node, indexer, skip_kw_filter=True)}.{signal}"
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_interface_type(self) -> str:
|
||||||
|
"""Get the SystemVerilog interface type name."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_slave_name(self) -> str:
|
||||||
|
"""Get the slave interface instance name."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_master_prefix(self) -> str:
|
||||||
|
"""Get the master interface name prefix."""
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class FlatInterface(Interface):
|
||||||
|
"""Flat signal-based interface handling."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_interface(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_port_declaration(self, slave_name: str, master_prefix: str) -> str:
|
||||||
|
"""Generate flat port declarations."""
|
||||||
|
slave_ports = self._get_slave_port_declarations(slave_name)
|
||||||
|
master_ports: list[str] = []
|
||||||
|
|
||||||
|
for child in self.cpuif.addressable_children:
|
||||||
|
master_ports.extend(self._get_master_port_declarations(child, master_prefix))
|
||||||
|
|
||||||
|
return ",\n".join(slave_ports + master_ports)
|
||||||
|
|
||||||
|
def signal(
|
||||||
|
self,
|
||||||
|
signal: str,
|
||||||
|
node: AddressableNode | None = None,
|
||||||
|
indexer: str | int | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Generate flat signal reference."""
|
||||||
|
if node is None:
|
||||||
|
# Node is none, so this is a slave signal
|
||||||
|
slave_prefix = self.get_slave_prefix()
|
||||||
|
return f"{slave_prefix}{signal}"
|
||||||
|
|
||||||
|
# Master signal
|
||||||
|
master_prefix = self.get_master_prefix()
|
||||||
|
base = f"{master_prefix}{node.inst_name}"
|
||||||
|
|
||||||
|
if not self.cpuif.check_is_array(node):
|
||||||
|
# Not an array or an unrolled element
|
||||||
|
if node.current_idx is not None:
|
||||||
|
# This is a specific instance of an unrolled array
|
||||||
|
return f"{base}_{signal}_{'_'.join(map(str, node.current_idx))}"
|
||||||
|
return f"{base}_{signal}"
|
||||||
|
|
||||||
|
# Is an array
|
||||||
|
if indexer is not None:
|
||||||
|
return f"{base}_{signal}[{indexer}]"
|
||||||
|
return f"{base}_{signal}[N_{node.inst_name.upper()}S]"
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _get_slave_port_declarations(self, slave_prefix: str) -> list[str]:
|
||||||
|
"""Get slave port declarations."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _get_master_port_declarations(self, child: AddressableNode, master_prefix: str) -> list[str]:
|
||||||
|
"""Get master port declarations for a child node."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_slave_prefix(self) -> str:
|
||||||
|
"""Get the slave signal name prefix."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_master_prefix(self) -> str:
|
||||||
|
"""Get the master signal name prefix."""
|
||||||
|
...
|
||||||
@@ -12,7 +12,7 @@ READ_DATA = 0x0BAD_F00D
|
|||||||
class _Apb3SlaveShim:
|
class _Apb3SlaveShim:
|
||||||
def __init__(self, dut):
|
def __init__(self, dut):
|
||||||
prefix = "s_apb"
|
prefix = "s_apb"
|
||||||
self.PSEL = getattr(dut, f"{prefix}_PSELx")
|
self.PSEL = getattr(dut, f"{prefix}_PSEL")
|
||||||
self.PENABLE = getattr(dut, f"{prefix}_PENABLE")
|
self.PENABLE = getattr(dut, f"{prefix}_PENABLE")
|
||||||
self.PWRITE = getattr(dut, f"{prefix}_PWRITE")
|
self.PWRITE = getattr(dut, f"{prefix}_PWRITE")
|
||||||
self.PADDR = getattr(dut, f"{prefix}_PADDR")
|
self.PADDR = getattr(dut, f"{prefix}_PADDR")
|
||||||
@@ -24,7 +24,7 @@ class _Apb3SlaveShim:
|
|||||||
|
|
||||||
class _Apb3MasterShim:
|
class _Apb3MasterShim:
|
||||||
def __init__(self, dut, base: str):
|
def __init__(self, dut, base: str):
|
||||||
self.PSEL = getattr(dut, f"{base}_PSELx")
|
self.PSEL = getattr(dut, f"{base}_PSEL")
|
||||||
self.PENABLE = getattr(dut, f"{base}_PENABLE")
|
self.PENABLE = getattr(dut, f"{base}_PENABLE")
|
||||||
self.PWRITE = getattr(dut, f"{base}_PWRITE")
|
self.PWRITE = getattr(dut, f"{base}_PWRITE")
|
||||||
self.PADDR = getattr(dut, f"{base}_PADDR")
|
self.PADDR = getattr(dut, f"{base}_PADDR")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ READ_DATA = 0x89AB_CDEF
|
|||||||
class _Apb4SlaveShim:
|
class _Apb4SlaveShim:
|
||||||
def __init__(self, dut):
|
def __init__(self, dut):
|
||||||
prefix = "s_apb"
|
prefix = "s_apb"
|
||||||
self.PSEL = getattr(dut, f"{prefix}_PSELx")
|
self.PSEL = getattr(dut, f"{prefix}_PSEL")
|
||||||
self.PENABLE = getattr(dut, f"{prefix}_PENABLE")
|
self.PENABLE = getattr(dut, f"{prefix}_PENABLE")
|
||||||
self.PWRITE = getattr(dut, f"{prefix}_PWRITE")
|
self.PWRITE = getattr(dut, f"{prefix}_PWRITE")
|
||||||
self.PADDR = getattr(dut, f"{prefix}_PADDR")
|
self.PADDR = getattr(dut, f"{prefix}_PADDR")
|
||||||
@@ -26,7 +26,7 @@ class _Apb4SlaveShim:
|
|||||||
|
|
||||||
class _Apb4MasterShim:
|
class _Apb4MasterShim:
|
||||||
def __init__(self, dut, base: str):
|
def __init__(self, dut, base: str):
|
||||||
self.PSEL = getattr(dut, f"{base}_PSELx")
|
self.PSEL = getattr(dut, f"{base}_PSEL")
|
||||||
self.PENABLE = getattr(dut, f"{base}_PENABLE")
|
self.PENABLE = getattr(dut, f"{base}_PENABLE")
|
||||||
self.PWRITE = getattr(dut, f"{base}_PWRITE")
|
self.PWRITE = getattr(dut, f"{base}_PWRITE")
|
||||||
self.PADDR = getattr(dut, f"{base}_PADDR")
|
self.PADDR = getattr(dut, f"{base}_PADDR")
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
[pytest]
|
|
||||||
python_files = test_*.py testcase.py
|
|
||||||
markers =
|
|
||||||
simulation: marks hardware simulation tests requiring cocotb/verilator
|
|
||||||
verilator: marks tests that invoke the Verilator toolchain
|
|
||||||
Reference in New Issue
Block a user