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
« 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# --------------------------------------------------------------------------------------------------
10from __future__ import annotations
12from typing import TYPE_CHECKING, Any
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
19from .vhdl_simulation_generator_common import VhdlSimulationGeneratorCommon
21if TYPE_CHECKING:
22 from pathlib import Path
24 from hdl_registers.field.register_field import RegisterField
25 from hdl_registers.register import Register
26 from hdl_registers.register_array import RegisterArray
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.
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
38 1. bit vector,
40 2. integer, or
42 3. native VHDL record type as given by :class:`.VhdlRecordPackageGenerator`.
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.
47 Uses VUnit Verification Component calls, via the procedures from
48 :class:`.VhdlSimulationReadWritePackageGenerator`.
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 """
55 __version__ = "1.2.1"
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(
67 self,
68 **kwargs: Any, # noqa: ANN401
69 ) -> Path:
70 """
71 See super class for API details.
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)
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
87 return f"""\
88library ieee;
89use ieee.fixed_pkg.all;
90use ieee.numeric_std.all;
91use ieee.std_logic_1164.all;
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;
100library register_file;
101use register_file.register_file_pkg.register_t;
102use register_file.register_operations_pkg.register_bus_master;
104use work.{self.name}_register_read_write_pkg.all;
105use work.{self.name}_register_record_pkg.all;
106use work.{self.name}_regs_pkg.all;
109package {package_name} is
111{self._declarations()}\
112end package;
114package body {package_name} is
116{self._implementations()}\
117end package body;
118"""
120 def _declarations(self) -> str:
121 """
122 Get procedure declarations for all procedures.
123 """
124 separator = self.get_separator_line(indent=2)
125 vhdl = ""
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 = []
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")
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")
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")
172 vhdl += separator
173 vhdl += "\n".join(declarations)
174 vhdl += separator
175 vhdl += "\n"
177 return vhdl
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 )
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"""
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 )
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 )
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"""
247 def _implementations(self) -> str:
248 """
249 Get implementations of all procedures.
250 """
251 separator = self.get_separator_line(indent=2)
252 vhdl = ""
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 )
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 ]
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 )
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 )
293 if implementations:
294 vhdl += separator
295 vhdl += "\n".join(implementations)
296 vhdl += separator
297 vhdl += "\n"
299 return vhdl
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 )
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;"""
332 else:
333 check = " check_equal(got=>got, expected=>expected, msg=>get_message);"
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()}\
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 );
357{check}
358 end procedure;
359"""
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 )
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})"
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") & ")"'
403 raise ValueError(f"Unsupported field type: {field}")
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;"""
420 else:
421 check = " check_equal(got=>got, expected=>expected, msg=>get_message);"
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()}\
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};
447{check}
448 end procedure;
449"""