reg-generator

A FuseSoC generator wrapping OpenTitan’s regtool.py to automate the generation of control-register infrastructure for hardware peripherals. It is used in 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:

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 <others> below) are used to render the output first. If you are rendering parameters with X-HEEP’s MCU-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 <structs_gen_dir>/<script_name_without_gen>.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.

<others>

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

<rtl_dir>/my_peripheral_reg_pkg.sv

SystemVerilog package with register field types and constants.

<rtl_dir>/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.

<sw_dir>/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-<inst>.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. <inst-name>_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:

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:

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 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, <others>) 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 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.