Coverage for hdl_registers/generator/register_code_generator_helpers.py: 96%
101 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-29 06:41 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-29 06:41 +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 over 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 iterate_register_objects(register_list=self.register_list)
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 yield from iterate_registers(register_list=self.register_list)
62 def iterate_plain_registers(self) -> Iterator[Register]:
63 """
64 Iterate over all plain registers (i.e. registers not in array) in the register list.
65 """
66 for register_object in self.iterate_register_objects():
67 if isinstance(register_object, Register):
68 yield register_object
70 def iterate_register_arrays(self) -> Iterator[RegisterArray]:
71 """
72 Iterate over all register arrays in the register list.
73 """
74 for register_object in self.iterate_register_objects():
75 if isinstance(register_object, RegisterArray):
76 yield register_object
78 def qualified_register_name(
79 self, register: Register, register_array: RegisterArray | None = None
80 ) -> str:
81 """
82 Get the qualified register name, e.g. "<module name>_<register name>".
83 To be used where the scope requires it, i.e. outside of records.
84 """
85 return qualified_register_name(
86 register_list=self.register_list, register=register, register_array=register_array
87 )
89 def qualified_register_array_name(self, register_array: RegisterArray) -> str:
90 """
91 Get the qualified register array name, e.g. "<module name>_<register array name>".
92 To be used where the scope requires it, i.e. outside of records.
93 """
94 return qualified_register_array_name(
95 register_list=self.register_list, register_array=register_array
96 )
98 def qualified_field_name(
99 self,
100 register: Register,
101 field: RegisterField,
102 register_array: RegisterArray | None = None,
103 ) -> str:
104 """
105 Get the qualified field name, e.g. "<module name>_<register name>_<field_name>".
106 To be used where the scope requires it, i.e. outside of records.
107 """
108 return qualified_field_name(
109 register_list=self.register_list,
110 register=register,
111 field=field,
112 register_array=register_array,
113 )
115 @staticmethod
116 def register_utilized_width(register: Register) -> int:
117 """
118 Get the number of bits that are utilized by the fields in the supplied register.
119 Note that this is not always the same as the width of the register.
120 Some generator implementations can be optimized by only taking into account the
121 bits that are actually utilized.
123 Note that if the register has no fields, we do not really know what the user is doing with
124 it, and we have to assume that the full width is used.
125 """
126 if not register.fields:
127 return 32
129 return register.fields[-1].base_index + register.fields[-1].width
131 @staticmethod
132 def register_default_value_uint(register: Register) -> int:
133 """
134 Get the default value of the supplied register, as an unsigned integer.
135 Depends on the default values of the register fields.
136 """
137 default_value = 0
138 for field in register.fields:
139 default_value += field.default_value_uint * 2**field.base_index
141 return default_value
143 def get_indentation(self, indent: int | None = None) -> str:
144 """
145 Get the requested indentation in spaces.
146 Will use the default indentation for this generator if not specified.
147 """
148 indent = self.DEFAULT_INDENTATION_LEVEL if indent is None else indent
149 return " " * indent
151 def get_separator_line(self, indent: int | None = None) -> str:
152 """
153 Get a separator line, e.g. ``# ---------------------------------``.
154 """
155 indentation = self.get_indentation(indent=indent)
156 result = f"{indentation}{self.COMMENT_START} "
158 num_dash = 80 - len(result) - len(self.COMMENT_END)
159 result += "-" * num_dash
160 result += f"{self.COMMENT_END}\n"
162 return result
164 def comment(self, comment: str, indent: int | None = None) -> str:
165 """
166 Create a one-line comment.
167 """
168 indentation = self.get_indentation(indent=indent)
169 return f"{indentation}{self.COMMENT_START} {comment}{self.COMMENT_END}\n"
171 def comment_block(self, text: list[str], indent: int | None = None) -> str:
172 """
173 Create a comment block from a list of text lines.
174 """
175 return "".join(self.comment(comment=line, indent=indent) for line in text)
177 @staticmethod
178 def register_description(
179 register: Register, register_array: RegisterArray | None = None
180 ) -> str:
181 """
182 Get a comment describing the register.
183 """
184 result = f"'{register.name}' register"
186 if register_array is None:
187 return result
189 return f"{result} within the '{register_array.name}' register array"
191 def field_description(
192 self,
193 register: Register,
194 field: RegisterField,
195 register_array: RegisterArray | None = None,
196 ) -> str:
197 """
198 Get a comment describing the field.
199 """
200 register_description = self.register_description(
201 register=register, register_array=register_array
202 )
203 return f"'{field.name}' field in the {register_description}"
205 @staticmethod
206 def field_setter_should_read_modify_write(register: Register) -> bool:
207 """
208 Returns True if a field value setter should read-modify-write the register.
210 Is only true if the register is of a writeable type where the software can also read back
211 a previously-written value.
212 Furthermore, read-modify-write only makes sense if there is more than one field, otherwise
213 it is a waste of CPU cycles.
214 """
215 if not register.fields:
216 raise ValueError("Should not end up here if the register has no fields.")
218 if register.mode == REGISTER_MODES["r_w"]:
219 return len(register.fields) > 1
221 if register.mode in [
222 REGISTER_MODES["w"],
223 REGISTER_MODES["wpulse"],
224 REGISTER_MODES["r_wpulse"],
225 ]:
226 return False
228 raise ValueError(f"Got non-writeable register: {register}")
230 @staticmethod
231 def to_pascal_case(snake_string: str) -> str:
232 """
233 Converts e.g., "my_funny_string" to "MyFunnyString".
235 Pascal case is like camel case but with the initial character being capitalized.
236 I.e. how classes are named in Python, C and C++.
237 """
238 return snake_string.title().replace("_", "")
241def iterate_register_objects(register_list: RegisterList) -> Iterator[Register | RegisterArray]:
242 """
243 Iterate over all register objects in the register list.
244 I.e. all plain registers and all register arrays.
245 """
246 yield from register_list.register_objects
249def iterate_registers(
250 register_list: RegisterList,
251) -> Iterator[tuple[Register, RegisterArray | None]]:
252 """
253 Iterate over all registers, plain or in array, in the register list.
255 Return:
256 If the register is plain, the array return value in the tuple will be ``None``.
257 If the register is in an array, the array return value will conversely be non-``None``.
258 """
259 for register_object in iterate_register_objects(register_list=register_list):
260 if isinstance(register_object, Register):
261 yield (register_object, None)
262 else:
263 for register in register_object.registers:
264 yield (register, register_object)
267def qualified_register_name(
268 register_list: RegisterList, register: Register, register_array: RegisterArray | None = None
269) -> str:
270 """
271 Get the qualified register name, e.g. "<module name>_<register name>".
272 To be used where the scope requires it, i.e. outside of records.
273 """
274 if register_array is None:
275 return f"{register_list.name}_{register.name}"
277 register_array_name = qualified_register_array_name(
278 register_list=register_list, register_array=register_array
279 )
280 return f"{register_array_name}_{register.name}"
283def qualified_register_array_name(
284 register_list: RegisterList, register_array: RegisterArray
285) -> str:
286 """
287 Get the qualified register array name, e.g. "<module name>_<register array name>".
288 To be used where the scope requires it, i.e. outside of records.
289 """
290 return f"{register_list.name}_{register_array.name}"
293def qualified_field_name(
294 register_list: RegisterList,
295 register: Register,
296 field: RegisterField,
297 register_array: RegisterArray | None = None,
298) -> str:
299 """
300 Get the qualified field name, e.g. "<module name>_<register name>_<field_name>".
301 To be used where the scope requires it, i.e. outside of records.
302 """
303 register_name = qualified_register_name(
304 register_list=register_list, register=register, register_array=register_array
305 )
306 return f"{register_name}_{field.name}"