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

146 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-12 11:11 +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 

23 

24from .cpp_generator_common import CppGeneratorCommon 

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 

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

79 

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

81 field_cpp_code = "" 

82 

83 for field in register.fields: 

84 field_cpp_code += self._field_attributes( 

85 register=register, register_array=register_array, field=field 

86 ) 

87 

88 cpp_code += field_cpp_code 

89 if field_cpp_code: 

90 cpp_code += "\n" 

91 

92 for register_array in self.iterate_register_arrays(): 

93 cpp_code += self._register_array_attributes(register_array=register_array) 

94 

95 cpp_code += f" class I{self._class_name}\n" 

96 cpp_code += " {\n" 

97 cpp_code += " public:\n" 

98 

99 cpp_code += self._constants() 

100 

101 cpp_code += self._num_registers() 

102 

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

104 

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

106 cpp_code += f"{self.get_separator_line()}" 

107 

108 description = self._get_methods_description( 

109 register=register, register_array=register_array 

110 ) 

111 description += f" Mode '{register.mode.name}'." 

112 

113 cpp_code += self.comment(comment=description) 

114 cpp_code += "\n" 

115 

116 if register.mode.software_can_read: 

117 cpp_code += self.comment( 

118 "Getter that will read the whole register's value over the register bus." 

119 ) 

120 signature = self._register_getter_function_signature( 

121 register=register, register_array=register_array 

122 ) 

123 cpp_code += f" virtual uint32_t {signature} const = 0;\n\n" 

124 

125 if register.mode.software_can_write: 

126 cpp_code += self.comment( 

127 "Setter that will write the whole register's value over the register bus." 

128 ) 

129 signature = self._register_setter_function_signature( 

130 register=register, register_array=register_array 

131 ) 

132 cpp_code += f" virtual void {signature} const = 0;\n\n" 

133 

134 cpp_code += self._field_interface(register, register_array) 

135 

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

137 

138 cpp_code_top = """\ 

139#pragma once 

140 

141#include <sstream> 

142#include <cstdint> 

143#include <cstdlib> 

144 

145""" 

146 return cpp_code_top + self._with_namespace(cpp_code) 

147 

148 def _constants(self) -> str: 

149 cpp_code = "" 

150 

151 for constant in self.iterate_constants(): 

152 if isinstance(constant, BooleanConstant): 

153 type_declaration = " bool" 

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

155 elif isinstance(constant, IntegerConstant): 

156 type_declaration = " int" 

157 value = str(constant.value) 

158 elif isinstance(constant, FloatConstant): 

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

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

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

162 # (IEEE 1076-2008, 5.2.5.1). 

163 type_declaration = "expr double" 

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

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

166 value = str(constant.value) 

167 elif isinstance(constant, StringConstant): 

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

169 type_declaration = "expr auto" 

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

171 elif isinstance(constant, UnsignedVectorConstant): 

172 type_declaration = " auto" 

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

174 else: 

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

176 

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

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

179 

180 if cpp_code: 

181 cpp_code += "\n" 

182 

183 return cpp_code 

184 

185 def _num_registers(self) -> str: 

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

187 num_registers = 0 

188 if self.register_list.register_objects: 

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

190 

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

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

193 return cpp_code 

194 

195 def _field_interface(self, register: Register, register_array: RegisterArray | None) -> str: 

196 def function(return_type_name: str, signature: str) -> str: 

197 return f" virtual {return_type_name} {signature} const = 0;\n" 

198 

199 cpp_code = "" 

200 for field in register.fields: 

201 field_description = self.field_description( 

202 register=register, register_array=register_array, field=field 

203 ) 

204 field_type_name = self._field_value_type_name( 

205 register=register, register_array=register_array, field=field 

206 ) 

207 

208 if register.mode.software_can_read: 

209 comment = [ 

210 f"Getter for the {field_description},", 

211 "which will read register value over the register bus.", 

212 ] 

213 cpp_code += self.comment_block(text=comment) 

214 

215 signature = self._field_getter_function_signature( 

216 register=register, 

217 register_array=register_array, 

218 field=field, 

219 from_value=False, 

220 ) 

221 cpp_code += function(return_type_name=field_type_name, signature=signature) 

222 

223 comment = [f"Getter for the {field_description},", "given a register value."] 

224 cpp_code += self.comment_block(text=comment) 

225 

226 signature = self._field_getter_function_signature( 

227 register=register, 

228 register_array=register_array, 

229 field=field, 

230 from_value=True, 

231 ) 

232 cpp_code += function(return_type_name=field_type_name, signature=signature) 

233 

234 if register.mode.software_can_write: 

235 comment = [f"Setter for the {field_description},"] 

236 if self.field_setter_should_read_modify_write(register=register): 

237 comment.append("which will read-modify-write over the register bus.") 

238 else: 

239 comment.append( 

240 "which will set the field to the given value, " 

241 "and everything else to default." 

242 ) 

243 

244 cpp_code += self.comment_block(text=comment) 

245 

246 signature = self._field_setter_function_signature( 

247 register=register, 

248 register_array=register_array, 

249 field=field, 

250 from_value=False, 

251 ) 

252 cpp_code += function(return_type_name="void", signature=signature) 

253 

254 comment = [ 

255 f"Setter for the {field_description},", 

256 "given a register value, which will return an updated value.", 

257 ] 

258 cpp_code += self.comment_block(text=comment) 

259 

260 signature = self._field_setter_function_signature( 

261 register=register, 

262 register_array=register_array, 

263 field=field, 

264 from_value=True, 

265 ) 

266 cpp_code += function(return_type_name="uint32_t", signature=signature) 

267 

268 cpp_code += "\n" 

269 

270 return cpp_code 

271 

272 @staticmethod 

273 def _get_default_value(field: RegisterField) -> str: 

274 """ 

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

276 """ 

277 if isinstance(field, (Bit, BitVector)): 

278 return f"0b{field.default_value}" 

279 

280 if isinstance(field, Enumeration): 

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

282 

283 if isinstance(field, Integer): 

284 return str(field.default_value) 

285 

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

287 

288 def _field_attributes( 

289 self, 

290 register: Register, 

291 register_array: RegisterArray | None, 

292 field: RegisterField, 

293 ) -> str: 

294 field_description = self.field_description( 

295 register=register, register_array=register_array, field=field 

296 ) 

297 cpp_code = self.comment(f"Attributes for the {field_description}.", indent=2) 

298 

299 array_namespace = f"::{register_array.name}" if register_array else "" 

300 namespace = f"{self.name}{array_namespace}::{register.name}::{field.name}" 

301 

302 cpp_code += f" namespace {namespace}\n" 

303 cpp_code += " {\n" 

304 cpp_code += f" static const auto width = {field.width};\n" 

305 

306 if isinstance(field, Enumeration): 

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

308 separator = "\n " 

309 cpp_code += f"""\ 

310 enum Enumeration 

311 { 

312 {separator.join(name_value_pairs)} 

313 } ; 

314""" 

315 

316 cpp_code += ( 

317 f" static const auto default_value = {self._get_default_value(field=field)};\n" 

318 ) 

319 cpp_code += " }\n" 

320 

321 return cpp_code 

322 

323 def _register_array_attributes(self, register_array: RegisterArray) -> str: 

324 return f"""\ 

325 // Attributes for the "{register_array.name}" register array. 

326 namespace {self.name}::{register_array.name} 

327 { 

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

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

330 } ; 

331 

332"""