Coverage for hdl_registers/generator/cpp/interface.py: 96%

161 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, Any 

13 

14from hdl_registers.constant.bit_vector_constant import UnsignedVectorConstant 

15from hdl_registers.constant.boolean_constant import BooleanConstant 

16from hdl_registers.constant.float_constant import FloatConstant 

17from hdl_registers.constant.integer_constant import IntegerConstant 

18from hdl_registers.constant.string_constant import StringConstant 

19from hdl_registers.field.bit import Bit 

20from hdl_registers.field.bit_vector import BitVector 

21from hdl_registers.field.enumeration import Enumeration 

22from hdl_registers.field.integer import Integer 

23from hdl_registers.register_array import RegisterArray 

24 

25from .cpp_generator_common import CppGeneratorCommon 

26 

27if TYPE_CHECKING: 

28 from pathlib import Path 

29 

30 from hdl_registers.field.register_field import RegisterField 

31 from hdl_registers.register import Register 

32 

33 

34class CppInterfaceGenerator(CppGeneratorCommon): 

35 """ 

36 Generate a C++ interface header, suitable for mocking in a unit test environment. 

37 See the :ref:`generator_cpp` article for usage details. 

38 

39 The interface header will contain: 

40 

41 * Attribute constants for each register and field, such as width, default value, etc. 

42 

43 * Enumeration types for all :ref:`field_enumeration`. 

44 

45 * Constant values for all :ref:`register constants <constant_overview>`. 

46 

47 * for each register, signature of getter and setter methods for reading/writing the register as 

48 an ``uint``. 

49 

50 * for each field in each register, signature of getter and setter methods for reading/writing 

51 the field as its native type (enumeration, positive/negative int, etc.). 

52 

53 * The setter will read-modify-write the register to update only the specified field, 

54 depending on the mode of the register. 

55 """ 

56 

57 __version__ = "1.0.0" 

58 

59 SHORT_DESCRIPTION = "C++ interface header" 

60 

61 DEFAULT_INDENTATION_LEVEL = 4 

62 

63 @property 

64 def output_file(self) -> Path: 

65 """ 

66 Result will be placed in this file. 

67 """ 

68 return self.output_folder / f"i_{self.name}.h" 

69 

70 def get_code( 

71 self, 

72 **kwargs: Any, # noqa: ANN401, ARG002 

73 ) -> str: 

74 """ 

75 Get a complete C++ interface header with constants, types, attributes and methods for 

76 accessing registers and fields. 

77 """ 

78 cpp_code = f"""{self._get_attributes()}\ 

79 class I{self._class_name} 

80 { 

81 public: 

82""" 

83 cpp_code += self._get_constants() 

84 

85 cpp_code += self._get_num_registers() 

86 

87 cpp_code += f" virtual ~I{self._class_name}() { } \n" 

88 

89 separator = self.get_separator_line() 

90 

91 for register, register_array in self.iterate_registers(): 

92 cpp_code += self._get_register_heading( 

93 register=register, register_array=register_array, separator=separator 

94 ) 

95 

96 if register.mode.software_can_read: 

97 cpp_code += self._get_getters(register=register, register_array=register_array) 

98 

99 if register.mode.software_can_write: 

100 # Add empty line between getter and setter interfaces. 

101 cpp_code += "\n" 

102 

103 if register.mode.software_can_write: 

104 cpp_code += self._get_setters(register=register, register_array=register_array) 

105 

106 cpp_code += separator 

107 

108 cpp_code += " };\n\n" 

109 

110 cpp_code_top = """\ 

111#pragma once 

112 

113#include <sstream> 

114#include <cstdint> 

115#include <cstdlib> 

116 

117""" 

118 return cpp_code_top + self._with_namespace(cpp_code) 

119 

120 def _get_attributes(self) -> str: 

121 if not self.register_list.register_objects: 

122 # Don't create a namespace if the register list contains only constants. 

123 return "" 

124 

125 attributes_cpp: list[str] = [] 

126 for register_object in self.iterate_register_objects(): 

127 if isinstance(register_object, RegisterArray): 

128 attributes_cpp.append( 

129 self._get_register_array_attributes(register_array=register_object) 

130 ) 

131 elif register_object.fields: 

132 attributes_cpp.append( 

133 self._get_register_attributes(register=register_object, indent=4) 

134 ) 

135 

136 attribute_cpp = "\n".join(attributes_cpp) 

137 return f"""\ 

138 namespace {self.name} 

139 { 

140 

141{attribute_cpp} 

142 } // namespace {self.name} 

143 

144""" 

145 

146 def _get_register_attributes(self, register: Register, indent: int) -> str: 

147 if not register.fields: 

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

149 

150 attributes_cpp: list[str] = [] 

151 struct_values_cpp: list[str] = [] 

152 default_values_cpp: list[str] = [] 

153 

154 for field in register.fields: 

155 indentation = self.get_indentation(indent=indent + 2) 

156 field_type = self._get_field_value_type( 

157 register=register, register_array=None, field=field, include_namespace=False 

158 ) 

