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

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# -------------------------------------------------------------------------------------------------- 

9 

10from __future__ import annotations 

11 

12import copy 

13import hashlib 

14from typing import TYPE_CHECKING 

15 

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 

23 

24if TYPE_CHECKING: 

25 from pathlib import Path 

26 

27 from .constant.constant import Constant 

28 from .register_mode import RegisterMode 

29 

30 

31class RegisterList: 

32 """ 

33 Used to handle the registers of a module. Also known as a register map. 

34 """ 

35 

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. 

43 

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 

49 

50 self.register_objects: list[Register | RegisterArray] = [] 

51 self.constants: list[Constant] = [] 

52 

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. 

59 

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. 

64 

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) 

80 

81 register_list = cls(name=name, source_definition_file=source_definition_file) 

82 register_list.register_objects = copy.deepcopy(default_registers) 

83 

84 return register_list 

85 

86 def append_register(self, name: str, mode: RegisterMode, description: str) -> Register: 

87 """ 

88 Append a register to this register list. 

89 

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 

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) 

102 

103 self.register_objects.append(register) 

104 return register 

105 

106 def append_register_array(self, name: str, length: int, description: str) -> RegisterArray: 

107 """ 

108 Append a register array to this list. 

109 

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. 

114 

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 ) 

122 

123 self.register_objects.append(register_array) 

124 return register_array 

125 

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. 

130 

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). 

135 

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. 

140 

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) 

147 

148 for register_object in self.register_objects: 

149 if isinstance(register_object, Register) and register_object.name == register_name: 

150 return register_object 

151 

152 raise ValueError( 

153 f'Could not find register "{register_name}" within register list "{self.name}"' 

154 ) 

155 

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. 

159 

160 Arguments: 

161 name: The name of the register array. 

162 

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 

169 

170 raise ValueError( 

171 f'Could not find register array "{name}" within register list "{self.name}"' 

172 ) 

173 

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. 

182 

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. 

189 

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) 

196 

197 return register.index 

198 

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 ) 

204 

205 register = register_array.get_register(name=register_name) 

206 register_index = register.index 

207 

208 return register_array_start_index + register_index 

209 

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. 

220 

221 Arguments: 

222 name: The name of the constant. 

223 value: The constant value. 

224 description: Textual description for the constant. 

225 

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) 

232 

233 elif isinstance(value, int): 

234 constant = IntegerConstant(name=name, value=value, description=description) 

235 

236 elif isinstance(value, float): 

237 constant = FloatConstant(name=name, value=value, description=description) 

238 

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) 

242 

243 elif isinstance(value, str): 

244 constant = StringConstant(name=name, value=value, description=description) 

245 

246 else: 

247 message = f'Error while parsing constant "{name}": Unknown type "{type(value)}".' 

248 raise TypeError(message) 

249 

250 self.constants.append(constant) 

251 return constant 

252 

253 def get_constant(self, name: str) -> Constant: 

254 """ 

255 Get a constant from this list. Will raise exception if no constant matches. 

256 

257 Arguments: 

258 name: The name of the constant. 

259 

260 Return: 

261 The constant. 

262 """ 

263 for constant in self.constants: 

264 if constant.name == name: 

265 return constant 

266 

267 raise ValueError(f'Could not find constant "{name}" within register list "{self.name}"') 

268 

269 @property 

270 def object_hash(self) -> str: 

271 """ 

272 Get a hash of this object representation. 

273 

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 

282 

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)"""