Coverage for hdl_registers/register_list.py: 97%
86 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-22 20:51 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-22 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
25if TYPE_CHECKING:
26 # Local folder libraries
27 from .constant.constant import Constant
30class RegisterList:
31 """
32 Used to handle the registers of a module. Also known as a register map.
33 """
35 def __init__(self, name: str, source_definition_file: Optional[Path] = None):
36 """
37 Arguments:
38 name: The name of this register list.
39 Typically the name of the module that uses it.
40 source_definition_file: The source file that defined this register list.
41 Will be displayed in generated source code and documentation for traceability.
43 Can be set to ``None`` if this information does not make sense in the current
44 use case.
45 """
46 self.name = name
47 self.source_definition_file = source_definition_file
49 self.register_objects: list[Union[Register, RegisterArray]] = []
50 self.constants: list["Constant"] = []
52 @classmethod
53 def from_default_registers(
54 cls, name: str, source_definition_file: Path, default_registers: list[Register]
55 ) -> "RegisterList":
56 """
57 Factory method. Create a ``RegisterList`` object from a plain list of registers.
59 Arguments:
60 name: The name of this register list.
61 source_definition_file: The source file that defined this register list.
62 Will be displayed in generated source code and documentation for traceability.
64 Can be set to ``None`` if this information does not make sense in the current
65 use case.
66 default_registers: These registers will be inserted at the beginning of the
67 register list.
68 """
69 # Before proceeding, perform a basic sanity check.
70 # If the indexes are not correct, that will cause problems with the default registers
71 # as well as all upcoming registers.
72 for list_idx, register in enumerate(default_registers):
73 if register.index != list_idx:
74 message = (
75 f'Default register index mismatch for "{register.name}". '
76 f'Got "{register.index}", expected "{list_idx}".'
77 )
78 raise ValueError(message)
80 register_list = cls(name=name, source_definition_file=source_definition_file)
81 register_list.register_objects = copy.deepcopy(default_registers) # type: ignore[arg-type]
83 return register_list
85 def append_register(self, name: str, mode: str, description: str) -> Register:
86 """
87 Append a register to this register list.
89 Arguments:
90 name: The name of the register.
91 mode: A valid register mode.
92 Should be a key in the ``REGISTER_MODES`` dictionary.
93 I.e. the shorthand name for the mode, e.g. ``"r_w"``.
94 See https://hdl-registers.com/rst/basic_feature/basic_feature_register_modes.html
95 for more information.
96 description: Textual register description.
97 Return:
98 The register object that was created.
99 """
100 if self.register_objects:
101 index = self.register_objects[-1].index + 1
102 else:
103 index = 0
105 register = Register(name, index, mode, description)
106 self.register_objects.append(register)
108 return register
110 def append_register_array(self, name: str, length: int, description: str) -> RegisterArray:
111 """
112 Append a register array to this list.
114 Arguments:
115 name: The name of the register array.
116 length: The number of times the register sequence shall be repeated.
117 description: Textual description of the register array.
118 Return:
119 The register array object that was created.
120 """
121 if self.register_objects:
122 base_index = self.register_objects[-1].index + 1
123 else:
124 base_index = 0
125 register_array = RegisterArray(
126 name=name, base_index=base_index, length=length, description=description
127 )
129 self.register_objects.append(register_array)
130 return register_array
132 def get_register(self, name: str) -> Register:
133 """
134 Get a register from this list.
135 Will only find plain registers, not registers in a register array.
136 Will raise exception if no register matches.
138 Arguments:
139 name: The name of the register.
140 Return:
141 The register.
142 """
143 for register_object in self.register_objects:
144 if isinstance(register_object, Register) and register_object.name == name:
145 return register_object
147 raise ValueError(f'Could not find register "{name}" within register list "{self.name}"')
149 def get_register_array(self, name: str) -> RegisterArray:
150 """
151 Get a register array from this list. Will raise exception if no register array matches.
153 Arguments:
154 name: The name of the register array.
155 Return:
156 The register array.
157 """
158 for register_object in self.register_objects:
159 if isinstance(register_object, RegisterArray) and register_object.name == name:
160 return register_object
162 raise ValueError(
163 f'Could not find register array "{name}" within register list "{self.name}"'
164 )
166 def get_register_index(
167 self,
168 register_name: str,
169 register_array_name: Optional[str] = None,
170 register_array_index: Optional[int] = None,
171 ) -> int:
172 """
173 Get the zero-based index within the register list for the specified register.
175 Arguments:
176 register_name: The name of the register.
177 register_array_name: If the register is within a register array, the name of the array
178 must be specified.
179 register_array_index: If the register is within a register array, the array iteration
180 index must be specified.
182 Return:
183 The index.
184 """
185 if register_array_name is None or register_array_index is None:
186 # Target is plain register
187 register = self.get_register(register_name)
189 return register.index
191 # Target is in register array
192 register_array = self.get_register_array(register_array_name)
193 register_array_start_index = register_array.get_start_index(register_array_index)
195 register = register_array.get_register(register_name)
196 register_index = register.index
198 return register_array_start_index + register_index
200 def add_constant(
201 self,
202 name: str,
203 value: Union[bool, float, int, str, UnsignedVector],
204 description: str,
205 ) -> "Constant":
206 """
207 Add a constant. Will be available in the generated packages and headers.
208 Will automatically determine the type of the constant based on the type of the
209 ``value`` argument.
211 Arguments:
212 name: The name of the constant.
213 value: The constant value.
214 description: Textual description for the constant.
215 Return:
216 The constant object that was created.
217 """
218 # Note that this is a sub-type of 'int', hence it must be before the check below.
219 if isinstance(value, bool):
220 constant: "Constant" = BooleanConstant(name=name, value=value, description=description)
222 elif isinstance(value, int):
223 constant = IntegerConstant(name=name, value=value, description=description)
225 elif isinstance(value, float):
226 constant = FloatConstant(name=name, value=value, description=description)
228 # Note that this is a sub-type of 'str', hence it must be before the check below.
229 elif isinstance(value, UnsignedVector):
230 constant = UnsignedVectorConstant(name=name, value=value, description=description)
232 elif isinstance(value, str):
233 constant = StringConstant(name=name, value=value, description=description)
235 else:
236 message = f'Error while parsing constant "{name}": Unknown type "{type(value)}".'
237 raise TypeError(message)
239 self.constants.append(constant)
240 return constant
242 def get_constant(self, name: str) -> "Constant":
243 """
244 Get a constant from this list. Will raise exception if no constant matches.
246 Arguments:
247 name: The name of the constant.
248 Return:
249 The constant.
250 """
251 for constant in self.constants:
252 if constant.name == name:
253 return constant
255 raise ValueError(f'Could not find constant "{name}" within register list "{self.name}"')
257 @property
258 def object_hash(self) -> str:
259 """
260 Get a hash of this object representation.
261 SHA1 is the fastest method according to e.g.
262 http://atodorov.org/blog/2013/02/05/performance-test-md5-sha1-sha256-sha512/
263 Result is a lowercase hexadecimal string.
264 """
265 return hashlib.sha1(repr(self).encode()).hexdigest()
267 def __repr__(self) -> str:
268 return f"""{self.__class__.__name__}(\
269name={self.name},\
270source_definition_file={repr(self.source_definition_file)},\
271register_objects={','.join([repr(register_object) for register_object in self.register_objects])},\
272constants={','.join([repr(constant) for constant in self.constants])},\
273)"""