Coverage for hdl_registers/register_cpp_generator.py: 99%
358 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-29 22:03 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-29 22:03 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the hdl_registers project, a HDL register generator fast enough to be run
5# in real time.
6# https://hdl-registers.com
7# https://gitlab.com/hdl_registers/hdl_registers
8# --------------------------------------------------------------------------------------------------
10# Local folder libraries
11from .register import REGISTER_MODES
12from .register_array import RegisterArray
13from .register_code_generator import RegisterCodeGenerator
16class RegisterCppGenerator:
17 """
18 Generate a C++ class with register definitions and methods.
20 There is only a very limited unit test of this class that checks the generated code.
21 It is instead functionally tested in the file test_register_compilation.py.
22 That test generates C++ code from an example register set, compiles it and performs some
23 run-time assertions in a C++ program.
24 That test is considered more meaningful and exhaustive than a unit test would be.
25 """
27 def __init__(self, module_name, generated_info):
28 """
29 Arguments:
30 module_name (str): The name of the register map.
31 generated_info (list(str)): Will be placed in the file headers.
32 """
33 self.module_name = module_name
34 self.generated_info = generated_info
36 def get_interface(self, register_objects, constants):
37 """
38 Get a complete C++ interface class header with all constant values and the signatures of
39 all methods.
41 Arguments:
42 register_objects (list): Register arrays and registers to be included.
43 constants (list(Constant)): Constants to be included.
45 Returns:
46 str: C++ code.
47 """
48 generator = InterfaceGenerator(
49 module_name=self.module_name, generated_info=self.generated_info
50 )
51 return generator.get_interface(register_objects=register_objects, constants=constants)
53 def get_header(self, register_objects):
54 """
55 Get a complete C++ class header for the implementation of all methods.
57 Arguments:
58 register_objects (list): Register arrays and registers to be included.
60 Returns:
61 str: C++ code.
62 """
63 generator = HeaderGenerator(
64 module_name=self.module_name, generated_info=self.generated_info
65 )
66 return generator.get_header(register_objects=register_objects)
68 def get_implementation(self, register_objects):
69 """
70 Get a complete C++ class implementation with all methods.
72 Arguments:
73 register_objects (list): Register arrays and registers to be included.
75 Returns:
76 str: C++ code.
77 """
78 generator = ImplementationGenerator(
79 module_name=self.module_name, generated_info=self.generated_info
80 )
81 return generator.get_implementation(register_objects=register_objects)
84class CommonGenerator(RegisterCodeGenerator):
85 """
86 Class with common methods for generating C++ code.
87 Do not use this directly, should use :class:`.RegisterCppGenerator`:
88 """
90 def __init__(self, module_name, generated_info):
91 self.module_name = module_name
93 self._class_name = self._to_pascal_case(module_name)
94 self._file_header = "".join([self._comment(header_line) for header_line in generated_info])
96 @staticmethod
97 def _get_separator_line(indentation):
98 """
99 Get a separator line, e.g. " // ---------------------------------\n"
100 """
101 result = " " * indentation + "// "
102 num_dash = 80 - len(result)
103 result += "-" * num_dash
104 result += "\n"
105 return result
107 @staticmethod
108 def _comment(comment, indentation=0):
109 indent = " " * indentation
110 return f"{indent}// {comment}\n"
112 @staticmethod
113 def _with_namespace(cpp_code_body):
114 cpp_code = "namespace fpga_regs\n"
115 cpp_code += "{\n\n"
116 cpp_code += f"{cpp_code_body}"
117 cpp_code += "\n} /* namespace fpga_regs */\n"
118 return cpp_code
120 def _constructor_signature(self):
121 return f"{self._class_name}(volatile uint8_t *base_address)"
123 @staticmethod
124 def _array_length_constant_name(register_array):
125 return f"{register_array.name}_array_length"
127 @staticmethod
128 def _get_methods_description(register, register_array):
129 result = f'Methods for the "{register.name}" register'
130 if register_array:
131 result += f' within the "{register_array.name}" register array'
132 result += "."
134 return result
136 @staticmethod
137 def _field_value_type_name():
138 """
139 We represent all fields as uint32_t. In the future we might support enum types.
140 """
141 return "uint32_t"
143 @staticmethod
144 def _register_getter_function_name(register, register_array):
145 result = "get"
147 if register_array:
148 result += f"_{register_array.name}"
150 result += f"_{register.name}"
152 return result
154 def _register_getter_function_signature(self, register, register_array):
155 function_name = self._register_getter_function_name(
156 register=register, register_array=register_array
157 )
158 result = f"{function_name}("
160 if register_array:
161 result += "size_t array_index"
163 result += ")"
165 return result
167 @staticmethod
168 def _field_getter_function_name(register, register_array, field, from_value):
169 result = "get"
171 if register_array:
172 result += f"_{register_array.name}"
174 result += f"_{register.name}_{field.name}"
176 if from_value:
177 result += "_from_value"
179 return result
181 def _field_getter_function_signature(self, register, register_array, field, from_value):
182 function_name = self._field_getter_function_name(
183 register=register, register_array=register_array, field=field, from_value=from_value
184 )
185 result = f"{function_name}("
187 if from_value:
188 # Value is supplied by user
189 result += "uint32_t register_value"
190 elif register_array:
191 # Value shall be read from bus, in which case we need to know array index if this
192 # is an array
193 result += "size_t array_index"
195 result += ")"
197 return result
199 @staticmethod
200 def _get_shift_and_mask(field):
201 cpp_code = f" const uint32_t shift = {field.base_index}uL;\n"
202 cpp_code += f' const uint32_t mask_at_base = 0b{"1" * field.width}uL;\n'
203 cpp_code += " const uint32_t mask_shifted = mask_at_base << shift;\n"
204 return cpp_code
206 @staticmethod
207 def _register_setter_function_name(register, register_array):
208 result = "set"
210 if register_array:
211 result += f"_{register_array.name}"
213 result += f"_{register.name}"
215 return result
217 def _register_setter_function_signature(self, register, register_array):
218 function_name = self._register_setter_function_name(
219 register=register, register_array=register_array
220 )
221 result = f"{function_name}("
223 if register_array:
224 result += "size_t array_index, "
226 result += "uint32_t register_value)"
228 return result
230 @staticmethod
231 def _field_setter_function_name(register, register_array, field, from_value):
232 result = "set"
234 if register_array:
235 result += f"_{register_array.name}"
237 result += f"_{register.name}_{field.name}"
239 if from_value:
240 result += "_from_value"
242 return result
244 def _field_setter_function_signature(self, register, register_array, field, from_value):
245 function_name = self._field_setter_function_name(
246 register=register, register_array=register_array, field=field, from_value=from_value
247 )
248 result = f"{function_name}("
250 if from_value:
251 # Current register value is supplied by user
252 result += "uint32_t register_value, "
253 elif register_array:
254 # Current register value shall be read from bus, in which case we need to know array
255 # index if this is an array
256 result += "size_t array_index, "
258 result += f"{self._field_value_type_name()} field_value)"
260 return result
263class InterfaceGenerator(CommonGenerator):
264 """
265 Class to generate a C++ interface header.
266 Do not use this directly, should use :class:`.RegisterCppGenerator`:
267 """
269 def get_interface(self, register_objects, constants):
270 """
271 Get a complete C++ interface class header. See :meth:`.RegisterCppGenerator.get_interface`
272 for more details.
273 """
274 cpp_code = f"class I{self._class_name}\n"
275 cpp_code += "{\n"
276 cpp_code += "public:\n"
278 cpp_code += self._constants(constants=constants)
280 cpp_code += self._num_registers(register_objects)
282 for register_object in register_objects:
283 if isinstance(register_object, RegisterArray):
284 cpp_code += self._comment(
285 f'Length of the "{register_object.name}" register array', indentation=2
286 )
287 constant_name = self._array_length_constant_name(register_object)
288 cpp_code += (
289 f" static const size_t {constant_name} = {register_object.length}uL;\n\n"
290 )
292 cpp_code += f" virtual ~I{self._class_name}() {{ }}\n"
294 for register, register_array in self._iterate_registers(register_objects):
295 cpp_code += f"\n{self._get_separator_line(indentation=2)}"
297 description = self._get_methods_description(
298 register=register, register_array=register_array
299 )
300 description += f' Mode "{REGISTER_MODES[register.mode].mode_readable}".'
302 cpp_code += self._comment(comment=description, indentation=2)
303 cpp_code += "\n"
305 if register.is_bus_readable:
306 cpp_code += self._comment(
307 "Getter that will read the whole register's value over the register bus.",
308 indentation=2,
309 )
310 signature = self._register_getter_function_signature(
311 register=register, register_array=register_array
312 )
313 cpp_code += f" virtual uint32_t {signature} const = 0;\n\n"
315 if register.is_bus_writeable:
316 cpp_code += self._comment(
317 "Setter that will write the whole register's value over the register bus.",
318 indentation=2,
319 )
320 signature = self._register_setter_function_signature(
321 register=register, register_array=register_array
322 )
323 cpp_code += f" virtual void {signature} const = 0;\n\n"
325 cpp_code += self._field_interface(register, register_array)
327 cpp_code += "};\n"
329 cpp_code_top = f"""\
330{self._file_header}
331#pragma once
333#include <cassert>
334#include <cstdint>
335#include <cstdlib>
337"""
338 return cpp_code_top + self._with_namespace(cpp_code)
340 def _constants(self, constants):
341 cpp_code = ""
343 for constant in constants:
344 if constant.is_boolean:
345 type_declaration = " bool"
346 value = str(constant.value).lower()
347 elif constant.is_integer:
348 type_declaration = " int"
349 value = str(constant.value)
350 elif constant.is_float:
351 # Expand "const" to "constexpr", which is needed for static floats. See
352 # https://stackoverflow.com/questions/9141950/
353 # initializing-const-member-within-class-declaration-in-c
354 type_declaration = "expr float"
355 value = str(constant.value)
356 else:
357 raise ValueError(f"Got unexpected constant type. {constant}")
359 cpp_code += self._comment("Register constant.", indentation=2)
360 cpp_code += f" static const{type_declaration} {constant.name} = {value};\n"
362 if constants:
363 cpp_code += "\n"
365 return cpp_code
367 def _num_registers(self, register_objects):
368 # It is possible that we have constants but no registers
369 num_registers = 0
370 if register_objects:
371 num_registers = register_objects[-1].index + 1
373 cpp_code = self._comment("Number of registers within this register map.", indentation=2)
374 cpp_code += f" static const size_t num_registers = {num_registers}uL;\n\n"
375 return cpp_code
377 def _field_interface(self, register, register_array):
378 def function(return_type_name, signature):
379 return f" virtual {return_type_name} {signature} const = 0;\n"
381 cpp_code = ""
382 for field in register.fields:
383 register_description = f'in the "{register.name}" register'
384 if register_array is not None:
385 register_description += f' within the "{register_array.name}" register array'
387 if register.is_bus_readable:
388 comment = (
389 f'Getter for the "{field.name}" field {register_description},\n'
390 "which will read register value over the register bus."
391 )
393 cpp_code += self._comment_block(text=comment, indentation=2)
395 signature = self._field_getter_function_signature(
396 register=register,
397 register_array=register_array,
398 field=field,
399 from_value=False,
400 )
401 cpp_code += function(
402 return_type_name=self._field_value_type_name(), signature=signature
403 )
405 comment = (
406 f'Getter for the "{field.name}" field {register_description},\n'
407 "given the register's current value."
408 )
409 cpp_code += self._comment_block(text=comment, indentation=2)
411 signature = self._field_getter_function_signature(
412 register=register,
413 register_array=register_array,
414 field=field,
415 from_value=True,
416 )
417 cpp_code += function(
418 return_type_name=self._field_value_type_name(), signature=signature
419 )
421 if register.is_bus_writeable:
422 comment = f'Setter for the "{field.name}" field {register_description},\n'
423 if register.mode == "r_w":
424 comment += "which will read-modify-write over the register bus."
425 elif register.mode in ["w", "wpulse", "r_wpulse"]:
426 comment += (
427 "which will set the field to the given value, and all other bits to zero."
428 )
429 else:
430 raise ValueError(f"Can not handle this register's mode: {register}")
432 cpp_code += self._comment_block(text=comment, indentation=2)
434 signature = self._field_setter_function_signature(
435 register=register,
436 register_array=register_array,
437 field=field,
438 from_value=False,
439 )
440 cpp_code += function(return_type_name="void", signature=signature)
442 comment = (
443 f'Setter for the "{field.name}" field {register_description},\n'
444 "given the register's current value, which will return an updated value."
445 )
446 cpp_code += self._comment_block(text=comment, indentation=2)
448 signature = self._field_setter_function_signature(
449 register=register,
450 register_array=register_array,
451 field=field,
452 from_value=True,
453 )
454 cpp_code += function(return_type_name="uint32_t", signature=signature)
456 cpp_code += "\n"
458 return cpp_code
461class HeaderGenerator(CommonGenerator):
462 """
463 Class to generate a C++ header.
464 Do not use this directly, should use :class:`.RegisterCppGenerator`:
465 """
467 def get_header(self, register_objects):
468 """
469 Get a complete C++ class header for the implementation of all methods.
470 See :meth:`.RegisterCppGenerator.get_header` for more details.
471 """
472 cpp_code = f"class {self._class_name} : public I{self._class_name}\n"
473 cpp_code += "{\n"
475 cpp_code += "private:\n"
476 cpp_code += " volatile uint32_t *m_registers;\n\n"
478 cpp_code += "public:\n"
479 cpp_code += f" {self._constructor_signature()};\n\n"
480 cpp_code += f" virtual ~{self._class_name}() {{ }}\n"
482 def function(return_type_name, signature):
483 return f" virtual {return_type_name} {signature} const override;\n"
485 for register, register_array in self._iterate_registers(register_objects):
486 cpp_code += f"\n{self._get_separator_line(indentation=2)}"
488 description = self._get_methods_description(
489 register=register, register_array=register_array
490 )
491 description += " See interface header for documentation."
492 cpp_code += self._comment(comment=description, indentation=2)
494 if register.is_bus_readable:
495 signature = self._register_getter_function_signature(
496 register=register,
497 register_array=register_array,
498 )
499 cpp_code += function(return_type_name="uint32_t", signature=signature)
501 for field in register.fields:
502 signature = self._field_getter_function_signature(
503 register=register,
504 register_array=register_array,
505 field=field,
506 from_value=False,
507 )
508 cpp_code += function(
509 return_type_name=self._field_value_type_name(), signature=signature
510 )
512 signature = self._field_getter_function_signature(
513 register=register,
514 register_array=register_array,
515 field=field,
516 from_value=True,
517 )
518 cpp_code += function(
519 return_type_name=self._field_value_type_name(), signature=signature
520 )
522 if register.is_bus_writeable:
523 signature = self._register_setter_function_signature(
524 register=register, register_array=register_array
525 )
527 cpp_code += function(return_type_name="void", signature=signature)
529 for field in register.fields:
530 signature = self._field_setter_function_signature(
531 register=register,
532 register_array=register_array,
533 field=field,
534 from_value=False,
535 )
536 cpp_code += function(return_type_name="void", signature=signature)
538 signature = self._field_setter_function_signature(
539 register=register,
540 register_array=register_array,
541 field=field,
542 from_value=True,
543 )
544 cpp_code += function(return_type_name="uint32_t", signature=signature)
546 cpp_code += "};\n"
548 cpp_code_top = f"""\
549{self._file_header}
550#pragma once
552#include "i_{self.module_name}.h"
554"""
555 return cpp_code_top + self._with_namespace(cpp_code)
558class ImplementationGenerator(CommonGenerator):
559 """
560 Class to generate a C++ implementation.
561 Do not use this directly, should use :class:`.RegisterCppGenerator`:
562 """
564 def get_implementation(self, register_objects):
565 """
566 Get a complete C++ class implementation with all methods.
567 See :meth:`.RegisterCppGenerator.get_implementation` for more details.
568 """
569 cpp_code = f"{self._class_name}::{self._constructor_signature()}\n"
570 cpp_code += " : m_registers(reinterpret_cast<volatile uint32_t *>(base_address))\n"
571 cpp_code += "{\n"
572 cpp_code += " // Empty\n"
573 cpp_code += "}\n\n"
575 for register, register_array in self._iterate_registers(register_objects):
576 cpp_code += f"\n{self._get_separator_line(indentation=0)}"
578 description = self._get_methods_description(
579 register=register, register_array=register_array
580 )
581 description += " See interface header for documentation.\n"
582 cpp_code += self._comment(comment=description, indentation=0)
584 if register.is_bus_readable:
585 cpp_code += self._register_getter_function(register, register_array)
587 for field in register.fields:
588 cpp_code += self._field_getter_function(register, register_array, field=field)
589 cpp_code += self._field_getter_function_from_value(
590 register, register_array, field=field
591 )
593 if register.is_bus_writeable:
594 cpp_code += self._register_setter_function(register, register_array)
596 for field in register.fields:
597 cpp_code += self._field_setter_function(register, register_array, field=field)
598 cpp_code += self._field_setter_function_from_value(
599 register, register_array, field=field
600 )
602 cpp_code_top = f"{self._file_header}\n"
603 cpp_code_top += f'#include "include/{self.module_name}.h"\n\n'
605 return cpp_code_top + self._with_namespace(cpp_code)
607 def _register_setter_function(self, register, register_array):
608 signature = self._register_setter_function_signature(
609 register=register, register_array=register_array
610 )
611 cpp_code = f"void {self._class_name}::{signature} const\n"
612 cpp_code += "{\n"
614 if register_array:
615 cpp_code += (
616 f" assert(array_index < {self._array_length_constant_name(register_array)});\n"
617 )
618 cpp_code += (
619 f" const size_t index = {register_array.base_index} "
620 f"+ array_index * {len(register_array.registers)} + {register.index};\n"
621 )
622 else:
623 cpp_code += f" const size_t index = {register.index};\n"
625 cpp_code += " m_registers[index] = register_value;\n"
626 cpp_code += "}\n\n"
627 return cpp_code
629 def _field_setter_function(self, register, register_array, field):
630 signature = self._field_setter_function_signature(
631 register=register, register_array=register_array, field=field, from_value=False
632 )
634 cpp_code = f"void {self._class_name}::{signature} const\n"
635 cpp_code += "{\n"
637 if register.mode == "r_w":
638 register_getter_function_name = self._register_getter_function_name(
639 register=register, register_array=register_array
640 )
641 cpp_code += self._comment(
642 "Get the current value of any other fields by reading register on the bus.",
643 indentation=2,
644 )
645 current_register_value = f"{register_getter_function_name}("
646 if register_array:
647 current_register_value += "array_index"
648 current_register_value += ")"
649 elif register.mode in ["w", "wpulse", "r_wpulse"]:
650 cpp_code += self._comment_block(
651 "This register type's currently written value can not be read back.\n"
652 "Hence set all other bits to zero when writing the value.",
653 indentation=2,
654 )
655 current_register_value = 0
656 else:
657 raise ValueError(f"Can not handle this register's mode: {register}")
659 cpp_code += f" const uint32_t current_register_value = {current_register_value};\n"
661 signature = self._field_setter_function_name(
662 register=register, register_array=register_array, field=field, from_value=True
663 )
664 cpp_code += (
665 " const uint32_t result_register_value = "
666 f" {signature}(current_register_value, field_value);\n"
667 )
669 register_setter_function_name = self._register_setter_function_name(
670 register=register, register_array=register_array
671 )
672 cpp_code += f" {register_setter_function_name}("
673 if register_array:
674 cpp_code += "array_index, "
675 cpp_code += "result_register_value);\n"
677 cpp_code += "}\n\n"
679 return cpp_code
681 def _field_setter_function_from_value(self, register, register_array, field):
682 signature = self._field_setter_function_signature(
683 register=register, register_array=register_array, field=field, from_value=True
684 )
686 cpp_code = f"uint32_t {self._class_name}::{signature} const\n"
687 cpp_code += "{\n"
689 cpp_code += self._get_shift_and_mask(field=field)
690 cpp_code += "\n const uint32_t field_value_masked = field_value & mask_at_base;\n"
691 cpp_code += (
692 " const uint32_t field_value_masked_and_shifted = field_value_masked << shift;\n\n"
693 )
695 cpp_code += " const uint32_t mask_shifted_inverse = ~mask_shifted;\n"
696 cpp_code += (
697 " const uint32_t register_value_masked = register_value & mask_shifted_inverse;\n\n"
698 )
700 cpp_code += (
701 " const uint32_t result_register_value = "
702 "register_value_masked | field_value_masked_and_shifted;\n\n"
703 )
704 cpp_code += " return result_register_value;\n"
706 cpp_code += "}\n\n"
708 return cpp_code
710 def _register_getter_function(self, register, register_array):
711 signature = self._register_getter_function_signature(
712 register=register, register_array=register_array
713 )
714 cpp_code = f"uint32_t {self._class_name}::{signature} const\n"
715 cpp_code += "{\n"
717 if register_array:
718 cpp_code += (
719 f" assert(array_index < {self._array_length_constant_name(register_array)});\n"
720 )
721 cpp_code += (
722 f" const size_t index = {register_array.base_index} "
723 f"+ array_index * {len(register_array.registers)} + {register.index};\n"
724 )
725 else:
726 cpp_code += f" const size_t index = {register.index};\n"
728 cpp_code += " const uint32_t result = m_registers[index];\n\n"
729 cpp_code += " return result;\n"
730 cpp_code += "}\n\n"
731 return cpp_code
733 def _field_getter_function(self, register, register_array, field):
734 signature = self._field_getter_function_signature(
735 register=register, register_array=register_array, field=field, from_value=False
736 )
738 cpp_code = f"{self._field_value_type_name()} {self._class_name}::{signature} const\n"
739 cpp_code += "{\n"
741 register_getter_function_name = self._register_getter_function_name(
742 register=register, register_array=register_array
743 )
745 field_getter_from_value_function_name = self._field_getter_function_name(
746 register=register, register_array=register_array, field=field, from_value=True
747 )
749 cpp_code += f" const uint32_t register_value = {register_getter_function_name}("
750 if register_array:
751 cpp_code += "array_index"
752 cpp_code += ");\n"
754 cpp_code += (
755 f" const uint32_t result = {field_getter_from_value_function_name}(register_value);\n"
756 )
757 cpp_code += "\n return result;\n"
758 cpp_code += "}\n\n"
760 return cpp_code
762 def _field_getter_function_from_value(self, register, register_array, field):
763 signature = self._field_getter_function_signature(
764 register=register, register_array=register_array, field=field, from_value=True
765 )
767 cpp_code = f"{self._field_value_type_name()} {self._class_name}::{signature} const\n"
768 cpp_code += "{\n"
770 cpp_code += self._get_shift_and_mask(field=field)
772 cpp_code += "\n const uint32_t result_masked = register_value & mask_shifted;\n"
773 cpp_code += " const uint32_t result_shifted = result_masked >> shift;\n"
774 cpp_code += "\n return result_shifted;\n"
775 cpp_code += "}\n\n"
777 return cpp_code