Writing a custom code generator

The register code generator API in hdl-registers is carefully designed to be very extensible. Writing and using your own custom code generator is very simple.

The key is to inherit the RegisterCodeGenerator class, as seen in the example below.

Example

The example below is a simple “code” generator that will dump the names of each register to a text file.

Example of a custom code generator.
 1# Standard libraries
 2import sys
 3from pathlib import Path
 4
 5# First party libraries
 6from hdl_registers.generator.register_code_generator import RegisterCodeGenerator
 7from hdl_registers.register_list import RegisterList
 8from hdl_registers.register_modes import REGISTER_MODES
 9
10
11class TxtRegisterListGenerator(RegisterCodeGenerator):
12    """
13    Custom code generator that generates a .txt file with all register names.
14    """
15
16    SHORT_DESCRIPTION = "text list"
17    COMMENT_START = "#"
18
19    @property
20    def output_file(self) -> Path:
21        """
22        Result will be placed in this file.
23        """
24        return self.output_folder / f"{self.name}_registers.txt"
25
26    def get_code(self, **kwargs) -> str:
27        """
28        Generate a textual list of all registers and register arrays.
29        """
30        txt = f'{self.header}\nAvailable registers in the "{self.name}" register list:\n\n'
31
32        for register, register_array in self.iterate_registers():
33            if register_array:
34                # This register is part of a register array.
35                txt += f"{register_array.name}[{register_array.length}].{register.name}\n"
36            else:
37                # This register is a plain register.
38                txt += f"{register.name}\n"
39
40        return txt
41
42
43def main(output_folder: Path):
44    """
45    Set up some registers and generate text file with our custom generator.
46    """
47    register_list = RegisterList(name="caesar")
48
49    register_list.append_register(name="config", mode=REGISTER_MODES["r_w"], description="")
50    register_list.append_register(name="status", mode=REGISTER_MODES["r"], description="")
51    register_list.append_register(name="command", mode=REGISTER_MODES["wpulse"], description="")
52
53    register_array = register_list.append_register_array(name="channels", length=4, description="")
54    register_array.append_register(name="read_address", mode=REGISTER_MODES["r_w"], description="")
55    register_array.append_register(name="write_address", mode=REGISTER_MODES["r_w"], description="")
56
57    TxtRegisterListGenerator(register_list=register_list, output_folder=output_folder).create()
58
59
60if __name__ == "__main__":
61    main(output_folder=Path(sys.argv[1]))

The COMMENT_START, SHORT_DESCRIPTION and output_file class properties are abstract in the super class, and MUST hence be implemented in the subclass. The same is true, naturally, for the get_code method which is where all the code generation happens.

The generator class inheriting from RegisterCodeGenerator means that it has the public methods RegisterCodeGenerator.create(), RegisterCodeGenerator.create_if_needed() and RegisterCodeGenerator.header() just like the standard hdl-registers generators.

It also, by extension, inherits from the RegisterCodeGeneratorHelpers class. Meaning it has access to some useful functions for constructing the generated code. Used in the example above is RegisterCodeGeneratorHelpers.iterate_registers().

Running the example script above will yield the following result file:

Result text file from custom code generator.
# This file is automatically generated by hdl-registers version 6.1.1-dev.
# Code generator TxtRegisterListGenerator version 0.0.1.
# Generated 2024-12-01 20:51 at commit 2ca830a4377d1e32.
# Register hash 2ca70b5e858aa6d1094aa79b15e90c1550c2158a.

Available registers in the "caesar" register list:

config
status
command
channels[4].read_address
channels[4].write_address

Note that the header uses the COMMENT_START character to start each line. This header is used by RegisterCodeGenerator.create_if_needed() to determine if a re-generate of the file is necessary.

Running the script above yields the following shell printout:

Shell output when running custom code generator.
Creating text list file: generated/sphinx_rst/register_code/extensions/extensions_custom_generator/caesar_registers.txt

Note that the “text list” part of the printout is the SHORT_DESCRIPTION property of our generator class above. The file name is of course also given by output_file of our generator class.

Contributions

If you write a high-quality code generator you are more than welcome to contribute it to the hdl-registers project. Please open an issue or a pull request.

Performance

Since code generation is usually quite slow, there is a mechanism in place for speeding up the process and giving a better user experience. For code generation that is time-critical, such as generating a register package before a user simulation run, it is recommended to call the RegisterCodeGenerator.create_if_needed() method. This method will only re-create the artifact if some register definition has changed or a tool version is bumped. Hence using this method as opposed to RegisterCodeGenerator.create() saves a lot of time, especially in a large project.

Code templating engine

The code generation in a custom code generator can be done either using plain Python, as in the example above, or using an engine such as Jinja. This is completely up to the user. The custom code generator is pure Python and you are free to be as fancy/creative as you want.

Custom arguments

The RegisterCodeGenerator.create(), RegisterCodeGenerator.create_if_needed() and RegisterCodeGenerator.get_code() all have a **kwargs argument available. If you want to send further information/arguments to the code generator for some exotic feature, this makes it possible.