# ==================================================================================================================== #
# _____ ____ _ _ ____ _ #
# _ __ _ _| ____| _ \ / \ / \ | _ \ ___ _ __ ___ _ __| |_ ___ #
# | '_ \| | | | _| | | | |/ _ \ / _ \ | |_) / _ \ '_ \ / _ \| '__| __/ __| #
# | |_) | |_| | |___| |_| / ___ \ / ___ \ _| _ < __/ |_) | (_) | | | |_\__ \ #
# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)_| \_\___| .__/ \___/|_| \__|___/ #
# |_| |___/ |_| #
# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# #
# License: #
# ==================================================================================================================== #
# Copyright 2026-2026 Electronic Design Automation Abstraction (EDA²) #
# #
# 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 #
# ==================================================================================================================== #
#
"""
The crosses of the covergroup coverage of the UCIS XML interchange format: their options and bins, each a combination
of the crossed coverpoints' bins.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Iterable, Optional as Nullable, Self
from lxml.etree import _Element
from pyTooling.Common import getFullyQualifiedName
from pyTooling.Decorators import export, readonly
from pyEDAA.Reports.CodeCoverage.UCIS.Bins import BinContents
from pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions import CrossOptions
from pyEDAA.Reports.CodeCoverage.UCIS.Coverpoints import BinBase
from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, UserAttribute
if TYPE_CHECKING:
from pyEDAA.Reports.CodeCoverage.UCIS.Covergroups import CovergroupInstance
[docs]
@export
class Cross(Base):
"""
A ``<cross>`` of a covergroup instance: the expressions - coverpoints - it crosses, its options and bins.
"""
_parent: Nullable[CovergroupInstance] #: The covergroup instance the cross belongs to.
_name: str #: Name of the cross.
_key: str #: UCIS key of the cross.
_options: CrossOptions #: The options of the cross.
_alias: Nullable[str] #: An alias of the cross' name.
_expressions: list[str] #: The crossed expressions, e.g. coverpoints' names.
_bins: list[CrossBin] #: The bins.
[docs]
def __init__(
self,
name: str,
key: str,
options: CrossOptions,
expressions: Nullable[Iterable[str]] = None,
alias: Nullable[str] = None,
userAttributes: Nullable[Iterable[UserAttribute]] = None,
*,
parent: Nullable[CovergroupInstance] = None
) -> None:
"""
Initialize the cross, and append it to the crosses of its covergroup instance.
Its bins are added by creating them with this cross as their parent.
:param name: Name of the cross, e.g. ``xab``.
:param key: UCIS key of the cross.
:param options: The options of the cross.
:param expressions: Optional, the crossed expressions, e.g. ``["cvpt_a", "cvpt_b"]``. Default: ``None``.
:param alias: Optional, an alias of the cross' name. Default: ``None``.
:param userAttributes: Optional, the user-defined attributes. Default: ``None``.
:param parent: Optional, the covergroup instance the cross belongs to. Default: ``None``.
:raises ValueError: If parameter ``name`` is ``None``.
:raises TypeError: If parameter ``name`` isn't of type :class:`str`.
:raises ValueError: If parameter ``key`` is ``None``.
:raises TypeError: If parameter ``key`` isn't of type :class:`str`.
:raises ValueError: If parameter ``options`` is ``None``.
:raises TypeError: If parameter ``options`` isn't of type
:class:`~pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions.CrossOptions`.
:raises TypeError: If parameter ``expressions`` isn't iterable.
:raises TypeError: If parameter ``expressions`` contains an element not of type :class:`str`.
:raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
:raises TypeError: If parameter ``userAttributes`` isn't iterable.
:raises TypeError: If parameter ``userAttributes`` contains an element not of type
:class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
:raises TypeError: If parameter ``parent`` isn't of type
:class:`~pyEDAA.Reports.CodeCoverage.UCIS.Covergroups.CovergroupInstance`.
"""
from pyEDAA.Reports.CodeCoverage.UCIS.Covergroups import CovergroupInstance
super().__init__(userAttributes)
for parameterName, value in (
("name", name),
("key", key)
):
if value is None:
raise ValueError(f"Parameter '{parameterName}' is None.")
elif not isinstance(value, str):
ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
raise ex
if options is None:
raise ValueError("Parameter 'options' is None.")
elif not isinstance(options, CrossOptions):
ex = TypeError("Parameter 'options' is not of type 'CrossOptions'.")
ex.add_note(f"Got type '{getFullyQualifiedName(options)}'.")
raise ex
if alias is not None and not isinstance(alias, str):
ex = TypeError("Parameter 'alias' is not of type 'str'.")
ex.add_note(f"Got type '{getFullyQualifiedName(alias)}'.")
raise ex
if parent is not None and not isinstance(parent, CovergroupInstance):
ex = TypeError("Parameter 'parent' is not of type 'CovergroupInstance'.")
ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
raise ex
self._parent = parent
self._name = name
self._key = key
self._options = options
self._alias = alias
self._expressions = expressionList = []
self._bins = []
if expressions is not None:
if not isinstance(expressions, Iterable):
ex = TypeError("Parameter 'expressions' is not iterable.")
ex.add_note(f"Got type '{getFullyQualifiedName(expressions)}'.")
raise ex
for expression in expressions:
if not isinstance(expression, str):
ex = TypeError("Parameter 'expressions' contains an element not of type 'str'.")
ex.add_note(f"Got type '{getFullyQualifiedName(expression)}'.")
raise ex
expressionList.append(expression)
if parent is not None:
parent._crosses.append(self)
[docs]
@classmethod
def Parse(cls, element: _Element, *, parent: Nullable[CovergroupInstance] = None) -> Self:
"""
Parse a cross, its options and bins from its ``<cross>`` element.
:param element: The ``<cross>`` element.
:param parent: Optional, the covergroup instance the cross belongs to. Default: ``None``.
:returns: The cross.
:raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
"""
cross = cls(
element.attrib["name"],
element.attrib["key"],
CrossOptions.Parse(element.find("{*}options")),
[(expressionElement.text or "").strip() for expressionElement in element.iterfind("{*}crossExpr")],
element.attrib.get("alias"),
cls._ParseUserAttributes(element),
parent=parent
)
for binElement in element.iterfind("{*}crossBin"):
CrossBin.Parse(binElement, parent=cross)
return cross
@readonly
def Parent(self) -> Nullable[CovergroupInstance]:
"""
Read-only property to access the covergroup instance the cross belongs to (:attr:`_parent`).
:returns: The covergroup instance; ``None``, if the cross belongs to none.
"""
return self._parent
@readonly
def Name(self) -> str:
"""
Read-only property to access the name of the cross (:attr:`_name`).
:returns: The name, as declared.
"""
return self._name
@readonly
def Key(self) -> str:
"""
Read-only property to access the UCIS key of the cross (:attr:`_key`).
:returns: The key, as stated.
"""
return self._key
@readonly
def Options(self) -> CrossOptions:
"""
Read-only property to access the options of the cross (:attr:`_options`).
:returns: The options.
"""
return self._options
@readonly
def Alias(self) -> Nullable[str]:
"""
Read-only property to access the alias of the cross' name (:attr:`_alias`).
:returns: The alias; ``None``, if the report states none.
"""
return self._alias
@readonly
def Expressions(self) -> list[str]:
"""
Read-only property to access the crossed expressions (:attr:`_expressions`).
:returns: The expressions, e.g. coverpoints' names, in the order the report lists them.
"""
return self._expressions
@readonly
def Bins(self) -> list[CrossBin]:
"""
Read-only property to access the bins (:attr:`_bins`).
:returns: The bins, in the order the report lists them.
"""
return self._bins
[docs]
@export
class CrossBin(BinBase):
"""
A ``<crossBin>`` of a cross: the indices of the crossed coverpoints' bins it combines, and how often the combination
was hit.
"""
_parent: Nullable[Cross] #: The cross the bin belongs to.
_indices: list[int] #: The indices of the crossed coverpoints' bins, one per coverpoint.
_contents: BinContents #: How often the combination was hit.
[docs]
def __init__(
self,
indices: Iterable[int],
contents: BinContents,
binType: str = "default",
alias: Nullable[str] = None,
name: Nullable[str] = None,
key: Nullable[str] = None,
userAttributes: Nullable[Iterable[UserAttribute]] = None,
*,
parent: Nullable[Cross] = None
) -> None:
"""
Initialize the cross bin, and append it to the bins of its cross.
:param indices: The indices of the crossed coverpoints' bins, one per coverpoint.
:param contents: How often the combination was hit.
:param binType: Optional, kind of the bin, e.g. ``default``, ``ignore`` or ``illegal``. Default:
``"default"``.
:param alias: Optional, an alias of the bin's name. Default: ``None``.
:param name: Optional, name of the bin, as pyucis writes it. Default: ``None``.
:param key: Optional, UCIS key of the bin, as pyucis writes it. Default: ``None``.
:param userAttributes: Optional, the user-defined attributes. Default: ``None``.
:param parent: Optional, the cross the bin belongs to. Default: ``None``.
:raises ValueError: If parameter ``indices`` is ``None``.
:raises TypeError: If parameter ``indices`` isn't iterable.
:raises TypeError: If parameter ``indices`` contains an element not of type :class:`int`.
:raises ValueError: If parameter ``indices`` is empty.
:raises ValueError: If parameter ``contents`` is ``None``.
:raises TypeError: If parameter ``contents`` isn't of type
:class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.BinContents`.
:raises ValueError: If parameter ``binType`` is ``None``.
:raises TypeError: If parameter ``binType`` isn't of type :class:`str`.
:raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
:raises TypeError: If parameter ``name`` isn't of type :class:`str`.
:raises TypeError: If parameter ``key`` isn't of type :class:`str`.
:raises TypeError: If parameter ``userAttributes`` isn't iterable.
:raises TypeError: If parameter ``userAttributes`` contains an element not of type
:class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
:raises TypeError: If parameter ``parent`` isn't of type :class:`Cross`.
"""
super().__init__(binType, alias, name, key, userAttributes)
if contents is None:
raise ValueError("Parameter 'contents' is None.")
elif not isinstance(contents, BinContents):
ex = TypeError("Parameter 'contents' is not of type 'BinContents'.")
ex.add_note(f"Got type '{getFullyQualifiedName(contents)}'.")
raise ex
if parent is not None and not isinstance(parent, Cross):
ex = TypeError("Parameter 'parent' is not of type 'Cross'.")
ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
raise ex
self._parent = parent
self._indices = indexList = []
self._contents = contents
if indices is None:
raise ValueError("Parameter 'indices' is None.")
elif not isinstance(indices, Iterable):
ex = TypeError("Parameter 'indices' is not iterable.")
ex.add_note(f"Got type '{getFullyQualifiedName(indices)}'.")
raise ex
for index in indices:
if not isinstance(index, int) or isinstance(index, bool):
ex = TypeError("Parameter 'indices' contains an element not of type 'int'.")
ex.add_note(f"Got type '{getFullyQualifiedName(index)}'.")
raise ex
indexList.append(index)
if len(self._indices) == 0:
raise ValueError("Parameter 'indices' is empty.")
if parent is not None:
parent._bins.append(self)
[docs]
@classmethod
def Parse(cls, element: _Element, *, parent: Nullable[Cross] = None) -> Self:
"""
Parse a cross bin from its ``<crossBin>`` element.
:param element: The ``<crossBin>`` element.
:param parent: Optional, the cross the bin belongs to. Default: ``None``.
:returns: The cross bin.
:raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
"""
return cls(
[int(indexElement.text) for indexElement in element.iterfind("{*}index")],
BinContents.Parse(element.find("{*}contents")),
element.attrib.get("type", "default"),
element.attrib.get("alias"),
element.attrib.get("name"),
element.attrib.get("key"),
cls._ParseUserAttributes(element),
parent=parent
)
@readonly
def Parent(self) -> Nullable[Cross]:
"""
Read-only property to access the cross the bin belongs to (:attr:`_parent`).
:returns: The cross; ``None``, if the bin belongs to none.
"""
return self._parent
@readonly
def Indices(self) -> list[int]:
"""
Read-only property to access the indices of the crossed coverpoints' bins (:attr:`_indices`).
:returns: The indices, one per crossed coverpoint.
"""
return self._indices
@readonly
def Contents(self) -> BinContents:
"""
Read-only property to access how often the combination was hit (:attr:`_contents`).
:returns: The contents: the count and the tests.
"""
return self._contents