Generating a Register Tree

The register_tree_gen.py script in the HotRIO firmware tools is used to generate a VHDL register tree from a JSON configuration file. This allows microcontroller interfaces to access registers in a clean and organized hierarchical structure.

Input Format

The input to the script is a JSON file describing the register tree. Each register entry includes address, name, type (read-only, read-write, constant), and its contents. Here’s an example format:

json:

{
    "generic_regs": [
        {
            "address": "0x00",
            "name": "fw_version",
            "type": "const",
            "contents": [
                {
                    "name": "Bitstream version",
                    "description": "Contains the identification code of the bitstream",
                    "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": "Enables Dual 0 of the PCS",
                    "signal_name": "cfg_tx_d0_en",
                    "start_bit": 0,
                    "end_bit": 0,
                    "default": 1
                },
                {
                    "name" : "TX Dual 1 enable",
                    "description": "Enables Dual 1 of the PCS",
                    "signal_name": "cfg_tx_d1_en",
                    "start_bit": 1,
                    "end_bit": 1,
                    "default": 0
                }
            ]
        },
        {
            "address": "0x02",
            "name": "pcs_status",
            "type": "ro",
            "contents": [
                {
                    "name": "TX Dual 0 ready",
                    "description": "High when Dual 0 of the PCS is ready to transmit",
                    "signal_name": "cfg_tx_d0_rdy",
                    "start_bit": 0,
                    "end_bit": 0,
                },
                {
                    "name" : "TX Dual 1 ready",
                    "description": "High when Dual 1 of the PCS is ready to transmit",
                    "signal_name": "cfg_tx_d1_rdy",
                    "start_bit": 1,
                    "end_bit": 1,
                }
            ]
        }
    ]
}

Rules:

  • Categories, such as “generic_regs” or “pcs_regs”, are only used as thematic groups for documentation. They do not affect the generation of the register tree.

  • Address, name, type and contents are mandatory fields for each register. The address can be expressed as hex or decimal string.

  • Available types are “rw”, “ro” and “const”. Signals must have a “default” in a “const” register, and they must not have it in a “ro” register. In a “rw” register the default is optional.

  • A register may contain any number of signals, as long as their bit ranges are all within the 15 downto 0 range and they are not overlapping.

  • Each signal must have name, description, signal_name, start_bit and end_bit. The start_bit must be less than or equal to the end_bit, even if the range is interpreted in the opposite bit order.

  • Defaults can be specified as decimal integers (without quotes) or as binary, octal or hexadecimal strings (with quotes), using the following format: “0b” prefix for binary, “0o” prefix for octal and “0x” prefix for hexadecimal.

  • The name field of registers and the signal_name field of signals will be modified by the script to be usable in VHDL code. Whitespace is replaced by underscores, and any non-alphanumeric character (except for underscores) is removed.

Usage

Run the tool in hotrio-software using:

python3 Firmware/Scripts/register_tree_gen.py -h

This prints:

usage: register_tree_gen.py [-h] [-l {CRITICAL,ERROR,WARNING,INFO,DEBUG}] [-n NAME] [-o OUTPUT_PATH] -c CONFIG_PATH [-v VSG_CFG_PATH] [-b BRANCHING_FACTOR] [-a ADDR_CMD] [-r READ_CMD] [-w WRITE_CMD] [-u]

Generate register map from JSON file

options:
-h, --help            show this help message and exit
-l {CRITICAL,ERROR,WARNING,INFO,DEBUG}, --loglevel {CRITICAL,ERROR,WARNING,INFO,DEBUG}
                        Log level, one of: CRITICAL, ERROR, WARNING, INFO, DEBUG
-n NAME, --name NAME  Name of the vhdl module
-o OUTPUT_PATH, --output_path OUTPUT_PATH
                        Output file path (relative to cwd)
-c CONFIG_PATH, --config_path CONFIG_PATH
                        Configuration file path (relative to cwd)
-v VSG_CFG_PATH, --vsg_cfg_path VSG_CFG_PATH
                        Path to a VSG configuration file. If this argument is not specified, VSG will not be used to fix output code formatting
-b BRANCHING_FACTOR, --branching_factor BRANCHING_FACTOR
                        Branching factor
-a ADDR_CMD, --addr_cmd ADDR_CMD
                        8-bit value of the set_address command
-r READ_CMD, --read_cmd READ_CMD
                        8-bit value of the read command
-w WRITE_CMD, --write_cmd WRITE_CMD
                        8-bit value of the write command
-u, --update_addresses
                        Update addresses in the configuration file to make them consecutive

Example

Assume your current path is inside the root folder of a vertical project.

You can generate the register tree VHDL module using:

python3 Libs/hotrio-software/Firmware/Scripts/register_tree_gen.py -n registers -o Source/modules/registers/hdl/registers.vhd -c Source/registers.json -v Libs/hotrio-software/Firmware/Scripts/QA/vsg.cfg -u

This command:

  • Names the VHDL module as registers

  • Generates the output VHDL and testbench files

  • Rewrites all register addresses to make them consecutive (-u)

  • Formats the code for readability using the provided configuration (-v)

Notes

  • Usage of -u is suggested unless the addresses need to be kept as specified. When -u is not used, the resulting register map may be larger than needed.

  • The register tree module and the connected SPI slave interface require a reset pulse before starting to operate correctly after power-up. On Lattice FPGAs, if the reset is transferred through the primary clock network, it may not be properly received by the modules until the first falling edge of the reset signal.