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

90 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-19 20:50 +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 of 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 def get_indentation(self, indent: int | None = None) -> str: 

116 """ 

117 Get the requested indentation in spaces. 

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

119 """ 

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

121 return " " * indent 

122 

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

124 """ 

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

126 """ 

127 indentation = self.get_indentation(indent=indent) 

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

129 

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

131 result += "-" * num_dash 

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

133 

134 return result 

135 

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

137 """ 

138 Create a one-line comment. 

139 """ 

140 indentation = self.get_indentation(indent=indent) 

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

142 

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

144 """ 

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

146 """ 

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

148 

149 @staticmethod 

150 def register_description( 

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

152 ) -> str: 

153 """ 

154 Get a comment describing the register. 

155 """ 

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

157 

158 if register_array is None: 

159 return result 

160 

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

162 

163 def field_description( 

164 self, 

165 register: Register, 

166 field: RegisterField, 

167 register_array: RegisterArray | None = None, 

168 ) -> str: 

169 """ 

170 Get a comment describing the field. 

171 """ 

172 register_description = self.register_description( 

173 register=register, register_array=register_array 

174 ) 

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

176 

177 @staticmethod 

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

179 """ 

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

181 

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

183 a previously-written value. 

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

185 it is a waste of CPU cycles. 

186 """ 

187 if not register.fields: 

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

189 

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

191 return len(register.fields) > 1 

192 

193 if register.mode in [ 

194 REGISTER_MODES["w"], 

195 REGISTER_MODES["wpulse"], 

196 REGISTER_MODES["r_wpulse"], 

197 ]: 

198 return False 

199 

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

201 

202 @staticmethod 

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

204 """ 

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

206 

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

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

209 """ 

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

211 

212 

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

214 """ 

215 Iterate over all register objects in the register list. 

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

217 """ 

218 yield from register_list.register_objects 

219 

220 

221def iterate_registers( 

222 register_list: RegisterList, 

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

224 """ 

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

226 

227 Return: 

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

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

230 """ 

231 for register_object in iterate_register_objects(register_list=register_list): 

232 if isinstance(register_object, Register): 

233 yield (register_object, None) 

234 else: 

235 for register in register_object.registers: 

236 yield (register, register_object) 

237 

238 

239def qualified_register_name( 

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

241) -> str: 

242 """ 

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

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

245 """ 

246 if register_array is None: 

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

248 

249 register_array_name = qualified_register_array_name( 

250 register_list=register_list, register_array=register_array 

251 ) 

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

253 

254 

255def qualified_register_array_name( 

256 register_list: RegisterList, register_array: RegisterArray 

257) -> str: 

258 """ 

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

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

261 """ 

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

263 

264 

265def qualified_field_name( 

266 register_list: RegisterList, 

267 register: Register, 

268 field: RegisterField, 

269 register_array: RegisterArray | None = None, 

270) -> str: 

271 """ 

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

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

274 """ 

275 register_name = qualified_register_name( 

276 register_list=register_list, register=register, register_array=register_array 

277 ) 

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