Coverage for hdl_registers/generator/cpp/cpp_generator_common.py: 93%

145 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.field.bit import Bit 

15from hdl_registers.field.bit_vector import BitVector 

16from hdl_registers.field.enumeration import Enumeration 

17from hdl_registers.field.integer import Integer 

18from hdl_registers.field.numerical_interpretation import ( 

19 Signed, 

20 SignedFixedPoint, 

21 Unsigned, 

22 UnsignedFixedPoint, 

23) 

24from hdl_registers.generator.register_code_generator import RegisterCodeGenerator 

25 

26if TYPE_CHECKING: 

27 from pathlib import Path 

28 

29 from hdl_registers.field.register_field import RegisterField 

30 from hdl_registers.register import Register 

31 from hdl_registers.register_array import RegisterArray 

32 from hdl_registers.register_list import RegisterList 

33 

34 

35class CppGeneratorCommon(RegisterCodeGenerator): 

36 """ 

37 Class with common methods for generating C++ code. 

38 """ 

39 

40 COMMENT_START = "//" 

41 

42 def __init__(self, register_list: RegisterList, output_folder: Path) -> None: 

43 super().__init__(register_list=register_list, output_folder=output_folder) 

44 

45 self._class_name = self.to_pascal_case(snake_string=self.name) 

46 

47 @staticmethod 

48 def _with_namespace(cpp_code_body: str) -> str: 

49 return f"""\ 

50namespace fpga_regs 

51{ 

52 

53{cpp_code_body}\ 

54} // namespace fpga_regs 

55""" 

56 

57 def _constructor_signature(self) -> str: 

58 return ( 

59 f"{self._class_name}(uintptr_t base_address, " 

60 "bool (*assertion_handler) (const std::string*))" 

61 ) 

62 

63 def _get_register_heading( 

64 self, 

65 register: Register, 

66 register_array: RegisterArray | None, 

67 separator: str, 

68 indent: int | None = None, 

69 ) -> str: 

70 indentation = self.get_indentation(indent=indent) 

71 description = self._get_methods_description( 

72 register=register, register_array=register_array 

73 ) 

74 return f""" 

75{separator}\ 

76{indentation}// {description} 

77{indentation}// Mode '{register.mode.name}'. 

78{separator}\ 

79""" 

80 

81 def _get_methods_description( 

82 self, register: Register, register_array: RegisterArray | None 

83 ) -> str: 

84 register_description = self.register_description( 

85 register=register, register_array=register_array 

86 ) 

87 return f"Methods for the {register_description}." 

88 

89 def _get_namespace( 

90 self, 

91 register: Register, 

92 register_array: RegisterArray | None = None, 

93 field: RegisterField | None = None, 

94 ) -> str: 

95 """ 

96 Get namespace to use within the class for the attributes of the field or register that is 

97 pointed out by the arguments. 

98 """ 

99 register_array_namespace = f"{register_array.name}::" if register_array else "" 

100 field_namespace = f"{field.name}::" if field else "" 

101 return f"{self.name}::{register_array_namespace}{register.name}::{field_namespace}" 

102 

103 def _get_register_value_type( 

104 self, register: Register, register_array: RegisterArray | None 

105 ) -> str: 

106 """ 

107 The name of the type used to represent the register value. 

108 """ 

109 if register.fields: 

110 namespace = self._get_namespace(register=register, register_array=register_array) 

111 return f"{namespace}Value" 

112 

113 return "uint32_t" 

114 

115 def _get_field_value_type( 

116 self, 

117 register: Register, 

118 register_array: RegisterArray | None, 

119 field: RegisterField, 

120 include_namespace: bool = True, 

121 ) -> str: 

122 """ 

123 The name of the type used to represent the field value. 

124 """ 

125 if isinstance(field, Bit): 

126 return "bool" 

127 

128 if isinstance(field, BitVector): 

129 if isinstance(field.numerical_interpretation, Unsigned): 

130 return "uint32_t" 

131 

132 if isinstance(field.numerical_interpretation, Signed): 

133 return "int32_t" 

134 

135 if isinstance(field.numerical_interpretation, (UnsignedFixedPoint, SignedFixedPoint)): 

136 # This assumes that the target platform/compiler uses IEEE-754 single-precision for 

137 # 'float' and IEEE-754 double-precision for 'double'. 

