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