.. _generator_cpp: C++ generator ============= A complete C++ class can be generated with methods to read/write the registers or fields. * :class:`.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. * :class:`.CppHeaderGenerator` creates a class header which inherits the abstract class. * :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 :ref:`toml_formatting` example. .. literalinclude:: py/generator_cpp.py :caption: Python code that parses the example TOML file and generates the C++ code we need. :language: Python :linenos: :lines: 10- Getters ------- It can be noted in the :ref:`interface_header` below that there are two ways to read a register field: 1. The method that reads the whole register, e.g. ``get_conf()``, which returns a ``struct`` with all field values. 2. The method that reads the register and returns the specific field value, e.g. ``get_conf_enable()``. 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 select the field values from the ``struct``. Apart from the methods discussed above, these is also a raw register getter available, e.g. ``get_conf_raw()``. This will read the whole register value and return it without any type conversion. Can be convenient in some special cases, for example when working with interrupt registers. Setters ------- Conversely there are two ways to write a register field: 1. The method that writes the whole register, e.g. ``set_conf()``, which takes a ``struct`` of field values as argument. 2. The method that reads the register, updates the value of the field, and then writes the register back, e.g. ``set_conf_enable()``. 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_conf()``, update our field values in the variable, and then call (1) once. Apart from the methods discussed above, these is also a raw register setter available, e.g. ``set_conf_raw()``. This will write the whole register value without any type conversion. Can be convenient in some special cases, for example when working with interrupt registers. Exceptions __________ The discussion about read-modify-write field setters 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 these registers will write all bits outside of the current field as their default value. This can for example be seen in the setter ``set_channels_conf_enable()`` in the generated code :ref:`below `. .. _cpp_assertion_macros: 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 :ref:`enumeration ` and :ref:`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 :ref:`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. .. code-block:: C++ 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: Interface header ---------------- Below is the resulting abstract interface header code, generated from the :ref:`toml_formatting` example. This contains the public API of the class. Note that all register constants as well as field attributes are included here. It can also be seen that when a register is part of an array, the setters/getters take a second argument ``array_index``. There is an assert in the :ref:`cpp_implementation` that user-provided array indexes are within bounds. .. literalinclude:: ../../../../generated/sphinx_rst/register_code/generator/generator_cpp/include/i_example.h :caption: Example interface header :language: C++ :linenos: .. _cpp_header: Class header ------------ Below is the generated class header. This overrides from the interface header and adds some internal private methods. .. literalinclude:: ../../../../generated/sphinx_rst/register_code/generator/generator_cpp/include/example.h :caption: Example class header :language: C++ :linenos: .. _cpp_implementation: Implementation -------------- Below is the generated class implementation. This implements all the methods of the class, whether public from the :ref:`interface_header` or private from the :ref:`cpp_header`. .. literalinclude:: ../../../../generated/sphinx_rst/register_code/generator/generator_cpp/example.cpp :caption: Example class implementation :language: C++ :linenos: