Coverage for hdl_registers/register_list.py: 94%
87 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
12import copy
13import hashlib
14from typing import TYPE_CHECKING
16from .constant.bit_vector_constant import UnsignedVector, UnsignedVectorConstant
17from .constant.boolean_constant import BooleanConstant
18from .constant.float_constant import FloatConstant
19from .constant.integer_constant import IntegerConstant
20from .constant.string_constant import StringConstant
21from .register import Register
22from .register_array import RegisterArray
24if TYPE_CHECKING:
25 from pathlib import Path
27 from .constant.constant import Constant
28 from .register_mode import RegisterMode
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: Path | None = None) -> 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[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)
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.
97 Return:
98 The register object that was created.
99 """
100 index = self.register_objects[-1].index + 1 if self.register_objects else 0
101 register = Register(name=name, index=index, mode=mode, description=description)
103 self.register_objects.append(register)
104 return register
106 def append_register_array(self, name: str, length: int, description: str) -> RegisterArray:
107 """
108 Append a register array to this list.
110 Arguments:
111 name: The name of the register array.
112 length: The number of times the register sequence shall be repeated.
113 description: Textual description of the register array.
115 Return:
116 The register array object that was created.
117 """
118 base_index = self.register_objects[-1].index + 1 if self.register_objects else 0
119 register_array = RegisterArray(
120 name=name, base_index=base_index, length=length, description=description
121 )
123 self.register_objects.append(register_array)
124 return register_array
126 def get_register(self, register_name: str, register_array_name: str | None = None) -> Register:
127 """
128 Get a register from this list.
129 Will raise exception if no register matches.
131 If ``register_array_name`` is specified, this method will search for registers within
132 that array.
133 If it is not specified, the method will only search for plain registers (not registers
134 in any arrays).
136 Arguments:
137 register_name: The name of the register.
138 register_array_name: If the register is within a register array, this is the name of
139 the array.
141 Return:
142 The register.
143 """
144 if register_array_name is not None:
145 register_array = self.get_register_array(name=register_array_name)
146 return register_array.get_register(name=register_name)
148 for register_object in self.register_objects:
149 if isinstance(register_object, Register) and register_object.name == register_name:
150 return register_object
152 raise ValueError(
153 f'Could not find register "{register_name}" within register list "{self.name}"'
154 )
156 def get_register_array(self, name: str) -> RegisterArray:
157 """
158 Get a register array from this list. Will raise exception if no register array matches.
160 Arguments:
161 name: The name of the register array.
163 Return:
164 The register array.
165 """
166 for register_object in self.register_objects:
167 if isinstance(register_object, RegisterArray) and register_object.name == name:
168 return register_object
170 raise ValueError(
171 f'Could not find register array "{name}" within register list "{self.name}"'
172 )
174 def get_register_index(
175 self,
176 register_name: str,
177 register_array_name: str | None = None,
178 register_array_index: int | None = None,
179 ) -> int:
180 """
181 Get the zero-based index within the register list for the specified register.
183 Arguments:
184 register_name: The name of the register.
185 register_array_name: If the register is within a register array, the name of the array
186 must be specified.
187 register_array_index: If the register is within a register array, the array iteration
188 index must be specified.
190 Return:
191 The index.
192 """
193 if register_array_name is None or register_array_index is None:
194 # Target is plain register
195 register = self.get_register(register_name=register_name)
197 return register.index
199 # Target is in register array
200 register_array = self.get_register_array(name=register_array_name)
201 register_array_start_index = register_array.get_start_index(
202 array_index=register_array_index
203 )
205 register = register_array.get_register(name=register_name)
206 register_index = register.index
208 return register_array_start_index + register_index
210 def add_constant(
211 self,
212 name: str,
213 value: bool | float | str | UnsignedVector,
214 description: str,
215 ) -> Constant:
216 """
217 Add a constant. Will be available in the generated packages and headers.
218 Will automatically determine the type of the constant based on the type of the
219 ``value`` argument.
221 Arguments:
222 name: The name of the constant.
223 value: The constant value.
224 description: Textual description for the constant.
226 Return:
227 The constant object that was created.
228 """
229 # Note that this is a sub-type of 'int', hence it must be before the check below.
230 if isinstance(value, bool):
231 constant: Constant = BooleanConstant(name=name, value=value, description=description)
233 elif isinstance(value, int):
234 constant = IntegerConstant(name=name, value=value, description=description)
236 elif isinstance(value, float):
237 constant = FloatConstant(name=name, value=value, description=description)
239 # Note that this is a sub-type of 'str', hence it must be before the check below.
240 elif isinstance(value, UnsignedVector):
241 constant = UnsignedVectorConstant(name=name, value=value, description=description)
243 elif isinstance(value, str):
244 constant = StringConstant(name=name, value=value, description=description)
246 else:
247 message = f'Error while parsing constant "{name}": Unknown type "{type(value)}".'
248 raise TypeError(message)
250 self.constants.append(constant)
251 return constant
253 def get_constant(self, name: str) -> Constant:
254 """
255 Get a constant from this list. Will raise exception if no constant matches.
257 Arguments:
258 name: The name of the constant.
260 Return:
261 The constant.
262 """
263 for constant in self.constants:
264 if constant.name == name:
265 return constant
267 raise ValueError(f'Could not find constant "{name}" within register list "{self.name}"')
269 @property
270 def object_hash(self) -> str:
271 """
272 Get a hash of this object representation.
274 SHA1 is the fastest method according to e.g.
275 http://atodorov.org/blog/2013/02/05/performance-test-md5-sha1-sha256-sha512/
276 Result is a lowercase hexadecimal string.
277 """
278 # Is considered insecure, but we don't need a cryptographically secure hash here.
279 # Just something that is fast and unique.
280 # https://docs.astral.sh/ruff/rules/hashlib-insecure-hash-function/
281 return hashlib.sha1(repr(self).encode()).hexdigest() # noqa: S324
283 def __repr__(self) -> str:
284 return f"""{self.__class__.__name__}(\
285name={self.name},\
286source_definition_file={self.source_definition_file!r},\
287register_objects={",".join([repr(register_object) for register_object in self.register_objects])},\
288constants={",".join([repr(constant) for constant in self.constants])},\
289)"""