Coverage for pyEDAA/ProjectModel/__init__.py: 70%
1070 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-18 17:08 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-18 17:08 +0000
1# ==================================================================================================================== #
2# _____ ____ _ _ ____ _ _ __ __ _ _ #
3# _ __ _ _| ____| _ \ / \ / \ | _ \ _ __ ___ (_) ___ ___| |_| \/ | ___ __| | ___| | #
4# | '_ \| | | | _| | | | |/ _ \ / _ \ | |_) | '__/ _ \| |/ _ \/ __| __| |\/| |/ _ \ / _` |/ _ \ | #
5# | |_) | |_| | |___| |_| / ___ \ / ___ \ _| __/| | | (_) | | __/ (__| |_| | | | (_) | (_| | __/ | #
6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)_| |_| \___// |\___|\___|\__|_| |_|\___/ \__,_|\___|_| #
7# |_| |___/ |__/ #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2017-2025 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-2025, Patrick Lehmann, Unai Martinez-Corral"
36__license__ = "Apache License, Version 2.0"
37__version__ = "0.6.0"
38__keywords__ = ["eda project", "model", "abstract", "xilinx", "vivado", "osvvm", "file set", "file group", "test bench", "test harness"]
40from os.path import relpath as path_relpath
41from pathlib import Path as pathlib_Path
42from sys import version_info
43from typing import Dict, Union, Optional as Nullable, List, Iterable, Generator, Tuple, Any as typing_Any, Type, Set, Any
45from pyTooling.Common import getFullyQualifiedName
46from pyTooling.Decorators import export
47from pyTooling.MetaClasses import ExtendedType
48from pyTooling.Graph import Graph, Vertex
49from pySVModel import SystemVerilogVersion
50from pyVHDLModel import VHDLVersion
51from pySystemRDLModel import SystemRDLVersion
54@export
55class Attribute(metaclass=ExtendedType):
56 KEY: str
57 VALUE_TYPE: typing_Any
59 @staticmethod
60 def resolve(obj: typing_Any, key: Type['Attribute']):
61 if isinstance(obj, File):
62 return obj._fileSet[key]
63 elif isinstance(obj, FileSet):
64 return obj._design[key]
65 elif isinstance(obj, Design): 65 ↛ 68line 65 didn't jump to line 68 because the condition on line 65 was always true
66 return obj._project[key]
67 else:
68 raise Exception("Resolution error")
71@export
72class FileType(ExtendedType):
73 """
74 A :term:`meta-class` to construct *FileType* classes.
76 Modifications done by this meta-class:
77 * Register all classes of type :class:`FileType` or derived variants in a class field :attr:`FileType.FileTypes` in this meta-class.
78 """
80 FileTypes: Dict[str, 'FileType'] = {} #: Dictionary of all classes of type :class:`FileType` or derived variants
81 Any: 'FileType'
83 def __init__(cls, name: str, bases: Tuple[type, ...], dictionary: Dict[str, typing_Any], **kwargs):
84 super().__init__(name, bases, dictionary, **kwargs)
85 cls.Any = cls
87 def __new__(cls, className, baseClasses, classMembers: Dict, *args, **kwargs):
88 fileType = super().__new__(cls, className, baseClasses, classMembers, *args, **kwargs)
89 cls.FileTypes[className] = fileType
90 return fileType
92 def __getattr__(cls, item) -> 'FileType':
93 if item[:2] != "__" and item[-2:] != "__":
94 return cls.FileTypes[item]
95 else:
96 return super().__getattribute__(item)
98 def __contains__(cls, item) -> bool:
99 return issubclass(item, cls)
102@export
103class File(metaclass=FileType, slots=True):
104 """
105 A :term:`File` represents a file in a design. This :term:`base-class` is used
106 for all derived file classes.
108 A file can be created standalone and later associated to a fileset, design and
109 project. Or a fileset, design and/or project can be associated immediately
110 while creating a file.
112 :arg path: Relative or absolute path to the file.
113 :arg project: Project the file is associated with.
114 :arg design: Design the file is associated with.
115 :arg fileSet: Fileset the file is associated with.
116 """
118 _path: pathlib_Path
119 _fileType: 'FileType'
120 _project: Nullable['Project']
121 _design: Nullable['Design']
122 _fileSet: Nullable['FileSet']
123 _attributes: Dict[Type[Attribute], typing_Any]
125 def __init__(
126 self,
127 path: pathlib_Path,
128 project: Nullable["Project"] = None,
129 design: Nullable["Design"] = None,
130 fileSet: Nullable["FileSet"] = None
131 ):
132 self._fileType = getattr(FileTypes, self.__class__.__name__)
133 self._path = path
134 if project is not None:
135 self._project = project
136 self._design = design
137 if fileSet is not None: 137 ↛ 138line 137 didn't jump to line 138 because the condition on line 137 was never true
138 self.FileSet = fileSet
139 elif design is not None:
140 self._project = design._project
141 self._design = design
142 self.FileSet = design.DefaultFileSet if fileSet is None else fileSet
143 elif fileSet is not None:
144 design = fileSet._design
145 if design is not None:
146 self._project = design._project
147 else:
148 self._project = None
149 self._design = design
150 self.FileSet = fileSet
151 else:
152 self._project = None
153 self._design = None
154 self._fileSet = None
156 self._attributes = {}
157 self._registerAttributes()
159 def _registerAttributes(self) -> None:
160 pass
162 @property
163 def FileType(self) -> 'FileType':
164 """Read-only property to return the file type of this file."""
165 return self._fileType
167 @property
168 def Path(self) -> pathlib_Path:
169 """
170 Read-only property to access the path to the file.
172 :returns: The file's path.
173 """
174 return self._path
176 # TODO: setter?
178 @property
179 def ResolvedPath(self) -> pathlib_Path:
180 """Read-only property returning the resolved path of this file."""
181 if self._path.is_absolute(): 181 ↛ 182line 181 didn't jump to line 182 because the condition on line 181 was never true
182 return self._path.resolve()
183 elif self._fileSet is not None: 183 ↛ 193line 183 didn't jump to line 193 because the condition on line 183 was always true
184 path = (self._fileSet.ResolvedPath / self._path).resolve()
186 if path.is_absolute(): 186 ↛ 190line 186 didn't jump to line 190 because the condition on line 186 was always true
187 return path
188 else:
189 # WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
190 return pathlib_Path(path_relpath(path, pathlib_Path.cwd()))
191 else:
192 # TODO: message and exception type
193 raise Exception("")
195 @property
196 def Project(self) -> Nullable['Project']:
197 """Property setting or returning the project this file is used in."""
198 return self._project
200 @Project.setter
201 def Project(self, value: 'Project') -> None:
202 self._project = value
204 if self._fileSet is None: 204 ↛ exitline 204 didn't return from function 'Project' because the condition on line 204 was always true
205 self._project.DefaultDesign.DefaultFileSet.AddFile(self)
207 @property
208 def Design(self) -> Nullable['Design']:
209 """Property setting or returning the design this file is used in."""
210 return self._design
212 @Design.setter
213 def Design(self, value: 'Design') -> None:
214 self._design = value
216 if self._fileSet is None: 216 ↛ 219line 216 didn't jump to line 219 because the condition on line 216 was always true
217 self._design.DefaultFileSet.AddFile(self)
219 if self._project is None: 219 ↛ 221line 219 didn't jump to line 221 because the condition on line 219 was always true
220 self._project = value._project
221 elif self._project is not value._project:
222 raise Exception("The design's project is not identical to the already assigned project.")
224 @property
225 def FileSet(self) -> Nullable['FileSet']:
226 """Property setting or returning the fileset this file is used in."""
227 return self._fileSet
229 @FileSet.setter
230 def FileSet(self, value: 'FileSet') -> None:
231 self._fileSet = value
232 value._files.append(self)
234 def Validate(self) -> None:
235 """Validate this file."""
236 if self._path is None: 236 ↛ 237line 236 didn't jump to line 237 because the condition on line 236 was never true
237 raise Exception("Validation: File has no path.")
238 try:
239 path = self.ResolvedPath
240 except Exception as ex:
241 raise Exception(f"Validation: File '{self._path}' could not compute resolved path.") from ex
242 if not path.exists(): 242 ↛ 243line 242 didn't jump to line 243 because the condition on line 242 was never true
243 raise Exception(f"Validation: File '{self._path}' (={path}) does not exist.")
244 if not path.is_file(): 244 ↛ 245line 244 didn't jump to line 245 because the condition on line 244 was never true
245 raise Exception(f"Validation: File '{self._path}' (={path}) is not a file.")
247 if self._fileSet is None: 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}' has no fileset.")
249 if self._design is None: 249 ↛ 250line 249 didn't jump to line 250 because the condition on line 249 was never true
250 raise Exception(f"Validation: File '{self._path}' has no design.")
251 if self._project is None: 251 ↛ 252line 251 didn't jump to line 252 because the condition on line 251 was never true
252 raise Exception(f"Validation: File '{self._path}' has no project.")
254 def __len__(self) -> int:
255 """
256 Returns number of attributes set on this file.
258 :returns: The number of attributes set on this file.
259 """
260 return len(self._attributes)
262 def __getitem__(self, key: Type[Attribute]) -> Any:
263 """Index access for returning attributes on this file.
265 :param key: The attribute type.
266 :returns: The attribute's value.
267 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
268 """
269 if not issubclass(key, Attribute): 269 ↛ 270line 269 didn't jump to line 270 because the condition on line 269 was never true
270 raise TypeError("Parameter 'key' is not an 'Attribute'.")
272 try:
273 return self._attributes[key]
274 except KeyError:
275 try:
276 return key.resolve(self, key)
277 except KeyError:
278 attribute = key()
279 self._attributes[key] = attribute
280 return attribute
282 def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
283 """
284 Index access for adding or setting attributes on this file.
286 :param key: The attribute type.
287 :param value: The attributes value.
288 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
289 """
290 if not issubclass(key, Attribute): 290 ↛ 291line 290 didn't jump to line 291 because the condition on line 290 was never true
291 raise TypeError("Parameter 'key' is not an 'Attribute'.")
293 self._attributes[key] = value
295 def __delitem__(self, key: Type[Attribute]) -> None:
296 """
297 Index access for deleting attributes on this file.
299 :param key: The attribute type.
300 """
301 if not issubclass(key, Attribute): 301 ↛ 302line 301 didn't jump to line 302 because the condition on line 301 was never true
302 raise TypeError("Parameter 'key' is not an 'Attribute'.")
304 del self._attributes[key]
306 def __str__(self) -> str:
307 return f"{self._path}"
310FileTypes = File
313@export
314class HumanReadableContent(metaclass=ExtendedType, mixin=True):
315 """A file type representing human-readable contents."""
318@export
319class XMLContent(HumanReadableContent, mixin=True):
320 """A file type representing XML contents."""
323@export
324class YAMLContent(HumanReadableContent, mixin=True):
325 """A file type representing YAML contents."""
328@export
329class JSONContent(HumanReadableContent, mixin=True):
330 """A file type representing JSON contents."""
333@export
334class INIContent(HumanReadableContent, mixin=True):
335 """A file type representing INI contents."""
338@export
339class TOMLContent(HumanReadableContent, mixin=True):
340 """A file type representing TOML contents."""
343@export
344class TCLContent(HumanReadableContent, mixin=True):
345 """A file type representing content in TCL code."""
348@export
349class SDCContent(TCLContent, mixin=True):
350 """A file type representing contents as Synopsys Design Constraints (SDC)."""
353@export
354class PythonContent(HumanReadableContent, mixin=True):
355 """A file type representing contents as Python source code."""
358@export
359class TextFile(File, HumanReadableContent):
360 """A text file (``*.txt``)."""
363@export
364class LogFile(File, HumanReadableContent):
365 """A log file (``*.log``)."""
368@export
369class XMLFile(File, XMLContent):
370 """An XML file (``*.xml``)."""
373@export
374class SourceFile(File):
375 """Base-class of all source files."""
378@export
379class HDLSourceFile(SourceFile):
380 """Base-class of all HDL source files."""
383@export
384class RDLSourceFile(SourceFile):
385 """Base-class of all RDL source files."""
388@export
389class NetlistFile(SourceFile):
390 """Base-class of all netlist source files."""
393@export
394class EDIFNetlistFile(NetlistFile):
395 """Netlist file in EDIF (Electronic Design Interchange Format)."""
398@export
399class TCLSourceFile(SourceFile, TCLContent):
400 """A TCL source file."""
403@export
404class VHDLSourceFile(HDLSourceFile, HumanReadableContent):
405 """
406 A VHDL source file (of any language version).
408 :arg path: Relative or absolute path to the file.
409 :arg vhdlLibrary: VHDLLibrary this VHDL source file is associated wih.
410 :arg vhdlVersion: VHDLVersion this VHDL source file is associated wih.
411 :arg project: Project the file is associated with.
412 :arg design: Design the file is associated with.
413 :arg fileSet: Fileset the file is associated with.
414 """
416 _vhdlLibrary: Nullable['VHDLLibrary']
417 _vhdlVersion: VHDLVersion
419 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):
420 super().__init__(path, project, design, fileSet)
422 if isinstance(vhdlLibrary, str): 422 ↛ 423line 422 didn't jump to line 423 because the condition on line 422 was never true
423 if design is not None:
424 try:
425 vhdlLibrary = design.VHDLLibraries[vhdlLibrary]
426 except KeyError as ex:
427 raise Exception(f"VHDL library '{vhdlLibrary}' not found in design '{design.Name}'.") from ex
428 elif project is not None:
429 try:
430 vhdlLibrary = project.DefaultDesign.VHDLLibraries[vhdlLibrary]
431 except KeyError as ex:
432 raise Exception(f"VHDL library '{vhdlLibrary}' not found in default design '{project.DefaultDesign.Name}'.") from ex
433 else:
434 raise Exception(f"Can't lookup VHDL library because neither 'project' nor 'design' is given as a parameter.")
435 elif isinstance(vhdlLibrary, VHDLLibrary):
436 self._vhdlLibrary = vhdlLibrary
437 vhdlLibrary.AddFile(self)
438 elif vhdlLibrary is None: 438 ↛ 441line 438 didn't jump to line 441 because the condition on line 438 was always true
439 self._vhdlLibrary = None
440 else:
441 ex = TypeError(f"Parameter 'vhdlLibrary' is neither a 'str' nor 'VHDLibrary'.")
442 if version_info >= (3, 11): # pragma: no cover
443 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibrary)}'.")
444 raise ex
446 self._vhdlVersion = vhdlVersion
448 def Validate(self) -> None:
449 """Validate this VHDL source file."""
450 super().Validate()
452 try:
453 _ = self.VHDLLibrary
454 except Exception as ex:
455 raise Exception(f"Validation: VHDLSourceFile '{self._path}' (={self.ResolvedPath}) has no VHDLLibrary assigned.") from ex
456 try:
457 _ = self.VHDLVersion
458 except Exception as ex:
459 raise Exception(f"Validation: VHDLSourceFile '{self._path}' (={self.ResolvedPath}) has no VHDLVersion assigned.") from ex
461 @property
462 def VHDLLibrary(self) -> 'VHDLLibrary':
463 """Property setting or returning the VHDL library this VHDL source file is used in."""
464 if self._vhdlLibrary is not None:
465 return self._vhdlLibrary
466 elif self._fileSet is not None:
467 return self._fileSet.VHDLLibrary
468 else:
469 raise Exception("VHDLLibrary was neither set locally nor globally.")
471 @VHDLLibrary.setter
472 def VHDLLibrary(self, value: 'VHDLLibrary') -> None:
473 self._vhdlLibrary = value
474 value._files.append(self)
476 @property
477 def VHDLVersion(self) -> VHDLVersion:
478 """Property setting or returning the VHDL version this VHDL source file is used in."""
479 if self._vhdlVersion is not None:
480 return self._vhdlVersion
481 elif self._fileSet is not None:
482 return self._fileSet.VHDLVersion
483 else:
484 raise Exception("VHDLVersion was neither set locally nor globally.")
486 @VHDLVersion.setter
487 def VHDLVersion(self, value: VHDLVersion) -> None:
488 self._vhdlVersion = value
490 def __repr__(self) -> str:
491 return f"<VHDL file: '{self.ResolvedPath}'; lib: '{self.VHDLLibrary}'; version: {self.VHDLVersion}>"
494class VerilogMixIn(metaclass=ExtendedType, mixin=True):
495 @property
496 def VerilogVersion(self) -> SystemVerilogVersion:
497 """Property setting or returning the Verilog version this Verilog source file is used in."""
498 if self._version is not None:
499 return self._version
500 elif self._fileSet is not None:
501 return self._fileSet.VerilogVersion
502 else:
503 raise Exception("VerilogVersion was neither set locally nor globally.")
505 @VerilogVersion.setter
506 def VerilogVersion(self, value: SystemVerilogVersion) -> None:
507 self._version = value
510class SystemVerilogMixIn(metaclass=ExtendedType, mixin=True):
511 @property
512 def SVVersion(self) -> SystemVerilogVersion:
513 """Property setting or returning the SystemVerilog version this SystemVerilog source file is used in."""
514 if self._version is not None:
515 return self._version
516 elif self._fileSet is not None:
517 return self._fileSet.SVVersion
518 else:
519 raise Exception("SVVersion was neither set locally nor globally.")
521 @SVVersion.setter
522 def SVVersion(self, value: SystemVerilogVersion) -> None:
523 self._version = value
526@export
527class VerilogBaseFile(HDLSourceFile, HumanReadableContent):
528 _version: SystemVerilogVersion
530 def __init__(self, path: pathlib_Path, version: Nullable[SystemVerilogVersion] = None, project: Nullable["Project"] = None, design: Nullable["Design"] = None, fileSet: Nullable["FileSet"] = None):
531 super().__init__(path, project, design, fileSet)
533 self._version = version
536@export
537class VerilogSourceFile(VerilogBaseFile, VerilogMixIn):
538 """A Verilog source file (of any language version)."""
541@export
542class VerilogHeaderFile(VerilogBaseFile, VerilogMixIn):
543 """A Verilog header file (of any language version)."""
546@export
547class SystemVerilogBaseFile(VerilogBaseFile):
548 ...
551@export
552class SystemVerilogSourceFile(SystemVerilogBaseFile, SystemVerilogMixIn):
553 """A SystemVerilog source file (of any language version)."""
556@export
557class SystemVerilogHeaderFile(SystemVerilogBaseFile, SystemVerilogMixIn):
558 """A SystemVerilog header file (of any language version)."""
561@export
562class SystemRDLSourceFile(RDLSourceFile, HumanReadableContent):
563 """A SystemRDL source file (of any language version)."""
565 _srdlVersion: SystemRDLVersion
567 def __init__(self, path: pathlib_Path, srdlVersion: Nullable[SystemRDLVersion] = None, project: Nullable["Project"] = None, design: Nullable["Design"] = None, fileSet: Nullable["FileSet"] = None):
568 super().__init__(path, project, design, fileSet)
570 self._srdlVersion = srdlVersion
572 @property
573 def SystemRDLVersion(self) -> SystemRDLVersion:
574 """Property setting or returning the SystemRDL version this SystemRDL source file is used in."""
575 if self._srdlVersion is not None:
576 return self._srdlVersion
577 elif self._fileSet is not None:
578 return self._fileSet.SRDLVersion
579 else:
580 raise Exception("SRDLVersion was neither set locally nor globally.")
582 @SystemRDLVersion.setter
583 def SystemRDLVersion(self, value: SystemRDLVersion) -> None:
584 self._srdlVersion = value
587@export
588class PythonSourceFile(SourceFile, PythonContent):
589 """A Python source file."""
592# TODO: move to a Cocotb module
593@export
594class CocotbPythonFile(PythonSourceFile):
595 """A Python source file used by Cocotb."""
598@export
599class ConstraintFile(File, HumanReadableContent):
600 """Base-class of all constraint files."""
603@export
604class ProjectFile(File):
605 """Base-class of all tool-specific project files."""
608@export
609class CSourceFile(SourceFile):
610 """Base-class of all ANSI-C source files."""
613@export
614class CppSourceFile(SourceFile):
615 """Base-class of all ANSI-C++ source files."""
618@export
619class SettingFile(File):
620 """Base-class of all tool-specific setting files."""
623@export
624class SimulationAnalysisFile(File):
625 """Base-class of all tool-specific analysis files."""
628@export
629class SimulationElaborationFile(File):
630 """Base-class of all tool-specific elaboration files."""
633@export
634class SimulationStartFile(File):
635 """Base-class of all tool-specific simulation start-up files."""
638@export
639class SimulationRunFile(File):
640 """Base-class of all tool-specific simulation run (execution) files."""
643@export
644class WaveformConfigFile(File):
645 """Base-class of all tool-specific waveform configuration files."""
648@export
649class WaveformDatabaseFile(File):
650 """Base-class of all tool-specific waveform database files."""
653@export
654class WaveformExchangeFile(File):
655 """Base-class of all tool-independent waveform exchange files."""
658@export
659class FileSet(metaclass=ExtendedType, slots=True):
660 """
661 A :term:`FileSet` represents a group of files. Filesets can have sub-filesets.
663 The order of insertion is preserved. A fileset can be created standalone and
664 later associated to another fileset, design and/or project. Or a fileset,
665 design and/or project can be associated immediately while creating the
666 fileset.
668 :arg name: Name of this fileset.
669 :arg topLevel: Name of the fileset's toplevel.
670 :arg directory: Path of this fileset (absolute or relative to a parent fileset or design).
671 :arg project: Project the file is associated with.
672 :arg design: Design the file is associated with.
673 :arg parent: Parent fileset if this fileset is nested.
674 :arg vhdlLibrary: Default VHDL library for files in this fileset, if not specified for the file itself.
675 :arg vhdlVersion: Default VHDL version for files in this fileset, if not specified for the file itself.
676 :arg verilogVersion: Default Verilog version for files in this fileset, if not specified for the file itself.
677 :arg svVersion: Default SystemVerilog version for files in this fileset, if not specified for the file itself.
678 :arg srdlVersion: Default SystemRDL version for files in this fileset, if not specified for the file itself.
679 """
681 _name: str
682 _topLevel: Nullable[str]
683 _project: Nullable['Project']
684 _design: Nullable['Design']
685 _directory: pathlib_Path
686 _parent: Nullable['FileSet']
687 _fileSets: Dict[str, 'FileSet']
688 _files: List[File]
689 _set: Set
690 _attributes: Dict[Type[Attribute], typing_Any]
691 _vhdlLibraries: Dict[str, 'VHDLLibrary']
692 _vhdlLibrary: 'VHDLLibrary'
693 _vhdlVersion: VHDLVersion
694 _verilogVersion: SystemVerilogVersion
695 _svVersion: SystemVerilogVersion
696 _srdlVersion: SystemRDLVersion
698 def __init__(
699 self,
700 name: str,
701 topLevel: Nullable[str] = None,
702 directory: pathlib_Path = pathlib_Path("."),
703 project: Nullable["Project"] = None,
704 design: Nullable["Design"] = None,
705 parent: Nullable['FileSet'] = None,
706 vhdlLibrary: Union[str, 'VHDLLibrary'] = None,
707 vhdlVersion: Nullable[VHDLVersion] = None,
708 verilogVersion: Nullable[SystemVerilogVersion] = None,
709 svVersion: Nullable[SystemVerilogVersion] = None,
710 srdlVersion: Nullable[SystemRDLVersion] = None
711 ):
712 self._name = name
713 self._topLevel = topLevel
714 if project is not None:
715 self._project = project
716 self._design = design if design is not None else project.DefaultDesign
718 elif design is not None:
719 self._project = design._project
720 self._design = design
721 else:
722 self._project = None
723 self._design = None
724 self._directory = directory
725 self._parent = parent
726 self._fileSets = {}
727 self._files = []
728 self._set = set()
730 if design is not None:
731 design._fileSets[name] = self
733 self._attributes = {}
734 self._vhdlLibraries = {}
736 # TODO: handle if vhdlLibrary is a string
737 self._vhdlLibrary = vhdlLibrary
738 self._vhdlVersion = vhdlVersion
739 self._verilogVersion = verilogVersion
740 self._svVersion = svVersion
741 self._srdlVersion = srdlVersion
743 @property
744 def Name(self) -> str:
745 """Property setting or returning the fileset's name."""
746 return self._name
748 @Name.setter
749 def Name(self, value: str) -> None:
750 self._name = value
752 @property
753 def TopLevel(self) -> str:
754 """Property setting or returning the fileset's toplevel."""
755 return self._topLevel
757 @TopLevel.setter
758 def TopLevel(self, value: str) -> None:
759 self._topLevel = value
761 @property
762 def Project(self) -> Nullable['Project']:
763 """Property setting or returning the project this fileset is used in."""
764 return self._project
766 @Project.setter
767 def Project(self, value: 'Project') -> None:
768 self._project = value
770 @property
771 def Design(self) -> Nullable['Design']:
772 """Property setting or returning the design this fileset is used in."""
773 if self._design is not None:
774 return self._design
775 elif self._parent is not None: 775 ↛ 776line 775 didn't jump to line 776 because the condition on line 775 was never true
776 return self._parent.Design
777 else:
778 return None
779 # TODO: raise exception instead
780 # QUESTION: how to handle if design and parent is set?
782 @Design.setter
783 def Design(self, value: 'Design') -> None:
784 self._design = value
785 if self._project is None: 785 ↛ 787line 785 didn't jump to line 787 because the condition on line 785 was always true
786 self._project = value._project
787 elif self._project is not value._project:
788 raise Exception("The design's project is not identical to the already assigned project.")
790 @property
791 def Directory(self) -> pathlib_Path:
792 """Property setting or returning the directory this fileset is located in."""
793 return self._directory
795 @Directory.setter
796 def Directory(self, value: pathlib_Path) -> None:
797 self._directory = value
799 @property
800 def ResolvedPath(self) -> pathlib_Path:
801 """Read-only property returning the resolved path of this fileset."""
802 if self._directory.is_absolute(): 802 ↛ 803line 802 didn't jump to line 803 because the condition on line 802 was never true
803 return self._directory.resolve()
804 else:
805 if self._parent is not None: 805 ↛ 806line 805 didn't jump to line 806 because the condition on line 805 was never true
806 directory = self._parent.ResolvedPath
807 elif self._design is not None: 807 ↛ 809line 807 didn't jump to line 809 because the condition on line 807 was always true
808 directory = self._design.ResolvedPath
809 elif self._project is not None:
810 directory = self._project.ResolvedPath
811 else:
812 # TODO: message and exception type
813 raise Exception("")
815 directory = (directory / self._directory).resolve()
816 if directory.is_absolute(): 816 ↛ 820line 816 didn't jump to line 820 because the condition on line 816 was always true
817 return directory
818 else:
819 # WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
820 return pathlib_Path(path_relpath(directory, pathlib_Path.cwd()))
822 @property
823 def Parent(self) -> Nullable['FileSet']:
824 """Property setting or returning the parent fileset this fileset is used in."""
825 return self._parent
827 @Parent.setter
828 def Parent(self, value: 'FileSet') -> None:
829 self._parent = value
830 value._fileSets[self._name] = self
831 # TODO: check it it already exists
832 # QUESTION: make an Add fileset method?
834 @property
835 def FileSets(self) -> Dict[str, 'FileSet']:
836 """Read-only property returning the dictionary of sub-filesets."""
837 return self._fileSets
839 def Files(self, fileType: FileType = FileTypes.Any, fileSet: Union[bool, str, 'FileSet'] = None) -> Generator[File, None, None]:
840 """
841 Method returning the files of this fileset.
843 :arg fileType: A filter for file types. Default: ``Any``.
844 :arg fileSet: Specifies how to handle sub-filesets.
845 """
846 if fileSet is False: 846 ↛ 847line 846 didn't jump to line 847 because the condition on line 846 was never true
847 for file in self._files:
848 if file.FileType in fileType:
849 yield file
850 elif fileSet is None: 850 ↛ 858line 850 didn't jump to line 858 because the condition on line 850 was always true
851 for fileSet in self._fileSets.values(): 851 ↛ 852line 851 didn't jump to line 852 because the loop on line 851 never started
852 for file in fileSet.Files(fileType):
853 yield file
854 for file in self._files:
855 if file.FileType in fileType:
856 yield file
857 else:
858 if isinstance(fileSet, str):
859 fileSetName = fileSet
860 try:
861 fileSet = self._fileSets[fileSetName]
862 except KeyError as ex:
863 raise Exception(f"Fileset {fileSetName} not bound to fileset {self.Name}.") from ex
864 elif not isinstance(fileSet, FileSet):
865 raise TypeError("Parameter 'fileSet' is not of type 'str' or 'FileSet' nor value 'None'.")
867 for file in fileSet.Files(fileType):
868 yield file
870 def AddFileSet(self, fileSet: "FileSet") -> None:
871 """
872 Method to add a single sub-fileset to this fileset.
874 :arg fileSet: A fileset to add to this fileset as sub-fileset.
875 """
876 if not isinstance(fileSet, FileSet): 876 ↛ 877line 876 didn't jump to line 877 because the condition on line 876 was never true
877 raise ValueError("Parameter 'fileSet' is not of type ProjectModel.FileSet.")
878 elif fileSet in self._fileSets: 878 ↛ 879line 878 didn't jump to line 879 because the condition on line 878 was never true
879 raise Exception("Sub-fileset already contains this fileset.")
880 elif fileSet.Name in self._fileSets.keys(): 880 ↛ 881line 880 didn't jump to line 881 because the condition on line 880 was never true
881 raise Exception(f"Fileset already contains a sub-fileset named '{fileSet.Name}'.")
883 self._fileSets[fileSet.Name] = fileSet
884 fileSet._parent = self
886 def AddFileSets(self, fileSets: Iterable["FileSet"]) -> None:
887 """
888 Method to add a multiple sub-filesets to this fileset.
890 :arg fileSets: An iterable of filesets to add each to the fileset.
891 """
892 for fileSet in fileSets:
893 self.AddFileSet(fileSet)
895 @property
896 def FileSetCount(self) -> int:
897 """Returns number of file sets excl. sub-filesets."""
898 return len(self._fileSets)
900 @property
901 def TotalFileSetCount(self) -> int:
902 """Returns number of file sets incl. sub-filesets."""
903 fileSetCount = len(self._fileSets)
904 for fileSet in self._fileSets.values():
905 fileSetCount += fileSet.TotalFileSetCount
907 return fileSetCount
909 def AddFile(self, file: File) -> None:
910 """
911 Method to add a single file to this fileset.
913 :arg file: A file to add to this fileset.
914 """
915 if not isinstance(file, File):
916 raise TypeError("Parameter 'file' is not of type ProjectModel.File.")
917 elif file._fileSet is not None:
918 ex = ValueError(f"File '{file.Path!s}' is already part of fileset '{file.FileSet.Name}'.")
919 if version_info >= (3, 11): # pragma: no cover
920 ex.add_note(f"A file can't be assigned to another fileset.")
921 raise ex
922 elif file in self._set: 922 ↛ 923line 922 didn't jump to line 923 because the condition on line 922 was never true
923 ex = ValueError(f"File '{file.Path!s}' is already part of this fileset.")
924 if version_info >= (3, 11): # pragma: no cover
925 ex.add_note(f"A file can't be added twice to a fileset.")
926 raise ex
928 self._files.append(file)
929 self._set.add(file)
930 file._fileSet = self
932 def AddFiles(self, files: Iterable[File]) -> None:
933 """
934 Method to add a multiple files to this fileset.
936 :arg files: An iterable of files to add each to the fileset.
937 """
938 for file in files:
939 self.AddFile(file)
941 @property
942 def FileCount(self) -> int:
943 """Returns number of files excl. sub-filesets."""
944 return len(self._files)
946 @property
947 def TotalFileCount(self) -> int:
948 """Returns number of files incl. the files in sub-filesets."""
949 fileCount = len(self._files)
950 for fileSet in self._fileSets.values():
951 fileCount += fileSet.FileCount
953 return fileCount
955 def Validate(self) -> None:
956 """Validate this fileset."""
957 if self._name is None or self._name == "": 957 ↛ 958line 957 didn't jump to line 958 because the condition on line 957 was never true
958 raise Exception("Validation: FileSet has no name.")
960 if self._directory is None: 960 ↛ 961line 960 didn't jump to line 961 because the condition on line 960 was never true
961 raise Exception(f"Validation: FileSet '{self._name}' has no directory.")
962 try:
963 path = self.ResolvedPath
964 except Exception as ex:
965 raise Exception(f"Validation: FileSet '{self._name}' could not compute resolved path.") from ex
966 if not path.exists(): 966 ↛ 967line 966 didn't jump to line 967 because the condition on line 966 was never true
967 raise Exception(f"Validation: FileSet '{self._name}'s directory '{path}' does not exist.")
968 if not path.is_dir(): 968 ↛ 969line 968 didn't jump to line 969 because the condition on line 968 was never true
969 raise Exception(f"Validation: FileSet '{self._name}'s directory '{path}' is not a directory.")
971 if self._design is None: 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._directory}' has no design.")
973 if self._project is None: 973 ↛ 974line 973 didn't jump to line 974 because the condition on line 973 was never true
974 raise Exception(f"Validation: FileSet '{self._directory}' has no project.")
976 for fileSet in self._fileSets.values(): 976 ↛ 977line 976 didn't jump to line 977 because the loop on line 976 never started
977 fileSet.Validate()
978 for file in self._files: 978 ↛ 979line 978 didn't jump to line 979 because the loop on line 978 never started
979 file.Validate()
981 def GetOrCreateVHDLLibrary(self, name) -> 'VHDLLibrary':
982 if name in self._vhdlLibraries:
983 return self._vhdlLibraries[name]
984 elif name in self._design._vhdlLibraries:
985 library = self._design._vhdlLibraries[name]
986 self._vhdlLibraries[name] = library
987 return library
988 else:
989 library = VHDLLibrary(name, design=self._design, vhdlVersion=self._vhdlVersion)
990 self._vhdlLibraries[name] = library
991 return library
993 @property
994 def VHDLLibrary(self) -> 'VHDLLibrary':
995 """Property setting or returning the VHDL library of this fileset."""
996 if self._vhdlLibrary is not None:
997 return self._vhdlLibrary
998 elif self._parent is not None: 998 ↛ 1000line 998 didn't jump to line 1000 because the condition on line 998 was always true
999 return self._parent.VHDLLibrary
1000 elif self._design is not None:
1001 return self._design.VHDLLibrary
1002 else:
1003 raise Exception("VHDLLibrary was neither set locally nor globally.")
1005 @VHDLLibrary.setter
1006 def VHDLLibrary(self, value: 'VHDLLibrary') -> None:
1007 self._vhdlLibrary = value
1009 @property
1010 def VHDLVersion(self) -> VHDLVersion:
1011 """Property setting or returning the VHDL version of this fileset."""
1012 if self._vhdlVersion is not None:
1013 return self._vhdlVersion
1014 elif self._parent is not None:
1015 return self._parent.VHDLVersion
1016 elif self._design is not None: 1016 ↛ 1019line 1016 didn't jump to line 1019 because the condition on line 1016 was always true
1017 return self._design.VHDLVersion
1018 else:
1019 raise Exception("VHDLVersion was neither set locally nor globally.")
1021 @VHDLVersion.setter
1022 def VHDLVersion(self, value: VHDLVersion) -> None:
1023 self._vhdlVersion = value
1025 @property
1026 def VerilogVersion(self) -> SystemVerilogVersion:
1027 """Property setting or returning the Verilog version of this fileset."""
1028 if self._verilogVersion is not None:
1029 return self._verilogVersion
1030 elif self._parent is not None:
1031 return self._parent.VerilogVersion
1032 elif self._design is not None: 1032 ↛ 1035line 1032 didn't jump to line 1035 because the condition on line 1032 was always true
1033 return self._design.VerilogVersion
1034 else:
1035 raise Exception("VerilogVersion was neither set locally nor globally.")
1037 @VerilogVersion.setter
1038 def VerilogVersion(self, value: SystemVerilogVersion) -> None:
1039 self._verilogVersion = value
1041 @property
1042 def SVVersion(self) -> SystemVerilogVersion:
1043 """Property setting or returning the SystemVerilog version of this fileset."""
1044 if self._svVersion is not None:
1045 return self._svVersion
1046 elif self._parent is not None:
1047 return self._parent.SVVersion
1048 elif self._design is not None: 1048 ↛ 1051line 1048 didn't jump to line 1051 because the condition on line 1048 was always true
1049 return self._design.SVVersion
1050 else:
1051 raise Exception("SVVersion was neither set locally nor globally.")
1053 @SVVersion.setter
1054 def SVVersion(self, value: SystemVerilogVersion) -> None:
1055 self._svVersion = value
1057 @property
1058 def SRDLVersion(self) -> SystemRDLVersion:
1059 if self._srdlVersion is not None:
1060 return self._srdlVersion
1061 elif self._parent is not None:
1062 return self._parent.SRDLVersion
1063 elif self._design is not None:
1064 return self._design.SRDLVersion
1065 else:
1066 raise Exception("SRDLVersion was neither set locally nor globally.")
1068 @SRDLVersion.setter
1069 def SRDLVersion(self, value: SystemRDLVersion) -> None:
1070 self._srdlVersion = value
1072 def __len__(self) -> int:
1073 """
1074 Returns number of attributes set on this fileset.
1076 :returns: The number of attributes set on this fileset.
1077 """
1078 return len(self._attributes)
1080 def __getitem__(self, key: Type[Attribute]) -> Any:
1081 """Index access for returning attributes on this fileset.
1083 :param key: The attribute type.
1084 :returns: The attribute's value.
1085 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1086 """
1087 if not issubclass(key, Attribute): 1087 ↛ 1088line 1087 didn't jump to line 1088 because the condition on line 1087 was never true
1088 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1090 try:
1091 return self._attributes[key]
1092 except KeyError:
1093 return key.resolve(self, key)
1095 def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1096 """
1097 Index access for adding or setting attributes on this fileset.
1099 :param key: The attribute type.
1100 :param value: The attributes value.
1101 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1102 """
1103 if not issubclass(key, Attribute): 1103 ↛ 1104line 1103 didn't jump to line 1104 because the condition on line 1103 was never true
1104 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1106 self._attributes[key] = value
1108 def __delitem__(self, key: Type[Attribute]) -> None:
1109 """
1110 Index access for deleting attributes on this fileset.
1112 :param key: The attribute type.
1113 """
1114 if not issubclass(key, Attribute): 1114 ↛ 1115line 1114 didn't jump to line 1115 because the condition on line 1114 was never true
1115 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1117 del self._attributes[key]
1119 def __str__(self) -> str:
1120 """Returns the fileset's name."""
1121 return self._name
1124@export
1125class VHDLLibrary(metaclass=ExtendedType, slots=True):
1126 """
1127 A :term:`VHDLLibrary` represents a group of VHDL source files compiled into the same VHDL library.
1129 :arg name: The VHDL libraries' name.
1130 :arg project: Project the VHDL library is associated with.
1131 :arg design: Design the VHDL library is associated with.
1132 :arg vhdlVersion: Default VHDL version for files in this VHDL library, if not specified for the file itself.
1133 """
1135 _name: str
1136 _project: Nullable['Project']
1137 _design: Nullable['Design']
1138 _files: List[File]
1139 _vhdlVersion: VHDLVersion
1141 _dependencyNode: Vertex
1143 def __init__(
1144 self,
1145 name: str,
1146 project: Nullable["Project"] = None,
1147 design: Nullable["Design"] = None,
1148 vhdlVersion: Nullable[VHDLVersion] = None
1149 ):
1150 self._name = name
1151 if project is not None:
1152 self._project = project
1153 self._design = project._defaultDesign if design is None else design
1154 self._dependencyNode = Vertex(value=self, graph=self._design._vhdlLibraryDependencyGraph)
1156 if name in self._design._vhdlLibraries: 1156 ↛ 1157line 1156 didn't jump to line 1157 because the condition on line 1156 was never true
1157 raise Exception(f"Library '{name}' already in design '{self._design.Name}'.")
1158 else:
1159 self._design._vhdlLibraries[name] = self
1161 elif design is not None:
1162 self._project = design._project
1163 self._design = design
1164 self._dependencyNode = Vertex(value=self, graph=design._vhdlLibraryDependencyGraph)
1166 if name in design._vhdlLibraries: 1166 ↛ 1167line 1166 didn't jump to line 1167 because the condition on line 1166 was never true
1167 raise Exception(f"Library '{name}' already in design '{design.Name}'.")
1168 else:
1169 design._vhdlLibraries[name] = self
1171 else:
1172 self._project = None
1173 self._design = None
1174 self._dependencyNode = None
1176 self._files = []
1177 self._vhdlVersion = vhdlVersion
1179 @property
1180 def Name(self) -> str:
1181 return self._name
1183 @property
1184 def Project(self) -> Nullable['Project']:
1185 """Property setting or returning the project this VHDL library is used in."""
1186 return self._project
1188 @Project.setter
1189 def Project(self, value: 'Project') -> None:
1190 if not isinstance(value, Project): 1190 ↛ 1191line 1190 didn't jump to line 1191 because the condition on line 1190 was never true
1191 raise TypeError("Parameter 'value' is not of type 'Project'.")
1193 if value is None: 1193 ↛ 1195line 1193 didn't jump to line 1195 because the condition on line 1193 was never true
1194 # TODO: unlink VHDLLibrary from project
1195 self._project = None
1196 else:
1197 self._project = value
1198 if self._design is None: 1198 ↛ exitline 1198 didn't return from function 'Project' because the condition on line 1198 was always true
1199 self._design = value._defaultDesign
1201 @property
1202 def Design(self) -> Nullable['Design']:
1203 """Property setting or returning the design this VHDL library is used in."""
1204 return self._design
1206 @Design.setter
1207 def Design(self, value: 'Design') -> None:
1208 if not isinstance(value, Design):
1209 raise TypeError("Parameter 'value' is not of type 'Design'.")
1211 if value is None:
1212 # TODO: unlink VHDLLibrary from design
1213 self._design = None
1214 else:
1215 if self._design is None:
1216 self._design = value
1217 self._dependencyNode = Vertex(value=self, graph=self._design._vhdlLibraryDependencyGraph)
1218 elif self._design is not value:
1219 # TODO: move VHDLLibrary to other design
1220 # TODO: create new vertex in dependency graph and remove vertex from old graph
1221 self._design = value
1222 else:
1223 pass
1225 if self._project is None:
1226 self._project = value._project
1227 elif self._project is not value._project:
1228 raise Exception("The design's project is not identical to the already assigned project.")
1230 @property
1231 def Files(self) -> Generator[File, None, None]:
1232 """Read-only property to return all files in this VHDL library."""
1233 for file in self._files:
1234 yield file
1236 @property
1237 def VHDLVersion(self) -> VHDLVersion:
1238 """Property setting or returning the VHDL version of this VHDL library."""
1239 if self._vhdlVersion is not None:
1240 return self._vhdlVersion
1241 elif self._design is not None: 1241 ↛ 1244line 1241 didn't jump to line 1244 because the condition on line 1241 was always true
1242 return self._design.VHDLVersion
1243 else:
1244 raise Exception("VHDLVersion is not set on VHDLLibrary nor parent object.")
1246 @VHDLVersion.setter
1247 def VHDLVersion(self, value: VHDLVersion) -> None:
1248 self._vhdlVersion = value
1250 def AddDependency(self, library: 'VHDLLibrary') -> None:
1251 library.parent = self
1253 def AddFile(self, vhdlFile: VHDLSourceFile) -> None:
1254 if not isinstance(vhdlFile, VHDLSourceFile): 1254 ↛ 1255line 1254 didn't jump to line 1255 because the condition on line 1254 was never true
1255 ex = TypeError(f"Parameter 'vhdlFile' is not a 'VHDLSourceFile'.")
1256 if version_info >= (3, 11): # pragma: no cover
1257 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlFile)}'.")
1258 raise ex
1260 self._files.append(vhdlFile)
1262 def AddFiles(self, vhdlFiles: Iterable[VHDLSourceFile]) -> None:
1263 for vhdlFile in vhdlFiles:
1264 if not isinstance(vhdlFile, VHDLSourceFile):
1265 raise TypeError(f"Item '{vhdlFile}' in parameter 'vhdlFiles' is not a 'VHDLSourceFile'.")
1267 self._files.append(vhdlFile)
1269 @property
1270 def FileCount(self) -> int:
1271 """Returns number of files."""
1272 return len(self._files)
1274 def __len__(self) -> int:
1275 """
1276 Returns number of attributes set on this VHDL library.
1278 :returns: The number of attributes set on this VHDL library.
1279 """
1280 return len(self._attributes)
1282 def __getitem__(self, key: Type[Attribute]) -> Any:
1283 """Index access for returning attributes on this VHDL library.
1285 :param key: The attribute type.
1286 :returns: The attribute's value.
1287 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1288 """
1289 if not issubclass(key, Attribute):
1290 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1292 try:
1293 return self._attributes[key]
1294 except KeyError:
1295 return key.resolve(self, key)
1297 def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1298 """
1299 Index access for adding or setting attributes on this VHDL library.
1301 :param key: The attribute type.
1302 :param value: The attributes value.
1303 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1304 """
1305 if not issubclass(key, Attribute):
1306 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1308 self._attributes[key] = value
1310 def __delitem__(self, key: Type[Attribute]) -> None:
1311 """
1312 Index access for deleting attributes on this VHDL library.
1314 :param key: The attribute type.
1315 """
1316 if not issubclass(key, Attribute):
1317 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1319 del self._attributes[key]
1321 def __str__(self) -> str:
1322 """Returns the VHDL library's name."""
1323 return self._name
1326@export
1327class Design(metaclass=ExtendedType, slots=True):
1328 """
1329 A :term:`Design` represents a group of filesets and the source files therein.
1331 Each design contains at least one fileset - the :term:`default fileset`. For
1332 designs with VHDL source files, a independent `VHDLLibraries` overlay structure
1333 exists.
1335 :arg name: The design's name.
1336 :arg topLevel: Name of the design's toplevel.
1337 :arg directory: Path of this design (absolute or relative to the project).
1338 :arg project: Project the design is associated with.
1339 :arg vhdlVersion: Default VHDL version for files in this design, if not specified for the file itself.
1340 :arg verilogVersion: Default Verilog version for files in this design, if not specified for the file itself.
1341 :arg svVersion: Default SystemVerilog version for files in this design, if not specified for the file itself.
1342 :arg srdlVersion: Default SystemRDL version for files in this fileset, if not specified for the file itself.
1343 """
1345 _name: str
1346 _topLevel: Nullable[str]
1347 _project: Nullable['Project']
1348 _directory: pathlib_Path
1349 _fileSets: Dict[str, FileSet]
1350 _defaultFileSet: Nullable[FileSet]
1351 _attributes: Dict[Type[Attribute], typing_Any]
1353 _vhdlLibraries: Dict[str, VHDLLibrary]
1354 _vhdlVersion: VHDLVersion
1355 _verilogVersion: SystemVerilogVersion
1356 _svVersion: SystemVerilogVersion
1357 _srdlVersion: SystemRDLVersion
1358 _externalVHDLLibraries: List
1360 _vhdlLibraryDependencyGraph: Graph
1361 _fileDependencyGraph: Graph
1363 def __init__(
1364 self,
1365 name: str,
1366 topLevel: Nullable[str] = None,
1367 directory: pathlib_Path = pathlib_Path("."),
1368 project: Nullable["Project"] = None,
1369 vhdlVersion: Nullable[VHDLVersion] = None,
1370 verilogVersion: Nullable[SystemVerilogVersion] = None,
1371 svVersion: Nullable[SystemVerilogVersion] = None,
1372 srdlVersion: Nullable[SystemRDLVersion] = None
1373 ):
1374 self._name = name
1375 self._topLevel = topLevel
1376 self._project = project
1377 if project is not None:
1378 project._designs[name] = self
1379 self._directory = directory
1380 self._fileSets = {}
1381 self._defaultFileSet = FileSet("default", project=project, design=self)
1382 self._attributes = {}
1383 self._vhdlLibraries = {}
1384 self._vhdlVersion = vhdlVersion
1385 self._verilogVersion = verilogVersion
1386 self._svVersion = svVersion
1387 self._srdlVersion = srdlVersion
1388 self._externalVHDLLibraries = []
1390 self._vhdlLibraryDependencyGraph = Graph()
1391 self._fileDependencyGraph = Graph()
1393 @property
1394 def Name(self) -> str:
1395 """Property setting or returning the design's name."""
1396 return self._name
1398 @Name.setter
1399 def Name(self, value: str) -> None:
1400 self._name = value
1402 @property
1403 def TopLevel(self) -> str:
1404 """Property setting or returning the fileset's toplevel."""
1405 return self._topLevel
1407 @TopLevel.setter
1408 def TopLevel(self, value: str) -> None:
1409 self._topLevel = value
1411 @property
1412 def Project(self) -> Nullable['Project']:
1413 """Property setting or returning the project this design is used in."""
1414 return self._project
1416 @Project.setter
1417 def Project(self, value: 'Project') -> None:
1418 self._project = value
1420 @property
1421 def Directory(self) -> pathlib_Path:
1422 """Property setting or returning the directory this design is located in."""
1423 return self._directory
1425 @Directory.setter
1426 def Directory(self, value: pathlib_Path) -> None:
1427 self._directory = value
1429 @property
1430 def ResolvedPath(self) -> pathlib_Path:
1431 """Read-only property returning the resolved path of this fileset."""
1432 if self._directory.is_absolute(): 1432 ↛ 1433line 1432 didn't jump to line 1433 because the condition on line 1432 was never true
1433 return self._directory.resolve()
1434 elif self._project is not None: 1434 ↛ 1444line 1434 didn't jump to line 1444 because the condition on line 1434 was always true
1435 path = (self._project.ResolvedPath / self._directory).resolve()
1437 if path.is_absolute(): 1437 ↛ 1441line 1437 didn't jump to line 1441 because the condition on line 1437 was always true
1438 return path
1439 else:
1440 # WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
1441 return pathlib_Path(path_relpath(path, pathlib_Path.cwd()))
1442 else:
1443 # TODO: message and exception type
1444 raise Exception("")
1446 @property
1447 def DefaultFileSet(self) -> FileSet:
1448 """Property setting or returning the default fileset of this design."""
1449 return self._defaultFileSet
1451 @DefaultFileSet.setter
1452 def DefaultFileSet(self, value: Union[str, FileSet]) -> None:
1453 if isinstance(value, str):
1454 if value not in self._fileSets.keys():
1455 raise Exception(f"Fileset '{value}' is not in this design.")
1457 self._defaultFileSet = self._fileSets[value]
1458 elif isinstance(value, FileSet):
1459 if value not in self.FileSets:
1460 raise Exception(f"Fileset '{value}' is not associated to this design.")
1462 self._defaultFileSet = value
1463 else:
1464 raise ValueError("Unsupported parameter type for 'value'.")
1466 # TODO: return generator with another method
1467 @property
1468 def FileSets(self) -> Dict[str, FileSet]:
1469 """Read-only property returning the dictionary of filesets."""
1470 return self._fileSets
1472 def Files(self, fileType: FileType = FileTypes.Any, fileSet: Union[str, FileSet] = None) -> Generator[File, None, None]:
1473 """
1474 Method returning the files of this design.
1476 :arg fileType: A filter for file types. Default: ``Any``.
1477 :arg fileSet: Specifies if all files from all filesets (``fileSet=None``) are files from a single fileset are returned.
1478 """
1479 if fileSet is None:
1480 for fileSet in self._fileSets.values():
1481 for file in fileSet.Files(fileType):
1482 yield file
1483 else:
1484 if isinstance(fileSet, str): 1484 ↛ 1489line 1484 didn't jump to line 1489 because the condition on line 1484 was always true
1485 try:
1486 fileSet = self._fileSets[fileSet]
1487 except KeyError as ex:
1488 raise Exception(f"Fileset {fileSet.Name} not bound to design {self.Name}.") from ex
1489 elif not isinstance(fileSet, FileSet):
1490 raise TypeError("Parameter 'fileSet' is not of type 'str' or 'FileSet' nor value 'None'.")
1492 for file in fileSet.Files(fileType):
1493 yield file
1495 def Validate(self) -> None:
1496 """Validate this design."""
1497 if self._name is None or self._name == "": 1497 ↛ 1498line 1497 didn't jump to line 1498 because the condition on line 1497 was never true
1498 raise Exception("Validation: Design has no name.")
1500 if self._directory is None: 1500 ↛ 1501line 1500 didn't jump to line 1501 because the condition on line 1500 was never true
1501 raise Exception(f"Validation: Design '{self._name}' has no directory.")
1502 try:
1503 path = self.ResolvedPath
1504 except Exception as ex:
1505 raise Exception(f"Validation: Design '{self._name}' could not compute resolved path.") from ex
1506 if not path.exists(): 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}'s directory '{path}' does not exist.")
1508 if not path.is_dir(): 1508 ↛ 1509line 1508 didn't jump to line 1509 because the condition on line 1508 was never true
1509 raise Exception(f"Validation: Design '{self._name}'s directory '{path}' is not a directory.")
1511 if len(self._fileSets) == 0: 1511 ↛ 1512line 1511 didn't jump to line 1512 because the condition on line 1511 was never true
1512 raise Exception(f"Validation: Design '{self._name}' has no fileset.")
1513 try:
1514 if self._defaultFileSet is not self._fileSets[self._defaultFileSet.Name]: 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 default fileset is the same as listed in filesets.")
1516 except KeyError as ex:
1517 raise Exception(f"Validation: Design '{self._name}'s default fileset is not in list of filesets.") from ex
1518 if self._project is None: 1518 ↛ 1519line 1518 didn't jump to line 1519 because the condition on line 1518 was never true
1519 raise Exception(f"Validation: Design '{self._path}' has no project.")
1521 for fileSet in self._fileSets.values():
1522 fileSet.Validate()
1524 @property
1525 def VHDLLibraries(self) -> Dict[str, VHDLLibrary]:
1526 return self._vhdlLibraries
1528 @property
1529 def VHDLVersion(self) -> VHDLVersion:
1530 if self._vhdlVersion is not None:
1531 return self._vhdlVersion
1532 elif self._project is not None: 1532 ↛ 1535line 1532 didn't jump to line 1535 because the condition on line 1532 was always true
1533 return self._project.VHDLVersion
1534 else:
1535 raise Exception("VHDLVersion was neither set locally nor globally.")
1537 @VHDLVersion.setter
1538 def VHDLVersion(self, value: VHDLVersion) -> None:
1539 self._vhdlVersion = value
1541 @property
1542 def VerilogVersion(self) -> SystemVerilogVersion:
1543 if self._verilogVersion is not None:
1544 return self._verilogVersion
1545 elif self._project is not None: 1545 ↛ 1548line 1545 didn't jump to line 1548 because the condition on line 1545 was always true
1546 return self._project.VerilogVersion
1547 else:
1548 raise Exception("VerilogVersion was neither set locally nor globally.")
1550 @VerilogVersion.setter
1551 def VerilogVersion(self, value: SystemVerilogVersion) -> None:
1552 self._verilogVersion = value
1554 @property
1555 def SVVersion(self) -> SystemVerilogVersion:
1556 if self._svVersion is not None:
1557 return self._svVersion
1558 elif self._project is not None: 1558 ↛ 1561line 1558 didn't jump to line 1561 because the condition on line 1558 was always true
1559 return self._project.SVVersion
1560 else:
1561 raise Exception("SVVersion was neither set locally nor globally.")
1563 @SVVersion.setter
1564 def SVVersion(self, value: SystemVerilogVersion) -> None:
1565 self._svVersion = value
1567 @property
1568 def SRDLVersion(self) -> SystemRDLVersion:
1569 if self._srdlVersion is not None:
1570 return self._srdlVersion
1571 elif self._project is not None:
1572 return self._project.SRDLVersion
1573 else:
1574 raise Exception("SRDLVersion was neither set locally nor globally.")
1576 @SRDLVersion.setter
1577 def SRDLVersion(self, value: SystemRDLVersion) -> None:
1578 self._srdlVersion = value
1580 @property
1581 def ExternalVHDLLibraries(self) -> List:
1582 return self._externalVHDLLibraries
1584 def AddFileSet(self, fileSet: FileSet) -> None:
1585 if not isinstance(fileSet, FileSet):
1586 raise ValueError("Parameter 'fileSet' is not of type ProjectModel.FileSet.")
1587 elif fileSet in self._fileSets:
1588 raise Exception("Design already contains this fileset.")
1589 elif fileSet.Name in self._fileSets.keys():
1590 raise Exception(f"Design already contains a fileset named '{fileSet.Name}'.")
1592 self._fileSets[fileSet.Name] = fileSet
1593 fileSet.Design = self
1594 fileSet._parent = self
1596 def AddFileSets(self, fileSets: Iterable[FileSet]) -> None:
1597 for fileSet in fileSets:
1598 self.AddFileSet(fileSet)
1600 @property
1601 def FileSetCount(self) -> int:
1602 """Returns number of file sets excl. sub-filesets."""
1603 return len(self._fileSets)
1605 @property
1606 def TotalFileSetCount(self) -> int:
1607 """Returns number of file sets incl. sub-filesets."""
1608 fileSetCount = len(self._fileSets)
1609 for fileSet in self._fileSets.values():
1610 fileSetCount += fileSet.TotalFileSetCount
1612 return fileSetCount
1614 def AddFile(self, file: File) -> None:
1615 if file.FileSet is None: 1615 ↛ 1618line 1615 didn't jump to line 1618 because the condition on line 1615 was always true
1616 self._defaultFileSet.AddFile(file)
1617 else:
1618 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.")
1620 def AddFiles(self, files: Iterable[File]) -> None:
1621 for file in files:
1622 self.AddFile(file)
1624 def AddVHDLLibrary(self, vhdlLibrary: VHDLLibrary) -> None:
1625 if vhdlLibrary.Name in self._vhdlLibraries:
1626 if self._vhdlLibraries[vhdlLibrary.Name] is vhdlLibrary:
1627 raise Exception(f"The VHDLLibrary '{vhdlLibrary.Name}' was already added to the design.")
1628 else:
1629 raise Exception(f"A VHDLLibrary with same name ('{vhdlLibrary.Name}') already exists for this design.")
1632 def __len__(self) -> int:
1633 """
1634 Returns number of attributes set on this design.
1636 :returns: The number of attributes set on this design.
1637 """
1638 return len(self._attributes)
1640 def __getitem__(self, key: Type[Attribute]) -> Any:
1641 """Index access for returning attributes on this design.
1643 :param key: The attribute type.
1644 :returns: The attribute's value.
1645 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1646 """
1647 if not issubclass(key, Attribute): 1647 ↛ 1648line 1647 didn't jump to line 1648 because the condition on line 1647 was never true
1648 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1650 try:
1651 return self._attributes[key]
1652 except KeyError:
1653 return key.resolve(self, key)
1655 def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1656 """
1657 Index access for adding or setting attributes on this design.
1659 :param key: The attribute type.
1660 :param value: The attributes value.
1661 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1662 """
1663 if not issubclass(key, Attribute): 1663 ↛ 1664line 1663 didn't jump to line 1664 because the condition on line 1663 was never true
1664 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1666 self._attributes[key] = value
1668 def __delitem__(self, key: Type[Attribute]) -> None:
1669 """
1670 Index access for deleting attributes on this design.
1672 :param key: The attribute type.
1673 """
1674 if not issubclass(key, Attribute): 1674 ↛ 1675line 1674 didn't jump to line 1675 because the condition on line 1674 was never true
1675 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1677 del self._attributes[key]
1679 def __str__(self) -> str:
1680 return self._name
1683@export
1684class Project(metaclass=ExtendedType, slots=True):
1685 """
1686 A :term:`Project` represents a group of designs and the source files therein.
1688 :arg name: The project's name.
1689 :arg rootDirectory: Base-path to the project.
1690 :arg vhdlVersion: Default VHDL version for files in this project, if not specified for the file itself.
1691 :arg verilogVersion: Default Verilog version for files in this project, if not specified for the file itself.
1692 :arg svVersion: Default SystemVerilog version for files in this project, if not specified for the file itself.
1693 """
1695 _name: str
1696 _rootDirectory: pathlib_Path
1697 _designs: Dict[str, Design]
1698 _defaultDesign: Design
1699 _attributes: Dict[Type[Attribute], typing_Any]
1701 _vhdlVersion: VHDLVersion
1702 _verilogVersion: SystemVerilogVersion
1703 _svVersion: SystemVerilogVersion
1704 _srdlVersion: SystemRDLVersion
1706 def __init__(
1707 self,
1708 name: str,
1709 rootDirectory: pathlib_Path = pathlib_Path("."),
1710 vhdlVersion: Nullable[VHDLVersion] = None,
1711 verilogVersion: Nullable[SystemVerilogVersion] = None,
1712 svVersion: Nullable[SystemVerilogVersion] = None
1713 ):
1714 self._name = name
1715 self._rootDirectory = rootDirectory
1716 self._designs = {}
1717 self._defaultDesign = Design("default", project=self)
1718 self._attributes = {}
1719 self._vhdlVersion = vhdlVersion
1720 self._verilogVersion = verilogVersion
1721 self._svVersion = svVersion
1723 @property
1724 def Name(self) -> str:
1725 """Property setting or returning the project's name."""
1726 return self._name
1728 @property
1729 def RootDirectory(self) -> pathlib_Path:
1730 """Property setting or returning the root directory this project is located in."""
1731 return self._rootDirectory
1733 @RootDirectory.setter
1734 def RootDirectory(self, value: pathlib_Path) -> None:
1735 self._rootDirectory = value
1737 @property
1738 def ResolvedPath(self) -> pathlib_Path:
1739 """Read-only property returning the resolved path of this fileset."""
1740 path = self._rootDirectory.resolve()
1741 if self._rootDirectory.is_absolute():
1742 return path
1743 else:
1744 # WORKAROUND: https://stackoverflow.com/questions/67452690/pathlib-path-relative-to-vs-os-path-relpath
1745 return pathlib_Path(path_relpath(path, pathlib_Path.cwd()))
1747 # TODO: return generator with another method
1748 @property
1749 def Designs(self) -> Dict[str, Design]:
1750 return self._designs
1752 @property
1753 def DefaultDesign(self) -> Design:
1754 return self._defaultDesign
1756 def Validate(self) -> None:
1757 """Validate this project."""
1758 if self._name is None or self._name == "": 1758 ↛ 1759line 1758 didn't jump to line 1759 because the condition on line 1758 was never true
1759 raise Exception("Validation: Project has no name.")
1761 if self._rootDirectory is None: 1761 ↛ 1762line 1761 didn't jump to line 1762 because the condition on line 1761 was never true
1762 raise Exception(f"Validation: Project '{self._name}' has no root directory.")
1763 try:
1764 path = self.ResolvedPath
1765 except Exception as ex:
1766 raise Exception(f"Validation: Project '{self._name}' could not compute resolved path.") from ex
1767 if not path.exists(): 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}'s directory '{path}' does not exist.")
1769 if not path.is_dir(): 1769 ↛ 1770line 1769 didn't jump to line 1770 because the condition on line 1769 was never true
1770 raise Exception(f"Validation: Project '{self._name}'s directory '{path}' is not a directory.")
1772 if len(self._designs) == 0: 1772 ↛ 1773line 1772 didn't jump to line 1773 because the condition on line 1772 was never true
1773 raise Exception(f"Validation: Project '{self._name}' has no design.")
1774 try:
1775 if self._defaultDesign is not self._designs[self._defaultDesign.Name]: 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 default design is the same as listed in designs.")
1777 except KeyError as ex:
1778 raise Exception(f"Validation: Project '{self._name}'s default design is not in list of designs.") from ex
1780 for design in self._designs.values():
1781 design.Validate()
1783 @property
1784 def DesignCount(self) -> int:
1785 """Returns number of designs."""
1786 return len(self._designs)
1788 @property
1789 def VHDLVersion(self) -> VHDLVersion:
1790 # TODO: check for None and return exception
1791 return self._vhdlVersion
1793 @VHDLVersion.setter
1794 def VHDLVersion(self, value: VHDLVersion) -> None:
1795 self._vhdlVersion = value
1797 @property
1798 def VerilogVersion(self) -> SystemVerilogVersion:
1799 # TODO: check for None and return exception
1800 return self._verilogVersion
1802 @VerilogVersion.setter
1803 def VerilogVersion(self, value: SystemVerilogVersion) -> None:
1804 self._verilogVersion = value
1806 @property
1807 def SVVersion(self) -> SystemVerilogVersion:
1808 # TODO: check for None and return exception
1809 return self._svVersion
1811 @SVVersion.setter
1812 def SVVersion(self, value: SystemVerilogVersion) -> None:
1813 self._svVersion = value
1815 @property
1816 def SRDLVersion(self) -> SystemRDLVersion:
1817 # TODO: check for None and return exception
1818 return self._srdlVersion
1820 @SRDLVersion.setter
1821 def SRDLVersion(self, value: SystemRDLVersion) -> None:
1822 self._srdlVersion = value
1824 def __len__(self) -> int:
1825 """
1826 Returns number of attributes set on this project.
1828 :returns: The number of attributes set on this project.
1829 """
1830 return len(self._attributes)
1832 def __getitem__(self, key: Type[Attribute]) -> Any:
1833 """Index access for returning attributes on this project.
1835 :param key: The attribute type.
1836 :returns: The attribute's value.
1837 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1838 """
1839 if not issubclass(key, Attribute): 1839 ↛ 1840line 1839 didn't jump to line 1840 because the condition on line 1839 was never true
1840 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1842 try:
1843 return self._attributes[key]
1844 except KeyError:
1845 return key.resolve(self, key)
1847 def __setitem__(self, key: Type[Attribute], value: typing_Any) -> None:
1848 """
1849 Index access for adding or setting attributes on this project.
1851 :param key: The attribute type.
1852 :param value: The attributes value.
1853 :raises TypeError: When parameter 'key' is not a subclass of Attribute.
1854 """
1855 if not issubclass(key, Attribute): 1855 ↛ 1856line 1855 didn't jump to line 1856 because the condition on line 1855 was never true
1856 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1858 self._attributes[key] = value
1860 def __delitem__(self, key: Type[Attribute]) -> None:
1861 """
1862 Index access for deleting attributes on this project.
1864 :param key: The attribute type.
1865 """
1866 if not issubclass(key, Attribute): 1866 ↛ 1867line 1866 didn't jump to line 1867 because the condition on line 1866 was never true
1867 raise TypeError("Parameter 'key' is not an 'Attribute'.")
1869 del self._attributes[key]
1871 def __str__(self) -> str:
1872 return self._name