Coverage for hdl_registers/generator/python/accessor.py: 97%
260 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# Third party libraries
15from black import Mode, format_str
17# First party libraries
18from hdl_registers.field.bit import Bit
19from hdl_registers.field.bit_vector import BitVector
20from hdl_registers.field.enumeration import Enumeration
21from hdl_registers.field.integer import Integer
22from hdl_registers.field.numerical_interpretation import (
23 Fixed,
24 Signed,
25 SignedFixedPoint,
26 Unsigned,
27 UnsignedFixedPoint,
28)
29from hdl_registers.generator.register_code_generator import RegisterCodeGenerator
30from hdl_registers.register import Register
32if TYPE_CHECKING:
33 # First party libraries
34 from hdl_registers.field.register_field import RegisterField
35 from hdl_registers.register_array import RegisterArray
38class PythonAccessorGenerator(RegisterCodeGenerator):
39 """
40 Generate a Python class to read/write/print register and field values on a target device.
41 Field and register values are represented using their native Python types, for easy handling.
42 Meaning, no manual type casting back and forth for the user.
44 See the :ref:`generator_python` article for usage details.
45 See also :class:`.PythonRegisterAccessorInterface`.
47 Needs to have the result from the :class:`.PythonPickleGenerator` generator in the
48 same output folder.
49 """
51 __version__ = "0.1.0"
53 SHORT_DESCRIPTION = "Python accessor"
55 COMMENT_START = "#"
57 @property
58 def output_file(self) -> Path:
59 """
60 Result will be placed in this file.
61 """
62 return self.output_folder / f"{self.name}_accessor.py"
64 def get_code(self, **kwargs: Any) -> str:
65 """
66 Get Python code for accessing register and field values.
67 """
68 class_name = f"{self.to_pascal_case(self.name)}Accessor"
70 if self.register_list.register_objects:
71 last_index = self.register_list.register_objects[-1].index
72 else:
73 last_index = -1
75 register_access_methods = self._get_register_access_methods()
76 register_value_types = self._get_register_value_types()
78 result = f'''\
79# Standard libraries
80import pickle
81from dataclasses import dataclass
82from enum import Enum
83from pathlib import Path
84from typing import TYPE_CHECKING
85from termcolor import colored
87# Third party libraries
88from hdl_registers.field.numerical_interpretation import to_unsigned_binary
89from tsfpga.math_utils import to_binary_nibble_string, to_hex_byte_string
91if TYPE_CHECKING:
92 # Third party libraries
93 from hdl_registers.generator.python.register_accessor_interface import (
94 PythonRegisterAccessorInterface,
95 )
96 from hdl_registers.register_list import RegisterList
99THIS_DIR = Path(__file__).parent
102class {class_name}:
103 """
104 Class to read/write registers and fields of the ``{self.name}`` register list.
105 """
107 def __init__(self, register_accessor: "PythonRegisterAccessorInterface"):
108 """
109 Arguments:
110 register_accessor: Object that implements register access methods in your system.
111 Must have the methods ``read_register`` and ``write_register``
112 with the same interface and behavior as the interface class
113 :class:`.PythonRegisterAccessorInterface`.
114 It is highly recommended that your accessor class inherits from that class.
115 """
116 self._register_accessor = register_accessor
118 pickle_path = THIS_DIR / "{self.name}.pickle"
119 if not pickle_path.exists():
120 raise FileNotFoundError(
121 f"Could not find the pickle file { pickle_path} , "
122 "make sure this artifact is generated."
123 )
125 with pickle_path.open("rb") as file_handle:
126 self._register_list: "RegisterList" = pickle.load(file_handle)
128 def _read_register(self, register_index: int) -> int:
129 """
130 Read a register value via the register accessor provided by the user.
131 We perform sanity checks in both directions here, since this is the glue logic between
132 two interfaces.
133 """
134 if not 0 <= register_index <= {last_index}:
135 raise ValueError(
136 f"Register index { register_index} out of range. This is likely an internal error."
137 )
139 register_value = self._register_accessor.read_register(
140 register_list_name="{self.register_list.name}", register_address=4 * register_index
141 )
142 if not 0 <= register_value < 2 ** 32:
143 raise ValueError(
144 f'Register read value "{ register_value} " from accessor is out of range.'
145 )
147 return register_value
149 def _write_register(self, register_index: int, register_value: int) -> None:
150 """
151 Write a register value via the register accessor provided by the user.
152 We perform sanity checks in both directions here, since this is the glue logic between
153 two interfaces.
154 """
155 if not 0 <= register_index <= {last_index}:
156 raise ValueError(
157 f"Register index { register_index} out of range. This is likely an internal error."
158 )
160 if not 0 <= register_value < 2 ** 32:
161 raise ValueError(
162 f'Register write value "{ register_value} " is out of range.'
163 )
165 self._register_accessor.write_register(
166 register_list_name="{self.register_list.name}",
167 register_address=4 * register_index,
168 register_value=register_value
169 )
170{register_access_methods}\
173def get_accessor(register_accessor: "PythonRegisterAccessorInterface") -> {class_name}:
174 """
175 Factory function to create an accessor object.
176 """
177 return {class_name}(register_accessor=register_accessor)
180def _format_unsigned_number(value: int, width: int, include_decimal: bool=True) -> str:
181 """
182 Format a string representation of the provided ``value``.
183 Suitable for printing field and register values.
184 """
185 result = ""
186 if include_decimal:
187 result += f"unsigned decimal { value} , "
189 hex_string = to_hex_byte_string(value=value, result_width_bits=width)
190 result += f"hexadecimal { hex_string} , "
192 binary_string = to_binary_nibble_string(value=value, result_width_bits=width)
193 result += f"binary { binary_string} "
195 return result
197{register_value_types}
198'''
199 return self._format_with_black(result)
201 def _get_register_value_type_name(
202 self, register: "Register", register_array: Optional["RegisterArray"]
203 ) -> str:
204 """
205 The name of the python type that represents the native Python value of a register.
206 Either a built-in type (int) or a dataclass that we define in another place in the file.
207 """
208 if not register.fields:
209 return "int"
211 register_name = self.qualified_register_name(
212 register=register, register_array=register_array
213 )
214 return f"{self.to_pascal_case(register_name)}Value"
216 def _get_register_value_types(self) -> str:
217 """
218 Get all the classes needed to represent register values.
219 One dataclass for each register that contains fields.
220 """
221 result = ""
223 for register, register_array in self.iterate_registers():
224 if not register.fields:
225 continue
227 result += self._get_register_value_type(
228 register=register, register_array=register_array
229 )
231 return result
233 def _get_register_value_type(
234 self, register: "Register", register_array: Optional["RegisterArray"]
235 ) -> str:
236 """
237 Get class for register value for one specific register.
238 Assumes that the register has fields.
239 """
240 class_name = self._get_register_value_type_name(
241 register=register, register_array=register_array
242 )
243 register_description = self.register_description(
244 register=register, register_array=register_array
245 )
246 result = f'''
247@dataclass
248class {class_name}:
249 """
250 Represents the field values of the {register_description}.
251 Uses native Python types for easy handling.
252 """
253'''
254 for field in register.fields:
255 if isinstance(field, Enumeration):
256 elements = [
257 f'{element.name.upper()} = "{element.name}"' for element in field.elements
258 ]
259 separator = "\n "
260 result += f'''
261 class {self._get_field_python_type_name(field=field)}(Enum):
262 """
263 Enumeration elements for the ``{field.name}`` field.
264 """
265 {separator.join(elements)}
267'''
268 for field in register.fields:
269 field_comment = self._get_field_type_and_range_comment(field=field)
270 field_type = self._get_field_python_type_name(field=field)
271 result += f"""\
272 # {field_comment}
273 {field.name}: {field_type}
274"""
275 result += '''
276 def to_string(self, indentation: str="", no_color: bool = False) -> str:
277 """
278 Get a string of the field values, suitable for printing.
280 Arguments:
281 indentation: Optionally indent each field value line.
282 no_color: Disable color output.
283 Can also be achieved by setting the environment variable ``NO_COLOR`` to any value.
284 """
285 values = []
287'''
288 for field in register.fields:
289 result += self._get_field_type_to_string_value(field=field)
290 result += f"""\
291 field_name = colored("{field.name}", color="light_cyan", no_color=no_color)
292 values.append(f"{ indentation} { field_name} : { value} ")
294"""
296 result += ' return "\\n".join(values)'
298 return result
300 def _get_field_python_type_name(
301 self, field: "RegisterField", global_type_name: Optional[str] = None
302 ) -> str:
303 """
304 Get the Python type name for the native type that represents a field value.
305 Is either a built-in type (e.g. int) or a type that we define in another place in the file.
307 Arguments:
308 global_type_name: When the field type is a custom one, the type class is declared within
309 the dataclass of the register.
310 When the field type is referenced within the register class, its local name can be
311 used, but when we want to reference it outside of the register class, we need to
312 know the name of the register value class also.
313 """
314 if isinstance(field, BitVector):
315 if isinstance(field.numerical_interpretation, Fixed):
316 return "float"
318 return "int"
320 if isinstance(field, Bit):
321 return "int"
323 if isinstance(field, Enumeration):
324 local_name = self.to_pascal_case(field.name)
326 if global_type_name is None:
327 return local_name
329 return f"{global_type_name}.{local_name}"
331 if isinstance(field, Integer):
332 return "int"
334 raise ValueError(f"Unknown field {field}")
336 @staticmethod
337 def _get_field_type_and_range_comment(field: "RegisterField") -> str:
338 """
339 A string the describes the field.
340 Suitable for descriptions in accessor methods.
341 """
342 if isinstance(field, BitVector):
343 if field.numerical_interpretation.is_signed:
344 sign_comment = "Signed"
345 else:
346 sign_comment = "Unsigned"
348 range_comment = (
349 f"Range: {field.numerical_interpretation.min_value} - "
350 f"{field.numerical_interpretation.max_value}."
351 )
353 if isinstance(field.numerical_interpretation, (Signed, Unsigned)):
354 return f"{sign_comment} bit vector. Width: {field.width}. {range_comment}"
356 if isinstance(field.numerical_interpretation, (SignedFixedPoint, UnsignedFixedPoint)):
357 integer_width = (
358 f"Integer width: {field.numerical_interpretation.integer_bit_width}."
359 )
360 fractional_width = (
361 f"Fractional width: {field.numerical_interpretation.fraction_bit_width}."
362 )
363 width_comment = f"{integer_width} {fractional_width} "
364 return f"{sign_comment} fixed-point bit vector. {width_comment}{range_comment}"
366 raise ValueError(f"Unknown field type {field}")
368 if isinstance(field, Bit):
369 return "Single bit. Range: 0-1."
371 if isinstance(field, Enumeration):
372 element_names = ", ".join([element.name.upper() for element in field.elements])
373 return f"Enumeration. Possible values: {element_names}."
375 if isinstance(field, Integer):
376 if field.is_signed:
377 sign_comment = "Signed"
378 else:
379 sign_comment = "Unsigned"
380 return f"{sign_comment} integer. Range: {field.min_value} - {field.max_value}."
382 raise ValueError(f"Unknown field {field}")
384 def _get_field_type_to_string_value(self, field: "RegisterField") -> str:
385 """
386 Get Python code that casts a field value to a string suitable for printing.
387 """
388 field_value = f"self.{field.name}"
390 if isinstance(field, Enumeration):
391 field_type = self._get_field_python_type_name(field=field)
392 return f"""
393 enum_index = list(self.{field_type}).index({field_value})
394 value = f"{ {field_value}.name} ({ enum_index} )"
395"""
396 if isinstance(field, Integer):
397 unsigned = (
398 f"to_unsigned_binary(num_bits={field.width}, value={field_value}, is_signed=True)"
399 if field.is_signed
400 else field_value
401 )
403 return f"""
404 unsigned_value = {unsigned}
405 value_comment = _format_unsigned_number(
406 value=unsigned_value, width={field.width}, include_decimal={field.is_signed}
407 )
408 value = f"{ {field_value}} ({ value_comment} )"
409"""
410 if isinstance(field, Bit):
411 return f" value = str(self.{field.name})\n"
413 if isinstance(field, BitVector):
414 if isinstance(field.numerical_interpretation, Unsigned):
415 return f"""
416 value_comment = _format_unsigned_number(
417 value={field_value}, width={field.width}, include_decimal=False
418 )
419 value = f"{ {field_value}} ({ value_comment} )"
420"""
421 if isinstance(field.numerical_interpretation, Signed):
422 return f"""
423 unsigned_value = to_unsigned_binary(
424 num_bits={field.width}, value={field_value}, is_signed=True
425 )
426 value_comment = _format_unsigned_number(
427 value=unsigned_value, width={field.width}
428 )
429 value = f"{ {field_value}} ({ value_comment} )"
430"""
432 if isinstance(field.numerical_interpretation, (UnsignedFixedPoint, SignedFixedPoint)):
433 return f"""
434 unsigned_value = to_unsigned_binary(
435 num_bits={field.width},
436 value={field_value},
437 num_integer_bits={field.numerical_interpretation.integer_bit_width},
438 num_fractional_bits={field.numerical_interpretation.fraction_bit_width},
439 is_signed={field.numerical_interpretation.is_signed}
440 )
441 value_comment = _format_unsigned_number(
442 value=unsigned_value, width={field.width}
443 )
444 value = f"{ {field_value}} ({ value_comment} )"
445"""
447 raise ValueError(f"Unknown field type {field}")
449 raise ValueError(f"Unknown field {field}")
451 def _get_register_access_methods(self) -> str:
452 """
453 Get all the methods to read and write registers and fields.
454 Will only include methods for registers that are accessible in either direction.
455 """
456 result = ""
458 for register, register_array in self.iterate_registers():
459 if register.mode.software_can_read:
460 if register.fields:
461 result += self._get_register_read_as_class(
462 register=register, register_array=register_array
463 )
464 else:
465 result += self._get_register_read_as_integer(
466 register=register, register_array=register_array
467 )
469 if register.mode.software_can_write:
470 if register.fields:
471 result += self._get_register_write_as_class(
472 register=register, register_array=register_array
473 )
474 result += self._get_fields_write(
475 register=register, register_array=register_array
476 )
477 else:
478 result += self._get_register_write_as_integer(
479 register=register, register_array=register_array
480 )
482 result += self._get_print_registers()
484 return result
486 def _get_register_read_as_class(
487 self, register: "Register", register_array: Optional["RegisterArray"]
488 ) -> str:
489 """
490 For a register that has fields, the unsigned value read from the bus is converted to a
491 custom class that represents the field values.
492 """
493 register_value_type_name = self._get_register_value_type_name(
494 register=register, register_array=register_array
495 )
496 register_array_name = f'"{register_array.name}"' if register_array else "None"
497 common = self._get_register_read_common(
498 register=register,
499 register_array=register_array,
500 register_value_type_name=register_value_type_name,
501 )
502 result = f"""{common}\
503 register = self._register_list.get_register(
504 register_name="{register.name}", register_array_name={register_array_name}
505 )
506 return {register_value_type_name}(
507"""
508 for field_idx, field in enumerate(register.fields):
509 get_value = f"register.fields[{field_idx}].get_value(register_value=value)"
511 if isinstance(field, Enumeration):
512 type_name = self._get_field_python_type_name(
513 field=field, global_type_name=register_value_type_name
514 )
515 get_value = f"{type_name}({get_value}.name)"
517 result += f"{field.name}={get_value},"
519 result += ")"
520 return result
522 def _get_register_read_as_integer(
523 self, register: "Register", register_array: Optional["RegisterArray"]
524 ) -> str:
525 """
526 For registers without fields, the unsigned value read from the bus is simply passed on.
527 """
528 common = self._get_register_read_common(
529 register=register,
530 register_array=register_array,
531 register_value_type_name="int",
532 extra_docstring=(
533 " Return type is a plain unsigned integer "
534 "since the register has no fields.\n"
535 ),
536 )
537 return f"""{common}\
538 return value
539"""
541 def _get_register_read_common(
542 self,
543 register: "Register",
544 register_array: Optional["RegisterArray"],
545 register_value_type_name: str,
546 extra_docstring: str = "",
547 ) -> str:
548 """
549 Common register read portions for both 'integer' and 'class'.
550 """
551 register_name = self._get_semi_qualified_register_name(
552 register=register, register_array=register_array
553 )
554 register_description = self.register_description(
555 register=register, register_array=register_array
556 )
558 array_index_argument = ", array_index: int" if register_array else ""
559 index_variable = self._get_index_variable(register=register, register_array=register_array)
560 array_index_documentation = (
561 f"""
562 Arguments:
563 array_index: Register array iteration index (range 0-{register_array.length - 1}).
564"""
565 if register_array
566 else ""
567 )
568 return f'''
569 def read_{register_name}(self{array_index_argument}) -> "{register_value_type_name}":
570 """
571 Read the {register_description}.
572{extra_docstring}\
573{array_index_documentation}\
574 """
575{index_variable}\
576 value = self._read_register(register_index=index)
577'''
579 def _get_register_write_as_class(
580 self, register: "Register", register_array: Optional["RegisterArray"]
581 ) -> str:
582 """
583 For a register that has fields, the value to be written is provided as a custom class.
584 This must be converted to an unsigned integer before writing to the bus.
585 """
586 register_value_type_name = self._get_register_value_type_name(
587 register=register, register_array=register_array
588 )
589 register_array_name = f'"{register_array.name}"' if register_array else "None"
590 common = self._get_register_write_common(
591 register=register,
592 register_array=register_array,
593 register_value_type_name=register_value_type_name,
594 register_value_docstring="Object with the field values that shall be written",
595 )
596 result = f"""{common}\
597 register = self._register_list.get_register(
598 register_name="{register.name}", register_array_name={register_array_name}
599 )
600 integer_value = 0
601"""
602 for field_idx, field in enumerate(register.fields):
603 convert = f"integer_value += register.fields[{field_idx}].set_value(field_value="
605 if isinstance(field, Enumeration):
606 convert += (
607 f"register.fields[{field_idx}]."
608 f"get_element_by_name(register_value.{field.name}.value)"
609 )
610 else:
611 convert += f"register_value.{field.name}"
613 result += f" {convert})\n"
615 result += """ self._write_register(
616 register_index=index, register_value=integer_value
617)
618"""
619 return result
621 def _get_register_write_as_integer(
622 self, register: "Register", register_array: Optional["RegisterArray"]
623 ) -> str:
624 """
625 For a register without fields, the value to be written is provided as an unsigned
626 integer that can be written straight away.
627 """
628 result = self._get_register_write_common(
629 register=register,
630 register_array=register_array,
631 register_value_type_name="int",
632 register_value_docstring=(
633 "Value to be written.\n"
634 " Is a plain unsigned integer since the register has no fields"
635 ),
636 )
638 result += """ self._write_register(
639 register_index=index, register_value=register_value
640)
641"""
642 return result
644 def _get_register_write_common(
645 self,
646 register: "Register",
647 register_array: Optional["RegisterArray"],
648 register_value_type_name: str,
649 register_value_docstring: str,
650 ) -> str:
651 """
652 Common register write portions for both 'integer' and 'class'.
653 """
654 register_name = self._get_semi_qualified_register_name(
655 register=register, register_array=register_array
656 )
657 register_description = self.register_description(
658 register=register, register_array=register_array
659 )
661 array_index_argument = ", array_index: int" if register_array else ""
662 index_variable = self._get_index_variable(register=register, register_array=register_array)
663 array_index_documentation = (
664 (
665 " array_index: Register array iteration "
666 f"index (range 0-{register_array.length - 1}).\n"
667 )
668 if register_array
669 else ""
670 )
671 return f'''
672 def write_{register_name}(self, \
673register_value: "{register_value_type_name}"{array_index_argument}) -> None:
674 """
675 Write the whole {register_description}.
677 Arguments:
678 register_value: {register_value_docstring}.
679{array_index_documentation}\
680 """
681{index_variable}\
682'''
684 @staticmethod
685 def _get_index_variable(register: "Register", register_array: Optional["RegisterArray"]) -> str:
686 """
687 Get Python code that extracts the register index from the register list.
688 """
689 register_array_name = f'"{register_array.name}"' if register_array else "None"
690 array_index_value = "array_index" if register_array else "None"
691 return f"""\
692 index = self._register_list.get_register_index(
693 register_name="{register.name}",
694 register_array_name={register_array_name},
695 register_array_index={array_index_value},
696 )
697"""
699 def _get_fields_write(
700 self, register: "Register", register_array: Optional["RegisterArray"]
701 ) -> str:
702 """
703 Get Python class methods for writing the values of each field in the register.
704 Will either write the whole register or read-modify-write the register.
705 """
706 result = ""
708 for field_idx, field in enumerate(register.fields):
709 if self.field_setter_should_read_modify_write(register=register):
710 result += self._get_field_read_modify_write(
711 field=field, register=register, register_array=register_array
712 )
713 else:
714 result += self._get_field_write(
715 field_idx=field_idx,
716 field=field,
717 register=register,
718 register_array=register_array,
719 )
721 return result
723 def _get_field_read_modify_write(
724 self,
725 field: "RegisterField",
726 register: "Register",
727 register_array: Optional["RegisterArray"],
728 ) -> str:
729 """
730 Python class method to read-modify-write a register, updating the value of one field.
731 """
732 result = self._get_field_write_common(
733 field=field,
734 register=register,
735 register_array=register_array,
736 docstring=f"""\
737Will read-modify-write the ``{register.name}`` register, setting the ``{field.name}`` field
738 to the provided value, while leaving the other fields at their previous values.
739""",
740 )
742 register_name = self._get_semi_qualified_register_name(
743 register=register, register_array=register_array
744 )
745 array_index_association_plain = "array_index=array_index" if register_array else ""
746 array_index_association_comma = ", array_index=array_index" if register_array else ""
747 result += f"""\
748 register_value = self.read_{register_name}({array_index_association_plain})
749 register_value.{field.name} = field_value
750 self.write_{register_name}(register_value=register_value{array_index_association_comma})
751"""
753 return result
755 def _get_field_write(
756 self,
757 field_idx: int,
758 field: "RegisterField",
759 register: "Register",
760 register_array: Optional["RegisterArray"],
761 ) -> str:
762 """
763 Python class method to write a register, setting the value of one field only.
764 """
765 result = self._get_field_write_common(
766 field=field,
767 register=register,
768 register_array=register_array,
769 docstring=f"""\
770Will write the ``{register.name}`` register, setting the ``{field.name}`` field to the
771 provided value, and all other fields to their default values.
772""",
773 )
775 index_variable = self._get_index_variable(register=register, register_array=register_array)
776 register_array_name = f'"{register_array.name}"' if register_array else "None"
777 result += f"""\
778{index_variable}
779 register = self._register_list.get_register(
780 register_name="{register.name}", register_array_name={register_array_name}
781 )
782 register_value = 0
783"""
785 for iterate_field_idx in range(len(register.fields)):
786 iterate_field = f"register.fields[{iterate_field_idx}]"
787 result += " register_value += "
788 if iterate_field_idx == field_idx:
789 if isinstance(field, Enumeration):
790 result += (
791 f"{iterate_field}.set_value({iterate_field}."
792 "get_element_by_name(field_value.value))"
793 )
794 else:
795 result += f"{iterate_field}.set_value(field_value)"
796 else:
797 result += f"{iterate_field}.default_value_uint << {iterate_field}.base_index"
799 result += "\n"
801 result += """
802 self._write_register(register_index=index, register_value=register_value)
803 """
805 return result
807 def _get_field_write_common(
808 self,
809 field: "RegisterField",
810 register: "Register",
811 register_array: Optional["RegisterArray"],
812 docstring: str,
813 ) -> str:
814 """
815 Common portions for field write methods.
816 """
817 field_name = self._get_semi_qualified_field_name(
818 field=field, register=register, register_array=register_array
819 )
820 field_value_type_name = self._get_field_python_type_name(
821 field=field,
822 global_type_name=self._get_register_value_type_name(
823 register=register, register_array=register_array
824 ),
825 )
826 field_description = self.field_description(
827 register=register, field=field, register_array=register_array
828 )
829 field_comment = self._get_field_type_and_range_comment(field=field)
831 array_index_argument = ", array_index: int" if register_array else ""
832 array_index_documentation = (
833 (
834 " array_index: Register array iteration "
835 f"index (range 0-{register_array.length - 1}).\n"
836 )
837 if register_array
838 else ""
839 )
840 return f'''
841 def write_{field_name}(self, \
842field_value: "{field_value_type_name}"{array_index_argument}) -> None:
843 """
844 Write the {field_description}.
845 {docstring}\
847 Arguments:
848 field_value: The field value to be written.
849 {field_comment}
850{array_index_documentation}\
851 """
852'''
854 @staticmethod
855 def _get_semi_qualified_register_name(
856 register: "Register", register_array: Optional["RegisterArray"]
857 ) -> str:
858 """
859 Base name of register access methods.
860 Name is a contrast to 'self.qualified_register_name'.
861 """
862 if register_array is None:
863 return register.name
865 return f"{register_array.name}_{register.name}"
867 def _get_semi_qualified_field_name(
868 self,
869 field: "RegisterField",
870 register: "Register",
871 register_array: Optional["RegisterArray"],
872 ) -> str:
873 """
874 Base name of field access methods.
875 Name is a contrast to 'self.qualified_field_name'.
876 """
877 register_name = self._get_semi_qualified_register_name(
878 register=register, register_array=register_array
879 )
881 return f"{register_name}_{field.name}"
883 def _get_print_registers(self) -> str:
884 """
885 Python method to print all registers and their values.
886 """
887 result = '''
888 def print_registers(self, no_color: bool=False) -> None:
889 """
890 Print all registers and their values to STDOUT.
892 Arguments:
893 no_color: Disable color output.
894 Can also be achieved by setting the environment variable ``NO_COLOR`` to any value.
895 """
896'''
898 for register_object in self.register_list.register_objects:
899 if isinstance(register_object, Register):
900 result += self._get_print_register(
901 is_readable=register_object.mode.software_can_read, register=register_object
902 )
903 else:
904 for array_index in range(register_object.length):
905 for register in register_object.registers:
906 result += self._get_print_register(
907 is_readable=register.mode.software_can_read,
908 register=register,
909 register_array=register_object,
910 register_array_index=array_index,
911 )
913 return result
915 @staticmethod
916 def _get_print_register(
917 is_readable: bool,
918 register: "Register",
919 register_array: Optional["RegisterArray"] = None,
920 register_array_index: Optional[int] = None,
921 ) -> str:
922 """
923 Python code to print a single register and its field values.
924 """
925 if register_array is not None and register_array_index is not None:
926 name = f"{register_array.name}[{register_array_index}].{register.name}"
927 index = (
928 register_array.get_start_index(array_index=register_array_index) + register.index
929 )
930 read = (
931 f"self.read_{register_array.name}_{register.name}"
932 f"(array_index={register_array_index})"
933 )
934 else:
935 name = register.name
936 index = register.index
937 read = f"self.read_{register.name}()"
939 heading = f"""\
940print(
941 colored("Register", color="light_yellow", no_color=no_color)
942 + " '{name}' "
943 + colored("." * {67 - len(name)}, color="dark_grey", no_color=no_color)
944 + " (index {index}, address {4 * index}):"
945)
946"""
948 if not is_readable:
949 return f"""\
950 {heading}\
951 print(" Not readable.\\n")
953"""
955 read_value = f"register_value = {read}\n"
956 if not register.fields:
957 return f"""\
958 {heading}\
959 {read_value}\
960 formatted_value = _format_unsigned_number(value=register_value, width=32)
961 print(f" { formatted_value} \\n")
963"""
965 return f"""\
966 {heading}\
967 {read_value}\
968 print(register_value.to_string(indentation=" ", no_color=no_color))
969 print()
971"""
973 def _format_with_black(self, code: str) -> str:
974 """
975 Format the code with the Black code formatter, so the result always looks perfect.
976 Means that we can be a little more sloppy when we construct the generated code.
977 But even then, it would be a huge pain to determine when to break and not break lines.
978 So it's easier to just apply a stable and fast formatter.
979 """
980 # Not that this is NOT a public API:
981 # * https://stackoverflow.com/questions/57652922
982 # * https://github.com/psf/black/issues/779
983 # The simple mode construction below will use the default settings for black
984 # (magic trailing comma, etc) apart from the line length.
985 # This is identical to how Python files in this project are formatted.
986 # Should give a consistent look and feel.
987 mode = Mode(line_length=100)
988 return format_str(src_contents=code, mode=mode)