# ==================================================================================================================== #
# _____ ____ _ _ ___ ______ ____ ____ __ #
# _ __ _ _| ____| _ \ / \ / \ / _ \/ ___\ \ / /\ \ / / \/ | #
# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | \___ \\ \ / / \ \ / /| |\/| | #
# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| |___) |\ V / \ V / | | | | #
# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/|____/ \_/ \_/ |_| |_| #
# |_| |___/ #
# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# #
# License: #
# ==================================================================================================================== #
# Copyright 2025-2026 Patrick Lehmann - Boetzingen, Germany #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# #
# SPDX-License-Identifier: Apache-2.0 #
# ==================================================================================================================== #
#
"""
Data model for OSVVM's ``*.pro`` files.
"""
from pathlib import Path
from typing import Optional as Nullable, List, Dict, Mapping, Iterable, TypeVar, Generic, Generator, NoReturn
from pyTooling.Decorators import readonly, export
from pyTooling.MetaClasses import ExtendedType
from pyTooling.Common import getFullyQualifiedName
from pyVHDLModel import VHDLVersion
from pyEDAA.OSVVM import OSVVMException
__all__ = ["osvvmContext", "_ParentType"]
_ParentType = TypeVar("_ParentType", bound="Base")
"""Type variable for the parent reference."""
[docs]
@export
class Base(Generic[_ParentType], metaclass=ExtendedType, slots=True):
"""
Base-class for all entities in the data model reflecting an OSVVM ``*.pro`` file.
"""
_parent: Nullable[_ParentType] #: Reference to a parent object.
[docs]
def __init__(self, parent: Nullable[_ParentType] = None) -> None:
"""
Initializes the base-class with a parent reference.
:param parent: Optional, reference to a parent object.
"""
self._parent = parent
@readonly
def Parent(self) -> Nullable[_ParentType]:
"""
Read-only property to access the parent object reference (:attr:`_parent`).
:returns: Parent object.
"""
return self._parent
[docs]
@export
class Named(Base[_ParentType], Generic[_ParentType]):
"""
Base-class for all named classes in the data model reflecting an OSVVM ``*.pro`` file.
"""
_name: str #: Name of the entity.
[docs]
def __init__(
self,
name: str,
parent: Nullable[_ParentType] = None
) -> None:
"""
Initializes the base-class with a parent reference.
:param name: Name of the entity.
:param parent: Optional, reference to a parent object.
:raises TypeError: When parameter 'name' is not of type string.
:raises ValueError: When parameter 'name' is empty.
"""
super().__init__(parent)
if not isinstance(name, str): # pragma: no cover
ex = TypeError(f"Parameter 'name' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.")
raise ex
elif name == "":
raise ValueError(f"Parameter 'name' is empty.")
self._name = name
@readonly
def Name(self) -> str:
"""
Read-only property to access the entity's name (:attr:`_name`).
:returns: Name of the entity.
"""
return self._name
[docs]
def __repr__(self) -> str:
return f"{self.__class__.__name__}: {self._name}"
[docs]
@export
class Option(metaclass=ExtendedType, slots=True):
"""
Base-class for all options in the data model used within an OSVVM ``*.pro`` file.
"""
[docs]
@export
class NoNullRangeWarning(Option):
"""
Analysis option: Disable null-range warnings for VHDL files at analysis.
"""
[docs]
def __init__(self) -> None:
"""
Initializes this option.
"""
super().__init__()
[docs]
def __repr__(self) -> str:
return "NoNullRangeWarning"
[docs]
@export
class SourceFile(Base[_ParentType], Generic[_ParentType]):
"""
A base-class describing any source file (VHDL, Verilog, ...) supported by OSVVM Scripts.
"""
_path: Path #: Path to the source file.
[docs]
def __init__(
self,
path: Path,
parent: Nullable[Base[_ParentType]] = None
) -> None:
"""
Initializes a source file.
:param path: Path to the source file.
:param parent: Reference to the parent entity.
:raises TypeError: When parameter 'path' is not of type :class:`pathlib.Path`.
"""
super().__init__(parent)
if not isinstance(path, Path): # pragma: no cover
ex = TypeError(f"Parameter 'path' is not a Path.")
ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
raise ex
self._path = path
@readonly
def Path(self) -> Path:
"""
Read-only property to access the path to the sourcefile (:attr:`_path`).
:returns: The sourcefile's path.
"""
return self._path
[docs]
def __repr__(self) -> str:
return f"{self.__class__.__name__}: {self._path}"
[docs]
@export
class XDCConstraintFile(SourceFile):
"""
Represents an XDC constraint file.
"""
_scopeToRef: Nullable[str] #: Bind this constraint file to a reference within the design (e.g. VHDL entity name).
_scopeToCell: Nullable[str] #: Bind this constraint file to a cell name within the design.
[docs]
def __init__(self, path: Path, scopeToRef: Nullable[str], scopeToCell: Nullable[str]) -> None:
"""
Initializes an XDC constraint file.
:param path: Path to the XDC file.
:param scopeToRef: Optional, ``ScopeToRef`` parameter for Vivado.
:param scopeToCell: Optional, ``ScopeToCell`` parameter for Vivado.
:raises TypeError: When parameter 'scopeToRef' is not of type string.
:raises TypeError: When parameter 'scopeToCell' is not of type string.
"""
super().__init__(path)
if scopeToRef is not None and not isinstance(scopeToRef, str):
ex = TypeError(f"Parameter 'scopeToRef' is not a str.")
ex.add_note(f"Got type '{getFullyQualifiedName(scopeToRef)}'.")
raise ex
self._scopeToRef = scopeToRef
if scopeToCell is not None and not isinstance(scopeToCell, str):
ex = TypeError(f"Parameter 'scopeToCell' is not a str.")
ex.add_note(f"Got type '{getFullyQualifiedName(scopeToCell)}'.")
raise ex
self._scopeToCell = scopeToCell
[docs]
def __repr__(self) -> str:
properties = []
if self._scopeToRef is not None:
properties.append(f"ScopeToRef={self._scopeToRef}")
if self._scopeToCell is not None:
properties.append(f"ScopeToCell={self._scopeToCell}")
props = f" {{{', '.join(properties)}}}" if len(properties) > 0 else ""
return f"{super().__repr__()}{props}"
[docs]
@export
class VHDLSourceFile(SourceFile["VHDLLibrary"]):
"""
Represents a VHDL source file.
"""
_vhdlVersion: VHDLVersion #: VHDL language revision used for analyzing the file.
_noNullRangeWarning: Nullable[bool] #: Optional setting, if null-range warnings should be suppressed while analysis.
_associatedFiles: List[SourceFile] #: List of associated XDC files.
[docs]
def __init__(
self,
path: Path,
vhdlVersion: VHDLVersion = VHDLVersion.VHDL2008,
vhdlLibrary: Nullable["VHDLLibrary"] = None,
noNullRangeWarning: Nullable[bool] = None,
associatedFiles: Nullable[Iterable[SourceFile]] = None
) -> None:
"""
Initializes a VHDL source file.
:param path: Path to the VHDL source file.
:param vhdlVersion: VHDL language revision used for analysis.
:param vhdlLibrary: Optional, VHDL library all contained design units are compiled into.
:param noNullRangeWarning: Optional, suppress null-range warnings while analyzing.
:param associatedFiles: Optional, list of associated files.
:raises TypeError: When parameter 'vhdlLibrary' is not of type :class:`VHDLLibrary`.
:raises TypeError: When parameter 'vhdlversion' is not of type :class:`~pyVHDLModel.VHDLVersion`.
:raises TypeError: When parameter 'noNullRangeWarning' is not of type boolean.
"""
if vhdlLibrary is None:
super().__init__(path)
elif isinstance(vhdlLibrary, VHDLLibrary):
super().__init__(path, vhdlLibrary)
vhdlLibrary._files.append(self)
else: # pragma: no cover
ex = TypeError(f"Parameter 'vhdlLibrary' is not a Library.")
ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibrary)}'.")
raise ex
if not isinstance(vhdlVersion, VHDLVersion): # pragma: no cover
ex = TypeError(f"Parameter 'vhdlVersion' is not a VHDLVersion.")
ex.add_note(f"Got type '{getFullyQualifiedName(vhdlVersion)}'.")
raise ex
self._vhdlVersion = vhdlVersion
if noNullRangeWarning is not None and not isinstance(noNullRangeWarning, bool):
ex = TypeError(f"Parameter 'noNullRangeWarning' is not a boolean.")
ex.add_note(f"Got type '{getFullyQualifiedName(noNullRangeWarning)}'.")
raise ex
self._noNullRangeWarning = noNullRangeWarning
# TODO: iterate and check element types
self._associatedFiles = [] if associatedFiles is None else [f for f in associatedFiles]
@readonly
def VHDLLibrary(self) -> Nullable["VHDLLibrary"]:
"""
Read-only property to access the VHDL file's VHDL library (:attr:`_parent`).
:returns: The VHDL library this file and its design units is compiled into.
"""
return self._parent
@property
def VHDLVersion(self) -> VHDLVersion:
"""
Property to access the VHDL language revision (:attr:`_vhdlVersion`).
:returns: The used VHDL revision to analyze the file.
"""
return self._vhdlVersion
@VHDLVersion.setter
def VHDLVersion(self, value: VHDLVersion) -> None:
if not isinstance(value, VHDLVersion):
ex = TypeError(f"Parameter 'value' is not a VHDLVersion.")
ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
raise ex
self._vhdlVersion = value
@property
def NoNullRangeWarning(self) -> Nullable[bool]:
"""
Property to access the no-null-range-warning option (:attr:`_noNullRangeWarning`).
:returns: The option's value.
"""
return self._noNullRangeWarning
@NoNullRangeWarning.setter
def NoNullRangeWarning(self, value: bool) -> None:
if value is not None and not isinstance(value, bool):
ex = TypeError(f"Parameter 'value' is not a boolean.")
ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
raise ex
self._noNullRangeWarning = value
@readonly
def AssociatedFiles(self) -> List[SourceFile]:
"""
Read-only property to access the list of associated files (:attr:`_associatedFiles`).
:returns: The list of associated files.
"""
return self._associatedFiles
[docs]
def __repr__(self) -> str:
options = ""
if self._noNullRangeWarning is not None:
options += f", NoNullRangeWarning"
return f"VHDLSourceFile: {self._path} ({self._vhdlVersion}{options})"
[docs]
@export
class VHDLLibrary(Named["Build"]):
"""
A VHDL library collecting multiple VHDL files containing VHDL design units.
"""
_files: List[VHDLSourceFile] #: VHDL source files within this VHDL library.
[docs]
def __init__(
self,
name: str,
vhdlFiles: Nullable[Iterable[VHDLSourceFile]] = None,
build: Nullable["Build"] = None
) -> None:
"""
Initializes a VHDL library.
:param name: Name of the VHDL library.
:param vhdlFiles: Optional, list of VHDL source files.
:param build: Optional, parent reference to a :class:`Build`.
:raises TypeError: When parameter 'name' is not of type string.
:raises ValueError: When parameter 'name' is empty.
:raises TypeError: When parameter 'build' is not of type :class:`Build`.
:raises TypeError: When parameter 'vhdlFiles' is not an iterable.
:raises TypeError: When parameter 'vhdlFiles' contains elements not of type :class:`VHDLSourceFile`.
"""
if build is None:
super().__init__(name, None)
elif isinstance(build, Build):
super().__init__(name, build)
build._vhdlLibraries[name] = self
else: # pragma: no cover
ex = TypeError(f"Parameter 'build' is not a Build.")
ex.add_note(f"Got type '{getFullyQualifiedName(build)}'.")
raise ex
self._files = []
if vhdlFiles is None:
pass
elif isinstance(vhdlFiles, Iterable):
for vhdlFile in vhdlFiles:
if not isinstance(vhdlFile, VHDLSourceFile):
ex = TypeError(f"Parameter 'vhdlFiles' contains elements not of type VHDLSourceFile.")
ex.add_note(f"Got type '{getFullyQualifiedName(vhdlFile)}'.")
raise ex
vhdlFile._parent = self
self._files.append(vhdlFile)
else: # pragma: no cover
ex = TypeError(f"Parameter 'vhdlFiles' is not an iterable of VHDLSourceFile.")
ex.add_note(f"Got type '{getFullyQualifiedName(vhdlFiles)}'.")
raise ex
@readonly
def Build(self) -> Nullable["Build"]:
"""
Read-only property to access the build object (:attr:`_parent`).
:returns: The parent object.
"""
return self._parent
@readonly
def Files(self) -> List[SourceFile]:
"""
Read-only property to access the list of VHDl source files in this VHDL library (:attr:`_files`).
:returns: The list of VHDL source files in this VHDL library.
"""
return self._files
[docs]
def AddFile(self, file: VHDLSourceFile) -> None:
"""
Add a file to this VHDL library.
:param file: VHDL source file to add.
:raises TypeError: When parameter 'file' is not of type :class:`VHDLSourceFile`.
"""
if not isinstance(file, VHDLSourceFile): # pragma: no cover
ex = TypeError(f"Parameter 'file' is not a VHDLSourceFile.")
ex.add_note(f"Got type '{getFullyQualifiedName(file)}'.")
raise ex
file._parent = self
self._files.append(file)
[docs]
def __repr__(self) -> str:
return f"VHDLLibrary: {self._name}"
[docs]
@export
class GenericValue(Option):
"""
Elaboration option: A generic value for a VHDL top-level entity.
"""
_name: str #: Name of the generic.
_value: str #: Value of the generic.
[docs]
def __init__(
self,
name: str,
value: str
) -> None:
"""
Initializes a generic value.
:param name: Name of the generic.
:param value: Value of the generic.
:raises TypeError: When parameter 'name' us not of type string.
:raises TypeError: When parameter 'value' us not of type string.
"""
super().__init__()
if not isinstance(name, str): # pragma: no cover
ex = TypeError(f"Parameter 'name' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.")
raise ex
elif name == "":
raise ValueError(f"Parameter 'name' is empty.")
self._name = name
if not isinstance(value, str): # pragma: no cover
ex = TypeError(f"Parameter 'value' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
raise ex
self._value = value
@readonly
def Name(self) -> str:
"""
Read-only property to access the generic's name (:attr:`_name`).
:returns: The parent object.
"""
return self._name
@readonly
def Value(self) -> str:
"""
Read-only property to access the generic's value (:attr:`_value`).
:returns: The parent object.
"""
return self._value
[docs]
def __repr__(self) -> str:
return f"{self._name} = {self._value}"
[docs]
@export
class ConstraintFile(Option):
"""
Associated file option: Associated constraint file for VHDL sourcefiles.
"""
_path: Path #: Path to the constraint file.
_scopeToRef: Nullable[str] #: Optional, ScopeToRef binding name.
_scopeToCell: Nullable[str] #: Optional, ScopeToCell binding name.
[docs]
def __init__(
self,
path: Path,
scopeToRef: Nullable[str] = None,
scopeToCell: Nullable[str] = None
) -> None:
"""
Initialize a constraint file option.
:param path: Path to the constraint file.
:param scopeToRef: Optional, ScopeToRef binding name.
:param scopeToCell: Optional, ScopeToCell binding name.
:raises TypeError: When parameter 'path' is not of type :class:`~pathlib.Path`.
:raises TypeError: When parameter 'scopeToRef' is not of type string.
:raises TypeError: When parameter 'scopeToCell' is not of type string.
"""
super().__init__()
if not isinstance(path, Path): # pragma: no cover
ex = TypeError(f"Parameter 'path' is not a Path.")
ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
raise ex
self._path = path
if scopeToRef is not None and not isinstance(scopeToRef, str):
ex = TypeError(f"Parameter 'scopeToRef' is not a str.")
ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
raise ex
self._scopeToRef = scopeToRef
if scopeToCell is not None and not isinstance(scopeToCell, str):
ex = TypeError(f"Parameter 'scopeToCell' is not a str.")
ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
raise ex
self._scopeToCell = scopeToCell
@readonly
def Path(self) -> Path:
"""
Read-only property to access the constraint file's path (:attr:`_path`).
:returns: The constraint file's path.
"""
return self._path
@readonly
def ScopeToRef(self) -> Nullable[str]:
"""
Read-only property to access the constraint file's binding to a reference in the design (:attr:`_scopeToRef`).
:returns: The ``ScopeToRef`` binding.
"""
return self._scopeToRef
@readonly
def ScopeToCell(self) -> Nullable[str]:
"""
Read-only property to access the constraint file's binding to a reference in the design (:attr:`_scopeToCell`).
:returns: The ``ScopeToCell`` binding.
"""
return self._scopeToCell
[docs]
def __repr__(self) -> str:
properties = []
if self._scopeToRef is not None:
properties.append(f"ScopeToRef={self._scopeToRef}")
if self._scopeToCell is not None:
properties.append(f"ScopeToCell={self._scopeToCell}")
props = f" {{{', '.join(properties)}}}" if len(properties) > 0 else ""
return f"{self._path}{props}"
[docs]
@export
class ScopeToRef(Option):
"""
Constrain file option: ScopeToRef binding.
"""
_reference: str #: Reference name.
[docs]
def __init__(
self,
reference: str
) -> None:
"""
Initialize a ScopeToRef binding.
:param reference: Reference name.
:raises TypeError: When parameter 'reference' is not of type string.
"""
super().__init__()
if not isinstance(reference, str): # pragma: no cover
ex = TypeError(f"Parameter 'reference' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(reference)}'.")
raise ex
self._reference = reference
@readonly
def Reference(self) -> str:
"""
Read-only property to access the reference name (:attr:`_reference`).
:returns: The reference name.
"""
return self._reference
[docs]
def __repr__(self) -> str:
return f"{self._reference}"
[docs]
@export
class ScopeToCell(Option):
"""
Constrain file option: ScopeToCell binding.
"""
_cell: str #: Cell name
[docs]
def __init__(
self,
cell: str
) -> None:
"""
Initialize a ScopeToCell binding.
:param cell: Cell name.
:raises TypeError: When parameter 'cell' is not of type string.
"""
super().__init__()
if not isinstance(cell, str): # pragma: no cover
ex = TypeError(f"Parameter 'cell' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(cell)}'.")
raise ex
self._cell = cell
@readonly
def Cell(self) -> str:
"""
Read-only property to access the cell name (:attr:`_cell`).
:returns: The cell name.
"""
return self._cell
[docs]
def __repr__(self) -> str:
return f"{self._cell}"
[docs]
@export
class Testcase(Named["Testsuite"]):
"""
An OSVVM testcase.
"""
_toplevelName: Nullable[str] #: Name of the VHDL simulation toplevel entity or configuration.
_generics: Dict[str, str] #: A dictionary of toplevel generic values.
[docs]
def __init__(
self,
name: str,
toplevelName: Nullable[str] = None,
generics: Nullable[Iterable[GenericValue] | Mapping[str, str]] = None,
testsuite: Nullable["Testsuite"] = None
) -> None:
"""
Initialize an OSVVM testcase.
:param name: Name of the testcase.
:param toplevelName: Optional, name of the toplevel entity or configuration.
:param generics: Optional, list or dictionary of generic values to run the simulation.
:param testsuite: Optional, reference to the parent testsuite.
:raises TypeError: When parameter 'name' is not of type string.
:raises ValueError: When parameter 'name' is empty.
:raises TypeError: When parameter 'testsuite' is not of type :class:`Testsuite`.
:raises TypeError: When parameter 'toplevelName' is not of type string.
:raises TypeError: When parameter 'generics' is not a mapping or iterable.
:raises TypeError: When parameter 'generics' contains elements not of type :class:`GenericValue`.
"""
if testsuite is None:
super().__init__(name, None)
elif isinstance(testsuite, Testsuite):
super().__init__(name, testsuite)
testsuite._testcases[name] = self
else: # pragma: no cover
ex = TypeError(f"Parameter 'testsuite' is not a Testsuite.")
ex.add_note(f"Got type '{getFullyQualifiedName(testsuite)}'.")
raise ex
if toplevelName is not None and not isinstance(toplevelName, str): # pragma: no cover
ex = TypeError(f"Parameter 'toplevelName' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(toplevelName)}'.")
raise ex
self._toplevelName = toplevelName
self._generics = {}
if generics is None:
pass
elif isinstance(generics, Mapping):
for key, value in generics.items():
# TODO: check for str and str?
self._generics[key] = value
elif isinstance(generics, Iterable):
for item in generics:
if not isinstance(item, GenericValue): # pragma: no cover
ex = TypeError(f"Parameter 'generics' contains elements which are not of type GenericValue.")
ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.")
raise ex
self._generics[item._name] = item._value
else: # pragma: no cover
ex = TypeError(f"Parameter 'generics' is not an iterable of GenericValue nor a dictionary of strings.")
ex.add_note(f"Got type '{getFullyQualifiedName(generics)}'.")
raise ex
@readonly
def Testsuite(self) -> "Testsuite":
"""
Read-only property to access the parent testsuite (:attr:`_parent`).
:returns: The parent testsuite.
"""
return self._parent
@readonly
def ToplevelName(self) -> str:
"""
Read-only property to access the testcases toplevel name (:attr:`_toplevelName`).
:returns: The toplevel name.
"""
return self._toplevelName
@readonly
def Generics(self) -> Dict[str, str]:
"""
Read-only property to access the testcase's toplevel generics (:attr:`_generics`).
:returns: The dictionary of generic values for this testcase.
"""
return self._generics
# TODO: why is this not a setter?
[docs]
def SetToplevel(self, toplevelName: str) -> None:
"""
Set the testcase's toplevel entity or configuration.
:param toplevelName: Name of the toplevel.
:raises TypeError: When parameter 'toplevelName' is not of type string.
"""
if not isinstance(toplevelName, str): # pragma: no cover
ex = TypeError(f"Parameter 'toplevelName' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(toplevelName)}'.")
raise ex
elif toplevelName == "":
raise ValueError(f"Parameter 'toplevelName' is empty.")
self._toplevelName = toplevelName
[docs]
def AddGeneric(self, genericValue: GenericValue) -> None:
"""
Add a generic value to this testcase.
:param genericValue: Generic value to be added.
:raises TypeError: When parameter 'genericValue' is not of type :class:`GenericValue`.
"""
if not isinstance(genericValue, GenericValue): # pragma: no cover
ex = TypeError(f"Parameter 'genericValue' is not a GenericValue.")
ex.add_note(f"Got type '{getFullyQualifiedName(genericValue)}'.")
raise ex
self._generics[genericValue._name] = genericValue._value
[docs]
def __repr__(self) -> str:
generics = f" - [{', '.join([f'{n}={v}' for n,v in self._generics.items()])}]" if len(self._generics) > 0 else ""
return f"Testcase: {self._name}{generics}"
[docs]
@export
class Testsuite(Named["Build"]):
"""
An OSVVM testsuite containing multiple OSVVM testcases.
"""
_testcases: Dict[str, Testcase] #: Dictionary of testcases.
[docs]
def __init__(
self,
name: str,
testcases: Nullable[Iterable[Testcase] | Mapping[str, Testcase]] = None,
build: Nullable["Build"] = None
) -> None:
"""
Initialize an OSVVM testsuite.
:param name: Name of the testsuite.
:param testcases: Optional, list or dictionary of testcases.
:param build: Optional, reference to the parent build.
:raises TypeError: When parameter 'name' is not of type string.
:raises ValueError: When parameter 'name' is empty.
:raises TypeError: When parameter 'build' is not of type :class:`Build`.
:raises TypeError: When parameter 'testcases' is not an iterable or mapping.
:raises TypeError: When parameter 'testcases' contains an element not of type :class:`Testcase`.
"""
if build is None:
super().__init__(name, None)
elif isinstance(build, Build):
super().__init__(name, build)
build._testsuites[name] = self
else: # pragma: no cover
ex = TypeError(f"Parameter 'build' is not a Build.")
ex.add_note(f"Got type '{getFullyQualifiedName(build)}'.")
raise ex
self._testcases = {}
if testcases is None:
pass
elif isinstance(testcases, Mapping):
for key, value in testcases.items():
# TODO: check types of key/value
value._parent = self
self._testcases[key] = value
elif isinstance(testcases, Iterable):
for item in testcases:
if not isinstance(item, Testcase): # pragma: no cover
ex = TypeError(f"Parameter 'testcases' contains elements not of type Testcase.")
ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.")
raise ex
item._parent = self
self._testcases[item._name] = item
else: # pragma: no cover
ex = TypeError(f"Parameter 'testcases' is not an iterable of Testcase nor a mapping of Testcase.")
ex.add_note(f"Got type '{getFullyQualifiedName(testcases)}'.")
raise ex
@readonly
def Build(self) -> Nullable["Build"]:
"""
Read-only property to access the parent build (:attr:`_parent`).
:returns: The parent build.
"""
return self._parent
@readonly
def Testcases(self) -> Dict[str, Testcase]:
"""
Read-only property to access the dictionary of testcases (:attr:`_testcases`).
:returns: The dictionary of testcases.
"""
return self._testcases
[docs]
def AddTestcase(self, testcase: Testcase) -> None:
"""
Add a testcase to the testsuite.
:param testcase: Testcase to add.
:raises TypeError: When parameter 'testcase' is not of type :class:`Testcase`.
"""
if not isinstance(testcase, Testcase): # pragma: no cover
ex = TypeError(f"Parameter 'testcase' is not a Testcase.")
ex.add_note(f"Got type '{getFullyQualifiedName(testcase)}'.")
raise ex
testcase._parent = self
self._testcases[testcase._name] = testcase
[docs]
def __repr__(self) -> str:
return f"Testsuite: {self._name}"
[docs]
@export
class BuildName(Option):
"""OSVVM option: Name of a build."""
_name: str #: Name of the build.
[docs]
def __init__(
self,
name: str,
) -> None:
"""
Initialize the build name option.
:param name: Name of the build
:raises TypeError: When parameter 'name' is not of type string.
"""
super().__init__()
if not isinstance(name, str): # pragma: no cover
ex = TypeError(f"Parameter 'name' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.")
raise ex
self._name = name
@readonly
def Name(self) -> str:
"""
Read-only property to access the build name (:attr:`_name`).
:returns: Name of the build.
"""
return self._name
[docs]
def __repr__(self) -> str:
return f"BuildName: {self._name}"
[docs]
@export
class Build(Named["Project"]):
"""
An OSVVM build containing one or multiple OSVVM testsuites.
"""
_includedFiles: List[Path] #: List of loaded (included) ``*.pro`` files.
_vhdlLibraries: Dict[str, VHDLLibrary] #: Dictionary of VHDL libraries.
_testsuites: Dict[str, Testsuite] #: Dictionary of testsuites.
[docs]
def __init__(
self,
name: str,
vhdlLibraries: Nullable[Iterable[VHDLLibrary] | Mapping[str, VHDLLibrary]] = None,
testsuites: Nullable[Iterable[Testsuite] | Mapping[str, Testsuite]] = None,
project: Nullable[Base] = None
) -> None:
"""
Initialize an OSVVM build.
:param name: Name of the build.
:param vhdlLibraries: Optional, list or dictionary of VHDL libraries.
:param testsuites: Optional, list or dictionary of OSVVM testsuites.
:param project: Optional, reference to the parent project.
:raises TypeError: When parameter 'name' is not of type string.
:raises ValueError: When parameter 'name' is empty.
:raises TypeError: When parameter 'project' is not of type :class:`Project`.
:raises TypeError: When parameter 'vhdlLibraries' is not an iterable or mapping.
:raises TypeError: When parameter 'vhdlLibraries' contains an element not of type :class:`VHDLLibrary`.
:raises TypeError: When parameter 'testsuites' is not an iterable or mapping.
:raises TypeError: When parameter 'testsuites' contains an element not of type :class:`Testsuites`.
"""
if project is None:
super().__init__(name, None)
elif isinstance(project, Project):
super().__init__(name, project)
project._builds[name] = self
else: # pragma: no cover
ex = TypeError(f"Parameter 'project' is not a Project.")
ex.add_note(f"Got type '{getFullyQualifiedName(project)}'.")
raise ex
self._includedFiles = []
self._vhdlLibraries = {}
if vhdlLibraries is None:
pass
elif isinstance(vhdlLibraries, Mapping):
for key, value in vhdlLibraries.items():
# TODO: check used datatypes
value._parent = self
self._vhdlLibraries[key] = value
elif isinstance(vhdlLibraries, Iterable):
for item in vhdlLibraries:
if not isinstance(item, VHDLLibrary): # pragma: no cover
ex = TypeError(f"Parameter 'vhdlLibraries' contains elements not of type VHDLLibrary.")
ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.")
raise ex
item._parent = self
self._vhdlLibraries[item._name] = item
else: # pragma: no cover
ex = TypeError(f"Parameter 'libraries' is not an iterable of VHDLLibrary nor a mapping of VHDLLibrary.")
ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibraries)}'.")
raise ex
self._testsuites = {}
if testsuites is None:
pass
elif isinstance(testsuites, Mapping):
for key, value in testsuites.items():
# TODO: check used datatypes
value._parent = self
self._testsuites[key] = value
elif isinstance(testsuites, Iterable):
for item in testsuites:
if not isinstance(item, Testsuite): # pragma: no cover
ex = TypeError(f"Parameter 'testsuites' contains elements not of type Testsuite.")
ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.")
raise ex
item._parent = self
self._testsuites[item._name] = item
else: # pragma: no cover
ex = TypeError(f"Parameter 'testsuites' is not an iterable of Testsuite nor a mapping of Testsuite.")
ex.add_note(f"Got type '{getFullyQualifiedName(testsuites)}'.")
raise ex
@readonly
def Project(self) -> Nullable["Project"]:
"""
Read-only property to access the parent project (:attr:`_parent`).
:returns: The parent project.
"""
return self._parent
@readonly
def IncludedFiles(self) -> Generator[Path, None, None]:
"""
Read-only property to return a generator for all included (loaded) ``*.pro`` files (:attr:`_includedFiles`).
:returns: The sequence of loaded ``*.pro`` files.
"""
return (file for file in self._includedFiles)
@readonly
def VHDLLibraries(self) -> Dict[str, VHDLLibrary]:
"""
Read-only property to access the dictionary of VHDL libraries (:attr:`_vhdlLibraries`).
:returns: The dictionary of VHDL libraries.
"""
return self._vhdlLibraries
@readonly
def Testsuites(self) -> Dict[str, Testsuite]:
"""
Read-only property to access the dictionary of testsuites (:attr:`_testsuites`).
:returns: The dictionary of testsuites.
"""
return self._testsuites
[docs]
def AddVHDLLibrary(self, vhdlLibrary: VHDLLibrary) -> None:
"""
Add a VHDL library to the build.
:param vhdlLibrary: VHDL library to add.
:raises TypeError: When parameter 'vhdlLibrary' is not of type :class:`VHDLLibrary`.
"""
if not isinstance(vhdlLibrary, VHDLLibrary): # pragma: no cover
ex = TypeError(f"Parameter 'vhdlLibrary' is not a VHDLLibrary.")
ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibrary)}'.")
raise ex
vhdlLibrary._parent = self
self._vhdlLibraries[vhdlLibrary._name] = vhdlLibrary
[docs]
def AddTestsuite(self, testsuite: Testsuite) -> None:
"""
Add a testsuite to the build.
:param testsuite: Testsuite to add.
:raises TypeError: When parameter 'testsuite' is not of type :class:`Testsuite`.
"""
if not isinstance(testsuite, Testsuite): # pragma: no cover
ex = TypeError(f"Parameter 'testsuite' is not a Testsuite.")
ex.add_note(f"Got type '{getFullyQualifiedName(testsuite)}'.")
raise ex
testsuite._parent = self
self._testsuites[testsuite._name] = testsuite
[docs]
def __repr__(self) -> str:
return f"Build: {self._name}"
[docs]
@export
class Project(Named):
"""
An OSVVM project containing one or multiple OSVVM builds.
"""
_builds: Dict[str, Build] #: Dictionary of builds
[docs]
def __init__(
self,
name: str,
builds: Nullable[Iterable[Build] | Mapping[str, Build]] = None
) -> None:
"""
Initializes an OSVVM project.
:param name: Name of the build.
:param builds: Optional, list or dictionary of OSVVM builds.
:raises TypeError: When parameter 'name' is not of type string.
:raises ValueError: When parameter 'name' is empty.
:raises TypeError: When parameter 'builds' is not an iterable or mapping.
:raises TypeError: When parameter 'builds' contains an element not of type :class:`Build`.
"""
super().__init__(name, None)
self._builds = {}
if builds is None:
pass
elif isinstance(builds, Mapping):
for key, value in builds.items():
# TODO: check used datatypes
value._parent = self
self._builds[key] = value
elif isinstance(builds, Iterable):
for item in builds:
if not isinstance(item, Build): # pragma: no cover
ex = TypeError(f"Parameter 'builds' contains elements not of type Build.")
ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.")
raise ex
item._parent = self
self._builds[item._name] = item
else: # pragma: no cover
ex = TypeError(f"Parameter 'builds' is not an iterable of Build nor a mapping of Build.")
ex.add_note(f"Got type '{getFullyQualifiedName(builds)}'.")
raise ex
@readonly
def Builds(self) -> Dict[str, Build]:
"""
Read-only property to access the dictionary of builds (:attr:`_builds`).
:returns: The dictionary of builds.
"""
return self._builds
@readonly
def IncludedFiles(self) -> Generator[Path, None, None]:
"""
Read-only property to return a generator for all included (loaded) ``*.pro`` files.
.. note::
This generator iterates all builds (:attr:`_builds`) and returns a combined generator for all included files.
:returns: The sequence of loaded ``*.pro`` files.
"""
for build in self._builds.values():
yield from build.IncludedFiles
[docs]
def AddBuild(self, build: Build) -> None:
"""
Add a build to the project.
:param build: Build to add.
:raises TypeError: When parameter 'build' is not of type :class:`Build`.
"""
if not isinstance(build, Build): # pragma: no cover
ex = TypeError(f"Parameter 'build' is not a Build.")
ex.add_note(f"Got type '{getFullyQualifiedName(build)}'.")
raise ex
build._parent = self
self._builds[build._name] = build
[docs]
def __repr__(self) -> str:
return f"Project: {self._name}"
[docs]
@export
class Context(Base):
"""
The OSVVM TCL execution context.
When an OSVVM ``*.pro`` file is executed, it relies on a context for storing the currently objects and settings. For
example the currently used testsuite or the currently set VHDL language revision.
.. hint::
The context stores the last seen exception within Python scripting in :attr:`_lastException`, because TCL doesn't
forward a raised Python exception through TCL back into the Python context. It just raises a generic
:exc:`~tkinter.TclError`. The helper function :func:`~pyEDAA.OSVVM.Project.TCL.getException` help to restore the
original Python exception using this context object.
"""
# _tcl: TclEnvironment
_processor: "OsvvmProFileProcessor" #: The TCL processor.
_lastException: Nullable[Exception] #: Last Python exception seen.
_workingDirectory: Path #: The working directory, where the processing started.
_currentDirectory: Path #: The virtual working directory, e.g. updated by including other ``*.pro`` files.
_includedFiles: List[Path] #: A list of used ``*.pro`` files.
_vhdlversion: VHDLVersion #: The currently set VHDL language revision.
_vhdlLibrary: Nullable[VHDLLibrary] #: The currently active VHDL library.
_vhdlLibraries: Dict[str, VHDLLibrary] #: A dictionary of known VHDL libraries.
_testsuite: Nullable[Testsuite] #: The currently active OSVVM testsuite.
_testsuites: Dict[str, Testsuite] #: A dictionary of known testsuites.
_testcase: Nullable[Testcase] #: The currently active OSVVM testcase.
_options: Dict[int, Option] #: A dictionary of gathered options.
_build: Nullable[Build] #: The currently active OSVVM build.
_builds: Dict[str, Build] #: A dictionary of known OSVVM builds.
[docs]
def __init__(self) -> None:
"""
Initializes a TCL execution context for OSVVM ``*.pro`` file processing.
"""
super().__init__()
self._processor = None
self._lastException = None
self._workingDirectory = Path.cwd()
self._currentDirectory = self._workingDirectory
self._includedFiles = []
self._vhdlversion = VHDLVersion.VHDL2008
self._vhdlLibrary = None
self._vhdlLibraries = {}
self._testcase = None
self._testsuite = None
self._testsuites = {}
self._options = {}
self._build = None
self._builds = {}
[docs]
def Clear(self) -> None:
"""
Clear the TCL execution context.
"""
self._processor = None
self._lastException = None
self._workingDirectory = Path.cwd()
self._currentDirectory = self._workingDirectory
self._includedFiles = []
self._vhdlversion = VHDLVersion.VHDL2008
self._vhdlLibrary = None
self._vhdlLibraries = {}
self._testcase = None
self._testsuite = None
self._testsuites = {}
self._options = {}
self._build = None
self._builds = {}
@readonly
def Processor(self): # -> "Tk":
"""
Read-only property to access the TCL processor (:attr:`_processor`).
:returns: The TCL processor.
"""
return self._processor
@property
def LastException(self) -> Nullable[Exception]:
"""
Property to access the last seen Python exception (:attr:`_lastException`).
:returns: The last seen Python exception. This might return ``None``.
"""
lastException = self._lastException
self._lastException = None
return lastException
@LastException.setter
def LastException(self, value: Exception) -> None:
self._lastException = value
@readonly
def WorkingDirectory(self) -> Path:
"""
Read-only property to access the working directory (:attr:`_workingDirectory`).
:returns: The working directory.
"""
return self._workingDirectory
@readonly
def CurrentDirectory(self) -> Path:
"""
Read-only property to access the current directory (:attr:`_currentDirectory`).
The current directory is a virtual working directory used while processing ``*.pro`` files.
:returns: The current directory.
"""
return self._currentDirectory
@property
def VHDLVersion(self) -> VHDLVersion:
"""
Property to access the VHDL language revision (:attr:`_vhdlVersion`).
:returns: The currently set VHDL revision.
"""
return self._vhdlversion
@VHDLVersion.setter
def VHDLVersion(self, value: VHDLVersion) -> None:
self._vhdlversion = value
@readonly
def IncludedFiles(self) -> List[Path]:
"""
Read-only property to access list of included ``*.pro`` files (:attr:`_includedFiles`).
:returns: The list of loaded files.
"""
return self._includedFiles
@readonly
def VHDLLibrary(self) -> VHDLLibrary:
"""
Read-only property to access the currently active VHDL library (:attr:`_vhdlLibrary`).
:returns: The active VHDL libraries.
"""
return self._vhdlLibrary
@readonly
def VHDLLibraries(self) -> Dict[str, VHDLLibrary]:
"""
Read-only property to access the dictionary of known VHDL libraries (:attr:`_vhdlLibraries`).
:returns: The dictionary of VHDL libraries.
"""
return self._vhdlLibraries
@readonly
def Testsuite(self) -> Testsuite:
"""
Read-only property to access the currently active OSVVM testsuite (:attr:`_testsuite`).
:returns: The active OSVVM testsuite.
"""
return self._testsuite
@readonly
def Testsuites(self) -> Dict[str, Testsuite]:
"""
Read-only property to access the dictionary of known OSVVM testsuites (:attr:`_testsuites`).
:returns: The dictionary of OSVVM testsuites.
"""
return self._testsuites
@readonly
def TestCase(self) -> Testcase:
"""
Read-only property to access the currently active OSVVM testcase (:attr:`_testcase`).
:returns: The active OSVVM testcase.
"""
return self._testcase
@readonly
def Build(self) -> Build:
"""
Read-only property to access the currently active OSVVM build (:attr:`_build`).
:returns: The active OSVVM build.
"""
return self._build
@readonly
def Builds(self) -> Dict[str, Build]:
"""
Read-only property to access the dictionary of known OSVVM builds (:attr:`_build`).
:returns: The dictionary of OSVVM builds.
"""
return self._builds
[docs]
def ToProject(self, projectName: str) -> Project:
"""
Convert the context to an OSVVM project.
:param projectName: Name of the project.
:returns: OSVVM project.
"""
return Project(projectName, self._builds)
[docs]
def RaiseException(self, ex: Exception, cause: Nullable[Exception] = None) -> NoReturn:
"""
Raise an exception, but keep a reference to the exception object in the TCL execution context.
:param ex: Exception to be raised.
"""
if cause is not None:
ex.__cause__ = cause
self._lastException = ex
raise ex
[docs]
def BeginBuild(self, buildName: str) -> Build:
"""
Begin a new build context within the overall TCL execution context.
:param buildName: Name of the new build.
:returns: Currently active OSVVM build object.
:raises OSVVMException: When a VHDL library has been created outside a build context.
:raises OSVVMException: When a OSVVM testsuite has been created outside a build context.
"""
if len(self._vhdlLibraries) > 0:
ex = OSVVMException(f"VHDL libraries have been created outside of an OSVVM build script.")
ex.add_note(f"TCL command 'library' has been called before 'build'.")
raise ex
if len(self._testsuites) > 0:
ex = OSVVMException(f"Testsuites have been created outside of an OSVVM build script.")
ex.add_note(f"TCL command 'TestSuite' has been called before 'build'.")
raise ex
build = Build(buildName)
build._vhdlLibraries = self._vhdlLibraries
build._testsuites = self._testsuites
self._build = build
self._builds[buildName] = build
return build
[docs]
def EndBuild(self) -> Build:
"""
Finalize the currently active build context.
The TCL execution context is cleaned up: partially reset the context and initialize some fields with new data
structures.
:returns: The OSVVM build object.
"""
build = self._build
self._vhdlLibrary = None
self._vhdlLibraries = {}
self._testcase = None
self._testsuite = None
self._testsuites = {}
self._build = None
# TODO: should this be handled in LoadBuildFile ?
self._currentDirectory = self._workingDirectory
return build
[docs]
def IncludeFile(self, proFileOrBuildDirectory: Path) -> Path:
"""
Include a specific ``*.pro`` file or the ``*.pro`` file from an OSVVM build directory.
.. hint::
An OSVVM build directory is a directory:
* containing a ``build.pro`` file (new style), or
* containing a ``*.pro`` file with the same name as the directory its contained in (old style).
:param proFileOrBuildDirectory: The path to the ``*.pro`` file or directory containing a ``*.pro`` file. |br|
Only relative paths are supported.
:returns: The resolved path to the found ``*.pro`` file.
:raises TypeError: When parameter 'proFileOrBuildDirectory' is not of type :class:`~pathlib.Path`.
:raises OSVVMException: When parameter 'proFileOrBuildDirectory' contains an absolut path.
:raises OSVVMException: When the resolved path doesn't reference to a ``*.pro`` file.
:raises OSVVMException: When the resolved path isn't an OSVVM build directory.
:raises OSVVMException: When the resolved path neither references a ``*.pro`` file nor an OSVVM build
directory.
"""
if not isinstance(proFileOrBuildDirectory, Path): # pragma: no cover
ex = TypeError(f"Parameter 'proFileOrBuildDirectory' is not a Path.")
ex.add_note(f"Got type '{getFullyQualifiedName(proFileOrBuildDirectory)}'.")
self.RaiseException(ex)
if proFileOrBuildDirectory.is_absolute():
ex = OSVVMException(f"Absolute path '{proFileOrBuildDirectory}' not supported.")
self.RaiseException(ex)
path = (self._currentDirectory / proFileOrBuildDirectory).resolve()
if path.is_file():
if path.suffix == ".pro":
self._currentDirectory = path.parent.relative_to(self._workingDirectory, walk_up=True)
proFile = self._currentDirectory / path.name
else:
self.RaiseException(OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a *.pro file."))
elif path.is_dir():
self._currentDirectory = path
proFile = path / "build.pro"
if not proFile.exists():
proFile = path / f"{path.name}.pro"
if not proFile.exists(): # pragma: no cover
ex = OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a build directory.")
ex.__cause__ = FileNotFoundError(path / "build.pro")
self.RaiseException(ex)
else: # pragma: no cover
self.RaiseException(OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a *.pro file or build directory."))
self._includedFiles.append(proFile)
return proFile
[docs]
def EvaluateFile(self, proFile: Path) -> None:
"""
Evaluate a ``*.pro`` file.
:param proFile: OSVVM ``*.pro`` file to process.
"""
self._processor.EvaluateProFile(proFile)
[docs]
def SetLibrary(self, name: str) -> None:
"""
Set or create the currently active VHDL library.
If the VHDL library isn't known in the current context, create a new VHDL library with the given name.
:param name: Name of the VHDL library.
:returns: Activated VHDL library.
"""
try:
self._vhdlLibrary = self._vhdlLibraries[name]
except KeyError:
self._vhdlLibrary = VHDLLibrary(name, build=self._build)
self._vhdlLibraries[name] = self._vhdlLibrary
[docs]
def AddVHDLFile(self, vhdlFile: VHDLSourceFile) -> None:
"""
Add a VHDL source file to the currently active VHDL library.
The VHDL source file's VHDL revision is derived from currently active VHDL revision of the TCL execution context.
.. note::
If there is no active VHDL library in the context, a new VHDL library named ``default`` is created.
:param vhdlFile: VHDL source file to be added.
"""
if self._vhdlLibrary is None:
self.SetLibrary("default")
vhdlFile.VHDLVersion = self._vhdlversion
self._vhdlLibrary.AddFile(vhdlFile)
[docs]
def SetTestsuite(self, testsuiteName: str) -> None:
"""
Set or create the currently active OSVVM testsuite.
If the testsuite isn't known in the current context, create a new testsuite with the given name.
:param testsuiteName: Name of the OSVVM testsuite.
:returns: Activated OSVVM testsuite.
"""
try:
self._testsuite = self._testsuites[testsuiteName]
except KeyError:
self._testsuite = Testsuite(testsuiteName)
self._testsuites[testsuiteName] = self._testsuite
# TODO: should this be called differently then Add***, because it doesn't take an object, but a new and creates a new object.
[docs]
def AddTestcase(self, testName: str) -> TestCase:
"""
Create a new testcase and add to the currently active OSVVM testsuite.
.. note::
If there is no active OSVVM testsuite in the context, a new testsuite named ``default`` is created.
:param testName: Name of the testcase.
:returns: The created OSVVM testcase object.
"""
if self._testsuite is None:
self.SetTestsuite("default")
self._testcase = Testcase(testName)
self._testsuite._testcases[testName] = self._testcase
return self._testcase
[docs]
def SetTestcaseToplevel(self, toplevel: str) -> TestCase:
"""
Set the testcase's toplevel entity or configuration name.
:param toplevel: Name of the toplevel entity or configuration.
:returns: The currently active OSVVM testcase.
:raises OSVVMException: When there is no active OSVVM testcase.
"""
if self._testcase is None:
self.RaiseException(OSVVMException("Can't set testcase toplevel, because no testcase was setup."))
self._testcase.SetToplevel(toplevel)
return self._testcase
# TODO: this this an add operation or a register operation?
[docs]
def AddOption(self, option: Option) -> int:
"""
Register a new option and return a unique ID.
.. hint::
TCL can't pass complex Python objects through the TCL layer back to Python. Therefore, complex objects like
options are registered in a dictionary and a unique ID (integer) is returned. Back in Python, this ID can be
converted back to the Python object.
This unique ID is based on :func:`id`.
:param option: Option to register.
:returns: Unique option ID.
"""
optionID = id(option)
self._options[optionID] = option
return optionID
osvvmContext: Context = Context()
"""
Global OSVVM processing context.
:type: Context
"""