Coverage for tsfpga/vivado/project.py: 89%
209 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 shutil
11from copy import deepcopy
12from pathlib import Path
13from typing import TYPE_CHECKING, Any, Optional, Union
15# First party libraries
16from tsfpga import TSFPGA_TCL
17from tsfpga.build_step_tcl_hook import BuildStepTclHook
18from tsfpga.constraint import Constraint
19from tsfpga.system_utils import create_file, read_file
21# Local folder libraries
22from .build_result import BuildResult
23from .common import run_vivado_gui, run_vivado_tcl
24from .hierarchical_utilization_parser import HierarchicalUtilizationParser
25from .logic_level_distribution_parser import LogicLevelDistributionParser
26from .tcl import VivadoTcl
28if TYPE_CHECKING:
29 # First party libraries
30 from tsfpga.module_list import ModuleList
32 # Local folder libraries
33 from .build_result_checker import MaximumLogicLevel, SizeChecker
36class VivadoProject:
37 """
38 Used for handling a Xilinx Vivado HDL project
39 """
41 # pylint: disable=too-many-arguments,too-many-instance-attributes
42 def __init__(
43 self,
44 name: str,
45 modules: "ModuleList",
46 part: str,
47 top: Optional[str] = None,
48 generics: Optional[dict[str, Any]] = None,
49 constraints: Optional[list["Constraint"]] = None,
50 tcl_sources: Optional[list[Path]] = None,
51 build_step_hooks: Optional[list["BuildStepTclHook"]] = None,
52 vivado_path: Optional[Path] = None,
53 default_run_index: int = 1,
54 impl_explore: bool = False,
55 defined_at: Optional[Path] = None,
56 **other_arguments: Any,
57 ): # pylint: disable=too-many-locals
58 """
59 Class constructor. Performs a shallow copy of the mutable arguments, so that the user
60 can e.g. append items to their list after creating an object.
62 Arguments:
63 name: Project name.
64 modules: Modules that shall be included in the project.
65 part: Part identification.
66 top: Name of top level entity.
67 If left out, the top level name will be inferred from the ``name``.
68 generics: A dict with generics values (name: value). Use this parameter
69 for "static" generics that do not change between multiple builds of this
70 project. These will be set in the project when it is created.
72 Compare to the build-time generic argument in :meth:`build`.
74 The generic value shall be of type
76 * :class:`bool` (suitable for VHDL type ``boolean`` and ``std_logic``),
77 * :class:`int` (suitable for VHDL type ``integer``, ``natural``, etc.),
78 * :class:`float` (suitable for VHDL type ``real``),
79 * :class:`.BitVectorGenericValue` (suitable for VHDL type ``std_logic_vector``,
80 ``unsigned``, etc.), or
81 * :class:`.StringGenericValue` (suitable for VHDL type ``string``).
82 constraints: Constraints that will be applied to the project.
83 tcl_sources: A list of TCL files. Use for e.g. block design, pinning, settings, etc.
84 build_step_hooks: Build step hooks that will be applied to the project.
85 vivado_path: A path to the Vivado executable.
86 If omitted, the default location from the system PATH will be used.
87 default_run_index: Default run index (synth_X and impl_X) that is set in the
88 project.
89 Can also use the argument to :meth:`build() <VivadoProject.build>` to
90 specify at build-time.
91 defined_at: Optional path to the file where you defined this project.
92 To get a useful ``build_fpga.py --list`` message. Is useful when you have many
93 projects set up.
94 other_arguments: Optional further arguments. Will not be used by tsfpga, but will
95 instead be passed on to
97 * :func:`BaseModule.get_synthesis_files()
98 <tsfpga.module.BaseModule.get_synthesis_files>`
99 * :func:`BaseModule.get_ip_core_files()
100 <tsfpga.module.BaseModule.get_ip_core_files>`
101 * :func:`BaseModule.get_scoped_constraints()
102 <tsfpga.module.BaseModule.get_scoped_constraints>`
103 * :func:`VivadoProject.pre_create`
104 * :func:`BaseModule.pre_build() <tsfpga.module.BaseModule.pre_build>`
105 * :func:`VivadoProject.pre_build`
106 * :func:`VivadoProject.post_build`
108 along with further arguments supplied at build-time to :meth:`.create` and
109 :meth:`.build`.
111 .. note::
112 This is a "kwargs" style argument. You can pass any number of named arguments.
113 """
114 self.name = name
115 self.modules = modules.copy()
116 self.part = part
117 self.static_generics = {} if generics is None else generics.copy()
118 self.constraints = [] if constraints is None else constraints.copy()
119 self.tcl_sources = [] if tcl_sources is None else tcl_sources.copy()
120 self.build_step_hooks = [] if build_step_hooks is None else build_step_hooks.copy()
121 self._vivado_path = vivado_path
122 self.default_run_index = default_run_index
123 self.impl_explore = impl_explore
124 self.defined_at = defined_at
125 self.other_arguments = None if other_arguments is None else other_arguments.copy()
127 # Will be set by subclass when applicable
128 self.is_netlist_build = False
129 self.analyze_synthesis_timing = True
130 self.report_logic_level_distribution = False
131 self.ip_cores_only = False
133 self.top = name + "_top" if top is None else top
135 self.tcl = VivadoTcl(name=self.name)
137 for constraint in self.constraints:
138 if not isinstance(constraint, Constraint):
139 raise TypeError(f'Got bad type for "constraints" element: {constraint}')
141 for tcl_source in self.tcl_sources:
142 if not isinstance(tcl_source, Path):
143 raise TypeError(f'Got bad type for "tcl_sources" element: {tcl_source}')
145 for build_step_hook in self.build_step_hooks:
146 if not isinstance(build_step_hook, BuildStepTclHook):
147 raise TypeError(f'Got bad type for "build_step_hooks" element: {build_step_hook}')
149 def project_file(self, project_path: Path) -> Path:
150 """
151 Arguments:
152 project_path: A path containing a Vivado project.
154 Return:
155 The project file of this project, in the given folder
156 """
157 return project_path / (self.name + ".xpr")
159 def _setup_tcl_sources(self) -> None:
160 tsfpga_tcl_sources = [
161 TSFPGA_TCL / "vivado_default_run.tcl",
162 TSFPGA_TCL / "vivado_fast_run.tcl",
163 TSFPGA_TCL / "vivado_messages.tcl",
164 ]
166 if self.impl_explore:
167 tsfpga_tcl_sources.append(TSFPGA_TCL / "vivado_strategies.tcl")
169 # Add tsfpga TCL sources first. The user might want to change something in the tsfpga
170 # settings. Conversely, tsfpga should not modify something that the user has set up.
171 self.tcl_sources = tsfpga_tcl_sources + self.tcl_sources
173 def _setup_build_step_hooks(self) -> None:
174 # Check that no ERROR messages have been sent by Vivado. After synthesis as well as
175 # after implementation.
176 self.build_step_hooks.append(
177 BuildStepTclHook(
178 TSFPGA_TCL / "check_no_error_messages.tcl", "STEPS.SYNTH_DESIGN.TCL.POST"
179 )
180 )
181 self.build_step_hooks.append(
182 BuildStepTclHook(
183 TSFPGA_TCL / "check_no_error_messages.tcl", "STEPS.WRITE_BITSTREAM.TCL.PRE"
184 )
185 )
187 # Check the implemented timing and resource utilization via TCL build hooks.
188 # This is different than for synthesis, where it is embedded in the build script.
189 # This is due to Vivado limitations related to post-synthesis hooks.
190 # Specifically, the report_utilization figures do not include IP cores when it is run in
191 # a post-synthesis hook.
192 self.build_step_hooks.append(
193 BuildStepTclHook(TSFPGA_TCL / "report_utilization.tcl", "STEPS.WRITE_BITSTREAM.TCL.PRE")
194 )
195 self.build_step_hooks.append(
196 BuildStepTclHook(TSFPGA_TCL / "check_timing.tcl", "STEPS.WRITE_BITSTREAM.TCL.PRE")
197 )
198 self.build_step_hooks.append(
199 BuildStepTclHook(TSFPGA_TCL / "check_cdc.tcl", "STEPS.WRITE_BITSTREAM.TCL.PRE")
200 )
202 if not self.analyze_synthesis_timing:
203 # In this special case however, the synthesized design is never opened (to save
204 # execution time), meaning 'report_utilization' is not run by the
205 # 'build_vivado_project.tcl' script.
206 # So in order to get a utilization report anyway we add it as a hook.
207 # This mode is exclusively used by netlist builds, which very rarely include IP cores,
208 # so it is acceptable that the utilization report might be erroneous with regards to
209 # IP cores.
210 self.build_step_hooks.append(
211 BuildStepTclHook(
212 TSFPGA_TCL / "report_utilization.tcl", "STEPS.SYNTH_DESIGN.TCL.POST"
213 )
214 )
216 if self.report_logic_level_distribution:
217 # Used by netlist builds
218 self.build_step_hooks.append(
219 BuildStepTclHook(
220 TSFPGA_TCL / "report_logic_level_distribution.tcl",
221 "STEPS.SYNTH_DESIGN.TCL.POST",
222 )
223 )
225 def _create_tcl(
226 self, project_path: Path, ip_cache_path: Optional[Path], all_arguments: dict[str, Any]
227 ) -> Path:
228 """
229 Make a TCL file that creates a Vivado project
230 """
231 if project_path.exists():
232 raise ValueError(f"Folder already exists: {project_path}")
233 project_path.mkdir(parents=True)
235 create_vivado_project_tcl = project_path / "create_vivado_project.tcl"
236 tcl = self.tcl.create(
237 project_folder=project_path,
238 modules=self.modules,
239 part=self.part,
240 top=self.top,
241 run_index=self.default_run_index,
242 generics=self.static_generics,
243 constraints=self.constraints,
244 tcl_sources=self.tcl_sources,
245 build_step_hooks=self.build_step_hooks,
246 ip_cache_path=ip_cache_path,
247 disable_io_buffers=self.is_netlist_build,
248 ip_cores_only=self.ip_cores_only,
249 other_arguments=all_arguments,
250 )
251 create_file(create_vivado_project_tcl, tcl)
253 return create_vivado_project_tcl
255 def create(
256 self,
257 project_path: Path,
258 ip_cache_path: Optional[Path] = None,
259 **other_arguments: Any,
260 ) -> bool:
261 """
262 Create a Vivado project
264 Arguments:
265 project_path: Path where the project shall be placed.
266 ip_cache_path: Path to a folder where the Vivado IP cache can be
267 placed. If omitted, the Vivado IP cache mechanism will not be enabled.
268 other_arguments: Optional further arguments. Will not be used by tsfpga, but will
269 instead be sent to
271 * :func:`BaseModule.get_synthesis_files()
272 <tsfpga.module.BaseModule.get_synthesis_files>`
273 * :func:`BaseModule.get_ip_core_files()
274 <tsfpga.module.BaseModule.get_ip_core_files>`
275 * :func:`BaseModule.get_scoped_constraints()
276 <tsfpga.module.BaseModule.get_scoped_constraints>`
277 * :func:`VivadoProject.pre_create`
279 along with further ``other_arguments`` supplied to :meth:`.__init__`.
281 .. note::
282 This is a "kwargs" style argument. You can pass any number of named arguments.
283 Return:
284 True if everything went well.
285 """
286 print(f"Creating Vivado project in {project_path}")
287 self._setup_tcl_sources()
288 self._setup_build_step_hooks()
290 # The pre-create hook might have side effects. E.g. change some register constants.
291 # So we make a deep copy of the module list before the hook is called.
292 # Note that the modules are copied before the pre-build hooks as well,
293 # since we do not know if we might be performing a create-only or
294 # build-only operation. The copy does not take any significant time, so this is not
295 # an issue.
296 self.modules = deepcopy(self.modules)
298 # Send all available arguments that are reasonable to use in pre-create and module getter
299 # functions. Prefer run-time values over the static.
300 all_arguments = copy_and_combine_dicts(self.other_arguments, other_arguments)
301 all_arguments.update(
302 generics=self.static_generics,
303 part=self.part,
304 )
306 if not self.pre_create(
307 project_path=project_path, ip_cache_path=ip_cache_path, **all_arguments
308 ):
309 print("ERROR: Project pre-create hook returned False. Failing the build.")
310 return False
312 create_vivado_project_tcl = self._create_tcl(
313 project_path=project_path, ip_cache_path=ip_cache_path, all_arguments=all_arguments
314 )
315 return run_vivado_tcl(self._vivado_path, create_vivado_project_tcl)
317 def pre_create(self, **kwargs: Any) -> bool: # pylint: disable=unused-argument
318 """
319 Override this function in a subclass if you wish to do something useful with it.
320 Will be called from :meth:`.create` right before the call to Vivado.
322 An example use case for this function is when TCL source scripts for the Vivado project
323 have to be auto generated. This could e.g. be scripts that set IP repo paths based on the
324 Vivado system PATH.
326 .. Note::
327 This default method does nothing. Shall be overridden by project that utilize
328 this mechanism.
330 Arguments:
331 kwargs: Will have all the :meth:`.create` parameters in it, as well as everything in
332 the ``other_arguments`` argument to :func:`VivadoProject.__init__`.
334 Return:
335 True if everything went well.
336 """
337 return True
339 def _build_tcl(
340 self,
341 project_path: Path,
342 output_path: Path,
343 num_threads: int,
344 run_index: int,
345 all_generics: dict[str, Any],
346 synth_only: bool,
347 from_impl: bool,
348 impl_explore: bool,
349 ) -> Path:
350 """
351 Make a TCL file that builds a Vivado project
352 """
353 project_file = self.project_file(project_path)
354 if not project_file.exists():
355 raise ValueError(
356 f"Project file does not exist in the specified location: {project_file}"
357 )
359 build_vivado_project_tcl = project_path / "build_vivado_project.tcl"
360 tcl = self.tcl.build(
361 project_file=project_file,
362 output_path=output_path,
363 num_threads=num_threads,
364 run_index=run_index,
365 generics=all_generics,
366 synth_only=synth_only,
367 from_impl=from_impl,
368 analyze_synthesis_timing=self.analyze_synthesis_timing,
369 impl_explore=impl_explore,
370 )
371 create_file(build_vivado_project_tcl, tcl)
373 return build_vivado_project_tcl
375 def pre_build(self, **kwargs: Any) -> bool: # pylint: disable=unused-argument
376 """
377 Override this function in a subclass if you wish to do something useful with it.
378 Will be called from :meth:`.build` right before the call to Vivado.
380 Arguments:
381 kwargs: Will have all the :meth:`.build` parameters in it. Including additional
382 parameters from the user.
384 Return:
385 True if everything went well.
386 """
387 return True
389 def post_build(self, **kwargs: Any) -> bool: # pylint: disable=unused-argument
390 """
391 Override this function in a subclass if you wish to do something useful with it.
392 Will be called from :meth:`.build` right after the call to Vivado.
394 An example use case for this function is to encrypt the bit file, or generate any other
395 material that shall be included in FPGA release artifacts.
397 .. Note::
398 This default method does nothing. Shall be overridden by project that utilize
399 this mechanism.
401 Arguments:
402 kwargs: Will have all the :meth:`.build` parameters in it. Including additional
403 parameters from the user. Will also include ``build_result`` with
404 implemented/synthesized size, which can be used for asserting the expected resource
405 utilization.
407 Return:
408 True if everything went well.
409 """
410 return True
412 def build( # pylint: disable=too-many-locals,too-many-branches
413 self,
414 project_path: Path,
415 output_path: Optional[Path] = None,
416 run_index: Optional[int] = None,
417 generics: Optional[dict[str, Any]] = None,
418 synth_only: bool = False,
419 from_impl: bool = False,
420 num_threads: int = 12,
421 **pre_and_post_build_parameters: Any,
422 ) -> BuildResult:
423 """
424 Build a Vivado project
426 Arguments:
427 project_path: A path containing a Vivado project.
428 output_path: Results (bit file, ...) will be placed here.
429 run_index: Select Vivado run (synth_X and impl_X) to build with.
430 generics: A dict with generics values (`dict(name: value)`). Use for run-time
431 generics, i.e. values that can change between each build of this project.
433 Compare to the create-time generics argument in :meth:`.__init__`.
435 The generic value types follow the same rules as for :meth:`.__init__`.
436 synth_only: Run synthesis and then stop.
437 from_impl: Run the ``impl`` steps and onward on an existing synthesized design.
438 num_threads: Number of parallel threads to use during run.
439 pre_and_post_build_parameters: Optional further arguments. Will not be used by tsfpga,
440 but will instead be sent to
442 * :func:`BaseModule.pre_build() <tsfpga.module.BaseModule.pre_build>`
443 * :func:`VivadoProject.pre_build`
444 * :func:`VivadoProject.post_build`
446 along with further ``other_arguments`` supplied to :meth:`.__init__`.
448 .. note::
449 This is a "kwargs" style argument. You can pass any number of named arguments.
451 Return:
452 Result object with build information.
453 """
454 synth_only = synth_only or self.is_netlist_build
456 if output_path is None and not synth_only:
457 raise ValueError("Must specify output_path when doing an implementation run")
459 if synth_only:
460 print(f"Synthesizing Vivado project in {project_path}")
461 else:
462 print(f"Building Vivado project in {project_path}, placing artifacts in {output_path}")
464 # Combine to all available generics. Prefer run-time values over static.
465 all_generics = copy_and_combine_dicts(self.static_generics, generics)
467 # Run index is optional to specify at build-time
468 run_index = self.default_run_index if run_index is None else run_index
470 # Send all available information to pre- and post build functions. Prefer build-time values
471 # over the static arguments.
472 all_parameters = copy_and_combine_dicts(self.other_arguments, pre_and_post_build_parameters)
473 all_parameters.update(
474 project_path=project_path,
475 output_path=output_path,
476 run_index=run_index,
477 generics=all_generics,
478 synth_only=synth_only,
479 from_impl=from_impl,
480 num_threads=num_threads,
481 )
483 # The pre-build hooks (either project pre-build hook or any of the module's pre-build hooks)
484 # might have side effects. E.g. change some register constants. So we make a deep copy of
485 # the module list before any of these hooks are called. Note that the modules are copied
486 # before the pre-create hook as well, since we do not know if we might be performing a
487 # create-only or build-only operation. The copy does not take any significant time, so this
488 # is not an issue.
489 self.modules = deepcopy(self.modules)
491 result = BuildResult(self.name)
493 for module in self.modules:
494 if not module.pre_build(project=self, **all_parameters):
495 print(
496 f"ERROR: Module {module.name} pre-build hook returned False. Failing the build."
497 )
498 result.success = False
499 return result
501 # Make sure register packages are up to date
502 module.create_register_synthesis_files()
504 if not self.pre_build(**all_parameters):
505 print("ERROR: Project pre-build hook returned False. Failing the build.")
506 result.success = False
507 return result
509 # We ignore the type of 'output_path' going from 'Path | None' to 'Path'.
510 # It is only used if 'synth_only' is False, and we have an assertion that 'output_path' is
511 # not None in that case above.
513 build_vivado_project_tcl = self._build_tcl(
514 project_path=project_path,
515 output_path=output_path, # type: ignore[arg-type]
516 num_threads=num_threads,
517 run_index=run_index,
518 all_generics=all_generics,
519 synth_only=synth_only,
520 from_impl=from_impl,
521 impl_explore=self.impl_explore,
522 )
524 if not run_vivado_tcl(self._vivado_path, build_vivado_project_tcl):
525 result.success = False
526 return result
528 result.synthesis_size = self._get_size(project_path, f"synth_{run_index}")
529 if self.report_logic_level_distribution:
530 result.logic_level_distribution = self._get_logic_level_distribution(
531 project_path, f"synth_{run_index}"
532 )
534 if not synth_only:
535 if self.impl_explore:
536 runs_path = project_path / f"{self.name}.runs"
537 for run in runs_path.iterdir():
538 if "impl_explore_" in run.resolve().name:
539 # Check files for existence, since not all runs may have completed
540 bit_file = run / f"{self.top}.bit"
541 bin_file = run / f"{self.top}.bin"
542 if bit_file.exists() or bin_file.exists():
543 impl_folder = run
544 run_name = run.resolve().name
545 break
546 else:
547 run_name = f"impl_{run_index}"
548 impl_folder = project_path / f"{self.name}.runs" / run_name
549 bit_file = impl_folder / f"{self.top}.bit"
550 bin_file = impl_folder / f"{self.top}.bin"
552 shutil.copy2(bit_file, output_path / f"{self.name}.bit") # type: ignore[operator]
553 shutil.copy2(bin_file, output_path / f"{self.name}.bin") # type: ignore[operator]
554 result.implementation_size = self._get_size(project_path, run_name)
556 # Send the result object, along with everything else, to the post-build function
557 all_parameters.update(build_result=result)
559 if not self.post_build(**all_parameters):
560 print("ERROR: Project post-build hook returned False. Failing the build.")
561 result.success = False
563 return result
565 def open(self, project_path: Path) -> bool:
566 """
567 Open the project in Vivado GUI.
569 Arguments:
570 project_path: A path containing a Vivado project.
572 Return:
573 True if everything went well.
574 """
575 return run_vivado_gui(self._vivado_path, self.project_file(project_path))
577 def _get_size(self, project_path: Path, run: str) -> dict[str, int]:
578 """
579 Reads the hierarchical utilization report and returns the top level size
580 for the specified run.
581 """
582 report_as_string = read_file(
583 project_path / f"{self.name}.runs" / run / "hierarchical_utilization.rpt"
584 )
585 return HierarchicalUtilizationParser.get_size(report_as_string)
587 def _get_logic_level_distribution(self, project_path: Path, run: str) -> str:
588 """
589 Reads the hierarchical utilization report and returns the top level size
590 for the specified run.
591 """
592 report_as_string = read_file(
593 project_path / f"{self.name}.runs" / run / "logical_level_distribution.rpt"
594 )
595 return LogicLevelDistributionParser.get_table(report_as_string)
597 def __str__(self) -> str:
598 result = f"{self.name}\n"
600 if self.defined_at is not None:
601 result += f"Defined at: {self.defined_at.resolve()}\n"
603 result += f"Type: {self.__class__.__name__}\n"
604 result += f"Top level: {self.top}\n"
606 if self.static_generics:
607 generics = self._dict_to_string(self.static_generics)
608 else:
609 generics = "-"
610 result += f"Generics: {generics}\n"
612 if self.other_arguments:
613 result += f"Arguments: {self._dict_to_string(self.other_arguments)}\n"
615 return result
617 @staticmethod
618 def _dict_to_string(data: dict[str, Any]) -> str:
619 return ", ".join([f"{name}={value}" for name, value in data.items()])
622class VivadoNetlistProject(VivadoProject):
623 """
624 Used for handling Vivado build of a module without top level pinning.
625 """
627 def __init__(
628 self,
629 analyze_synthesis_timing: bool = False,
630 build_result_checkers: Optional[list[Union["SizeChecker", "MaximumLogicLevel"]]] = None,
631 **kwargs: Any,
632 ) -> None:
633 """
634 Arguments:
635 analyze_synthesis_timing: Enable analysis of the synthesized design's timing.
636 This will make the build flow open the design, and check for unhandled clock
637 crossings and pulse width violations.
638 Enabling it will add significant build time (can be as much as +40%).
639 Also, in order for clock crossing check to work, the clocks have to be created
640 using a constraint file.
641 build_result_checkers:
642 Checkers that will be executed after a successful build. Is used to automatically
643 check that e.g. resource utilization is not greater than expected.
644 kwargs: Further arguments accepted by :meth:`.VivadoProject.__init__`.
645 """
646 super().__init__(**kwargs)
648 self.is_netlist_build = True
649 self.analyze_synthesis_timing = analyze_synthesis_timing
650 self.report_logic_level_distribution = True
651 self.build_result_checkers = [] if build_result_checkers is None else build_result_checkers
653 def build( # type: ignore # pylint: disable=arguments-differ
654 self, **kwargs: Any
655 ) -> BuildResult:
656 """
657 Build the project.
659 Arguments:
660 kwargs: All arguments as accepted by :meth:`.VivadoProject.build`.
661 """
662 result = super().build(**kwargs)
663 result.success = result.success and self._check_size(result)
665 return result
667 def _check_size(self, build_result: BuildResult) -> bool:
668 if not build_result.success:
669 print(f"Can not do post_build check for {self.name} since it did not succeed.")
670 return False
672 success = True
673 for build_result_checker in self.build_result_checkers:
674 checker_result = build_result_checker.check(build_result)
675 success = success and checker_result
677 return success
680class VivadoIpCoreProject(VivadoProject):
681 """
682 A Vivado project that is only used to generate simulation models of IP cores.
683 """
685 ip_cores_only = True
687 def __init__(self, **kwargs: Any) -> None:
688 """
689 Arguments:
690 kwargs: Arguments as accepted by :meth:`.VivadoProject.__init__`.
691 """
692 super().__init__(**kwargs)
694 def build(self, **kwargs: Any): # type: ignore # pylint: disable=arguments-differ
695 """
696 Not implemented.
697 """
698 raise NotImplementedError("IP core project can not be built")
701def copy_and_combine_dicts(
702 dict_first: Optional[dict[str, Any]], dict_second: Optional[dict[str, Any]]
703) -> dict[str, Any]:
704 """
705 Will prefer values in the second dict, in case the same key occurs in both.
706 Will return an empty dictionary if both are ``None``.
707 """
708 if dict_first is None:
709 if dict_second is None:
710 return dict()
712 return dict_second.copy()
714 if dict_second is None:
715 return dict_first.copy()
717 result = dict_first.copy()
718 result.update(dict_second)
720 return result