Coverage for hdl_registers/generator/cpp/interface.py: 96%
161 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-29 06:41 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-29 06:41 +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
23from hdl_registers.register_array import RegisterArray
25from .cpp_generator_common import CppGeneratorCommon
27if TYPE_CHECKING:
28 from pathlib import Path
30 from hdl_registers.field.register_field import RegisterField
31 from hdl_registers.register import Register
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 = f"""{self._get_attributes()}\
79 class I{self._class_name}
80 {
81 public:
82"""
83 cpp_code += self._get_constants()
85 cpp_code += self._get_num_registers()
87 cpp_code += f" virtual ~I{self._class_name}() { } \n"
89 separator = self.get_separator_line()
91 for register, register_array in self.iterate_registers():
92 cpp_code += self._get_register_heading(
93 register=register, register_array=register_array, separator=separator
94 )
96 if register.mode.software_can_read:
97 cpp_code += self._get_getters(register=register, register_array=register_array)
99 if register.mode.software_can_write:
100 # Add empty line between getter and setter interfaces.
101 cpp_code += "\n"
103 if register.mode.software_can_write:
104 cpp_code += self._get_setters(register=register, register_array=register_array)
106 cpp_code += separator
108 cpp_code += " };\n\n"
110 cpp_code_top = """\
111#pragma once
113#include <sstream>
114#include <cstdint>
115#include <cstdlib>
117"""
118 return cpp_code_top + self._with_namespace(cpp_code)
120 def _get_attributes(self) -> str:
121 if not self.register_list.register_objects:
122 # Don't create a namespace if the register list contains only constants.
123 return ""
125 attributes_cpp: list[str] = []
126 for register_object in self.iterate_register_objects():
127 if isinstance(register_object, RegisterArray):
128 attributes_cpp.append(
129 self._get_register_array_attributes(register_array=register_object)
130 )
131 elif register_object.fields:
132 attributes_cpp.append(
133 self._get_register_attributes(register=register_object, indent=4)
134 )
136 attribute_cpp = "\n".join(attributes_cpp)
137 return f"""\
138 namespace {self.name}
139 {
141{attribute_cpp}
142 } // namespace {self.name}
144"""
146 def _get_register_attributes(self, register: Register, indent: int) -> str:
147 if not register.fields:
148 raise ValueError("Should not end up here if the register has no fields.")
150 attributes_cpp: list[str] = []
151 struct_values_cpp: list[str] = []
152 default_values_cpp: list[str] = []
154 for field in register.fields:
155 indentation = self.get_indentation(indent=indent + 2)
156 field_type = self._get_field_value_type(
157 register=register, register_array=None, field=field, include_namespace=False
158 )
160 if isinstance(field, Enumeration):
161 separator = "\n" + " " * (indent + 6)
162 name_value_pairs = "".join(
163 [f"{separator}{element.name} = {element.value}," for element in field.elements]
164 )
165 typedef = f"""
166{indentation} // The valid elements for this enumeration field.
167{indentation} enum Enumeration
168{indentation} { \
169{name_value_pairs}
170{indentation} } ;
171"""
172 field_type_struct = f"{field.name}::Enumeration"
173 else:
174 typedef = ""
175 field_type_struct = field_type
177 default_value = self._get_default_value(field=field)
178 default_value_raw = self._get_default_value(field=field, raw=True)
180 # If 'width' is 32, '1 << width' is a 33-bit unsigned number.
181 # The C++ standard requires an "int" to be at least 16 bits, "long" at least 32,
182 # and "long long" at least 64.
183 # Hence in order to avoid overflow, we have to use "uLL".
184 # Once '1' is subtracted from the shifted value, it will always fit in 32 unsigned bits.
185 attributes_cpp.append(f"""\
186{indentation}// Attributes for the '{field.name}' field.
187{indentation}namespace {field.name}
188{indentation}{
189{indentation} // The number of bits that the field occupies.
190{indentation} static const size_t width = {field.width};
191{indentation} // The bit index of the lowest bit in the field.
192{indentation} static const size_t shift = {field.base_index};
193{indentation} // The bit mask of the field, at index zero.
194{indentation} static const uint32_t mask_at_base = (1uLL << width) - 1;
195{indentation} // The bit mask of the field, at the field's bit index.
196{indentation} static const uint32_t mask_shifted = mask_at_base << shift;
197{typedef}
198{indentation} // Initial value of the field at device startup/reset.
199{indentation} static const {field_type} default_value = {default_value};
200{indentation} // Raw representation of the initial value, at the the field's bit index.
201{indentation} static const uint32_t default_value_raw = {default_value_raw} << shift;
202{indentation}}
203""")
204 struct_values_cpp.append(f"{indentation} {field_type_struct} {field.name};")
205 default_values_cpp.append(f"{indentation} {field.name}::default_value,")
207 indentation = self.get_indentation(indent=indent)
208 attribute_cpp = "\n".join(attributes_cpp)
209 struct_value_cpp = "\n".join(struct_values_cpp)
210 default_value_cpp = "\n".join(default_values_cpp)
212 return f"""\
213{indentation}// Attributes for the '{register.name}' register.
214{indentation}namespace {register.name} {
215{attribute_cpp}
216{indentation} // Struct that holds the value of each field in the register,
217{indentation} // in a native C++ representation.
218{indentation} struct Value {
219{struct_value_cpp}
220{indentation} } ;
221{indentation} // Initial value of the register at device startup/reset.
222{indentation} const Value default_value = {
223{default_value_cpp}
224{indentation} } ;
225{indentation}}
226"""
228 @staticmethod
229 def _get_default_value(field: RegisterField, raw: bool = False) -> str: # noqa: PLR0911
230 """
231 Get the field's default value formatted in a way suitable for C++ code.
232 """
233 if isinstance(field, Bit):
234 if raw:
235 return field.default_value
237 return "true" if field.default_value == "1" else "false"
239 if isinstance(field, BitVector):
240 if raw:
241 return f"0b{field.default_value}uL"
243 # Note that casting a Python float to string guarantees full precision in the
244 # resulting string: https://stackoverflow.com/a/60026172
245 return str(
246 field.numerical_interpretation.convert_from_unsigned_binary(
247 unsigned_binary=int(field.default_value, base=2)
248 )
249 )
251 if isinstance(field, Enumeration):
252 if raw:
253 return f"{field.default_value.value}uL"
255 return f"Enumeration::{field.default_value.name}"
257 if isinstance(field, Integer):
258 if raw:
259 return f"{field.default_value_uint}uL"
260 return str(field.default_value)
262 raise ValueError(f'Unknown field type for "{field.name}" field: {type(field)}')
264 def _get_register_array_attributes(self, register_array: RegisterArray) -> str:
265 registers_cpp = [
266 self._get_register_attributes(register=register, indent=6)
267 for register in register_array.registers
268 if register.fields
269 ]
270 register_cpp = "\n".join(registers_cpp)
272 return f"""\
273 // Attributes for the '{register_array.name}' register array.
274 namespace {register_array.name}
275 {
276 // Number of times the registers of the array are repeated.
277 static const auto array_length = {register_array.length};
279{register_cpp}\
280 }
281"""
283 def _get_constants(self) -> str:
284 cpp_code = ""
286 for constant in self.iterate_constants():
287 if isinstance(constant, BooleanConstant):
288 type_declaration = " bool"
289 value = str(constant.value).lower()
290 elif isinstance(constant, IntegerConstant):
291 type_declaration = " int"
292 value = str(constant.value)
293 elif isinstance(constant, FloatConstant):
294 # Expand "const" to "constexpr", which is needed for static floats:
295 # https://stackoverflow.com/questions/9141950/
296 # Use "double", to match the VHDL type which is at least 64 bits
297 # (IEEE 1076-2008, 5.2.5.1).
298 type_declaration = "expr double"
299 # Note that casting a Python float to string guarantees full precision in the
300 # resulting string: https://stackoverflow.com/a/60026172
301 value = str(constant.value)
302 elif isinstance(constant, StringConstant):
303 # Expand "const" to "constexpr", which is needed for static string literals.
304 type_declaration = "expr auto"
305 value = f'"{constant.value}"'
306 elif isinstance(constant, UnsignedVectorConstant):
307 type_declaration = " auto"
308 value = f"{constant.prefix}{constant.value_without_separator}"
309 else:
310 raise TypeError(f"Got unexpected constant type: {constant}")
312 cpp_code += self.comment("Register constant.")
313 cpp_code += f" static const{type_declaration} {constant.name} = {value};\n"
315 if cpp_code:
316 cpp_code += "\n"
318 return cpp_code
320 def _get_num_registers(self) -> str:
321 # It is possible that we have constants but no registers
322 num_registers = 0
323 if self.register_list.register_objects:
324 num_registers = self.register_list.register_objects[-1].index + 1
326 cpp_code = self.comment("Number of registers within this register list.")
327 cpp_code += f" static const size_t num_registers = {num_registers}uL;\n\n"
328 return cpp_code
330 def _get_getters(self, register: Register, register_array: RegisterArray | None) -> str:
331 def get_function(comment: str, return_type: str, signature: str) -> str:
332 return f"""\
333{comment}\
334 virtual {return_type} {signature} = 0;
335"""
337 cpp_code: list[str] = []
339 register_type = self._get_register_value_type(
340 register=register, register_array=register_array
341 )
342 signature = self._register_getter_signature(
343 register=register, register_array=register_array
344 )
345 cpp_code.append(
346 get_function(
347 comment=self._get_getter_comment(),
348 return_type=register_type,
349 signature=signature,
350 )
351 )
353 if register.fields:
354 # The main getter will perform type conversion.
355 # Provide a getter that returns the raw value also.
356 signature = self._register_getter_signature(
357 register=register, register_array=register_array, raw=True
358 )
359 cpp_code.append(
360 get_function(
361 comment=self._get_getter_comment(raw=True),
362 return_type="uint32_t",
363 signature=signature,
364 )
365 )
367 for field in register.fields:
368 field_type = self._get_field_value_type(
369 register=register, register_array=register_array, field=field
370 )
372 signature = self._field_getter_signature(
373 register=register,
374 register_array=register_array,
375 field=field,
376 from_raw=False,
377 )
378 cpp_code.append(
379 get_function(
380 comment=self._get_getter_comment(field=field),
381 return_type=field_type,
382 signature=signature,
383 )
384 )
386 return "\n".join(cpp_code)
388 def _get_setters(self, register: Register, register_array: RegisterArray | None) -> str:
389 def get_function(comment: str, signature: str) -> str:
390 return f"{comment} virtual void {signature} = 0;\n"
392 cpp_code: list[str] = []
394 signature = self._register_setter_signature(
395 register=register, register_array=register_array
396 )
397 cpp_code.append(
398 get_function(
399 comment=self._get_setter_comment(register=register),
400 signature=signature,
401 )
402 )
404 if register.fields:
405 # The main setter will perform type conversion.
406 # Provide a setter that takes a raw value also.
407 signature = self._register_setter_signature(
408 register=register, register_array=register_array, raw=True
409 )
410 cpp_code.append(
411 get_function(
412 comment=self._get_setter_comment(register=register, raw=True),
413 signature=signature,
414 )
415 )
417 for field in register.fields:
418 signature = self._field_setter_signature(
419 register=register,
420 register_array=register_array,
421 field=field,
422 from_raw=False,
423 )
424 cpp_code.append(
425 get_function(
426 comment=self._get_setter_comment(register=register, field=field),
427 signature=signature,
428 )
429 )
431 return "\n".join(cpp_code)