159 

160 if isinstance(field, Enumeration): 

161 separator = "\n" + " " * (indent + 6) 

162 name_value_pairs = "".join( 

163 [f"{separator}{element.name} = {element.value}," for element in field.elements] 

164 ) 

165 typedef = f""" 

166{indentation} // The valid elements for this enumeration field. 

167{indentation} enum Enumeration 

168{indentation} { \ 

169{name_value_pairs} 

170{indentation} } ; 

171""" 

172 field_type_struct = f"{field.name}::Enumeration" 

173 else: 

174 typedef = "" 

175 field_type_struct = field_type 

176 

177 default_value = self._get_default_value(field=field) 

178 default_value_raw = self._get_default_value(field=field, raw=True) 

179 

180 # If 'width' is 32, '1 << width' is a 33-bit unsigned number. 

181 # The C++ standard requires an "int" to be at least 16 bits, "long" at least 32, 

182 # and "long long" at least 64. 

183 # Hence in order to avoid overflow, we have to use "uLL". 

184 # Once '1' is subtracted from the shifted value, it will always fit in 32 unsigned bits. 

185 attributes_cpp.append(f"""\ 

186{indentation}// Attributes for the '{field.name}' field. 

187{indentation}namespace {field.name} 

188{indentation}{ 

189{indentation} // The number of bits that the field occupies. 

190{indentation} static const size_t width = {field.width}; 

191{indentation} // The bit index of the lowest bit in the field. 

192{indentation} static const size_t shift = {field.base_index}; 

193{indentation} // The bit mask of the field, at index zero. 

194{indentation} static const uint32_t mask_at_base = (1uLL << width) - 1; 

195{indentation} // The bit mask of the field, at the field's bit index. 

196{indentation} static const uint32_t mask_shifted = mask_at_base << shift; 

197{typedef} 

198{indentation} // Initial value of the field at device startup/reset. 

199{indentation} static const {field_type} default_value = {default_value}; 

200{indentation} // Raw representation of the initial value, at the the field's bit index. 

201{indentation} static const uint32_t default_value_raw = {default_value_raw} << shift; 

202{indentation}} 

203""") 

204 struct_values_cpp.append(f"{indentation} {field_type_struct} {field.name};") 

205 default_values_cpp.append(f"{indentation} {field.name}::default_value,") 

206 

207 indentation = self.get_indentation(indent=indent) 

208 attribute_cpp = "\n".join(attributes_cpp) 

209 struct_value_cpp = "\n".join(struct_values_cpp) 

210 default_value_cpp = "\n".join(default_values_cpp) 

211 

212 return f"""\ 

213{indentation}// Attributes for the '{register.name}' register. 

214{indentation}namespace {register.name} { 

215{attribute_cpp} 

216{indentation} // Struct that holds the value of each field in the register, 

217{indentation} // in a native C++ representation. 

218{indentation} struct Value { 

219{struct_value_cpp} 

220{indentation} } ; 

221{indentation} // Initial value of the register at device startup/reset. 

222{indentation} const Value default_value = { 

223{default_value_cpp} 

224{indentation} } ; 

225{indentation}} 

226""" 

227 

228 @staticmethod 

229 def _get_default_value(field: RegisterField, raw: bool = False) -> str: # noqa: PLR0911 

230 """ 

231 Get the field's default value formatted in a way suitable for C++ code. 

232 """ 

233 if isinstance(field, Bit): 

234 if raw: 

235 return field.default_value 

236 

237 return "true" if field.default_value == "1" else "false" 

238 

239 if isinstance(field, BitVector): 

240 if raw: 

241 return f"0b{field.default_value}uL" 

242 

243 # Note that casting a Python float to string guarantees full precision in the 

244 # resulting string: https://stackoverflow.com/a/60026172 

245 return str( 

246 field.numerical_interpretation.convert_from_unsigned_binary( 

247 unsigned_binary=int(field.default_value, base=2) 

248 ) 

249 ) 

250 

251 if isinstance(field, Enumeration): 

252 if raw: 

253 return f"{field.default_value.value}uL" 

254 

255 return f"Enumeration::{field.default_value.name}" 

256 

257 if isinstance(field, Integer): 

258 if raw: 

259 return f"{field.default_value_uint}uL" 

260 return str(field.default_value) 

261 

262 raise ValueError(f'Unknown field type for "{field.name}" field: {type(field)}') 

263 

264 def _get_register_array_attributes(self, register_array: RegisterArray) -> str: 

265 registers_cpp = [ 

266 self._get_register_attributes(register=register, indent=6) 

267 for register in register_array.registers 

268 if register.fields 

269 ] 

270 register_cpp = "\n".join(registers_cpp) 

271 

272 return f"""\ 

273 // Attributes for the '{register_array.name}' register array. 

274 namespace {register_array.name} 

275 { 

276 // Number of times the registers of the array are repeated. 

277 static const auto array_length = {register_array.length}; 

278 

279{register_cpp}\ 

280 } 

281""" 

282 

283 def _get_constants(self) -> str: 

