Coverage for hdl_registers/generator/register_code_generator_helpers.py: 96%

101 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-29 06:41 +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 

12from typing import TYPE_CHECKING 

13 

14from hdl_registers.register import Register 

15from hdl_registers.register_array import RegisterArray 

16from hdl_registers.register_modes import REGISTER_MODES 

17 

18if TYPE_CHECKING: 

19 from collections.abc import Iterator 

20 

21 from hdl_registers.constant.constant import Constant 

22 from hdl_registers.field.register_field import RegisterField 

23 from hdl_registers.register_list import RegisterList 

24 

25 

26class RegisterCodeGeneratorHelpers: 

27 """ 

28 Various helper methods that make register code generation easier. 

29 """ 

30 

31 # Defined in 'RegisterCodeGenerator' class, which shall also be inherited wherever this class 

32 # is used. 

33 register_list: RegisterList 

34 name: str 

35 DEFAULT_INDENTATION_LEVEL: int 

36 COMMENT_START: str 

37 COMMENT_END: str 

38 

39 def iterate_constants(self) -> Iterator[Constant]: 

40 """ 

41 Iterate over all constants in the register list. 

42 """ 

43 yield from self.register_list.constants 

44 

45 def iterate_register_objects(self) -> Iterator[Register | RegisterArray]: 

46 """ 

47 Iterate over all register objects in the register list. 

48 I.e. all plain registers and all register arrays. 

49 """ 

50 yield from iterate_register_objects(register_list=self.register_list) 

51 

52 def iterate_registers(self) -> Iterator[tuple[Register, RegisterArray | None]]: 

53 """ 

54 Iterate over all registers, plain or in array, in the register list. 

55 

56 Return: 

57 If the register is plain, the array return value in the tuple will be ``None``. 

58 If the register is in an array, the array return value will conversely be non-``None``. 

59 """ 

60 yield from iterate_registers(register_list=self.register_list) 

61 

62 def iterate_plain_registers(self) -> Iterator[Register]: 

63 """ 

64 Iterate over all plain registers (i.e. registers not in array) in the register list. 

65 """ 

66 for register_object in self.iterate_register_objects(): 

67 if isinstance(register_object, Register): 

68 yield register_object 

69 

70 def iterate_register_arrays(self) -> Iterator[RegisterArray]: 

71 """ 

72 Iterate over all register arrays in the register list. 

73 """ 

74 for register_object in self.iterate_register_objects(): 

75 if isinstance(register_object, RegisterArray): 

76 yield register_object 

77 

78 def qualified_register_name( 

79 self, register: Register, register_array: RegisterArray | None = None 

80 ) -> str: 

81 """ 

82 Get the qualified register name, e.g. "<module name>_<register name>". 

83 To be used where the scope requires it, i.e. outside of records. 

84 """ 

85 return qualified_register_name( 

86 register_list=self.register_list, register=register, register_array=register_array 

87 ) 

88 

89 def qualified_register_array_name(self, register_array: RegisterArray) -> str: 

90 """ 

91 Get the qualified register array name, e.g. "<module name>_<register array name>". 

92 To be used where the scope requires it, i.e. outside of records. 

93 """ 

94 return qualified_register_array_name( 

95 register_list=self.register_list, register_array=register_array 

96 ) 

97 

98 def qualified_field_name( 

99 self, 

100 register: Register, 

101 field: RegisterField, 

102 register_array: RegisterArray | None = None, 

103 ) -> str: 

104 """ 

105 Get the qualified field name, e.g. "<module name>_<register name>_<field_name>". 

106 To be used where the scope requires it, i.e. outside of records. 

107 """ 

108 return qualified_field_name( 

109 register_list=self.register_list, 

110 register=register, 

111 field=field, 

112 register_array=register_array, 

113 ) 

114 

115 @staticmethod 

116 def register_utilized_width(register: Register) -> int: 

117 """ 

118 Get the number of bits that are utilized by the fields in the supplied register. 

119 Note that this is not always the same as the width of the register. 

120 Some generator implementations can be optimized by only taking into account the 

121 bits that are actually utilized. 

122 

123 Note that if the register has no fields, we do not really know what the user is doing with 

124 it, and we have to assume that the full width is used. 

125 """ 

126 if not register.fields: 

127 return 32 

128 

129 return register.fields[-1].base_index + register.fields[-1].width 

130 

131 @staticmethod 

132 def register_default_value_uint(register: Register) -> int: 

133 """ 

134 Get the default value of the supplied register, as an unsigned integer. 

135 Depends on the default values of the register fields. 

136 """ 

137 default_value = 0 

138 for field in register.fields: 

139 default_value += field.default_value_uint * 2**field.base_index 

140 

141 return default_value 

142 

143 def get_indentation(self, indent: int | None = None) -> str: 

144 """ 

145 Get the requested indentation in spaces. 

146 Will use the default indentation for this generator if not specified. 

147 """ 

148 indent = self.DEFAULT_INDENTATION_LEVEL if indent is None else indent 

149 return " " * indent 

150 

151 def get_separator_line(self, indent: int | None = None) -> str: 

152 """ 

153 Get a separator line, e.g. ``# ---------------------------------``. 

154 """ 

