Coverage for hdl_registers/generator/cpp/implementation.py: 96%
244 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, Literal
14from hdl_registers.field.bit import Bit
15from hdl_registers.field.bit_vector import BitVector
16from hdl_registers.field.enumeration import Enumeration
17from hdl_registers.field.integer import Integer
18from hdl_registers.field.numerical_interpretation import (
19 Signed,
20 SignedFixedPoint,
21 Unsigned,
22 UnsignedFixedPoint,
23)
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
32 from hdl_registers.register_array import RegisterArray
35class CppImplementationGenerator(CppGeneratorCommon):
36 """
37 Generate a C++ class implementation.
38 See the :ref:`generator_cpp` article for usage details.
40 The class implementation will contain:
42 * for each register, implementation of getter and setter methods for reading/writing the
43 register as an ``uint``.
45 * for each field in each register, implementation of getter and setter methods for
46 reading/writing the field as its native type (enumeration, positive/negative int, etc.).
48 * The setter will read-modify-write the register to update only the specified field,
49 depending on the mode of the register.
50 """
52 __version__ = "2.0.2"
54 SHORT_DESCRIPTION = "C++ implementation"
56 DEFAULT_INDENTATION_LEVEL = 2
58 @property
59 def output_file(self) -> Path:
60 """
61 Result will be placed in this file.
62 """
63 return self.output_folder / f"{self.name}.cpp"
65 def get_code(
66 self,
67 **kwargs: Any, # noqa: ANN401, ARG002
68 ) -> str:
69 """
70 Get a complete C++ class implementation with all methods.
71 """
72 cpp_code = f"""\
73{self._get_macros()}\
74 {self._class_name}::{self._constructor_signature()}
75 : m_registers(reinterpret_cast<volatile uint32_t *>(base_address)),
76 m_assertion_handler(assertion_handler)
77 {
78 // Empty
79 }
80"""
82 separator = self.get_separator_line()
83 for register, register_array in self.iterate_registers():
84 cpp_code += self._get_register_heading(
85 register=register, register_array=register_array, separator=separator
86 )
88 methods_cpp: list[str] = []
90 if register.mode.software_can_read:
91 methods_cpp.append(
92 self._get_register_getter(register=register, register_array=register_array)
93 )
95 if register.fields:
96 # The main getter will perform type conversion.
97 # Provide a getter that returns the raw value also.
98 methods_cpp.append(
99 self._get_register_raw_getter(
100 register=register, register_array=register_array
101 )
102 )
104 for field in register.fields:
105 methods_cpp.append(
106 self._get_field_getter(
107 register=register, register_array=register_array, field=field
108 )
109 )
110 methods_cpp.append(
111 self._get_field_getter_from_raw(register, register_array, field=field)
112 )
114 if register.mode.software_can_write:
115 methods_cpp.append(
116 self._get_register_setter(register=register, register_array=register_array)
117 )
119 if register.fields:
120 # The main getter will perform type conversion.
121 # Provide a setter that takes a raw value also.
122 methods_cpp.append(
123 self._get_register_raw_setter(
124 register=register, register_array=register_array
125 )
126 )
128 for field in register.fields:
129 methods_cpp.append(
130 self._get_field_setter(
131 register=register, register_array=register_array, field=field
132 )
133 )
134 methods_cpp.append(
135 self._get_field_to_raw(register, register_array, field=field)
136 )
138 cpp_code += "\n".join(methods_cpp)
139 cpp_code += separator
141 cpp_code += "\n"
142 cpp_code_top = f'#include "include/{self.name}.h"\n\n'
144 return cpp_code_top + self._with_namespace(cpp_code)
146 def _get_macros(self) -> str:
147 file_name = self.output_file.name
149 def get_macro(name: str) -> str:
150 macro_name = f"_{name}_ASSERT_TRUE"
151 guard_name = f"NO_REGISTER_{name}_ASSERT"
152 name_space = " " * (38 - len(name))
153 file_name_space = " " * (44 - len(file_name))
154 base = """\
155#ifdef {guard_name}
157#define {macro_name}(expression, message) ((void)0)
159#else // Not {guard_name}.
161// This macro is called by the register code to check for runtime errors.
162#define {macro_name}(expression, message) {name_space}\\
163 {{ \\
164 if (!static_cast<bool>(expression)) {{ \\
165 std::ostringstream diagnostics; \\
166 diagnostics << "{file_name}:" << __LINE__ {file_name_space}\\
167 << ": " << message << "."; \\
168 std::string diagnostic_message = diagnostics.str(); \\
169 m_assertion_handler(&diagnostic_message); \\
170 }} \\
171 }}
173#endif // {guard_name}.
174"""
175 return base.format(
176 guard_name=guard_name,
177 macro_name=macro_name,
178 name=name,
179 name_space=name_space,
180 file_name=file_name,
181 file_name_space=file_name_space,
182 )
184 setter_assert = get_macro(name="SETTER")
185 getter_assert = get_macro(name="GETTER")
186 array_index_assert = get_macro(name="ARRAY_INDEX")
187 return f"""\
188{setter_assert}
189{getter_assert}
190{array_index_assert}
191"""
193 def _get_register_getter(self, register: Register, register_array: RegisterArray | None) -> str:
194 comment = self._get_getter_comment()
195 return_type = self._get_register_value_type(
196 register=register, register_array=register_array
197 )
198 signature = self._register_getter_signature(
199 register=register, register_array=register_array
200 )
202 if register.fields:
203 raw_value = self._get_read_raw_value_call(
204 register=register, register_array=register_array
205 )
207 fields = ""
208 values: list[str] = []
209 for field in register.fields:
210 field_type = self._get_field_value_type(
211 register=register, register_array=register_array, field=field
212 )
213 getter_name = self._field_getter_name(
214 register=register, register_array=register_array, field=field, from_raw=True
215 )
216 fields += f" const {field_type} {field.name}_value = {getter_name}(raw_value);\n"
217 values.append(f"{field.name}_value")
219 value = ", ".join(values)
220 result = f"""\
221{raw_value}
222{fields}
223 return { {value}} ;\
224"""
225 else:
226 raw_value = self._get_read_raw_value_code(
227 register=register, register_array=register_array
228 )
229 result = f"""\
230{raw_value}
231 return raw_value;\
232"""
234 return f"""\
235{comment}\
236 {return_type} {self._class_name}::{signature}
237 {
238{result}
239 }
240"""
242 def _get_read_raw_value_call(
243 self, register: Register, register_array: RegisterArray | None
244 ) -> str:
245 getter_name = self._register_getter_name(
246 register=register, register_array=register_array, raw=True
247 )
248 array_index = "array_index" if register_array else ""
249 return f"""\
250 const uint32_t raw_value = {getter_name}({array_index});
251"""
253 def _get_read_raw_value_code(
254 self,
255 register: Register,
256 register_array: RegisterArray | None,
257 include_index: bool = True,
258 ) -> str:
259 index = (
260 self._get_index(register=register, register_array=register_array)
261 if include_index
262 else ""
263 )
264 return f"""\
265{index}\
266 const uint32_t raw_value = m_registers[index];
267"""
269 def _get_index(self, register: Register, register_array: RegisterArray | None) -> str:
270 if register_array:
271 checker = f"""\
272 _ARRAY_INDEX_ASSERT_TRUE(
273 array_index < {self.name}::{register_array.name}::array_length,
274 "Got '{register_array.name}' array index out of range: " << array_index
275 );
276"""
277 index = (
278 f"{register_array.base_index} "
279 f"+ array_index * {len(register_array.registers)} + {register.index}"
280 )
281 else:
282 checker = ""
283 index = str(register.index)
285 return f"""\
286{checker}\
287 const size_t index = {index};
288"""
290 def _get_register_raw_getter(
291 self, register: Register, register_array: RegisterArray | None
292 ) -> str:
293 comment = self._get_getter_comment(raw=True)
294 signature = self._register_getter_signature(
295 register=register, register_array=register_array, raw=True
296 )
297 raw_value = self._get_read_raw_value_code(register=register, register_array=register_array)
299 return f"""\
300{comment}\
301 uint32_t {self._class_name}::{signature}
302 {
303{raw_value}
304 return raw_value;
305 }
306"""
308 def _get_field_getter(
309 self, register: Register, register_array: RegisterArray | None, field: RegisterField
310 ) -> str:
311 comment = self._get_getter_comment(field=field)
312 field_type = self._get_field_value_type(
313 register=register, register_array=register_array, field=field
314 )
315 signature = self._field_getter_signature(
316 register=register,
317 register_array=register_array,
318 field=field,
319 from_raw=False,
320 )
321 raw_value = self._get_read_raw_value_call(register=register, register_array=register_array)
322 from_raw_name = self._field_getter_name(
323 register=register, register_array=register_array, field=field, from_raw=True
324 )
326 return f"""\
327{comment}\
328 {field_type} {self._class_name}::{signature}
329 {
330{raw_value}
331 return {from_raw_name}(raw_value);
332 }
333"""
335 def _get_field_getter_from_raw(
336 self, register: Register, register_array: RegisterArray | None, field: RegisterField
337 ) -> str:
338 namespace = self._get_namespace(
339 register=register, register_array=register_array, field=field
340 )
341 comment = self._get_from_raw_comment(field=field)
342 field_type = self._get_field_value_type(
343 register=register, register_array=register_array, field=field
344 )
345 signature = self._field_getter_signature(
346 register=register, register_array=register_array, field=field, from_raw=True
347 )
348 cast = self._get_from_raw_cast(field=field, field_type=field_type)
349 checker = self._get_field_checker(field=field, setter_or_getter="getter")
351 return f"""\
352{comment}\
353 {field_type} {self._class_name}::{signature}
354 {
355 const uint32_t result_masked = register_value & {namespace}mask_shifted;
356 const uint32_t result_shifted = result_masked >> {namespace}shift;
358{cast}
359{checker}\
360 return field_value;
361 }
362"""
364 def _get_from_raw_cast(self, field: RegisterField, field_type: str) -> str: # noqa: PLR0911
365 no_cast = """\
366 // No casting needed.
367 const uint32_t field_value = result_shifted;
368"""
370 if isinstance(field, Bit):
371 return """\
372 // Convert to the result type.
373 const bool field_value = static_cast<bool>(result_shifted);
374"""
376 if isinstance(field, BitVector):
377 if isinstance(field.numerical_interpretation, Unsigned):
378 return no_cast
380 if isinstance(field.numerical_interpretation, Signed):
381 return self._get_field_to_negative(field=field)
383 if isinstance(field.numerical_interpretation, UnsignedFixedPoint):
384 return self._get_field_to_real(
385 field=field, field_type=field_type, variable="result_shifted"
386 )
388 if isinstance(field.numerical_interpretation, SignedFixedPoint):
389 return (
390 self._get_field_to_negative(field=field, variable="result_negative")
391 + "\n"
392 + self._get_field_to_real(
393 field=field, field_type=field_type, variable="result_negative"
394 )
395 )
397 raise TypeError(
398 f"Got unexpected numerical interpretation type: {field.numerical_interpretation}"
399 )
401 if isinstance(field, Enumeration):
402 return f"""\
403 // "Cast" to the enum type.
404 const auto field_value = {field_type}(result_shifted);
405"""
407 if isinstance(field, Integer):
408 if field.is_signed:
409 return self._get_field_to_negative(field=field)
411 return no_cast
413 raise TypeError(f"Got unexpected field type: {field}")
415 def _get_field_to_negative(self, field: Integer, variable: str = "field_value") -> str:
416 # Note that the shift result has maximum value of '1 << 31', which always
417 # fits in a 32-bit unsigned integer.
418 return f"""\
419 const uint32_t sign_bit_mask = 1uL << {field.width - 1};
420 int32_t {variable};
421 if (result_shifted & sign_bit_mask)
422 {
423 // Value is to be interpreted as negative.
424 // This can be seen as a sign extension from the width of the field to the width of
425 // the result variable.
426 {variable} = result_shifted - 2 * sign_bit_mask;
427 }
428 else
429 {
430 // Value is positive.
431 {variable} = result_shifted;
432 }
433"""
435 def _get_field_to_real(self, field: BitVector, field_type: str, variable: str) -> str:
436 divisor = 2**field.numerical_interpretation.fraction_bit_width
437 return f"""\
438 const {field_type} result_real = static_cast<{field_type}>({variable});
439 const {field_type} field_value = result_real / {divisor};
440"""
442 def _get_field_checker(
443 self, field: RegisterField, setter_or_getter: Literal["setter", "getter"]
444 ) -> str:
445 if isinstance(field, Bit):
446 # Values is represented as boolean in C++, and in HDL it is a single bit.
447 # Can not be out of range in either direction.
448 return ""
450 if isinstance(field, BitVector):
451 if setter_or_getter == "getter":
452 # HDL can by definition not use bits outside the field.
453 return ""
455 # If the maximum value is the natural maximum value of the field, this check is moot.
456 # Add guard for this in the future.
457 # https://github.com/hdl-registers/hdl-registers/issues/169
458 max_value = field.numerical_interpretation.max_value
460 # Minimum value check would be moot if unsigned, since the C++ type used will
461 # be 'uint32_t'.
462 min_value = (
463 None
464 if isinstance(field.numerical_interpretation, Unsigned)
465 else field.numerical_interpretation.min_value
466 )
468 return self._get_checker(
469 field_name=field.name,
470 setter_or_getter=setter_or_getter,
471 min_value=min_value,
472 max_value=max_value,
473 )
475 if isinstance(field, Enumeration):
476 # There should be a check that getter value is within range.
477 # Unless, the maximum value is the natural maximum value of the field.
478 # https://github.com/hdl-registers/hdl-registers/issues/169
479 return ""
481 if isinstance(field, Integer):
482 # If the maximum value is the natural maximum value of the field, this check is moot.
483 # But only for getters, the setter can still be out of range
484 # unless the field width is 32.
485 # Add guard for this in the future.
486 # https://github.com/hdl-registers/hdl-registers/issues/169
487 max_value = field.max_value
489 # Minimum value check would be moot if unsigned and minimum value zero, since the C++
490 # type used will be 'uint32_t'.
491 # Note that there is the case where the field is unsigned, but has a minimum allowed
492 # value that is greater than zero.
493 # In that case, the minimum value check is still needed.
494 #
495 # If the minimum value is the natural minimum value of the field, signed or unsigned,
496 # this check is moot.
497 # Add guard for this in the future.
498 # https://github.com/hdl-registers/hdl-registers/issues/169
499 min_value = field.min_value if field.is_signed or field.min_value != 0 else None
501 return self._get_checker(
502 field_name=field.name,
503 setter_or_getter=setter_or_getter,
504 min_value=min_value,
505 max_value=max_value,
506 )
508 raise TypeError(f"Got unexpected field type: {field}")
510 def _get_checker(
511 self,
512 field_name: str,
513 setter_or_getter: Literal["setter", "getter"],
514 min_value: str | None = None,
515 max_value: str | None = None,
516 ) -> str:
517 checks: list[str] = []
518 if min_value is not None:
519 checks.append(f"field_value >= {min_value}")
520 if max_value is not None:
521 checks.append(f"field_value <= {max_value}")
522 check = " && ".join(checks)
524 if not check:
525 return ""
527 macro = f"_{setter_or_getter.upper()}_ASSERT_TRUE"
529 return f"""\
530 {macro}(
531 {check},
532 "Got '{field_name}' value out of range: " << field_value
533 );
535"""
537 def _get_register_setter(self, register: Register, register_array: RegisterArray | None) -> str:
538 comment = self._get_setter_comment(register=register)
539 signature = self._register_setter_signature(
540 register=register, register_array=register_array
541 )
543 if register.fields:
544 cast = ""
545 values: list[str] = []
546 for field in register.fields:
547 to_raw_name = self._field_to_raw_name(
548 register=register, register_array=register_array, field=field
549 )
550 cast += (
551 f" const uint32_t {field.name}_value = "
552 f"{to_raw_name}(register_value.{field.name});\n"
553 )
554 values.append(f"{field.name}_value")
556 value = " | ".join(values)
557 cast += f"""\
558 const uint32_t raw_value = {value};
560"""
561 set_raw_value = self._get_write_raw_value_call(
562 register=register, register_array=register_array
563 )
564 else:
565 cast = ""
566 set_raw_value = self._get_write_raw_value_code(
567 register=register, register_array=register_array
568 )
570 return f"""\
571{comment}\
572 void {self._class_name}::{signature}
573 {
574{cast}\
575{set_raw_value}\
576 }
577"""
579 def _get_write_raw_value_call(
580 self, register: Register, register_array: RegisterArray | None
581 ) -> str:
582 setter_name = self._register_setter_name(
583 register=register, register_array=register_array, raw=True
584 )
585 array_index = "array_index, " if register_array else ""
586 return f"""\
587 {setter_name}({array_index}raw_value);
588"""
590 def _get_write_raw_value_code(
591 self,
592 register: Register,
593 register_array: RegisterArray | None,
594 include_index: bool = True,
595 ) -> str:
596 index = (
597 self._get_index(register=register, register_array=register_array)
598 if include_index
599 else ""
600 )
601 return f"""\
602{index}\
603 m_registers[index] = register_value;
604"""
606 def _get_register_raw_setter(
607 self, register: Register, register_array: RegisterArray | None
608 ) -> str:
609 comment = self._get_setter_comment(register=register, raw=True)
610 signature = self._register_setter_signature(
611 register=register, register_array=register_array, raw=True
612 )
613 set_raw_value = self._get_write_raw_value_code(
614 register=register, register_array=register_array
615 )
617 return f"""\
618{comment}\
619 void {self._class_name}::{signature}
620 {
621{set_raw_value}\
622 }
623"""
625 def _get_field_setter(
626 self, register: Register, register_array: RegisterArray | None, field: RegisterField
627 ) -> str:
628 comment = self._get_setter_comment(register=register, field=field)
629 signature = self._field_setter_signature(
630 register=register,
631 register_array=register_array,
632 field=field,
633 from_raw=False,
634 )
635 index = self._get_index(register=register, register_array=register_array)
637 if self.field_setter_should_read_modify_write(register=register):
638 namespace = self._get_namespace(
639 register=register, register_array=register_array, field=field
640 )
641 raw_value = self._get_read_raw_value_code(
642 register=register, register_array=register_array, include_index=False
643 )
644 base_value = f"""\
645{raw_value}\
646 const uint32_t mask_shifted_inverse = ~{namespace}mask_shifted;
647 const uint32_t base_value = raw_value & mask_shifted_inverse;
648"""
650 else:
651 # The '0' is needed in case there are no other fields than the one we are writing.
652 default_values = ["0"]
653 for loop_field in register.fields:
654 if loop_field.name != field.name:
655 namespace = self._get_namespace(
656 register=register, register_array=register_array, field=loop_field
657 )
658 default_values.append(f"{namespace}default_value_raw")
660 default_value = " | ".join(default_values)
661 base_value = f"""\
662 const uint32_t base_value = {default_value};
663"""
665 to_raw_name = self._field_to_raw_name(
666 register=register, register_array=register_array, field=field
667 )
668 write_raw_value = self._get_write_raw_value_code(
669 register=register, register_array=register_array, include_index=False
670 )
672 return f"""\
673{comment}\
674 void {self._class_name}::{signature}
675 {
676{index}
677{base_value}\
679 const uint32_t field_value_raw = {to_raw_name}(field_value);
680 const uint32_t register_value = base_value | field_value_raw;
682{write_raw_value}\
683 }
684"""
686 def _get_field_to_raw(
687 self, register: Register, register_array: RegisterArray | None, field: RegisterField
688 ) -> str:
689 comment = self._get_to_raw_comment(field=field)
690 signature = self._field_to_raw_signature(
691 register=register, register_array=register_array, field=field
692 )
693 namespace = self._get_namespace(
694 register=register, register_array=register_array, field=field
695 )
696 checker = self._get_field_checker(field=field, setter_or_getter="setter")
697 cast, variable = self._get_to_raw_cast(
698 register=register, register_array=register_array, field=field
699 )
701 return f"""\
702{comment}\
703 uint32_t {self._class_name}::{signature}
704 {
705{checker}\
706{cast}\
707 const uint32_t field_value_shifted = {variable} << {namespace}shift;
709 return field_value_shifted;
710 }
711"""
713 def _get_to_raw_cast( # noqa: C901, PLR0911
714 self, register: Register, register_array: RegisterArray | None, field: RegisterField
715 ) -> tuple[str, str]:
716 # Useful for values that are in an unsigned integer representation, but not
717 # explicitly 'uint32_t'.
718 cast_to_uint32 = """\
719 const uint32_t field_value_casted = static_cast<uint32_t>(field_value);
720"""
722 def _get_reinterpret_as_uint32(variable: str = "field_value") -> str:
723 # Reinterpret as unsigned and then mask out all the sign extended bits above
724 # the field. Useful for signed integer values.
725 # Signed to unsigned static cast produces no change in the bit pattern
726 # https://stackoverflow.com/a/1751368
727 namespace = self._get_namespace(
728 register=register, register_array=register_array, field=field
729 )
730 return f"""\
731 const uint32_t field_value_unsigned = (uint32_t){variable};
732 const uint32_t field_value_masked = field_value_unsigned & {namespace}mask_at_base;
733"""
735 if isinstance(field, Bit):
736 return (cast_to_uint32, "field_value_casted")
738 if isinstance(field, BitVector):
739 if isinstance(field.numerical_interpretation, Unsigned):
740 return ("", "field_value")
742 if isinstance(field.numerical_interpretation, Signed):
743 return (_get_reinterpret_as_uint32(), "field_value_masked")
745 value_type = self._get_field_value_type(
746 register=register, register_array=register_array, field=field
747 )
748 multiplier = 2**field.numerical_interpretation.fraction_bit_width
750 fixed_type = "int32_t" if field.numerical_interpretation.is_signed else "uint32_t"
752 # Static cast implies truncation, which should guarantee that the
753 # fixed-point representation fits in the field.
754 to_fixed = f"""\
755 const {value_type} field_value_multiplied = field_value * {multiplier};
756 const {fixed_type} field_value_fixed = static_cast<{fixed_type}>(field_value_multiplied);
757"""
759 if isinstance(field.numerical_interpretation, UnsignedFixedPoint):
760 return (to_fixed, "field_value_fixed")
762 if isinstance(field.numerical_interpretation, SignedFixedPoint):
763 return (
764 to_fixed + _get_reinterpret_as_uint32(variable="field_value_fixed"),
765 "field_value_masked",
766 )
768 raise TypeError(
769 f"Got unexpected numerical interpretation: {field.numerical_interpretation}"
770 )
772 if isinstance(field, Enumeration):
773 return (cast_to_uint32, "field_value_casted")
775 if isinstance(field, Integer):
776 if field.is_signed:
777 return (_get_reinterpret_as_uint32(), "field_value_masked")
779 return ("", "field_value")
781 raise TypeError(f"Got unexpected field type: {field}")