Coverage for tsfpga/vivado/project.py: 89%
208 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-10 20:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-10 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 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.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 )
199 if not self.analyze_synthesis_timing:
200 # In this special case however, the synthesized design is never opened, and
201 # report_utilization is not run by the build_vivado_project.tcl.
202 # So in order to get a utilization report anyway we add it as a hook.
203 # This mode is exclusively used by netlist builds, which very rarely include IP cores,
204 # so it is acceptable that the utilization report might be erroneous with regards to
205 # IP cores.
206 self.build_step_hooks.append(
207 BuildStepTclHook(
208 TSFPGA_TCL / "report_utilization.tcl", "STEPS.SYNTH_DESIGN.TCL.POST"
209 )
210 )
212 if self.report_logic_level_distribution:
213 # Used by netlist builds
214 self.build_step_hooks.append(
215 BuildStepTclHook(
216 TSFPGA_TCL / "report_logic_level_distribution.tcl",
217 "STEPS.SYNTH_DESIGN.TCL.POST",
218 )
219 )
221 def _create_tcl(
222 self, project_path: Path, ip_cache_path: Optional[Path], all_arguments: dict[str, Any]
223 ) -> Path:
224 """
225 Make a TCL file that creates a Vivado project
226 """
227 if project_path.exists():
228 raise ValueError(f"Folder already exists: {project_path}")
229 project_path.mkdir(parents=True)
231 create_vivado_project_tcl = project_path / "create_vivado_project.tcl"
232 tcl = self.tcl.create(
233 project_folder=project_path,
234 modules=self.modules,
235 part=self.part,
236 top=self.top,
237 run_index=self.default_run_index,
238 generics=self.static_generics,
239 constraints=self.constraints,
240 tcl_sources=self.tcl_sources,
241 build_step_hooks=self.build_step_hooks,
242 ip_cache_path=ip_cache_path,
243 disable_io_buffers=self.is_netlist_build,
244 ip_cores_only=self.ip_cores_only,
245 other_arguments=all_arguments,
246 )
247 create_file(create_vivado_project_tcl, tcl)
249 return create_vivado_project_tcl
251 def create(
252 self,
253 project_path: Path,
254 ip_cache_path: Optional[Path] = None,
255 **other_arguments: Any,
256 ) -> bool:
257 """
258 Create a Vivado project
260 Arguments:
261 project_path: Path where the project shall be placed.
262 ip_cache_path: Path to a folder where the Vivado IP cache can be
263 placed. If omitted, the Vivado IP cache mechanism will not be enabled.
264 other_arguments: Optional further arguments. Will not be used by tsfpga, but will
265 instead be sent to
267 * :func:`BaseModule.get_synthesis_files()
268 <tsfpga.module.BaseModule.get_synthesis_files>`
269 * :func:`BaseModule.get_ip_core_files()
270 <tsfpga.module.BaseModule.get_ip_core_files>`
271 * :func:`BaseModule.get_scoped_constraints()
272 <tsfpga.module.BaseModule.get_scoped_constraints>`
273 * :func:`VivadoProject.pre_create`
275 along with further ``other_arguments`` supplied to :meth:`.__init__`.
277 .. note::
278 This is a "kwargs" style argument. You can pass any number of named arguments.
279 Return:
280 True if everything went well.
281 """
282 print(f"Creating Vivado project in {project_path}")
283 self._setup_tcl_sources()
284 self._setup_build_step_hooks()
286 # The pre-create hook might have side effects. E.g. change some register constants.
287 # So we make a deep copy of the module list before the hook is called.
288 # Note that the modules are copied before the pre-build hooks as well,
289 # since we do not know if we might be performing a create-only or
290 # build-only operation. The copy does not take any significant time, so this is not
291 # an issue.
292 self.modules = deepcopy(self.modules)
294 # Send all available arguments that are reasonable to use in pre-create and module getter
295 # functions. Prefer run-time values over the static.
296 all_arguments = copy_and_combine_dicts(self.other_arguments, other_arguments)
297 all_arguments.update(
298 generics=self.static_generics,
299 part=self.part,
300 )
302 if not self.pre_create(
303 project_path=project_path, ip_cache_path=ip_cache_path, **all_arguments
304 ):
305 print("ERROR: Project pre-create hook returned False. Failing the build.")
306 return False
308 create_vivado_project_tcl = self._create_tcl(
309 project_path=project_path, ip_cache_path=ip_cache_path, all_arguments=all_arguments
310 )
311 return run_vivado_tcl(self._vivado_path, create_vivado_project_tcl)
313 def pre_create(self, **kwargs: Any) -> bool: # pylint: disable=unused-argument
314 """
315 Override this function in a subclass if you wish to do something useful with it.
316 Will be called from :meth:`.create` right before the call to Vivado.
318 An example use case for this function is when TCL source scripts for the Vivado project
319 have to be auto generated. This could e.g. be scripts that set IP repo paths based on the
320 Vivado system PATH.
322 .. Note::
323 This default method does nothing. Shall be overridden by project that utilize
324 this mechanism.
326 Arguments:
327 kwargs: Will have all the :meth:`.create` parameters in it, as well as everything in
328 the ``other_arguments`` argument to :func:`VivadoProject.__init__`.
330 Return:
331 True if everything went well.
332 """
333 return True
335 def _build_tcl(
336 self,
337 project_path: Path,
338 output_path: Path,
339 num_threads: int,
340 run_index: int,
341 all_generics: dict[str, Any],
342 synth_only: bool,
343 from_impl: bool,
344 impl_explore: bool,
345 ) -> Path:
346 """
347 Make a TCL file that builds a Vivado project
348 """
349 project_file = self.project_file(project_path)
350 if not project_file.exists():
351 raise ValueError(
352 f"Project file does not exist in the specified location: {project_file}"
353 )
355 build_vivado_project_tcl = project_path / "build_vivado_project.tcl"
356 tcl = self.tcl.build(
357 project_file=project_file,
358 output_path=output_path,
359 num_threads=num_threads,
360 run_index=run_index,
361 generics=all_generics,
362 synth_only=synth_only,
363 from_impl=from_impl,
364 analyze_synthesis_timing=self.analyze_synthesis_timing,
365 impl_explore=impl_explore,
366 )
367 create_file(build_vivado_project_tcl, tcl)
369 return build_vivado_project_tcl
371 def pre_build(self, **kwargs: Any) -> bool: # pylint: disable=unused-argument
372 """
373 Override this function in a subclass if you wish to do something useful with it.
374 Will be called from :meth:`.build` right before the call to Vivado.
376 Arguments:
377 kwargs: Will have all the :meth:`.build` parameters in it. Including additional
378 parameters from the user.
380 Return:
381 True if everything went well.
382 """
383 return True
385 def post_build(self, **kwargs: Any) -> bool: # pylint: disable=unused-argument
386 """
387 Override this function in a subclass if you wish to do something useful with it.
388 Will be called from :meth:`.build` right after the call to Vivado.
390 An example use case for this function is to encrypt the bit file, or generate any other
391 material that shall be included in FPGA release artifacts.
393 .. Note::
394 This default method does nothing. Shall be overridden by project that utilize
395 this mechanism.
397 Arguments:
398 kwargs: Will have all the :meth:`.build` parameters in it. Including additional
399 parameters from the user. Will also include ``build_result`` with
400 implemented/synthesized size, which can be used for asserting the expected resource
401 utilization.
403 Return:
404 True if everything went well.
405 """
406 return True
408 def build( # pylint: disable=too-many-locals,too-many-branches
409 self,
410 project_path: Path,
411 output_path: Optional[Path] = None,
412 run_index: Optional[int] = None,
413 generics: Optional[dict[str, Any]] = None,
414 synth_only: bool = False,
415 from_impl: bool = False,
416 num_threads: int = 12,
417 **pre_and_post_build_parameters: Any,
418 ) -> BuildResult:
419 """
420 Build a Vivado project
422 Arguments:
423 project_path: A path containing a Vivado project.
424 output_path: Results (bit file, ...) will be placed here.
425 run_index: Select Vivado run (synth_X and impl_X) to build with.
426 generics: A dict with generics values (`dict(name: value)`). Use for run-time
427 generics, i.e. values that can change between each build of this project.
429 Compare to the create-time generics argument in :meth:`.__init__`.
431 The generic value types follow the same rules as for :meth:`.__init__`.
432 synth_only: Run synthesis and then stop.
433 from_impl: Run the ``impl`` steps and onward on an existing synthesized design.
434 num_threads: Number of parallel threads to use during run.
435 pre_and_post_build_parameters: Optional further arguments. Will not be used by tsfpga,
436 but will instead be sent to
438 * :func:`BaseModule.pre_build() <tsfpga.module.BaseModule.pre_build>`
439 * :func:`VivadoProject.pre_build`
440 * :func:`VivadoProject.post_build`
442 along with further ``other_arguments`` supplied to :meth:`.__init__`.
444 .. note::
445 This is a "kwargs" style argument. You can pass any number of named arguments.
447 Return:
448 Result object with build information.
449 """
450 synth_only = synth_only or self.is_netlist_build
452 if output_path is None and not synth_only:
453 raise ValueError("Must specify output_path when doing an implementation run")
455 if synth_only:
456 print(f"Synthesizing Vivado project in {project_path}")
457 else:
458 print(f"Building Vivado project in {project_path}, placing artifacts in {output_path}")
460 # Combine to all available generics. Prefer run-time values over static.
461 all_generics = copy_and_combine_dicts(self.static_generics, generics)
463 # Run index is optional to specify at build-time
464 run_index = self.default_run_index if run_index is None else run_index
466 # Send all available information to pre- and post build functions. Prefer build-time values
467 # over the static arguments.
468 all_parameters = copy_and_combine_dicts(self.other_arguments, pre_and_post_build_parameters)
469 all_parameters.update(
470 project_path=project_path,
471 output_path=output_path,
472 run_index=run_index,
473 generics=all_generics,
474 synth_only=synth_only,
475 from_impl=from_impl,
476 num_threads=num_threads,
477 )
479 # The pre-build hooks (either project pre-build hook or any of the module's pre-build hooks)
480 # might have side effects. E.g. change some register constants. So we make a deep copy of
481 # the module list before any of these hooks are called. Note that the modules are copied
482 # before the pre-create hook as well, since we do not know if we might be performing a
483 # create-only or build-only operation. The copy does not take any significant time, so this
484 # is not an issue.
485 self.modules = deepcopy(self.modules)
487 result = BuildResult(self.name)
489 for module in self.modules:
490 if not module.pre_build(project=self, **all_parameters):
491 print(
492 f"ERROR: Module {module.name} pre-build hook returned False. Failing the build."
493 )
494 result.success = False
495 return result
497 # Make sure register packages are up to date
498 module.create_regs_vhdl_package()
500 if not self.pre_build(**all_parameters):
501 print("ERROR: Project pre-build hook returned False. Failing the build.")
502 result.success = False
503 return result
505 # We ignore the type of 'output_path' going from 'Path | None' to 'Path'.
506 # It is only used if 'synth_only' is False, and we have an assertion that 'output_path' is
507 # not None in that case above.
509 build_vivado_project_tcl = self._build_tcl(
510 project_path=project_path,
511 output_path=output_path, # type: ignore[arg-type]
512 num_threads=num_threads,
513 run_index=run_index,
514 all_generics=all_generics,
515 synth_only=synth_only,
516 from_impl=from_impl,
517 impl_explore=self.impl_explore,
518 )
520 if not run_vivado_tcl(self._vivado_path, build_vivado_project_tcl):
521 result.success = False
522 return result
524 result.synthesis_size = self._get_size(project_path, f"synth_{run_index}")
525 if self.report_logic_level_distribution:
526 result.logic_level_distribution = self._get_logic_level_distribution(
527 project_path, f"synth_{run_index}"
528 )
530 if not synth_only:
531 if self.impl_explore:
532 runs_path = project_path / f"{self.name}.runs"
533 for run in runs_path.iterdir():
534 if "impl_explore_" in run.resolve().name:
535 # Check files for existence, since not all runs may have completed
536 bit_file = run / f"{self.top}.bit"
537 bin_file = run / f"{self.top}.bin"
538 if bit_file.exists() or bin_file.exists():
539 impl_folder = run
540 run_name = run.resolve().name
541 break
542 else:
543 run_name = f"impl_{run_index}"
544 impl_folder = project_path / f"{self.name}.runs" / run_name
545 bit_file = impl_folder / f"{self.top}.bit"
546 bin_file = impl_folder / f"{self.top}.bin"
548 shutil.copy2(bit_file, output_path / f"{self.name}.bit") # type: ignore[operator]
549 shutil.copy2(bin_file, output_path / f"{self.name}.bin") # type: ignore[operator]
550 result.implementation_size = self._get_size(project_path, run_name)
552 # Send the result object, along with everything else, to the post-build function
553 all_parameters.update(build_result=result)
555 if not self.post_build(**all_parameters):
556 print("ERROR: Project post-build hook returned False. Failing the build.")
557 result.success = False
559 return result
561 def open(self, project_path: Path) -> bool:
562 """
563 Open the project in Vivado GUI.
565 Arguments:
566 project_path: A path containing a Vivado project.
568 Return:
569 True if everything went well.
570 """
571 return run_vivado_gui(self._vivado_path, self.project_file(project_path))
573 def _get_size(self, project_path: Path, run: str) -> dict[str, int]:
574 """
575 Reads the hierarchical utilization report and returns the top level size
576 for the specified run.
577 """
578 report_as_string = read_file(
579 project_path / f"{self.name}.runs" / run / "hierarchical_utilization.rpt"
580 )
581 return HierarchicalUtilizationParser.get_size(report_as_string)
583 def _get_logic_level_distribution(self, project_path: Path, run: str) -> str:
584 """
585 Reads the hierarchical utilization report and returns the top level size
586 for the specified run.
587 """
588 report_as_string = read_file(
589 project_path / f"{self.name}.runs" / run / "logical_level_distribution.rpt"
590 )
591 return LogicLevelDistributionParser.get_table(report_as_string)
593 def __str__(self) -> str:
594 result = f"{self.name}\n"
596 if self.defined_at is not None:
597 result += f"Defined at: {self.defined_at.resolve()}\n"
599 result += f"Type: {self.__class__.__name__}\n"
600 result += f"Top level: {self.top}\n"
602 if self.static_generics:
603 generics = self._dict_to_string(self.static_generics)
604 else:
605 generics = "-"
606 result += f"Generics: {generics}\n"
608 if self.other_arguments:
609 result += f"Arguments: {self._dict_to_string(self.other_arguments)}\n"
611 return result
613 @staticmethod
614 def _dict_to_string(data: dict[str, Any]) -> str:
615 return ", ".join([f"{name}={value}" for name, value in data.items()])
618class VivadoNetlistProject(VivadoProject):
619 """
620 Used for handling Vivado build of a module without top level pinning.
621 """
623 def __init__(
624 self,
625 analyze_synthesis_timing: bool = False,
626 build_result_checkers: Optional[list[Union["SizeChecker", "MaximumLogicLevel"]]] = None,
627 **kwargs: Any,
628 ) -> None:
629 """
630 Arguments:
631 analyze_synthesis_timing: Enable analysis of the synthesized design's timing.
632 This will make the build flow open the design, and check for unhandled clock
633 crossings and pulse width violations.
634 Enabling it will add significant build time (can be as much as +40%).
635 Also, in order for clock crossing check to work, the clocks have to be created
636 using a constraint file.
637 build_result_checkers:
638 Checkers that will be executed after a successful build. Is used to automatically
639 check that e.g. resource utilization is not greater than expected.
640 kwargs: Further arguments accepted by :meth:`.VivadoProject.__init__`.
641 """
642 super().__init__(**kwargs)
644 self.is_netlist_build = True
645 self.analyze_synthesis_timing = analyze_synthesis_timing
646 self.report_logic_level_distribution = True
647 self.build_result_checkers = [] if build_result_checkers is None else build_result_checkers
649 def build( # type: ignore # pylint: disable=arguments-differ
650 self, **kwargs: Any
651 ) -> BuildResult:
652 """
653 Build the project.
655 Arguments:
656 kwargs: All arguments as accepted by :meth:`.VivadoProject.build`.
657 """
658 result = super().build(**kwargs)
659 result.success = result.success and self._check_size(result)
661 return result
663 def _check_size(self, build_result: BuildResult) -> bool:
664 if not build_result.success:
665 print(f"Can not do post_build check for {self.name} since it did not succeed.")
666 return False
668 success = True
669 for build_result_checker in self.build_result_checkers:
670 checker_result = build_result_checker.check(build_result)
671 success = success and checker_result
673 return success
676class VivadoIpCoreProject(VivadoProject):
677 """
678 A Vivado project that is only used to generate simulation models of IP cores.
679 """
681 ip_cores_only = True
683 def __init__(self, **kwargs: Any) -> None:
684 """
685 Arguments:
686 kwargs: Arguments as accepted by :meth:`.VivadoProject.__init__`.
687 """
688 super().__init__(**kwargs)
690 def build(self, **kwargs: Any): # type: ignore # pylint: disable=arguments-differ
691 """
692 Not implemented.
693 """
694 raise NotImplementedError("IP core project can not be built")
697def copy_and_combine_dicts(
698 dict_first: Optional[dict[str, Any]], dict_second: Optional[dict[str, Any]]
699) -> dict[str, Any]:
700 """
701 Will prefer values in the second dict, in case the same key occurs in both.
702 Will return an empty dictionary if both are ``None``.
703 """
704 if dict_first is None:
705 if dict_second is None:
706 return dict()
708 return dict_second.copy()
710 if dict_second is None:
711 return dict_first.copy()
713 result = dict_first.copy()
714 result.update(dict_second)
716 return result