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.

Assertion macros

There are a few register-related things that can go wrong in an embedded system:

  1. An error in hardware might result in reading a field value that is out of bounds. This is mostly possible for enumeration and integer fields.

  2. Conversely, an error in software might result in writing a field value that is out of bounds.

  3. An error in software might result in a register array read/write with an index that is out of bounds.

The generated C++ implementation checks for these errors using custom assertion macros. Meaning, they can be detected at runtime as well as in unit tests.

If an assertion fails a user-specified handler function is called. In this function, the user could e.g. call a custom logger, perform a controlled shutdown, throw exceptions, etc. One argument is provided that contains a descriptive error message. The function must return a boolean true.

This error handler function must be provided to the constructor of the generated class.

Minimal example

A minimal and somewhat unrealistic handler function is shown below. More advanced handler functions are left to the user.

uintptr_t base_address = 0x43C00000;

bool register_assert_fail_handler(const std::string *diagnostic_message)
{
  std::cerr << *diagnostic_message << std::endl;
  std::exit(EXIT_FAILURE);
  return true;
}

fpga_regs::Example example(base_address, register_assert_fail_handler);

Disabling assertions

The assertions add to the binary size and to the runtime of the program. The user can disable the assertions by defining the following macros (usually with the -D compiler flag):

  1. NO_REGISTER_GETTER_ASSERT

  2. NO_REGISTER_SETTER_ASSERT

  3. NO_REGISTER_ARRAY_INDEX_ASSERT

This should result in no overhead.

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

Class header

Below is the generated class header:

Example class header
  1// -----------------------------------------------------------------------------
  2// This file is automatically generated by hdl-registers version 7.0.2-dev.
  3// Code generator CppHeaderGenerator version 1.0.0.
  4// Generated 2025-01-21 20:52 from file toml_format.toml at commit 3c3e6c67d817.
  5// Register hash 4df9765ebb584803b583628671e4659579eb85f4.
  6// -----------------------------------------------------------------------------
  7
  8#pragma once
  9
 10#include "i_example.h"
 11
 12namespace fpga_regs
 13{
 14
 15  class Example : public IExample
 16  {
 17  private:
 18    volatile uint32_t *m_registers;
 19    bool (*m_assertion_handler) (const std::string*);
 20
 21    void _assert_failed(const std::string *message) const;
 22
 23  public:
 24    /**
 25     * Class constructor.
 26     * @param base_address Byte address where these registers are memory mapped.
 27     *                     Can be e.g. '0x43C00000' in bare metal, or e.g.
 28     *                     'reinterpret_cast<uintptr_t>(mmap(...))' in Linux.
 29     *                     When using an operating system, care must be taken to pass the
 30     *                     virtual address, not the physical address.
 31     *                     When using bare metal, these are the same.
 32     * @param assertion_handler Function to call when an assertion fails.
 33     *                          Function takes a string pointer as an argument and must return a
 34     *                          boolean 'true'.
 35     */
 36    Example(uintptr_t base_address, bool (*assertion_handler) (const std::string*));
 37
 38    virtual ~Example() {}
 39
 40    // -------------------------------------------------------------------------
 41    // Methods for the 'config' register.
 42    // See interface header for documentation.
 43    virtual uint32_t get_config() const override;
 44    virtual uint32_t get_config_enable() const override;
 45    virtual uint32_t get_config_enable_from_value(
 46      uint32_t register_value
 47    ) const override;
 48    virtual example::config::direction::Enumeration get_config_direction() const override;
 49    virtual example::config::direction::Enumeration get_config_direction_from_value(
 50      uint32_t register_value
 51    ) const override;
 52    virtual void set_config(
 53      uint32_t register_value
 54    ) const override;
 55    virtual void set_config_enable(
 56      uint32_t field_value
 57    ) const override;
 58    virtual uint32_t set_config_enable_from_value(
 59      uint32_t register_value,
 60      uint32_t field_value
 61    ) const override;
 62    virtual void set_config_direction(
 63      example::config::direction::Enumeration field_value
 64    ) const override;
 65    virtual uint32_t set_config_direction_from_value(
 66      uint32_t register_value,
 67      example::config::direction::Enumeration field_value
 68    ) const override;
 69
 70    // -------------------------------------------------------------------------
 71    // Methods for the 'status' register.
 72    // See interface header for documentation.
 73    virtual uint32_t get_status() const override;
 74
 75    // -------------------------------------------------------------------------
 76    // Methods for the 'read_address' register within the 'channels' register array.
 77    // See interface header for documentation.
 78    virtual uint32_t get_channels_read_address(
 79      size_t array_index
 80    ) const override;
 81    virtual void set_channels_read_address(
 82      size_t array_index,
 83      uint32_t register_value
 84    ) const override;
 85
 86    // -------------------------------------------------------------------------
 87    // Methods for the 'config' register within the 'channels' register array.
 88    // See interface header for documentation.
 89    virtual void set_channels_config(
 90      size_t array_index,
 91      uint32_t register_value
 92    ) const override;
 93    virtual void set_channels_config_enable(
 94      size_t array_index,
 95      uint32_t field_value
 96    ) const override;
 97    virtual uint32_t set_channels_config_enable_from_value(
 98      uint32_t register_value,
 99      uint32_t field_value
100    ) const override;
101    virtual void set_channels_config_tuser(
102      size_t array_index,
103      uint32_t field_value
104    ) const override;
105    virtual uint32_t set_channels_config_tuser_from_value(
106      uint32_t register_value,
107      uint32_t field_value
108    ) const override;
109  };
110} /* namespace fpga_regs */

