C++ code generator

A complete C++ class can be generated with methods to read/write the registers or fields.

  • CppInterfaceGenerator creates an abstract interface header that can be used for mocking in a unit test environment. Contains method declarations, register attributes, and register constant values.

  • CppHeaderGenerator creates a class header which inherits the abstract class.

  • CppImplementationGenerator creates a class implementation with setters and getters.

C++ code is generated by running the Python code below. Note that it will parse and generate artifacts from the TOML file used in the TOML Format example.

Python code that parses the example TOML file and generates the C++ code we need.
 1# Standard libraries
 2import sys
 3from pathlib import Path
 4
 5# First party libraries
 6from hdl_registers.generator.cpp.header import CppHeaderGenerator
 7from hdl_registers.generator.cpp.implementation import CppImplementationGenerator
 8from hdl_registers.generator.cpp.interface import CppInterfaceGenerator
 9from hdl_registers.parser.toml import from_toml
10
11THIS_DIR = Path(__file__).parent
12
13
14def main(output_folder: Path):
15    """
16    Create register C++ artifacts from the TOML example file.
17    """
18    register_list = from_toml(
19        name="example",
20        toml_file=THIS_DIR.parent.parent / "user_guide" / "toml" / "toml_format.toml",
21    )
22
23    CppInterfaceGenerator(
24        register_list=register_list, output_folder=output_folder / "include"
25    ).create()
26
27    CppHeaderGenerator(
28        register_list=register_list, output_folder=output_folder / "include"
29    ).create()
30
31    CppImplementationGenerator(register_list=register_list, output_folder=output_folder).create()
32
33
34if __name__ == "__main__":
35    main(output_folder=Path(sys.argv[1]))

Getters

It can be noted, most clearly in the Interface header below, that there are three ways to read a register field:

  1. The method that reads the whole register, e.g. get_configuration().

  2. The method that reads the register and then slices out the field value, e.g. get_configuration_enable().

  3. The method that slices out the field value given a previously read register value, e.g. get_configuration_enable_from_value(register_value).

Method (2) is the most convenient in most cases. However if we want to read out more than one field from a register it would be very inefficient to read the register value more than once over the register bus, which would be the result of calling (2) multiple times. Instead we can call (1) once and then (3) multiple times to get our field values.

Setters

Conversely there are three ways to write a register field:

  1. The method that writes the whole register, e.g. set_configuration().

  2. The method that reads the register, updates the value of the field, and then writes the register back, e.g. set_configuration_enable().

  3. The method that updates the value of the field given a previously read register value, and returns an updated register value, e.g. set_configuration_enable_from_value(register_value).

Method (2) is the most convenient in most cases. However if we want to update more than one field of a register it would be very inefficient to read and write the register more than once over the register bus, which would be the result of calling (2) multiple times. Instead we can call a register getter once, e.g. get_configuration(), and then (3) multiple times to get our updated register value. This value is then written over the register bus using (1).

Exceptions

The discussion about setters above is valid for “read write” mode registers, which is arguably the most common type. However there are three register modes where the previously written register value can not be read back over the bus and then modified: “write only”, “write pulse”, and “read, write pulse”. The field setters for registers of this mode will write all bits outside of the current field as zero. This can for example be seen in the setter set_channels_configuration_enable() in the generated code below.

Interface header

Below is the resulting abstract interface header code, generated from the TOML Format example. Note that all register constants as well as field attributes are included here.

