Coverage for hdl_registers/generator/cpp/interface.py: 96%
146 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 11:11 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 11:11 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the hdl-registers project, an HDL register generator fast enough to run
5# in real time.
6# https://hdl-registers.com
7# https://github.com/hdl-registers/hdl-registers
8# --------------------------------------------------------------------------------------------------
10from __future__ import annotations
12from typing import TYPE_CHECKING, Any
14from hdl_registers.constant.bit_vector_constant import UnsignedVectorConstant
15from hdl_registers.constant.boolean_constant import BooleanConstant
16from hdl_registers.constant.float_constant import FloatConstant
17from hdl_registers.constant.integer_constant import IntegerConstant
18from hdl_registers.constant.string_constant import StringConstant
19from hdl_registers.field.bit import Bit
20from hdl_registers.field.bit_vector import BitVector
21from hdl_registers.field.enumeration import Enumeration
22from hdl_registers.field.integer import Integer
24from .cpp_generator_common import CppGeneratorCommon
26if TYPE_CHECKING:
27 from pathlib import Path
29 from hdl_registers.field.register_field import RegisterField
30 from hdl_registers.register import Register
31 from hdl_registers.register_array import RegisterArray
34class CppInterfaceGenerator(CppGeneratorCommon):
35 """
36 Generate a C++ interface header, suitable for mocking in a unit test environment.
37 See the :ref:`generator_cpp` article for usage details.
39 The interface header will contain:
41 * Attribute constants for each register and field, such as width, default value, etc.
43 * Enumeration types for all :ref:`field_enumeration`.
45 * Constant values for all :ref:`register constants <constant_overview>`.
47 * for each register, signature of getter and setter methods for reading/writing the register as
48 an ``uint``.
50 * for each field in each register, signature of getter and setter methods for reading/writing
51 the field as its native type (enumeration, positive/negative int, etc.).
53 * The setter will read-modify-write the register to update only the specified field,
54 depending on the mode of the register.
55 """
57 __version__ = "1.0.0"
59 SHORT_DESCRIPTION = "C++ interface header"
61 DEFAULT_INDENTATION_LEVEL = 4
63 @property
64 def output_file(self) -> Path:
65 """
66 Result will be placed in this file.
67 """
68 return self.output_folder / f"i_{self.name}.h"
70 def get_code(
71 self,
72 **kwargs: Any, # noqa: ANN401, ARG002
73 ) -> str:
74 """
75 Get a complete C++ interface header with constants, types, attributes and methods for
76 accessing registers and fields.
77 """
78 cpp_code = ""
80 for register, register_array in self.iterate_registers():
81 field_cpp_code = ""
83 for field in register.fields:
84 field_cpp_code += self._field_attributes(
85 register=register, register_array=register_array, field=field
86 )
88 cpp_code += field_cpp_code
89 if field_cpp_code:
90 cpp_code += "\n"
92 for register_array in self.iterate_register_arrays():
93 cpp_code += self._register_array_attributes(register_array=register_array)
95 cpp_code += f" class I{self._class_name}\n"
96 cpp_code += " {\n"
97 cpp_code += " public:\n"
99 cpp_code += self._constants()
101 cpp_code += self._num_registers()
103 cpp_code += f" virtual ~I{self._class_name}() { } \n\n"
105 for register, register_array in self.iterate_registers():
106 cpp_code += f"{self.get_separator_line()}"
108 description = self._get_methods_description(
109 register=register, register_array=register_array
110 )
111 description += f" Mode '{register.mode.name}'."
113 cpp_code += self.comment(comment=description)
114 cpp_code += "\n"
116 if register.mode.software_can_read:
117 cpp_code += self.comment(
118 "Getter that will read the whole register's value over the register bus."
119 )
120 signature = self._register_getter_function_signature(
121 register=register, register_array=register_array
122 )
123 cpp_code += f" virtual uint32_t {signature} const = 0;\n\n"
125 if register.mode.software_can_write:
126 cpp_code += self.comment(
127 "Setter that will write the whole register's value over the register bus."
128 )
129 signature = self._register_setter_function_signature(
130 register=register, register_array=register_array
131 )
132 cpp_code += f" virtual void {signature} const = 0;\n\n"
134 cpp_code += self._field_interface(register, register_array)
136 cpp_code += " };\n\n"
138 cpp_code_top = """\
139#pragma once
141#include <sstream>
142#include <cstdint>
143#include <cstdlib>
145"""
146 return cpp_code_top + self._with_namespace(cpp_code)
148 def _constants(self) -> str:
149 cpp_code = ""
151 for constant in self.iterate_constants():
152 if isinstance(constant, BooleanConstant):
153 type_declaration = " bool"
154 value = str(constant.value).lower()
155 elif isinstance(constant, IntegerConstant):
156 type_declaration = " int"
157 value = str(constant.value)
158 elif isinstance(constant, FloatConstant):
159 # Expand "const" to "constexpr", which is needed for static floats:
160 # https://stackoverflow.com/questions/9141950/
161 # Use "double", to match the VHDL type which is at least 64 bits
162 # (IEEE 1076-2008, 5.2.5.1).
163 type_declaration = "expr double"
164 # Note that casting a Python float to string guarantees full precision in the
165 # resulting string: https://stackoverflow.com/a/60026172
166 value = str(constant.value)
167 elif isinstance(constant, StringConstant):
168 # Expand "const" to "constexpr", which is needed for static string literals.
169 type_declaration = "expr auto"
170 value = f'"{constant.value}"'
171 elif isinstance(constant, UnsignedVectorConstant):
172 type_declaration = " auto"
173 value = f"{constant.prefix}{constant.value_without_separator}"
174 else:
175 raise TypeError(f"Got unexpected constant type: {constant}")
177 cpp_code += self.comment("Register constant.")
178 cpp_code += f" static const{type_declaration} {constant.name} = {value};\n"
180 if cpp_code:
181 cpp_code += "\n"
183 return cpp_code
185 def _num_registers(self) -> str:
186 # It is possible that we have constants but no registers
187 num_registers = 0
188 if self.register_list.register_objects:
189 num_registers = self.register_list.register_objects[-1].index + 1
191 cpp_code = self.comment("Number of registers within this register list.")
192 cpp_code += f" static const size_t num_registers = {num_registers}uL;\n\n"
193 return cpp_code
195 def _field_interface(self, register: Register, register_array: RegisterArray | None) -> str:
196 def function(return_type_name: str, signature: str) -> str:
197 return f" virtual {return_type_name} {signature} const = 0;\n"
199 cpp_code = ""
200 for field in register.fields:
201 field_description = self.field_description(
202 register=register, register_array=register_array, field=field
203 )
204 field_type_name = self._field_value_type_name(
205 register=register, register_array=register_array, field=field
206 )
208 if register.mode.software_can_read:
209 comment = [
210 f"Getter for the {field_description},",
211 "which will read register value over the register bus.",
212 ]
213 cpp_code += self.comment_block(text=comment)
215 signature = self._field_getter_function_signature(
216 register=register,
217 register_array=register_array,
218 field=field,
219 from_value=False,
220 )
221 cpp_code += function(return_type_name=field_type_name, signature=signature)
223 comment = [f"Getter for the {field_description},", "given a register value."]
224 cpp_code += self.comment_block(text=comment)
226 signature = self._field_getter_function_signature(
227 register=register,
228 register_array=register_array,
229 field=field,
230 from_value=True,
231 )
232 cpp_code += function(return_type_name=field_type_name, signature=signature)
234 if register.mode.software_can_write:
235 comment = [f"Setter for the {field_description},"]
236 if self.field_setter_should_read_modify_write(register=register):
237 comment.append("which will read-modify-write over the register bus.")
238 else:
239 comment.append(
240 "which will set the field to the given value, "
241 "and everything else to default."
242 )
244 cpp_code += self.comment_block(text=comment)
246 signature = self._field_setter_function_signature(
247 register=register,
248 register_array=register_array,
249 field=field,
250 from_value=False,
251 )
252 cpp_code += function(return_type_name="void", signature=signature)
254 comment = [
255 f"Setter for the {field_description},",
256 "given a register value, which will return an updated value.",
257 ]
258 cpp_code += self.comment_block(text=comment)
260 signature = self._field_setter_function_signature(
261 register=register,
262 register_array=register_array,
263 field=field,
264 from_value=True,
265 )
266 cpp_code += function(return_type_name="uint32_t", signature=signature)
268 cpp_code += "\n"
270 return cpp_code
272 @staticmethod
273 def _get_default_value(field: RegisterField) -> str:
274 """
275 Get the field's default value formatted in a way suitable for C++ code.
276 """
277 if isinstance(field, (Bit, BitVector)):
278 return f"0b{field.default_value}"
280 if isinstance(field, Enumeration):
281 return f"Enumeration::{field.default_value.name}"
283 if isinstance(field, Integer):
284 return str(field.default_value)
286 raise ValueError(f'Unknown field type for "{field.name}" field: {type(field)}')
288 def _field_attributes(
289 self,
290 register: Register,
291 register_array: RegisterArray | None,
292 field: RegisterField,
293 ) -> str:
294 field_description = self.field_description(
295 register=register, register_array=register_array, field=field
296 )
297 cpp_code = self.comment(f"Attributes for the {field_description}.", indent=2)
299 array_namespace = f"::{register_array.name}" if register_array else ""
300 namespace = f"{self.name}{array_namespace}::{register.name}::{field.name}"
302 cpp_code += f" namespace {namespace}\n"
303 cpp_code += " {\n"
304 cpp_code += f" static const auto width = {field.width};\n"
306 if isinstance(field, Enumeration):
307 name_value_pairs = [f"{element.name} = {element.value}," for element in field.elements]
308 separator = "\n "
309 cpp_code += f"""\
310 enum Enumeration
311 {
312 {separator.join(name_value_pairs)}
313 } ;
314"""
316 cpp_code += (
317 f" static const auto default_value = {self._get_default_value(field=field)};\n"
318 )
319 cpp_code += " }\n"
321 return cpp_code
323 def _register_array_attributes(self, register_array: RegisterArray) -> str:
324 return f"""\
325 // Attributes for the "{register_array.name}" register array.
326 namespace {self.name}::{register_array.name}
327 {
328 // Number of times the registers of the array are repeated.
329 static const auto array_length = {register_array.length};
330 } ;
332"""