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

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 

24 

25if TYPE_CHECKING: 

26 # Local folder libraries 

27 from .constant.constant import Constant 

28 

29 

30class RegisterList: 

31 """ 

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

33 """ 

34 

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. 

42 

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 

48 

49 self.register_objects: list[Union[Register, RegisterArray]] = [] 

50 self.constants: list["Constant"] = [] 

51 

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. 

58 

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. 

63 

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) 

79 

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] 

82 

83 return register_list 

84 

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

86 """ 

87 Append a register to this register list. 

88 

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 

104 

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

106 self.register_objects.append(register) 

107 

108 return register 

109 

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

111 """ 

112 Append a register array to this list. 

113 

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 ) 

128 

129 self.register_objects.append(register_array) 

130 return register_array 

131 

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. 

137 

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 

146 

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

148 

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. 

152 

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 

161 

162 raise ValueError( 

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

164 ) 

165 

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. 

174 

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. 

181 

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) 

188 

189 return register.index 

190 

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) 

194 

195 register = register_array.get_register(register_name) 

196 register_index = register.index 

197 

198 return register_array_start_index + register_index 

199 

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. 

210 

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) 

221 

222 elif isinstance(value, int): 

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

224 

225 elif isinstance(value, float): 

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

227 

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) 

231 

232 elif isinstance(value, str): 

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

234 

235 else: 

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

237 raise TypeError(message) 

238 

239 self.constants.append(constant) 

240 return constant 

241 

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

243 """ 

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

245 

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 

254 

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

256 

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

266 

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