Coverage for tsfpga/examples/simulation_utils.py: 0%
110 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 20:52 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 20:52 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project, a project platform for modern FPGA development.
5# https://tsfpga.com
6# https://github.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9# Standard libraries
10import argparse
11from pathlib import Path
12from typing import TYPE_CHECKING, Any, Optional, Type
14# Third party libraries
15import hdl_registers
16from vunit.ui import VUnit
17from vunit.vivado.vivado import add_from_compile_order_file, create_compile_order_file
18from vunit.vunit_cli import VUnitCLI
20# First party libraries
21import tsfpga
22import tsfpga.create_vhdl_ls_config
23from tsfpga.git_simulation_subset import GitSimulationSubset
24from tsfpga.module_list import ModuleList
25from tsfpga.vivado.common import get_vivado_path
26from tsfpga.vivado.ip_cores import VivadoIpCores
27from tsfpga.vivado.project import VivadoIpCoreProject
28from tsfpga.vivado.simlib import VivadoSimlib
30if TYPE_CHECKING:
31 # First party libraries
32 from tsfpga.vivado.simlib_common import VivadoSimlibCommon
35def get_arguments_cli(default_output_path: Path) -> VUnitCLI:
36 """
37 Get arguments for the simulation flow.
39 Arguments:
40 default_output_path: Will be set as default for output path arguments
41 (both VUnit files and Vivado files).
42 """
43 cli = VUnitCLI()
45 # Print default values when doing --help
46 cli.parser.formatter_class = argparse.ArgumentDefaultsHelpFormatter
48 # Set the supplied default value for VUnit output path. pylint: disable=protected-access
49 for action in cli.parser._actions:
50 if action.dest == "output_path":
51 action.default = default_output_path / "vunit_out"
52 break
53 else:
54 raise AssertionError("VUnit --output-path argument not found")
56 cli.parser.add_argument(
57 "--output-path-vivado",
58 type=Path,
59 default=default_output_path,
60 help=(
61 "where to place Vivado IP core and simlib files. "
62 "Note that --output-path is for VUnit files"
63 ),
64 )
66 cli.parser.add_argument(
67 "--vivado-skip", action="store_true", help="skip all steps that require Vivado"
68 )
70 cli.parser.add_argument(
71 "--ip-compile", action="store_true", help="force (re)compile of IP cores"
72 )
74 cli.parser.add_argument(
75 "--simlib-compile", action="store_true", help="force (re)compile of Vivado simlib"
76 )
78 cli.parser.add_argument(
79 "--vcs-minimal",
80 action="store_true",
81 help="run a minimal set of tests based on version control system (e.g. git) history",
82 )
84 cli.parser.add_argument(
85 "--inspect",
86 action="store_true",
87 help="optionally inspect some simulation result. Is only available for some modules",
88 )
90 return cli
93class SimulationProject:
94 """
95 Class for setting up and handling a VUnit simulation project. Should be reusable in most cases.
96 """
98 def __init__(self, args: argparse.Namespace, enable_preprocessing: bool = False) -> None:
99 """
100 Create a VUnit project, configured according to the given arguments.
102 Arguments:
103 args: Command line argument namespace from ``simulate.py``.
104 enable_preprocessing: If ``True``, VUnit location/check preprocessing will be enabled.
105 """
106 self.args = args
108 self.vunit_proj = VUnit.from_args(args=args)
109 self.vunit_proj.add_vhdl_builtins()
110 self.vunit_proj.add_verification_components()
111 self.vunit_proj.add_random()
113 if enable_preprocessing:
114 self.vunit_proj.enable_location_preprocessing()
115 self.vunit_proj.enable_check_preprocessing()
117 self.has_commercial_simulator = self.vunit_proj.get_simulator_name() != "ghdl"
119 def add_modules(
120 self,
121 modules: ModuleList,
122 modules_no_test: Optional[ModuleList] = None,
123 include_vhdl_files: bool = True,
124 include_verilog_files: bool = True,
125 include_systemverilog_files: bool = True,
126 **setup_vunit_kwargs: Any,
127 ) -> None:
128 """
129 Add module source files to the VUnit project.
131 Arguments:
132 modules: These modules will be included in the simulation project.
133 modules_no_test: Source and simulation files from these modules will be included in the
134 simulation project, but no testbenches.
135 Provide your dependency modules here, if you have any.
136 include_vhdl_files: Optionally disable inclusion of VHDL files from
137 the modules.
138 include_verilog_files: Optionally disable inclusion of Verilog files from
139 the modules.
140 include_systemverilog_files: Optionally disable inclusion of SystemVerilog files from
141 the modules.
142 setup_vunit_kwargs: Further arguments that will be sent to
143 :meth:`.BaseModule.setup_vunit` for each module.
144 Note that this is a "kwargs" style argument; any number of named arguments can
145 be sent.
146 """
147 modules_no_test = ModuleList() if modules_no_test is None else modules_no_test
149 include_unisim = not self.args.vivado_skip
150 include_ip_cores = self.has_commercial_simulator and not self.args.vivado_skip
152 for module in modules + modules_no_test:
153 vunit_library = self.vunit_proj.add_library(
154 library_name=module.library_name, allow_duplicate=True
155 )
156 simulate_this_module = module not in modules_no_test
158 for hdl_file in module.get_simulation_files(
159 include_tests=simulate_this_module,
160 include_unisim=include_unisim,
161 include_ip_cores=include_ip_cores,
162 include_vhdl_files=include_vhdl_files,
163 include_verilog_files=include_verilog_files,
164 include_systemverilog_files=include_systemverilog_files,
165 ):
166 vunit_library.add_source_file(hdl_file.path)
168 if simulate_this_module:
169 module.setup_vunit(
170 vunit_proj=self.vunit_proj,
171 include_unisim=include_unisim,
172 include_ip_cores=include_ip_cores,
173 inspect=self.args.inspect,
174 **setup_vunit_kwargs,
175 )
177 def add_vivado_simlib(self) -> Optional["VivadoSimlibCommon"]:
178 """
179 Add Vivado simlib to the VUnit project, unless instructed not to by ``args``.
180 Will compile simlib if necessary.
182 Return:
183 The simlib object, ``None`` if simlib was not added due to command line argument.
184 """
185 if self.args.vivado_skip:
186 return None
188 return self._add_simlib(
189 output_path=self.args.output_path_vivado, force_compile=self.args.simlib_compile
190 )
192 def _add_simlib(self, output_path: Path, force_compile: bool) -> "VivadoSimlibCommon":
193 """
194 Add Vivado simlib to the VUnit project. Compile if needed.
196 .. note::
198 This method can be overloaded in a subclass if you want to do something more
199 advanced, e.g. fetching compiled simlib from Artifactory.
201 Arguments:
202 output_path: Compiled simlib will be placed in sub-directory of this path.
203 force_compile: Will (re)-compile simlib even if compiled artifacts exist.
205 Return:
206 The simlib object.
207 """
208 vivado_simlib = VivadoSimlib.init(output_path=output_path, vunit_proj=self.vunit_proj)
209 if force_compile or vivado_simlib.compile_is_needed:
210 vivado_simlib.compile()
211 vivado_simlib.to_archive()
213 vivado_simlib.add_to_vunit_project()
215 # Code in the "vital2000" package gives GHDL errors such as "result subtype of a pure
216 # function cannot have access sub-elements". Hence, relaxed rules need to be enabled when
217 # using unisim.
218 self.vunit_proj.set_sim_option("ghdl.elab_flags", ["-frelaxed-rules"])
220 return vivado_simlib
222 def add_vivado_ip_cores(
223 self,
224 modules: ModuleList,
225 vivado_part_name: str = "xc7z020clg400-1",
226 vivado_ip_core_project_class: Optional[Type[VivadoIpCoreProject]] = None,
227 ) -> Optional[Path]:
228 """
229 Generate IP cores from the modules, unless instructed not to by ``args``.
230 When running with a commercial simulator they will be added to the VUnit project.
232 Arguments:
233 modules: IP cores from these modules will be included in the simulation project.
234 vivado_part_name: Part name to be used for Vivado IP core project.
235 Might have to change from default depending on what parts you have available in your
236 Vivado installation.
237 vivado_ip_core_project_class: Class to be used for Vivado IP core project.
238 Can be left at default in most cases.
240 Return:
241 Path to the Vivado IP core project's ``project`` directory.
242 ``None`` if Vivado IP cores were not added due to command line argument.
243 """
244 if self.args.vivado_skip:
245 return None
247 # Generate IP core simulation files. Might be used for the vhdl_ls config,
248 # even if they are not added to the simulation project.
249 (
250 ip_core_compile_order_file,
251 ip_core_vivado_project_directory,
252 ) = self._generate_ip_core_files(
253 modules=modules,
254 output_path=self.args.output_path_vivado,
255 force_generate=self.args.ip_compile,
256 part_name=vivado_part_name,
257 vivado_project_class=vivado_ip_core_project_class,
258 )
259 if self.has_commercial_simulator:
260 add_from_compile_order_file(
261 vunit_obj=self.vunit_proj, compile_order_file=ip_core_compile_order_file
262 )
264 return ip_core_vivado_project_directory
266 @staticmethod
267 def _generate_ip_core_files(
268 modules: ModuleList,
269 output_path: Path,
270 force_generate: bool,
271 part_name: str,
272 vivado_project_class: Optional[Type[VivadoIpCoreProject]] = None,
273 ) -> tuple[Path, Path]:
274 """
275 Generate Vivado IP core files that are to be added to the VUnit project.
276 Create a new project to generate files if needed.
278 Arguments:
279 modules: IP cores from these modules will be included.
280 output_path: IP core files will be placed in sub-directory of this path.
281 force_generate: Will (re)-generate files even if they exist.
282 part_name: Vivado part name.
283 vivado_project_class: Class to be used for Vivado IP core project.
284 """
285 vivado_ip_cores = VivadoIpCores(
286 modules=modules,
287 output_path=output_path,
288 part_name=part_name,
289 vivado_project_class=vivado_project_class,
290 )
292 if force_generate:
293 vivado_ip_cores.create_vivado_project()
294 vivado_project_created = True
295 else:
296 vivado_project_created = vivado_ip_cores.create_vivado_project_if_needed()
298 if vivado_project_created:
299 # If the IP core Vivado project has been (re)created we need to create
300 # a new compile order file
301 create_compile_order_file(
302 project_file=vivado_ip_cores.vivado_project_file,
303 compile_order_file=vivado_ip_cores.compile_order_file,
304 )
306 return vivado_ip_cores.compile_order_file, vivado_ip_cores.project_directory
309class NoVcsDiffTestsFound(Exception):
310 """
311 Raised by :meth:`.find_git_test_filter` when no tests are found due to no
312 VHDL-related git diff.
313 """
316def find_git_test_filter(
317 args: argparse.Namespace,
318 repo_root: Path,
319 modules: "ModuleList",
320 modules_no_test: Optional["ModuleList"] = None,
321 reference_branch: str = "origin/main",
322 **setup_vunit_kwargs: Any,
323) -> argparse.Namespace:
324 """
325 Construct a VUnit test filter that will run all test cases that are affected by git changes.
326 The current git state is compared to a reference branch, and differences are derived.
327 See :class:`.GitSimulationSubset` for details.
329 Arguments:
330 args: Command line argument namespace.
331 repo_root: Path to the repository root.
332 Git commands will be run here.
333 modules: Will be passed on to :meth:`.SimulationProject.add_modules`.
334 modules_no_test: Will be passed on to :meth:`.SimulationProject.add_modules`.
335 reference_branch: The name of the reference branch that is used to collect a diff.
336 setup_vunit_kwargs : Will be passed on to :meth:`.SimulationProject.add_modules`.
338 Return:
339 An updated argument namespace from which a VUnit project can be created.
340 """
341 if args.test_patterns != "*":
342 raise ValueError(
343 "Can not specify a test pattern when using the --vcs-minimal flag."
344 f" Got {args.test_patterns}",
345 )
347 # Set up a dummy VUnit project that will be used for dependency scanning.
348 # We could use the "real" simulation project, which the user has no doubt created, but
349 # in the VUnit project there are two issues:
350 # 1. It is impossible to change the test filter after the project has been created.
351 # 2. We would have to access the _minimal private member.
352 # Hence we create a new project here.
353 # We add the 'modules_no_test' as well as simlib, not because we need them but to avoid
354 # excessive terminal printouts about missing files in dependency scanning.
355 simulation_project = SimulationProject(args=args)
356 simulation_project.add_modules(
357 args=args,
358 modules=modules,
359 modules_no_test=modules_no_test,
360 include_verilog_files=False,
361 include_systemverilog_files=False,
362 **setup_vunit_kwargs,
363 )
364 simulation_project.add_vivado_simlib()
366 testbenches_to_run = GitSimulationSubset(
367 repo_root=repo_root,
368 reference_branch=reference_branch,
369 vunit_proj=simulation_project.vunit_proj,
370 modules=modules,
371 ).find_subset()
373 if not testbenches_to_run:
374 raise NoVcsDiffTestsFound()
376 # Override the test pattern argument to VUnit.
377 args.test_patterns = []
378 for testbench_file_name, library_name in testbenches_to_run:
379 args.test_patterns.append(f"{library_name}.{testbench_file_name}.*")
381 print(f"Running VUnit with test pattern {args.test_patterns}")
383 # Enable minimal compilation in VUnit to save time.
384 args.minimal = True
386 return args
389def create_vhdl_ls_configuration(
390 output_path: Path,
391 modules: ModuleList,
392 vunit_proj: VUnit,
393 ip_core_vivado_project_directory: Optional[Path] = None,
394) -> None:
395 """
396 Create a configuration file (``vhdl_ls.toml``) for the rust_hdl VHDL Language Server
397 (https://github.com/VHDL-LS/rust_hdl).
399 The call is very quick (10-15 ms).
400 Running it from ``simulate.py``, a script that is run "often", might be a good idea
401 in order to always have an up-to-date vhdl_ls config.
403 Arguments:
404 output_path: Config file will be placed in this directory.
405 modules: All files from these modules will be added.
406 vunit_proj: All files in this VUnit project will be added.
407 This includes the files from VUnit itself, and any user files.
409 .. warning::
410 Using a VUnit project with location/check preprocessing enabled might be
411 dangerous, since it introduces the risk of editing a generated file.
412 ip_core_vivado_project_directory: Vivado IP core files in this location will be added.
413 """
414 # Add some files needed when doing hdl-registers development.
415 hdl_register_repo_root = Path(hdl_registers.__file__).parent.parent
416 additional_files = []
417 for path in [
418 hdl_register_repo_root / "tests" / "functional" / "simulation",
419 hdl_register_repo_root / "generated" / "vunit_out" / "generated_register",
420 hdl_register_repo_root / "doc" / "sphinx" / "rst" / "generator" / "sim",
421 ]:
422 # Add only if they exist. To avoid vhdl_ls warning about missing files.
423 if path.exists():
424 additional_files.append((path / "*.vhd", "example"))
426 try:
427 vivado_location = get_vivado_path()
428 except FileNotFoundError:
429 vivado_location = None
431 tsfpga.create_vhdl_ls_config.create_configuration(
432 output_path=output_path,
433 modules=modules,
434 vunit_proj=vunit_proj,
435 files=additional_files,
436 vivado_location=vivado_location,
437 ip_core_vivado_project_directory=ip_core_vivado_project_directory,
438 )