138 # This is NOT guaranteed by the C++ standard. 

139 # The user is encouraged to check their platform's limits 

140 # with e.g. 'std::numeric_limits<float>::is_iec559'. 

141 # https://stackoverflow.com/questions/24157094 

142 return "double" if field.width > 24 else "float" 

143 

144 raise ValueError( 

145 f"Unsupported numerical interpretation: {field.numerical_interpretation}" 

146 ) 

147 

148 if isinstance(field, Enumeration): 

149 # The name of an enum available in this field's attributes. 

150 namespace = ( 

151 self._get_namespace(register=register, register_array=register_array, field=field) 

152 if include_namespace 

153 else "" 

154 ) 

155 return f"{namespace}Enumeration" 

156 

157 if isinstance(field, Integer): 

158 return "int32_t" if field.is_signed else "uint32_t" 

159 

160 raise ValueError(f"Got unknown field type: {field}") 

161 

162 @staticmethod 

163 def _register_getter_name( 

164 register: Register, register_array: RegisterArray | None, raw: bool 

165 ) -> str: 

166 register_array_name = f"_{register_array.name}" if register_array else "" 

167 raw_name = "_raw" if raw else "" 

168 return f"get{register_array_name}_{register.name}{raw_name}" 

169 

170 def _register_getter_signature( 

171 self, 

172 register: Register, 

173 register_array: RegisterArray | None, 

174 raw: bool = False, 

175 indent: int | None = None, 

176 ) -> str: 

177 function_name = self._register_getter_name( 

178 register=register, register_array=register_array, raw=raw 

179 ) 

180 

181 if register_array: 

182 indentation = self.get_indentation(indent=indent) 

183 array_index = f""" 

184{indentation} size_t array_index 

185{indentation}\ 

186""" 

187 else: 

188 array_index = "" 

189 

190 return f"{function_name}({array_index}) const" 

191 

192 @staticmethod 

193 def _field_getter_name( 

194 register: Register, 

195 register_array: RegisterArray | None, 

196 field: RegisterField, 

197 from_raw: bool, 

198 ) -> str: 

199 register_array_name = f"_{register_array.name}" if register_array else "" 

200 raw_name = "_from_raw" if from_raw else "" 

201 return f"get{register_array_name}_{register.name}_{field.name}{raw_name}" 

202 

203 def _field_getter_signature( 

204 self, 

205 register: Register, 

206 register_array: RegisterArray | None, 

207 field: RegisterField, 

208 from_raw: bool, 

209 indent: int | None = None, 

210 ) -> str: 

211 indentation = self.get_indentation(indent=indent) 

212 

213 function_name = self._field_getter_name( 

214 register=register, register_array=register_array, field=field, from_raw=from_raw 

215 ) 

216 result = f"{function_name}(" 

217 

218 if from_raw: 

219 # Value is supplied by user 

220 result += f"\n{indentation} uint32_t register_value\n{indentation}" 

221 elif register_array: 

222 # Value shall be read from bus, in which case we need to know array index if this 

223 # is an array 

224 result += f"\n{indentation} size_t array_index\n{indentation}" 

225 

226 result += ") const" 

227 

228 return result 

229 

230 @staticmethod 

231 def _field_to_raw_name( 

232 register: Register, register_array: RegisterArray | None, field: RegisterField 

233 ) -> str: 

234 array = f"{register_array.name}_" if register_array else "" 

235 return f"get_{array}{register.name}_{field.name}_to_raw" 

236 

237 def _field_to_raw_signature( 

238 self, register: Register, register_array: RegisterArray | None, field: RegisterField 

239 ) -> str: 

240 field_type = self._get_field_value_type( 

241 register=register, register_array=register_array, field=field 

242 ) 

243 function_name = self._field_to_raw_name( 

244 register=register, register_array=register_array, field=field 

245 ) 

246 return f"{function_name}({field_type} field_value) const" 

247 

248 @staticmethod 

249 def _register_setter_name( 

250 register: Register, register_array: RegisterArray | None, raw: bool 

251 ) -> str: 

252 array_name = f"_{register_array.name}" if register_array else "" 

253 raw_name = "_raw" if raw else "" 

254 

255 return f"set{array_name}_{register.name}{raw_name}" 

256 

257 def _register_setter_signature( 

258 self, 

259 register: Register, 

260 register_array: RegisterArray | None, 

261 raw: bool = False, 

262 indent: int | None = None, 

263 ) -> str: 

