Coverage for hdl_registers/generator/python/accessor.py: 97%
260 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-01 20:50 +0000
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-01 20:50 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the hdl-registers project, an HDL register generator fast enough to run
5# in real time.
6# https://hdl-registers.com
7# https://github.com/hdl-registers/hdl-registers
8# --------------------------------------------------------------------------------------------------
10# Standard libraries
11from pathlib import Path
12from typing import TYPE_CHECKING, Any, Optional
14# 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{self.header}
80# Standard libraries
81import pickle
82from dataclasses import dataclass
83from enum import Enum
84from pathlib import Path
85from typing import TYPE_CHECKING
86from termcolor import colored
88# Third party libraries
89from hdl_registers.field.numerical_interpretation import to_unsigned_binary
90from tsfpga.math_utils import to_binary_nibble_string, to_hex_byte_string
92if TYPE_CHECKING:
93 # Third party libraries
94 from hdl_registers.generator.python.register_accessor_interface import (
95 PythonRegisterAccessorInterface,
96 )
97 from hdl_registers.register_list import RegisterList
100THIS_DIR = Path(__file__).parent
103class {class_name}:
104 """
105 Class to read/write registers and fields of the ``{self.name}`` register list.
106 """
108 def __init__(self, register_accessor: "PythonRegisterAccessorInterface"):
109 """
110 Arguments:
111 register_accessor: Object that implements register access methods in your system.
112 Must have the methods ``read_register`` and ``write_register``
113 with the same interface and behavior as the interface class
114 :class:`.PythonRegisterAccessorInterface`.
115 It is highly recommended that your accessor class inherits from that class.
116 """
117 self._register_accessor = register_accessor
119 pickle_path = THIS_DIR / "{self.name}.pickle"
120 if not pickle_path.exists():
121 raise FileNotFoundError(
122 f"Could not find the pickle file {{pickle_path}}, "
123 "make sure this artifact is generated."
124 )
126 with pickle_path.open("rb") as file_handle:
127 self._register_list: "RegisterList" = pickle.load(file_handle)
129 def _read_register(self, register_index: int) -> int:
130 """
131 Read a register value via the register accessor provided by the user.
132 We perform sanity checks in both directions here, since this is the glue logic between
133 two interfaces.
134 """
135 if not 0 <= register_index <= {last_index}:
136 raise ValueError(
137 f"Register index {{register_index}} out of range. This is likely an internal error."
138 )
140 register_value = self._register_accessor.read_register(
141 register_list_name="{self.register_list.name}", register_address=4 * register_index
142 )
143 if not 0 <= register_value < 2 ** 32:
144 raise ValueError(
145 f'Register read value "{{register_value}}" from accessor is out of range.'
146 )
148 return register_value
150 def _write_register(self, register_index: int, register_value: int) -> None:
151 """
152 Write a register value via the register accessor provided by the user.
153 We perform sanity checks in both directions here, since this is the glue logic between
154 two interfaces.
155 """
156 if not 0 <= register_index <= {last_index}:
157 raise ValueError(
158 f"Register index {{register_index}} out of range. This is likely an internal error."
159 )
161 if not 0 <= register_value < 2 ** 32:
162 raise ValueError(
163 f'Register write value "{{register_value}}" is out of range.'
164 )
166 self._register_accessor.write_register(
167 register_list_name="{self.register_list.name}",
168 register_address=4 * register_index,
169 register_value=register_value
170 )
171{register_access_methods}\
174def get_accessor(register_accessor: "PythonRegisterAccessorInterface") -> {class_name}:
175 """
176 Factory function to create an accessor object.
177 """
178 return {class_name}(register_accessor=register_accessor)
181def _format_unsigned_number(value: int, width: int, include_decimal: bool=True) -> str:
182 """
183 Format a string representation of the provided ``value``.
184 Suitable for printing field and register values.
185 """
186 result = ""
187 if include_decimal:
188 result += f"unsigned decimal {{value}}, "
190 hex_string = to_hex_byte_string(value=value, result_width_bits=width)
191 result += f"hexadecimal {{hex_string}}, "
193 binary_string = to_binary_nibble_string(value=value, result_width_bits=width)
194 result += f"binary {{binary_string}}"
196 return result
198{register_value_types}
199'''
200 return self._format_with_black(result)
202 def _get_register_value_type_name(
203 self, register: "Register", register_array: Optional["RegisterArray"]
204 ) -> str:
205 """
206 The name of the python type that represents the native Python value of a register.
207 Either a built-in type (int) or a dataclass that we define in another place in the file.
208 """
209 if not register.fields:
210 return "int"
212 register_name = self.qualified_register_name(
213 register=register, register_array=register_array
214 )
215 return f"{self.to_pascal_case(register_name)}Value"
217 def _get_register_value_types(self) -> str:
218 """
219 Get all the classes needed to represent register values.
220 One dataclass for each register that contains fields.
221 """
222 result = ""
224 for register, register_array in self.iterate_registers():
225 if not register.fields:
226 continue
228 result += self._get_register_value_type(
229 register=register, register_array=register_array
230 )
232 return result
234 def _get_register_value_type(
235 self, register: "Register", register_array: Optional["RegisterArray"]
236 ) -> str:
237 """
238 Get class for register value for one specific register.
239 Assumes that the register has fields.
240 """
241 class_name = self._get_register_value_type_name(
242 register=register, register_array=register_array
243 )
244 register_description = self.register_description(
245 register=register, register_array=register_array
246 )
247 result = f'''
248@dataclass
249class {class_name}:
250 """
251 Represents the field values of the {register_description}.
252 Uses native Python types for easy handling.
253 """
254'''
255 for field in register.fields:
256 if isinstance(field, Enumeration):
257 elements = [
258 f'{element.name.upper()} = "{element.name}"' for element in field.elements
259 ]
260 separator = "\n "
261 result += f'''
262 class {self._get_field_python_type_name(field=field)}(Enum):
263 """
264 Enumeration elements for the ``{field.name}`` field.
265 """
266 {separator.join(elements)}
268'''
269 for field in register.fields:
270 field_comment = self._get_field_type_and_range_comment(field=field)
271 field_type = self._get_field_python_type_name(field=field)
272 result += f"""\
273 # {field_comment}
274 {field.name}: {field_type}
275"""
276 result += '''
277 def to_string(self, indentation: str="", no_color: bool = False) -> str:
278 """
279 Get a string of the field values, suitable for printing.
281 Arguments:
282 indentation: Optionally indent each field value line.
283 no_color: Disable color output.
284 Can also be achieved by setting the environment variable ``NO_COLOR`` to any value.
285 """
286 values = []
288'''
289 for field in register.fields:
290 result += self._get_field_type_to_string_value(field=field)
291 result += f"""\
292 field_name = colored("{field.name}", color="light_cyan", no_color=no_color)
293 values.append(f"{{indentation}}{{field_name}}: {{value}}")
295"""
297 result += ' return "\\n".join(values)'
299 return result
301 def _get_field_python_type_name(
302 self, field: "RegisterField", global_type_name: Optional[str] = None
303 ) -> str:
304 """
305 Get the Python type name for the native type that represents a field value.
306 Is either a built-in type (e.g. int) or a type that we define in another place in the file.
308 Arguments:
309 global_type_name: When the field type is a custom one, the type class is declared within
310 the dataclass of the register.
311 When the field type is referenced within the register class, its local name can be
312 used, but when we want to reference it outside of the register class, we need to
313 know the name of the register value class also.
314 """
315 if isinstance(field, BitVector):
316 if isinstance(field.numerical_interpretation, Fixed):
317 return "float"
319 return "int"
321 if isinstance(field, Bit):
322 return "int"
324 if isinstance(field, Enumeration):
325 local_name = self.to_pascal_case(field.name)
327 if global_type_name is None:
328 return local_name
330 return f"{global_type_name}.{local_name}"
332 if isinstance(field, Integer):
333 return "int"
335 raise ValueError(f"Unknown field {field}")
337 @staticmethod
338 def _get_field_type_and_range_comment(field: "RegisterField") -> str:
339 """
340 A string the describes the field.
341 Suitable for descriptions in accessor methods.
342 """
343 if isinstance(field, BitVector):
344 if field.numerical_interpretation.is_signed:
345 sign_comment = "Signed"
346 else:
347 sign_comment = "Unsigned"
349 range_comment = (
350 f"Range: {field.numerical_interpretation.min_value} - "
351 f"{field.numerical_interpretation.max_value}."
352 )
354 if isinstance(field.numerical_interpretation, (Signed, Unsigned)):
355 return f"{sign_comment} bit vector. Width: {field.width}. {range_comment}"
357 if isinstance(field.numerical_interpretation, (SignedFixedPoint, UnsignedFixedPoint)):
358 integer_width = (
359 f"Integer width: {field.numerical_interpretation.integer_bit_width}."
360 )
361 fractional_width = (
362 f"Fractional width: {field.numerical_interpretation.fraction_bit_width}."
363 )
364 width_comment = f"{integer_width} {fractional_width} "
365 return f"{sign_comment} fixed-point bit vector. {width_comment}{range_comment}"
367 raise ValueError(f"Unknown field type {field}")
369 if isinstance(field, Bit):
370 return "Single bit. Range: 0-1."
372 if isinstance(field, Enumeration):
373 element_names = ", ".join([element.name.upper() for element in field.elements])
374 return f"Enumeration. Possible values: {element_names}."
376 if isinstance(field, Integer):
377 if field.is_signed:
378 sign_comment = "Signed"
379 else:
380 sign_comment = "Unsigned"
381 return f"{sign_comment} integer. Range: {field.min_value} - {field.max_value}."
383 raise ValueError(f"Unknown field {field}")
385 def _get_field_type_to_string_value(self, field: "RegisterField") -> str:
386 """
387 Get Python code that casts a field value to a string suitable for printing.
388 """
389 field_value = f"self.{field.name}"
391 if isinstance(field, Enumeration):
392 field_type = self._get_field_python_type_name(field=field)
393 return f"""
394 enum_index = list(self.{field_type}).index({field_value})
395 value = f"{{{field_value}.name}} ({{enum_index}})"
396"""
397 if isinstance(field, Integer):
398 unsigned = (
399 f"to_unsigned_binary(num_bits={field.width}, value={field_value}, is_signed=True)"
400 if field.is_signed
401 else field_value
402 )
404 return f"""
405 unsigned_value = {unsigned}
406 value_comment = _format_unsigned_number(
407 value=unsigned_value, width={field.width}, include_decimal={field.is_signed}
408 )
409 value = f"{{{field_value}}} ({{value_comment}})"
410"""
411 if isinstance(field, Bit):
412 return f" value = str(self.{field.name})\n"
414 if isinstance(field, BitVector):
415 if isinstance(field.numerical_interpretation, Unsigned):
416 return f"""
417 value_comment = _format_unsigned_number(
418 value={field_value}, width={field.width}, include_decimal=False
419 )
420 value = f"{{{field_value}}} ({{value_comment}})"
421"""
422 if isinstance(field.numerical_interpretation, Signed):
423 return f"""
424 unsigned_value = to_unsigned_binary(
425 num_bits={field.width}, value={field_value}, is_signed=True
426 )
427 value_comment = _format_unsigned_number(
428 value=unsigned_value, width={field.width}
429 )
430 value = f"{{{field_value}}} ({{value_comment}})"
431"""
433 if isinstance(field.numerical_interpretation, (UnsignedFixedPoint, SignedFixedPoint)):
434 return f"""
435 unsigned_value = to_unsigned_binary(
436 num_bits={field.width},
437 value={field_value},
438 num_integer_bits={field.numerical_interpretation.integer_bit_width},
439 num_fractional_bits={field.numerical_interpretation.fraction_bit_width},
440 is_signed={field.numerical_interpretation.is_signed}
441 )
442 value_comment = _format_unsigned_number(
443 value=unsigned_value, width={field.width}
444 )
445 value = f"{{{field_value}}} ({{value_comment}})"
446"""
448 raise ValueError(f"Unknown field type {field}")
450 raise ValueError(f"Unknown field {field}")
452 def _get_register_access_methods(self) -> str:
453 """
454 Get all the methods to read and write registers and fields.
455 Will only include methods for registers that are accessible in either direction.
456 """
457 result = ""
459 for register, register_array in self.iterate_registers():
460 if register.mode.software_can_read:
461 if register.fields:
462 result += self._get_register_read_as_class(
463 register=register, register_array=register_array
464 )
465 else:
466 result += self._get_register_read_as_integer(
467 register=register, register_array=register_array
468 )
470 if register.mode.software_can_write:
471 if register.fields:
472 result += self._get_register_write_as_class(
473 register=register, register_array=register_array
474 )
475 result += self._get_fields_write(
476 register=register, register_array=register_array
477 )
478 else:
479 result += self._get_register_write_as_integer(
480 register=register, register_array=register_array
481 )
483 result += self._get_print_registers()
485 return result
487 def _get_register_read_as_class(
488 self, register: "Register", register_array: Optional["RegisterArray"]
489 ) -> str:
490 """
491 For a register that has fields, the unsigned value read from the bus is converted to a
492 custom class that represents the field values.
493 """
494 register_value_type_name = self._get_register_value_type_name(
495 register=register, register_array=register_array
496 )
497 register_array_name = f'"{register_array.name}"' if register_array else "None"
498 common = self._get_register_read_common(
499 register=register,
500 register_array=register_array,
501 register_value_type_name=register_value_type_name,
502 )
503 result = f"""{common}\
504 register = self._register_list.get_register(
505 register_name="{register.name}", register_array_name={register_array_name}
506 )
507 return {register_value_type_name}(
508"""
509 for field_idx, field in enumerate(register.fields):
510 get_value = f"register.fields[{field_idx}].get_value(register_value=value)"
512 if isinstance(field, Enumeration):
513 type_name = self._get_field_python_type_name(
514 field=field, global_type_name=register_value_type_name
515 )
516 get_value = f"{type_name}({get_value}.name)"
518 result += f"{field.name}={get_value},"
520 result += ")"
521 return result
523 def _get_register_read_as_integer(
524 self, register: "Register", register_array: Optional["RegisterArray"]
525 ) -> str:
526 """
527 For registers without fields, the unsigned value read from the bus is simply passed on.
528 """
529 common = self._get_register_read_common(
530 register=register,
531 register_array=register_array,
532 register_value_type_name="int",
533 extra_docstring=(
534 " Return type is a plain unsigned integer "
535 "since the register has no fields.\n"
536 ),
537 )
538 return f"""{common}\
539 return value
540"""
542 def _get_register_read_common(
543 self,
544 register: "Register",
545 register_array: Optional["RegisterArray"],
546 register_value_type_name: str,
547 extra_docstring: str = "",
548 ) -> str:
549 """
550 Common register read portions for both 'integer' and 'class'.
551 """
552 register_name = self._get_semi_qualified_register_name(
553 register=register, register_array=register_array
554 )
555 register_description = self.register_description(
556 register=register, register_array=register_array
557 )
559 array_index_argument = ", array_index: int" if register_array else ""
560 index_variable = self._get_index_variable(register=register, register_array=register_array)
561 array_index_documentation = (
562 f"""
563 Arguments:
564 array_index: Register array iteration index (range 0-{register_array.length - 1}).
565"""
566 if register_array
567 else ""
568 )
569 return f'''
570 def read_{register_name}(self{array_index_argument}) -> "{register_value_type_name}":
571 """
572 Read the {register_description}.
573{extra_docstring}\
574{array_index_documentation}\
575 """
576{index_variable}\
577 value = self._read_register(register_index=index)
578'''
580 def _get_register_write_as_class(
581 self, register: "Register", register_array: Optional["RegisterArray"]
582 ) -> str:
583 """
584 For a register that has fields, the value to be written is provided as a custom class.
585 This must be converted to an unsigned integer before writing to the bus.
586 """
587 register_value_type_name = self._get_register_value_type_name(
588 register=register, register_array=register_array
589 )
590 register_array_name = f'"{register_array.name}"' if register_array else "None"
591 common = self._get_register_write_common(
592 register=register,
593 register_array=register_array,
594 register_value_type_name=register_value_type_name,
595 register_value_docstring="Object with the field values that shall be written",
596 )
597 result = f"""{common}\
598 register = self._register_list.get_register(
599 register_name="{register.name}", register_array_name={register_array_name}
600 )
601 integer_value = 0
602"""
603 for field_idx, field in enumerate(register.fields):
604 convert = f"integer_value += register.fields[{field_idx}].set_value(field_value="
606 if isinstance(field, Enumeration):
607 convert += (
608 f"register.fields[{field_idx}]."
609 f"get_element_by_name(register_value.{field.name}.value)"
610 )
611 else:
612 convert += f"register_value.{field.name}"
614 result += f" {convert})\n"
616 result += """ self._write_register(
617 register_index=index, register_value=integer_value
618)
619"""
620 return result
622 def _get_register_write_as_integer(
623 self, register: "Register", register_array: Optional["RegisterArray"]
624 ) -> str:
625 """
626 For a register without fields, the value to be written is provided as an unsigned
627 integer that can be written straight away.
628 """
629 result = self._get_register_write_common(
630 register=register,
631 register_array=register_array,
632 register_value_type_name="int",
633 register_value_docstring=(
634 "Value to be written.\n"
635 " Is a plain unsigned integer since the register has no fields"
636 ),
637 )
639 result += """ self._write_register(
640 register_index=index, register_value=register_value
641)
642"""
643 return result
645 def _get_register_write_common(
646 self,
647 register: "Register",
648 register_array: Optional["RegisterArray"],
649 register_value_type_name: str,
650 register_value_docstring: str,
651 ) -> str:
652 """
653 Common register write portions for both 'integer' and 'class'.
654 """
655 register_name = self._get_semi_qualified_register_name(
656 register=register, register_array=register_array
657 )
658 register_description = self.register_description(
659 register=register, register_array=register_array
660 )
662 array_index_argument = ", array_index: int" if register_array else ""
663 index_variable = self._get_index_variable(register=register, register_array=register_array)
664 array_index_documentation = (
665 (
666 " array_index: Register array iteration "
667 f"index (range 0-{register_array.length - 1}).\n"
668 )
669 if register_array
670 else ""
671 )
672 return f'''
673 def write_{register_name}(self, \
674register_value: "{register_value_type_name}"{array_index_argument}) -> None:
675 """
676 Write the whole {register_description}.
678 Arguments:
679 register_value: {register_value_docstring}.
680{array_index_documentation}\
681 """
682{index_variable}\
683'''
685 @staticmethod
686 def _get_index_variable(register: "Register", register_array: Optional["RegisterArray"]) -> str:
687 """
688 Get Python code that extracts the register index from the register list.
689 """
690 register_array_name = f'"{register_array.name}"' if register_array else "None"
691 array_index_value = "array_index" if register_array else "None"
692 return f"""\
693 index = self._register_list.get_register_index(
694 register_name="{register.name}",
695 register_array_name={register_array_name},
696 register_array_index={array_index_value},
697 )
698"""
700 def _get_fields_write(
701 self, register: "Register", register_array: Optional["RegisterArray"]
702 ) -> str:
703 """
704 Get Python class methods for writing the values of each field in the register.
705 Will either write the whole register or read-modify-write the register.
706 """
707 result = ""
709 for field_idx, field in enumerate(register.fields):
710 if self.field_setter_should_read_modify_write(register=register):
711 result += self._get_field_read_modify_write(
712 field=field, register=register, register_array=register_array
713 )
714 else:
715 result += self._get_field_write(
716 field_idx=field_idx,
717 field=field,
718 register=register,
719 register_array=register_array,
720 )
722 return result
724 def _get_field_read_modify_write(
725 self,
726 field: "RegisterField",
727 register: "Register",
728 register_array: Optional["RegisterArray"],
729 ) -> str:
730 """
731 Python class method to read-modify-write a register, updating the value of one field.
732 """
733 result = self._get_field_write_common(
734 field=field,
735 register=register,
736 register_array=register_array,
737 docstring=f"""\
738Will read-modify-write the ``{register.name}`` register, setting the ``{field.name}`` field
739 to the provided value, while leaving the other fields at their previous values.
740""",
741 )
743 register_name = self._get_semi_qualified_register_name(
744 register=register, register_array=register_array
745 )
746 array_index_association_plain = "array_index=array_index" if register_array else ""
747 array_index_association_comma = ", array_index=array_index" if register_array else ""
748 result += f"""\
749 register_value = self.read_{register_name}({array_index_association_plain})
750 register_value.{field.name} = field_value
751 self.write_{register_name}(register_value=register_value{array_index_association_comma})
752"""
754 return result
756 def _get_field_write(
757 self,
758 field_idx: int,
759 field: "RegisterField",
760 register: "Register",
761 register_array: Optional["RegisterArray"],
762 ) -> str:
763 """
764 Python class method to write a register, setting the value of one field only.
765 """
766 result = self._get_field_write_common(
767 field=field,
768 register=register,
769 register_array=register_array,
770 docstring=f"""\
771Will write the ``{register.name}`` register, setting the ``{field.name}`` field to the
772 provided value, and all other fields to their default values.
773""",
774 )
776 index_variable = self._get_index_variable(register=register, register_array=register_array)
777 register_array_name = f'"{register_array.name}"' if register_array else "None"
778 result += f"""\
779{index_variable}
780 register = self._register_list.get_register(
781 register_name="{register.name}", register_array_name={register_array_name}
782 )
783 register_value = 0
784"""
786 for iterate_field_idx in range(len(register.fields)):
787 iterate_field = f"register.fields[{iterate_field_idx}]"
788 result += " register_value += "
789 if iterate_field_idx == field_idx:
790 if isinstance(field, Enumeration):
791 result += (
792 f"{iterate_field}.set_value({iterate_field}."
793 "get_element_by_name(field_value.value))"
794 )
795 else:
796 result += f"{iterate_field}.set_value(field_value)"
797 else:
798 result += f"{iterate_field}.default_value_uint << {iterate_field}.base_index"
800 result += "\n"
802 result += """
803 self._write_register(register_index=index, register_value=register_value)
804 """
806 return result
808 def _get_field_write_common(
809 self,
810 field: "RegisterField",
811 register: "Register",
812 register_array: Optional["RegisterArray"],
813 docstring: str,
814 ) -> str:
815 """
816 Common portions for field write methods.
817 """
818 field_name = self._get_semi_qualified_field_name(
819 field=field, register=register, register_array=register_array
820 )
821 field_value_type_name = self._get_field_python_type_name(
822 field=field,
823 global_type_name=self._get_register_value_type_name(
824 register=register, register_array=register_array
825 ),
826 )
827 field_description = self.field_description(
828 register=register, field=field, register_array=register_array
829 )
830 field_comment = self._get_field_type_and_range_comment(field=field)
832 array_index_argument = ", array_index: int" if register_array else ""
833 array_index_documentation = (
834 (
835 " array_index: Register array iteration "
836 f"index (range 0-{register_array.length - 1}).\n"
837 )
838 if register_array
839 else ""
840 )
841 return f'''
842 def write_{field_name}(self, \
843field_value: "{field_value_type_name}"{array_index_argument}) -> None:
844 """
845 Write the {field_description}.
846 {docstring}\
848 Arguments:
849 field_value: The field value to be written.
850 {field_comment}
851{array_index_documentation}\
852 """
853'''
855 @staticmethod
856 def _get_semi_qualified_register_name(
857 register: "Register", register_array: Optional["RegisterArray"]
858 ) -> str:
859 """
860 Base name of register access methods.
861 Name is a contrast to 'self.qualified_register_name'.
862 """
863 if register_array is None:
864 return register.name
866 return f"{register_array.name}_{register.name}"
868 def _get_semi_qualified_field_name(
869 self,
870 field: "RegisterField",
871 register: "Register",
872 register_array: Optional["RegisterArray"],
873 ) -> str:
874 """
875 Base name of field access methods.
876 Name is a contrast to 'self.qualified_field_name'.
877 """
878 register_name = self._get_semi_qualified_register_name(
879 register=register, register_array=register_array
880 )
882 return f"{register_name}_{field.name}"
884 def _get_print_registers(self) -> str:
885 """
886 Python method to print all registers and their values.
887 """
888 result = '''
889 def print_registers(self, no_color: bool=False) -> None:
890 """
891 Print all registers and their values to STDOUT.
893 Arguments:
894 no_color: Disable color output.
895 Can also be achieved by setting the environment variable ``NO_COLOR`` to any value.
896 """
897'''
899 for register_object in self.register_list.register_objects:
900 if isinstance(register_object, Register):
901 result += self._get_print_register(
902 is_readable=register_object.mode.software_can_read, register=register_object
903 )
904 else:
905 for array_index in range(register_object.length):
906 for register in register_object.registers:
907 result += self._get_print_register(
908 is_readable=register.mode.software_can_read,
909 register=register,
910 register_array=register_object,
911 register_array_index=array_index,
912 )
914 return result
916 @staticmethod
917 def _get_print_register(
918 is_readable: bool,
919 register: "Register",
920 register_array: Optional["RegisterArray"] = None,
921 register_array_index: Optional[int] = None,
922 ) -> str:
923 """
924 Python code to print a single register and its field values.
925 """
926 if register_array is not None and register_array_index is not None:
927 name = f"{register_array.name}[{register_array_index}].{register.name}"
928 index = (
929 register_array.get_start_index(array_index=register_array_index) + register.index
930 )
931 read = (
932 f"self.read_{register_array.name}_{register.name}"
933 f"(array_index={register_array_index})"
934 )
935 else:
936 name = register.name
937 index = register.index
938 read = f"self.read_{register.name}()"
940 heading = f"""\
941print(
942 colored("Register", color="light_yellow", no_color=no_color)
943 + " '{name}' "
944 + colored("." * {67 - len(name)}, color="dark_grey", no_color=no_color)
945 + " (index {index}, address {4 * index}):"
946)
947"""
949 if not is_readable:
950 return f"""\
951 {heading}\
952 print(" Not readable.\\n")
954"""
956 read_value = f"register_value = {read}\n"
957 if not register.fields:
958 return f"""\
959 {heading}\
960 {read_value}\
961 formatted_value = _format_unsigned_number(value=register_value, width=32)
962 print(f" {{formatted_value}}\\n")
964"""
966 return f"""\
967 {heading}\
968 {read_value}\
969 print(register_value.to_string(indentation=" ", no_color=no_color))
970 print()
972"""
974 def _format_with_black(self, code: str) -> str:
975 """
976 Format the code with the Black code formatter, so the result always looks perfect.
977 Means that we can be a little more sloppy when we construct the generated code.
978 But even then, it would be a huge pain to determine when to break and not break lines.
979 So it's easier to just apply a stable and fast formatter.
980 """
981 # Not that this is NOT a public API:
982 # * https://stackoverflow.com/questions/57652922
983 # * https://github.com/psf/black/issues/779
984 # The simple mode construction below will use the default settings for black
985 # (magic trailing comma, etc) apart from the line length.
986 # This is identical to how Python files in this project are formatted.
987 # Should give a consistent look and feel.
988 mode = Mode(line_length=100)
989 return format_str(src_contents=code, mode=mode)