Example interface header
  1// This file is automatically generated by hdl-registers version 5.1.4-dev.
  2// Code generator CppInterfaceGenerator version 1.0.0.
  3// Generated 2024-04-27 20:52 from file toml_format.toml at commit 2c446088490c1e41.
  4// Register hash 0d636d16ec9bdadff4d5e144aaacd9416830c91d.
  5
  6#pragma once
  7
  8#include <cassert>
  9#include <cstdint>
 10#include <cstdlib>
 11
 12namespace fpga_regs
 13{
 14
 15  // Attributes for the 'enable' field in the 'config' register.
 16  namespace example::config::enable
 17  {
 18    static const auto width = 1;
 19    static const auto default_value = 0b1;
 20  }
 21  // Attributes for the 'direction' field in the 'config' register.
 22  namespace example::config::direction
 23  {
 24    static const auto width = 2;
 25    enum Enumeration
 26    {
 27      data_in = 0,
 28      high_z = 1,
 29      data_out = 2,
 30    };
 31    static const auto default_value = Enumeration::high_z;
 32  }
 33
 34  // Attributes for the 'enable' field in the 'config' register within the 'channels' register array.
 35  namespace example::channels::config::enable
 36  {
 37    static const auto width = 1;
 38    static const auto default_value = 0b0;
 39  }
 40  // Attributes for the 'tuser' field in the 'config' register within the 'channels' register array.
 41  namespace example::channels::config::tuser
 42  {
 43    static const auto width = 8;
 44    static const auto default_value = 0b00000000;
 45  }
 46
 47  // Attributes for the "channels" register array.
 48  namespace example::channels
 49  {
 50    // Number of times the registers of the array are repeated.
 51    static const auto array_length = 4;
 52  };
 53
 54  class IExample
 55  {
 56  public:
 57    // Register constant.
 58    static const int axi_data_width = 64;
 59    // Register constant.
 60    static constexpr double clock_rate_hz = 156250000.0;
 61
 62    // Number of registers within this register map.
 63    static const size_t num_registers = 10uL;
 64
 65    virtual ~IExample() {}
 66
 67    // -------------------------------------------------------------------------
 68    // Methods for the 'config' register. Mode 'Read, Write'.
 69
 70    // Getter that will read the whole register's value over the register bus.
 71    virtual uint32_t get_config() const = 0;
 72
 73    // Setter that will write the whole register's value over the register bus.
 74    virtual void set_config(
 75      uint32_t register_value
 76    ) const = 0;
 77
 78    // Getter for the 'enable' field in the 'config' register,
 79    // which will read register value over the register bus.
 80    virtual uint32_t get_config_enable() const = 0;
 81    // Getter for the 'enable' field in the 'config' register,
 82    // given a register value.
 83    virtual uint32_t get_config_enable_from_value(
 84      uint32_t register_value
 85    ) const = 0;
 86    // Setter for the 'enable' field in the 'config' register,
 87    // which will read-modify-write over the register bus.
 88    virtual void set_config_enable(
 89      uint32_t field_value
 90    ) const = 0;
 91    // Setter for the 'enable' field in the 'config' register,
 92    // given a register value, which will return an updated value.
 93    virtual uint32_t set_config_enable_from_value(
 94      uint32_t register_value,
 95      uint32_t field_value
 96    ) const = 0;
 97
 98    // Getter for the 'direction' field in the 'config' register,
 99    // which will read register value over the register bus.
100    virtual example::config::direction::Enumeration get_config_direction() const = 0;
101    // Getter for the 'direction' field in the 'config' register,
102    // given a register value.
103    virtual example::config::direction::Enumeration get_config_direction_from_value(
104      uint32_t register_value
105    ) const = 0;
106    // Setter for the 'direction' field in the 'config' register,
107    // which will read-modify-write over the register bus.
108    virtual void set_config_direction(
109      example::config::direction::Enumeration field_value
110    ) const = 0;
111    // Setter for the 'direction' field in the 'config' register,
112    // given a register value, which will return an updated value.
113    virtual uint32_t set_config_direction_from_value(
114      uint32_t register_value,
115      example::config::direction::Enumeration field_value
116    ) const = 0;
117
118    // -------------------------------------------------------------------------
119    // Methods for the 'status' register. Mode 'Read'.
120
121    // Getter that will read the whole register's value over the register bus.
122    virtual uint32_t get_status() const = 0;
123
124    // -------------------------------------------------------------------------
125    // Methods for the 'read_address' register within the 'channels' register array. Mode 'Read, Write'.
126
127    // Getter that will read the whole register's value over the register bus.
128    virtual uint32_t get_channels_read_address(
129      size_t array_index
130    ) const = 0;
131
132    // Setter that will write the whole register's value over the register bus.
133    virtual void set_channels_read_address(
134      size_t array_index,
135      uint32_t register_value
136    ) const = 0;
137
138    // -------------------------------------------------------------------------
139    // Methods for the 'config' register within the 'channels' register array. Mode 'Write'.
140
141    // Setter that will write the whole register's value over the register bus.
142    virtual void set_channels_config(
143      size_t array_index,
144      uint32_t register_value
145    ) const = 0;
146
147    // Setter for the 'enable' field in the 'config' register within the 'channels' register array,
148    // which will set the field to the given value, and everything else to default.
149    virtual void set_channels_config_enable(
150      size_t array_index,
151      uint32_t field_value
152    ) const = 0;
153    // Setter for the 'enable' field in the 'config' register within the 'channels' register array,
154    // given a register value, which will return an updated value.
155    virtual uint32_t set_channels_config_enable_from_value(
156      uint32_t register_value,
157      uint32_t field_value
158    ) const = 0;
159
160    // Setter for the 'tuser' field in the 'config' register within the 'channels' register array,
161    // which will set the field to the given value, and everything else to default.
162    virtual void set_channels_config_tuser(
163      size_t array_index,
164      uint32_t field_value
165    ) const = 0;
166    // Setter for the 'tuser' field in the 'config' register within the 'channels' register array,
167    // given a register value, which will return an updated value.
168    virtual uint32_t set_channels_config_tuser_from_value(
169      uint32_t register_value,
170      uint32_t field_value
171    ) const = 0;
172
173  };
174
175} /* namespace fpga_regs */