Implementation

Below is the generated class implementation:

Example class implementation
  1// -----------------------------------------------------------------------------
  2// This file is automatically generated by hdl-registers version 7.0.2-dev.
  3// Code generator CppImplementationGenerator version 2.0.0.
  4// Generated 2025-01-21 20:52 from file toml_format.toml at commit 3c3e6c67d817.
  5// Register hash 4df9765ebb584803b583628671e4659579eb85f4.
  6// -----------------------------------------------------------------------------
  7
  8#include "include/example.h"
  9
 10namespace fpga_regs
 11{
 12
 13#ifdef NO_REGISTER_SETTER_ASSERT
 14
 15#define _SETTER_ASSERT_TRUE(expression, message) ((void)0)
 16
 17#else // Not NO_REGISTER_SETTER_ASSERT.
 18
 19// This macro is called by the register code to check for runtime errors.
 20#define _SETTER_ASSERT_TRUE(expression, message)                                 \
 21  {                                                                              \
 22    if (!static_cast<bool>(expression)) {                                        \
 23      std::ostringstream diagnostics;                                            \
 24      diagnostics << "Tried to set value out of range in " << __FILE__ << ":"    \
 25                  << __LINE__ << ", message: " << message << ".";                \
 26      std::string diagnostic_message = diagnostics.str();                        \
 27      _assert_failed(&diagnostic_message);                                       \
 28    }                                                                            \
 29  }
 30
 31#endif // NO_REGISTER_SETTER_ASSERT.
 32
 33#ifdef NO_REGISTER_GETTER_ASSERT
 34
 35#define _GETTER_ASSERT_TRUE(expression, message) ((void)0)
 36
 37#else // Not NO_REGISTER_GETTER_ASSERT.
 38
 39// This macro is called by the register code to check for runtime errors.
 40#define _GETTER_ASSERT_TRUE(expression, message)                                 \
 41  {                                                                              \
 42    if (!static_cast<bool>(expression)) {                                        \
 43      std::ostringstream diagnostics;                                            \
 44      diagnostics << "Got read value out of range in " << __FILE__ << ":"        \
 45                  << __LINE__ << ", message: " << message << ".";                \
 46      std::string diagnostic_message = diagnostics.str();                        \
 47      _assert_failed(&diagnostic_message);                                       \
 48    }                                                                            \
 49  }
 50
 51#endif // NO_REGISTER_GETTER_ASSERT.
 52
 53#ifdef NO_REGISTER_ARRAY_INDEX_ASSERT
 54
 55#define _ARRAY_INDEX_ASSERT_TRUE(expression, message) ((void)0)
 56
 57#else // Not NO_REGISTER_ARRAY_INDEX_ASSERT.
 58
 59// This macro is called by the register code to check for runtime errors.
 60#define _ARRAY_INDEX_ASSERT_TRUE(expression, message)                            \
 61  {                                                                              \
 62    if (!static_cast<bool>(expression)) {                                        \
 63      std::ostringstream diagnostics;                                            \
 64      diagnostics << "Provided array index out of range in " << __FILE__ << ":"  \
 65                  << __LINE__ << ", message: " << message << ".";                \
 66      std::string diagnostic_message = diagnostics.str();                        \
 67      _assert_failed(&diagnostic_message);                                       \
 68    }                                                                            \
 69  }
 70
 71#endif // NO_REGISTER_ARRAY_INDEX_ASSERT.
 72
 73  Example::Example(uintptr_t base_address, bool (*assertion_handler) (const std::string*))
 74      : m_registers(reinterpret_cast<volatile uint32_t *>(base_address)),
 75        m_assertion_handler(assertion_handler)
 76  {
 77    // Empty
 78  }
 79
 80  void Example::_assert_failed(const std::string *message) const
 81  {
 82    m_assertion_handler(message);
 83  }
 84
 85  // ---------------------------------------------------------------------------
 86  // Methods for the 'config' register.
 87  // See interface header for documentation.
 88
 89  uint32_t Example::get_config() const
 90  {
 91    const size_t index = 0;
 92    const uint32_t result = m_registers[index];
 93
 94    return result;
 95  }
 96
 97  uint32_t Example::get_config_enable() const
 98  {
 99    const uint32_t register_value = get_config();
100    const uint32_t field_value = get_config_enable_from_value(register_value);
101
102    return field_value;
103  }
104
105  uint32_t Example::get_config_enable_from_value(
106    uint32_t register_value
107  ) const
108  {
109    const uint32_t shift = 0uL;
110    const uint32_t mask_at_base = 0b1uL;
111    const uint32_t mask_shifted = mask_at_base << shift;
112
113    const uint32_t result_masked = register_value & mask_shifted;
114    const uint32_t result_shifted = result_masked >> shift;
115
116    uint32_t field_value;
117
118    // No casting needed.
119    field_value = result_shifted;
120
121    return field_value;
122  }
123
124  example::config::direction::Enumeration Example::get_config_direction() const
125  {
126    const uint32_t register_value = get_config();
127    const example::config::direction::Enumeration field_value = get_config_direction_from_value(register_value);
128
129    return field_value;
130  }
131
132  example::config::direction::Enumeration Example::get_config_direction_from_value(
133    uint32_t register_value
134  ) const
135  {
136    const uint32_t shift = 1uL;
137    const uint32_t mask_at_base = 0b11uL;
138    const uint32_t mask_shifted = mask_at_base << shift;
139
140    const uint32_t result_masked = register_value & mask_shifted;
141    const uint32_t result_shifted = result_masked >> shift;
142
143    example::config::direction::Enumeration field_value;
144
145    // "Cast" to the enum type.
146    field_value = example::config::direction::Enumeration(result_shifted);
147
148    return field_value;
149  }
150
151  void Example::set_config(
152    uint32_t register_value
153  ) const
154  {
155    const size_t index = 0;
156    m_registers[index] = register_value;
157  }
158
159  void Example::set_config_enable(
160    uint32_t field_value
161  ) const
162  {
163    // Get the current value of other fields by reading register on the bus.
164    const uint32_t current_register_value = get_config();
165    const uint32_t result_register_value = set_config_enable_from_value(current_register_value, field_value);
166    set_config(result_register_value);
167  }
168
169  uint32_t Example::set_config_enable_from_value(
170    uint32_t register_value,
171    uint32_t field_value
172  ) const  {
173    const uint32_t shift = 0uL;
174    const uint32_t mask_at_base = 0b1uL;
175    const uint32_t mask_shifted = mask_at_base << shift;
176
177    // Check that field value is within the legal range.
178    const uint32_t mask_at_base_inverse = ~mask_at_base;
179    _SETTER_ASSERT_TRUE(
180      (field_value & mask_at_base_inverse) == 0,
181      "'enable' value too many bits used, got '" << field_value << "'"
182    );
183
184    const uint32_t field_value_masked = field_value & mask_at_base;
185    const uint32_t field_value_masked_and_shifted = field_value_masked << shift;
186
187    const uint32_t mask_shifted_inverse = ~mask_shifted;
188    const uint32_t register_value_masked = register_value & mask_shifted_inverse;
189
190    const uint32_t result_register_value = register_value_masked | field_value_masked_and_shifted;
191
192    return result_register_value;
193  }
194
195  void Example::set_config_direction(
196    example::config::direction::Enumeration field_value
197  ) const
198  {
199    // Get the current value of other fields by reading register on the bus.
200    const uint32_t current_register_value = get_config();
201    const uint32_t result_register_value = set_config_direction_from_value(current_register_value, field_value);
202    set_config(result_register_value);
203  }
204
205  uint32_t Example::set_config_direction_from_value(
206    uint32_t register_value,
207    example::config::direction::Enumeration field_value
208  ) const  {
209    const uint32_t shift = 1uL;
210    const uint32_t mask_at_base = 0b11uL;
211    const uint32_t mask_shifted = mask_at_base << shift;
212
213    const uint32_t field_value_masked = field_value & mask_at_base;
214    const uint32_t field_value_masked_and_shifted = field_value_masked << shift;
215
216    const uint32_t mask_shifted_inverse = ~mask_shifted;
217    const uint32_t register_value_masked = register_value & mask_shifted_inverse;
218
219    const uint32_t result_register_value = register_value_masked | field_value_masked_and_shifted;
220
221    return result_register_value;
222  }
223
224  // ---------------------------------------------------------------------------
225  // Methods for the 'status' register.
226  // See interface header for documentation.
227
228  uint32_t Example::get_status() const
229  {
230    const size_t index = 1;
231    const uint32_t result = m_registers[index];
232
233    return result;
234  }
235
236  // ---------------------------------------------------------------------------
237  // Methods for the 'read_address' register within the 'channels' register array.
238  // See interface header for documentation.
239
240  uint32_t Example::get_channels_read_address(
241    size_t array_index
242  ) const
243  {
244    _ARRAY_INDEX_ASSERT_TRUE(
245      array_index < example::channels::array_length,
246      "'channels' array index out of range, got '" << array_index << "'"
247    );
248
249    const size_t index = 2 + array_index * 2 + 0;
250    const uint32_t result = m_registers[index];
251
252    return result;
253  }
254
255  void Example::set_channels_read_address(
256    size_t array_index,
257    uint32_t register_value
258  ) const
259  {
260    _ARRAY_INDEX_ASSERT_TRUE(
261      array_index < example::channels::array_length,
262      "'channels' array index out of range, got '" << array_index << "'"
263    );
264
265    const size_t index = 2 + array_index * 2 + 0;
266    m_registers[index] = register_value;
267  }
268
269  // ---------------------------------------------------------------------------
270  // Methods for the 'config' register within the 'channels' register array.
271  // See interface header for documentation.
272
273  void Example::set_channels_config(
274    size_t array_index,
275    uint32_t register_value
276  ) const
277  {
278    _ARRAY_INDEX_ASSERT_TRUE(
279      array_index < example::channels::array_length,
280      "'channels' array index out of range, got '" << array_index << "'"
281    );
282
283    const size_t index = 2 + array_index * 2 + 1;
284    m_registers[index] = register_value;
285  }
286
287  void Example::set_channels_config_enable(
288    size_t array_index,
289    uint32_t field_value
290  ) const
291  {
292    // Set everything except for the field to default when writing the value.
293    const uint32_t current_register_value = 0;
294    const uint32_t result_register_value = set_channels_config_enable_from_value(current_register_value, field_value);
295    set_channels_config(array_index, result_register_value);
296  }
297
298  uint32_t Example::set_channels_config_enable_from_value(
299    uint32_t register_value,
300    uint32_t field_value
301  ) const  {
302    const uint32_t shift = 0uL;
303    const uint32_t mask_at_base = 0b1uL;
304    const uint32_t mask_shifted = mask_at_base << shift;
305
306    // Check that field value is within the legal range.
307    const uint32_t mask_at_base_inverse = ~mask_at_base;
308    _SETTER_ASSERT_TRUE(
309      (field_value & mask_at_base_inverse) == 0,
310      "'enable' value too many bits used, got '" << field_value << "'"
311    );
312
313    const uint32_t field_value_masked = field_value & mask_at_base;
314    const uint32_t field_value_masked_and_shifted = field_value_masked << shift;
315
316    const uint32_t mask_shifted_inverse = ~mask_shifted;
317    const uint32_t register_value_masked = register_value & mask_shifted_inverse;
318
319    const uint32_t result_register_value = register_value_masked | field_value_masked_and_shifted;
320
321    return result_register_value;
322  }
323
324  void Example::set_channels_config_tuser(
325    size_t array_index,
326    uint32_t field_value
327  ) const
328  {
329    // Set everything except for the field to default when writing the value.
330    const uint32_t current_register_value = 0;
331    const uint32_t result_register_value = set_channels_config_tuser_from_value(current_register_value, field_value);
332    set_channels_config(array_index, result_register_value);
333  }
334
335  uint32_t Example::set_channels_config_tuser_from_value(
336    uint32_t register_value,
337    uint32_t field_value
338  ) const  {
339    const uint32_t shift = 1uL;
340    const uint32_t mask_at_base = 0b11111111uL;
341    const uint32_t mask_shifted = mask_at_base << shift;
342
343    // Check that field value is within the legal range.
344    const uint32_t mask_at_base_inverse = ~mask_at_base;
345    _SETTER_ASSERT_TRUE(
346      (field_value & mask_at_base_inverse) == 0,
347      "'tuser' value too many bits used, got '" << field_value << "'"
348    );
349
350    const uint32_t field_value_masked = field_value & mask_at_base;
351    const uint32_t field_value_masked_and_shifted = field_value_masked << shift;
352
353    const uint32_t mask_shifted_inverse = ~mask_shifted;
354    const uint32_t register_value_masked = register_value & mask_shifted_inverse;
355
356    const uint32_t result_register_value = register_value_masked | field_value_masked_and_shifted;
357
358    return result_register_value;
359  }
360
361} /* 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.