155 indentation = self.get_indentation(indent=indent) 

156 result = f"{indentation}{self.COMMENT_START} " 

157 

158 num_dash = 80 - len(result) - len(self.COMMENT_END) 

159 result += "-" * num_dash 

160 result += f"{self.COMMENT_END}\n" 

161 

162 return result 

163 

164 def comment(self, comment: str, indent: int | None = None) -> str: 

165 """ 

166 Create a one-line comment. 

167 """ 

168 indentation = self.get_indentation(indent=indent) 

169 return f"{indentation}{self.COMMENT_START} {comment}{self.COMMENT_END}\n" 

170 

171 def comment_block(self, text: list[str], indent: int | None = None) -> str: 

172 """ 

173 Create a comment block from a list of text lines. 

174 """ 

175 return "".join(self.comment(comment=line, indent=indent) for line in text) 

176 

177 @staticmethod 

178 def register_description( 

179 register: Register, register_array: RegisterArray | None = None 

180 ) -> str: 

181 """ 

182 Get a comment describing the register. 

183 """ 

184 result = f"'{register.name}' register" 

185 

186 if register_array is None: 

187 return result 

188 

189 return f"{result} within the '{register_array.name}' register array" 

190 

191 def field_description( 

192 self, 

193 register: Register, 

194 field: RegisterField, 

195 register_array: RegisterArray | None = None, 

196 ) -> str: 

197 """ 

198 Get a comment describing the field. 

199 """ 

200 register_description = self.register_description( 

201 register=register, register_array=register_array 

202 ) 

203 return f"'{field.name}' field in the {register_description}" 

204 

205 @staticmethod 

206 def field_setter_should_read_modify_write(register: Register) -> bool: 

207 """ 

208 Returns True if a field value setter should read-modify-write the register. 

209 

210 Is only true if the register is of a writeable type where the software can also read back 

211 a previously-written value. 

212 Furthermore, read-modify-write only makes sense if there is more than one field, otherwise 

213 it is a waste of CPU cycles. 

214 """ 

215 if not register.fields: 

216 raise ValueError("Should not end up here if the register has no fields.") 

217 

218 if register.mode == REGISTER_MODES["r_w"]: 

219 return len(register.fields) > 1 

220 

221 if register.mode in [ 

222 REGISTER_MODES["w"], 

223 REGISTER_MODES["wpulse"], 

224 REGISTER_MODES["r_wpulse"], 

225 ]: 

226 return False 

227 

228 raise ValueError(f"Got non-writeable register: {register}") 

229 

230 @staticmethod 

231 def to_pascal_case(snake_string: str) -> str: 

232 """ 

233 Converts e.g., "my_funny_string" to "MyFunnyString". 

234 

235 Pascal case is like camel case but with the initial character being capitalized. 

236 I.e. how classes are named in Python, C and C++. 

237 """ 

238 return snake_string.title().replace("_", "") 

239 

240 

241def iterate_register_objects(register_list: RegisterList) -> Iterator[Register | RegisterArray]: 

242 """ 

243 Iterate over all register objects in the register list. 

244 I.e. all plain registers and all register arrays. 

245 """ 

246 yield from register_list.register_objects 

247 

248 

249def iterate_registers( 

250 register_list: RegisterList, 

251) -> Iterator[tuple[Register, RegisterArray | None]]: 

252 """ 

253 Iterate over all registers, plain or in array, in the register list. 

254 

255 Return: 

256 If the register is plain, the array return value in the tuple will be ``None``. 

257 If the register is in an array, the array return value will conversely be non-``None``. 

258 """ 

259 for register_object in iterate_register_objects(register_list=register_list): 

260 if isinstance(register_object, Register): 

261 yield (register_object, None) 

262 else: 

263 for register in register_object.registers: 

264 yield (register, register_object) 

265 

266 

267def qualified_register_name( 

268 register_list: RegisterList, register: Register, register_array: RegisterArray | None = None 

269) -> str: 

270 """ 

271 Get the qualified register name, e.g. "<module name>_<register name>". 

272 To be used where the scope requires it, i.e. outside of records. 

273 """ 

274 if register_array is None: 

275 return f"{register_list.name}_{register.name}" 

276 

277 register_array_name = qualified_register_array_name( 

278 register_list=register_list, register_array=register_array 

279 ) 

280 return f"{register_array_name}_{register.name}" 

281 

282 

283def qualified_register_array_name( 

284 register_list: RegisterList, register_array: RegisterArray 

285) -> str: 

286 """ 

287 Get the qualified register array name, e.g. "<module name>_<register array name>". 

288 To be used where the scope requires it, i.e. outside of records. 

289 """ 

290 return f"{register_list.name}_{register_array.name}" 

291 

292 

293def qualified_field_name( 

294 register_list: RegisterList, 

295 register: Register, 

296 field: RegisterField, 

297 register_array: RegisterArray | None = None, 

298) -> str: 

299 """ 

300 Get the qualified field name, e.g. "<module name>_<register name>_<field_name>". 

301 To be used where the scope requires it, i.e. outside of records. 

302 """ 

303 register_name = qualified_register_name( 

304 register_list=register_list, register=register, register_array=register_array 

305 ) 

306 return f"{register_name}_{field.name}"