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

94 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.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"""\ 

82library ieee; 

83use ieee.fixed_pkg.all; 

84use ieee.numeric_std.all; 

85use ieee.std_logic_1164.all; 

86 

87library vunit_lib; 

88use vunit_lib.bus_master_pkg.bus_master_t; 

89use vunit_lib.check_pkg.all; 

90use vunit_lib.checker_pkg.all; 

91use vunit_lib.com_types_pkg.network_t; 

92use vunit_lib.string_ops.hex_image; 

93 

94library common; 

95use common.addr_pkg.addr_t; 

96 

97library register_file; 

98use register_file.register_file_pkg.register_t; 

99use register_file.register_operations_pkg.register_bus_master; 

100 

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

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

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

104 

105 

106package {package_name} is 

107 

108{self._declarations()}\ 

109end package; 

110 

111package body {package_name} is 

112 

113{self._implementations()}\ 

114end package body; 

115""" 

116 

117 return vhdl 

118 

119 def _declarations(self) -> str: 

120 """ 

121 Get procedure declarations for all procedures. 

122 """ 

123 separator = self.get_separator_line(indent=2) 

124 vhdl = "" 

125 

126 for register, register_array in self.iterate_software_accessible_registers( 

127 direction=SoftwareAccessDirection.READ 

128 ): 

129 register_name = self.qualified_register_name( 

130 register=register, register_array=register_array 

131 ) 

132 declarations = [] 

133 

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

135 signature = self._register_check_signature( 

136 register=register, register_array=register_array, value_type="integer" 

137 ) 

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

139 

140 if register.fields: 

141 # Check the register value as a record. 

142 signature = self._register_check_signature( 

143 register=register, 

144 register_array=register_array, 

145 value_type=f"{register_name}_t", 

146 ) 

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

148 else: 

149 # Check the register value as a plain SLV. 

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

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

152 # that some compilers can not resolve. 

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

154 # Where the field is a std_logic. 

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

156 # or the one with SLV. 

157 signature = self._register_check_signature( 

158 register=register, register_array=register_array, value_type="register_t" 

159 ) 

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

161 

162 for field in register.fields: 

163 # Check the value of each field. 

164 signature = self._field_check_signature( 

165 register=register, 

166 register_array=register_array, 

167 field=field, 

168 ) 

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

170 

171 vhdl += separator 

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

173 vhdl += separator 

174 vhdl += "\n" 

175 

176 return vhdl 

177 

178 def _register_check_signature( 

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

180 ) -> str: 

181 """ 

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

183 """ 

184 register_name = self.qualified_register_name( 

185 register=register, register_array=register_array 

186 ) 

187 register_description = self.register_description( 

188 register=register, register_array=register_array 

189 ) 

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

191 # since it is the default. 

192 type_comment = ( 

193 " as a plain SLV" 

194 if value_type == "register_t" 

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

196 ) 

197 

198 return f"""\ 

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

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

201 procedure check_{register_name}_equal( 

202 signal net : inout network_t; 

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

204 expected : in {value_type}; 

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

206 bus_handle : in bus_master_t := register_bus_master; 

207 message : in string := "" 

208 )\ 

209""" 

210 

211 def _field_check_signature( 

212 self, 

213 register: "Register", 

214 register_array: Optional["RegisterArray"], 

215 field: "RegisterField", 

216 ) -> str: 

217 """ 

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

219 """ 

220 value_type = self.field_type_name( 

221 register=register, register_array=register_array, field=field 

222 ) 

223 

224 field_name = self.qualified_field_name( 

225 register=register, register_array=register_array, field=field 

226 ) 

227 field_description = self.field_description( 

228 register=register, field=field, register_array=register_array 

229 ) 

230 

231 return f"""\ 

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

233 -- equals the given 'expected' value. 

234 procedure check_{field_name}_equal( 

235 signal net : inout network_t; 

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

237 expected : in {value_type}; 

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

239 bus_handle : in bus_master_t := register_bus_master; 

240 message : in string := "" 

241 )\ 

242""" 

243 

244 def _implementations(self) -> str: 

245 """ 

246 Get implementations of all procedures. 

247 """ 

248 separator = self.get_separator_line(indent=2) 

249 vhdl = "" 

250 

251 for register, register_array in self.iterate_software_accessible_registers( 

252 direction=SoftwareAccessDirection.READ 

253 ): 

