Coverage for hdl_registers/generator/vhdl/simulation/check_package.py: 96%

94 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-19 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.field.bit_vector import BitVector 

16from hdl_registers.field.enumeration import Enumeration 

17from hdl_registers.field.numerical_interpretation import Fixed 

18from hdl_registers.register_mode import SoftwareAccessDirection 

19 

20# Local folder libraries 

21from .vhdl_simulation_generator_common import VhdlSimulationGeneratorCommon 

22 

23if TYPE_CHECKING: 

24 # First party libraries 

25 from hdl_registers.field.register_field import RegisterField 

26 from hdl_registers.register import Register 

27 from hdl_registers.register_array import RegisterArray 

28 

29 

30class VhdlSimulationCheckPackageGenerator(VhdlSimulationGeneratorCommon): 

31 """ 

32 Generate VHDL code with simulation procedures to check the values of registers and fields. 

33 See the :ref:`generator_vhdl` article for usage details. 

34 

35 * For each readable register, procedures that check that the register's current value is equal 

36 to a given expected value. 

37 Expected value can be provided as 

38 

39 1. bit vector, 

40 

41 2. integer, or 

42 

43 3. native VHDL record type as given by :class:`.VhdlRecordPackageGenerator`. 

44 

45 * For each field in each readable register, a procedure that checks that the register field's 

46 current value is equal to a given natively-typed value. 

47 

48 Uses VUnit Verification Component calls, via the procedures from 

49 :class:`.VhdlSimulationReadWritePackageGenerator`. 

50 

51 The generated VHDL file needs also the generated packages from 

52 :class:`.VhdlRegisterPackageGenerator` and :class:`.VhdlRecordPackageGenerator`. 

53 """ 

54 

55 __version__ = "1.2.0" 

56 

57 SHORT_DESCRIPTION = "VHDL simulation check package" 

58 

59 @property 

60 def output_file(self) -> Path: 

61 """ 

62 Result will be placed in this file. 

63 """ 

64 return self.output_folder / f"{self.name}_register_check_pkg.vhd" 

65 

66 def create(self, **kwargs: Any) -> Path: 

67 """ 

68 See super class for API details. 

69 

70 Overloaded here because this package file shall only be created if the register list 

71 actually has any registers. 

72 """ 

73 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs) 

74 

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

76 """ 

77 Get a package with methods for checking register/field values. 

78 """ 

79 package_name = self.output_file.stem 

80 

81 vhdl = f"""\ 

82{self.header} 

83library ieee; 

84use ieee.fixed_pkg.all; 

85use ieee.numeric_std.all; 

86use ieee.std_logic_1164.all; 

87 

88library vunit_lib; 

89use vunit_lib.bus_master_pkg.bus_master_t; 

90use vunit_lib.check_pkg.all; 

91use vunit_lib.checker_pkg.all; 

92use vunit_lib.com_types_pkg.network_t; 

93use vunit_lib.string_ops.hex_image; 

94 

95library common; 

96use common.addr_pkg.addr_t; 

97 

98library reg_file; 

99use reg_file.reg_file_pkg.reg_t; 

100use reg_file.reg_operations_pkg.regs_bus_master; 

101 

102use work.{self.name}_register_read_write_pkg.all; 

103use work.{self.name}_register_record_pkg.all; 

104use work.{self.name}_regs_pkg.all; 

105 

106 

107package {package_name} is 

108 

109{self._declarations()}\ 

110end package; 

111 

112package body {package_name} is 

113 

114{self._implementations()}\ 

115end package body; 

116""" 

117 

118 return vhdl 

119 

120 def _declarations(self) -> str: 

121 """ 

122 Get procedure declarations for all procedures. 

123 """ 

124 separator = self.get_separator_line(indent=2) 

125 vhdl = "" 

126 

127 for register, register_array in self.iterate_software_accessible_registers( 

128 direction=SoftwareAccessDirection.READ 

129 ): 

130 register_name = self.qualified_register_name( 

131 register=register, register_array=register_array 

132 ) 

133 declarations = [] 

134 

135 # Check the register value as a plain SLV casted to integer. 

136 signature = self._register_check_signature( 

137 register=register, register_array=register_array, value_type="integer" 

138 ) 

139 declarations.append(f"{signature};\n") 

140 

141 if register.fields: 

142 # Check the register value as a record. 

143 signature = self._register_check_signature( 

144 register=register, 

145 register_array=register_array, 

146 value_type=f"{register_name}_t", 

147 ) 

148 declarations.append(f"{signature};\n") 

149 else: 

150 # Check the register value as a plain SLV. 

151 # This one is made available only if there are no fields. 

152 # This is because there can be a signature ambiguity if both are available 

153 # that some compilers can not resolve. 

154 # Namely e.g. value=>(field_name => '1'). 

