Coverage for hdl_registers/generator/vhdl/simulation/check_package.py: 96%
94 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-01 20:50 +0000
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-01 20:50 +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"""\
82{self.header}
83library ieee;
84use ieee.fixed_pkg.all;
85use ieee.numeric_std.all;
86use ieee.std_logic_1164.all;
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;
95library common;
96use common.addr_pkg.addr_t;
98library reg_file;
99use reg_file.reg_file_pkg.reg_t;
100use reg_file.reg_operations_pkg.regs_bus_master;
102use work.{self.name}_register_read_write_pkg.all;
103use work.{self.name}_register_record_pkg.all;
104use work.{self.name}_regs_pkg.all;
107package {package_name} is
109{self._declarations()}\
110end package;
112package body {package_name} is
114{self._implementations()}\
115end package body;
116"""
118 return vhdl
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="reg_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: 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 )
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"""
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 )
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 )
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"""
245 def _implementations(self) -> str:
246 """
247 Get implementations of all procedures.
248 """
249 separator = self.get_separator_line(indent=2)
250 vhdl = ""
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 = []
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 )
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 )
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 )
292 if implementations:
293 vhdl += separator
294 vhdl += "\n".join(implementations)
295 vhdl += separator
296 vhdl += "\n"
298 return vhdl
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 )
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;"""
331 else:
332 check = " check_equal(got=>got, expected=>expected, msg=>get_message);"
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()}\
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 );
356{check}
357 end procedure;
358"""
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 )
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})"
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") & ")"'
402 raise ValueError(f"Unsupported field type: {field}")
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;"""
419 else:
420 check = " check_equal(got=>got, expected=>expected, msg=>get_message);"
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()}\
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};
446{check}
447 end procedure;
448"""