Compare commits
9 Commits
b0db1f60e4
...
copilot/su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3191ab3848 | ||
|
|
227df5ad05 | ||
|
|
e95069019a | ||
|
|
b66681f46a | ||
|
|
b942c2e2d2 | ||
|
|
6bb4f08ca4 | ||
|
|
9bf1c3e944 | ||
|
|
fdac38133c | ||
|
|
bbbeab85c5 |
@@ -1,22 +0,0 @@
|
|||||||
FROM verilator/verilator:latest
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends \
|
|
||||||
python3 \
|
|
||||||
python3-venv \
|
|
||||||
python3-pip \
|
|
||||||
python3-dev \
|
|
||||||
build-essential \
|
|
||||||
pkg-config \
|
|
||||||
git \
|
|
||||||
curl \
|
|
||||||
ca-certificates && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
ENV UV_INSTALL_DIR=/usr/local/bin
|
|
||||||
ENV UV_LINK_MODE=copy
|
|
||||||
# Install uv globally so both VS Code and terminals can use it
|
|
||||||
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
||||||
RUN uv --version
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "PeakRDL BusDecoder",
|
|
||||||
"build": {
|
|
||||||
"dockerfile": "Dockerfile"
|
|
||||||
},
|
|
||||||
"runArgs": [
|
|
||||||
"--init"
|
|
||||||
],
|
|
||||||
"features": {
|
|
||||||
"ghcr.io/devcontainers/features/common-utils:2": {
|
|
||||||
"username": "vscode",
|
|
||||||
"uid": "1000",
|
|
||||||
"gid": "1000",
|
|
||||||
"installZsh": "false",
|
|
||||||
"installOhMyZsh": "false"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"remoteUser": "vscode",
|
|
||||||
"postCreateCommand": "uv sync --frozen --all-extras --group tools --group test",
|
|
||||||
"customizations": {
|
|
||||||
"vscode": {
|
|
||||||
"settings": {
|
|
||||||
"python.defaultInterpreterPath": ".venv/bin/python",
|
|
||||||
"terminal.integrated.shell.linux": "/bin/bash"
|
|
||||||
},
|
|
||||||
"extensions": [
|
|
||||||
"ms-python.python",
|
|
||||||
"ms-python.vscode-pylance",
|
|
||||||
"ms-vscode.cpptools",
|
|
||||||
"charliermarsh.ruff",
|
|
||||||
"astral-sh.ty",
|
|
||||||
"meta.pyrefly"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
16
.github/workflows/test.yml
vendored
16
.github/workflows/test.yml
vendored
@@ -14,13 +14,11 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
|
||||||
image: verilator/verilator:latest
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
|
python-version: ['3.10', '3.11', '3.12', '3.13']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -29,21 +27,19 @@ jobs:
|
|||||||
uses: astral-sh/setup-uv@v3
|
uses: astral-sh/setup-uv@v3
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
enable-cache: true
|
|
||||||
|
|
||||||
- name: Check Verilator version
|
- name: Install Verilator
|
||||||
run: verilator --version
|
|
||||||
|
|
||||||
- name: Install Python development packages
|
|
||||||
run: |
|
run: |
|
||||||
apt-get update && apt-get install -y python3-dev libpython3-dev
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y verilator
|
||||||
|
verilator --version
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
uv sync --all-extras --group test
|
uv sync --all-extras --group test
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: uv run pytest tests/ --cov=peakrdl_busdecoder --cov-report=xml --cov-report=term
|
run: uv run pytest tests/ -v --cov=peakrdl_busdecoder --cov-report=xml --cov-report=term
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v4
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ version = "0.5.0"
|
|||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = ["jinja2>=3.1.6", "systemrdl-compiler~=1.30.1"]
|
dependencies = ["jinja2>=3.1.6", "systemrdl-compiler~=1.30.1"]
|
||||||
|
|
||||||
authors = [{ name = "Arnav Sacheti" }]
|
authors = [{ name = "Alex Mykyta" }]
|
||||||
description = "Generate a SystemVerilog bus decoder from SystemRDL for splitting CPU interfaces to multiple sub-address spaces"
|
description = "Generate a SystemVerilog bus decoder from SystemRDL for splitting CPU interfaces to multiple sub-address spaces"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "LGPLv3" }
|
license = { text = "LGPLv3" }
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from peakrdl.config import schema
|
|||||||
from peakrdl.plugins.entry_points import get_entry_points
|
from peakrdl.plugins.entry_points import get_entry_points
|
||||||
from peakrdl.plugins.exporter import ExporterSubcommandPlugin
|
from peakrdl.plugins.exporter import ExporterSubcommandPlugin
|
||||||
|
|
||||||
from .cpuif import BaseCpuif, apb3, apb4, axi4lite, taxi_apb
|
from .cpuif import BaseCpuif, apb3, apb4, axi4lite
|
||||||
from .exporter import BusDecoderExporter
|
from .exporter import BusDecoderExporter
|
||||||
from .udps import ALL_UDPS
|
from .udps import ALL_UDPS
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@ def get_cpuifs(config: list[tuple[str, Any]]) -> dict[str, type[BaseCpuif]]:
|
|||||||
"apb3-flat": apb3.APB3CpuifFlat,
|
"apb3-flat": apb3.APB3CpuifFlat,
|
||||||
"apb4": apb4.APB4Cpuif,
|
"apb4": apb4.APB4Cpuif,
|
||||||
"apb4-flat": apb4.APB4CpuifFlat,
|
"apb4-flat": apb4.APB4CpuifFlat,
|
||||||
"taxi-apb": taxi_apb.TaxiAPBCpuif,
|
|
||||||
"axi4-lite": axi4lite.AXI4LiteCpuif,
|
"axi4-lite": axi4lite.AXI4LiteCpuif,
|
||||||
"axi4-lite-flat": axi4lite.AXI4LiteCpuifFlat,
|
"axi4-lite-flat": axi4lite.AXI4LiteCpuifFlat,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
from systemrdl.node import AddressableNode
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
from ...utils import clog2, get_indexed_path
|
from ...utils import get_indexed_path
|
||||||
from ..base_cpuif import BaseCpuif
|
from ..base_cpuif import BaseCpuif
|
||||||
from .apb4_interface import APB4FlatInterface
|
from .apb4_interface import APB4FlatInterface
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class APB4CpuifFlat(BaseCpuif):
|
|||||||
fanout[self.signal("PWRITE", node, "gi")] = (
|
fanout[self.signal("PWRITE", node, "gi")] = (
|
||||||
f"cpuif_wr_sel.{get_indexed_path(self.exp.ds.top_node, node, 'gi')}"
|
f"cpuif_wr_sel.{get_indexed_path(self.exp.ds.top_node, node, 'gi')}"
|
||||||
)
|
)
|
||||||
fanout[self.signal("PADDR", node, "gi")] = f"{self.signal('PADDR')}[{clog2(node.size) - 1}:0]"
|
fanout[self.signal("PADDR", node, "gi")] = self.signal("PADDR")
|
||||||
fanout[self.signal("PPROT", node, "gi")] = self.signal("PPROT")
|
fanout[self.signal("PPROT", node, "gi")] = self.signal("PPROT")
|
||||||
fanout[self.signal("PWDATA", node, "gi")] = "cpuif_wr_data"
|
fanout[self.signal("PWDATA", node, "gi")] = "cpuif_wr_data"
|
||||||
fanout[self.signal("PSTRB", node, "gi")] = "cpuif_wr_byte_en"
|
fanout[self.signal("PSTRB", node, "gi")] = "cpuif_wr_byte_en"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from systemrdl.node import AddressableNode
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
from ...utils import clog2
|
|
||||||
from ..interface import FlatInterface, SVInterface
|
from ..interface import FlatInterface, SVInterface
|
||||||
|
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ class APB4FlatInterface(FlatInterface):
|
|||||||
f"output logic {self.signal('PSEL', child)}",
|
f"output logic {self.signal('PSEL', child)}",
|
||||||
f"output logic {self.signal('PENABLE', child)}",
|
f"output logic {self.signal('PENABLE', child)}",
|
||||||
f"output logic {self.signal('PWRITE', child)}",
|
f"output logic {self.signal('PWRITE', child)}",
|
||||||
f"output logic [{clog2(child.size) - 1}:0] {self.signal('PADDR', 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 [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 - 1}:0] {self.signal('PWDATA', child)}",
|
||||||
f"output logic [{self.cpuif.data_width // 8 - 1}:0] {self.signal('PSTRB', child)}",
|
f"output logic [{self.cpuif.data_width // 8 - 1}:0] {self.signal('PSTRB', child)}",
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
assert_bad_data_width: assert($bits({{cpuif.signal("PWDATA")}}) == {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH)
|
assert_bad_data_width: assert($bits({{cpuif.signal("PWDATA")}}) == {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH)
|
||||||
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("PWDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH);
|
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("PWDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH);
|
||||||
end
|
end
|
||||||
`ifdef PEAKRDL_ASSERTIONS
|
|
||||||
assert_wr_sel: assert property (@(posedge {{cpuif.signal("PCLK")}}) {{cpuif.signal("PSEL")}} && {{cpuif.signal("PWRITE")}} |-> ##1 ({{cpuif.signal("PREADY")}} || {{cpuif.signal("PSLVERR")}}))
|
assert_wr_sel: assert property (@(posedge {{cpuif.signal("PCLK")}}) {{cpuif.signal("PSEL")}} && {{cpuif.signal("PWRITE")}} |-> ##1 ({{cpuif.signal("PREADY")}} || {{cpuif.signal("PSLVERR")}}))
|
||||||
else $error("APB4 Slave port SEL implies that cpuif_wr_sel must be one-hot encoded");
|
else $error("APB4 Slave port SEL implies that cpuif_wr_sel must be one-hot encoded");
|
||||||
`endif
|
|
||||||
`endif
|
`endif
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
$bits({{cpuif.signal("WDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH);
|
$bits({{cpuif.signal("WDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH);
|
||||||
end
|
end
|
||||||
|
|
||||||
`ifdef PEAKRDL_ASSERTIONS
|
|
||||||
// Simple handshake sanity (one-cycle implication; relax/adjust as needed)
|
// Simple handshake sanity (one-cycle implication; relax/adjust as needed)
|
||||||
assert_rd_resp_enc: assert property (@(posedge {{cpuif.signal("ACLK")}})
|
assert_rd_resp_enc: assert property (@(posedge {{cpuif.signal("ACLK")}})
|
||||||
{{cpuif.signal("RVALID")}} |-> (^{{cpuif.signal("RRESP")}} !== 1'bx))
|
{{cpuif.signal("RVALID")}} |-> (^{{cpuif.signal("RRESP")}} !== 1'bx))
|
||||||
@@ -24,7 +23,6 @@
|
|||||||
assert_wr_resp_enc: assert property (@(posedge {{cpuif.signal("ACLK")}})
|
assert_wr_resp_enc: assert property (@(posedge {{cpuif.signal("ACLK")}})
|
||||||
{{cpuif.signal("BVALID")}} |-> (^{{cpuif.signal("BRESP")}} !== 1'bx))
|
{{cpuif.signal("BVALID")}} |-> (^{{cpuif.signal("BRESP")}} !== 1'bx))
|
||||||
else $error("BRESP must be a legal AXI response when BVALID is high");
|
else $error("BRESP must be a legal AXI response when BVALID is high");
|
||||||
`endif
|
|
||||||
`endif
|
`endif
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
|
||||||
|
|||||||
@@ -64,20 +64,17 @@ class Interface(ABC):
|
|||||||
class SVInterface(Interface):
|
class SVInterface(Interface):
|
||||||
"""SystemVerilog interface-based signal handling."""
|
"""SystemVerilog interface-based signal handling."""
|
||||||
|
|
||||||
slave_modport_name = "slave"
|
|
||||||
master_modport_name = "master"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_interface(self) -> bool:
|
def is_interface(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_port_declaration(self, slave_name: str, master_prefix: str) -> str:
|
def get_port_declaration(self, slave_name: str, master_prefix: str) -> str:
|
||||||
"""Generate SystemVerilog interface port declarations."""
|
"""Generate SystemVerilog interface port declarations."""
|
||||||
slave_ports: list[str] = [f"{self.get_interface_type()}.{self.slave_modport_name} {slave_name}"]
|
slave_ports: list[str] = [f"{self.get_interface_type()}.slave {slave_name}"]
|
||||||
master_ports: list[str] = []
|
master_ports: list[str] = []
|
||||||
|
|
||||||
for child in self.cpuif.addressable_children:
|
for child in self.cpuif.addressable_children:
|
||||||
base = f"{self.get_interface_type()}.{self.master_modport_name} {master_prefix}{child.inst_name}"
|
base = f"{self.get_interface_type()}.master {master_prefix}{child.inst_name}"
|
||||||
|
|
||||||
# When unrolled, current_idx is set - append it to the name
|
# When unrolled, current_idx is set - append it to the name
|
||||||
if child.current_idx is not None:
|
if child.current_idx is not None:
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from .taxi_apb_cpuif import TaxiAPBCpuif
|
|
||||||
|
|
||||||
__all__ = ["TaxiAPBCpuif"]
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
from typing import TYPE_CHECKING, overload
|
|
||||||
|
|
||||||
from systemrdl.node import AddressableNode
|
|
||||||
|
|
||||||
from ...utils import get_indexed_path
|
|
||||||
from ..base_cpuif import BaseCpuif
|
|
||||||
from .taxi_apb_interface import TaxiAPBSVInterface
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from ...exporter import BusDecoderExporter
|
|
||||||
|
|
||||||
|
|
||||||
class TaxiAPBCpuif(BaseCpuif):
|
|
||||||
template_path = "taxi_apb_tmpl.sv"
|
|
||||||
|
|
||||||
def __init__(self, exp: "BusDecoderExporter") -> None:
|
|
||||||
super().__init__(exp)
|
|
||||||
self._interface = TaxiAPBSVInterface(self)
|
|
||||||
self._interface.master_modport_name = "mst"
|
|
||||||
self._interface.slave_modport_name = "slv"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_interface(self) -> bool:
|
|
||||||
return self._interface.is_interface
|
|
||||||
|
|
||||||
@property
|
|
||||||
def port_declaration(self) -> str:
|
|
||||||
"""Returns the port declaration for the APB4 interface."""
|
|
||||||
return self._interface.get_port_declaration("s_apb", "m_apb_")
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def signal(self, signal: str, node: None = None, indexer: None = None) -> str: ...
|
|
||||||
@overload
|
|
||||||
def signal(self, signal: str, node: AddressableNode, indexer: str) -> 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] = {}
|
|
||||||
fanout[self.signal("psel", node, "gi")] = (
|
|
||||||
f"cpuif_wr_sel.{get_indexed_path(self.exp.ds.top_node, node, 'gi')}|cpuif_rd_sel.{get_indexed_path(self.exp.ds.top_node, node, 'gi')}"
|
|
||||||
)
|
|
||||||
fanout[self.signal("penable", node, "gi")] = self.signal("penable")
|
|
||||||
fanout[self.signal("pwrite", node, "gi")] = (
|
|
||||||
f"cpuif_wr_sel.{get_indexed_path(self.exp.ds.top_node, node, 'gi')}"
|
|
||||||
)
|
|
||||||
fanout[self.signal("paddr", node, "gi")] = self.signal("paddr")
|
|
||||||
fanout[self.signal("pprot", node, "gi")] = self.signal("pprot")
|
|
||||||
fanout[self.signal("pwdata", node, "gi")] = "cpuif_wr_data"
|
|
||||||
fanout[self.signal("pstrb", node, "gi")] = "cpuif_wr_byte_en"
|
|
||||||
# no user?
|
|
||||||
|
|
||||||
return "\n".join(map(lambda kv: f"assign {kv[0]} = {kv[1]};", 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:
|
|
||||||
# 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")
|
|
||||||
|
|
||||||
# no user?
|
|
||||||
return "\n".join(map(lambda kv: f"{kv[0]} = {kv[1]};", fanin.items()))
|
|
||||||
|
|
||||||
def readback(self, node: AddressableNode | None = None) -> str:
|
|
||||||
fanin: dict[str, str] = {}
|
|
||||||
if node is None:
|
|
||||||
fanin["cpuif_rd_data"] = "'0"
|
|
||||||
else:
|
|
||||||
# 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;",
|
|
||||||
]
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
"""Taxi APB-specific interface implementations."""
|
|
||||||
|
|
||||||
from ..interface import SVInterface
|
|
||||||
|
|
||||||
|
|
||||||
class TaxiAPBSVInterface(SVInterface):
|
|
||||||
"""Taxi APB SystemVerilog interface."""
|
|
||||||
|
|
||||||
def get_interface_type(self) -> str:
|
|
||||||
return "taxi_apb_if"
|
|
||||||
|
|
||||||
def get_slave_name(self) -> str:
|
|
||||||
return "s_apb"
|
|
||||||
|
|
||||||
def get_master_prefix(self) -> str:
|
|
||||||
return "m_apb_"
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
{%- if cpuif.is_interface %}
|
|
||||||
`ifndef SYNTHESIS
|
|
||||||
initial begin
|
|
||||||
assert_bad_addr_width: assert($bits({{cpuif.signal("paddr")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH)
|
|
||||||
else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("paddr")}}), {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH);
|
|
||||||
assert_bad_data_width: assert($bits({{cpuif.signal("pwdata")}}) == {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH)
|
|
||||||
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("pwdata")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH);
|
|
||||||
end
|
|
||||||
`ifdef PEAKRDL_ASSERTIONS
|
|
||||||
assert_wr_sel: assert property (@(posedge {{cpuif.signal("PCLK")}}) {{cpuif.signal("psel")}} && {{cpuif.signal("pwrite")}} |-> ##1 ({{cpuif.signal("pready")}} || {{cpuif.signal("pslverr")}}))
|
|
||||||
else $error("APB4 Slave port SEL implies that cpuif_wr_sel must be one-hot encoded");
|
|
||||||
`endif
|
|
||||||
`endif
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
assign cpuif_req = {{cpuif.signal("psel")}};
|
|
||||||
assign cpuif_wr_en = {{cpuif.signal("pwrite")}};
|
|
||||||
assign cpuif_rd_en = !{{cpuif.signal("pwrite")}};
|
|
||||||
|
|
||||||
assign cpuif_wr_addr = {{cpuif.signal("paddr")}};
|
|
||||||
assign cpuif_rd_addr = {{cpuif.signal("paddr")}};
|
|
||||||
|
|
||||||
assign cpuif_wr_data = {{cpuif.signal("pwdata")}};
|
|
||||||
assign cpuif_wr_byte_en = {{cpuif.signal("pstrb")}};
|
|
||||||
|
|
||||||
assign {{cpuif.signal("prdata")}} = cpuif_rd_data;
|
|
||||||
assign {{cpuif.signal("pready")}} = cpuif_rd_ack;
|
|
||||||
assign {{cpuif.signal("pslverr")}} = cpuif_rd_err | cpuif_rd_sel.cpuif_err | cpuif_wr_sel.cpuif_err;
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
|
||||||
// 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
|
|
||||||
//--------------------------------------------------------------------------
|
|
||||||
{{fanin|walk(cpuif=cpuif)}}
|
|
||||||
@@ -9,7 +9,6 @@ from typing import Any, Iterable
|
|||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.triggers import Timer
|
from cocotb.triggers import Timer
|
||||||
|
|
||||||
from tests.cocotb_lib.handle_utils import SignalHandle, resolve_handle
|
|
||||||
|
|
||||||
class _Apb3SlaveShim:
|
class _Apb3SlaveShim:
|
||||||
"""Accessor for the APB3 slave signals on the DUT."""
|
"""Accessor for the APB3 slave signals on the DUT."""
|
||||||
@@ -34,7 +33,10 @@ def _load_config() -> dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
def _resolve(handle, indices: Iterable[int]):
|
def _resolve(handle, indices: Iterable[int]):
|
||||||
return resolve_handle(handle, indices)
|
ref = handle
|
||||||
|
for idx in indices:
|
||||||
|
ref = ref[idx]
|
||||||
|
return ref
|
||||||
|
|
||||||
|
|
||||||
def _set_value(handle, indices: Iterable[int], value: int) -> None:
|
def _set_value(handle, indices: Iterable[int], value: int) -> None:
|
||||||
@@ -52,16 +54,16 @@ def _build_master_table(dut, masters_cfg: list[dict[str, Any]]) -> dict[str, dic
|
|||||||
entry = {
|
entry = {
|
||||||
"indices": [tuple(idx) for idx in master["indices"]] or [tuple()],
|
"indices": [tuple(idx) for idx in master["indices"]] or [tuple()],
|
||||||
"outputs": {
|
"outputs": {
|
||||||
"PSEL": SignalHandle(dut, f"{prefix}_PSEL"),
|
"PSEL": getattr(dut, f"{prefix}_PSEL"),
|
||||||
"PENABLE": SignalHandle(dut, f"{prefix}_PENABLE"),
|
"PENABLE": getattr(dut, f"{prefix}_PENABLE"),
|
||||||
"PWRITE": SignalHandle(dut, f"{prefix}_PWRITE"),
|
"PWRITE": getattr(dut, f"{prefix}_PWRITE"),
|
||||||
"PADDR": SignalHandle(dut, f"{prefix}_PADDR"),
|
"PADDR": getattr(dut, f"{prefix}_PADDR"),
|
||||||
"PWDATA": SignalHandle(dut, f"{prefix}_PWDATA"),
|
"PWDATA": getattr(dut, f"{prefix}_PWDATA"),
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"PRDATA": SignalHandle(dut, f"{prefix}_PRDATA"),
|
"PRDATA": getattr(dut, f"{prefix}_PRDATA"),
|
||||||
"PREADY": SignalHandle(dut, f"{prefix}_PREADY"),
|
"PREADY": getattr(dut, f"{prefix}_PREADY"),
|
||||||
"PSLVERR": SignalHandle(dut, f"{prefix}_PSLVERR"),
|
"PSLVERR": getattr(dut, f"{prefix}_PSLVERR"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
table[master["inst_name"]] = entry
|
table[master["inst_name"]] = entry
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from typing import Any, Iterable
|
|||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.triggers import Timer
|
from cocotb.triggers import Timer
|
||||||
|
|
||||||
from tests.cocotb_lib.handle_utils import SignalHandle, resolve_handle
|
|
||||||
|
|
||||||
class _Apb4SlaveShim:
|
class _Apb4SlaveShim:
|
||||||
"""Lightweight accessor for the APB4 slave side of the DUT."""
|
"""Lightweight accessor for the APB4 slave side of the DUT."""
|
||||||
@@ -38,7 +37,10 @@ def _load_config() -> dict[str, Any]:
|
|||||||
|
|
||||||
def _resolve(handle, indices: Iterable[int]):
|
def _resolve(handle, indices: Iterable[int]):
|
||||||
"""Index into hierarchical cocotb handles."""
|
"""Index into hierarchical cocotb handles."""
|
||||||
return resolve_handle(handle, indices)
|
ref = handle
|
||||||
|
for idx in indices:
|
||||||
|
ref = ref[idx]
|
||||||
|
return ref
|
||||||
|
|
||||||
|
|
||||||
def _set_value(handle, indices: Iterable[int], value: int) -> None:
|
def _set_value(handle, indices: Iterable[int], value: int) -> None:
|
||||||
@@ -57,18 +59,18 @@ def _build_master_table(dut, masters_cfg: list[dict[str, Any]]) -> dict[str, dic
|
|||||||
"port_prefix": port_prefix,
|
"port_prefix": port_prefix,
|
||||||
"indices": [tuple(idx) for idx in master["indices"]] or [tuple()],
|
"indices": [tuple(idx) for idx in master["indices"]] or [tuple()],
|
||||||
"outputs": {
|
"outputs": {
|
||||||
"PSEL": SignalHandle(dut, f"{port_prefix}_PSEL"),
|
"PSEL": getattr(dut, f"{port_prefix}_PSEL"),
|
||||||
"PENABLE": SignalHandle(dut, f"{port_prefix}_PENABLE"),
|
"PENABLE": getattr(dut, f"{port_prefix}_PENABLE"),
|
||||||
"PWRITE": SignalHandle(dut, f"{port_prefix}_PWRITE"),
|
"PWRITE": getattr(dut, f"{port_prefix}_PWRITE"),
|
||||||
"PADDR": SignalHandle(dut, f"{port_prefix}_PADDR"),
|
"PADDR": getattr(dut, f"{port_prefix}_PADDR"),
|
||||||
"PPROT": SignalHandle(dut, f"{port_prefix}_PPROT"),
|
"PPROT": getattr(dut, f"{port_prefix}_PPROT"),
|
||||||
"PWDATA": SignalHandle(dut, f"{port_prefix}_PWDATA"),
|
"PWDATA": getattr(dut, f"{port_prefix}_PWDATA"),
|
||||||
"PSTRB": SignalHandle(dut, f"{port_prefix}_PSTRB"),
|
"PSTRB": getattr(dut, f"{port_prefix}_PSTRB"),
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"PRDATA": SignalHandle(dut, f"{port_prefix}_PRDATA"),
|
"PRDATA": getattr(dut, f"{port_prefix}_PRDATA"),
|
||||||
"PREADY": SignalHandle(dut, f"{port_prefix}_PREADY"),
|
"PREADY": getattr(dut, f"{port_prefix}_PREADY"),
|
||||||
"PSLVERR": SignalHandle(dut, f"{port_prefix}_PSLVERR"),
|
"PSLVERR": getattr(dut, f"{port_prefix}_PSLVERR"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
table[master["inst_name"]] = entry
|
table[master["inst_name"]] = entry
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from peakrdl_busdecoder.cpuif.apb4.apb4_cpuif_flat import APB4CpuifFlat
|
from peakrdl_busdecoder.cpuif.apb4.apb4_cpuif_flat import APB4CpuifFlat
|
||||||
@@ -43,38 +43,17 @@ def test_apb4_smoke(tmp_path: Path, rdl_file: str, top_name: str) -> None:
|
|||||||
|
|
||||||
runner = get_runner("verilator")
|
runner = get_runner("verilator")
|
||||||
sim_build = build_root / "sim_build"
|
sim_build = build_root / "sim_build"
|
||||||
|
|
||||||
try:
|
|
||||||
runner.build(
|
|
||||||
sources=sources,
|
|
||||||
hdl_toplevel=module_path.stem,
|
|
||||||
build_dir=sim_build,
|
|
||||||
log_file=str(build_root / "build.log"),
|
|
||||||
)
|
|
||||||
except SystemExit as e:
|
|
||||||
# Print build log on failure for easier debugging
|
|
||||||
log_path = build_root / "build.log"
|
|
||||||
if log_path.exists():
|
|
||||||
logging.error("\n\n=== Build Log ===\n")
|
|
||||||
logging.error(log_path.read_text())
|
|
||||||
logging.error("\n=== End Build Log ===\n")
|
|
||||||
if e.code != 0:
|
|
||||||
raise
|
|
||||||
|
|
||||||
try:
|
runner.build(
|
||||||
runner.test(
|
sources=sources,
|
||||||
hdl_toplevel=module_path.stem,
|
hdl_toplevel=module_path.stem,
|
||||||
test_module="tests.cocotb.apb4.smoke.test_register_access",
|
build_dir=sim_build,
|
||||||
build_dir=sim_build,
|
)
|
||||||
log_file=str(build_root / "simulation.log"),
|
|
||||||
extra_env={"RDL_TEST_CONFIG": json.dumps(config)},
|
runner.test(
|
||||||
)
|
hdl_toplevel=module_path.stem,
|
||||||
except SystemExit as e:
|
test_module="tests.cocotb.apb4.smoke.test_register_access",
|
||||||
# Print simulation log on failure for easier debugging
|
build_dir=sim_build,
|
||||||
log_path = build_root / "simulation.log"
|
log_file=str(build_root / "simulation.log"),
|
||||||
if log_path.exists():
|
extra_env={"RDL_TEST_CONFIG": json.dumps(config)},
|
||||||
logging.error("\n\n=== Simulation Log ===\n")
|
)
|
||||||
logging.error(log_path.read_text())
|
|
||||||
logging.error("\n=== End Simulation Log ===\n")
|
|
||||||
if e.code != 0:
|
|
||||||
raise
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from typing import Any, Iterable
|
|||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.triggers import Timer
|
from cocotb.triggers import Timer
|
||||||
|
|
||||||
from tests.cocotb_lib.handle_utils import SignalHandle, resolve_handle
|
|
||||||
|
|
||||||
class _AxilSlaveShim:
|
class _AxilSlaveShim:
|
||||||
"""Accessor for AXI4-Lite slave ports on the DUT."""
|
"""Accessor for AXI4-Lite slave ports on the DUT."""
|
||||||
@@ -45,7 +44,10 @@ def _load_config() -> dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
def _resolve(handle, indices: Iterable[int]):
|
def _resolve(handle, indices: Iterable[int]):
|
||||||
return resolve_handle(handle, indices)
|
ref = handle
|
||||||
|
for idx in indices:
|
||||||
|
ref = ref[idx]
|
||||||
|
return ref
|
||||||
|
|
||||||
|
|
||||||
def _set_value(handle, indices: Iterable[int], value: int) -> None:
|
def _set_value(handle, indices: Iterable[int], value: int) -> None:
|
||||||
@@ -63,25 +65,25 @@ def _build_master_table(dut, masters_cfg: list[dict[str, Any]]) -> dict[str, dic
|
|||||||
entry = {
|
entry = {
|
||||||
"indices": [tuple(idx) for idx in master["indices"]] or [tuple()],
|
"indices": [tuple(idx) for idx in master["indices"]] or [tuple()],
|
||||||
"outputs": {
|
"outputs": {
|
||||||
"AWVALID": SignalHandle(dut, f"{prefix}_AWVALID"),
|
"AWVALID": getattr(dut, f"{prefix}_AWVALID"),
|
||||||
"AWADDR": SignalHandle(dut, f"{prefix}_AWADDR"),
|
"AWADDR": getattr(dut, f"{prefix}_AWADDR"),
|
||||||
"AWPROT": SignalHandle(dut, f"{prefix}_AWPROT"),
|
"AWPROT": getattr(dut, f"{prefix}_AWPROT"),
|
||||||
"WVALID": SignalHandle(dut, f"{prefix}_WVALID"),
|
"WVALID": getattr(dut, f"{prefix}_WVALID"),
|
||||||
"WDATA": SignalHandle(dut, f"{prefix}_WDATA"),
|
"WDATA": getattr(dut, f"{prefix}_WDATA"),
|
||||||
"WSTRB": SignalHandle(dut, f"{prefix}_WSTRB"),
|
"WSTRB": getattr(dut, f"{prefix}_WSTRB"),
|
||||||
"ARVALID": SignalHandle(dut, f"{prefix}_ARVALID"),
|
"ARVALID": getattr(dut, f"{prefix}_ARVALID"),
|
||||||
"ARADDR": SignalHandle(dut, f"{prefix}_ARADDR"),
|
"ARADDR": getattr(dut, f"{prefix}_ARADDR"),
|
||||||
"ARPROT": SignalHandle(dut, f"{prefix}_ARPROT"),
|
"ARPROT": getattr(dut, f"{prefix}_ARPROT"),
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"AWREADY": SignalHandle(dut, f"{prefix}_AWREADY"),
|
"AWREADY": getattr(dut, f"{prefix}_AWREADY"),
|
||||||
"WREADY": SignalHandle(dut, f"{prefix}_WREADY"),
|
"WREADY": getattr(dut, f"{prefix}_WREADY"),
|
||||||
"BVALID": SignalHandle(dut, f"{prefix}_BVALID"),
|
"BVALID": getattr(dut, f"{prefix}_BVALID"),
|
||||||
"BRESP": SignalHandle(dut, f"{prefix}_BRESP"),
|
"BRESP": getattr(dut, f"{prefix}_BRESP"),
|
||||||
"ARREADY": SignalHandle(dut, f"{prefix}_ARREADY"),
|
"ARREADY": getattr(dut, f"{prefix}_ARREADY"),
|
||||||
"RVALID": SignalHandle(dut, f"{prefix}_RVALID"),
|
"RVALID": getattr(dut, f"{prefix}_RVALID"),
|
||||||
"RDATA": SignalHandle(dut, f"{prefix}_RDATA"),
|
"RDATA": getattr(dut, f"{prefix}_RDATA"),
|
||||||
"RRESP": SignalHandle(dut, f"{prefix}_RRESP"),
|
"RRESP": getattr(dut, f"{prefix}_RRESP"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
table[master["inst_name"]] = entry
|
table[master["inst_name"]] = entry
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
"""Utilities for resolving cocotb signal handles across simulators."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any, Iterable
|
|
||||||
|
|
||||||
|
|
||||||
class SignalHandle:
|
|
||||||
"""
|
|
||||||
Wrapper that resolves array elements even when the simulator does not expose
|
|
||||||
unpacked arrays through ``handle[idx]``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, dut, name: str) -> None:
|
|
||||||
self._dut = dut
|
|
||||||
self._name = name
|
|
||||||
self._base = getattr(dut, name, None)
|
|
||||||
self._cache: dict[tuple[int, ...], Any] = {}
|
|
||||||
|
|
||||||
def resolve(self, indices: tuple[int, ...]):
|
|
||||||
if not indices:
|
|
||||||
return self._base if self._base is not None else self._lookup(tuple())
|
|
||||||
|
|
||||||
if indices not in self._cache:
|
|
||||||
self._cache[indices] = self._direct_or_lookup(indices)
|
|
||||||
return self._cache[indices]
|
|
||||||
|
|
||||||
def _direct_or_lookup(self, indices: tuple[int, ...]):
|
|
||||||
if self._base is not None:
|
|
||||||
ref = self._base
|
|
||||||
try:
|
|
||||||
for idx in indices:
|
|
||||||
ref = ref[idx]
|
|
||||||
return ref
|
|
||||||
except (IndexError, TypeError, AttributeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return self._lookup(indices)
|
|
||||||
|
|
||||||
def _lookup(self, indices: tuple[int, ...]):
|
|
||||||
suffix = "".join(f"[{idx}]" for idx in indices)
|
|
||||||
path = f"{self._name}{suffix}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
return getattr(self._dut, path)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
errors: list[Exception] = []
|
|
||||||
for extended in (False, True):
|
|
||||||
try:
|
|
||||||
return self._dut._id(path, extended=extended)
|
|
||||||
except (AttributeError, ValueError) as exc:
|
|
||||||
errors.append(exc)
|
|
||||||
|
|
||||||
raise AttributeError(f"Unable to resolve handle '{path}' via dut._id") from errors[-1]
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_handle(handle, indices: Iterable[int]):
|
|
||||||
"""Resolve either a regular cocotb handle or a ``SignalHandle`` wrapper."""
|
|
||||||
index_tuple = tuple(indices)
|
|
||||||
|
|
||||||
if isinstance(handle, SignalHandle):
|
|
||||||
return handle.resolve(index_tuple)
|
|
||||||
|
|
||||||
ref = handle
|
|
||||||
for idx in index_tuple:
|
|
||||||
ref = ref[idx]
|
|
||||||
return ref
|
|
||||||
Reference in New Issue
Block a user