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
« 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# --------------------------------------------------------------------------------------------------
10# Standard libraries
11from pathlib import Path
12from typing import TYPE_CHECKING, Any, Optional
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
20# Local folder libraries
21from .vhdl_simulation_generator_common import VhdlSimulationGeneratorCommon
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
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.
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
39 1. bit vector,
41 2. integer, or
43 3. native VHDL record type as given by :class:`.VhdlRecordPackageGenerator`.
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.
48 Uses VUnit Verification Component calls, via the procedures from
49 :class:`.VhdlSimulationReadWritePackageGenerator`.
51 The generated VHDL file needs also the generated packages from
52 :class:`.VhdlRegisterPackageGenerator` and :class:`.VhdlRecordPackageGenerator`.
53 """
55 __version__ = "1.2.0"
57 SHORT_DESCRIPTION = "VHDL simulation check package"
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"
66 def create(self, **kwargs: Any) -> Path:
67 """
68 See super class for API details.
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)
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
81 vhdl = f"""\
82library ieee;
83use ieee.fixed_pkg.all;
84use ieee.numeric_std.all;
85use ieee.std_logic_1164.all;
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;
94library common;
95use common.addr_pkg.addr_t;
97library register_file;
98use register_file.register_file_pkg.register_t;
99use register_file.register_operations_pkg.register_bus_master;
101use work.{self.name}_register_read_write_pkg.all;
102use work.{self.name}_register_record_pkg.all;
103use work.{self.name}_regs_pkg.all;
106package {package_name} is
108{self._declarations()}\
109end package;
111package body {package_name} is
113{self._implementations()}\
114end package body;
115"""
117 return vhdl
119 def _declarations(self) -> str:
120 """
121 Get procedure declarations for all procedures.
122 """
123 separator = self.get_separator_line(indent=2)
124 vhdl = ""
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 = []
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")
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")
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")
171 vhdl += separator
172 vhdl += "\n".join(declarations)
173 vhdl += separator
174 vhdl += "\n"
176 return vhdl
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 )
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"""
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 )
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 )
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"""
244 def _implementations(self) -> str:
245 """
246 Get implementations of all procedures.
247 """
248 separator = self.get_separator_line(indent=2)
249 vhdl = ""
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 = []
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 )
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 )
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 )
291 if implementations:
292 vhdl += separator
293 vhdl += "\n".join(implementations)
294 vhdl += separator
295 vhdl += "\n"
297 return vhdl
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 )
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;"""
330 else:
331 check = " check_equal(got=>got, expected=>expected, msg=>get_message);"
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()}\
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 );
355{check}
356 end procedure;
357"""
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 )
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})"
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") & ")"'
401 raise ValueError(f"Unsupported field type: {field}")
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;"""
418 else:
419 check = " check_equal(got=>got, expected=>expected, msg=>get_message);"
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()}\
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};
445{check}
446 end procedure;
447"""