# ==================================================================================================================== #
# _____ ____ _ _ ____ _ #
# _ __ _ _| ____| _ \ / \ / \ | _ \ ___ _ __ ___ _ __| |_ ___ #
# | '_ \| | | | _| | | | |/ _ \ / _ \ | |_) / _ \ '_ \ / _ \| '__| __/ __| #
# | |_) | |_| | |___| |_| / ___ \ / ___ \ _| _ < __/ |_) | (_) | | | |_\__ \ #
# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)_| \_\___| .__/ \___/|_| \__|___/ #
# |_| |___/ |_| #
# ==================================================================================================================== #
# 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 options of the covergroup coverage of the UCIS XML interchange format: the ``<options>`` of a covergroup instance,
a coverpoint or a cross, as SystemVerilog declares them.
"""
from __future__ import annotations
from typing import Self
from lxml.etree import _Element
from pyTooling.Common import getFullyQualifiedName
from pyTooling.Decorators import export, readonly
from pyTooling.MetaClasses import ExtendedType, abstractclass
from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base
[docs]
@export
@abstractclass
class Options(metaclass=ExtendedType, slots=True):
"""
Base-class of the ``<options>`` of a covergroup instance, a coverpoint or a cross: the options SystemVerilog declares
for all three.
It is a value, as a path is: it names no parent.
"""
_weight: int #: Weight in computing the coverage.
_goal: int #: Coverage goal, in percent.
_comment: str #: A comment.
_atLeast: int #: How often a bin must be hit to be covered.
[docs]
def __init__(self, weight: int = 1, goal: int = 100, comment: str = "", atLeast: int = 1) -> None:
"""
Initialize the options.
:param weight: Optional, weight in computing the coverage. Default: ``1``.
:param goal: Optional, coverage goal, in percent. Default: ``100``.
:param comment: Optional, a comment. Default: ``""``.
:param atLeast: Optional, how often a bin must be hit to be covered. Default: ``1``.
:raises ValueError: If parameter ``weight`` is ``None``.
:raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
:raises ValueError: If parameter ``weight`` is negative.
:raises ValueError: If parameter ``goal`` is ``None``.
:raises TypeError: If parameter ``goal`` isn't of type :class:`int`.
:raises ValueError: If parameter ``goal`` is negative.
:raises ValueError: If parameter ``comment`` is ``None``.
:raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
:raises ValueError: If parameter ``atLeast`` is ``None``.
:raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`.
:raises ValueError: If parameter ``atLeast`` is negative.
"""
for parameterName, value in (
("weight", weight),
("goal", goal),
("atLeast", atLeast)
):
if value is None:
raise ValueError(f"Parameter '{parameterName}' is None.")
elif not isinstance(value, int) or isinstance(value, bool):
ex = TypeError(f"Parameter '{parameterName}' is not of type 'int'.")
ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
raise ex
elif value < 0:
ex = ValueError(f"Parameter '{parameterName}' is negative.")
ex.add_note(f"Got value '{value}'.")
raise ex
if comment is None:
raise ValueError("Parameter 'comment' is None.")
elif not isinstance(comment, str):
ex = TypeError("Parameter 'comment' is not of type 'str'.")
ex.add_note(f"Got type '{getFullyQualifiedName(comment)}'.")
raise ex
self._weight = weight
self._goal = goal
self._comment = comment
self._atLeast = atLeast
@readonly
def Weight(self) -> int:
"""
Read-only property to access the weight in computing the coverage (:attr:`_weight`).
:returns: The weight; ``1``, if the report states none.
"""
return self._weight
@readonly
def Goal(self) -> int:
"""
Read-only property to access the coverage goal (:attr:`_goal`).
:returns: The goal, in percent; ``100``, if the report states none.
"""
return self._goal
@readonly
def Comment(self) -> str:
"""
Read-only property to access the comment (:attr:`_comment`).
:returns: The comment; empty, if the report states none.
"""
return self._comment
@readonly
def AtLeast(self) -> int:
"""
Read-only property to access how often a bin must be hit to be covered (:attr:`_atLeast`).
:returns: The count; ``1``, if the report states none.
"""
return self._atLeast
[docs]
@export
class CoverpointOptions(Options):
"""
The ``<options>`` of a coverpoint.
"""
_detectOverlap: bool #: Whether to warn about overlapping bins.
_autoBinMax: int #: Maximum number of bins created automatically.
[docs]
def __init__(
self,
weight: int = 1,
goal: int = 100,
comment: str = "",
atLeast: int = 1,
detectOverlap: bool = False,
autoBinMax: int = 64
) -> None:
"""
Initialize the options of a coverpoint.
:param weight: Optional, weight in computing the coverage. Default: ``1``.
:param goal: Optional, coverage goal, in percent. Default: ``100``.
:param comment: Optional, a comment. Default: ``""``.
:param atLeast: Optional, how often a bin must be hit to be covered. Default: ``1``.
:param detectOverlap: Optional, whether to warn about overlapping bins. Default: ``False``.
:param autoBinMax: Optional, maximum number of bins created automatically. Default: ``64``.
:raises ValueError: If parameter ``weight`` is ``None``.
:raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
:raises ValueError: If parameter ``weight`` is negative.
:raises ValueError: If parameter ``goal`` is ``None``.
:raises TypeError: If parameter ``goal`` isn't of type :class:`int`.
:raises ValueError: If parameter ``goal`` is negative.
:raises ValueError: If parameter ``comment`` is ``None``.
:raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
:raises ValueError: If parameter ``atLeast`` is ``None``.
:raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`.
:raises ValueError: If parameter ``atLeast`` is negative.
:raises ValueError: If parameter ``detectOverlap`` is ``None``.
:raises TypeError: If parameter ``detectOverlap`` isn't of type :class:`bool`.
:raises ValueError: If parameter ``autoBinMax`` is ``None``.
:raises TypeError: If parameter ``autoBinMax`` isn't of type :class:`int`.
:raises ValueError: If parameter ``autoBinMax`` is negative.
"""
super().__init__(weight, goal, comment, atLeast)
if detectOverlap is None:
raise ValueError("Parameter 'detectOverlap' is None.")
elif not isinstance(detectOverlap, bool):
ex = TypeError("Parameter 'detectOverlap' is not of type 'bool'.")
ex.add_note(f"Got type '{getFullyQualifiedName(detectOverlap)}'.")
raise ex
if autoBinMax is None:
raise ValueError("Parameter 'autoBinMax' is None.")
elif not isinstance(autoBinMax, int) or isinstance(autoBinMax, bool):
ex = TypeError("Parameter 'autoBinMax' is not of type 'int'.")
ex.add_note(f"Got type '{getFullyQualifiedName(autoBinMax)}'.")
raise ex
elif autoBinMax < 0:
ex = ValueError("Parameter 'autoBinMax' is negative.")
ex.add_note(f"Got value '{autoBinMax}'.")
raise ex
self._detectOverlap = detectOverlap
self._autoBinMax = autoBinMax
[docs]
@classmethod
def Parse(cls, element: _Element) -> Self:
"""
Parse the options of a coverpoint from its ``<options>`` element; an option it doesn't state has its default.
:param element: The ``<options>`` element.
:returns: The options.
"""
attributes = element.attrib
return cls(
int(attributes.get("weight", "1")),
int(attributes.get("goal", "100")),
attributes.get("comment", ""),
int(attributes.get("at_least", "1")),
Base._ParseBoolean(attributes.get("detect_overlap", "false")),
int(attributes.get("auto_bin_max", "64"))
)
@readonly
def DetectOverlap(self) -> bool:
"""
Read-only property to access whether to warn about overlapping bins (:attr:`_detectOverlap`).
:returns: ``True``, if overlapping bins are reported.
"""
return self._detectOverlap
@readonly
def AutoBinMax(self) -> int:
"""
Read-only property to access the maximum number of bins created automatically (:attr:`_autoBinMax`).
:returns: The maximum; ``64``, if the report states none.
"""
return self._autoBinMax
[docs]
@export
class CrossOptions(Options):
"""
The ``<options>`` of a cross.
"""
_crossNumPrintMissing: int #: Number of uncovered cross bins to save and report.
[docs]
def __init__(
self,
weight: int = 1,
goal: int = 100,
comment: str = "",
atLeast: int = 1,
crossNumPrintMissing: int = 0
) -> None:
"""
Initialize the options of a cross.
:param weight: Optional, weight in computing the coverage. Default: ``1``.
:param goal: Optional, coverage goal, in percent. Default: ``100``.
:param comment: Optional, a comment. Default: ``""``.
:param atLeast: Optional, how often a bin must be hit to be covered. Default: ``1``.
:param crossNumPrintMissing: Optional, number of uncovered cross bins to save and report. Default: ``0``.
:raises ValueError: If parameter ``weight`` is ``None``.
:raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
:raises ValueError: If parameter ``weight`` is negative.
:raises ValueError: If parameter ``goal`` is ``None``.
:raises TypeError: If parameter ``goal`` isn't of type :class:`int`.
:raises ValueError: If parameter ``goal`` is negative.
:raises ValueError: If parameter ``comment`` is ``None``.
:raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
:raises ValueError: If parameter ``atLeast`` is ``None``.
:raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`.
:raises ValueError: If parameter ``atLeast`` is negative.
:raises ValueError: If parameter ``crossNumPrintMissing`` is ``None``.
:raises TypeError: If parameter ``crossNumPrintMissing`` isn't of type :class:`int`.
:raises ValueError: If parameter ``crossNumPrintMissing`` is negative.
"""
super().__init__(weight, goal, comment, atLeast)
if crossNumPrintMissing is None:
raise ValueError("Parameter 'crossNumPrintMissing' is None.")
elif not isinstance(crossNumPrintMissing, int) or isinstance(crossNumPrintMissing, bool):
ex = TypeError("Parameter 'crossNumPrintMissing' is not of type 'int'.")
ex.add_note(f"Got type '{getFullyQualifiedName(crossNumPrintMissing)}'.")
raise ex
elif crossNumPrintMissing < 0:
ex = ValueError("Parameter 'crossNumPrintMissing' is negative.")
ex.add_note(f"Got value '{crossNumPrintMissing}'.")
raise ex
self._crossNumPrintMissing = crossNumPrintMissing
[docs]
@classmethod
def Parse(cls, element: _Element) -> Self:
"""
Parse the options of a cross from its ``<options>`` element; an option it doesn't state has its default.
:param element: The ``<options>`` element.
:returns: The options.
"""
attributes = element.attrib
return cls(
int(attributes.get("weight", "1")),
int(attributes.get("goal", "100")),
attributes.get("comment", ""),
int(attributes.get("at_least", "1")),
int(attributes.get("cross_num_print_missing", "0"))
)
@readonly
def CrossNumPrintMissing(self) -> int:
"""
Read-only property to access the number of uncovered cross bins to save and report (:attr:`_crossNumPrintMissing`).
:returns: The number; ``0``, if the report states none.
"""
return self._crossNumPrintMissing
[docs]
@export
class CovergroupOptions(CoverpointOptions):
"""
The ``<options>`` of a covergroup instance.
"""
_crossNumPrintMissing: int #: Number of uncovered cross bins to save and report.
_perInstance: bool #: Whether the instance's coverage is kept on its own.
_mergeInstances: bool #: Whether the covergroup's coverage merges its instances.
[docs]
def __init__(
self,
weight: int = 1,
goal: int = 100,
comment: str = "",
atLeast: int = 1,
detectOverlap: bool = False,
autoBinMax: int = 64,
crossNumPrintMissing: int = 0,
perInstance: bool = False,
mergeInstances: bool = False
) -> None:
"""
Initialize the options of a covergroup instance.
:param weight: Optional, weight in computing the coverage. Default: ``1``.
:param goal: Optional, coverage goal, in percent. Default: ``100``.
:param comment: Optional, a comment. Default: ``""``.
:param atLeast: Optional, how often a bin must be hit to be covered. Default: ``1``.
:param detectOverlap: Optional, whether to warn about overlapping bins. Default: ``False``.
:param autoBinMax: Optional, maximum number of bins created automatically. Default: ``64``.
:param crossNumPrintMissing: Optional, number of uncovered cross bins to save and report. Default: ``0``.
:param perInstance: Optional, whether the instance's coverage is kept on its own. Default: ``False``.
:param mergeInstances: Optional, whether the covergroup's coverage merges its instances. Default:
``False``.
:raises ValueError: If parameter ``weight`` is ``None``.
:raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
:raises ValueError: If parameter ``weight`` is negative.
:raises ValueError: If parameter ``goal`` is ``None``.
:raises TypeError: If parameter ``goal`` isn't of type :class:`int`.
:raises ValueError: If parameter ``goal`` is negative.
:raises ValueError: If parameter ``comment`` is ``None``.
:raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
:raises ValueError: If parameter ``atLeast`` is ``None``.
:raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`.
:raises ValueError: If parameter ``atLeast`` is negative.
:raises ValueError: If parameter ``detectOverlap`` is ``None``.
:raises TypeError: If parameter ``detectOverlap`` isn't of type :class:`bool`.
:raises ValueError: If parameter ``autoBinMax`` is ``None``.
:raises TypeError: If parameter ``autoBinMax`` isn't of type :class:`int`.
:raises ValueError: If parameter ``autoBinMax`` is negative.
:raises ValueError: If parameter ``crossNumPrintMissing`` is ``None``.
:raises TypeError: If parameter ``crossNumPrintMissing`` isn't of type :class:`int`.
:raises ValueError: If parameter ``crossNumPrintMissing`` is negative.
:raises ValueError: If parameter ``perInstance`` is ``None``.
:raises TypeError: If parameter ``perInstance`` isn't of type :class:`bool`.
:raises ValueError: If parameter ``mergeInstances`` is ``None``.
:raises TypeError: If parameter ``mergeInstances`` isn't of type :class:`bool`.
"""
super().__init__(weight, goal, comment, atLeast, detectOverlap, autoBinMax)
if crossNumPrintMissing is None:
raise ValueError("Parameter 'crossNumPrintMissing' is None.")
elif not isinstance(crossNumPrintMissing, int) or isinstance(crossNumPrintMissing, bool):
ex = TypeError("Parameter 'crossNumPrintMissing' is not of type 'int'.")
ex.add_note(f"Got type '{getFullyQualifiedName(crossNumPrintMissing)}'.")
raise ex
elif crossNumPrintMissing < 0:
ex = ValueError("Parameter 'crossNumPrintMissing' is negative.")
ex.add_note(f"Got value '{crossNumPrintMissing}'.")
raise ex
if perInstance is None:
raise ValueError("Parameter 'perInstance' is None.")
elif not isinstance(perInstance, bool):
ex = TypeError("Parameter 'perInstance' is not of type 'bool'.")
ex.add_note(f"Got type '{getFullyQualifiedName(perInstance)}'.")
raise ex
if mergeInstances is None:
raise ValueError("Parameter 'mergeInstances' is None.")
elif not isinstance(mergeInstances, bool):
ex = TypeError("Parameter 'mergeInstances' is not of type 'bool'.")
ex.add_note(f"Got type '{getFullyQualifiedName(mergeInstances)}'.")
raise ex
self._crossNumPrintMissing = crossNumPrintMissing
self._perInstance = perInstance
self._mergeInstances = mergeInstances
[docs]
@classmethod
def Parse(cls, element: _Element) -> Self:
"""
Parse the options of a covergroup instance from its ``<options>`` element; an option it doesn't state has its
default.
:param element: The ``<options>`` element.
:returns: The options.
"""
attributes = element.attrib
return cls(
int(attributes.get("weight", "1")),
int(attributes.get("goal", "100")),
attributes.get("comment", ""),
int(attributes.get("at_least", "1")),
Base._ParseBoolean(attributes.get("detect_overlap", "false")),
int(attributes.get("auto_bin_max", "64")),
int(attributes.get("cross_num_print_missing", "0")),
Base._ParseBoolean(attributes.get("per_instance", "false")),
Base._ParseBoolean(attributes.get("merge_instances", "false"))
)
@readonly
def CrossNumPrintMissing(self) -> int:
"""
Read-only property to access the number of uncovered cross bins to save and report (:attr:`_crossNumPrintMissing`).
:returns: The number; ``0``, if the report states none.
"""
return self._crossNumPrintMissing
@readonly
def PerInstance(self) -> bool:
"""
Read-only property to access whether the instance's coverage is kept on its own (:attr:`_perInstance`).
:returns: ``True``, if kept per instance.
"""
return self._perInstance
@readonly
def MergeInstances(self) -> bool:
"""
Read-only property to access whether the covergroup's coverage merges its instances (:attr:`_mergeInstances`).
:returns: ``True``, if the instances are merged; otherwise, it is their weighted average.
"""
return self._mergeInstances