155 # Where the field is a std_logic. 

156 # GHDL gets confused in this case between using the signature with the record 

157 # or the one with SLV. 

158 signature = self._register_check_signature( 

159 register=register, register_array=register_array, value_type="reg_t" 

160 ) 

161 declarations.append(f"{signature};\n") 

162 

163 for field in register.fields: 

164 # Check the value of each field. 

165 signature = self._field_check_signature( 

166 register=register, 

167 register_array=register_array, 

168 field=field, 

169 ) 

170 declarations.append(f"{signature};\n") 

171 

172 vhdl += separator 

173 vhdl += "\n".join(declarations) 

174 vhdl += separator 

175 vhdl += "\n" 

176 

177 return vhdl 

178 

179 def _register_check_signature( 

180 self, register: "Register", register_array: Optional["RegisterArray"], value_type: str 

181 ) -> str: 

182 """ 

183 Get signature for a 'check_X_equal' procedure for register values. 

184 """ 

185 register_name = self.qualified_register_name( 

186 register=register, register_array=register_array 

187 ) 

188 register_description = self.register_description( 

189 register=register, register_array=register_array 

190 ) 

191 # If it is not either of these, then it is the native type which shall not have a comment 

192 # since it is the default. 

193 type_comment = ( 

194 " as a plain SLV" 

195 if value_type == "reg_t" 

196 else " as a plain SLV casted to integer" if value_type == "integer" else "" 

197 ) 

198 

199 return f"""\ 

200 -- Check that the current value of the {register_description} 

201 -- equals the given 'expected' value{type_comment}. 

202 procedure check_{register_name}_equal( 

203 signal net : inout network_t; 

204{self.get_array_index_port(register_array=register_array)}\ 

205 expected : in {value_type}; 

206 base_address : in addr_t := (others => '0'); 

207 bus_handle : in bus_master_t := regs_bus_master; 

208 message : in string := "" 

209 )\ 

210""" 

211 

212 def _field_check_signature( 

213 self, 

214 register: "Register", 

215 register_array: Optional["RegisterArray"], 

216 field: "RegisterField", 

217 ) -> str: 

218 """ 

219 Get signature for a 'check_X_equal' procedure for field values. 

220 """ 

221 value_type = self.field_type_name( 

222 register=register, register_array=register_array, field=field 

223 ) 

224 

225 field_name = self.qualified_field_name( 

226 register=register, register_array=register_array, field=field 

227 ) 

228 field_description = self.field_description( 

229 register=register, field=field, register_array=register_array 

230 ) 

231 

232 return f"""\ 

233 -- Check that the current value of the {field_description} 

234 -- equals the given 'expected' value. 

235 procedure check_{field_name}_equal( 

236 signal net : inout network_t; 

237{self.get_array_index_port(register_array=register_array)}\ 

238 expected : in {value_type}; 

239 base_address : in addr_t := (others => '0'); 

240 bus_handle : in bus_master_t := regs_bus_master; 

241 message : in string := "" 

242 )\ 

243""" 

244 

245 def _implementations(self) -> str: 

246 """ 

247 Get implementations of all procedures. 

248 """ 

249 separator = self.get_separator_line(indent=2) 

250 vhdl = "" 

251 

252 for register, register_array in self.iterate_software_accessible_registers( 

253 direction=SoftwareAccessDirection.READ 

254 ): 

255 register_name = self.qualified_register_name( 

256 register=register, register_array=register_array 

257 ) 

258 implementations = [] 

259 

260 # Check the register value as a plain SLV casted to integer. 

261 implementations.append( 

262 self._register_check_implementation( 

263 register=register, register_array=register_array, value_type="integer" 

264 ) 

265 ) 

266 

267 if register.fields: 

268 # Check the register value as a record. 

269 implementations.append( 

270 self._register_check_implementation( 

271 register=register, 

272 register_array=register_array, 

273 value_type=f"{register_name}_t", 

274 ) 

275 ) 

276 else: 

277 # Check the register value as a plain SLV. 

278 implementations.append( 

279 self._register_check_implementation( 

280 register=register, register_array=register_array, value_type="reg_t" 

281 ) 

282 ) 

283 

284 # Check the value of each field. 

285 for field in register.fields: 

286 implementations.append( 

287 self._field_check_implementation( 

288 register=register, register_array=register_array, field=field 

289 ) 

290 ) 

291 

292 if implementations: 

293 vhdl += separator 

294 vhdl += "\n".join(implementations) 

295 vhdl += separator 

296 vhdl += "\n" 

297 

298 return vhdl 

299 

300 def _register_check_implementation( 

301 self, register: "Register", register_array: Optional["RegisterArray"], value_type: str 

302 ) -> str: 

303 """ 

304 Get implementation for a 'check_X_equal' procedure for field values. 

305 """ 

