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

92 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-29 06:41 +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.field.bit_vector import BitVector 

15from hdl_registers.field.enumeration import Enumeration 

16from hdl_registers.field.numerical_interpretation import Fixed 

17from hdl_registers.register_mode import SoftwareAccessDirection 

18 

19from .vhdl_simulation_generator_common import VhdlSimulationGeneratorCommon 

20 

21if TYPE_CHECKING: 

22 from pathlib import Path 

23 

24 from hdl_registers.field.register_field import RegisterField 

25 from hdl_registers.register import Register 

26 from hdl_registers.register_array import RegisterArray 

27 

28 

29class VhdlSimulationCheckPackageGenerator(VhdlSimulationGeneratorCommon): 

30 """ 

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

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

33 

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

35 to a given expected value. 

36 Expected value can be provided as 

37 

38 1. bit vector, 

39 

40 2. integer, or 

41 

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

43 

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

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

46 

47 Uses VUnit Verification Component calls, via the procedures from 

48 :class:`.VhdlSimulationReadWritePackageGenerator`. 

49 

50 The generated VHDL file needs also the generated packages from 

51 :class:`.VhdlRegisterPackageGenerator` and :class:`.VhdlRecordPackageGenerator`. 

52 See also :ref:`vhdl_dependencies` for further dependencies. 

53 """ 

54 

55 __version__ = "1.2.1" 

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( 

67 self, 

68 **kwargs: Any, # noqa: ANN401 

69 ) -> Path: 

70 """ 

71 See super class for API details. 

72 

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

74 actually has any registers. 

75 """ 

76 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs) 

77 

78 def get_code( 

79 self, 

80 **kwargs: Any, # noqa: ANN401, ARG002 

81 ) -> str: 

82 """ 

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

84 """ 

85 package_name = self.output_file.stem 

86 

87 return f"""\ 

88library ieee; 

89use ieee.fixed_pkg.all; 

90use ieee.numeric_std.all; 

91use ieee.std_logic_1164.all; 

92 

93library vunit_lib; 

94use vunit_lib.bus_master_pkg.bus_master_t; 

95use vunit_lib.check_pkg.all; 

96use vunit_lib.checker_pkg.all; 

97use vunit_lib.com_types_pkg.network_t; 

98use vunit_lib.string_ops.hex_image; 

99 

100library register_file; 

101use register_file.register_file_pkg.register_t; 

102use register_file.register_operations_pkg.register_bus_master; 

103 

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

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

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

107 

108 

109package {package_name} is 

110 

111{self._declarations()}\ 

112end package; 

113 

114package body {package_name} is 

115 

116{self._implementations()}\ 

117end package body; 

118""" 

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="register_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: RegisterArray | None, 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 == "register_t" 

196 else " as a plain SLV casted to integer" 

197 if value_type == "integer" 

198 else "" 

199 ) 

200 

201 return f"""\ 

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

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

204 procedure check_{register_name}_equal( 

205 signal net : inout network_t; 

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

207 expected : in {value_type}; 

208 base_address : in unsigned(32 - 1 downto 0) := (others => '0'); 

209 bus_handle : in bus_master_t := register_bus_master; 

210 message : in string := "" 

211 )\ 

212""" 

213 

214 def _field_check_signature( 

215 self, 

216 register: Register, 

217 register_array: RegisterArray | None, 

218 field: RegisterField, 

219 ) -> str: 

220 """ 

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

222 """ 

223 value_type = self.field_type_name( 

224 register=register, register_array=register_array, field=field 

225 ) 

226 

227 field_name = self.qualified_field_name( 

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

229 ) 

230 field_description = self.field_description( 

231 register=register, field=field, register_array=register_array 

232 ) 

233 

234 return f"""\ 

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

236 -- equals the given 'expected' value. 

237 procedure check_{field_name}_equal( 

238 signal net : inout network_t; 

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

240 expected : in {value_type}; 

241 base_address : in unsigned(32 - 1 downto 0) := (others => '0'); 

242 bus_handle : in bus_master_t := register_bus_master; 

243 message : in string := "" 

244 )\ 

245""" 

246 

247 def _implementations(self) -> str: 

248 """ 

249 Get implementations of all procedures. 

250 """ 

251 separator = self.get_separator_line(indent=2) 

252 vhdl = "" 

253 

