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