Class header

Below is the generated class header:

Example class header
 1// This file is automatically generated by hdl-registers version 5.1.4-dev.
 2// Code generator CppHeaderGenerator version 1.0.0.
 3// Generated 2024-04-27 20:52 from file toml_format.toml at commit 2c446088490c1e41.
 4// Register hash 0d636d16ec9bdadff4d5e144aaacd9416830c91d.
 5
 6#pragma once
 7
 8#include "i_example.h"
 9
10namespace fpga_regs
11{
12
13  class Example : public IExample
14  {
15  private:
16    volatile uint32_t *m_registers;
17
18  public:
19    Example(volatile uint8_t *base_address);
20
21    virtual ~Example() {}
22
23    // -------------------------------------------------------------------------
24    // Methods for the 'config' register.
25    // See interface header for documentation.
26    virtual uint32_t get_config() const override;
27    virtual uint32_t get_config_enable() const override;
28    virtual uint32_t get_config_enable_from_value(
29      uint32_t register_value
30    ) const override;
31    virtual example::config::direction::Enumeration get_config_direction() const override;
32    virtual example::config::direction::Enumeration get_config_direction_from_value(
33      uint32_t register_value
34    ) const override;
35    virtual void set_config(
36      uint32_t register_value
37    ) const override;
38    virtual void set_config_enable(
39      uint32_t field_value
40    ) const override;
41    virtual uint32_t set_config_enable_from_value(
42      uint32_t register_value,
43      uint32_t field_value
44    ) const override;
45    virtual void set_config_direction(
46      example::config::direction::Enumeration field_value
47    ) const override;
48    virtual uint32_t set_config_direction_from_value(
49      uint32_t register_value,
50      example::config::direction::Enumeration field_value
51    ) const override;
52
53    // -------------------------------------------------------------------------
54    // Methods for the 'status' register.
55    // See interface header for documentation.
56    virtual uint32_t get_status() const override;
57
58    // -------------------------------------------------------------------------
59    // Methods for the 'read_address' register within the 'channels' register array.
60    // See interface header for documentation.
61    virtual uint32_t get_channels_read_address(
62      size_t array_index
63    ) const override;
64    virtual void set_channels_read_address(
65      size_t array_index,
66      uint32_t register_value
67    ) const override;
68
69    // -------------------------------------------------------------------------
70    // Methods for the 'config' register within the 'channels' register array.
71    // See interface header for documentation.
72    virtual void set_channels_config(
73      size_t array_index,
74      uint32_t register_value
75    ) const override;
76    virtual void set_channels_config_enable(
77      size_t array_index,
78      uint32_t field_value
79    ) const override;
80    virtual uint32_t set_channels_config_enable_from_value(
81      uint32_t register_value,
82      uint32_t field_value
83    ) const override;
84    virtual void set_channels_config_tuser(
85      size_t array_index,
86      uint32_t field_value
87    ) const override;
88    virtual uint32_t set_channels_config_tuser_from_value(
89      uint32_t register_value,
90      uint32_t field_value
91    ) const override;
92  };
93} /* namespace fpga_regs */

Implementation

Below is the generated class implementation:

