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

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 

10# Standard libraries 

11import copy 

12import hashlib 

13from pathlib import Path 

14from typing import TYPE_CHECKING, Optional, Union 

15 

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 

25 

26if TYPE_CHECKING: 

27 # Local folder libraries 

28 from .constant.constant import Constant 

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

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[Union[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) # type: ignore[arg-type] 

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

103 

104 register = Register(name, index, mode, description) 

105 self.register_objects.append(register) 

106 

107 return register 

108 

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

110 """ 

111 Append a register array to this list. 

112 

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 ) 

127 

128 self.register_objects.append(register_array) 

129 return register_array 

130 

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. 

137 

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

142 

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) 

153 

154 for register_object in self.register_objects: 

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

156 return register_object 

157 

158 raise ValueError( 

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

160 ) 

161 

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. 

165 

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 

174 

175 raise ValueError( 

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

177 ) 

178 

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. 

187 

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. 

194 

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) 

201 

202 return register.index 

203 

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 ) 

209 

210 register = register_array.get_register(name=register_name) 

211 register_index = register.index 

212 

213 return register_array_start_index + register_index 

214 

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. 

225 

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) 

236 

237 elif isinstance(value, int): 

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

239 

240 elif isinstance(value, float): 

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

242 

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) 

246 

247 elif isinstance(value, str): 

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

249 

250 else: 

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

252 raise TypeError(message) 

253 

254 self.constants.append(constant) 

255 return constant 

256 

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

258 """ 

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

260 

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 

269 

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

271 

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

281 

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