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

145 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-07 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 = f"""\ 

137{self.header} 

138#pragma once 

139 

140#include <cassert> 

141#include <cstdint> 

142#include <cstdlib> 

143 

144""" 

145 return cpp_code_top + self._with_namespace(cpp_code) 

146 

147 def _constants(self) -> str: 

148 cpp_code = "" 

149 

150 for constant in self.iterate_constants(): 

151 if isinstance(constant, BooleanConstant): 

152 type_declaration = " bool" 

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

154 elif isinstance(constant, IntegerConstant): 

155 type_declaration = " int" 

156 value = str(constant.value) 

157 elif isinstance(constant, FloatConstant): 

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

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

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

161 # (IEEE 1076-2008, 5.2.5.1). 

162 type_declaration = "expr double" 

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

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

165 value = str(constant.value) 

166 elif isinstance(constant, StringConstant): 

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

168 type_declaration = "expr auto" 

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

170 elif isinstance(constant, UnsignedVectorConstant): 

171 type_declaration = " auto" 

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

173 else: 

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

175 

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

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

178 

179 if cpp_code: 

180 cpp_code += "\n" 

181 

182 return cpp_code 

183 

184 def _num_registers(self) -> str: 

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

186 num_registers = 0 

187 if self.register_list.register_objects: 

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

189 

190 cpp_code = self.comment("Number of registers within this register map.") 

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

192 return cpp_code 

193 

194 def _field_interface( 

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

196 ) -> str: 

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

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

199 

200 cpp_code = "" 

201 for field in register.fields: 

202 field_description = self.field_description( 

203 register=register, register_array=register_array, field=field 

204 ) 

205 field_type_name = self._field_value_type_name( 

206 register=register, register_array=register_array, field=field 

207 ) 

208 

209 if register.mode.software_can_read: 

210 comment = [ 

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

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

213 ] 

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

215 

216 signature = self._field_getter_function_signature( 

217 register=register, 

218 register_array=register_array, 

219 field=field, 

220 from_value=False, 

221 ) 

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

223 

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

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

226 

227 signature = self._field_getter_function_signature( 

228 register=register, 

229 register_array=register_array, 

230 field=field, 

231 from_value=True, 

232 ) 

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

234 

235 if register.mode.software_can_write: 

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

237 if self.field_setter_should_read_modify_write(register=register): 

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

239 else: 

240 comment.append( 

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

242 "and everything else to default." 

243 ) 

244 

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

246 

247 signature = self._field_setter_function_signature( 

248 register=register, 

249 register_array=register_array, 

250 field=field, 

251 from_value=False, 

252 ) 

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

254 

255 comment = [ 

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

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

258 ] 

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

260 

261 signature = self._field_setter_function_signature( 

262 register=register, 

263 register_array=register_array, 

264 field=field, 

265 from_value=True, 

266 ) 

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

268 

269 cpp_code += "\n" 

270 

271 return cpp_code 

272 

273 @staticmethod 

274 def _get_default_value(field: "RegisterField") -> str: 

275 """ 

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

277 """ 

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

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

280 

281 if isinstance(field, Enumeration): 

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

283 

284 if isinstance(field, Integer): 

285 return str(field.default_value) 

286 

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

288 

289 def _field_attributes( 

290 self, 

291 register: "Register", 

292 register_array: Optional["RegisterArray"], 

293 field: "RegisterField", 

294 ) -> str: 

295 field_description = self.field_description( 

296 register=register, register_array=register_array, field=field 

297 ) 

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

299 

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

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

302 

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

304 cpp_code += " {\n" 

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

306 

307 if isinstance(field, Enumeration): 

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

309 separator = "\n " 

310 cpp_code += f"""\ 

311 enum Enumeration 

312 {{ 

313 {separator.join(name_value_pairs)} 

314 }}; 

315""" 

316 

317 cpp_code += ( 

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

319 ) 

320 cpp_code += " }\n" 

321 

322 return cpp_code 

323 

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

325 return f"""\ 

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

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

328 {{ 

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

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

331 }}; 

332 

333"""