Coverage for hdl_registers/generator/cpp/interface.py: 97%
145 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-01 20:50 +0000
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-01 20:50 +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 = f"""\
137{self.header}
138#pragma once
140#include <cassert>
141#include <cstdint>
142#include <cstdlib>
144"""
145 return cpp_code_top + self._with_namespace(cpp_code)
147 def _constants(self) -> str:
148 cpp_code = ""
150 for constant in self.iterate_constants():
151 if isinstance(constant, BooleanConstant):
152 type_declaration = " bool"
153 value = str(constant.value).lower()
154 elif isinstance(constant, IntegerConstant):
155 type_declaration = " int"
156 value = str(constant.value)
157 elif isinstance(constant, FloatConstant):
158 # Expand "const" to "constexpr", which is needed for static floats:
159 # https://stackoverflow.com/questions/9141950/
160 # Use "double", to match the VHDL type which is at least 64 bits
161 # (IEEE 1076-2008, 5.2.5.1).
162 type_declaration = "expr double"
163 # Note that casting a Python float to string guarantees full precision in the
164 # resulting string: https://stackoverflow.com/a/60026172
165 value = str(constant.value)
166 elif isinstance(constant, StringConstant):
167 # Expand "const" to "constexpr", which is needed for static string literals.
168 type_declaration = "expr auto"
169 value = f'"{constant.value}"'
170 elif isinstance(constant, UnsignedVectorConstant):
171 type_declaration = " auto"
172 value = f"{constant.prefix}{constant.value_without_separator}"
173 else:
174 raise ValueError(f"Got unexpected constant type. {constant}")
176 cpp_code += self.comment("Register constant.")
177 cpp_code += f" static const{type_declaration} {constant.name} = {value};\n"
179 if cpp_code:
180 cpp_code += "\n"
182 return cpp_code
184 def _num_registers(self) -> str:
185 # It is possible that we have constants but no registers
186 num_registers = 0
187 if self.register_list.register_objects:
188 num_registers = self.register_list.register_objects[-1].index + 1
190 cpp_code = self.comment("Number of registers within this register map.")
191 cpp_code += f" static const size_t num_registers = {num_registers}uL;\n\n"
192 return cpp_code
194 def _field_interface(
195 self, register: "Register", register_array: Optional["RegisterArray"]
196 ) -> str:
197 def function(return_type_name: str, signature: str) -> str:
198 return f" virtual {return_type_name} {signature} const = 0;\n"
200 cpp_code = ""
201 for field in register.fields:
202 field_description = self.field_description(
203 register=register, register_array=register_array, field=field
204 )
205 field_type_name = self._field_value_type_name(
206 register=register, register_array=register_array, field=field
207 )
209 if register.mode.software_can_read:
210 comment = [
211 f"Getter for the {field_description},",
212 "which will read register value over the register bus.",
213 ]
214 cpp_code += self.comment_block(text=comment)
216 signature = self._field_getter_function_signature(
217 register=register,
218 register_array=register_array,
219 field=field,
220 from_value=False,
221 )
222 cpp_code += function(return_type_name=field_type_name, signature=signature)
224 comment = [f"Getter for the {field_description},", "given a register value."]
225 cpp_code += self.comment_block(text=comment)
227 signature = self._field_getter_function_signature(
228 register=register,
229 register_array=register_array,
230 field=field,
231 from_value=True,
232 )
233 cpp_code += function(return_type_name=field_type_name, signature=signature)
235 if register.mode.software_can_write:
236 comment = [f"Setter for the {field_description},"]
237 if self.field_setter_should_read_modify_write(register=register):
238 comment.append("which will read-modify-write over the register bus.")
239 else:
240 comment.append(
241 "which will set the field to the given value, "
242 "and everything else to default."
243 )
245 cpp_code += self.comment_block(text=comment)
247 signature = self._field_setter_function_signature(
248 register=register,
249 register_array=register_array,
250 field=field,
251 from_value=False,
252 )
253 cpp_code += function(return_type_name="void", signature=signature)
255 comment = [
256 f"Setter for the {field_description},",
257 "given a register value, which will return an updated value.",
258 ]
259 cpp_code += self.comment_block(text=comment)
261 signature = self._field_setter_function_signature(
262 register=register,
263 register_array=register_array,
264 field=field,
265 from_value=True,
266 )
267 cpp_code += function(return_type_name="uint32_t", signature=signature)
269 cpp_code += "\n"
271 return cpp_code
273 @staticmethod
274 def _get_default_value(field: "RegisterField") -> str:
275 """
276 Get the field's default value formatted in a way suitable for C++ code.
277 """
278 if isinstance(field, (Bit, BitVector)):
279 return f"0b{field.default_value}"
281 if isinstance(field, Enumeration):
282 return f"Enumeration::{field.default_value.name}"
284 if isinstance(field, Integer):
285 return str(field.default_value)
287 raise ValueError(f'Unknown field type for "{field.name}" field: {type(field)}')
289 def _field_attributes(
290 self,
291 register: "Register",
292 register_array: Optional["RegisterArray"],
293 field: "RegisterField",
294 ) -> str:
295 field_description = self.field_description(
296 register=register, register_array=register_array, field=field
297 )
298 cpp_code = self.comment(f"Attributes for the {field_description}.", indent=2)
300 array_namespace = f"::{register_array.name}" if register_array else ""
301 namespace = f"{self.name}{array_namespace}::{register.name}::{field.name}"
303 cpp_code += f" namespace {namespace}\n"
304 cpp_code += " {\n"
305 cpp_code += f" static const auto width = {field.width};\n"
307 if isinstance(field, Enumeration):
308 name_value_pairs = [f"{element.name} = {element.value}," for element in field.elements]
309 separator = "\n "
310 cpp_code += f"""\
311 enum Enumeration
312 {{
313 {separator.join(name_value_pairs)}
314 }};
315"""
317 cpp_code += (
318 f" static const auto default_value = {self._get_default_value(field=field)};\n"
319 )
320 cpp_code += " }\n"
322 return cpp_code
324 def _register_array_attributes(self, register_array: "RegisterArray") -> str:
325 return f"""\
326 // Attributes for the "{register_array.name}" register array.
327 namespace {self.name}::{register_array.name}
328 {{
329 // Number of times the registers of the array are repeated.
330 static const auto array_length = {register_array.length};
331 }};
333"""