PythonRegisterTree
Overview
The RegisterTree module is a Python library used to define FPGA register maps and generate artifacts from them, mainly:
VHDL register-tree modules (for SPI-accessible register interfaces)
Markdown register documentation
JSON export from in-memory register models
It is centered on a strict 16-bit register model and a JSON-driven workflow.
What it does
The module models three layers:
Signal(bit field inside a register)Register(16-bit register containing non-overlapping signals)Tree(full register map grouped by logical categories)
From this model, it can generate:
a VHDL module with SPI command handling and register instances
signal-level top ports (
*_ifor RO,*_ofor RW/CONST)update pulses for RW registers (
<register_name>_update_o)
Main files and roles
RegisterTree/signal.py:Signaldefinition and field metadataRegisterTree/register.py:Registercontainer and validation rulesRegisterTree/tree.py: full tree checks + VHDL generationRegisterTree/jsonTools.py: JSON import/export helpersRegisterTree/utils.py: helpers (name normalization, duplicates, ceil-div, command formatting)RegisterTree/vhdl_module.py: generic VHDL source builderregister_tree_gen.py: CLI entrypoint to generate VHDL from JSON
JSON format
Top-level structure
The JSON root must be an object (dictionary). Each key is a register group name, and each value is a list of register objects.
Example:
{
"generic_regs": [],
"control_regs": [],
"status_regs": []
}
Group names are mainly for organization/documentation. Generation logic uses register content, not group semantics.
Register object: required keywords
Each register entry must define:
name(string): human-readable register nameaddress(string hex like"0x1A") unless auto-address mode is usedtype(string): one of"rw","ro","const"contents(list): list of signal objects
Example:
{
"address": "0x02",
"name": "pcs_status",
"type": "ro",
"contents": {}
}
Register type meaning
rw: Read/Write registerro: Read-Only registerconst: Constant register (compile-time fixed value)
Type-specific rules:
const: every signal must providedefaultro: signals must not providedefaultrw:defaultis optional
Signal object: required keywords
Each signal in contents must define:
name(string): display name (documentation-facing)description(string): meaning of the fieldsignal_name(string): technical/internal identifier (used for VHDL naming)start_bit(integer): LSB indexend_bit(integer): MSB index
Optional signal keywords:
default(integer): reset/default value (required forconst)values(object): symbolic descriptions mapped to numeric codesany additional custom metadata fields (preserved in dictionary export)
Signal example:
{
"name": "TX Dual 0 enable",
"description": "Enables Dual 0 of the PCS",
"signal_name": "cfg_tx_d0_en",
"start_bit": 0,
"end_bit": 0,
"default": 1
}
Validation constraints enforced
Register address uniqueness in a tree
Register name uniqueness (after normalization)
Signal-name uniqueness across tree (intended behavior)
Signal bit ranges must satisfy: -
0 <= start_bit <= 15-0 <= end_bit <= 15-start_bit <= end_bitSignals in same register must not overlap bit ranges
Names are normalized for VHDL safety (lowercase, spaces to underscores, special chars removed)
Complete minimal JSON example
{
"generic_regs": [
{
"address": "0x00",
"name": "fw_version",
"type": "const",
"contents": [
{
"name": "Bitstream version",
"description": "Contains firmware ID",
"signal_name": "cfg_fw_version",
"start_bit": 0,
"end_bit": 15,
"default": 20225
}
]
}
],
"pcs_regs": [
{
"address": "0x01",
"name": "pcs_controls",
"type": "rw",
"contents": [
{
"name": "TX Dual 0 enable",
"description": "Enable TX path",
"signal_name": "cfg_tx_d0_en",
"start_bit": 0,
"end_bit": 0,
"default": 1
}
]
},
{
"address": "0x02",
"name": "pcs_status",
"type": "ro",
"contents": [
{
"name": "TX ready",
"description": "High when transmitter is ready",
"signal_name": "cfg_tx_ready",
"start_bit": 0,
"end_bit": 0
}
]
}
]
}
How to generate VHDL (CLI)
Use:
python3 Firmware/Scripts/register_tree_gen.py \
-c Source/registers.json \
-n registers \
-o Source/modules/registers/hdl/registers.vhd \
-b 8 -a 32 -r 64 -w 128
Important CLI options:
-c/--config_path: input JSON (required)-n/--name: generated VHDL module name-o/--output_path: output VHDL path-u/--update_addresses: assign consecutive addresses in-memory while parsing-v/--vsg_cfg_path: run VSG formatter after generation
Generating a Register Tree directly from Python
You can bypass JSON completely and build the register tree in code using
Tree, Register, and Signal objects.
Basic workflow
Create a
Treeinstance.Create
Registerobjects with aRegisterType(RW,RO,CONST).Add
Signalobjects to each register (with optional defaults/values).Add registers to the tree with a group name.
Generate VHDL (or Markdown / JSON export) from the tree.
Example
from RegisterTree.tree import Tree
from RegisterTree.register import Register, RegisterType
from RegisterTree.signal import Signal
from RegisterTree.jsonTools import dumpTreeToJSON
# 1) Create tree
tree = Tree("example_tree")
# 2) CONST register
reg_fw = Register("fw_version", 0x00, RegisterType.CONST)
sig_fw = Signal(
signalName="cfg_fw_version",
name="Bitstream version",
description="Contains firmware ID",
start_bit=0,
end_bit=15
)
sig_fw.setDefault(20225) # required for CONST fields
reg_fw.addSignal(sig_fw)
tree.addRegister(reg_fw, "generic_regs")
# 3) RW register
reg_ctrl = Register("pcs_controls", 0x01, RegisterType.RW)
sig_en = Signal(
signalName="cfg_tx_d0_en",
name="TX Dual 0 enable",
description="Enable TX path",
start_bit=0,
end_bit=0
)
sig_en.setDefault(1) # optional for RW
reg_ctrl.addSignal(sig_en)
tree.addRegister(reg_ctrl, "control_regs")
# 4) RO register
reg_status = Register("pcs_status", 0x02, RegisterType.RO)
sig_ready = Signal(
signalName="cfg_tx_ready",
name="TX ready",
description="High when transmitter is ready",
start_bit=0,
end_bit=0
)
reg_status.addSignal(sig_ready) # RO fields should not have default
tree.addRegister(reg_status, "status_regs")
# 5) Generate outputs
vhdl = tree.generateTreeModule(
treeModuleName="registers",
branching_factor=8,
addr_cmd=32,
read_cmd=64,
write_cmd=128
)
markdown = tree.getMarkdown("Registers")
json_text = dumpTreeToJSON(tree)
# Save if needed
with open("registers.vhd", "w") as f:
f.write(vhdl)
Important notes
The same validation rules as JSON mode apply (bit ranges, overlap, uniqueness, type rules).
Group names (second argument of
tree.addRegister(...)) are used for organization/documentation.You can attach custom metadata with
setAddtionalInfo(...)on registers/signals; it is preserved in dictionary/JSON exports.
Other useful outputs
Markdown documentation:
md = tree.getMarkdown("My Register Tree")
JSON re-serialization:
from RegisterTree.jsonTools import dumpTreeToJSON json_text = dumpTreeToJSON(tree)
Practical guidance
Keep
signal_namestable over time (it affects generated port names).Use grouped top-level keys to keep large maps readable.
Prefer contiguous addresses unless sparse maps are intentional.
For
constregisters, verify every field has a validdefaultfitting its bit width.For
roregisters, never includedefaultin JSON.