254 register_name = self.qualified_register_name( 

255 register=register, register_array=register_array 

256 ) 

257 implementations = [] 

258 

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

260 implementations.append( 

261 self._register_check_implementation( 

262 register=register, register_array=register_array, value_type="integer" 

263 ) 

264 ) 

265 

266 if register.fields: 

267 # Check the register value as a record. 

268 implementations.append( 

269 self._register_check_implementation( 

270 register=register, 

271 register_array=register_array, 

272 value_type=f"{register_name}_t", 

273 ) 

274 ) 

275 else: 

276 # Check the register value as a plain SLV. 

277 implementations.append( 

278 self._register_check_implementation( 

279 register=register, register_array=register_array, value_type="register_t" 

280 ) 

281 ) 

282 

283 # Check the value of each field. 

284 for field in register.fields: 

285 implementations.append( 

286 self._field_check_implementation( 

287 register=register, register_array=register_array, field=field 

288 ) 

289 ) 

290 

291 if implementations: 

292 vhdl += separator 

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

294 vhdl += separator 

295 vhdl += "\n" 

296 

297 return vhdl 

298 

299 def _register_check_implementation( 

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

301 ) -> str: 

302 """ 

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

304 """ 

305 signature = self._register_check_signature( 

306 register=register, register_array=register_array, value_type=value_type 

307 ) 

308 register_name = self.qualified_register_name( 

309 register=register, register_array=register_array 

310 ) 

311 

312 if value_type not in ["register_t", "integer"]: 

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

314 # they are custom types. 

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

316 check = """\ 

317 if got /= expected then 

318 failing_check( 

319 checker => default_checker, 

320 msg => p_std_msg( 

321 check_result => "Equality check failed", 

322 msg => get_message, 

323 ctx => ( 

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

325 ) 

326 ) 

327 ); 

328 end if;""" 

329 

330 else: 

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

332 

333 return f"""\ 

334{signature} is 

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

336{self.get_base_address_message()}\ 

337 constant base_message : string := ( 

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

339 & register_array_message 

340 & base_address_message 

341 & "." 

342 ); 

343{self.get_message()}\ 

344 

345 variable got : {value_type}; 

346 begin 

347 read_{register_name}( 

348 net => net, 

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

350 value => got, 

351 base_address => base_address, 

352 bus_handle => bus_handle 

353 ); 

354 

355{check} 

356 end procedure; 

357""" 

358 

359 def _field_check_implementation( 

360 self, 

361 register: "Register", 

362 register_array: Optional["RegisterArray"], 

363 field: "RegisterField", 

364 ) -> str: 

365 """ 

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

367 """ 

368 signature = self._field_check_signature( 

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

370 ) 

371 register_name = self.qualified_register_name( 

372 register=register, register_array=register_array 

373 ) 

374 field_name = self.qualified_field_name( 

375 register=register, register_array=register_array, field=field 

376 ) 

377 value_type = self.field_type_name( 

378 register=register, register_array=register_array, field=field 

379 ) 

380 

381 if isinstance(field, Enumeration) or ( 

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

383 ): 

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

385 # Enumeration because it is a custom type. 

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

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

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

389 # procedures in VUnit. 

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

391 # not have to copy so much code. 

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

393 if isinstance(field, Enumeration): 

394 return f"to_string({name})" 

395 

396 if isinstance(field, BitVector) and isinstance( 

397 field.numerical_interpretation, Fixed 

398 ): 

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

400 

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

402 

403 check = f"""\ 

404 if got /= expected then 

405 failing_check( 

406 checker => default_checker, 

407 msg => p_std_msg( 

408 check_result => "Equality check failed", 

409 msg => get_message, 

410 ctx => ( 

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

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

413 ) 

414 ) 

415 ); 

416 end if;""" 

417 

418 else: 

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

420 

421 return f"""\ 

422{signature} is 

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

424{self.get_base_address_message()}\ 

425 constant base_message : string := ( 

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

427 & register_array_message 

428 & base_address_message 

429 & "." 

430 ); 

431{self.get_message()}\ 

432 

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

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

435 begin 

436 read_{register_name}( 

437 net => net, 

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

439 value => got_reg, 

440 base_address => base_address, 

441 bus_handle => bus_handle 

442 ); 

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

444 

445{check} 

446 end procedure; 

447"""