# reg-generator A FuseSoC generator wrapping [OpenTitan's `regtool.py`](https://opentitan.org/book/util/reggen/index.html) to automate the generation of control-register infrastructure for hardware peripherals. It is used in [X-HEEP](https://github.com/esl-epfl/x-heep) to replace per-peripheral generation shell scripts with a single, reusable FuseSoC generator. ## Quick start Add `xheep:util:reg-generator` as a dependency of your peripheral core and declare a `generate` block that invokes the `regtool` generator: ```yaml CAPI=2: name: "vendor:ip:my_peripheral" filesets: files_rtl: depend: - xheep:util:reg-generator files: - rtl/my_peripheral.sv file_type: systemVerilogSource generate: my-peripheral-regs: generator: regtool parameters: name: my_peripheral # register block basename config: data/my_peripheral.hjson # HJSON register description (possibly a .tpl) rtl_dir: rtl # output directory for RTL files sw_path: ../../../sw/drivers/my_peripheral/my_peripheral_regs.h doc_path: ../../../sw/drivers/my_peripheral/my_peripheral_regs.md targets: default: filesets: - files_rtl generate: - my-peripheral-regs ``` All paths are relative to `files_root`, which FuseSoC sets to the directory containing your `.core` file. ## Parameters | Parameter | Required | Description | |--------------------|----------|-------------| | `name` | yes | Name of the register block (used to derive output filenames). | | `config` | yes | Path to the HJSON register description for `regtool.py`, relative to `files_root`. If a `.hjson.tpl` Mako template is passed, the key-value parameters provided to the generator (see `` below) are used to render the output first. If you are rendering parameters with X-HEEP's [MCU-GEN](https://github.com/x-heep/xheep_gen) or equivalent tool, provide the path to the _rendered_ `.hjson`. | | `rtl_dir` | no | Output directory for the generated RTL files, relative to `files_root`. When omitted, no RTL is generated. | | `sw_path` | no | Output file path for the generated C register-defines header, relative to `files_root`. When omitted, no C header is generated. | | `doc_path` | no | Output file path for the generated Markdown register documentation, relative to `files_root`. When omitted, no documentation is generated. | | `regtool_path` | no | Path to `regtool.py`, relative to `files_root`. Overrides the built-in default. The `REGTOOL` environment variable takes even higher priority and can be used in CI without modifying `.core` files. | | `structs_sw_path` | no | Output file path for the C structs header, relative to `files_root`. When set, triggers generation of the C structs header using the script resolved by `PERIPH_STRUCTS_GEN`, `structs_gen_path`, or the built-in default. | | `structs_gen_path` | no | Path to the peripheral C-structs generator script, relative to `files_root`. Overrides the built-in default. The `PERIPH_STRUCTS_GEN` environment variable takes even higher priority. Only used when `structs_sw_path` is set. | | `structs_tpl_path` | no | Path to the template for the C structs header, relative to `files_root`. Defaults to `/.tpl`. Only used when `structs_sw_path` is set. | | `ver_core` | no | VLNV of the core whose semantic version is exposed as `version_hex` in Mako templates (format `vendor:library:name`). Defaults to the top-level core of the current FuseSoC target. | | `` | no | Any additional key–value pairs are forwarded as template variables when rendering a Mako `.tpl` config file. | ## What the generator produces Given a peripheral named `my_peripheral`, the generator produces: | Output | Description | |--------|-------------| | `/my_peripheral_reg_pkg.sv` | SystemVerilog package with register field types and constants. | | `/my_peripheral_reg_top.sv` | SystemVerilog register-file top module (TL-UL interface). | | `sw_path` | C header (`#define`-based) with register offsets and field masks, compatible with the OpenTitan software register model. | | `doc_path` | Markdown documentation of all registers and fields, generated by `regtool`. | | `/my_peripheral_structs.h` | *(optional)* C structs header providing typed bit-field access to each register. Requires `structs_sw_path` and a valid structs generator (see `PERIPH_STRUCTS_GEN`). | | `my_peripheral-.core` | FuseSoC `.core` file listing the two generated RTL files as `systemVerilogSource` dependencies, so downstream cores can depend on the register block by VLNV. | ## How FuseSoC invokes the generator When FuseSoC encounters a `generate` block, it: 1. Collects all cores in the dependency graph and serializes the context into a YAML input file (e.g. `_input.yml`) placed in a per-instance subdirectory under `build/`. 2. Invokes the generator script, e.g., `reg-generator.py` with that YAML file as its only argument. 3. Expects the generator to write a `.core` file named after the generator instance in its working directory. FuseSoC reads this file to discover the generated RTL as new dependencies for the rest of the build. The input YAML for `reg-generator` contains, among other things: ```yaml files_root: /absolute/path/to/the/directory/containing/your.core # base for all relative paths in 'parameters' parameters: # key-value pairs from your 'generate' block name: my_peripheral config: data/my_peripheral.hjson rtl_dir: rtl sw_path: ../../../sw/drivers/my_peripheral/my_peripheral_regs.h doc_path: ../../../sw/drivers/my_peripheral/my_peripheral_regs.md vlnv: vendor:ip:my_peripheral-my-peripheral-regs:0 # VLNV of this generator instance toplevel: vendor:systems:my_soc:1.0.0 # top-level core of the current FuseSoC run cores: # full dependency graph — all cores with their resolved paths xheep:util:reg-generator: core_root: /absolute/path/to/reg-generator ... ... ``` `files_root` is the key field to understand when writing paths in `parameters`: every relative path is resolved against it, which is always the directory containing the invoking `.core` file, not the generator's own directory or the build directory. The `.core` file written by the generator back to FuseSoC lists the two generated RTL files with absolute paths, for example: ```yaml CAPI=2: name: vendor:ip:my_peripheral-my-peripheral-regs:0 filesets: rtl: files: - /absolute/path/to/rtl/my_peripheral_reg_pkg.sv - /absolute/path/to/rtl/my_peripheral_reg_top.sv file_type: systemVerilogSource targets: default: filesets: [rtl] ``` ## Template-based HJSON When `config` has a `.tpl` extension, the generator first renders it as a [Mako](https://www.makotemplates.org/) template before passing it to `regtool`. This is useful for parametric register maps whose field count or addresses depend on design-time constants. All `parameters` key-value pairs (e.g, ``) entries are forwarded to the template as variables, plus a pre-computed `version_hex` (a `0x00MMmmPP` hex string derived from the target core's semantic version). > **Note:** In those cases where a dedicated template renderer is used before calling FuseSoC, as it happens with [MCU-GEN](https://github.com/x-heep/xheep_gen) in X-HEEP, the rendered `.hjson` file must be passed as the `config` parameter. ## Caching The generator skips all subprocess calls (regtool and the optional structs generator) when it can determine the outputs are already up to date. Generation is skipped if and only if **both** conditions hold: 1. The SHA-256 hash of the (rendered) HJSON input file matches the hash stored from the previous run. 2. Every expected output file exists on disk. If either condition fails — the input changed, or any output was deleted — the full generation runs and the cache is updated on success. A failed run never updates the cache, so the next invocation always retries. The cache is stored as a plain-text file named `.{name}_reg_gen.cache` in `files_root` (the directory containing the invoking `.core` file). The cache key is: - **Template HJSON** (`.tpl`): SHA-256 of the template file content and all rendering kwargs (parameters + `version_hex`). Any change to the template source or its parameters invalidates the cache. - **Plain HJSON**: SHA-256 of the HJSON file content. The cache file lives in the source tree alongside the IP and persists across `make clean` (which only removes the `build/` directory). It can be invalidated explicitly by deleting it, or implicitly by modifying the HJSON input (or template and its parameters) or removing any expected output file. ## Path resolution ### `regtool.py` The generator locates `regtool.py` in this order: 1. **`REGTOOL` environment variable** — highest priority; useful in CI or when integrating reg-generator into a project that vendors its own copy of OpenTitan. 2. **`regtool_path` parameter** in the `.core` file — relative to `files_root`. 3. **Built-in default** — the plain name `regtool.py`, resolved via `PATH`. ### `periph_structs_gen.py` When `structs_sw_path` is set, The generator locates `periph_structs_gen.py` in this order: 1. **`PERIPH_STRUCTS_GEN` environment variable** — highest priority; useful in CI or when integrating reg-generator into a project that vendors its own copy of X-HEEP. 2. **`structs_gen_path` parameter** in the `.core` file — relative to `files_root`. 3. **Built-in default** — the plain name `periph_structs_gen.py`, resolved via `PATH`.