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.

volatile uint8_t *base_address = reinterpret_cast<volatile uint8_t*>(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// This file is automatically generated by hdl-registers version 6.2.1-dev.
  2// Code generator CppInterfaceGenerator version 1.0.0.
  3// Generated 2024-12-19 20:52 from file toml_format.toml at commit cd01ff93f646632c.
  4// Register hash 4df9765ebb584803b583628671e4659579eb85f4.
  5
  6#pragma once
  7
  8#include <sstream>
  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 6.2.1-dev.
  2// Code generator CppHeaderGenerator version 1.0.0.
  3// Generated 2024-12-19 20:52 from file toml_format.toml at commit cd01ff93f646632c.
  4// Register hash 4df9765ebb584803b583628671e4659579eb85f4.
  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    bool (*m_assertion_handler) (const std::string*);
 18
 19    void _assert_failed(const std::string *message) const;
 20
 21  public:
 22    /**
 23     * Class constructor.
 24     * @param base_address Pointer to where these registers are memory mapped.
 25     *                     When using an operating system, care must be taken to pass the
 26     *                     physical address, not the virtual address.
 27     *                     When using bare metal, these are the same.
 28     * @param assertion_handler Function to call when an assertion fails.
 29     */
 30    Example(volatile uint8_t *base_address, bool (*assertion_handler) (const std::string*));
 31
 32    virtual ~Example() {}
 33
 34    // -------------------------------------------------------------------------
 35    // Methods for the 'config' register.
 36    // See interface header for documentation.
 37    virtual uint32_t get_config() const override;
 38    virtual uint32_t get_config_enable() const override;
 39    virtual uint32_t get_config_enable_from_value(
 40      uint32_t register_value
 41    ) const override;
 42    virtual example::config::direction::Enumeration get_config_direction() const override;
 43    virtual example::config::direction::Enumeration get_config_direction_from_value(
 44      uint32_t register_value
 45    ) const override;
 46    virtual void set_config(
 47      uint32_t register_value
 48    ) const override;
 49    virtual void set_config_enable(
 50      uint32_t field_value
 51    ) const override;
 52    virtual uint32_t set_config_enable_from_value(
 53      uint32_t register_value,
 54      uint32_t field_value
 55    ) const override;
 56    virtual void set_config_direction(
 57      example::config::direction::Enumeration field_value
 58    ) const override;
 59    virtual uint32_t set_config_direction_from_value(
 60      uint32_t register_value,
 61      example::config::direction::Enumeration field_value
 62    ) const override;
 63
 64    // -------------------------------------------------------------------------
 65    // Methods for the 'status' register.
 66    // See interface header for documentation.
 67    virtual uint32_t get_status() const override;
 68
 69    // -------------------------------------------------------------------------
 70    // Methods for the 'read_address' register within the 'channels' register array.
 71    // See interface header for documentation.
 72    virtual uint32_t get_channels_read_address(
 73      size_t array_index
 74    ) const override;
 75    virtual void set_channels_read_address(
 76      size_t array_index,
 77      uint32_t register_value
 78    ) const override;
 79
 80    // -------------------------------------------------------------------------
 81    // Methods for the 'config' register within the 'channels' register array.
 82    // See interface header for documentation.
 83    virtual void set_channels_config(
 84      size_t array_index,
 85      uint32_t register_value
 86    ) const override;
 87    virtual void set_channels_config_enable(
 88      size_t array_index,
 89      uint32_t field_value
 90    ) const override;
 91    virtual uint32_t set_channels_config_enable_from_value(
 92      uint32_t register_value,
 93      uint32_t field_value
 94    ) const override;
 95    virtual void set_channels_config_tuser(
 96      size_t array_index,
 97      uint32_t field_value
 98    ) const override;
 99    virtual uint32_t set_channels_config_tuser_from_value(
100      uint32_t register_value,
101      uint32_t field_value
102    ) const override;
103  };
104} /* namespace fpga_regs */

Implementation

Below is the generated class implementation:

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