Coverage for pyEDAA / OSVVM / Project / __init__.py: 85%
570 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-13 22:31 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-13 22:31 +0000
1# ==================================================================================================================== #
2# _____ ____ _ _ ___ ______ ____ ____ __ #
3# _ __ _ _| ____| _ \ / \ / \ / _ \/ ___\ \ / /\ \ / / \/ | #
4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | \___ \\ \ / / \ \ / /| |\/| | #
5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| |___) |\ V / \ V / | | | | #
6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/|____/ \_/ \_/ |_| |_| #
7# |_| |___/ #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2025-2026 Patrick Lehmann - Boetzingen, Germany #
15# #
16# Licensed under the Apache License, Version 2.0 (the "License"); #
17# you may not use this file except in compliance with the License. #
18# You may obtain a copy of the License at #
19# #
20# http://www.apache.org/licenses/LICENSE-2.0 #
21# #
22# Unless required by applicable law or agreed to in writing, software #
23# distributed under the License is distributed on an "AS IS" BASIS, #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25# See the License for the specific language governing permissions and #
26# limitations under the License. #
27# #
28# SPDX-License-Identifier: Apache-2.0 #
29# ==================================================================================================================== #
30#
31"""
32Data model for OSVVM's ``*.pro`` files.
33"""
34from pathlib import Path
35from typing import Optional as Nullable, List, Dict, Mapping, Iterable, TypeVar, Generic, Generator, NoReturn
37from pyTooling.Decorators import readonly, export
38from pyTooling.MetaClasses import ExtendedType
39from pyTooling.Common import getFullyQualifiedName
40from pyVHDLModel import VHDLVersion
42from pyEDAA.OSVVM import OSVVMException
45__all__ = ["osvvmContext", "_ParentType"]
48_ParentType = TypeVar("_ParentType", bound="Base")
49"""Type variable for the parent reference."""
52@export
53class Base(Generic[_ParentType], metaclass=ExtendedType, slots=True):
54 """
55 Base-class for all entities in the data model reflecting an OSVVM ``*.pro`` file.
56 """
57 _parent: Nullable[_ParentType] #: Reference to a parent object.
59 def __init__(self, parent: Nullable[_ParentType] = None) -> None:
60 """
61 Initializes the base-class with a parent reference.
63 :param parent: Optional, reference to a parent object.
64 """
65 self._parent = parent
67 @readonly
68 def Parent(self) -> Nullable[_ParentType]:
69 """
70 Read-only property to access the parent object reference (:attr:`_parent`).
72 :returns: Parent object.
73 """
74 return self._parent
77@export
78class Named(Base[_ParentType], Generic[_ParentType]):
79 """
80 Base-class for all named classes in the data model reflecting an OSVVM ``*.pro`` file.
81 """
82 _name: str #: Name of the entity.
84 def __init__(
85 self,
86 name: str,
87 parent: Nullable[_ParentType] = None
88 ) -> None:
89 """
90 Initializes the base-class with a parent reference.
92 :param name: Name of the entity.
93 :param parent: Optional, reference to a parent object.
94 :raises TypeError: When parameter 'name' is not of type string.
95 :raises ValueError: When parameter 'name' is empty.
96 """
97 super().__init__(parent)
99 if not isinstance(name, str): # pragma: no cover
100 ex = TypeError(f"Parameter 'name' is not a string.")
101 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.")
102 raise ex
103 elif name == "": 103 ↛ 104line 103 didn't jump to line 104 because the condition on line 103 was never true
104 raise ValueError(f"Parameter 'name' is empty.")
106 self._name = name
108 @readonly
109 def Name(self) -> str:
110 """
111 Read-only property to access the entity's name (:attr:`_name`).
113 :returns: Name of the entity.
114 """
115 return self._name
117 def __repr__(self) -> str:
118 return f"{self.__class__.__name__}: {self._name}"
121@export
122class Option(metaclass=ExtendedType, slots=True):
123 """
124 Base-class for all options in the data model used within an OSVVM ``*.pro`` file.
125 """
128@export
129class NoNullRangeWarning(Option):
130 """
131 Analysis option: Disable null-range warnings for VHDL files at analysis.
132 """
133 def __init__(self) -> None:
134 """
135 Initializes this option.
136 """
137 super().__init__()
139 def __repr__(self) -> str:
140 return "NoNullRangeWarning"
143@export
144class SourceFile(Base[_ParentType], Generic[_ParentType]):
145 """
146 A base-class describing any source file (VHDL, Verilog, ...) supported by OSVVM Scripts.
147 """
149 _path: Path #: Path to the source file.
151 def __init__(
152 self,
153 path: Path,
154 parent: Nullable[Base[_ParentType]] = None
155 ) -> None:
156 """
157 Initializes a source file.
159 :param path: Path to the source file.
160 :param parent: Reference to the parent entity.
161 :raises TypeError: When parameter 'path' is not of type :class:`pathlib.Path`.
162 """
163 super().__init__(parent)
165 if not isinstance(path, Path): # pragma: no cover
166 ex = TypeError(f"Parameter 'path' is not a Path.")
167 ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
168 raise ex
170 self._path = path
172 @readonly
173 def Path(self) -> Path:
174 """
175 Read-only property to access the path to the sourcefile (:attr:`_path`).
177 :returns: The sourcefile's path.
178 """
179 return self._path
181 def __repr__(self) -> str:
182 return f"{self.__class__.__name__}: {self._path}"
185@export
186class XDCConstraintFile(SourceFile):
187 """
188 Represents an XDC constraint file.
189 """
190 _scopeToRef: Nullable[str] #: Bind this constraint file to a reference within the design (e.g. VHDL entity name).
191 _scopeToCell: Nullable[str] #: Bind this constraint file to a cell name within the design.
193 def __init__(self, path: Path, scopeToRef: Nullable[str], scopeToCell: Nullable[str]) -> None:
194 """
195 Initializes an XDC constraint file.
197 :param path: Path to the XDC file.
198 :param scopeToRef: Optional, ``ScopeToRef`` parameter for Vivado.
199 :param scopeToCell: Optional, ``ScopeToCell`` parameter for Vivado.
200 :raises TypeError: When parameter 'scopeToRef' is not of type string.
201 :raises TypeError: When parameter 'scopeToCell' is not of type string.
202 """
203 super().__init__(path)
205 if scopeToRef is not None and not isinstance(scopeToRef, str): 205 ↛ 206line 205 didn't jump to line 206 because the condition on line 205 was never true
206 ex = TypeError(f"Parameter 'scopeToRef' is not a str.")
207 ex.add_note(f"Got type '{getFullyQualifiedName(scopeToRef)}'.")
208 raise ex
210 self._scopeToRef = scopeToRef
212 if scopeToCell is not None and not isinstance(scopeToCell, str): 212 ↛ 213line 212 didn't jump to line 213 because the condition on line 212 was never true
213 ex = TypeError(f"Parameter 'scopeToCell' is not a str.")
214 ex.add_note(f"Got type '{getFullyQualifiedName(scopeToCell)}'.")
215 raise ex
217 self._scopeToCell = scopeToCell
219 def __repr__(self) -> str:
220 properties = []
221 if self._scopeToRef is not None:
222 properties.append(f"ScopeToRef={self._scopeToRef}")
223 if self._scopeToCell is not None:
224 properties.append(f"ScopeToCell={self._scopeToCell}")
226 props = f" {{{', '.join(properties)}}}" if len(properties) > 0 else ""
228 return f"{super().__repr__()}{props}"
231@export
232class VHDLSourceFile(SourceFile["VHDLLibrary"]):
233 """
234 Represents a VHDL source file.
235 """
236 _vhdlVersion: VHDLVersion #: VHDL language revision used for analyzing the file.
237 _noNullRangeWarning: Nullable[bool] #: Optional setting, if null-range warnings should be suppressed while analysis.
238 _associatedFiles: List[SourceFile] #: List of associated XDC files.
240 def __init__(
241 self,
242 path: Path,
243 vhdlVersion: VHDLVersion = VHDLVersion.VHDL2008,
244 vhdlLibrary: Nullable["VHDLLibrary"] = None,
245 noNullRangeWarning: Nullable[bool] = None,
246 associatedFiles: Nullable[Iterable[SourceFile]] = None
247 ) -> None:
248 """
249 Initializes a VHDL source file.
251 :param path: Path to the VHDL source file.
252 :param vhdlVersion: VHDL language revision used for analysis.
253 :param vhdlLibrary: Optional, VHDL library all contained design units are compiled into.
254 :param noNullRangeWarning: Optional, suppress null-range warnings while analyzing.
255 :param associatedFiles: Optional, list of associated files.
256 :raises TypeError: When parameter 'vhdlLibrary' is not of type :class:`VHDLLibrary`.
257 :raises TypeError: When parameter 'vhdlversion' is not of type :class:`~pyVHDLModel.VHDLVersion`.
258 :raises TypeError: When parameter 'noNullRangeWarning' is not of type boolean.
259 """
260 if vhdlLibrary is None:
261 super().__init__(path)
262 elif isinstance(vhdlLibrary, VHDLLibrary):
263 super().__init__(path, vhdlLibrary)
264 vhdlLibrary._files.append(self)
265 else: # pragma: no cover
266 ex = TypeError(f"Parameter 'vhdlLibrary' is not a Library.")
267 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibrary)}'.")
268 raise ex
270 if not isinstance(vhdlVersion, VHDLVersion): # pragma: no cover
271 ex = TypeError(f"Parameter 'vhdlVersion' is not a VHDLVersion.")
272 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlVersion)}'.")
273 raise ex
275 self._vhdlVersion = vhdlVersion
277 if noNullRangeWarning is not None and not isinstance(noNullRangeWarning, bool): 277 ↛ 278line 277 didn't jump to line 278 because the condition on line 277 was never true
278 ex = TypeError(f"Parameter 'noNullRangeWarning' is not a boolean.")
279 ex.add_note(f"Got type '{getFullyQualifiedName(noNullRangeWarning)}'.")
280 raise ex
282 self._noNullRangeWarning = noNullRangeWarning
283 # TODO: iterate and check element types
284 self._associatedFiles = [] if associatedFiles is None else [f for f in associatedFiles]
286 @readonly
287 def VHDLLibrary(self) -> Nullable["VHDLLibrary"]:
288 """
289 Read-only property to access the VHDL file's VHDL library (:attr:`_parent`).
291 :returns: The VHDL library this file and its design units is compiled into.
292 """
293 return self._parent
295 @property
296 def VHDLVersion(self) -> VHDLVersion:
297 """
298 Property to access the VHDL language revision (:attr:`_vhdlVersion`).
300 :returns: The used VHDL revision to analyze the file.
301 """
302 return self._vhdlVersion
304 @VHDLVersion.setter
305 def VHDLVersion(self, value: VHDLVersion) -> None:
306 if not isinstance(value, VHDLVersion): 306 ↛ 307line 306 didn't jump to line 307 because the condition on line 306 was never true
307 ex = TypeError(f"Parameter 'value' is not a VHDLVersion.")
308 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
309 raise ex
311 self._vhdlVersion = value
313 @property
314 def NoNullRangeWarning(self) -> Nullable[bool]:
315 """
316 Property to access the no-null-range-warning option (:attr:`_noNullRangeWarning`).
318 :returns: The option's value.
319 """
320 return self._noNullRangeWarning
322 @NoNullRangeWarning.setter
323 def NoNullRangeWarning(self, value: bool) -> None:
324 if value is not None and not isinstance(value, bool):
325 ex = TypeError(f"Parameter 'value' is not a boolean.")
326 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
327 raise ex
329 self._noNullRangeWarning = value
331 @readonly
332 def AssociatedFiles(self) -> List[SourceFile]:
333 """
334 Read-only property to access the list of associated files (:attr:`_associatedFiles`).
336 :returns: The list of associated files.
337 """
338 return self._associatedFiles
340 def __repr__(self) -> str:
341 options = ""
342 if self._noNullRangeWarning is not None: 342 ↛ 343line 342 didn't jump to line 343 because the condition on line 342 was never true
343 options += f", NoNullRangeWarning"
344 return f"VHDLSourceFile: {self._path} ({self._vhdlVersion}{options})"
347@export
348class VHDLLibrary(Named["Build"]):
349 """
350 A VHDL library collecting multiple VHDL files containing VHDL design units.
351 """
353 _files: List[VHDLSourceFile] #: VHDL source files within this VHDL library.
355 def __init__(
356 self,
357 name: str,
358 vhdlFiles: Nullable[Iterable[VHDLSourceFile]] = None,
359 build: Nullable["Build"] = None
360 ) -> None:
361 """
362 Initializes a VHDL library.
364 :param name: Name of the VHDL library.
365 :param vhdlFiles: Optional, list of VHDL source files.
366 :param build: Optional, parent reference to a :class:`Build`.
367 :raises TypeError: When parameter 'name' is not of type string.
368 :raises ValueError: When parameter 'name' is empty.
369 :raises TypeError: When parameter 'build' is not of type :class:`Build`.
370 :raises TypeError: When parameter 'vhdlFiles' is not an iterable.
371 :raises TypeError: When parameter 'vhdlFiles' contains elements not of type :class:`VHDLSourceFile`.
372 """
373 if build is None:
374 super().__init__(name, None)
375 elif isinstance(build, Build):
376 super().__init__(name, build)
377 build._vhdlLibraries[name] = self
378 else: # pragma: no cover
379 ex = TypeError(f"Parameter 'build' is not a Build.")
380 ex.add_note(f"Got type '{getFullyQualifiedName(build)}'.")
381 raise ex
383 self._files = []
384 if vhdlFiles is None:
385 pass
386 elif isinstance(vhdlFiles, Iterable):
387 for vhdlFile in vhdlFiles:
388 if not isinstance(vhdlFile, VHDLSourceFile): 388 ↛ 389line 388 didn't jump to line 389 because the condition on line 388 was never true
389 ex = TypeError(f"Parameter 'vhdlFiles' contains elements not of type VHDLSourceFile.")
390 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlFile)}'.")
391 raise ex
393 vhdlFile._parent = self
394 self._files.append(vhdlFile)
395 else: # pragma: no cover
396 ex = TypeError(f"Parameter 'vhdlFiles' is not an iterable of VHDLSourceFile.")
397 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlFiles)}'.")
398 raise ex
400 @readonly
401 def Build(self) -> Nullable["Build"]:
402 """
403 Read-only property to access the build object (:attr:`_parent`).
405 :returns: The parent object.
406 """
407 return self._parent
409 @readonly
410 def Files(self) -> List[SourceFile]:
411 """
412 Read-only property to access the list of VHDl source files in this VHDL library (:attr:`_files`).
414 :returns: The list of VHDL source files in this VHDL library.
415 """
416 return self._files
418 def AddFile(self, file: VHDLSourceFile) -> None:
419 """
420 Add a file to this VHDL library.
422 :param file: VHDL source file to add.
423 :raises TypeError: When parameter 'file' is not of type :class:`VHDLSourceFile`.
424 """
425 if not isinstance(file, VHDLSourceFile): # pragma: no cover
426 ex = TypeError(f"Parameter 'file' is not a VHDLSourceFile.")
427 ex.add_note(f"Got type '{getFullyQualifiedName(file)}'.")
428 raise ex
430 file._parent = self
431 self._files.append(file)
433 def __repr__(self) -> str:
434 return f"VHDLLibrary: {self._name}"
437@export
438class GenericValue(Option):
439 """
440 Elaboration option: A generic value for a VHDL top-level entity.
441 """
442 _name: str #: Name of the generic.
443 _value: str #: Value of the generic.
445 def __init__(
446 self,
447 name: str,
448 value: str
449 ) -> None:
450 """
451 Initializes a generic value.
453 :param name: Name of the generic.
454 :param value: Value of the generic.
455 :raises TypeError: When parameter 'name' us not of type string.
456 :raises TypeError: When parameter 'value' us not of type string.
457 """
458 super().__init__()
460 if not isinstance(name, str): # pragma: no cover
461 ex = TypeError(f"Parameter 'name' is not a string.")
462 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.")
463 raise ex
464 elif name == "": 464 ↛ 465line 464 didn't jump to line 465 because the condition on line 464 was never true
465 raise ValueError(f"Parameter 'name' is empty.")
467 self._name = name
469 if not isinstance(value, str): # pragma: no cover
470 ex = TypeError(f"Parameter 'value' is not a string.")
471 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
472 raise ex
474 self._value = value
476 @readonly
477 def Name(self) -> str:
478 """
479 Read-only property to access the generic's name (:attr:`_name`).
481 :returns: The parent object.
482 """
483 return self._name
485 @readonly
486 def Value(self) -> str:
487 """
488 Read-only property to access the generic's value (:attr:`_value`).
490 :returns: The parent object.
491 """
492 return self._value
494 def __repr__(self) -> str:
495 return f"{self._name} = {self._value}"
498@export
499class ConstraintFile(Option):
500 """
501 Associated file option: Associated constraint file for VHDL sourcefiles.
502 """
503 _path: Path #: Path to the constraint file.
504 _scopeToRef: Nullable[str] #: Optional, ScopeToRef binding name.
505 _scopeToCell: Nullable[str] #: Optional, ScopeToCell binding name.
507 def __init__(
508 self,
509 path: Path,
510 scopeToRef: Nullable[str] = None,
511 scopeToCell: Nullable[str] = None
512 ) -> None:
513 """
514 Initialize a constraint file option.
516 :param path: Path to the constraint file.
517 :param scopeToRef: Optional, ScopeToRef binding name.
518 :param scopeToCell: Optional, ScopeToCell binding name.
519 :raises TypeError: When parameter 'path' is not of type :class:`~pathlib.Path`.
520 :raises TypeError: When parameter 'scopeToRef' is not of type string.
521 :raises TypeError: When parameter 'scopeToCell' is not of type string.
522 """
523 super().__init__()
525 if not isinstance(path, Path): # pragma: no cover
526 ex = TypeError(f"Parameter 'path' is not a Path.")
527 ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
528 raise ex
530 self._path = path
532 if scopeToRef is not None and not isinstance(scopeToRef, str): 532 ↛ 533line 532 didn't jump to line 533 because the condition on line 532 was never true
533 ex = TypeError(f"Parameter 'scopeToRef' is not a str.")
534 ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
535 raise ex
537 self._scopeToRef = scopeToRef
539 if scopeToCell is not None and not isinstance(scopeToCell, str): 539 ↛ 540line 539 didn't jump to line 540 because the condition on line 539 was never true
540 ex = TypeError(f"Parameter 'scopeToCell' is not a str.")
541 ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
542 raise ex
544 self._scopeToCell = scopeToCell
546 @readonly
547 def Path(self) -> Path:
548 """
549 Read-only property to access the constraint file's path (:attr:`_path`).
551 :returns: The constraint file's path.
552 """
553 return self._path
555 @readonly
556 def ScopeToRef(self) -> Nullable[str]:
557 """
558 Read-only property to access the constraint file's binding to a reference in the design (:attr:`_scopeToRef`).
560 :returns: The ``ScopeToRef`` binding.
561 """
562 return self._scopeToRef
564 @readonly
565 def ScopeToCell(self) -> Nullable[str]:
566 """
567 Read-only property to access the constraint file's binding to a reference in the design (:attr:`_scopeToCell`).
569 :returns: The ``ScopeToCell`` binding.
570 """
571 return self._scopeToCell
573 def __repr__(self) -> str:
574 properties = []
575 if self._scopeToRef is not None:
576 properties.append(f"ScopeToRef={self._scopeToRef}")
577 if self._scopeToCell is not None:
578 properties.append(f"ScopeToCell={self._scopeToCell}")
580 props = f" {{{', '.join(properties)}}}" if len(properties) > 0 else ""
582 return f"{self._path}{props}"
585@export
586class ScopeToRef(Option):
587 """
588 Constrain file option: ScopeToRef binding.
589 """
590 _reference: str #: Reference name.
592 def __init__(
593 self,
594 reference: str
595 ) -> None:
596 """
597 Initialize a ScopeToRef binding.
599 :param reference: Reference name.
600 :raises TypeError: When parameter 'reference' is not of type string.
601 """
602 super().__init__()
604 if not isinstance(reference, str): # pragma: no cover
605 ex = TypeError(f"Parameter 'reference' is not a string.")
606 ex.add_note(f"Got type '{getFullyQualifiedName(reference)}'.")
607 raise ex
609 self._reference = reference
611 @readonly
612 def Reference(self) -> str:
613 """
614 Read-only property to access the reference name (:attr:`_reference`).
616 :returns: The reference name.
617 """
618 return self._reference
620 def __repr__(self) -> str:
621 return f"{self._reference}"
624@export
625class ScopeToCell(Option):
626 """
627 Constrain file option: ScopeToCell binding.
628 """
629 _cell: str #: Cell name
631 def __init__(
632 self,
633 cell: str
634 ) -> None:
635 """
636 Initialize a ScopeToCell binding.
638 :param cell: Cell name.
639 :raises TypeError: When parameter 'cell' is not of type string.
640 """
641 super().__init__()
643 if not isinstance(cell, str): # pragma: no cover
644 ex = TypeError(f"Parameter 'cell' is not a string.")
645 ex.add_note(f"Got type '{getFullyQualifiedName(cell)}'.")
646 raise ex
648 self._cell = cell
650 @readonly
651 def Cell(self) -> str:
652 """
653 Read-only property to access the cell name (:attr:`_cell`).
655 :returns: The cell name.
656 """
657 return self._cell
659 def __repr__(self) -> str:
660 return f"{self._cell}"
663@export
664class Testcase(Named["Testsuite"]):
665 """
666 An OSVVM testcase.
667 """
668 _toplevelName: Nullable[str] #: Name of the VHDL simulation toplevel entity or configuration.
669 _generics: Dict[str, str] #: A dictionary of toplevel generic values.
671 def __init__(
672 self,
673 name: str,
674 toplevelName: Nullable[str] = None,
675 generics: Nullable[Iterable[GenericValue] | Mapping[str, str]] = None,
676 testsuite: Nullable["Testsuite"] = None
677 ) -> None:
678 """
679 Initialize an OSVVM testcase.
681 :param name: Name of the testcase.
682 :param toplevelName: Optional, name of the toplevel entity or configuration.
683 :param generics: Optional, list or dictionary of generic values to run the simulation.
684 :param testsuite: Optional, reference to the parent testsuite.
685 :raises TypeError: When parameter 'name' is not of type string.
686 :raises ValueError: When parameter 'name' is empty.
687 :raises TypeError: When parameter 'testsuite' is not of type :class:`Testsuite`.
688 :raises TypeError: When parameter 'toplevelName' is not of type string.
689 :raises TypeError: When parameter 'generics' is not a mapping or iterable.
690 :raises TypeError: When parameter 'generics' contains elements not of type :class:`GenericValue`.
691 """
692 if testsuite is None:
693 super().__init__(name, None)
694 elif isinstance(testsuite, Testsuite):
695 super().__init__(name, testsuite)
696 testsuite._testcases[name] = self
697 else: # pragma: no cover
698 ex = TypeError(f"Parameter 'testsuite' is not a Testsuite.")
699 ex.add_note(f"Got type '{getFullyQualifiedName(testsuite)}'.")
700 raise ex
702 if toplevelName is not None and not isinstance(toplevelName, str): # pragma: no cover
703 ex = TypeError(f"Parameter 'toplevelName' is not a string.")
704 ex.add_note(f"Got type '{getFullyQualifiedName(toplevelName)}'.")
705 raise ex
707 self._toplevelName = toplevelName
709 self._generics = {}
710 if generics is None:
711 pass
712 elif isinstance(generics, Mapping):
713 for key, value in generics.items():
714 # TODO: check for str and str?
715 self._generics[key] = value
716 elif isinstance(generics, Iterable):
717 for item in generics:
718 if not isinstance(item, GenericValue): # pragma: no cover
719 ex = TypeError(f"Parameter 'generics' contains elements which are not of type GenericValue.")
720 ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.")
721 raise ex
723 self._generics[item._name] = item._value
724 else: # pragma: no cover
725 ex = TypeError(f"Parameter 'generics' is not an iterable of GenericValue nor a dictionary of strings.")
726 ex.add_note(f"Got type '{getFullyQualifiedName(generics)}'.")
727 raise ex
729 @readonly
730 def Testsuite(self) -> "Testsuite":
731 """
732 Read-only property to access the parent testsuite (:attr:`_parent`).
734 :returns: The parent testsuite.
735 """
736 return self._parent
738 @readonly
739 def ToplevelName(self) -> str:
740 """
741 Read-only property to access the testcases toplevel name (:attr:`_toplevelName`).
743 :returns: The toplevel name.
744 """
745 return self._toplevelName
747 @readonly
748 def Generics(self) -> Dict[str, str]:
749 """
750 Read-only property to access the testcase's toplevel generics (:attr:`_generics`).
752 :returns: The dictionary of generic values for this testcase.
753 """
754 return self._generics
756 # TODO: why is this not a setter?
757 def SetToplevel(self, toplevelName: str) -> None:
758 """
759 Set the testcase's toplevel entity or configuration.
761 :param toplevelName: Name of the toplevel.
762 :raises TypeError: When parameter 'toplevelName' is not of type string.
763 """
764 if not isinstance(toplevelName, str): # pragma: no cover
765 ex = TypeError(f"Parameter 'toplevelName' is not a string.")
766 ex.add_note(f"Got type '{getFullyQualifiedName(toplevelName)}'.")
767 raise ex
768 elif toplevelName == "": 768 ↛ 769line 768 didn't jump to line 769 because the condition on line 768 was never true
769 raise ValueError(f"Parameter 'toplevelName' is empty.")
771 self._toplevelName = toplevelName
773 def AddGeneric(self, genericValue: GenericValue) -> None:
774 """
775 Add a generic value to this testcase.
777 :param genericValue: Generic value to be added.
778 :raises TypeError: When parameter 'genericValue' is not of type :class:`GenericValue`.
779 """
780 if not isinstance(genericValue, GenericValue): # pragma: no cover
781 ex = TypeError(f"Parameter 'genericValue' is not a GenericValue.")
782 ex.add_note(f"Got type '{getFullyQualifiedName(genericValue)}'.")
783 raise ex
785 self._generics[genericValue._name] = genericValue._value
787 def __repr__(self) -> str:
788 generics = f" - [{', '.join([f'{n}={v}' for n,v in self._generics.items()])}]" if len(self._generics) > 0 else ""
789 return f"Testcase: {self._name}{generics}"
792@export
793class Testsuite(Named["Build"]):
794 """
795 An OSVVM testsuite containing multiple OSVVM testcases.
796 """
797 _testcases: Dict[str, Testcase] #: Dictionary of testcases.
799 def __init__(
800 self,
801 name: str,
802 testcases: Nullable[Iterable[Testcase] | Mapping[str, Testcase]] = None,
803 build: Nullable["Build"] = None
804 ) -> None:
805 """
806 Initialize an OSVVM testsuite.
808 :param name: Name of the testsuite.
809 :param testcases: Optional, list or dictionary of testcases.
810 :param build: Optional, reference to the parent build.
811 :raises TypeError: When parameter 'name' is not of type string.
812 :raises ValueError: When parameter 'name' is empty.
813 :raises TypeError: When parameter 'build' is not of type :class:`Build`.
814 :raises TypeError: When parameter 'testcases' is not an iterable or mapping.
815 :raises TypeError: When parameter 'testcases' contains an element not of type :class:`Testcase`.
816 """
817 if build is None:
818 super().__init__(name, None)
819 elif isinstance(build, Build):
820 super().__init__(name, build)
821 build._testsuites[name] = self
822 else: # pragma: no cover
823 ex = TypeError(f"Parameter 'build' is not a Build.")
824 ex.add_note(f"Got type '{getFullyQualifiedName(build)}'.")
825 raise ex
827 self._testcases = {}
828 if testcases is None:
829 pass
830 elif isinstance(testcases, Mapping):
831 for key, value in testcases.items():
832 # TODO: check types of key/value
833 value._parent = self
834 self._testcases[key] = value
835 elif isinstance(testcases, Iterable):
836 for item in testcases:
837 if not isinstance(item, Testcase): # pragma: no cover
838 ex = TypeError(f"Parameter 'testcases' contains elements not of type Testcase.")
839 ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.")
840 raise ex
842 item._parent = self
843 self._testcases[item._name] = item
844 else: # pragma: no cover
845 ex = TypeError(f"Parameter 'testcases' is not an iterable of Testcase nor a mapping of Testcase.")
846 ex.add_note(f"Got type '{getFullyQualifiedName(testcases)}'.")
847 raise ex
849 @readonly
850 def Build(self) -> Nullable["Build"]:
851 """
852 Read-only property to access the parent build (:attr:`_parent`).
854 :returns: The parent build.
855 """
856 return self._parent
858 @readonly
859 def Testcases(self) -> Dict[str, Testcase]:
860 """
861 Read-only property to access the dictionary of testcases (:attr:`_testcases`).
863 :returns: The dictionary of testcases.
864 """
865 return self._testcases
867 def AddTestcase(self, testcase: Testcase) -> None:
868 """
869 Add a testcase to the testsuite.
871 :param testcase: Testcase to add.
872 :raises TypeError: When parameter 'testcase' is not of type :class:`Testcase`.
873 """
874 if not isinstance(testcase, Testcase): # pragma: no cover
875 ex = TypeError(f"Parameter 'testcase' is not a Testcase.")
876 ex.add_note(f"Got type '{getFullyQualifiedName(testcase)}'.")
877 raise ex
879 testcase._parent = self
880 self._testcases[testcase._name] = testcase
882 def __repr__(self) -> str:
883 return f"Testsuite: {self._name}"
886@export
887class BuildName(Option):
888 """OSVVM option: Name of a build."""
889 _name: str #: Name of the build.
891 def __init__(
892 self,
893 name: str,
894 ) -> None:
895 """
896 Initialize the build name option.
898 :param name: Name of the build
899 :raises TypeError: When parameter 'name' is not of type string.
900 """
901 super().__init__()
903 if not isinstance(name, str): # pragma: no cover
904 ex = TypeError(f"Parameter 'name' is not a string.")
905 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.")
906 raise ex
908 self._name = name
910 @readonly
911 def Name(self) -> str:
912 """
913 Read-only property to access the build name (:attr:`_name`).
915 :returns: Name of the build.
916 """
917 return self._name
919 def __repr__(self) -> str:
920 return f"BuildName: {self._name}"
923@export
924class Build(Named["Project"]):
925 """
926 An OSVVM build containing one or multiple OSVVM testsuites.
927 """
928 _includedFiles: List[Path] #: List of loaded (included) ``*.pro`` files.
929 _vhdlLibraries: Dict[str, VHDLLibrary] #: Dictionary of VHDL libraries.
930 _testsuites: Dict[str, Testsuite] #: Dictionary of testsuites.
932 def __init__(
933 self,
934 name: str,
935 vhdlLibraries: Nullable[Iterable[VHDLLibrary] | Mapping[str, VHDLLibrary]] = None,
936 testsuites: Nullable[Iterable[Testsuite] | Mapping[str, Testsuite]] = None,
937 project: Nullable[Base] = None
938 ) -> None:
939 """
940 Initialize an OSVVM build.
942 :param name: Name of the build.
943 :param vhdlLibraries: Optional, list or dictionary of VHDL libraries.
944 :param testsuites: Optional, list or dictionary of OSVVM testsuites.
945 :param project: Optional, reference to the parent project.
946 :raises TypeError: When parameter 'name' is not of type string.
947 :raises ValueError: When parameter 'name' is empty.
948 :raises TypeError: When parameter 'project' is not of type :class:`Project`.
949 :raises TypeError: When parameter 'vhdlLibraries' is not an iterable or mapping.
950 :raises TypeError: When parameter 'vhdlLibraries' contains an element not of type :class:`VHDLLibrary`.
951 :raises TypeError: When parameter 'testsuites' is not an iterable or mapping.
952 :raises TypeError: When parameter 'testsuites' contains an element not of type :class:`Testsuites`.
953 """
954 if project is None:
955 super().__init__(name, None)
956 elif isinstance(project, Project):
957 super().__init__(name, project)
958 project._builds[name] = self
959 else: # pragma: no cover
960 ex = TypeError(f"Parameter 'project' is not a Project.")
961 ex.add_note(f"Got type '{getFullyQualifiedName(project)}'.")
962 raise ex
964 self._includedFiles = []
965 self._vhdlLibraries = {}
966 if vhdlLibraries is None:
967 pass
968 elif isinstance(vhdlLibraries, Mapping):
969 for key, value in vhdlLibraries.items():
970 # TODO: check used datatypes
971 value._parent = self
972 self._vhdlLibraries[key] = value
973 elif isinstance(vhdlLibraries, Iterable):
974 for item in vhdlLibraries:
975 if not isinstance(item, VHDLLibrary): # pragma: no cover
976 ex = TypeError(f"Parameter 'vhdlLibraries' contains elements not of type VHDLLibrary.")
977 ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.")
978 raise ex
980 item._parent = self
981 self._vhdlLibraries[item._name] = item
982 else: # pragma: no cover
983 ex = TypeError(f"Parameter 'libraries' is not an iterable of VHDLLibrary nor a mapping of VHDLLibrary.")
984 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibraries)}'.")
985 raise ex
987 self._testsuites = {}
988 if testsuites is None:
989 pass
990 elif isinstance(testsuites, Mapping):
991 for key, value in testsuites.items():
992 # TODO: check used datatypes
993 value._parent = self
994 self._testsuites[key] = value
995 elif isinstance(testsuites, Iterable):
996 for item in testsuites:
997 if not isinstance(item, Testsuite): # pragma: no cover
998 ex = TypeError(f"Parameter 'testsuites' contains elements not of type Testsuite.")
999 ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.")
1000 raise ex
1002 item._parent = self
1003 self._testsuites[item._name] = item
1004 else: # pragma: no cover
1005 ex = TypeError(f"Parameter 'testsuites' is not an iterable of Testsuite nor a mapping of Testsuite.")
1006 ex.add_note(f"Got type '{getFullyQualifiedName(testsuites)}'.")
1007 raise ex
1009 @readonly
1010 def Project(self) -> Nullable["Project"]:
1011 """
1012 Read-only property to access the parent project (:attr:`_parent`).
1014 :returns: The parent project.
1015 """
1016 return self._parent
1018 @readonly
1019 def IncludedFiles(self) -> Generator[Path, None, None]:
1020 """
1021 Read-only property to return a generator for all included (loaded) ``*.pro`` files (:attr:`_includedFiles`).
1023 :returns: The sequence of loaded ``*.pro`` files.
1024 """
1025 return (file for file in self._includedFiles)
1027 @readonly
1028 def VHDLLibraries(self) -> Dict[str, VHDLLibrary]:
1029 """
1030 Read-only property to access the dictionary of VHDL libraries (:attr:`_vhdlLibraries`).
1032 :returns: The dictionary of VHDL libraries.
1033 """
1034 return self._vhdlLibraries
1036 @readonly
1037 def Testsuites(self) -> Dict[str, Testsuite]:
1038 """
1039 Read-only property to access the dictionary of testsuites (:attr:`_testsuites`).
1041 :returns: The dictionary of testsuites.
1042 """
1043 return self._testsuites
1045 def AddVHDLLibrary(self, vhdlLibrary: VHDLLibrary) -> None:
1046 """
1047 Add a VHDL library to the build.
1049 :param vhdlLibrary: VHDL library to add.
1050 :raises TypeError: When parameter 'vhdlLibrary' is not of type :class:`VHDLLibrary`.
1051 """
1052 if not isinstance(vhdlLibrary, VHDLLibrary): # pragma: no cover
1053 ex = TypeError(f"Parameter 'vhdlLibrary' is not a VHDLLibrary.")
1054 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibrary)}'.")
1055 raise ex
1057 vhdlLibrary._parent = self
1058 self._vhdlLibraries[vhdlLibrary._name] = vhdlLibrary
1060 def AddTestsuite(self, testsuite: Testsuite) -> None:
1061 """
1062 Add a testsuite to the build.
1064 :param testsuite: Testsuite to add.
1065 :raises TypeError: When parameter 'testsuite' is not of type :class:`Testsuite`.
1066 """
1067 if not isinstance(testsuite, Testsuite): # pragma: no cover
1068 ex = TypeError(f"Parameter 'testsuite' is not a Testsuite.")
1069 ex.add_note(f"Got type '{getFullyQualifiedName(testsuite)}'.")
1070 raise ex
1072 testsuite._parent = self
1073 self._testsuites[testsuite._name] = testsuite
1075 def __repr__(self) -> str:
1076 return f"Build: {self._name}"
1079@export
1080class Project(Named):
1081 """
1082 An OSVVM project containing one or multiple OSVVM builds.
1083 """
1084 _builds: Dict[str, Build] #: Dictionary of builds
1086 def __init__(
1087 self,
1088 name: str,
1089 builds: Nullable[Iterable[Build] | Mapping[str, Build]] = None
1090 ) -> None:
1091 """
1092 Initializes an OSVVM project.
1094 :param name: Name of the build.
1095 :param builds: Optional, list or dictionary of OSVVM builds.
1096 :raises TypeError: When parameter 'name' is not of type string.
1097 :raises ValueError: When parameter 'name' is empty.
1098 :raises TypeError: When parameter 'builds' is not an iterable or mapping.
1099 :raises TypeError: When parameter 'builds' contains an element not of type :class:`Build`.
1100 """
1101 super().__init__(name, None)
1103 self._builds = {}
1104 if builds is None:
1105 pass
1106 elif isinstance(builds, Mapping):
1107 for key, value in builds.items():
1108 # TODO: check used datatypes
1109 value._parent = self
1110 self._builds[key] = value
1111 elif isinstance(builds, Iterable):
1112 for item in builds:
1113 if not isinstance(item, Build): # pragma: no cover
1114 ex = TypeError(f"Parameter 'builds' contains elements not of type Build.")
1115 ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.")
1116 raise ex
1118 item._parent = self
1119 self._builds[item._name] = item
1120 else: # pragma: no cover
1121 ex = TypeError(f"Parameter 'builds' is not an iterable of Build nor a mapping of Build.")
1122 ex.add_note(f"Got type '{getFullyQualifiedName(builds)}'.")
1123 raise ex
1125 @readonly
1126 def Builds(self) -> Dict[str, Build]:
1127 """
1128 Read-only property to access the dictionary of builds (:attr:`_builds`).
1130 :returns: The dictionary of builds.
1131 """
1132 return self._builds
1134 @readonly
1135 def IncludedFiles(self) -> Generator[Path, None, None]:
1136 """
1137 Read-only property to return a generator for all included (loaded) ``*.pro`` files.
1139 .. note::
1141 This generator iterates all builds (:attr:`_builds`) and returns a combined generator for all included files.
1143 :returns: The sequence of loaded ``*.pro`` files.
1144 """
1145 for build in self._builds.values():
1146 yield from build.IncludedFiles
1148 def AddBuild(self, build: Build) -> None:
1149 """
1150 Add a build to the project.
1152 :param build: Build to add.
1153 :raises TypeError: When parameter 'build' is not of type :class:`Build`.
1154 """
1155 if not isinstance(build, Build): # pragma: no cover
1156 ex = TypeError(f"Parameter 'build' is not a Build.")
1157 ex.add_note(f"Got type '{getFullyQualifiedName(build)}'.")
1158 raise ex
1160 build._parent = self
1161 self._builds[build._name] = build
1163 def __repr__(self) -> str:
1164 return f"Project: {self._name}"
1167@export
1168class Context(Base):
1169 """
1170 The OSVVM TCL execution context.
1172 When an OSVVM ``*.pro`` file is executed, it relies on a context for storing the currently objects and settings. For
1173 example the currently used testsuite or the currently set VHDL language revision.
1175 .. hint::
1177 The context stores the last seen exception within Python scripting in :attr:`_lastException`, because TCL doesn't
1178 forward a raised Python exception through TCL back into the Python context. It just raises a generic
1179 :exc:`~tkinter.TclError`. The helper function :func:`~pyEDAA.OSVVM.Project.TCL.getException` help to restore the
1180 original Python exception using this context object.
1181 """
1182 # _tcl: TclEnvironment
1184 _processor: "OsvvmProFileProcessor" #: The TCL processor.
1185 _lastException: Nullable[Exception] #: Last Python exception seen.
1187 _workingDirectory: Path #: The working directory, where the processing started.
1188 _currentDirectory: Path #: The virtual working directory, e.g. updated by including other ``*.pro`` files.
1189 _includedFiles: List[Path] #: A list of used ``*.pro`` files.
1191 _vhdlversion: VHDLVersion #: The currently set VHDL language revision.
1193 _vhdlLibrary: Nullable[VHDLLibrary] #: The currently active VHDL library.
1194 _vhdlLibraries: Dict[str, VHDLLibrary] #: A dictionary of known VHDL libraries.
1196 _testsuite: Nullable[Testsuite] #: The currently active OSVVM testsuite.
1197 _testsuites: Dict[str, Testsuite] #: A dictionary of known testsuites.
1198 _testcase: Nullable[Testcase] #: The currently active OSVVM testcase.
1199 _options: Dict[int, Option] #: A dictionary of gathered options.
1201 _build: Nullable[Build] #: The currently active OSVVM build.
1202 _builds: Dict[str, Build] #: A dictionary of known OSVVM builds.
1204 def __init__(self) -> None:
1205 """
1206 Initializes a TCL execution context for OSVVM ``*.pro`` file processing.
1207 """
1208 super().__init__()
1210 self._processor = None
1211 self._lastException = None
1213 self._workingDirectory = Path.cwd()
1214 self._currentDirectory = self._workingDirectory
1215 self._includedFiles = []
1217 self._vhdlversion = VHDLVersion.VHDL2008
1219 self._vhdlLibrary = None
1220 self._vhdlLibraries = {}
1222 self._testcase = None
1223 self._testsuite = None
1224 self._testsuites = {}
1225 self._options = {}
1227 self._build = None
1228 self._builds = {}
1230 def Clear(self) -> None:
1231 """
1232 Clear the TCL execution context.
1233 """
1234 self._processor = None
1235 self._lastException = None
1237 self._workingDirectory = Path.cwd()
1238 self._currentDirectory = self._workingDirectory
1239 self._includedFiles = []
1241 self._vhdlversion = VHDLVersion.VHDL2008
1243 self._vhdlLibrary = None
1244 self._vhdlLibraries = {}
1246 self._testcase = None
1247 self._testsuite = None
1248 self._testsuites = {}
1249 self._options = {}
1251 self._build = None
1252 self._builds = {}
1254 @readonly
1255 def Processor(self): # -> "Tk":
1256 """
1257 Read-only property to access the TCL processor (:attr:`_processor`).
1259 :returns: The TCL processor.
1260 """
1261 return self._processor
1263 @property
1264 def LastException(self) -> Nullable[Exception]:
1265 """
1266 Property to access the last seen Python exception (:attr:`_lastException`).
1268 :returns: The last seen Python exception. This might return ``None``.
1269 """
1270 lastException = self._lastException
1271 self._lastException = None
1272 return lastException
1274 @LastException.setter
1275 def LastException(self, value: Exception) -> None:
1276 self._lastException = value
1278 @readonly
1279 def WorkingDirectory(self) -> Path:
1280 """
1281 Read-only property to access the working directory (:attr:`_workingDirectory`).
1283 :returns: The working directory.
1284 """
1285 return self._workingDirectory
1287 @readonly
1288 def CurrentDirectory(self) -> Path:
1289 """
1290 Read-only property to access the current directory (:attr:`_currentDirectory`).
1292 The current directory is a virtual working directory used while processing ``*.pro`` files.
1294 :returns: The current directory.
1295 """
1296 return self._currentDirectory
1298 @property
1299 def VHDLVersion(self) -> VHDLVersion:
1300 """
1301 Property to access the VHDL language revision (:attr:`_vhdlVersion`).
1303 :returns: The currently set VHDL revision.
1304 """
1305 return self._vhdlversion
1307 @VHDLVersion.setter
1308 def VHDLVersion(self, value: VHDLVersion) -> None:
1309 self._vhdlversion = value
1311 @readonly
1312 def IncludedFiles(self) -> List[Path]:
1313 """
1314 Read-only property to access list of included ``*.pro`` files (:attr:`_includedFiles`).
1316 :returns: The list of loaded files.
1317 """
1318 return self._includedFiles
1320 @readonly
1321 def VHDLLibrary(self) -> VHDLLibrary:
1322 """
1323 Read-only property to access the currently active VHDL library (:attr:`_vhdlLibrary`).
1325 :returns: The active VHDL libraries.
1326 """
1327 return self._vhdlLibrary
1329 @readonly
1330 def VHDLLibraries(self) -> Dict[str, VHDLLibrary]:
1331 """
1332 Read-only property to access the dictionary of known VHDL libraries (:attr:`_vhdlLibraries`).
1334 :returns: The dictionary of VHDL libraries.
1335 """
1336 return self._vhdlLibraries
1338 @readonly
1339 def Testsuite(self) -> Testsuite:
1340 """
1341 Read-only property to access the currently active OSVVM testsuite (:attr:`_testsuite`).
1343 :returns: The active OSVVM testsuite.
1344 """
1345 return self._testsuite
1347 @readonly
1348 def Testsuites(self) -> Dict[str, Testsuite]:
1349 """
1350 Read-only property to access the dictionary of known OSVVM testsuites (:attr:`_testsuites`).
1352 :returns: The dictionary of OSVVM testsuites.
1353 """
1354 return self._testsuites
1356 @readonly
1357 def TestCase(self) -> Testcase:
1358 """
1359 Read-only property to access the currently active OSVVM testcase (:attr:`_testcase`).
1361 :returns: The active OSVVM testcase.
1362 """
1363 return self._testcase
1365 @readonly
1366 def Build(self) -> Build:
1367 """
1368 Read-only property to access the currently active OSVVM build (:attr:`_build`).
1370 :returns: The active OSVVM build.
1371 """
1372 return self._build
1374 @readonly
1375 def Builds(self) -> Dict[str, Build]:
1376 """
1377 Read-only property to access the dictionary of known OSVVM builds (:attr:`_build`).
1379 :returns: The dictionary of OSVVM builds.
1380 """
1381 return self._builds
1383 def ToProject(self, projectName: str) -> Project:
1384 """
1385 Convert the context to an OSVVM project.
1387 :param projectName: Name of the project.
1388 :returns: OSVVM project.
1389 """
1390 return Project(projectName, self._builds)
1392 def RaiseException(self, ex: Exception, cause: Nullable[Exception] = None) -> NoReturn:
1393 """
1394 Raise an exception, but keep a reference to the exception object in the TCL execution context.
1396 :param ex: Exception to be raised.
1397 """
1398 if cause is not None:
1399 ex.__cause__ = cause
1401 self._lastException = ex
1402 raise ex
1404 def BeginBuild(self, buildName: str) -> Build:
1405 """
1406 Begin a new build context within the overall TCL execution context.
1408 :param buildName: Name of the new build.
1409 :returns: Currently active OSVVM build object.
1410 :raises OSVVMException: When a VHDL library has been created outside a build context.
1411 :raises OSVVMException: When a OSVVM testsuite has been created outside a build context.
1412 """
1413 if len(self._vhdlLibraries) > 0: 1413 ↛ 1414line 1413 didn't jump to line 1414 because the condition on line 1413 was never true
1414 ex = OSVVMException(f"VHDL libraries have been created outside of an OSVVM build script.")
1415 ex.add_note(f"TCL command 'library' has been called before 'build'.")
1416 raise ex
1417 if len(self._testsuites) > 0: 1417 ↛ 1418line 1417 didn't jump to line 1418 because the condition on line 1417 was never true
1418 ex = OSVVMException(f"Testsuites have been created outside of an OSVVM build script.")
1419 ex.add_note(f"TCL command 'TestSuite' has been called before 'build'.")
1420 raise ex
1422 build = Build(buildName)
1423 build._vhdlLibraries = self._vhdlLibraries
1424 build._testsuites = self._testsuites
1426 self._build = build
1427 self._builds[buildName] = build
1429 return build
1431 def EndBuild(self) -> Build:
1432 """
1433 Finalize the currently active build context.
1435 The TCL execution context is cleaned up: partially reset the context and initialize some fields with new data
1436 structures.
1438 :returns: The OSVVM build object.
1439 """
1440 build = self._build
1442 self._vhdlLibrary = None
1443 self._vhdlLibraries = {}
1444 self._testcase = None
1445 self._testsuite = None
1446 self._testsuites = {}
1447 self._build = None
1449 # TODO: should this be handled in LoadBuildFile ?
1450 self._currentDirectory = self._workingDirectory
1452 return build
1454 def IncludeFile(self, proFileOrBuildDirectory: Path) -> Path:
1455 """
1456 Include a specific ``*.pro`` file or the ``*.pro`` file from an OSVVM build directory.
1458 .. hint::
1460 An OSVVM build directory is a directory:
1462 * containing a ``build.pro`` file (new style), or
1463 * containing a ``*.pro`` file with the same name as the directory its contained in (old style).
1465 :param proFileOrBuildDirectory: The path to the ``*.pro`` file or directory containing a ``*.pro`` file. |br|
1466 Only relative paths are supported.
1467 :returns: The resolved path to the found ``*.pro`` file.
1468 :raises TypeError: When parameter 'proFileOrBuildDirectory' is not of type :class:`~pathlib.Path`.
1469 :raises OSVVMException: When parameter 'proFileOrBuildDirectory' contains an absolut path.
1470 :raises OSVVMException: When the resolved path doesn't reference to a ``*.pro`` file.
1471 :raises OSVVMException: When the resolved path isn't an OSVVM build directory.
1472 :raises OSVVMException: When the resolved path neither references a ``*.pro`` file nor an OSVVM build
1473 directory.
1474 """
1475 if not isinstance(proFileOrBuildDirectory, Path): # pragma: no cover
1476 ex = TypeError(f"Parameter 'proFileOrBuildDirectory' is not a Path.")
1477 ex.add_note(f"Got type '{getFullyQualifiedName(proFileOrBuildDirectory)}'.")
1478 self.RaiseException(ex)
1480 if proFileOrBuildDirectory.is_absolute(): 1480 ↛ 1481line 1480 didn't jump to line 1481 because the condition on line 1480 was never true
1481 ex = OSVVMException(f"Absolute path '{proFileOrBuildDirectory}' not supported.")
1482 self.RaiseException(ex)
1484 path = (self._currentDirectory / proFileOrBuildDirectory).resolve()
1485 if path.is_file():
1486 if path.suffix == ".pro": 1486 ↛ 1490line 1486 didn't jump to line 1490 because the condition on line 1486 was always true
1487 self._currentDirectory = path.parent.relative_to(self._workingDirectory, walk_up=True)
1488 proFile = self._currentDirectory / path.name
1489 else:
1490 self.RaiseException(OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a *.pro file."))
1491 elif path.is_dir():
1492 self._currentDirectory = path
1493 proFile = path / "build.pro"
1494 if not proFile.exists():
1495 proFile = path / f"{path.name}.pro"
1496 if not proFile.exists(): # pragma: no cover
1497 ex = OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a build directory.")
1498 ex.__cause__ = FileNotFoundError(path / "build.pro")
1499 self.RaiseException(ex)
1500 else: # pragma: no cover
1501 self.RaiseException(OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a *.pro file or build directory."))
1503 self._includedFiles.append(proFile)
1504 return proFile
1506 def EvaluateFile(self, proFile: Path) -> None:
1507 """
1508 Evaluate a ``*.pro`` file.
1510 :param proFile: OSVVM ``*.pro`` file to process.
1511 """
1512 self._processor.EvaluateProFile(proFile)
1514 def SetLibrary(self, name: str) -> None:
1515 """
1516 Set or create the currently active VHDL library.
1518 If the VHDL library isn't known in the current context, create a new VHDL library with the given name.
1520 :param name: Name of the VHDL library.
1521 :returns: Activated VHDL library.
1522 """
1523 try:
1524 self._vhdlLibrary = self._vhdlLibraries[name]
1525 except KeyError:
1526 self._vhdlLibrary = VHDLLibrary(name, build=self._build)
1527 self._vhdlLibraries[name] = self._vhdlLibrary
1529 def AddVHDLFile(self, vhdlFile: VHDLSourceFile) -> None:
1530 """
1531 Add a VHDL source file to the currently active VHDL library.
1533 The VHDL source file's VHDL revision is derived from currently active VHDL revision of the TCL execution context.
1535 .. note::
1537 If there is no active VHDL library in the context, a new VHDL library named ``default`` is created.
1539 :param vhdlFile: VHDL source file to be added.
1540 """
1541 if self._vhdlLibrary is None:
1542 self.SetLibrary("default")
1544 vhdlFile.VHDLVersion = self._vhdlversion
1545 self._vhdlLibrary.AddFile(vhdlFile)
1547 def SetTestsuite(self, testsuiteName: str) -> None:
1548 """
1549 Set or create the currently active OSVVM testsuite.
1551 If the testsuite isn't known in the current context, create a new testsuite with the given name.
1553 :param testsuiteName: Name of the OSVVM testsuite.
1554 :returns: Activated OSVVM testsuite.
1555 """
1556 try:
1557 self._testsuite = self._testsuites[testsuiteName]
1558 except KeyError:
1559 self._testsuite = Testsuite(testsuiteName)
1560 self._testsuites[testsuiteName] = self._testsuite
1562 # TODO: should this be called differently then Add***, because it doesn't take an object, but a new and creates a new object.
1563 def AddTestcase(self, testName: str) -> TestCase:
1564 """
1565 Create a new testcase and add to the currently active OSVVM testsuite.
1567 .. note::
1569 If there is no active OSVVM testsuite in the context, a new testsuite named ``default`` is created.
1571 :param testName: Name of the testcase.
1572 :returns: The created OSVVM testcase object.
1573 """
1574 if self._testsuite is None:
1575 self.SetTestsuite("default")
1577 self._testcase = Testcase(testName)
1578 self._testsuite._testcases[testName] = self._testcase
1580 return self._testcase
1582 def SetTestcaseToplevel(self, toplevel: str) -> TestCase:
1583 """
1584 Set the testcase's toplevel entity or configuration name.
1586 :param toplevel: Name of the toplevel entity or configuration.
1587 :returns: The currently active OSVVM testcase.
1588 :raises OSVVMException: When there is no active OSVVM testcase.
1589 """
1590 if self._testcase is None: 1590 ↛ 1591line 1590 didn't jump to line 1591 because the condition on line 1590 was never true
1591 self.RaiseException(OSVVMException("Can't set testcase toplevel, because no testcase was setup."))
1593 self._testcase.SetToplevel(toplevel)
1595 return self._testcase
1597 # TODO: this this an add operation or a register operation?
1598 def AddOption(self, option: Option) -> int:
1599 """
1600 Register a new option and return a unique ID.
1602 .. hint::
1604 TCL can't pass complex Python objects through the TCL layer back to Python. Therefore, complex objects like
1605 options are registered in a dictionary and a unique ID (integer) is returned. Back in Python, this ID can be
1606 converted back to the Python object.
1608 This unique ID is based on :func:`id`.
1610 :param option: Option to register.
1611 :returns: Unique option ID.
1612 """
1613 optionID = id(option)
1614 self._options[optionID] = option
1616 return optionID
1619osvvmContext: Context = Context()
1620"""
1621Global OSVVM processing context.
1623:type: Context
1624"""