Skip to main content

Export SystemRDL to PyBind11 modules for Python-based hardware testing

Project description

PeakRDL-pybind11

License Documentation Status

PeakRDL-pybind11 generates Python bindings from SystemRDL register descriptions so a Python-fluent person — bring-up engineer, test author, FW dev, lab automator, debugger — can drive a chip from a REPL or a Jupyter notebook with dir(soc) and a docstring as the only documentation needed.

Status: the API documented in this README is the design target. The current release ships the foundation (exporter, generated bindings, pluggable masters, the context-manager pattern) and is converging on the surface described here. The full design lives in docs/IDEAL_API_SKETCH.md. Sections marked aspirational are roadmap items; please file an issue if you hit a gap.

Mental model

A generated module is a typed tree of nodes that mirrors the RDL hierarchy. Every node knows its path, its absolute address, its kind (AddrMap, RegFile, Reg, Field, Mem, InterruptGroup, Alias, Signal), its metadata, and which master serves its address range.

SoC
├── AddrMap          (peripherals)
│   ├── RegFile      (uart[0..3])
│   │   ├── Reg      (control)
│   │   │   └── Field (enable, baudrate, parity)
│   │   ├── Reg      (status)            ← side-effecting reads (rclr) are flagged
│   │   └── InterruptGroup               ← auto-detected from intr_state/enable/test
│   └── Mem          (sram)              ← buffer-protocol, ndarray, slice
└── Master           (the bus, pluggable)

Nodes are descriptors, not values. They produce values by reading. Values produced by reads are typed wrappers (RegisterValue, FieldValue) that behave like ints, decode enums, and round-trip cleanly back into writes.

There are exactly four primitive operations:

reg.read()              # → RegisterValue   (1 bus read)
reg.write(value)        # raw write         (1 bus write, no read)
reg.modify(**fields)    # RMW               (1 read + 1 write)
reg.poke(value)         # explicit "I know what I'm doing", same as .write()

field.read() reads the parent register and slices. field.write(v) is shorthand for reg.modify(field=v) — one read, one write. The names are chosen so the bus cost is the obvious reading.

For non-clearing inspection of registers with read-side effects, use peek(). Side-effect-only operations are first-class: clear(), set(), pulse(), acknowledge().

Features

  • Hierarchy & discovery — typed tree mirroring RDL. Navigate by attribute, by index, or by path string. soc.find(0x4000_1004), soc.walk(kind=Reg), soc.find_by_name("control", glob=True).
  • Atomic multi-field updatesmodify(**fields) is the canonical RMW; kwargs map to fields by name and are type-checked against generated .pyi stubs.
  • Interrupts as a first-class groupINTR_STATE/INTR_ENABLE/INTR_TEST trios are auto-detected and exposed as InterruptGroup nodes with wait(), clear(), pending(), enable_all(), ISR-friendly iteration, and an asyncio variant.
  • Memory as numpy-aware MemViewmem[10:20] is a live view; .copy() / .read() snapshots into a one-burst ndarray. Buffer-protocol and __array__ interop everywhere.
  • Snapshots & diffsoc.snapshot() for whole-SoC golden-state captures, JSON/pickle round-trip, snap2.diff(snap1) for change tables, soc.restore(snap) for replay.
  • Jupyter widgets_repr_html_ on every node renders a field table with side-effect badges, color-coded access modes, and click-to-expand source location. node.watch(period=0.1) produces a refreshing live monitor.
  • Pluggable masters with tracing & replay — Mock, OpenOCD, SSH, simulator, and a RecordingMaster/ReplayMaster pair. Transactions are reified objects you can script.
  • Type-safe stubs — generated .pyi declares enum types, field literals, and Unpack[TypedDict] for modify(...). IDE completion is the spec; mypy/pyright catch unknown field names and out-of-range values.
  • Typed valuesRegisterValue/FieldValue are immutable, hashable, picklable, JSON-serializable. Mutation goes through .replace(**fields) and never through assignment.
  • Side effects loud, not silentrepr shows ⚠ rclr, ↻ singlepulse, ✱ sticky, ⚡ volatile. with soc.no_side_effects(): blocks destructive reads. --strict-fields=false opt-out exists for C-driver porting and emits a DeprecationWarning on every loose assignment.

Quick example

The first three vignettes from docs/IDEAL_API_SKETCH.md §24:

Bring-up

from MyChip import MyChip
from MyChip.uart import BaudRate
from peakrdl_pybind11.masters import OpenOCDMaster

soc = MyChip.create(master=OpenOCDMaster())
print(soc.uart.control)
soc.uart.control.modify(enable=1, baudrate=BaudRate.BAUD_115200)
soc.uart.data.write(ord('A'))
soc.uart.interrupts.tx_done.wait(timeout=1.0)
soc.uart.interrupts.tx_done.clear()

Test

def test_uart_loopback(soc):
    with soc.uart.control as r:
        r.enable    = 1
        r.loopback  = 1
        r.baudrate  = BaudRate.BAUD_115200

    soc.uart.data.write(0xA5)
    soc.uart.status.rx_ready.wait_for(True, timeout=0.1)
    assert soc.uart.data.read() == 0xA5

    soc.uart.interrupts.clear_all()

Bulk memory init

import numpy as np

soc.ram[:] = 0
soc.ram[0:64] = np.arange(64, dtype=np.uint32) * 4
checksum = np.bitwise_xor.reduce(np.asarray(soc.ram)[0:1024])

