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.8.4.tar.gz (331.1 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.8.4-py3-none-any.whl (322.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: peakrdl_pybind11-0.8.4.tar.gz
  • Upload date:
  • Size: 331.1 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.8.4.tar.gz
Algorithm Hash digest
SHA256 0c6b61c90142f5bfb1c2933cbcc030f2464a8ed87b57654a8e6eb814439e380c
MD5 cb04af6d623be2a75d9a900caac79833
BLAKE2b-256 13a6299cfc329256444dc2c2926de89a93c7f68ba1c651807c152efc2af788ac

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for peakrdl_pybind11-0.8.4-py3-none-any.whl
Algorithm Hash digest
SHA256 99d6133e79e3d4ff78b4cf0bb4ccc380eb1590c680d5db84b5d8602e3056c5e0
MD5 1d8b9535c405f6c0aa1b41130f31ecff
BLAKE2b-256 44f507212344ae4161f66ef6cfb1b9804ea12a0d428d8513daf94526efbc6b69

See more details on using hashes here.

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