Coverage for hdl_registers/generator/cpp/interface.py: 97%
145 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-28 20:51 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-28 20:51 +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# --------------------------------------------------------------------------------------------------
10# Standard libraries
11from pathlib import Path
12from typing import TYPE_CHECKING, Any, Optional
14# First party libraries
15from hdl_registers.constant.bit_vector_constant import UnsignedVectorConstant
16from hdl_registers.constant.boolean_constant import BooleanConstant
17from hdl_registers.constant.float_constant import FloatConstant
18from hdl_registers.constant.integer_constant import IntegerConstant
19from hdl_registers.constant.string_constant import StringConstant
20from hdl_registers.field.bit import Bit
21from hdl_registers.field.bit_vector import BitVector
22from hdl_registers.field.enumeration import Enumeration
23from hdl_registers.field.integer import Integer
25# Local folder libraries
26from .cpp_generator_common import CppGeneratorCommon
28if TYPE_CHECKING:
29 # First party libraries
30 from hdl_registers.field.register_field import RegisterField
31 from hdl_registers.register import Register
32 from hdl_registers.register_array import RegisterArray
35class CppInterfaceGenerator(CppGeneratorCommon):
36 """
37 Generate a C++ interface header, suitable for mocking in a unit test environment.
38 See the :ref:`generator_cpp` article for usage details.
40 The interface header will contain:
42 * Attribute constants for each register and field, such as width, default value, etc.
44 * Enumeration types for all :ref:`field_enumeration`.
46 * Constant values for all :ref:`register constants <constant_overview>`.
48 * for each register, signature of getter and setter methods for reading/writing the register as
49 an ``uint``.
51 * for each field in each register, signature of getter and setter methods for reading/writing
52 the field as its native type (enumeration, positive/negative int, etc.).
54 * The setter will read-modify-write the register to update only the specified field,
55 depending on the mode of the register.
56 """
58 __version__ = "1.0.0"
60 SHORT_DESCRIPTION = "C++ interface header"
62 DEFAULT_INDENTATION_LEVEL = 4
64 @property
65 def output_file(self) -> Path:
66 """
67 Result will be placed in this file.
68 """
69 return self.output_folder / f"i_{self.name}.h"
71 def get_code(self, **kwargs: Any) -> str:
72 """
73 Get a complete C++ interface header with constants, types, attributes and methods for
74 accessing registers and fields.
75 """
76 cpp_code = ""
78 for register, register_array in self.iterate_registers():
79 field_cpp_code = ""
81 for field in register.fields:
82 field_cpp_code += self._field_attributes(
83 register=register, register_array=register_array, field=field
84 )
86 cpp_code += field_cpp_code
87 if field_cpp_code:
88 cpp_code += "\n"
90 for register_array in self.iterate_register_arrays():
91 cpp_code += self._register_array_attributes(register_array=register_array)
93 cpp_code += f" class I{self._class_name}\n"
94 cpp_code += " {\n"
95 cpp_code += " public:\n"
97 cpp_code += self._constants()
99 cpp_code += self._num_registers()
101 cpp_code += f" virtual ~I{self._class_name}() { } \n\n"
103 for register, register_array in self.iterate_registers():
104 cpp_code += f"{self.get_separator_line()}"
106 description = self._get_methods_description(
107 register=register, register_array=register_array
108 )
109 description += f" Mode '{register.mode.name}'."
111 cpp_code += self.comment(comment=description)
112 cpp_code += "\n"
114 if register.mode.software_can_read:
115 cpp_code += self.comment(
116 "Getter that will read the whole register's value over the register bus."
117 )
118 signature = self._register_getter_function_signature(
119 register=register, register_array=register_array
120 )
121 cpp_code += f" virtual uint32_t {signature} const = 0;\n\n"
123 if register.mode.software_can_write:
124 cpp_code += self.comment(
125 "Setter that will write the whole register's value over the register bus."
126 )
127 signature = self._register_setter_function_signature(
128 register=register, register_array=register_array
129 )
130 cpp_code += f" virtual void {signature} const = 0;\n\n"
132 cpp_code += self._field_interface(register, register_array)
134 cpp_code += " };\n\n"
136 cpp_code_top = """\
137#pragma once
139#include <sstream>
140#include <cstdint>
141#include <cstdlib>
143"""
144 return cpp_code_top + self._with_namespace(cpp_code)
146 def _constants(self) -> str:
147 cpp_code = ""
149 for constant in self.iterate_constants():
150 if isinstance(constant, BooleanConstant):
151 type_declaration = " bool"
152 value = str(constant.value).lower()
153 elif isinstance(constant, IntegerConstant):
154 type_declaration = " int"
155 value = str(constant.value)
156 elif isinstance(constant, FloatConstant):
157 # Expand "const" to "constexpr", which is needed for static floats:
158 # https://stackoverflow.com/questions/9141950/
159 # Use "double", to match the VHDL type which is at least 64 bits
160 # (IEEE 1076-2008, 5.2.5.1).
161 type_declaration = "expr double"
162 # Note that casting a Python float to string guarantees full precision in the
163 # resulting string: https://stackoverflow.com/a/60026172
164 value = str(constant.value)
165 elif isinstance(constant, StringConstant):
166 # Expand "const" to "constexpr", which is needed for static string literals.
167 type_declaration = "expr auto"
168 value = f'"{constant.value}"'
169 elif isinstance(constant, UnsignedVectorConstant):
170 type_declaration = " auto"
171 value = f"{constant.prefix}{constant.value_without_separator}"
172 else:
173 raise ValueError(f"Got unexpected constant type. {constant}")
175 cpp_code += self.comment("Register constant.")
176 cpp_code += f" static const{type_declaration} {constant.name} = {value};\n"
178 if cpp_code:
179 cpp_code += "\n"
181 return cpp_code
183 def _num_registers(self) -> str:
184 # It is possible that we have constants but no registers
185 num_registers = 0
186 if self.register_list.register_objects:
187 num_registers = self.register_list.register_objects[-1].index + 1
189 cpp_code = self.comment("Number of registers within this register list.")
190 cpp_code += f" static const size_t num_registers = {num_registers}uL;\n\n"
191 return cpp_code
193 def _field_interface(
194 self, register: "Register", register_array: Optional["RegisterArray"]
195 ) -> 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: Optional["RegisterArray"],
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"""