9 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
3191ab3848 Remove Python 3.14 from test matrix - incompatible with cocotb
Python 3.14 is still in development (alpha/beta) and causing cocotb simulation tests to fail. The cocotb library and its dependencies are not yet fully compatible with Python 3.14. Tests work correctly on Python 3.10-3.13, which aligns with the project's requirements.

Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com>
2025-11-10 15:44:20 +00:00
copilot-swe-agent[bot]
227df5ad05 Initial plan 2025-11-10 15:39:35 +00:00
Arnav Sacheti
e95069019a remove redundant check 2025-11-09 22:36:14 -08:00
Arnav Sacheti
b66681f46a version bump + ignore runner warning 2025-11-09 22:30:15 -08:00
Arnav Sacheti
b942c2e2d2 apb3 working 2025-11-09 22:25:28 -08:00
Arnav Sacheti
6bb4f08ca4 apb4 working 2025-11-09 22:23:43 -08:00
Arnav Sacheti
9bf1c3e944 update sv int 2025-11-09 21:26:03 -08:00
Arnav Sacheti
fdac38133c reorg 2025-11-09 21:26:03 -08:00
Arnav Sacheti
bbbeab85c5 wip 2025-11-09 21:26:03 -08:00
9 changed files with 68 additions and 214 deletions

View File

@@ -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

View File

@@ -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"
]
}
}
}

View File

@@ -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

View File

@@ -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" }

View File

@@ -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

View File

@@ -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

View File

@@ -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
@@ -44,24 +44,12 @@ 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( runner.build(
sources=sources, sources=sources,
hdl_toplevel=module_path.stem, hdl_toplevel=module_path.stem,
build_dir=sim_build, 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.test( runner.test(
hdl_toplevel=module_path.stem, hdl_toplevel=module_path.stem,
test_module="tests.cocotb.apb4.smoke.test_register_access", test_module="tests.cocotb.apb4.smoke.test_register_access",
@@ -69,12 +57,3 @@ def test_apb4_smoke(tmp_path: Path, rdl_file: str, top_name: str) -> None:
log_file=str(build_root / "simulation.log"), log_file=str(build_root / "simulation.log"),
extra_env={"RDL_TEST_CONFIG": json.dumps(config)}, extra_env={"RDL_TEST_CONFIG": json.dumps(config)},
) )
except SystemExit as e:
# Print simulation log on failure for easier debugging
log_path = build_root / "simulation.log"
if log_path.exists():
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

View File

@@ -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

View File

@@ -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