254 for register, register_array in self.iterate_software_accessible_registers( 

255 direction=SoftwareAccessDirection.READ 

256 ): 

257 register_name = self.qualified_register_name( 

258 register=register, register_array=register_array 

259 ) 

260 

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

262 implementations = [ 

263 self._register_check_implementation( 

264 register=register, register_array=register_array, value_type="integer" 

265 ) 

266 ] 

267 

268 if register.fields: 

269 # Check the register value as a record. 

270 implementations.append( 

271 self._register_check_implementation( 

272 register=register, 

273 register_array=register_array, 

274 value_type=f"{register_name}_t", 

275 ) 

276 ) 

277 else: 

278 # Check the register value as a plain SLV. 

279 implementations.append( 

280 self._register_check_implementation( 

281 register=register, register_array=register_array, value_type="register_t" 

282 ) 

283 ) 

284 

285 # Check the value of each field. 

286 implementations.extend( 

287 self._field_check_implementation( 

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

289 ) 

290 for field in register.fields 

291 ) 

292 

293 if implementations: 

294 vhdl += separator 

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

296 vhdl += separator 

297 vhdl += "\n" 

298 

299 return vhdl 

300 

301 def _register_check_implementation( 

302 self, register: Register, register_array: RegisterArray | None, value_type: str 

303 ) -> str: 

304 """ 

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

306 """ 

307 signature = self._register_check_signature( 

308 register=register, register_array=register_array, value_type=value_type 

309 ) 

310 register_name = self.qualified_register_name( 

311 register=register, register_array=register_array 

312 ) 

313 

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

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

316 # they are custom types. 

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

318 check = """\ 

319 if got /= expected then 

320 failing_check( 

321 checker => default_checker, 

322 msg => p_std_msg( 

323 check_result => "Equality check failed", 

324 msg => get_message, 

325 ctx => ( 

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

327 ) 

328 ) 

329 ); 

330 end if;""" 

331 

332 else: 

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

334 

335 return f"""\ 

336{signature} is 

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

338{self.get_base_address_message()}\ 

339 constant base_message : string := ( 

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

341 & register_array_message 

342 & base_address_message 

343 & "." 

344 ); 

345{self.get_message()}\ 

346 

347 variable got : {value_type}; 

348 begin 

349 read_{register_name}( 

350 net => net, 

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

352 value => got, 

353 base_address => base_address, 

354 bus_handle => bus_handle 

355 ); 

356 

357{check} 

358 end procedure; 

359""" 

360 

361 def _field_check_implementation( 

362 self, 

363 register: Register, 

364 register_array: RegisterArray | None, 

365 field: RegisterField, 

366 ) -> str: 

367 """ 

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

369 """ 

370 signature = self._field_check_signature( 

371 register=register, register_array=register_array, field=field 

372 ) 

373 register_name = self.qualified_register_name( 

374 register=register, register_array=register_array 

375 ) 

376 field_name = self.qualified_field_name( 

377 register=register, register_array=register_array, field=field 

378 ) 

379 value_type = self.field_type_name( 

380 register=register, register_array=register_array, field=field 

381 ) 

382 

383 if isinstance(field, Enumeration) or ( 

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

385 ): 

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

387 # Enumeration because it is a custom type. 

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

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

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

391 # procedures in VUnit. 

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

393 # not have to copy so much code. 

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

395 if isinstance(field, Enumeration): 

396 return f"to_string({name})" 

397 

398 if isinstance(field, BitVector) and isinstance( 

399 field.numerical_interpretation, Fixed 

400 ): 

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

402 

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

404 

405 check = f"""\ 

406 if got /= expected then 

407 failing_check( 

408 checker => default_checker, 

409 msg => p_std_msg( 

410 check_result => "Equality check failed", 

411 msg => get_message, 

412 ctx => ( 

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

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

415 ) 

416 ) 

417 ); 

418 end if;""" 

419 

420 else: 

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

422 

423 return f"""\ 

424{signature} is 

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

426{self.get_base_address_message()}\ 

427 constant base_message : string := ( 

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

429 & register_array_message 

430 & base_address_message 

431 & "." 

432 ); 

433{self.get_message()}\ 

434 

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

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

437 begin 

438 read_{register_name}( 

439 net => net, 

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

441 value => got_reg, 

442 base_address => base_address, 

443 bus_handle => bus_handle 

444 ); 

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

446 

447{check} 

448 end procedure; 

449"""