Coverage for hdl_registers/register_list.py: 97%
90 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
11import copy
12import hashlib
13from pathlib import Path
14from typing import TYPE_CHECKING, Optional, Union
16# Local folder libraries
17from .constant.bit_vector_constant import UnsignedVector, UnsignedVectorConstant
18from .constant.boolean_constant import BooleanConstant
19from .constant.float_constant import FloatConstant
20from .constant.integer_constant import IntegerConstant
21from .constant.string_constant import StringConstant
22from .register import Register
23from .register_array import RegisterArray
24from .register_mode import RegisterMode
26if TYPE_CHECKING:
27 # Local folder libraries
28 from .constant.constant import Constant
31class RegisterList:
32 """
33 Used to handle the registers of a module. Also known as a register map.
34 """
36 def __init__(self, name: str, source_definition_file: Optional[Path] = None):
37 """
38 Arguments:
39 name: The name of this register list.
40 Typically the name of the module that uses it.
41 source_definition_file: The source file that defined this register list.
42 Will be displayed in generated source code and documentation for traceability.
44 Can be set to ``None`` if this information does not make sense in the current
45 use case.
46 """
47 self.name = name
48 self.source_definition_file = source_definition_file
50 self.register_objects: list[Union[Register, RegisterArray]] = []
51 self.constants: list["Constant"] = []
53 @classmethod
54 def from_default_registers(
55 cls, name: str, source_definition_file: Path, default_registers: list[Register]
56 ) -> "RegisterList":
57 """
58 Factory method. Create a ``RegisterList`` object from a plain list of registers.
60 Arguments:
61 name: The name of this register list.
62 source_definition_file: The source file that defined this register list.
63 Will be displayed in generated source code and documentation for traceability.
65 Can be set to ``None`` if this information does not make sense in the current
66 use case.
67 default_registers: These registers will be inserted at the beginning of the
68 register list.
69 """
70 # Before proceeding, perform a basic sanity check.
71 # If the indexes are not correct, that will cause problems with the default registers
72 # as well as all upcoming registers.
73 for list_idx, register in enumerate(default_registers):
74 if register.index != list_idx:
75 message = (
76 f'Default register index mismatch for "{register.name}". '
77 f'Got "{register.index}", expected "{list_idx}".'
78 )
79 raise ValueError(message)
81 register_list = cls(name=name, source_definition_file=source_definition_file)
82 register_list.register_objects = copy.deepcopy(default_registers) # type: ignore[arg-type]
84 return register_list
86 def append_register(self, name: str, mode: "RegisterMode", description: str) -> Register:
87 """
88 Append a register to this register list.
90 Arguments:
91 name: The name of the register.
92 mode: A mode that decides the behavior of the register.
93 See https://hdl-registers.com/rst/basic_feature/basic_feature_register_modes.html
94 for more information about the different modes.
95 description: Textual register description.
96 Return:
97 The register object that was created.
98 """
99 if self.register_objects:
100 index = self.register_objects[-1].index + 1
101 else:
102 index = 0
104 register = Register(name, index, mode, description)
105 self.register_objects.append(register)
107 return register
109 def append_register_array(self, name: str, length: int, description: str) -> RegisterArray:
110 """
111 Append a register array to this list.
113 Arguments:
114 name: The name of the register array.
115 length: The number of times the register sequence shall be repeated.
116 description: Textual description of the register array.
117 Return:
118 The register array object that was created.
119 """
120 if self.register_objects:
121 base_index = self.register_objects[-1].index + 1
122 else:
123 base_index = 0
124 register_array = RegisterArray(
125 name=name, base_index=base_index, length=length, description=description
126 )
128 self.register_objects.append(register_array)
129 return register_array
131 def get_register(
132 self, register_name: str, register_array_name: Optional[str] = None
133 ) -> Register:
134 """
135 Get a register from this list.
136 Will raise exception if no register matches.
138 If ``register_array_name`` is specified, this method will search for registers within
139 that array.
140 If it is not specified, the method will only search for plain registers (not registers
141 in any arrays).
143 Arguments:
144 register_name: The name of the register.
145 register_array_name: If the register is within a register array, this is the name of
146 the array.
147 Return:
148 The register.
149 """
150 if register_array_name is not None:
151 register_array = self.get_register_array(name=register_array_name)
152 return register_array.get_register(name=register_name)
154 for register_object in self.register_objects:
155 if isinstance(register_object, Register) and register_object.name == register_name:
156 return register_object
158 raise ValueError(
159 f'Could not find register "{register_name}" within register list "{self.name}"'
160 )
162 def get_register_array(self, name: str) -> RegisterArray:
163 """
164 Get a register array from this list. Will raise exception if no register array matches.
166 Arguments:
167 name: The name of the register array.
168 Return:
169 The register array.
170 """
171 for register_object in self.register_objects:
172 if isinstance(register_object, RegisterArray) and register_object.name == name:
173 return register_object
175 raise ValueError(
176 f'Could not find register array "{name}" within register list "{self.name}"'
177 )
179 def get_register_index(
180 self,
181 register_name: str,
182 register_array_name: Optional[str] = None,
183 register_array_index: Optional[int] = None,
184 ) -> int:
185 """
186 Get the zero-based index within the register list for the specified register.
188 Arguments:
189 register_name: The name of the register.
190 register_array_name: If the register is within a register array, the name of the array
191 must be specified.
192 register_array_index: If the register is within a register array, the array iteration
193 index must be specified.
195 Return:
196 The index.
197 """
198 if register_array_name is None or register_array_index is None:
199 # Target is plain register
200 register = self.get_register(register_name=register_name)
202 return register.index
204 # Target is in register array
205 register_array = self.get_register_array(name=register_array_name)
206 register_array_start_index = register_array.get_start_index(
207 array_index=register_array_index
208 )
210 register = register_array.get_register(name=register_name)
211 register_index = register.index
213 return register_array_start_index + register_index
215 def add_constant(
216 self,
217 name: str,
218 value: Union[bool, float, int, str, UnsignedVector],
219 description: str,
220 ) -> "Constant":
221 """
222 Add a constant. Will be available in the generated packages and headers.
223 Will automatically determine the type of the constant based on the type of the
224 ``value`` argument.
226 Arguments:
227 name: The name of the constant.
228 value: The constant value.
229 description: Textual description for the constant.
230 Return:
231 The constant object that was created.
232 """
233 # Note that this is a sub-type of 'int', hence it must be before the check below.
234 if isinstance(value, bool):
235 constant: "Constant" = BooleanConstant(name=name, value=value, description=description)
237 elif isinstance(value, int):
238 constant = IntegerConstant(name=name, value=value, description=description)
240 elif isinstance(value, float):
241 constant = FloatConstant(name=name, value=value, description=description)
243 # Note that this is a sub-type of 'str', hence it must be before the check below.
244 elif isinstance(value, UnsignedVector):
245 constant = UnsignedVectorConstant(name=name, value=value, description=description)
247 elif isinstance(value, str):
248 constant = StringConstant(name=name, value=value, description=description)
250 else:
251 message = f'Error while parsing constant "{name}": Unknown type "{type(value)}".'
252 raise TypeError(message)
254 self.constants.append(constant)
255 return constant
257 def get_constant(self, name: str) -> "Constant":
258 """
259 Get a constant from this list. Will raise exception if no constant matches.
261 Arguments:
262 name: The name of the constant.
263 Return:
264 The constant.
265 """
266 for constant in self.constants:
267 if constant.name == name:
268 return constant
270 raise ValueError(f'Could not find constant "{name}" within register list "{self.name}"')
272 @property
273 def object_hash(self) -> str:
274 """
275 Get a hash of this object representation.
276 SHA1 is the fastest method according to e.g.
277 http://atodorov.org/blog/2013/02/05/performance-test-md5-sha1-sha256-sha512/
278 Result is a lowercase hexadecimal string.
279 """
280 return hashlib.sha1(repr(self).encode()).hexdigest()
282 def __repr__(self) -> str:
283 return f"""{self.__class__.__name__}(\
284name={self.name},\
285source_definition_file={repr(self.source_definition_file)},\
286register_objects={','.join([repr(register_object) for register_object in self.register_objects])},\
287constants={','.join([repr(constant) for constant in self.constants])},\
288)"""