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

145 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-28 20:51 +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 

10# Standard libraries 

11from pathlib import Path 

12from typing import TYPE_CHECKING, Any, Optional 

13 

14# First party libraries 

15from hdl_registers.constant.bit_vector_constant import UnsignedVectorConstant 

16from hdl_registers.constant.boolean_constant import BooleanConstant 

17from hdl_registers.constant.float_constant import FloatConstant 

18from hdl_registers.constant.integer_constant import IntegerConstant 

19from hdl_registers.constant.string_constant import StringConstant 

20from hdl_registers.field.bit import Bit 

21from hdl_registers.field.bit_vector import BitVector 

22from hdl_registers.field.enumeration import Enumeration 

23from hdl_registers.field.integer import Integer 

24 

25# Local folder libraries 

26from .cpp_generator_common import CppGeneratorCommon 

27 

28if TYPE_CHECKING: 

29 # First party libraries 

30 from hdl_registers.field.register_field import RegisterField 

31 from hdl_registers.register import Register 

32 from hdl_registers.register_array import RegisterArray 

33 

34 

35class CppInterfaceGenerator(CppGeneratorCommon): 

36 """ 

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

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

39 

40 The interface header will contain: 

41 

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

43 

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

45 

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

47 

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

49 an ``uint``. 

50 

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

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

53 

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

55 depending on the mode of the register. 

56 """ 

57 

58 __version__ = "1.0.0" 

59 

60 SHORT_DESCRIPTION = "C++ interface header" 

61 

62 DEFAULT_INDENTATION_LEVEL = 4 

63 

64 @property 

65 def output_file(self) -> Path: 

66 """ 

67 Result will be placed in this file. 

68 """ 

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

70 

71 def get_code(self, **kwargs: Any) -> str: 

72 """ 

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

74 accessing registers and fields. 

75 """ 

76 cpp_code = "" 

77 

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

79 field_cpp_code = "" 

80 

81 for field in register.fields: 

82 field_cpp_code += self._field_attributes( 

83 register=register, register_array=register_array, field=field 

84 ) 

85 

86 cpp_code += field_cpp_code 

87 if field_cpp_code: 

88 cpp_code += "\n" 

89 

90 for register_array in self.iterate_register_arrays(): 

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

92 

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

94 cpp_code += " {\n" 

95 cpp_code += " public:\n" 

96 

97 cpp_code += self._constants() 

98 

99 cpp_code += self._num_registers() 

100 

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

102 

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

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

105 

106 description = self._get_methods_description( 

107 register=register, register_array=register_array 

108 ) 

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

110 

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

112 cpp_code += "\n" 

113 

114 if register.mode.software_can_read: 

115 cpp_code += self.comment( 

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

117 ) 

118 signature = self._register_getter_function_signature( 

119 register=register, register_array=register_array 

120 ) 

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

122 

123 if register.mode.software_can_write: 

124 cpp_code += self.comment( 

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

126 ) 

127 signature = self._register_setter_function_signature( 

128 register=register, register_array=register_array 

129 ) 

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

131 

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

133 

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

135 

136 cpp_code_top = """\ 

137#pragma once 

138 

139#include <sstream> 

140#include <cstdint> 

141#include <cstdlib> 

142 

143""" 

144 return cpp_code_top + self._with_namespace(cpp_code) 

145 

146 def _constants(self) -> str: 

147 cpp_code = "" 

148 

149 for constant in self.iterate_constants(): 

150 if isinstance(constant, BooleanConstant): 

151 type_declaration = " bool" 

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

153 elif isinstance(constant, IntegerConstant): 

154 type_declaration = " int" 

155 value = str(constant.value) 

156 elif isinstance(constant, FloatConstant): 

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

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

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

160 # (IEEE 1076-2008, 5.2.5.1). 

161 type_declaration = "expr double" 

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

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

164 value = str(constant.value) 

165 elif isinstance(constant, StringConstant): 

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

167 type_declaration = "expr auto" 

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

169 elif isinstance(constant, UnsignedVectorConstant): 

170 type_declaration = " auto" 

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

172 else: 

173 raise ValueError(f"Got unexpected constant type. {constant}") 

174 

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

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

177 

178 if cpp_code: 

179 cpp_code += "\n" 

180 

181 return cpp_code 

182 

183 def _num_registers(self) -> str: 

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

185 num_registers = 0 

186 if self.register_list.register_objects: 

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

188 

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

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

191 return cpp_code 

192 

193 def _field_interface( 

194 self, register: "Register", register_array: Optional["RegisterArray"] 

195 ) -> 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: Optional["RegisterArray"], 

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