modify(**fields) is the canonical multi-field update; the with soc.uart.control as r: block is the secondary form for staging many writes (and for cases where one staged value is read back to compute another).

Installation

pip install peakrdl-pybind11

Usage

Command line

peakrdl pybind11 input.rdl -o output_dir --soc-name MySoC --top top_addrmap --gen-pyi

CLI options

  • --soc-name — name of the generated SoC module (default: derived from input file)
  • --top — top-level address map node to export (default: top-level node)
  • --gen-pyi — generate .pyi stub files for type hints (enabled by default)
  • --split-bindings COUNT — split bindings into multiple files for parallel compilation when register count exceeds COUNT. Default: 100. Set to 0 to disable. Ignored when --split-by-hierarchy is used.
  • --split-by-hierarchy — split bindings by addrmap/regfile hierarchy instead of by register count. Keeps related registers together; recommended for designs with clear hierarchical structure.

For large register maps, compilation can be slow. PeakRDL-pybind11 emits CMake projects that compile split files in parallel and uses -O1 even for debug builds to reduce template-heavy build times — see COMPILATION_OPTIMIZATIONS.md for the full breakdown.

Python API

from peakrdl_pybind11 import Pybind11Exporter
from systemrdl import RDLCompiler

# Compile SystemRDL
rdl = RDLCompiler()
rdl.compile_file("input.rdl")
root = rdl.elaborate()

# Export to PyBind11
exporter = Pybind11Exporter()
exporter.export(root, "output_dir", soc_name="MySoC")

# For large designs, enable hierarchical binding splitting (recommended)
exporter.export(root, "output_dir", soc_name="MySoC", split_by_hierarchy=True)

Using generated modules

import MySoC
from MySoC.uart import BaudRate, Parity
from peakrdl_pybind11.masters import MockMaster

soc = MySoC.create(master=MockMaster())

# Read a register — typed value with decoded fields in repr
v = soc.uart.control.read()
print(v)

# Atomic multi-field update — one read, one write, type-checked from .pyi
soc.uart.control.modify(enable=1, baudrate=BaudRate.BAUD_115200, parity=Parity.NONE)

# Wait for an interrupt
soc.uart.interrupts.tx_done.wait(timeout=1.0)
soc.uart.interrupts.tx_done.clear()

# Stage many writes with the secondary context-manager form
with soc.uart.control as r:
    r.enable    = 1
    r.baudrate  = BaudRate.BAUD_115200
    if r.parity.read() == Parity.NONE:
        r.parity = Parity.EVEN
# 1 read + 1 write hits the bus on exit

# Snapshot & diff
snap1 = soc.snapshot()
do_thing()
snap2 = soc.snapshot()
print(snap2.diff(snap1))

Requirements

  • Python >= 3.10
  • systemrdl-compiler >= 1.30.1
  • NumPy — hard runtime dependency (bursts, bulk reads, buffer protocol, snapshot frames)
  • Jinja2
  • CMake >= 3.15 (for building generated modules)
  • C++11 compatible compiler (for building generated modules)
  • pybind11 (runtime dependency for generated code)

Benchmarks

Performance benchmarks measure export and build times across realistic register maps:

# Run fast export benchmarks
python benchmarks/run_benchmarks.py fast

# Run all benchmarks
pytest benchmarks/ --benchmark-only

# See all benchmark options
python benchmarks/run_benchmarks.py

See benchmarks/README.md for detailed documentation.

Documentation

Full documentation is available at ReadTheDocs. The full design sketch lives in docs/IDEAL_API_SKETCH.md.

To build the documentation locally:

pip install -e .[docs]
cd docs
make html

The built documentation will be in docs/_build/html/.

License

This project is licensed under the GPL-3.0 License — see the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

peakrdl_pybind11-0.7.3.tar.gz (282.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

peakrdl_pybind11-0.7.3-py3-none-any.whl (291.3 kB view details)

Uploaded Python 3

File details

Details for the file peakrdl_pybind11-0.7.3.tar.gz.

File metadata

  • Download URL: peakrdl_pybind11-0.7.3.tar.gz
  • Upload date:
  • Size: 282.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for peakrdl_pybind11-0.7.3.tar.gz
Algorithm Hash digest
SHA256 e7fde1c1ba6a55a273da294bfe1a0520b62df988089d9964e071a0c5a566b104
MD5 61536d94db8f2b875100e8f787cc8cc9
BLAKE2b-256 cd63b77642ad52ebe184e41fca86fce12f6f1b2248bd3a7a54858ab5a7ac72ae

See more details on using hashes here.

Provenance

The following attestation bundles were made for peakrdl_pybind11-0.7.3.tar.gz:

Publisher: release.yml on arnavsacheti/PeakRDL-pybind11

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file peakrdl_pybind11-0.7.3-py3-none-any.whl.

File metadata

File hashes

Hashes for peakrdl_pybind11-0.7.3-py3-none-any.whl
Algorithm Hash digest
SHA256 3e903392b4d510c8ec574e066b3a6016b319f28f6ee4d5c42fa19877a934b660
MD5 a567f968199c2dc094ed6b3dbbc4942e
BLAKE2b-256 224a435a3879db3f00c34e775ce79b5a017198547a02c0d6ce305eb56880695a

See more details on using hashes here.

Provenance

The following attestation bundles were made for peakrdl_pybind11-0.7.3-py3-none-any.whl:

Publisher: release.yml on arnavsacheti/PeakRDL-pybind11

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page