306 signature = self._register_check_signature( 

307 register=register, register_array=register_array, value_type=value_type 

308 ) 

309 register_name = self.qualified_register_name( 

310 register=register, register_array=register_array 

311 ) 

312 

313 if value_type not in ["reg_t", "integer"]: 

314 # These value types do not work with the standard VUnit check procedures, because 

315 # they are custom types. 

316 # They also can not be casted to string directly. 

317 check = """\ 

318 if got /= expected then 

319 failing_check( 

320 checker => default_checker, 

321 msg => p_std_msg( 

322 check_result => "Equality check failed", 

323 msg => get_message, 

324 ctx => ( 

325 "Got " & to_string(to_slv(got)) & ". Expected " & to_string(to_slv(expected)) & "." 

326 ) 

327 ) 

328 ); 

329 end if;""" 

330 

331 else: 

332 check = " check_equal(got=>got, expected=>expected, msg=>get_message);" 

333 

334 return f"""\ 

335{signature} is 

336{self.get_register_array_message(register_array=register_array)}\ 

337{self.get_base_address_message()}\ 

338 constant base_message : string := ( 

339 "Checking the '{register.name}' register" 

340 & register_array_message 

341 & base_address_message 

342 & "." 

343 ); 

344{self.get_message()}\ 

345 

346 variable got : {value_type}; 

347 begin 

348 read_{register_name}( 

349 net => net, 

350{self.get_array_index_association(register_array=register_array)}\ 

351 value => got, 

352 base_address => base_address, 

353 bus_handle => bus_handle 

354 ); 

355 

356{check} 

357 end procedure; 

358""" 

359 

360 def _field_check_implementation( 

361 self, 

362 register: "Register", 

363 register_array: Optional["RegisterArray"], 

364 field: "RegisterField", 

365 ) -> str: 

366 """ 

367 Get implementation for a 'check_X_equal' procedure for field values. 

368 """ 

369 signature = self._field_check_signature( 

370 register=register, register_array=register_array, field=field 

371 ) 

372 register_name = self.qualified_register_name( 

373 register=register, register_array=register_array 

374 ) 

375 field_name = self.qualified_field_name( 

376 register=register, register_array=register_array, field=field 

377 ) 

378 value_type = self.field_type_name( 

379 register=register, register_array=register_array, field=field 

380 ) 

381 

382 if isinstance(field, Enumeration) or ( 

383 isinstance(field, BitVector) and isinstance(field.numerical_interpretation, Fixed) 

384 ): 

385 # These field types do not work with the standard VUnit check procedures. 

386 # Enumeration because it is a custom type. 

387 # ufixed and sfixed could for all intents and purposes be supported in VUnit, 

388 # but they are not at the moment (4.7.0). 

389 # Instead, do a custom check, with error reporting in the same way as in check_equal 

390 # procedures in VUnit. 

391 # The VUnit check also has a logging upon a passing check, but we skip that so we do 

392 # not have to copy so much code. 

393 def to_string(name: str) -> str: 

394 if isinstance(field, Enumeration): 

395 return f"to_string({name})" 

396 

397 if isinstance(field, BitVector) and isinstance( 

398 field.numerical_interpretation, Fixed 

399 ): 

400 return f'to_string({name}) & " (" & to_string(to_real({name}), "%f") & ")"' 

401 

402 raise ValueError(f"Unsupported field type: {field}") 

403 

404 check = f"""\ 

405 if got /= expected then 

406 failing_check( 

407 checker => default_checker, 

408 msg => p_std_msg( 

409 check_result => "Equality check failed", 

410 msg => get_message, 

411 ctx => ( 

412 "Got " & {to_string("got")} & "." 

413 & " Expected " & {to_string("expected")} & "." 

414 ) 

415 ) 

416 ); 

417 end if;""" 

418 

419 else: 

420 check = " check_equal(got=>got, expected=>expected, msg=>get_message);" 

421 

422 return f"""\ 

423{signature} is 

424{self.get_register_array_message(register_array=register_array)}\ 

425{self.get_base_address_message()}\ 

426 constant base_message : string := ( 

427 "Checking the '{field.name}' field in the '{register.name}' register" 

428 & register_array_message 

429 & base_address_message 

430 & "." 

431 ); 

432{self.get_message()}\ 

433 

434 variable got_reg : {register_name}_t := {register_name}_init; 

435 variable got : {value_type} := {field_name}_init; 

436 begin 

437 read_{register_name}( 

438 net => net, 

439{self.get_array_index_association(register_array=register_array)}\ 

440 value => got_reg, 

441 base_address => base_address, 

442 bus_handle => bus_handle 

443 ); 

444 got := got_reg.{field.name}; 

445 

446{check} 

447 end procedure; 

448"""