Coverage for tsfpga/examples/simulation_utils.py: 0%
98 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-08-29 20:51 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-08-29 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# --------------------------------------------------------------------------------------------------
9from __future__ import annotations
11import argparse
12from pathlib import Path
13from typing import TYPE_CHECKING, Any
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
20import tsfpga
21import tsfpga.create_vhdl_ls_config
22from tsfpga.git_simulation_subset import GitSimulationSubset
23from tsfpga.module_list import ModuleList
24from tsfpga.vivado.common import get_vivado_path
25from tsfpga.vivado.ip_cores import VivadoIpCores
26from tsfpga.vivado.simlib import VivadoSimlib
28if TYPE_CHECKING:
29 from tsfpga.vivado.project import VivadoIpCoreProject
30 from tsfpga.vivado.simlib_common import VivadoSimlibCommon
33def get_arguments_cli(default_output_path: Path) -> VUnitCLI:
34 """
35 Get arguments for the simulation flow.
37 Arguments:
38 default_output_path: Will be set as default for output path arguments
39 (both VUnit files and Vivado files).
40 """
41 cli = VUnitCLI()
43 # Print default values when doing --help
44 cli.parser.formatter_class = argparse.ArgumentDefaultsHelpFormatter
46 # Set the supplied default value for VUnit output path.
47 for action in cli.parser._actions: # noqa: SLF001
48 if action.dest == "output_path":
49 action.default = default_output_path / "vunit_out"
50 break
51 else:
52 raise AssertionError("VUnit --output-path argument not found")
54 cli.parser.add_argument(
55 "--output-path-vivado",
56 type=Path,
57 default=default_output_path,
58 help=(
59 "where to place Vivado IP core and simlib files. "
60 "Note that --output-path is for VUnit files"
61 ),
62 )
64 cli.parser.add_argument(
65 "--vivado-skip", action="store_true", help="skip all steps that require Vivado"
66 )
68 cli.parser.add_argument(
69 "--ip-compile", action="store_true", help="force (re)compile of IP cores"
70 )
72 cli.parser.add_argument(
73 "--simlib-compile", action="store_true", help="force (re)compile of Vivado simlib"
74 )
76 cli.parser.add_argument(
77 "--vcs-minimal",
78 action="store_true",
79 help="run a minimal set of tests based on version control system (e.g. git) history",
80 )
82 cli.parser.add_argument(
83 "--inspect",
84 action="store_true",
85 help="optionally inspect some simulation result. Is only available for some modules",
86 )
88 return cli
91class SimulationProject:
92 """
93 Class for setting up and handling a VUnit simulation project. Should be reusable in most cases.
94 """
96 def __init__(self, args: argparse.Namespace, enable_preprocessing: bool = False) -> None:
97 """
98 Create a VUnit project, configured according to the given arguments.
100 Arguments:
101 args: Command line argument namespace from ``simulate.py``.
102 enable_preprocessing: If ``True``, VUnit location/check preprocessing will be enabled.
103 """
104 self.args = args
106 self.vunit_proj = VUnit.from_args(args=args)
107 self.vunit_proj.add_vhdl_builtins()
108 self.vunit_proj.add_verification_components()
109 self.vunit_proj.add_random()
111 if enable_preprocessing:
112 self.vunit_proj.enable_location_preprocessing()
113 self.vunit_proj.enable_check_preprocessing()
115 self.has_commercial_simulator = self.vunit_proj.get_simulator_name() not in ["ghdl", "nvc"]
117 def add_modules(
118 self,
119 modules: ModuleList,
120 modules_no_test: ModuleList | None = None,
121 include_vhdl_files: bool = True,
122 include_verilog_files: bool = True,
123 include_systemverilog_files: bool = True,
124 **setup_vunit_kwargs: Any, # noqa: ANN401
125 ) -> None:
126 """
127 Add module source files to the VUnit project.
129 Arguments:
130 modules: These modules will be included in the simulation project.
131 modules_no_test: Source and simulation files from these modules will be included in the
132 simulation project, but no testbenches.
133 Provide your dependency modules here, if you have any.
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_test = ModuleList() if modules_no_test is None else modules_no_test
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_test:
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_test
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) -> VivadoSimlibCommon | None:
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: type[VivadoIpCoreProject] | None = None,
225 ) -> Path | None:
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: type[VivadoIpCoreProject] | None = 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
307def set_git_test_pattern(
308 args: argparse.Namespace,
309 repo_root: Path,
310 vunit_proj: VUnit,
311 modules: ModuleList,
312 reference_branch: str = "origin/main",
313) -> bool:
314 """
315 Update the VUnit project's test pattern to run everything that has a difference in the local git
316 tree compared to a reference branch.
318 Arguments:
319 args: Command line argument namespace.
320 repo_root: Path to the repository root.
321 Git commands will be run here.
322 vunit_proj: The test pattern of this VUnit project will be updated.
323 modules: Will look for changes in these modules' register data files also.
324 reference_branch: Changes in the local tree compared to this branch will be simulated.
326 Return:
327 True if any HDL-related changes were found in git.
328 """
329 if args.test_patterns != "*":
330 raise ValueError(
331 "Can not specify a test pattern when using the --vcs-minimal flag."
332 f" Got {args.test_patterns}",
333 )
335 hdl_file_diff = GitSimulationSubset(
336 repo_root=repo_root, reference_branch=reference_branch, modules=modules
337 ).update_test_pattern(vunit_proj=vunit_proj)
339 if not hdl_file_diff:
340 print("Nothing to run. Appears to be no HDL-related git diff.")
341 return False
343 # Enable minimal compilation in VUnit to save time.
344 vunit_proj._args.minimal = True # noqa: SLF001
346 return True
349def create_vhdl_ls_configuration(
350 output_path: Path,
351 modules: ModuleList,
352 vunit_proj: VUnit,
353 ip_core_vivado_project_directory: Path | None = None,
354) -> None:
355 """
356 Create a configuration file (``vhdl_ls.toml``) for the rust_hdl VHDL Language Server
357 (https://github.com/VHDL-LS/rust_hdl).
359 The call is very quick (10-15 ms).
360 Running it from ``simulate.py``, a script that is run "often", might be a good idea
361 in order to always have an up-to-date vhdl_ls config.
363 Arguments:
364 output_path: Config file will be placed in this directory.
365 modules: All files from these modules will be added.
366 vunit_proj: All files in this VUnit project will be added.
367 This includes the files from VUnit itself, and any user files.
369 .. warning::
370 Using a VUnit project with location/check preprocessing enabled might be
371 dangerous, since it introduces the risk of editing a generated file.
372 ip_core_vivado_project_directory: Vivado IP core files in this location will be added.
373 """
374 # Add some files needed when doing hdl-registers development.
375 # But only if they currently exist in the file system, to avoid vhdl_ls warning about
376 # missing files.
377 hdl_register_repo_root = Path(hdl_registers.__file__).parent.parent
378 additional_files = [
379 (path / "*.vhd", "example")
380 for path in [
381 hdl_register_repo_root / "tests" / "functional" / "simulation" / "vhdl",
382 hdl_register_repo_root / "generated" / "vunit_out" / "generated_register",
383 hdl_register_repo_root / "doc" / "sphinx" / "rst" / "generator" / "example_counter",
384 ]
385 if path.exists()
386 ]
388 try:
389 vivado_location = get_vivado_path()
390 except FileNotFoundError:
391 vivado_location = None
393 tsfpga.create_vhdl_ls_config.create_configuration(
394 output_path=output_path,
395 modules=modules,
396 vunit_proj=vunit_proj,
397 files=additional_files,
398 vivado_location=vivado_location,
399 ip_core_vivado_project_directory=ip_core_vivado_project_directory,
400 )