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: 1. ``Signal`` (bit field inside a register) 2. ``Register`` (16-bit register containing non-overlapping signals) 3. ``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 (``*_i`` for RO, ``*_o`` for RW/CONST) - update pulses for RW registers (``_update_o``) Main files and roles -------------------- - ``RegisterTree/signal.py``: ``Signal`` definition and field metadata - ``RegisterTree/register.py``: ``Register`` container and validation rules - ``RegisterTree/tree.py``: full tree checks + VHDL generation - ``RegisterTree/jsonTools.py``: JSON import/export helpers - ``RegisterTree/utils.py``: helpers (name normalization, duplicates, ceil-div, command formatting) - ``RegisterTree/vhdl_module.py``: generic VHDL source builder - ``register_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: .. code-block:: json { "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 name - ``address`` (string hex like ``"0x1A"``) unless auto-address mode is used - ``type`` (string): one of ``"rw"``, ``"ro"``, ``"const"`` - ``contents`` (list): list of signal objects Example: .. code-block:: json { "address": "0x02", "name": "pcs_status", "type": "ro", "contents": {} } Register ``type`` meaning ~~~~~~~~~~~~~~~~~~~~~~~~~ - ``rw``: Read/Write register - ``ro``: Read-Only register - ``const``: Constant register (compile-time fixed value) Type-specific rules: - ``const``: every signal must provide ``default`` - ``ro``: signals must **not** provide ``default`` - ``rw``: ``default`` is optional Signal object: required keywords ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Each signal in ``contents`` must define: - ``name`` (string): display name (documentation-facing) - ``description`` (string): meaning of the field - ``signal_name`` (string): technical/internal identifier (used for VHDL naming) - ``start_bit`` (integer): LSB index - ``end_bit`` (integer): MSB index Optional signal keywords: - ``default`` (integer): reset/default value (required for ``const``) - ``values`` (object): symbolic descriptions mapped to numeric codes - any additional custom metadata fields (preserved in dictionary export) Signal example: .. code-block:: json { "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_bit`` - Signals 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: json { "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: .. code-block:: bash 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 ~~~~~~~~~~~~~~ 1. Create a ``Tree`` instance. 2. Create ``Register`` objects with a ``RegisterType`` (``RW``, ``RO``, ``CONST``). 3. Add ``Signal`` objects to each register (with optional defaults/values). 4. Add registers to the tree with a group name. 5. Generate VHDL (or Markdown / JSON export) from the tree. Example ~~~~~~~ .. code-block:: python 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: .. code-block:: python md = tree.getMarkdown("My Register Tree") - JSON re-serialization: .. code-block:: python from RegisterTree.jsonTools import dumpTreeToJSON json_text = dumpTreeToJSON(tree) Practical guidance ------------------ - Keep ``signal_name`` stable 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 ``const`` registers, verify every field has a valid ``default`` fitting its bit width. - For ``ro`` registers, never include ``default`` in JSON.