Coverage for hdl_registers/generator/register_code_generator_helpers.py: 95%
80 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 11:11 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 11:11 +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
14from hdl_registers.register import Register
15from hdl_registers.register_array import RegisterArray
16from hdl_registers.register_modes import REGISTER_MODES
18if TYPE_CHECKING:
19 from collections.abc import Iterator
21 from hdl_registers.constant.constant import Constant
22 from hdl_registers.field.register_field import RegisterField
23 from hdl_registers.register_list import RegisterList
26class RegisterCodeGeneratorHelpers:
27 """
28 Various helper methods that make register code generation easier.
29 """
31 # Defined in 'RegisterCodeGenerator' class, which shall also be inherited wherever this class
32 # is used.
33 register_list: RegisterList
34 name: str
35 DEFAULT_INDENTATION_LEVEL: int
36 COMMENT_START: str
37 COMMENT_END: str
39 def iterate_constants(self) -> Iterator[Constant]:
40 """
41 Iterate of all constants in the register list.
42 """
43 yield from self.register_list.constants
45 def iterate_register_objects(self) -> Iterator[Register | RegisterArray]:
46 """
47 Iterate over all register objects in the register list.
48 I.e. all plain registers and all register arrays.
49 """
50 yield from self.register_list.register_objects
52 def iterate_registers(self) -> Iterator[tuple[Register, RegisterArray | None]]:
53 """
54 Iterate over all registers, plain or in array, in the register list.
56 Return:
57 If the register is plain, the array return value in the tuple will be ``None``.
58 If the register is in an array, the array return value will conversely be non-``None``.
59 """
60 for register_object in self.iterate_register_objects():
61 if isinstance(register_object, Register):
62 yield (register_object, None)
63 else:
64 for register in register_object.registers:
65 yield (register, register_object)
67 def iterate_plain_registers(self) -> Iterator[Register]:
68 """
69 Iterate over all plain registers (i.e. registers not in array) in the register list.
70 """
71 for register_object in self.iterate_register_objects():
72 if isinstance(register_object, Register):
73 yield register_object
75 def iterate_register_arrays(self) -> Iterator[RegisterArray]:
76 """
77 Iterate over all register arrays in the register list.
78 """
79 for register_object in self.iterate_register_objects():
80 if isinstance(register_object, RegisterArray):
81 yield register_object
83 def qualified_register_name(
84 self, register: Register, register_array: RegisterArray | None = None
85 ) -> str:
86 """
87 Get the qualified register name, e.g. "<module name>_<register name>".
88 To be used where the scope requires it, i.e. outside of records.
89 """
90 if register_array is None:
91 return f"{self.name}_{register.name}"
93 register_array_name = self.qualified_register_array_name(register_array=register_array)
94 return f"{register_array_name}_{register.name}"
96 def qualified_register_array_name(self, register_array: RegisterArray) -> str:
97 """
98 Get the qualified register array name, e.g. "<module name>_<register array name>".
99 To be used where the scope requires it, i.e. outside of records.
100 """
101 return f"{self.name}_{register_array.name}"
103 def qualified_field_name(
104 self,
105 register: Register,
106 field: RegisterField,
107 register_array: RegisterArray | None = None,
108 ) -> str:
109 """
110 Get the qualified field name, e.g. "<module name>_<register name>_<field_name>".
111 To be used where the scope requires it, i.e. outside of records.
112 """
113 register_name = self.qualified_register_name(
114 register=register, register_array=register_array
115 )
116 return f"{register_name}_{field.name}"
118 def get_indentation(self, indent: int | None = None) -> str:
119 """
120 Get the requested indentation in spaces.
121 Will use the default indentation for this generator if not specified.
122 """
123 indent = self.DEFAULT_INDENTATION_LEVEL if indent is None else indent
124 return " " * indent
126 def get_separator_line(self, indent: int | None = None) -> str:
127 """
128 Get a separator line, e.g. ``# ---------------------------------``.
129 """
130 indentation = self.get_indentation(indent=indent)
131 result = f"{indentation}{self.COMMENT_START} "
133 num_dash = 80 - len(result) - len(self.COMMENT_END)
134 result += "-" * num_dash
135 result += f"{self.COMMENT_END}\n"
137 return result
139 def comment(self, comment: str, indent: int | None = None) -> str:
140 """
141 Create a one-line comment.
142 """
143 indentation = self.get_indentation(indent=indent)
144 return f"{indentation}{self.COMMENT_START} {comment}{self.COMMENT_END}\n"
146 def comment_block(self, text: list[str], indent: int | None = None) -> str:
147 """
148 Create a comment block from a list of text lines.
149 """
150 return "".join(self.comment(comment=line, indent=indent) for line in text)
152 @staticmethod
153 def register_description(
154 register: Register, register_array: RegisterArray | None = None
155 ) -> str:
156 """
157 Get a comment describing the register.
158 """
159 result = f"'{register.name}' register"
161 if register_array is None:
162 return result
164 return f"{result} within the '{register_array.name}' register array"
166 def field_description(
167 self,
168 register: Register,
169 field: RegisterField,
170 register_array: RegisterArray | None = None,
171 ) -> str:
172 """
173 Get a comment describing the field.
174 """
175 register_description = self.register_description(
176 register=register, register_array=register_array
177 )
178 return f"'{field.name}' field in the {register_description}"
180 @staticmethod
181 def field_setter_should_read_modify_write(register: Register) -> bool:
182 """
183 Returns True if a field value setter should read-modify-write the register.
185 Is only true if the register is of a writeable type where the software can also read back
186 a previously-written value.
187 Furthermore, read-modify-write only makes sense if there is more than one field, otherwise
188 it is a waste of CPU cycles.
189 """
190 if not register.fields:
191 raise ValueError("Should not end up here if the register has no fields.")
193 if register.mode == REGISTER_MODES["r_w"]:
194 return len(register.fields) > 1
196 if register.mode in [
197 REGISTER_MODES["w"],
198 REGISTER_MODES["wpulse"],
199 REGISTER_MODES["r_wpulse"],
200 ]:
201 return False
203 raise ValueError(f"Got non-writeable register: {register}")
205 @staticmethod
206 def to_pascal_case(snake_string: str) -> str:
207 """
208 Converts e.g., "my_funny_string" to "MyFunnyString".
210 Pascal case is like camel case but with the initial character being capitalized.
211 I.e. how classes are named in Python, C and C++.
212 """
213 return snake_string.title().replace("_", "")