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