264 indentation = self.get_indentation(indent=indent) 

265 

266 function_name = self._register_setter_name( 

267 register=register, register_array=register_array, raw=raw 

268 ) 

269 

270 array_index = f"{indentation} size_t array_index,\n" if register_array else "" 

271 value_type = ( 

272 "uint32_t" 

273 if raw 

274 else self._get_register_value_type(register=register, register_array=register_array) 

275 ) 

276 

277 return f"""\ 

278{function_name}( 

279{array_index}\ 

280{indentation} {value_type} register_value 

281{indentation}) const\ 

282""" 

283 

284 @staticmethod 

285 def _field_setter_name( 

286 register: Register, 

287 register_array: RegisterArray | None, 

288 field: RegisterField, 

289 from_raw: bool, 

290 ) -> str: 

291 register_array_name = f"_{register_array.name}" if register_array else "" 

292 from_raw_name = "_from_raw" if from_raw else "" 

293 return f"set{register_array_name}_{register.name}_{field.name}{from_raw_name}" 

294 

295 def _field_setter_signature( 

296 self, 

297 register: Register, 

298 register_array: RegisterArray | None, 

299 field: RegisterField, 

300 from_raw: bool, 

301 indent: int | None = None, 

302 ) -> str: 

303 indentation = self.get_indentation(indent=indent) 

304 

305 function_name = self._field_setter_name( 

306 register=register, register_array=register_array, field=field, from_raw=from_raw 

307 ) 

308 result = f"{function_name}(\n" 

309 

310 if from_raw: 

311 # Current register value is supplied by user 

312 result += f"{indentation} uint32_t register_value,\n" 

313 elif register_array: 

314 # Current register value shall be read from bus, in which case we need to know array 

315 # index if this is an array 

316 result += f"{indentation} size_t array_index,\n" 

317 

318 field_type = self._get_field_value_type( 

319 register=register, register_array=register_array, field=field 

320 ) 

321 result += f"{indentation} {field_type} field_value\n{indentation}) const" 

322 

323 return result 

324 

325 def _get_getter_comment(self, field: RegisterField | None = None, raw: bool = False) -> str: 

326 """ 

327 Generate a comment for getter method documentation. 

328 """ 

329 if field: 

330 if raw: 

331 raise ValueError("Invalid arguments") 

332 

333 return self.comment( 

334 comment=f"Read the register and slice out the '{field.name}' field value.", 

335 ) 

336 

337 comment = ["Read the whole register value over the register bus."] 

338 if raw: 

339 comment.append( 

340 "This method returns the raw register value without any type conversion." 

341 ) 

342 

343 return self.comment_block(text=comment) 

344 

345 def _get_setter_comment( 

346 self, register: Register, field: RegisterField | None = None, raw: bool = False 

347 ) -> str: 

348 """ 

349 Generate a comment for setter method documentation. 

350 """ 

351 if field: 

352 if raw: 

353 raise ValueError("Invalid arguments") 

354 

355 comment = [f"Write the '{field.name}' field value."] 

356 if self.field_setter_should_read_modify_write(register=register): 

357 comment.append("Will read-modify-write the register.") 

358 else: 

359 comment.append("Will write the register with all other fields set as default.") 

360 

361 return self.comment_block(text=comment) 

362 

363 comment = ["Write the whole register value over the register bus."] 

364 

365 if raw: 

366 comment.append( 

367 "This method takes a raw register value and does not perform any type conversion." 

368 ) 

369 

370 return self.comment_block(text=comment) 

371 

372 def _get_from_raw_comment(self, field: RegisterField) -> str: 

373 """ 

374 Generate a comment for a ``get_<field>_from_raw`` method documentation. 

375 """ 

376 return self.comment_block( 

377 text=[ 

378 f"Slice out the '{field.name}' field value from a given raw register value.", 

379 "Performs no operation on the register bus.", 

380 ] 

381 ) 

382 

383 def _get_to_raw_comment(self, field: RegisterField) -> str: 

384 """ 

385 Generate a comment for a ``get_<field>_to_raw`` method documentation. 

386 """ 

387 return self.comment_block( 

388 text=[ 

389 f"Get the raw representation of a given '{field.name}' field value.", 

390 "Performs no operation on the register bus.", 

391 ] 

392 )