284 cpp_code = "" 

285 

286 for constant in self.iterate_constants(): 

287 if isinstance(constant, BooleanConstant): 

288 type_declaration = " bool" 

289 value = str(constant.value).lower() 

290 elif isinstance(constant, IntegerConstant): 

291 type_declaration = " int" 

292 value = str(constant.value) 

293 elif isinstance(constant, FloatConstant): 

294 # Expand "const" to "constexpr", which is needed for static floats: 

295 # https://stackoverflow.com/questions/9141950/ 

296 # Use "double", to match the VHDL type which is at least 64 bits 

297 # (IEEE 1076-2008, 5.2.5.1). 

298 type_declaration = "expr double" 

299 # Note that casting a Python float to string guarantees full precision in the 

300 # resulting string: https://stackoverflow.com/a/60026172 

301 value = str(constant.value) 

302 elif isinstance(constant, StringConstant): 

303 # Expand "const" to "constexpr", which is needed for static string literals. 

304 type_declaration = "expr auto" 

305 value = f'"{constant.value}"' 

306 elif isinstance(constant, UnsignedVectorConstant): 

307 type_declaration = " auto" 

308 value = f"{constant.prefix}{constant.value_without_separator}" 

309 else: 

310 raise TypeError(f"Got unexpected constant type: {constant}") 

311 

312 cpp_code += self.comment("Register constant.") 

313 cpp_code += f" static const{type_declaration} {constant.name} = {value};\n" 

314 

315 if cpp_code: 

316 cpp_code += "\n" 

317 

318 return cpp_code 

319 

320 def _get_num_registers(self) -> str: 

321 # It is possible that we have constants but no registers 

322 num_registers = 0 

323 if self.register_list.register_objects: 

324 num_registers = self.register_list.register_objects[-1].index + 1 

325 

326 cpp_code = self.comment("Number of registers within this register list.") 

327 cpp_code += f" static const size_t num_registers = {num_registers}uL;\n\n" 

328 return cpp_code 

329 

330 def _get_getters(self, register: Register, register_array: RegisterArray | None) -> str: 

331 def get_function(comment: str, return_type: str, signature: str) -> str: 

332 return f"""\ 

333{comment}\ 

334 virtual {return_type} {signature} = 0; 

335""" 

336 

337 cpp_code: list[str] = [] 

338 

339 register_type = self._get_register_value_type( 

340 register=register, register_array=register_array 

341 ) 

342 signature = self._register_getter_signature( 

343 register=register, register_array=register_array 

344 ) 

345 cpp_code.append( 

346 get_function( 

347 comment=self._get_getter_comment(), 

348 return_type=register_type, 

349 signature=signature, 

350 ) 

351 ) 

352 

353 if register.fields: 

354 # The main getter will perform type conversion. 

355 # Provide a getter that returns the raw value also. 

356 signature = self._register_getter_signature( 

357 register=register, register_array=register_array, raw=True 

358 ) 

359 cpp_code.append( 

360 get_function( 

361 comment=self._get_getter_comment(raw=True), 

362 return_type="uint32_t", 

363 signature=signature, 

364 ) 

365 ) 

366 

367 for field in register.fields: 

368 field_type = self._get_field_value_type( 

369 register=register, register_array=register_array, field=field 

370 ) 

371 

372 signature = self._field_getter_signature( 

373 register=register, 

374 register_array=register_array, 

375 field=field, 

376 from_raw=False, 

377 ) 

378 cpp_code.append( 

379 get_function( 

380 comment=self._get_getter_comment(field=field), 

381 return_type=field_type, 

382 signature=signature, 

383 ) 

384 ) 

385 

386 return "\n".join(cpp_code) 

387 

388 def _get_setters(self, register: Register, register_array: RegisterArray | None) -> str: 

389 def get_function(comment: str, signature: str) -> str: 

390 return f"{comment} virtual void {signature} = 0;\n" 

391 

392 cpp_code: list[str] = [] 

393 

394 signature = self._register_setter_signature( 

395 register=register, register_array=register_array 

396 ) 

397 cpp_code.append( 

398 get_function( 

399 comment=self._get_setter_comment(register=register), 

400 signature=signature, 

401 ) 

402 ) 

403 

404 if register.fields: 

405 # The main setter will perform type conversion. 

406 # Provide a setter that takes a raw value also. 

407 signature = self._register_setter_signature( 

408 register=register, register_array=register_array, raw=True 

409 ) 

410 cpp_code.append( 

411 get_function( 

412 comment=self._get_setter_comment(register=register, raw=True), 

413 signature=signature, 

414 ) 

415 ) 

416 

417 for field in register.fields: 

418 signature = self._field_setter_signature( 

419 register=register, 

420 register_array=register_array, 

421 field=field, 

422 from_raw=False, 

423 ) 

424 cpp_code.append( 

425 get_function( 

426 comment=self._get_setter_comment(register=register, field=field), 

427 signature=signature, 

428 ) 

429 ) 

430 

431 return "\n".join(cpp_code)