Coverage for pyEDAA/ProjectModel/__init__.py: 69%
1015 statements
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-13 18:08 +0000
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-13 18:08 +0000
1# ==================================================================================================================== #
2# _____ ____ _ _ ____ _ _ __ __ _ _ #
3# _ __ _ _| ____| _ \ / \ / \ | _ \ _ __ ___ (_) ___ ___| |_| \/ | ___ __| | ___| | #
4# | '_ \| | | | _| | | | |/ _ \ / _ \ | |_) | '__/ _ \| |/ _ \/ __| __| |\/| |/ _ \ / _` |/ _ \ | #
5# | |_) | |_| | |___| |_| / ___ \ / ___ \ _| __/| | | (_) | | __/ (__| |_| | | | (_) | (_| | __/ | #
6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)_| |_| \___// |\___|\___|\__|_| |_|\___/ \__,_|\___|_| #
7# |_| |___/ |__/ #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2017-2026 Patrick Lehmann - Boetzingen, Germany #
15# Copyright 2014-2016 Technische Universität Dresden - Germany, Chair of VLSI-Design, Diagnostics and Architecture #
16# #
17# Licensed under the Apache License, Version 2.0 (the "License"); #
18# you may not use this file except in compliance with the License. #
19# You may obtain a copy of the License at #
20# #
21# http://www.apache.org/licenses/LICENSE-2.0 #
22# #
23# Unless required by applicable law or agreed to in writing, software #
24# distributed under the License is distributed on an "AS IS" BASIS, #
25# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
26# See the License for the specific language governing permissions and #
27# limitations under the License. #
28# #
29# SPDX-License-Identifier: Apache-2.0 #
30# ==================================================================================================================== #
31#
32"""An abstract model of EDA tool projects."""
33__author__ = "Patrick Lehmann"
34__email__ = "Paebbels@gmail.com"
35__copyright__ = "2014-2026, Patrick Lehmann, Unai Martinez-Corral"
36__license__ = "Apache License, Version 2.0"
37__version__ = "0.6.4"
38__keywords__ = ["eda project", "model", "abstract", "xilinx", "vivado", "osvvm", "file set", "file group", "test bench", "test harness"]
39__project_url__ = "https://github.com/edaa-org/pyEDAA.ProjectModel"
40__documentation_url__ = "https://edaa-org.github.io/pyEDAA.ProjectModel"
41__issue_tracker_url__ = "https://GitHub.com/edaa-org/pyEDAA.ProjectModel/issues"
43from os.path import relpath as path_relpath
44from pathlib import Path as pathlib_Path
45from sys import version_info
46from typing import Dict, Union, Optional as Nullable, List, Iterable, Generator, Tuple, Any as typing_Any, Type, Set, Self
48from pyTooling.Common import getFullyQualifiedName
49from pyTooling.Decorators import export
50from pyTooling.MetaClasses import ExtendedType
51from pyTooling.Graph import Graph, Vertex
52from pySVModel import SystemVerilogVersion
53from pyVHDLModel import VHDLVersion
54from pySystemRDLModel import SystemRDLVersion
57@export
58class Attribute(metaclass=ExtendedType):
59 KEY: str
60 VALUE_TYPE: typing_Any
62 @staticmethod
63 def resolve(obj: typing_Any, key: Type['Attribute']):
64 if isinstance(obj, File):
65 return obj._fileSet[key]
66 elif isinstance(obj, FileSet):
67 return obj._design[key]
68 elif isinstance(obj, Design): 68 ↛ 71line 68 didn't jump to line 71 because the condition on line 68 was always true
69 return obj._project[key]
70 else:
71 raise Exception("Resolution error")
74@export
75class FileType(ExtendedType):
76 """
77 A :term:`meta-class` to construct *FileType* classes.
79 Modifications done by this meta-class:
80 * Register all classes of type :class:`FileType` or derived variants in a class field :attr:`FileType.FileTypes` in this meta-class.
81 """
83 FileTypes: Dict[str, 'FileType'] = {} #: Dictionary of all classes of type :class:`FileType` or derived variants
84 Any: 'FileType'
86 def __init__(cls, name: str, bases: Tuple[type, ...], dictionary: Dict[str, typing_Any], **kwargs) -> None:
87 super().__init__(name, bases, dictionary, **kwargs)
88 cls.Any = cls
90 def __new__(cls, className, baseClasses, classMembers: Dict, *args, **kwargs) -> Self:
91 fileType = super().__new__(cls, className, baseClasses, classMembers, *args, **kwargs)
92 cls.FileTypes[className] = fileType
93 return fileType
95 def __getattr__(cls, item) -> 'FileType':
96 if item[:2] != "__" and item[-2:] != "__":
97 return cls.FileTypes[item]
98 else:
99 return super().__getattribute__(item)
101 def __contains__(cls, item) -> bool:
102 return issubclass(item, cls)
105@export
106class File(metaclass=FileType, slots=True):
107 """
108 A :term:`File` represents a file in a design. This :term:`base-class` is used
109 for all derived file classes.
111 A file can be created standalone and later associated to a fileset, design and
112 project. Or a fileset, design and/or project can be associated immediately
113 while creating a file.
115 :arg path: Relative or absolute path to the file.
116 :arg project: Project the file is associated with.
117 :arg design: Design the file is associated with.
118 :arg fileSet: Fileset the file is associated with.
119 """
121 _path: pathlib_Path
122 _fileType: 'FileType'
123 _project: Nullable['Project']
124 _design: Nullable['Design']
125 _fileSet: Nullable['FileSet']
126 _attributes: Dict[Type[Attribute], typing_Any]
128 def __init__(
129 self,
130 path: pathlib_Path,
131 project: Nullable["Project"] = None,
132 design: Nullable["Design"] = None,
133 fileSet: Nullable["FileSet"] = None
134 ) -> None:
135 self._fileType = getattr(FileTypes, self.__class__.__name__)
136 self._path = path
137 if project is not None:
138 self._project = project
139 self._design = design
140 if fileSet is not None: 140 ↛ 141line 140 didn't jump to line 141 because the condition on line 140 was never true
141 self.FileSet = fileSet
142 elif design is not None:
143 self._project = design._project
144 self._design = design
145 self.FileSet = design.DefaultFileSet if fileSet is None else fileSet
146 elif fileSet is not None:
147 design = fileSet._design
148 if design is not None:
149 self._project = design._project
150 else:
151 self._project = None
152 self._design = design
153 self.FileSet = fileSet
154 else:
155 self._project = None
156 self._design = None
157 self._fileSet = None
159 self._attributes = {}
160 self._registerAttributes()
162 def _registerAttributes(self) -> None:
163 pass
165 @property
166 def FileType(self) -> 'FileType':
167 """Read-only property to return the file type of this file."""
168 return self._fileType
170 @property
171 def Path(self) -> pathlib_Path:
172 """
173 Read-only property to access the path to the file.
175 :returns: The file's path.
176 """
177 return self._path
179 # TODO: setter?
181 @property
182 def ResolvedPath(self) -> pathlib_Path:
183 """Read-only property returning the resolved path of this file."""
184 if self._path.is_absolute(): 184 ↛ 185line 184 didn't jump to line 185 because the condition on line 184 was never true
185 return self._path.resolve()
186 elif self._fileSet is not None: 186 ↛ 196line 186 didn't jump to line 196 because the condition on line 186 was always true
187 path = (self._fileSet.ResolvedPath / self._path).resolve()
189 if path.is_absolute(): 189 ↛ 193line 189 didn't jump to line 193 because the condition on line 189 was always true
190 return path
191 else:
192 # WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
193 return pathlib_Path(path_relpath(path, pathlib_Path.cwd()))
194 else:
195 # TODO: message and exception type
196 raise Exception("")
198 @property
199 def Project(self) -> Nullable['Project']:
200 """Property setting or returning the project this file is used in."""
201 return self._project
203 @Project.setter
204 def Project(self, value: 'Project') -> None:
205 self._project = value
207 if self._fileSet is None: 207 ↛ exitline 207 didn't return from function 'Project' because the condition on line 207 was always true
208 self._project.DefaultDesign.DefaultFileSet.AddFile(self)
210 @property
211 def Design(self) -> Nullable['Design']:
212 """Property setting or returning the design this file is used in."""
213 return self._design
215 @Design.setter
216 def Design(self, value: 'Design') -> None:
217 self._design = value
219 if self._fileSet is None: 219 ↛ 222line 219 didn't jump to line 222 because the condition on line 219 was always true
220 self._design.DefaultFileSet.AddFile(self)
222 if self._project is None: 222 ↛ 224line 222 didn't jump to line 224 because the condition on line 222 was always true
223 self._project = value._project
224 elif self._project is not value._project:
225 raise Exception("The design's project is not identical to the already assigned project.")
227 @property
228 def FileSet(self) -> Nullable['FileSet']:
229 """Property setting or returning the fileset this file is used in."""
230 return self._fileSet
232 @FileSet.setter
233 def FileSet(self, value: 'FileSet') -> None:
234 self._fileSet = value
235 value._files.append(self)
237 def Validate(self) -> None:
238 """Validate this file."""
239 if self._path is None: 239 ↛ 240line 239 didn't jump to line 240 because the condition on line 239 was never true
240 raise Exception("Validation: File has no path.")
241 try:
242 path = self.ResolvedPath
243 except Exception as ex:
244 raise Exception(f"Validation: File '{self._path}' could not compute resolved path.") from ex
245 if not path.exists(): 245 ↛ 246line 245 didn't jump to line 246 because the condition on line 245 was never true
246 raise Exception(f"Validation: File '{self._path}' (={path}) does not exist.")
247 if not path.is_file(): 247 ↛ 248line 247 didn't jump to line 248 because the condition on line 247 was never true
248 raise Exception(f"Validation: File '{self._path}' (={path}) is not a file.")
250 if self._fileSet is None: 250 ↛ 251line 250 didn't jump to line 251 because the condition on line 250 was never true
251 raise Exception(f"Validation: File '{self._path}' has no fileset.")
252 if self._design is None: 252 ↛ 253line 252 didn't jump to line 253 because the condition on line 252 was never true
253 raise Exception(f"Validation: File '{self._path}' has no design.")
254 if self._project is None: 254 ↛ 255line 254 didn't jump to line 255 because the condition on line 254 was never true
255 raise Exception(f"Validation: File '{self._path}' has no project.")
257 def __len__(self) -> int:
258 """
259 Returns number of attributes set on this file.
261 :returns: The number of attributes set on this file.
262 """
263 return len(self._attributes)
265 def __getitem__(self, key: Type[Attribute]) -> typing_Any:
266 """Index access for returning attributes on this file.
268 :param key: The attribute type.
269 :returns: The attribute's value.
270 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
271 """
272 if not issubclass(key, Attribute): 272 ↛ 273line 272 didn't jump to line 273 because the condition on line 272 was never true
273 raise TypeError("Parameter 'key' is not an 'Attribute'.")
275 try:
276 return self._attributes[key]
277 except KeyError:
278 try:
279 return key.resolve(self, key)
280 except KeyError:
281 attribute = key()
282 self._attributes[key] = attribute
283 return attribute
285 def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
286 """
287 Index access for adding or setting attributes on this file.
289 :param key: The attribute type.
290 :param value: The attributes value.
291 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
292 """
293 if not issubclass(key, Attribute): 293 ↛ 294line 293 didn't jump to line 294 because the condition on line 293 was never true
294 raise TypeError("Parameter 'key' is not an 'Attribute'.")
296 self._attributes[key] = value
298 def __delitem__(self, key: Type[Attribute]) -> None:
299 """
300 Index access for deleting attributes on this file.
302 :param key: The attribute type.
303 """
304 if not issubclass(key, Attribute): 304 ↛ 305line 304 didn't jump to line 305 because the condition on line 304 was never true
305 raise TypeError("Parameter 'key' is not an 'Attribute'.")
307 del self._attributes[key]
309 def __str__(self) -> str:
310 return f"{self._path}"
313FileTypes = File
316@export
317class HumanReadableContent(metaclass=ExtendedType, mixin=True):
318 """A file type representing human-readable contents."""
321@export
322class XMLContent(HumanReadableContent, mixin=True):
323 """A file type representing XML contents."""
326@export
327class YAMLContent(HumanReadableContent, mixin=True):
328 """A file type representing YAML contents."""
331@export
332class JSONContent(HumanReadableContent, mixin=True):
333 """A file type representing JSON contents."""
336@export
337class INIContent(HumanReadableContent, mixin=True):
338 """A file type representing INI contents."""
341@export
342class TOMLContent(HumanReadableContent, mixin=True):
343 """A file type representing TOML contents."""
346@export
347class TCLContent(HumanReadableContent, mixin=True):
348 """A file type representing content in TCL code."""
351@export
352class SDCContent(TCLContent, mixin=True):
353 """A file type representing contents as Synopsys Design Constraints (SDC)."""
356@export
357class PythonContent(HumanReadableContent, mixin=True):
358 """A file type representing contents as Python source code."""
361@export
362class TextFile(File, HumanReadableContent):
363 """A text file (``*.txt``)."""
366@export
367class LogFile(File, HumanReadableContent):
368 """A log file (``*.log``)."""
371@export
372class XMLFile(File, XMLContent):
373 """An XML file (``*.xml``)."""
376@export
377class SourceFile(File):
378 """Base-class of all source files."""
381@export
382class HDLSourceFile(SourceFile):
383 """Base-class of all HDL source files."""
386@export
387class RDLSourceFile(SourceFile):
388 """Base-class of all RDL source files."""
391@export
392class NetlistFile(SourceFile):
393 """Base-class of all netlist source files."""
396@export
397class EDIFNetlistFile(NetlistFile):
398 """Netlist file in EDIF (Electronic Design Interchange Format)."""
401@export
402class TCLSourceFile(SourceFile, TCLContent):
403 """A TCL source file."""
406@export
407class VHDLSourceFile(HDLSourceFile, HumanReadableContent):
408 """
409 A VHDL source file (of any language version).
411 :arg path: Relative or absolute path to the file.
412 :arg vhdlLibrary: VHDLLibrary this VHDL source file is associated wih.
413 :arg vhdlVersion: VHDLVersion this VHDL source file is associated wih.
414 :arg project: Project the file is associated with.
415 :arg design: Design the file is associated with.
416 :arg fileSet: Fileset the file is associated with.
417 """
419 _vhdlLibrary: Nullable['VHDLLibrary']
420 _vhdlVersion: VHDLVersion
422 def __init__(self, path: pathlib_Path, vhdlLibrary: Union[str, 'VHDLLibrary'] = None, vhdlVersion: Nullable[VHDLVersion] = None, project: Nullable["Project"] = None, design: Nullable["Design"] = None, fileSet: Nullable["FileSet"] = None) -> None:
423 super().__init__(path, project, design, fileSet)
425 if isinstance(vhdlLibrary, str): 425 ↛ 426line 425 didn't jump to line 426 because the condition on line 425 was never true
426 if design is not None:
427 try:
428 vhdlLibrary = design.VHDLLibraries[vhdlLibrary]
429 except KeyError as ex:
430 raise Exception(f"VHDL library '{vhdlLibrary}' not found in design '{design.Name}'.") from ex
431 elif project is not None:
432 try:
433 vhdlLibrary = project.DefaultDesign.VHDLLibraries[vhdlLibrary]
434 except KeyError as ex:
435 raise Exception(f"VHDL library '{vhdlLibrary}' not found in default design '{project.DefaultDesign.Name}'.") from ex
436 else:
437 raise Exception(f"Can't lookup VHDL library because neither 'project' nor 'design' is given as a parameter.")
438 elif isinstance(vhdlLibrary, VHDLLibrary):
439 self._vhdlLibrary = vhdlLibrary
440 vhdlLibrary.AddFile(self)
441 elif vhdlLibrary is None: 441 ↛ 444line 441 didn't jump to line 444 because the condition on line 441 was always true
442 self._vhdlLibrary = None
443 else:
444 ex = TypeError(f"Parameter 'vhdlLibrary' is neither a 'str' nor 'VHDLibrary'.")
445 if version_info >= (3, 11): # pragma: no cover
446 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibrary)}'.")
447 raise ex
449 self._vhdlVersion = vhdlVersion
451 def Validate(self) -> None:
452 """Validate this VHDL source file."""
453 super().Validate()
455 try:
456 _ = self.VHDLLibrary
457 except Exception as ex:
458 raise Exception(f"Validation: VHDLSourceFile '{self._path}' (={self.ResolvedPath}) has no VHDLLibrary assigned.") from ex
459 try:
460 _ = self.VHDLVersion
461 except Exception as ex:
462 raise Exception(f"Validation: VHDLSourceFile '{self._path}' (={self.ResolvedPath}) has no VHDLVersion assigned.") from ex
464 @property
465 def VHDLLibrary(self) -> 'VHDLLibrary':
466 """Property setting or returning the VHDL library this VHDL source file is used in."""
467 if self._vhdlLibrary is not None:
468 return self._vhdlLibrary
469 elif self._fileSet is not None:
470 return self._fileSet.VHDLLibrary
471 else:
472 raise Exception("VHDLLibrary was neither set locally nor globally.")
474 @VHDLLibrary.setter
475 def VHDLLibrary(self, value: 'VHDLLibrary') -> None:
476 self._vhdlLibrary = value
477 value._files.append(self)
479 @property
480 def VHDLVersion(self) -> VHDLVersion:
481 """Property setting or returning the VHDL version this VHDL source file is used in."""
482 if self._vhdlVersion is not None:
483 return self._vhdlVersion
484 elif self._fileSet is not None:
485 return self._fileSet.VHDLVersion
486 else:
487 raise Exception("VHDLVersion was neither set locally nor globally.")
489 @VHDLVersion.setter
490 def VHDLVersion(self, value: VHDLVersion) -> None:
491 self._vhdlVersion = value
493 def __repr__(self) -> str:
494 return f"<VHDL file: '{self.ResolvedPath}'; lib: '{self.VHDLLibrary}'; version: {self.VHDLVersion}>"
497class VerilogMixIn(metaclass=ExtendedType, mixin=True):
498 @property
499 def VerilogVersion(self) -> SystemVerilogVersion:
500 """Property setting or returning the Verilog version this Verilog source file is used in."""
501 if self._version is not None:
502 return self._version
503 elif self._fileSet is not None:
504 return self._fileSet.VerilogVersion
505 else:
506 raise Exception("VerilogVersion was neither set locally nor globally.")
508 @VerilogVersion.setter
509 def VerilogVersion(self, value: SystemVerilogVersion) -> None:
510 self._version = value
513class SystemVerilogMixIn(metaclass=ExtendedType, mixin=True):
514 @property
515 def SVVersion(self) -> SystemVerilogVersion:
516 """Property setting or returning the SystemVerilog version this SystemVerilog source file is used in."""
517 if self._version is not None:
518 return self._version
519 elif self._fileSet is not None:
520 return self._fileSet.SVVersion
521 else:
522 raise Exception("SVVersion was neither set locally nor globally.")
524 @SVVersion.setter
525 def SVVersion(self, value: SystemVerilogVersion) -> None:
526 self._version = value
529@export
530class VerilogBaseFile(HDLSourceFile, HumanReadableContent):
531 _version: SystemVerilogVersion
533 def __init__(self, path: pathlib_Path, version: Nullable[SystemVerilogVersion] = None, project: Nullable["Project"] = None, design: Nullable["Design"] = None, fileSet: Nullable["FileSet"] = None) -> None:
534 super().__init__(path, project, design, fileSet)
536 self._version = version
539@export
540class VerilogSourceFile(VerilogBaseFile, VerilogMixIn):
541 """A Verilog source file (of any language version)."""
544@export
545class VerilogHeaderFile(VerilogBaseFile, VerilogMixIn):
546 """A Verilog header file (of any language version)."""
549@export
550class SystemVerilogBaseFile(VerilogBaseFile):
551 ...
554@export
555class SystemVerilogSourceFile(SystemVerilogBaseFile, SystemVerilogMixIn):
556 """A SystemVerilog source file (of any language version)."""
559@export
560class SystemVerilogHeaderFile(SystemVerilogBaseFile, SystemVerilogMixIn):
561 """A SystemVerilog header file (of any language version)."""
564@export
565class SystemRDLSourceFile(RDLSourceFile, HumanReadableContent):
566 """A SystemRDL source file (of any language version)."""
568 _srdlVersion: SystemRDLVersion
570 def __init__(self, path: pathlib_Path, srdlVersion: Nullable[SystemRDLVersion] = None, project: Nullable["Project"] = None, design: Nullable["Design"] = None, fileSet: Nullable["FileSet"] = None) -> None:
571 super().__init__(path, project, design, fileSet)
573 self._srdlVersion = srdlVersion
575 @property
576 def SystemRDLVersion(self) -> SystemRDLVersion:
577 """Property setting or returning the SystemRDL version this SystemRDL source file is used in."""
578 if self._srdlVersion is not None:
579 return self._srdlVersion
580 elif self._fileSet is not None:
581 return self._fileSet.SRDLVersion
582 else:
583 raise Exception("SRDLVersion was neither set locally nor globally.")
585 @SystemRDLVersion.setter
586 def SystemRDLVersion(self, value: SystemRDLVersion) -> None:
587 self._srdlVersion = value
590@export
591class PythonSourceFile(SourceFile, PythonContent):
592 """A Python source file."""
595# TODO: move to a Cocotb module
596@export
597class CocotbPythonFile(PythonSourceFile):
598 """A Python source file used by Cocotb."""
601@export
602class ConstraintFile(File, HumanReadableContent):
603 """Base-class of all constraint files."""
606@export
607class ProjectFile(File):
608 """Base-class of all tool-specific project files."""
611@export
612class CSourceFile(SourceFile):
613 """Base-class of all ANSI-C source files."""
616@export
617class CppSourceFile(SourceFile):
618 """Base-class of all ANSI-C++ source files."""
621@export
622class SettingFile(File):
623 """Base-class of all tool-specific setting files."""
626@export
627class SimulationAnalysisFile(File):
628 """Base-class of all tool-specific analysis files."""
631@export
632class SimulationElaborationFile(File):
633 """Base-class of all tool-specific elaboration files."""
636@export
637class SimulationStartFile(File):
638 """Base-class of all tool-specific simulation start-up files."""
641@export
642class SimulationRunFile(File):
643 """Base-class of all tool-specific simulation run (execution) files."""
646@export
647class WaveformConfigFile(File):
648 """Base-class of all tool-specific waveform configuration files."""
651@export
652class WaveformDatabaseFile(File):
653 """Base-class of all tool-specific waveform database files."""
656@export
657class WaveformExchangeFile(File):
658 """Base-class of all tool-independent waveform exchange files."""
661@export
662class FileSet(metaclass=ExtendedType, slots=True):
663 """
664 A :term:`FileSet` represents a group of files. Filesets can have sub-filesets.
666 The order of insertion is preserved. A fileset can be created standalone and
667 later associated to another fileset, design and/or project. Or a fileset,
668 design and/or project can be associated immediately while creating the
669 fileset.
671 :arg name: Name of this fileset.
672 :arg topLevel: Name of the fileset's toplevel.
673 :arg directory: Path of this fileset (absolute or relative to a parent fileset or design).
674 :arg project: Project the file is associated with.
675 :arg design: Design the file is associated with.
676 :arg parent: Parent fileset if this fileset is nested.
677 :arg vhdlLibrary: Default VHDL library for files in this fileset, if not specified for the file itself.
678 :arg vhdlVersion: Default VHDL version for files in this fileset, if not specified for the file itself.
679 :arg verilogVersion: Default Verilog version for files in this fileset, if not specified for the file itself.
680 :arg svVersion: Default SystemVerilog version for files in this fileset, if not specified for the file itself.
681 :arg srdlVersion: Default SystemRDL version for files in this fileset, if not specified for the file itself.
682 """
684 _name: str
685 _topLevel: Nullable[str]
686 _project: Nullable['Project']
687 _design: Nullable['Design']
688 _directory: pathlib_Path
689 _parent: Nullable['FileSet']
690 _fileSets: Dict[str, 'FileSet']
691 _files: List[File]
692 _set: Set
693 _attributes: Dict[Type[Attribute], typing_Any]
694 _vhdlLibraries: Dict[str, 'VHDLLibrary']
695 _vhdlLibrary: 'VHDLLibrary'
696 _vhdlVersion: VHDLVersion
697 _verilogVersion: SystemVerilogVersion
698 _svVersion: SystemVerilogVersion
699 _srdlVersion: SystemRDLVersion
701 def __init__(
702 self,
703 name: str,
704 topLevel: Nullable[str] = None,
705 directory: pathlib_Path = pathlib_Path("."),
706 project: Nullable["Project"] = None,
707 design: Nullable["Design"] = None,
708 parent: Nullable['FileSet'] = None,
709 vhdlLibrary: Union[str, 'VHDLLibrary'] = None,
710 vhdlVersion: Nullable[VHDLVersion] = None,
711 verilogVersion: Nullable[SystemVerilogVersion] = None,
712 svVersion: Nullable[SystemVerilogVersion] = None,
713 srdlVersion: Nullable[SystemRDLVersion] = None
714 ) -> None:
715 self._name = name
716 self._topLevel = topLevel
717 if project is not None:
718 self._project = project
719 self._design = design if design is not None else project.DefaultDesign
721 elif design is not None:
722 self._project = design._project
723 self._design = design
724 else:
725 self._project = None
726 self._design = None
727 self._directory = directory
728 self._parent = parent
729 self._fileSets = {}
730 self._files = []
731 self._set = set()
733 if design is not None:
734 design._fileSets[name] = self
736 self._attributes = {}
737 self._vhdlLibraries = {}
739 # TODO: handle if vhdlLibrary is a string
740 self._vhdlLibrary = vhdlLibrary
741 self._vhdlVersion = vhdlVersion
742 self._verilogVersion = verilogVersion
743 self._svVersion = svVersion
744 self._srdlVersion = srdlVersion
746 @property
747 def Name(self) -> str:
748 """Property setting or returning the fileset's name."""
749 return self._name
751 @Name.setter
752 def Name(self, value: str) -> None:
753 self._name = value
755 @property
756 def TopLevel(self) -> str:
757 """Property setting or returning the fileset's toplevel."""
758 return self._topLevel
760 @TopLevel.setter
761 def TopLevel(self, value: str) -> None:
762 self._topLevel = value
764 @property
765 def Project(self) -> Nullable['Project']:
766 """Property setting or returning the project this fileset is used in."""
767 return self._project
769 @Project.setter
770 def Project(self, value: 'Project') -> None:
771 self._project = value
773 @property
774 def Design(self) -> Nullable['Design']:
775 """Property setting or returning the design this fileset is used in."""
776 if self._design is not None:
777 return self._design
778 elif self._parent is not None: 778 ↛ 779line 778 didn't jump to line 779 because the condition on line 778 was never true
779 return self._parent.Design
780 else:
781 return None
782 # TODO: raise exception instead
783 # QUESTION: how to handle if design and parent is set?
785 @Design.setter
786 def Design(self, value: 'Design') -> None:
787 self._design = value
788 if self._project is None: 788 ↛ 790line 788 didn't jump to line 790 because the condition on line 788 was always true
789 self._project = value._project
790 elif self._project is not value._project:
791 raise Exception("The design's project is not identical to the already assigned project.")
793 @property
794 def Directory(self) -> pathlib_Path:
795 """Property setting or returning the directory this fileset is located in."""
796 return self._directory
798 @Directory.setter
799 def Directory(self, value: pathlib_Path) -> None:
800 self._directory = value
802 @property
803 def ResolvedPath(self) -> pathlib_Path:
804 """Read-only property returning the resolved path of this fileset."""
805 if self._directory.is_absolute(): 805 ↛ 806line 805 didn't jump to line 806 because the condition on line 805 was never true
806 return self._directory.resolve()
807 else:
808 if self._parent is not None: 808 ↛ 809line 808 didn't jump to line 809 because the condition on line 808 was never true
809 directory = self._parent.ResolvedPath
810 elif self._design is not None: 810 ↛ 812line 810 didn't jump to line 812 because the condition on line 810 was always true
811 directory = self._design.ResolvedPath
812 elif self._project is not None:
813 directory = self._project.ResolvedPath
814 else:
815 # TODO: message and exception type
816 raise Exception("")
818 directory = (directory / self._directory).resolve()
819 if directory.is_absolute(): 819 ↛ 823line 819 didn't jump to line 823 because the condition on line 819 was always true
820 return directory
821 else:
822 # WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
823 return pathlib_Path(path_relpath(directory, pathlib_Path.cwd()))
825 @property
826 def Parent(self) -> Nullable['FileSet']:
827 """Property setting or returning the parent fileset this fileset is used in."""
828 return self._parent
830 @Parent.setter
831 def Parent(self, value: 'FileSet') -> None:
832 self._parent = value
833 value._fileSets[self._name] = self
834 # TODO: check it it already exists
835 # QUESTION: make an Add fileset method?
837 @property
838 def FileSets(self) -> Dict[str, 'FileSet']:
839 """Read-only property returning the dictionary of sub-filesets."""
840 return self._fileSets
842 def Files(self, fileType: FileType = FileTypes.Any, fileSet: Union[bool, str, 'FileSet'] = None) -> Generator[File, None, None]:
843 """
844 Method returning the files of this fileset.
846 :arg fileType: A filter for file types. Default: ``Any``.
847 :arg fileSet: Specifies how to handle sub-filesets.
848 """
849 if fileSet is False: 849 ↛ 850line 849 didn't jump to line 850 because the condition on line 849 was never true
850 for file in self._files:
851 if file.FileType in fileType:
852 yield file
853 elif fileSet is None: 853 ↛ 861line 853 didn't jump to line 861 because the condition on line 853 was always true
854 for fileSet in self._fileSets.values(): 854 ↛ 855line 854 didn't jump to line 855 because the loop on line 854 never started
855 for file in fileSet.Files(fileType):
856 yield file
857 for file in self._files:
858 if file.FileType in fileType:
859 yield file
860 else:
861 if isinstance(fileSet, str):
862 fileSetName = fileSet
863 try:
864 fileSet = self._fileSets[fileSetName]
865 except KeyError as ex:
866 raise Exception(f"Fileset {fileSetName} not bound to fileset {self.Name}.") from ex
867 elif not isinstance(fileSet, FileSet):
868 raise TypeError("Parameter 'fileSet' is not of type 'str' or 'FileSet' nor value 'None'.")
870 for file in fileSet.Files(fileType):
871 yield file
873 def AddFileSet(self, fileSet: "FileSet") -> None:
874 """
875 Method to add a single sub-fileset to this fileset.
877 :arg fileSet: A fileset to add to this fileset as sub-fileset.
878 """
879 if not isinstance(fileSet, FileSet): 879 ↛ 880line 879 didn't jump to line 880 because the condition on line 879 was never true
880 raise ValueError("Parameter 'fileSet' is not of type ProjectModel.FileSet.")
881 elif fileSet in self._fileSets: 881 ↛ 882line 881 didn't jump to line 882 because the condition on line 881 was never true
882 raise Exception("Sub-fileset already contains this fileset.")
883 elif fileSet.Name in self._fileSets.keys(): 883 ↛ 884line 883 didn't jump to line 884 because the condition on line 883 was never true
884 raise Exception(f"Fileset already contains a sub-fileset named '{fileSet.Name}'.")
886 self._fileSets[fileSet.Name] = fileSet
887 fileSet._parent = self
889 def AddFileSets(self, fileSets: Iterable["FileSet"]) -> None:
890 """
891 Method to add a multiple sub-filesets to this fileset.
893 :arg fileSets: An iterable of filesets to add each to the fileset.
894 """
895 for fileSet in fileSets:
896 self.AddFileSet(fileSet)
898 @property
899 def FileSetCount(self) -> int:
900 """Returns number of file sets excl. sub-filesets."""
901 return len(self._fileSets)
903 @property
904 def TotalFileSetCount(self) -> int:
905 """Returns number of file sets incl. sub-filesets."""
906 fileSetCount = len(self._fileSets)
907 for fileSet in self._fileSets.values():
908 fileSetCount += fileSet.TotalFileSetCount
910 return fileSetCount
912 def AddFile(self, file: File) -> None:
913 """
914 Method to add a single file to this fileset.
916 :arg file: A file to add to this fileset.
917 """
918 if not isinstance(file, File):
919 raise TypeError("Parameter 'file' is not of type ProjectModel.File.")
920 elif file._fileSet is not None:
921 ex = ValueError(f"File '{file.Path!s}' is already part of fileset '{file.FileSet.Name}'.")
922 if version_info >= (3, 11): # pragma: no cover
923 ex.add_note(f"A file can't be assigned to another fileset.")
924 raise ex
925 elif file in self._set: 925 ↛ 926line 925 didn't jump to line 926 because the condition on line 925 was never true
926 ex = ValueError(f"File '{file.Path!s}' is already part of this fileset.")
927 if version_info >= (3, 11): # pragma: no cover
928 ex.add_note(f"A file can't be added twice to a fileset.")
929 raise ex
931 self._files.append(file)
932 self._set.add(file)
933 file._fileSet = self
935 def AddFiles(self, files: Iterable[File]) -> None:
936 """
937 Method to add a multiple files to this fileset.
939 :arg files: An iterable of files to add each to the fileset.
940 """
941 for file in files:
942 self.AddFile(file)
944 @property
945 def FileCount(self) -> int:
946 """Returns number of files excl. sub-filesets."""
947 return len(self._files)
949 @property
950 def TotalFileCount(self) -> int:
951 """Returns number of files incl. the files in sub-filesets."""
952 fileCount = len(self._files)
953 for fileSet in self._fileSets.values():
954 fileCount += fileSet.FileCount
956 return fileCount
958 def Validate(self) -> None:
959 """Validate this fileset."""
960 if self._name is None or self._name == "": 960 ↛ 961line 960 didn't jump to line 961 because the condition on line 960 was never true
961 raise Exception("Validation: FileSet has no name.")
963 if self._directory is None: 963 ↛ 964line 963 didn't jump to line 964 because the condition on line 963 was never true
964 raise Exception(f"Validation: FileSet '{self._name}' has no directory.")
965 try:
966 path = self.ResolvedPath
967 except Exception as ex:
968 raise Exception(f"Validation: FileSet '{self._name}' could not compute resolved path.") from ex
969 if not path.exists(): 969 ↛ 970line 969 didn't jump to line 970 because the condition on line 969 was never true
970 raise Exception(f"Validation: FileSet '{self._name}'s directory '{path}' does not exist.")
971 if not path.is_dir(): 971 ↛ 972line 971 didn't jump to line 972 because the condition on line 971 was never true
972 raise Exception(f"Validation: FileSet '{self._name}'s directory '{path}' is not a directory.")
974 if self._design is None: 974 ↛ 975line 974 didn't jump to line 975 because the condition on line 974 was never true
975 raise Exception(f"Validation: FileSet '{self._directory}' has no design.")
976 if self._project is None: 976 ↛ 977line 976 didn't jump to line 977 because the condition on line 976 was never true
977 raise Exception(f"Validation: FileSet '{self._directory}' has no project.")
979 for fileSet in self._fileSets.values(): 979 ↛ 980line 979 didn't jump to line 980 because the loop on line 979 never started
980 fileSet.Validate()
981 for file in self._files: 981 ↛ 982line 981 didn't jump to line 982 because the loop on line 981 never started
982 file.Validate()
984 def GetOrCreateVHDLLibrary(self, name) -> 'VHDLLibrary':
985 if name in self._vhdlLibraries:
986 return self._vhdlLibraries[name]
987 elif name in self._design._vhdlLibraries:
988 library = self._design._vhdlLibraries[name]
989 self._vhdlLibraries[name] = library
990 return library
991 else:
992 library = VHDLLibrary(name, design=self._design, vhdlVersion=self._vhdlVersion)
993 self._vhdlLibraries[name] = library
994 return library
996 @property
997 def VHDLLibrary(self) -> 'VHDLLibrary':
998 """Property setting or returning the VHDL library of this fileset."""
999 if self._vhdlLibrary is not None:
1000 return self._vhdlLibrary
1001 elif self._parent is not None: 1001 ↛ 1003line 1001 didn't jump to line 1003 because the condition on line 1001 was always true
1002 return self._parent.VHDLLibrary
1003 elif self._design is not None:
1004 return self._design.VHDLLibrary # FIXME: no library property. add a DefaultVHDLLibrary property
1005 else:
1006 raise Exception("VHDLLibrary was neither set locally nor globally.")
1008 @VHDLLibrary.setter
1009 def VHDLLibrary(self, value: 'VHDLLibrary') -> None:
1010 self._vhdlLibrary = value
1012 @property
1013 def VHDLVersion(self) -> VHDLVersion:
1014 """Property setting or returning the VHDL version of this fileset."""
1015 if self._vhdlVersion is not None:
1016 return self._vhdlVersion
1017 elif self._parent is not None:
1018 return self._parent.VHDLVersion
1019 elif self._design is not None: 1019 ↛ 1022line 1019 didn't jump to line 1022 because the condition on line 1019 was always true
1020 return self._design.VHDLVersion
1021 else:
1022 raise Exception("VHDLVersion was neither set locally nor globally.")
1024 @VHDLVersion.setter
1025 def VHDLVersion(self, value: VHDLVersion) -> None:
1026 self._vhdlVersion = value
1028 @property
1029 def VerilogVersion(self) -> SystemVerilogVersion:
1030 """Property setting or returning the Verilog version of this fileset."""
1031 if self._verilogVersion is not None:
1032 return self._verilogVersion
1033 elif self._parent is not None:
1034 return self._parent.VerilogVersion
1035 elif self._design is not None: 1035 ↛ 1038line 1035 didn't jump to line 1038 because the condition on line 1035 was always true
1036 return self._design.VerilogVersion
1037 else:
1038 raise Exception("VerilogVersion was neither set locally nor globally.")
1040 @VerilogVersion.setter
1041 def VerilogVersion(self, value: SystemVerilogVersion) -> None:
1042 self._verilogVersion = value
1044 @property
1045 def SVVersion(self) -> SystemVerilogVersion:
1046 """Property setting or returning the SystemVerilog version of this fileset."""
1047 if self._svVersion is not None:
1048 return self._svVersion
1049 elif self._parent is not None:
1050 return self._parent.SVVersion
1051 elif self._design is not None: 1051 ↛ 1054line 1051 didn't jump to line 1054 because the condition on line 1051 was always true
1052 return self._design.SVVersion
1053 else:
1054 raise Exception("SVVersion was neither set locally nor globally.")
1056 @SVVersion.setter
1057 def SVVersion(self, value: SystemVerilogVersion) -> None:
1058 self._svVersion = value
1060 @property
1061 def SRDLVersion(self) -> SystemRDLVersion:
1062 if self._srdlVersion is not None:
1063 return self._srdlVersion
1064 elif self._parent is not None:
1065 return self._parent.SRDLVersion
1066 elif self._design is not None:
1067 return self._design.SRDLVersion
1068 else:
1069 raise Exception("SRDLVersion was neither set locally nor globally.")
1071 @SRDLVersion.setter
1072 def SRDLVersion(self, value: SystemRDLVersion) -> None:
1073 self._srdlVersion = value
1075 def __len__(self) -> int:
1076 """
1077 Returns number of attributes set on this fileset.
1079 :returns: The number of attributes set on this fileset.
1080 """
1081 return len(self._attributes)
1083 def __getitem__(self, key: Type[Attribute]) -> typing_Any:
1084 """Index access for returning attributes on this fileset.
1086 :param key: The attribute type.
1087 :returns: The attribute's value.
1088 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1089 """
1090 if not issubclass(key, Attribute): 1090 ↛ 1091line 1090 didn't jump to line 1091 because the condition on line 1090 was never true
1091 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1093 try:
1094 return self._attributes[key]
1095 except KeyError:
1096 return key.resolve(self, key)
1098 def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1099 """
1100 Index access for adding or setting attributes on this fileset.
1102 :param key: The attribute type.
1103 :param value: The attributes value.
1104 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1105 """
1106 if not issubclass(key, Attribute): 1106 ↛ 1107line 1106 didn't jump to line 1107 because the condition on line 1106 was never true
1107 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1109 self._attributes[key] = value
1111 def __delitem__(self, key: Type[Attribute]) -> None:
1112 """
1113 Index access for deleting attributes on this fileset.
1115 :param key: The attribute type.
1116 """
1117 if not issubclass(key, Attribute): 1117 ↛ 1118line 1117 didn't jump to line 1118 because the condition on line 1117 was never true
1118 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1120 del self._attributes[key]
1122 def __str__(self) -> str:
1123 """Returns the fileset's name."""
1124 return self._name
1127@export
1128class VHDLLibrary(metaclass=ExtendedType, slots=True):
1129 """
1130 A :term:`VHDLLibrary` represents a group of VHDL source files compiled into the same VHDL library.
1132 :arg name: The VHDL libraries' name.
1133 :arg project: Project the VHDL library is associated with.
1134 :arg design: Design the VHDL library is associated with.
1135 :arg vhdlVersion: Default VHDL version for files in this VHDL library, if not specified for the file itself.
1136 """
1138 _name: str
1139 _project: Nullable['Project']
1140 _design: Nullable['Design']
1141 _files: List[File]
1142 _vhdlVersion: VHDLVersion
1144 _attributes: Dict[Attribute, typing_Any]
1145 _dependencyNode: Vertex
1147 def __init__(
1148 self,
1149 name: str,
1150 project: Nullable["Project"] = None,
1151 design: Nullable["Design"] = None,
1152 vhdlVersion: Nullable[VHDLVersion] = None
1153 ) -> None:
1154 self._name = name
1155 self._attributes = {}
1157 if project is not None:
1158 self._project = project
1159 self._design = project._defaultDesign if design is None else design
1160 self._dependencyNode = Vertex(value=self, graph=self._design._vhdlLibraryDependencyGraph)
1162 if name in self._design._vhdlLibraries: 1162 ↛ 1163line 1162 didn't jump to line 1163 because the condition on line 1162 was never true
1163 raise Exception(f"Library '{name}' already in design '{self._design.Name}'.")
1164 else:
1165 self._design._vhdlLibraries[name] = self
1167 elif design is not None:
1168 self._project = design._project
1169 self._design = design
1170 self._dependencyNode = Vertex(value=self, graph=design._vhdlLibraryDependencyGraph)
1172 if name in design._vhdlLibraries: 1172 ↛ 1173line 1172 didn't jump to line 1173 because the condition on line 1172 was never true
1173 raise Exception(f"Library '{name}' already in design '{design.Name}'.")
1174 else:
1175 design._vhdlLibraries[name] = self
1177 else:
1178 self._project = None
1179 self._design = None
1180 self._dependencyNode = None
1182 self._files = []
1183 self._vhdlVersion = vhdlVersion
1185 @property
1186 def Name(self) -> str:
1187 return self._name
1189 @property
1190 def Project(self) -> Nullable['Project']:
1191 """Property setting or returning the project this VHDL library is used in."""
1192 return self._project
1194 @Project.setter
1195 def Project(self, value: 'Project') -> None:
1196 if not isinstance(value, Project): 1196 ↛ 1197line 1196 didn't jump to line 1197 because the condition on line 1196 was never true
1197 raise TypeError("Parameter 'value' is not of type 'Project'.")
1199 if value is None: 1199 ↛ 1201line 1199 didn't jump to line 1201 because the condition on line 1199 was never true
1200 # TODO: unlink VHDLLibrary from project
1201 self._project = None
1202 else:
1203 self._project = value
1204 if self._design is None: 1204 ↛ exitline 1204 didn't return from function 'Project' because the condition on line 1204 was always true
1205 self._design = value._defaultDesign
1207 @property
1208 def Design(self) -> Nullable['Design']:
1209 """Property setting or returning the design this VHDL library is used in."""
1210 return self._design
1212 @Design.setter
1213 def Design(self, value: 'Design') -> None:
1214 if not isinstance(value, Design):
1215 raise TypeError("Parameter 'value' is not of type 'Design'.")
1217 if value is None:
1218 # TODO: unlink VHDLLibrary from design
1219 self._design = None
1220 else:
1221 if self._design is None:
1222 self._design = value
1223 self._dependencyNode = Vertex(value=self, graph=self._design._vhdlLibraryDependencyGraph)
1224 elif self._design is not value:
1225 # TODO: move VHDLLibrary to other design
1226 # TODO: create new vertex in dependency graph and remove vertex from old graph
1227 self._design = value
1228 else:
1229 pass
1231 if self._project is None:
1232 self._project = value._project
1233 elif self._project is not value._project:
1234 raise Exception("The design's project is not identical to the already assigned project.")
1236 @property
1237 def Files(self) -> Generator[File, None, None]:
1238 """Read-only property to return all files in this VHDL library."""
1239 for file in self._files:
1240 yield file
1242 @property
1243 def VHDLVersion(self) -> VHDLVersion:
1244 """Property setting or returning the VHDL version of this VHDL library."""
1245 if self._vhdlVersion is not None:
1246 return self._vhdlVersion
1247 elif self._design is not None: 1247 ↛ 1250line 1247 didn't jump to line 1250 because the condition on line 1247 was always true
1248 return self._design.VHDLVersion
1249 else:
1250 raise Exception("VHDLVersion is not set on VHDLLibrary nor parent object.")
1252 @VHDLVersion.setter
1253 def VHDLVersion(self, value: VHDLVersion) -> None:
1254 self._vhdlVersion = value
1256 def AddDependency(self, library: 'VHDLLibrary') -> None:
1257 library.parent = self
1259 def AddFile(self, vhdlFile: VHDLSourceFile) -> None:
1260 if not isinstance(vhdlFile, VHDLSourceFile): 1260 ↛ 1261line 1260 didn't jump to line 1261 because the condition on line 1260 was never true
1261 ex = TypeError(f"Parameter 'vhdlFile' is not a 'VHDLSourceFile'.")
1262 if version_info >= (3, 11): # pragma: no cover
1263 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlFile)}'.")
1264 raise ex
1266 self._files.append(vhdlFile)
1268 def AddFiles(self, vhdlFiles: Iterable[VHDLSourceFile]) -> None:
1269 for vhdlFile in vhdlFiles:
1270 if not isinstance(vhdlFile, VHDLSourceFile):
1271 raise TypeError(f"Item '{vhdlFile}' in parameter 'vhdlFiles' is not a 'VHDLSourceFile'.")
1273 self._files.append(vhdlFile)
1275 @property
1276 def FileCount(self) -> int:
1277 """Returns number of files."""
1278 return len(self._files)
1280 def __len__(self) -> int:
1281 """
1282 Returns number of attributes set on this VHDL library.
1284 :returns: The number of attributes set on this VHDL library.
1285 """
1286 return len(self._attributes)
1288 def __getitem__(self, key: Type[Attribute]) -> typing_Any:
1289 """Index access for returning attributes on this VHDL library.
1291 :param key: The attribute type.
1292 :returns: The attribute's value.
1293 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1294 """
1295 if not issubclass(key, Attribute):
1296 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1298 try:
1299 return self._attributes[key]
1300 except KeyError:
1301 return key.resolve(self, key)
1303 def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1304 """
1305 Index access for adding or setting attributes on this VHDL library.
1307 :param key: The attribute type.
1308 :param value: The attributes value.
1309 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1310 """
1311 if not issubclass(key, Attribute):
1312 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1314 self._attributes[key] = value
1316 def __delitem__(self, key: Type[Attribute]) -> None:
1317 """
1318 Index access for deleting attributes on this VHDL library.
1320 :param key: The attribute type.
1321 """
1322 if not issubclass(key, Attribute):
1323 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1325 del self._attributes[key]
1327 def __str__(self) -> str:
1328 """Returns the VHDL library's name."""
1329 return self._name
1332@export
1333class Design(metaclass=ExtendedType, slots=True):
1334 """
1335 A :term:`Design` represents a group of filesets and the source files therein.
1337 Each design contains at least one fileset - the :term:`default fileset`. For
1338 designs with VHDL source files, a independent `VHDLLibraries` overlay structure
1339 exists.
1341 :arg name: The design's name.
1342 :arg topLevel: Name of the design's toplevel.
1343 :arg directory: Path of this design (absolute or relative to the project).
1344 :arg project: Project the design is associated with.
1345 :arg vhdlVersion: Default VHDL version for files in this design, if not specified for the file itself.
1346 :arg verilogVersion: Default Verilog version for files in this design, if not specified for the file itself.
1347 :arg svVersion: Default SystemVerilog version for files in this design, if not specified for the file itself.
1348 :arg srdlVersion: Default SystemRDL version for files in this fileset, if not specified for the file itself.
1349 """
1351 _name: str
1352 _topLevel: Nullable[str]
1353 _project: Nullable['Project']
1354 _directory: pathlib_Path
1355 _fileSets: Dict[str, FileSet]
1356 _defaultFileSet: Nullable[FileSet]
1357 _attributes: Dict[Type[Attribute], typing_Any]
1359 _vhdlLibraries: Dict[str, VHDLLibrary]
1360 _vhdlVersion: VHDLVersion
1361 _verilogVersion: SystemVerilogVersion
1362 _svVersion: SystemVerilogVersion
1363 _srdlVersion: SystemRDLVersion
1364 _externalVHDLLibraries: List
1366 _vhdlLibraryDependencyGraph: Graph
1367 _fileDependencyGraph: Graph
1369 def __init__(
1370 self,
1371 name: str,
1372 topLevel: Nullable[str] = None,
1373 directory: pathlib_Path = pathlib_Path("."),
1374 project: Nullable["Project"] = None,
1375 vhdlVersion: Nullable[VHDLVersion] = None,
1376 verilogVersion: Nullable[SystemVerilogVersion] = None,
1377 svVersion: Nullable[SystemVerilogVersion] = None,
1378 srdlVersion: Nullable[SystemRDLVersion] = None
1379 ) -> None:
1380 self._name = name
1381 self._topLevel = topLevel
1382 self._project = project
1383 if project is not None:
1384 project._designs[name] = self
1385 self._directory = directory
1386 self._fileSets = {}
1387 self._defaultFileSet = FileSet("default", project=project, design=self)
1388 self._attributes = {}
1389 self._vhdlLibraries = {}
1390 self._vhdlVersion = vhdlVersion
1391 self._verilogVersion = verilogVersion
1392 self._svVersion = svVersion
1393 self._srdlVersion = srdlVersion
1394 self._externalVHDLLibraries = []
1396 self._vhdlLibraryDependencyGraph = Graph()
1397 self._fileDependencyGraph = Graph()
1399 @property
1400 def Name(self) -> str:
1401 """Property setting or returning the design's name."""
1402 return self._name
1404 @Name.setter
1405 def Name(self, value: str) -> None:
1406 self._name = value
1408 @property
1409 def TopLevel(self) -> str:
1410 """Property setting or returning the fileset's toplevel."""
1411 return self._topLevel
1413 @TopLevel.setter
1414 def TopLevel(self, value: str) -> None:
1415 self._topLevel = value
1417 @property
1418 def Project(self) -> Nullable['Project']:
1419 """Property setting or returning the project this design is used in."""
1420 return self._project
1422 @Project.setter
1423 def Project(self, value: 'Project') -> None:
1424 self._project = value
1426 @property
1427 def Directory(self) -> pathlib_Path:
1428 """Property setting or returning the directory this design is located in."""
1429 return self._directory
1431 @Directory.setter
1432 def Directory(self, value: pathlib_Path) -> None:
1433 self._directory = value
1435 @property
1436 def ResolvedPath(self) -> pathlib_Path:
1437 """Read-only property returning the resolved path of this fileset."""
1438 if self._directory.is_absolute(): 1438 ↛ 1439line 1438 didn't jump to line 1439 because the condition on line 1438 was never true
1439 return self._directory.resolve()
1440 elif self._project is not None: 1440 ↛ 1450line 1440 didn't jump to line 1450 because the condition on line 1440 was always true
1441 path = (self._project.ResolvedPath / self._directory).resolve()
1443 if path.is_absolute(): 1443 ↛ 1447line 1443 didn't jump to line 1447 because the condition on line 1443 was always true
1444 return path
1445 else:
1446 # WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
1447 return pathlib_Path(path_relpath(path, pathlib_Path.cwd()))
1448 else:
1449 # TODO: message and exception type
1450 raise Exception("")
1452 @property
1453 def DefaultFileSet(self) -> FileSet:
1454 """Property setting or returning the default fileset of this design."""
1455 return self._defaultFileSet
1457 @DefaultFileSet.setter
1458 def DefaultFileSet(self, value: Union[str, FileSet]) -> None:
1459 if isinstance(value, str):
1460 if value not in self._fileSets.keys():
1461 raise Exception(f"Fileset '{value}' is not in this design.")
1463 self._defaultFileSet = self._fileSets[value]
1464 elif isinstance(value, FileSet):
1465 if value not in self.FileSets:
1466 raise Exception(f"Fileset '{value}' is not associated to this design.")
1468 self._defaultFileSet = value
1469 else:
1470 raise ValueError("Unsupported parameter type for 'value'.")
1472 # TODO: return generator with another method
1473 @property
1474 def FileSets(self) -> Dict[str, FileSet]:
1475 """Read-only property returning the dictionary of filesets."""
1476 return self._fileSets
1478 def Files(self, fileType: FileType = FileTypes.Any, fileSet: Union[str, FileSet] = None) -> Generator[File, None, None]:
1479 """
1480 Method returning the files of this design.
1482 :arg fileType: A filter for file types. Default: ``Any``.
1483 :arg fileSet: Specifies if all files from all filesets (``fileSet=None``) are files from a single fileset are returned.
1484 """
1485 if fileSet is None:
1486 for fileSet in self._fileSets.values():
1487 for file in fileSet.Files(fileType):
1488 yield file
1489 else:
1490 if isinstance(fileSet, str): 1490 ↛ 1495line 1490 didn't jump to line 1495 because the condition on line 1490 was always true
1491 try:
1492 fileSet = self._fileSets[fileSet]
1493 except KeyError as ex:
1494 raise Exception(f"Fileset {fileSet.Name} not bound to design {self.Name}.") from ex
1495 elif not isinstance(fileSet, FileSet):
1496 raise TypeError("Parameter 'fileSet' is not of type 'str' or 'FileSet' nor value 'None'.")
1498 for file in fileSet.Files(fileType):
1499 yield file
1501 def Validate(self) -> None:
1502 """Validate this design."""
1503 if self._name is None or self._name == "": 1503 ↛ 1504line 1503 didn't jump to line 1504 because the condition on line 1503 was never true
1504 raise Exception("Validation: Design has no name.")
1506 if self._directory is None: 1506 ↛ 1507line 1506 didn't jump to line 1507 because the condition on line 1506 was never true
1507 raise Exception(f"Validation: Design '{self._name}' has no directory.")
1508 try:
1509 path = self.ResolvedPath
1510 except Exception as ex:
1511 raise Exception(f"Validation: Design '{self._name}' could not compute resolved path.") from ex
1512 if not path.exists(): 1512 ↛ 1513line 1512 didn't jump to line 1513 because the condition on line 1512 was never true
1513 raise Exception(f"Validation: Design '{self._name}'s directory '{path}' does not exist.")
1514 if not path.is_dir(): 1514 ↛ 1515line 1514 didn't jump to line 1515 because the condition on line 1514 was never true
1515 raise Exception(f"Validation: Design '{self._name}'s directory '{path}' is not a directory.")
1517 if len(self._fileSets) == 0: 1517 ↛ 1518line 1517 didn't jump to line 1518 because the condition on line 1517 was never true
1518 raise Exception(f"Validation: Design '{self._name}' has no fileset.")
1519 try:
1520 if self._defaultFileSet is not self._fileSets[self._defaultFileSet.Name]: 1520 ↛ 1521line 1520 didn't jump to line 1521 because the condition on line 1520 was never true
1521 raise Exception(f"Validation: Design '{self._name}'s default fileset is the same as listed in filesets.")
1522 except KeyError as ex:
1523 raise Exception(f"Validation: Design '{self._name}'s default fileset is not in list of filesets.") from ex
1524 if self._project is None: 1524 ↛ 1525line 1524 didn't jump to line 1525 because the condition on line 1524 was never true
1525 raise Exception(f"Validation: Design '{self._path}' has no project.")
1527 for fileSet in self._fileSets.values():
1528 fileSet.Validate()
1530 @property
1531 def VHDLLibraries(self) -> Dict[str, VHDLLibrary]:
1532 return self._vhdlLibraries
1534 @property
1535 def VHDLVersion(self) -> VHDLVersion:
1536 if self._vhdlVersion is not None:
1537 return self._vhdlVersion
1538 elif self._project is not None: 1538 ↛ 1541line 1538 didn't jump to line 1541 because the condition on line 1538 was always true
1539 return self._project.VHDLVersion
1540 else:
1541 raise Exception("VHDLVersion was neither set locally nor globally.")
1543 @VHDLVersion.setter
1544 def VHDLVersion(self, value: VHDLVersion) -> None:
1545 self._vhdlVersion = value
1547 @property
1548 def VerilogVersion(self) -> SystemVerilogVersion:
1549 if self._verilogVersion is not None:
1550 return self._verilogVersion
1551 elif self._project is not None: 1551 ↛ 1554line 1551 didn't jump to line 1554 because the condition on line 1551 was always true
1552 return self._project.VerilogVersion
1553 else:
1554 raise Exception("VerilogVersion was neither set locally nor globally.")
1556 @VerilogVersion.setter
1557 def VerilogVersion(self, value: SystemVerilogVersion) -> None:
1558 self._verilogVersion = value
1560 @property
1561 def SVVersion(self) -> SystemVerilogVersion:
1562 if self._svVersion is not None:
1563 return self._svVersion
1564 elif self._project is not None: 1564 ↛ 1567line 1564 didn't jump to line 1567 because the condition on line 1564 was always true
1565 return self._project.SVVersion
1566 else:
1567 raise Exception("SVVersion was neither set locally nor globally.")
1569 @SVVersion.setter
1570 def SVVersion(self, value: SystemVerilogVersion) -> None:
1571 self._svVersion = value
1573 @property
1574 def SRDLVersion(self) -> SystemRDLVersion:
1575 if self._srdlVersion is not None:
1576 return self._srdlVersion
1577 elif self._project is not None:
1578 return self._project.SRDLVersion
1579 else:
1580 raise Exception("SRDLVersion was neither set locally nor globally.")
1582 @SRDLVersion.setter
1583 def SRDLVersion(self, value: SystemRDLVersion) -> None:
1584 self._srdlVersion = value
1586 @property
1587 def ExternalVHDLLibraries(self) -> List:
1588 return self._externalVHDLLibraries
1590 def AddFileSet(self, fileSet: FileSet) -> None:
1591 if not isinstance(fileSet, FileSet):
1592 raise ValueError("Parameter 'fileSet' is not of type ProjectModel.FileSet.")
1593 elif fileSet in self._fileSets:
1594 raise Exception("Design already contains this fileset.")
1595 elif fileSet.Name in self._fileSets.keys():
1596 raise Exception(f"Design already contains a fileset named '{fileSet.Name}'.")
1598 self._fileSets[fileSet.Name] = fileSet
1599 fileSet.Design = self
1600 fileSet._parent = self
1602 def AddFileSets(self, fileSets: Iterable[FileSet]) -> None:
1603 for fileSet in fileSets:
1604 self.AddFileSet(fileSet)
1606 @property
1607 def FileSetCount(self) -> int:
1608 """Returns number of file sets excl. sub-filesets."""
1609 return len(self._fileSets)
1611 @property
1612 def TotalFileSetCount(self) -> int:
1613 """Returns number of file sets incl. sub-filesets."""
1614 fileSetCount = len(self._fileSets)
1615 for fileSet in self._fileSets.values():
1616 fileSetCount += fileSet.TotalFileSetCount
1618 return fileSetCount
1620 def AddFile(self, file: File) -> None:
1621 if file.FileSet is None: 1621 ↛ 1624line 1621 didn't jump to line 1624 because the condition on line 1621 was always true
1622 self._defaultFileSet.AddFile(file)
1623 else:
1624 raise ValueError(f"File '{file.Path!s}' is already part of fileset '{file.FileSet.Name}' and can't be assigned via Design to a default fileset.")
1626 def AddFiles(self, files: Iterable[File]) -> None:
1627 for file in files:
1628 self.AddFile(file)
1630 def AddVHDLLibrary(self, vhdlLibrary: VHDLLibrary) -> None:
1631 if vhdlLibrary.Name in self._vhdlLibraries:
1632 if self._vhdlLibraries[vhdlLibrary.Name] is vhdlLibrary:
1633 raise Exception(f"The VHDLLibrary '{vhdlLibrary.Name}' was already added to the design.")
1634 else:
1635 raise Exception(f"A VHDLLibrary with same name ('{vhdlLibrary.Name}') already exists for this design.")
1638 def __len__(self) -> int:
1639 """
1640 Returns number of attributes set on this design.
1642 :returns: The number of attributes set on this design.
1643 """
1644 return len(self._attributes)
1646 def __getitem__(self, key: Type[Attribute]) -> typing_Any:
1647 """Index access for returning attributes on this design.
1649 :param key: The attribute type.
1650 :returns: The attribute's value.
1651 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1652 """
1653 if not issubclass(key, Attribute): 1653 ↛ 1654line 1653 didn't jump to line 1654 because the condition on line 1653 was never true
1654 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1656 try:
1657 return self._attributes[key]
1658 except KeyError:
1659 return key.resolve(self, key)
1661 def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1662 """
1663 Index access for adding or setting attributes on this design.
1665 :param key: The attribute type.
1666 :param value: The attributes value.
1667 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1668 """
1669 if not issubclass(key, Attribute): 1669 ↛ 1670line 1669 didn't jump to line 1670 because the condition on line 1669 was never true
1670 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1672 self._attributes[key] = value
1674 def __delitem__(self, key: Type[Attribute]) -> None:
1675 """
1676 Index access for deleting attributes on this design.
1678 :param key: The attribute type.
1679 """
1680 if not issubclass(key, Attribute): 1680 ↛ 1681line 1680 didn't jump to line 1681 because the condition on line 1680 was never true
1681 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1683 del self._attributes[key]
1685 def __str__(self) -> str:
1686 return self._name
1689@export
1690class Project(metaclass=ExtendedType, slots=True):
1691 """
1692 A :term:`Project` represents a group of designs and the source files therein.
1694 :arg name: The project's name.
1695 :arg rootDirectory: Base-path to the project.
1696 :arg vhdlVersion: Default VHDL version for files in this project, if not specified for the file itself.
1697 :arg verilogVersion: Default Verilog version for files in this project, if not specified for the file itself.
1698 :arg svVersion: Default SystemVerilog version for files in this project, if not specified for the file itself.
1699 """
1701 _name: str
1702 _rootDirectory: pathlib_Path
1703 _designs: Dict[str, Design]
1704 _defaultDesign: Design
1705 _attributes: Dict[Type[Attribute], typing_Any]
1707 _vhdlVersion: VHDLVersion
1708 _verilogVersion: SystemVerilogVersion
1709 _svVersion: SystemVerilogVersion
1710 _srdlVersion: SystemRDLVersion
1712 def __init__(
1713 self,
1714 name: str,
1715 rootDirectory: pathlib_Path = pathlib_Path("."),
1716 vhdlVersion: Nullable[VHDLVersion] = None,
1717 verilogVersion: Nullable[SystemVerilogVersion] = None,
1718 svVersion: Nullable[SystemVerilogVersion] = None
1719 ) -> None:
1720 self._name = name
1721 self._rootDirectory = rootDirectory
1722 self._designs = {}
1723 self._defaultDesign = Design("default", project=self)
1724 self._attributes = {}
1725 self._vhdlVersion = vhdlVersion
1726 self._verilogVersion = verilogVersion
1727 self._svVersion = svVersion
1729 @property
1730 def Name(self) -> str:
1731 """Property setting or returning the project's name."""
1732 return self._name
1734 @property
1735 def RootDirectory(self) -> pathlib_Path:
1736 """Property setting or returning the root directory this project is located in."""
1737 return self._rootDirectory
1739 @RootDirectory.setter
1740 def RootDirectory(self, value: pathlib_Path) -> None:
1741 self._rootDirectory = value
1743 @property
1744 def ResolvedPath(self) -> pathlib_Path:
1745 """Read-only property returning the resolved path of this fileset."""
1746 path = self._rootDirectory.resolve()
1747 if self._rootDirectory.is_absolute():
1748 return path
1749 else:
1750 # WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
1751 return pathlib_Path(path_relpath(path, pathlib_Path.cwd()))
1753 # TODO: return generator with another method
1754 @property
1755 def Designs(self) -> Dict[str, Design]:
1756 return self._designs
1758 @property
1759 def DefaultDesign(self) -> Design:
1760 return self._defaultDesign
1762 def Validate(self) -> None:
1763 """Validate this project."""
1764 if self._name is None or self._name == "": 1764 ↛ 1765line 1764 didn't jump to line 1765 because the condition on line 1764 was never true
1765 raise Exception("Validation: Project has no name.")
1767 if self._rootDirectory is None: 1767 ↛ 1768line 1767 didn't jump to line 1768 because the condition on line 1767 was never true
1768 raise Exception(f"Validation: Project '{self._name}' has no root directory.")
1769 try:
1770 path = self.ResolvedPath
1771 except Exception as ex:
1772 raise Exception(f"Validation: Project '{self._name}' could not compute resolved path.") from ex
1773 if not path.exists(): 1773 ↛ 1774line 1773 didn't jump to line 1774 because the condition on line 1773 was never true
1774 raise Exception(f"Validation: Project '{self._name}'s directory '{path}' does not exist.")
1775 if not path.is_dir(): 1775 ↛ 1776line 1775 didn't jump to line 1776 because the condition on line 1775 was never true
1776 raise Exception(f"Validation: Project '{self._name}'s directory '{path}' is not a directory.")
1778 if len(self._designs) == 0: 1778 ↛ 1779line 1778 didn't jump to line 1779 because the condition on line 1778 was never true
1779 raise Exception(f"Validation: Project '{self._name}' has no design.")
1780 try:
1781 if self._defaultDesign is not self._designs[self._defaultDesign.Name]: 1781 ↛ 1782line 1781 didn't jump to line 1782 because the condition on line 1781 was never true
1782 raise Exception(f"Validation: Project '{self._name}'s default design is the same as listed in designs.")
1783 except KeyError as ex:
1784 raise Exception(f"Validation: Project '{self._name}'s default design is not in list of designs.") from ex
1786 for design in self._designs.values():
1787 design.Validate()
1789 @property
1790 def DesignCount(self) -> int:
1791 """Returns number of designs."""
1792 return len(self._designs)
1794 @property
1795 def VHDLVersion(self) -> VHDLVersion:
1796 # TODO: check for None and return exception
1797 return self._vhdlVersion
1799 @VHDLVersion.setter
1800 def VHDLVersion(self, value: VHDLVersion) -> None:
1801 self._vhdlVersion = value
1803 @property
1804 def VerilogVersion(self) -> SystemVerilogVersion:
1805 # TODO: check for None and return exception
1806 return self._verilogVersion
1808 @VerilogVersion.setter
1809 def VerilogVersion(self, value: SystemVerilogVersion) -> None:
1810 self._verilogVersion = value
1812 @property
1813 def SVVersion(self) -> SystemVerilogVersion:
1814 # TODO: check for None and return exception
1815 return self._svVersion
1817 @SVVersion.setter
1818 def SVVersion(self, value: SystemVerilogVersion) -> None:
1819 self._svVersion = value
1821 @property
1822 def SRDLVersion(self) -> SystemRDLVersion:
1823 # TODO: check for None and return exception
1824 return self._srdlVersion
1826 @SRDLVersion.setter
1827 def SRDLVersion(self, value: SystemRDLVersion) -> None:
1828 self._srdlVersion = value
1830 def __len__(self) -> int:
1831 """
1832 Returns number of attributes set on this project.
1834 :returns: The number of attributes set on this project.
1835 """
1836 return len(self._attributes)
1838 def __getitem__(self, key: Type[Attribute]) -> typing_Any:
1839 """Index access for returning attributes on this project.
1841 :param key: The attribute type.
1842 :returns: The attribute's value.
1843 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1844 """
1845 if not issubclass(key, Attribute): 1845 ↛ 1846line 1845 didn't jump to line 1846 because the condition on line 1845 was never true
1846 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1848 try:
1849 return self._attributes[key]
1850 except KeyError:
1851 return key.resolve(self, key)
1853 def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1854 """
1855 Index access for adding or setting attributes on this project.
1857 :param key: The attribute type.
1858 :param value: The attributes value.
1859 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1860 """
1861 if not issubclass(key, Attribute): 1861 ↛ 1862line 1861 didn't jump to line 1862 because the condition on line 1861 was never true
1862 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1864 self._attributes[key] = value
1866 def __delitem__(self, key: Type[Attribute]) -> None:
1867 """
1868 Index access for deleting attributes on this project.
1870 :param key: The attribute type.
1871 """
1872 if not issubclass(key, Attribute): 1872 ↛ 1873line 1872 didn't jump to line 1873 because the condition on line 1872 was never true
1873 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1875 del self._attributes[key]
1877 def __str__(self) -> str:
1878 return self._name