Example class implementation
  1// This file is automatically generated by hdl-registers version 5.1.4-dev.
  2// Code generator CppImplementationGenerator version 1.0.0.
  3// Generated 2024-04-27 20:52 from file toml_format.toml at commit 2c446088490c1e41.
  4// Register hash 0d636d16ec9bdadff4d5e144aaacd9416830c91d.
  5
  6#include "include/example.h"
  7
  8namespace fpga_regs
  9{
 10
 11  Example::Example(volatile uint8_t *base_address)
 12      : m_registers(reinterpret_cast<volatile uint32_t *>(base_address))
 13  {
 14    // Empty
 15  }
 16
 17  // ---------------------------------------------------------------------------
 18  // Methods for the 'config' register.
 19  // See interface header for documentation.
 20
 21  uint32_t Example::get_config() const
 22  {
 23    const size_t index = 0;
 24    const uint32_t result = m_registers[index];
 25
 26    return result;
 27  }
 28
 29  uint32_t Example::get_config_enable() const
 30  {
 31    const uint32_t register_value = get_config();
 32    const uint32_t field_value = get_config_enable_from_value(register_value);
 33
 34    return field_value;
 35  }
 36
 37  uint32_t Example::get_config_enable_from_value(
 38    uint32_t register_value
 39  ) const
 40  {
 41    const uint32_t shift = 0uL;
 42    const uint32_t mask_at_base = 0b1uL;
 43    const uint32_t mask_shifted = mask_at_base << shift;
 44
 45    const uint32_t result_masked = register_value & mask_shifted;
 46    const uint32_t result_shifted = result_masked >> shift;
 47
 48    uint32_t field_value;
 49
 50    // No casting needed.
 51    field_value = result_shifted;
 52
 53    return field_value;
 54  }
 55
 56  example::config::direction::Enumeration Example::get_config_direction() const
 57  {
 58    const uint32_t register_value = get_config();
 59    const example::config::direction::Enumeration field_value = get_config_direction_from_value(register_value);
 60
 61    return field_value;
 62  }
 63
 64  example::config::direction::Enumeration Example::get_config_direction_from_value(
 65    uint32_t register_value
 66  ) const
 67  {
 68    const uint32_t shift = 1uL;
 69    const uint32_t mask_at_base = 0b11uL;
 70    const uint32_t mask_shifted = mask_at_base << shift;
 71
 72    const uint32_t result_masked = register_value & mask_shifted;
 73    const uint32_t result_shifted = result_masked >> shift;
 74
 75    example::config::direction::Enumeration field_value;
 76
 77    // "Cast" to the enum type.
 78    field_value = example::config::direction::Enumeration(result_shifted);
 79
 80    return field_value;
 81  }
 82
 83  void Example::set_config(
 84    uint32_t register_value
 85  ) const
 86  {
 87    const size_t index = 0;
 88    m_registers[index] = register_value;
 89  }
 90
 91  void Example::set_config_enable(
 92    uint32_t field_value
 93  ) const
 94  {
 95    // Get the current value of other fields by reading register on the bus.
 96    const uint32_t current_register_value = get_config();
 97    const uint32_t result_register_value = set_config_enable_from_value(current_register_value, field_value);
 98    set_config(result_register_value);
 99  }
100
101  uint32_t Example::set_config_enable_from_value(
102    uint32_t register_value,
103    uint32_t field_value
104  ) const  {
105    const uint32_t shift = 0uL;
106    const uint32_t mask_at_base = 0b1uL;
107    const uint32_t mask_shifted = mask_at_base << shift;
108
109    const uint32_t field_value_masked = field_value & mask_at_base;
110    const uint32_t field_value_masked_and_shifted = field_value_masked << shift;
111
112    const uint32_t mask_shifted_inverse = ~mask_shifted;
113    const uint32_t register_value_masked = register_value & mask_shifted_inverse;
114
115    const uint32_t result_register_value = register_value_masked | field_value_masked_and_shifted;
116
117    return result_register_value;
118  }
119
120  void Example::set_config_direction(
121    example::config::direction::Enumeration field_value
122  ) const
123  {
124    // Get the current value of other fields by reading register on the bus.
125    const uint32_t current_register_value = get_config();
126    const uint32_t result_register_value = set_config_direction_from_value(current_register_value, field_value);
127    set_config(result_register_value);
128  }
129
130  uint32_t Example::set_config_direction_from_value(
131    uint32_t register_value,
132    example::config::direction::Enumeration field_value
133  ) const  {
134    const uint32_t shift = 1uL;
135    const uint32_t mask_at_base = 0b11uL;
136    const uint32_t mask_shifted = mask_at_base << shift;
137
138    const uint32_t field_value_masked = field_value & mask_at_base;
139    const uint32_t field_value_masked_and_shifted = field_value_masked << shift;
140
141    const uint32_t mask_shifted_inverse = ~mask_shifted;
142    const uint32_t register_value_masked = register_value & mask_shifted_inverse;
143
144    const uint32_t result_register_value = register_value_masked | field_value_masked_and_shifted;
145
146    return result_register_value;
147  }
148
149  // ---------------------------------------------------------------------------
150  // Methods for the 'status' register.
151  // See interface header for documentation.
152
153  uint32_t Example::get_status() const
154  {
155    const size_t index = 1;
156    const uint32_t result = m_registers[index];
157
158    return result;
159  }
160
161  // ---------------------------------------------------------------------------
162  // Methods for the 'read_address' register within the 'channels' register array.
163  // See interface header for documentation.
164
165  uint32_t Example::get_channels_read_address(
166    size_t array_index
167  ) const
168  {
169    assert(array_index < example::channels::array_length);
170    const size_t index = 2 + array_index * 2 + 0;
171    const uint32_t result = m_registers[index];
172
173    return result;
174  }
175
176  void Example::set_channels_read_address(
177    size_t array_index,
178    uint32_t register_value
179  ) const
180  {
181    assert(array_index < example::channels::array_length);
182    const size_t index = 2 + array_index * 2 + 0;
183    m_registers[index] = register_value;
184  }
185
186  // ---------------------------------------------------------------------------
187  // Methods for the 'config' register within the 'channels' register array.
188  // See interface header for documentation.
189
190  void Example::set_channels_config(
191    size_t array_index,
192    uint32_t register_value
193  ) const
194  {
195    assert(array_index < example::channels::array_length);
196    const size_t index = 2 + array_index * 2 + 1;
197    m_registers[index] = register_value;
198  }
199
200  void Example::set_channels_config_enable(
201    size_t array_index,
202    uint32_t field_value
203  ) const
204  {
205    // Set everything except for the field to default when writing the value.
206    const uint32_t current_register_value = 0;
207    const uint32_t result_register_value = set_channels_config_enable_from_value(current_register_value, field_value);
208    set_channels_config(array_index, result_register_value);
209  }
210
211  uint32_t Example::set_channels_config_enable_from_value(
212    uint32_t register_value,
213    uint32_t field_value
214  ) const  {
215    const uint32_t shift = 0uL;
216    const uint32_t mask_at_base = 0b1uL;
217    const uint32_t mask_shifted = mask_at_base << shift;
218
219    const uint32_t field_value_masked = field_value & mask_at_base;
220    const uint32_t field_value_masked_and_shifted = field_value_masked << shift;
221
222    const uint32_t mask_shifted_inverse = ~mask_shifted;
223    const uint32_t register_value_masked = register_value & mask_shifted_inverse;
224
225    const uint32_t result_register_value = register_value_masked | field_value_masked_and_shifted;
226
227    return result_register_value;
228  }
229
230  void Example::set_channels_config_tuser(
231    size_t array_index,
232    uint32_t field_value
233  ) const
234  {
235    // Set everything except for the field to default when writing the value.
236    const uint32_t current_register_value = 0;
237    const uint32_t result_register_value = set_channels_config_tuser_from_value(current_register_value, field_value);
238    set_channels_config(array_index, result_register_value);
239  }
240
241  uint32_t Example::set_channels_config_tuser_from_value(
242    uint32_t register_value,
243    uint32_t field_value
244  ) const  {
245    const uint32_t shift = 1uL;
246    const uint32_t mask_at_base = 0b11111111uL;
247    const uint32_t mask_shifted = mask_at_base << shift;
248
249    // Check that field value is within the legal range.
250    const uint32_t mask_at_base_inverse = ~mask_at_base;
251    assert((field_value & mask_at_base_inverse) == 0);
252
253    const uint32_t field_value_masked = field_value & mask_at_base;
254    const uint32_t field_value_masked_and_shifted = field_value_masked << shift;
255
256    const uint32_t mask_shifted_inverse = ~mask_shifted;
257    const uint32_t register_value_masked = register_value & mask_shifted_inverse;
258
259    const uint32_t result_register_value = register_value_masked | field_value_masked_and_shifted;
260
261    return result_register_value;
262  }
263
264} /* namespace fpga_regs */

Note that when the register is part of an array, the register setter/getter takes a second argument array_index. There is an assert that the user-provided array index is within the bounds of the array.