Coverage for pyEDAA/Reports/FunctionalCoverage/__init__.py: 100%
571 statements
« prev ^ index » next coverage.py v7.17.3, created at 2026-11-14 02:26 +0000
« prev ^ index » next coverage.py v7.17.3, created at 2026-11-14 02:26 +0000
1# ==================================================================================================================== #
2# _____ ____ _ _ ____ _ #
3# _ __ _ _| ____| _ \ / \ / \ | _ \ ___ _ __ ___ _ __| |_ ___ #
4# | '_ \| | | | _| | | | |/ _ \ / _ \ | |_) / _ \ '_ \ / _ \| '__| __/ __| #
5# | |_) | |_| | |___| |_| / ___ \ / ___ \ _| _ < __/ |_) | (_) | | | |_\__ \ #
6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)_| \_\___| .__/ \___/|_| \__|___/ #
7# |_| |___/ |_| #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2026-2026 Electronic Design Automation Abstraction (EDA²) #
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"""
32Abstract data model of functional coverage: covergroups, their coverpoints and crosses, and the bins counting how often
33a value - or a combination of values - was sampled.
35The model is tool-neutral: it follows the terms of SystemVerilog covergroups and of the Accellera UCIS standard, and
36an OSVVM coverage model maps to it too.
38* The root, :class:`FunctionalCoverageReport`, holds the covergroups of a report, e.g. of a testcase.
39* A :class:`Covergroup` holds its coverage items, each by name: :class:`Coverpoint` - one sampled value - and
40 :class:`Cross` - a combination of values, one per crossed item.
41* A coverage item holds its bins: a :class:`CoverpointBin` counts the values of its :class:`ValueRange` objects, a
42 :class:`CrossBin` the combinations of values, one set of ranges per crossed item. A bin's :class:`BinKind` says
43 whether it counts toward the coverage, is ignored or must not be hit.
45A bin is covered, when its count reaches its *at least* count. :meth:`FunctionalCoverageReport.Aggregate` computes the
46counters of every coverage item, covergroup and of the report, and two ratios:
48* :attr:`~BaseWithCoverage.BinCoverage` - the ratio of covered bins, weighted by the coverage items' and covergroups'
49 weights, as SystemVerilog and UCIS compute coverage.
50* :attr:`~BaseWithCoverage.HitCoverage` - the ratio of hits counted toward the bins' *at least* counts, as OSVVM
51 computes coverage.
53Every element has key-value pairs (:class:`~pyTooling.DataModel.KeyValuePairsMixin`) for data the model doesn't name,
54e.g. a tool's settings.
56A report format's model derives its document from :class:`Document`, which converts it to this model.
57"""
58from __future__ import annotations
60from datetime import timedelta
61from enum import Enum
62from pathlib import Path
63from typing import Any, Callable, ClassVar, Generator, Hashable, Iterable, Mapping, Optional as Nullable
65from pyTooling.Common import getFullyQualifiedName
66from pyTooling.DataModel import KeyValuePairsMixin
67from pyTooling.Decorators import export, readonly
68from pyTooling.Exceptions import AlreadyInHierarchyError, DuplicateElementError
69from pyTooling.MetaClasses import ExtendedType, abstractclass, abstractmethod
71from pyEDAA.Reports import ReportException
74@export
75class FunctionalCoverageError(ReportException):
76 """Base-exception of the functional coverage model."""
79@export
80class AlreadyInFunctionalCoverageReportError(FunctionalCoverageError, AlreadyInHierarchyError):
81 """The exception is raised when an element is added to an element, but it already has a parent."""
84@export
85class DuplicateCoverpointError(FunctionalCoverageError, DuplicateElementError):
86 """The exception is raised when a covergroup already contains a coverpoint or a cross of the same name."""
89@export
90class DuplicateCrossError(FunctionalCoverageError, DuplicateElementError):
91 """The exception is raised when a covergroup already contains a cross or a coverpoint of the same name."""
94@export
95class BinKind(Enum):
96 """The kind of a bin: whether it counts toward the coverage, is ignored or must not be hit."""
98 Count = 0 #: Counts toward the coverage; covered, when its count reaches its *at least* count.
99 Ignore = 1 #: Excluded from the coverage: its values aren't of interest.
100 Illegal = 2 #: Excluded from the coverage: its values must not occur, a hit is an error.
101 Default = 3 #: Excluded from the coverage: counts the values no other bin of the coverage item counts.
104@export
105class ValueRange(metaclass=ExtendedType, slots=True):
106 """
107 A range of values - from a lowest to a highest value, both included - a bin counts.
109 A single value is a range from and to this value. It is a value, as a path is: it names no parent.
110 """
112 _low: int #: The lowest value of the range.
113 _high: int #: The highest value of the range.
115 def __init__(self, low: int, high: int) -> None:
116 """
117 Initializes a range of values.
119 :param low: The lowest value of the range.
120 :param high: The highest value of the range.
121 :raises ValueError: If parameter ``low`` is ``None``.
122 :raises TypeError: If parameter ``low`` isn't of type :class:`int`.
123 :raises ValueError: If parameter ``high`` is ``None``.
124 :raises TypeError: If parameter ``high`` isn't of type :class:`int`.
125 :raises ValueError: If parameter ``high`` is less than parameter ``low``.
126 """
127 for parameterName, value in (
128 ("low", low),
129 ("high", high)
130 ):
131 if value is None:
132 raise ValueError(f"Parameter '{parameterName}' is None.")
133 elif not isinstance(value, int) or isinstance(value, bool):
134 ex = TypeError(f"Parameter '{parameterName}' is not of type 'int'.")
135 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
136 raise ex
138 if high < low:
139 ex = ValueError("Parameter 'high' is less than parameter 'low'.")
140 ex.add_note(f"Got range {low} to {high}.")
141 raise ex
143 self._low = low
144 self._high = high
146 @readonly
147 def Low(self) -> int:
148 """
149 Read-only property to access the lowest value of the range (:attr:`_low`).
151 :returns: The lowest value.
152 """
153 return self._low
155 @readonly
156 def High(self) -> int:
157 """
158 Read-only property to access the highest value of the range (:attr:`_high`).
160 :returns: The highest value.
161 """
162 return self._high
164 def __eq__(self, other: Any) -> bool:
165 """
166 Compare two ranges of values.
168 :param other: The other range.
169 :returns: ``True``, if both ranges have the same lowest and highest value.
170 :raises TypeError: If parameter ``other`` isn't of type :class:`ValueRange`.
171 """
172 if not isinstance(other, ValueRange):
173 ex = TypeError("Second operand is not supported by == operator.")
174 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
175 ex.add_note("Supported types for second operand: ValueRange")
176 raise ex
178 return self._low == other._low and self._high == other._high
180 def __hash__(self) -> int:
181 """
182 Return a hash of the range of values, so equal ranges have the same hash.
184 :returns: The hash of the lowest and highest value.
185 """
186 return hash((self._low, self._high))
188 def __repr__(self) -> str:
189 """
190 Return a representation of the range of values for debugging.
192 A range from 3 to 11 reads ``<ValueRange 3..11>``.
194 :returns: The lowest and the highest value.
195 """
196 return f"<ValueRange {self._low}..{self._high}>"
199@export
200class Base(KeyValuePairsMixin, metaclass=ExtendedType, slots=True):
201 """
202 Base-class of every element of the functional coverage model: key-value pairs, a reference to the element containing
203 it, and to the report's root.
204 """
206 _PARENT_TYPE: ClassVar[tuple[type, ...]] #: The types a parent may have; assigned by each class having a parent.
208 _parent: Nullable[Base] #: The element containing this one, or ``None``.
209 _root: Nullable[FunctionalCoverageReport] #: The report's root, or ``None`` while the element isn't part of one.
211 def __init__(self, keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, *, parent: Nullable[Base] = None) -> None:
212 """
213 Initializes an element with its key-value pairs, its parent and the parent's root.
215 The parent doesn't list the element yet: the class setting the fields the parent checks - a name, the crossed
216 items - adds it with ``parent._AddElement(self)``.
218 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``.
219 :param parent: Optional, the element containing this one. Default: ``None``.
220 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
221 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`.
222 """
223 super().__init__(keyValuePairs)
225 if parent is None:
226 self._parent = None
227 self._root = None
228 elif not isinstance(parent, self._PARENT_TYPE):
229 typeNames = " or ".join(f"'{parentType.__name__}'" for parentType in self._PARENT_TYPE)
230 ex = TypeError(f"Parameter 'parent' is not of type {typeNames}.")
231 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
232 raise ex
233 else:
234 self._parent = parent
235 self._root = parent._root
237 @property
238 def Parent(self) -> Nullable[Base]:
239 """
240 Property to access the element containing this one (:attr:`_parent`).
242 Assigning a parent adds this element to it, and sets the parent's root as the root of this element and of every
243 element below it.
245 :returns: The parent, or ``None``.
246 :raises ValueError: If ``None`` is assigned.
247 :raises TypeError: If the assigned parent isn't of a type this class declares in
248 :attr:`_PARENT_TYPE`.
249 :raises TypeError: If a parent is assigned to a :class:`FunctionalCoverageReport`,
250 which has no parent.
251 :raises AlreadyInFunctionalCoverageReportError: If the element already has a parent.
252 :raises DuplicateCoverpointError: If the assigned covergroup already contains a coverpoint or a
253 cross of the coverpoint's name.
254 :raises DuplicateCrossError: If the assigned covergroup already contains a cross or a
255 coverpoint of the cross' name.
256 :raises FunctionalCoverageError: If a cross bin has not one set of ranges per crossed item of the
257 assigned cross.
258 """
259 return self._parent
261 @Parent.setter
262 def Parent(self, parent: Base) -> None:
263 if parent is None:
264 raise ValueError("Parameter 'parent' is None.")
265 elif not isinstance(parent, self._PARENT_TYPE):
266 typeNames = " or ".join(f"'{parentType.__name__}'" for parentType in self._PARENT_TYPE)
267 ex = TypeError(f"Parameter 'parent' is not of type {typeNames}.")
268 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
269 raise ex
270 elif self._parent is not None:
271 ex = AlreadyInFunctionalCoverageReportError(f"{self.__class__.__name__} already has a parent.")
272 ex.add_note(f"Got element '{self!r}'.")
273 raise ex
275 parent._AddElement(self)
276 self._parent = parent
277 self._SetRoot(parent._root)
279 @readonly
280 def Root(self) -> Nullable[FunctionalCoverageReport]:
281 """
282 Read-only property to access the report's root (:attr:`_root`).
284 The root is maintained by :attr:`Parent`: assigning a parent sets it for this element and every element below it.
286 :returns: The root, or ``None`` while the element isn't part of a report.
287 """
288 return self._root
290 def _SetRoot(self, root: Nullable[FunctionalCoverageReport]) -> None:
291 """
292 Set the report's root of this element.
294 A class with child elements extends it to set the root of its children too.
296 :param root: The report's root, or ``None`` while the element isn't part of a report.
297 """
298 self._root = root
301@export
302class BaseWithName(Base):
303 """
304 Base-class of the named elements: the report, covergroups and coverage items.
305 """
307 _name: str #: Name of the element.
309 def __init__(
310 self,
311 name: str,
312 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None,
313 *,
314 parent: Nullable[Base] = None
315 ) -> None:
316 """
317 Initializes the key-value pairs, the parent and the name.
319 :param name: Name of the element.
320 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``.
321 :param parent: Optional, the element containing this one. Default: ``None``.
322 :raises ValueError: If parameter ``name`` is ``None``.
323 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
324 :raises ValueError: If parameter ``name`` is empty.
325 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
326 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`.
327 """
328 super().__init__(keyValuePairs, parent=parent)
330 if name is None:
331 raise ValueError("Parameter 'name' is None.")
332 elif not isinstance(name, str):
333 ex = TypeError("Parameter 'name' is not of type 'str'.")
334 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.")
335 raise ex
336 elif name == "":
337 raise ValueError("Parameter 'name' is empty.")
339 self._name = name
341 @readonly
342 def Name(self) -> str:
343 """
344 Read-only property to access the name (:attr:`_name`).
346 :returns: The name.
347 """
348 return self._name
351@export
352class BaseWithCoverage(BaseWithName):
353 """
354 Base-class of the elements with coverage counters: the report, covergroups and coverage items.
356 The counters are zero until :meth:`Aggregate` computed them. A covergroup weights the hit counters of its coverage
357 items by their weights, the report those of its covergroups; the bin counters are plain sums.
358 """
360 _binCount: int #: Number of bins counting toward the coverage.
361 _coveredBinCount: int #: Number of bins counting toward the coverage, which reached their *at least* count.
362 _ignoreBinCount: int #: Number of ignored bins.
363 _illegalBinCount: int #: Number of illegal bins.
364 _defaultBinCount: int #: Number of default bins.
365 _illegalHitCount: int #: Number of hits of illegal bins.
366 _hitCount: int #: Number of hits counted toward the *at least* counts, each bin's capped at its own.
367 _requiredHitCount: int #: Number of hits required by the *at least* counts.
368 _binCoverage: float #: The weighted ratio of covered bins.
370 def __init__(
371 self,
372 name: str,
373 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None,
374 *,
375 parent: Nullable[Base] = None
376 ) -> None:
377 """
378 Initializes the key-value pairs, the parent, the name and the counters.
380 :param name: Name of the element.
381 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``.
382 :param parent: Optional, the element containing this one. Default: ``None``.
383 :raises ValueError: If parameter ``name`` is ``None``.
384 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
385 :raises ValueError: If parameter ``name`` is empty.
386 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
387 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`.
388 """
389 super().__init__(name, keyValuePairs=keyValuePairs, parent=parent)
391 self._ResetCounters()
393 def _ResetCounters(self) -> None:
394 """
395 Set the counters to zero, and the bin coverage to ``1.0``.
396 """
397 self._binCount = 0
398 self._coveredBinCount = 0
399 self._ignoreBinCount = 0
400 self._illegalBinCount = 0
401 self._defaultBinCount = 0
402 self._illegalHitCount = 0
403 self._hitCount = 0
404 self._requiredHitCount = 0
405 self._binCoverage = 1.0
407 def _AggregateCounters(self, others: Iterable[BaseWithGoal]) -> None:
408 """
409 Set the counters to the sums of the counters of covergroups or coverage items, the hit counters weighted by their
410 weights, and the bin coverage to the mean of theirs, weighted by their weights.
412 :param others: The aggregated covergroups or coverage items.
413 """
414 self._ResetCounters()
415 weightedBinCoverage = 0.0
416 totalWeight = 0
417 for other in others:
418 self._binCount += other._binCount
419 self._coveredBinCount += other._coveredBinCount
420 self._ignoreBinCount += other._ignoreBinCount
421 self._illegalBinCount += other._illegalBinCount
422 self._defaultBinCount += other._defaultBinCount
423 self._illegalHitCount += other._illegalHitCount
424 self._hitCount += other._weight * other._hitCount
425 self._requiredHitCount += other._weight * other._requiredHitCount
426 weightedBinCoverage += other._weight * other._binCoverage
427 totalWeight += other._weight
429 if totalWeight > 0:
430 self._binCoverage = weightedBinCoverage / totalWeight
432 @readonly
433 def BinCount(self) -> int:
434 """
435 Read-only property to access the number of bins counting toward the coverage (:attr:`_binCount`).
437 :returns: The number of bins of kind :attr:`BinKind.Count`.
438 """
439 return self._binCount
441 @readonly
442 def CoveredBinCount(self) -> int:
443 """
444 Read-only property to access the number of bins counting toward the coverage, which reached their *at least* count
445 (:attr:`_coveredBinCount`).
447 :returns: The number of covered bins.
448 """
449 return self._coveredBinCount
451 @readonly
452 def MissingBinCount(self) -> int:
453 """
454 Read-only property to return the number of bins counting toward the coverage, which missed their *at least* count.
456 :returns: :attr:`BinCount` minus :attr:`CoveredBinCount`.
457 """
458 return self._binCount - self._coveredBinCount
460 @readonly
461 def IgnoreBinCount(self) -> int:
462 """
463 Read-only property to access the number of ignored bins (:attr:`_ignoreBinCount`).
465 :returns: The number of bins of kind :attr:`BinKind.Ignore`.
466 """
467 return self._ignoreBinCount
469 @readonly
470 def IllegalBinCount(self) -> int:
471 """
472 Read-only property to access the number of illegal bins (:attr:`_illegalBinCount`).
474 :returns: The number of bins of kind :attr:`BinKind.Illegal`.
475 """
476 return self._illegalBinCount
478 @readonly
479 def DefaultBinCount(self) -> int:
480 """
481 Read-only property to access the number of default bins (:attr:`_defaultBinCount`).
483 :returns: The number of bins of kind :attr:`BinKind.Default`.
484 """
485 return self._defaultBinCount
487 @readonly
488 def IllegalHitCount(self) -> int:
489 """
490 Read-only property to access the number of hits of illegal bins (:attr:`_illegalHitCount`).
492 :returns: The sum of the counts of the illegal bins; ``0``, if no illegal value occurred.
493 """
494 return self._illegalHitCount
496 @readonly
497 def HitCount(self) -> int:
498 """
499 Read-only property to access the number of hits counted toward the *at least* counts (:attr:`_hitCount`).
501 A bin's hits count up to its *at least* count, so extra hits of one bin don't make up for missing hits of another.
503 :returns: The number of hits, weighted by the weights of the coverage items and covergroups.
504 """
505 return self._hitCount
507 @readonly
508 def RequiredHitCount(self) -> int:
509 """
510 Read-only property to access the number of hits required by the *at least* counts (:attr:`_requiredHitCount`).
512 :returns: The sum of the *at least* counts, weighted by the weights of the coverage items and covergroups.
513 """
514 return self._requiredHitCount
516 @readonly
517 def BinCoverage(self) -> float:
518 """
519 Read-only property to access the ratio of covered bins (:attr:`_binCoverage`).
521 It is the coverage as SystemVerilog and UCIS compute it: of a coverage item, its covered bins divided by its bins
522 counting toward the coverage; of a covergroup, the mean of its coverage items' bin coverage, weighted by their
523 weights; of the report, the same over the covergroups.
525 :returns: The bin coverage in range 0.0..1.0; ``1.0`` if there is nothing to cover.
526 """
527 return self._binCoverage
529 @readonly
530 def HitCoverage(self) -> float:
531 """
532 Read-only property to return the ratio of hits counted toward the *at least* counts.
534 It is the coverage as OSVVM computes it.
536 :returns: :attr:`HitCount` divided by :attr:`RequiredHitCount`, in range 0.0..1.0; ``1.0`` if no hit is
537 required.
538 """
539 return 1.0 if self._requiredHitCount == 0 else self._hitCount / self._requiredHitCount
541 @abstractmethod
542 def Aggregate(self) -> None:
543 """
544 Compute the counters.
545 """
548@export
549class BaseWithGoal(BaseWithCoverage):
550 """
551 Base-class of the elements with a weight and a goal: covergroups and coverage items.
552 """
554 _weight: int #: Weight in computing the coverage of the element containing this one.
555 _goal: float #: Coverage goal, in percent.
557 def __init__(
558 self,
559 name: str,
560 weight: int = 1,
561 goal: float = 100.0,
562 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None,
563 *,
564 parent: Nullable[Base] = None
565 ) -> None:
566 """
567 Initializes the key-value pairs, the parent, the name, the counters, the weight and the goal.
569 :param name: Name of the element.
570 :param weight: Optional, weight in computing the coverage of the element containing this one; ``0``
571 excludes it. Default: ``1``.
572 :param goal: Optional, coverage goal, in percent. Default: ``100.0``.
573 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``.
574 :param parent: Optional, the element containing this one. Default: ``None``.
575 :raises ValueError: If parameter ``name`` is ``None``.
576 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
577 :raises ValueError: If parameter ``name`` is empty.
578 :raises ValueError: If parameter ``weight`` is ``None``.
579 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
580 :raises ValueError: If parameter ``weight`` is negative.
581 :raises ValueError: If parameter ``goal`` is ``None``.
582 :raises TypeError: If parameter ``goal`` isn't of type :class:`float` or :class:`int`.
583 :raises ValueError: If parameter ``goal`` is negative.
584 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
585 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`.
586 """
587 super().__init__(name, keyValuePairs=keyValuePairs, parent=parent)
589 if weight is None:
590 raise ValueError("Parameter 'weight' is None.")
591 elif not isinstance(weight, int) or isinstance(weight, bool):
592 ex = TypeError("Parameter 'weight' is not of type 'int'.")
593 ex.add_note(f"Got type '{getFullyQualifiedName(weight)}'.")
594 raise ex
595 elif weight < 0:
596 ex = ValueError("Parameter 'weight' is negative.")
597 ex.add_note(f"Got value '{weight}'.")
598 raise ex
600 if goal is None:
601 raise ValueError("Parameter 'goal' is None.")
602 elif not isinstance(goal, (float, int)) or isinstance(goal, bool):
603 ex = TypeError("Parameter 'goal' is not of type 'float' or 'int'.")
604 ex.add_note(f"Got type '{getFullyQualifiedName(goal)}'.")
605 raise ex
606 elif goal < 0:
607 ex = ValueError("Parameter 'goal' is negative.")
608 ex.add_note(f"Got value '{goal}'.")
609 raise ex
611 self._weight = weight
612 self._goal = float(goal)
614 @readonly
615 def Weight(self) -> int:
616 """
617 Read-only property to access the weight in computing the coverage of the element containing this one
618 (:attr:`_weight`).
620 :returns: The weight; ``0``, if the element is excluded from the coverage of the element containing it.
621 """
622 return self._weight
624 @readonly
625 def Goal(self) -> float:
626 """
627 Read-only property to access the coverage goal (:attr:`_goal`).
629 :returns: The goal, in percent.
630 """
631 return self._goal
634@export
635class FunctionalCoverageReport(BaseWithCoverage):
636 """
637 The root of a functional coverage report: its covergroups.
639 It is its own root, and has no parent. A covergroup joins it by naming it as its parent, in the covergroup's
640 constructor or by :attr:`~Base.Parent`. The names of covergroups needn't be unique: several instances of a covergroup
641 may share a name.
642 """
644 _covergroups: list[Covergroup] #: The covergroups, in the order they were added.
646 def __init__(self, name: str, keyValuePairs: Nullable[Mapping[Hashable, Any]] = None) -> None:
647 """
648 Initializes the root of a functional coverage report.
650 :param name: Name of the report, e.g. of the testcase measured.
651 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``.
652 :raises ValueError: If parameter ``name`` is ``None``.
653 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
654 :raises ValueError: If parameter ``name`` is empty.
655 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
656 """
657 super().__init__(name, keyValuePairs=keyValuePairs)
659 self._root = self
660 self._covergroups = []
662 @Base.Parent.setter
663 def Parent(self, parent: None) -> None:
664 ex = TypeError(f"A '{getFullyQualifiedName(self)}' has no parent.")
665 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
666 raise ex
668 def _AddElement(self, covergroup: Covergroup) -> None:
669 """
670 Add a covergroup, which names the report as its parent.
672 :param covergroup: The covergroup.
673 """
674 self._covergroups.append(covergroup)
676 @readonly
677 def Covergroups(self) -> list[Covergroup]:
678 """
679 Read-only property to access the covergroups (:attr:`_covergroups`).
681 :returns: The covergroups, in the order they were added.
682 """
683 return self._covergroups
685 @readonly
686 def CovergroupCount(self) -> int:
687 """
688 Read-only property to return the number of covergroups.
690 :returns: The number of covergroups.
691 """
692 return len(self._covergroups)
694 def IterateCovergroups(
695 self,
696 predicate: Nullable[Callable[[Covergroup], bool]] = None
697 ) -> Generator[Covergroup, None, None]:
698 """
699 Iterate all or selected covergroups.
701 The covergroups are iterated in the order they were added. If parameter ``predicate`` is not None, the given filter
702 function is used to skip covergroups in the generator.
704 :param predicate: Optional, filter function accepting any covergroup and returning a boolean. Default: ``None``.
705 :returns: A generator of the covergroups.
706 """
707 if predicate is None:
708 yield from self._covergroups
709 else:
710 for covergroup in self._covergroups:
711 if predicate(covergroup):
712 yield covergroup
714 def Aggregate(self) -> None:
715 """
716 Aggregate the covergroups, then sum their counters, weighted by their weights.
717 """
718 for covergroup in self._covergroups:
719 covergroup.Aggregate()
721 self._AggregateCounters(self._covergroups)
723 def __repr__(self) -> str:
724 """
725 Return a representation of the report for debugging.
727 A report ``TbStream_SendGetRandom1`` of 6 covergroups reads
728 ``<FunctionalCoverageReport TbStream_SendGetRandom1: 6 covergroups, bins 50.0%, hits 47.6%>``.
730 :returns: The report's name, its number of covergroups and its coverage.
731 """
732 return (
733 f"<FunctionalCoverageReport {self._name}: {len(self._covergroups)} covergroups, bins {self.BinCoverage:.1%}, "
734 f"hits {self.HitCoverage:.1%}>"
735 )
738@export
739class Covergroup(BaseWithGoal):
740 """
741 A covergroup: its coverage items - coverpoints and crosses -, each by name, its weight in the report's coverage, its
742 goal, and the testcases, which sampled it.
744 Its parent is the :class:`FunctionalCoverageReport`. Coverpoints and crosses share one namespace.
745 """
747 _PARENT_TYPE: ClassVar[tuple[type, ...]] = (FunctionalCoverageReport, ) #: A covergroup is in a report.
749 _testcases: list[str] #: The names of the testcases, which sampled the covergroup.
750 _coverpoints: dict[str, Coverpoint] #: The coverpoints, by name.
751 _crosses: dict[str, Cross] #: The crosses, by name.
753 def __init__(
754 self,
755 name: str,
756 weight: int = 1,
757 goal: float = 100.0,
758 testcases: Nullable[Iterable[str]] = None,
759 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None,
760 *,
761 parent: Nullable[FunctionalCoverageReport] = None
762 ) -> None:
763 """
764 Initializes a covergroup, and adds it to its report.
766 Its coverpoints and crosses are added by creating them with this covergroup as their parent.
768 :param name: Name of the covergroup.
769 :param weight: Optional, weight in computing the report's coverage; ``0`` excludes it. Default: ``1``.
770 :param goal: Optional, coverage goal, in percent. Default: ``100.0``.
771 :param testcases: Optional, the names of the testcases, which sampled the covergroup. Default: ``None``.
772 :param keyValuePairs: Optional, the key-value pairs, e.g. a tool's settings. Default: ``None``.
773 :param parent: Optional, the report containing the covergroup. Default: ``None``.
774 :raises ValueError: If parameter ``name`` is ``None``.
775 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
776 :raises ValueError: If parameter ``name`` is empty.
777 :raises ValueError: If parameter ``weight`` is ``None``.
778 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
779 :raises ValueError: If parameter ``weight`` is negative.
780 :raises ValueError: If parameter ``goal`` is ``None``.
781 :raises TypeError: If parameter ``goal`` isn't of type :class:`float` or :class:`int`.
782 :raises ValueError: If parameter ``goal`` is negative.
783 :raises TypeError: If parameter ``testcases`` isn't iterable.
784 :raises TypeError: If parameter ``testcases`` contains an element not of type :class:`str`.
785 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
786 :raises TypeError: If parameter ``parent`` isn't of type :class:`FunctionalCoverageReport`.
787 """
788 super().__init__(name, weight, goal, keyValuePairs=keyValuePairs, parent=parent)
790 self._testcases = testcaseList = []
791 self._coverpoints = {}
792 self._crosses = {}
794 if testcases is not None:
795 if not isinstance(testcases, Iterable):
796 ex = TypeError("Parameter 'testcases' is not iterable.")
797 ex.add_note(f"Got type '{getFullyQualifiedName(testcases)}'.")
798 raise ex
800 for testcase in testcases:
801 if not isinstance(testcase, str):
802 ex = TypeError("Parameter 'testcases' contains an element not of type 'str'.")
803 ex.add_note(f"Got type '{getFullyQualifiedName(testcase)}'.")
804 raise ex
806 testcaseList.append(testcase)
808 if parent is not None:
809 parent._AddElement(self)
811 def _AddElement(self, item: Coverpoint | Cross) -> None:
812 """
813 Add a coverpoint or a cross, which names this covergroup as its parent.
815 :param item: The coverpoint or cross.
816 :raises DuplicateCoverpointError: If this covergroup already contains a coverpoint or cross of the coverpoint's
817 name.
818 :raises DuplicateCrossError: If this covergroup already contains a cross or coverpoint of the cross' name.
819 """
820 if isinstance(item, Coverpoint):
821 items = self._coverpoints
822 exception = DuplicateCoverpointError
823 else:
824 items = self._crosses
825 exception = DuplicateCrossError
827 if item._name in self._coverpoints or item._name in self._crosses:
828 raise exception(f"Covergroup '{self._name}' already contains '{item._name}'.")
830 items[item._name] = item
832 def _SetRoot(self, root: Nullable[FunctionalCoverageReport]) -> None:
833 """
834 Set the report's root of this covergroup, and of its coverpoints and crosses.
836 :param root: The report's root, or ``None`` while the covergroup isn't part of a report.
837 """
838 super()._SetRoot(root)
839 for item in (*self._coverpoints.values(), *self._crosses.values()):
840 item._SetRoot(root)
842 @readonly
843 def Testcases(self) -> list[str]:
844 """
845 Read-only property to access the names of the testcases, which sampled the covergroup (:attr:`_testcases`).
847 :returns: The names of the testcases; empty, if the report doesn't say.
848 """
849 return self._testcases
851 @readonly
852 def Coverpoints(self) -> dict[str, Coverpoint]:
853 """
854 Read-only property to access the coverpoints (:attr:`_coverpoints`).
856 :returns: The coverpoints, by name.
857 """
858 return self._coverpoints
860 @readonly
861 def CoverpointCount(self) -> int:
862 """
863 Read-only property to return the number of coverpoints.
865 :returns: The number of coverpoints.
866 """
867 return len(self._coverpoints)
869 @readonly
870 def Crosses(self) -> dict[str, Cross]:
871 """
872 Read-only property to access the crosses (:attr:`_crosses`).
874 :returns: The crosses, by name.
875 """
876 return self._crosses
878 @readonly
879 def CrossCount(self) -> int:
880 """
881 Read-only property to return the number of crosses.
883 :returns: The number of crosses.
884 """
885 return len(self._crosses)
887 def IterateCoverpoints(
888 self,
889 predicate: Nullable[Callable[[Coverpoint], bool]] = None
890 ) -> Generator[Coverpoint, None, None]:
891 """
892 Iterate all or selected coverpoints.
894 The coverpoints are iterated in the order they were added. If parameter ``predicate`` is not None, the given filter
895 function is used to skip coverpoints in the generator.
897 :param predicate: Optional, filter function accepting any coverpoint and returning a boolean. Default: ``None``.
898 :returns: A generator of the coverpoints.
899 """
900 if predicate is None:
901 yield from self._coverpoints.values()
902 else:
903 for coverpoint in self._coverpoints.values():
904 if predicate(coverpoint):
905 yield coverpoint
907 def IterateCrosses(self, predicate: Nullable[Callable[[Cross], bool]] = None) -> Generator[Cross, None, None]:
908 """
909 Iterate all or selected crosses.
911 The crosses are iterated in the order they were added. If parameter ``predicate`` is not None, the given filter
912 function is used to skip crosses in the generator.
914 :param predicate: Optional, filter function accepting any cross and returning a boolean. Default: ``None``.
915 :returns: A generator of the crosses.
916 """
917 if predicate is None:
918 yield from self._crosses.values()
919 else:
920 for cross in self._crosses.values():
921 if predicate(cross):
922 yield cross
924 def Aggregate(self) -> None:
925 """
926 Aggregate the coverpoints and crosses, then sum their counters, weighted by their weights.
927 """
928 items = (*self._coverpoints.values(), *self._crosses.values())
929 for item in items:
930 item.Aggregate()
932 self._AggregateCounters(items)
934 def __repr__(self) -> str:
935 """
936 Return a representation of the covergroup for debugging.
938 A covergroup ``DelayCov`` of one coverpoint reads ``<Covergroup DelayCov: 1 coverpoints, 0 crosses, bins 0.0%,
939 hits 22.0%>``.
941 :returns: The covergroup's name, its number of coverpoints and crosses, and its coverage.
942 """
943 return (
944 f"<Covergroup {self._name}: {len(self._coverpoints)} coverpoints, {len(self._crosses)} crosses, "
945 f"bins {self.BinCoverage:.1%}, hits {self.HitCoverage:.1%}>"
946 )
949@export
950@abstractclass
951class CoverageItem(BaseWithGoal):
952 """
953 Base-class of the coverage items of a covergroup - coverpoints and crosses -: their bins, their weight in the
954 covergroup's coverage and their goal.
956 Its parent is the :class:`Covergroup` containing it.
957 """
959 _PARENT_TYPE: ClassVar[tuple[type, ...]] = (Covergroup, ) #: A coverage item is in a covergroup.
961 _bins: list[Bin] #: The bins, in the order they were added.
963 def __init__(
964 self,
965 name: str,
966 weight: int = 1,
967 goal: float = 100.0,
968 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None,
969 *,
970 parent: Nullable[Covergroup] = None
971 ) -> None:
972 """
973 Initializes the key-value pairs, the parent, the name, the counters, the weight, the goal and the bins.
975 :param name: Name of the coverage item.
976 :param weight: Optional, weight in computing the covergroup's coverage; ``0`` excludes it. Default: ``1``.
977 :param goal: Optional, coverage goal, in percent. Default: ``100.0``.
978 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``.
979 :param parent: Optional, the covergroup containing the coverage item. Default: ``None``.
980 :raises ValueError: If parameter ``name`` is ``None``.
981 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
982 :raises ValueError: If parameter ``name`` is empty.
983 :raises ValueError: If parameter ``weight`` is ``None``.
984 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
985 :raises ValueError: If parameter ``weight`` is negative.
986 :raises ValueError: If parameter ``goal`` is ``None``.
987 :raises TypeError: If parameter ``goal`` isn't of type :class:`float` or :class:`int`.
988 :raises ValueError: If parameter ``goal`` is negative.
989 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
990 :raises TypeError: If parameter ``parent`` isn't of type :class:`Covergroup`.
991 """
992 super().__init__(name, weight, goal, keyValuePairs=keyValuePairs, parent=parent)
994 self._bins = []
996 def _AddElement(self, coverageBin: Bin) -> None:
997 """
998 Add a bin, which names this coverage item as its parent.
1000 :param coverageBin: The bin.
1001 """
1002 self._bins.append(coverageBin)
1004 def _SetRoot(self, root: Nullable[FunctionalCoverageReport]) -> None:
1005 """
1006 Set the report's root of this coverage item, and of its bins.
1008 :param root: The report's root, or ``None`` while the coverage item isn't part of a report.
1009 """
1010 super()._SetRoot(root)
1011 for coverageBin in self._bins:
1012 coverageBin._SetRoot(root)
1014 @readonly
1015 def Bins(self) -> list[Bin]:
1016 """
1017 Read-only property to access the bins (:attr:`_bins`).
1019 :returns: The bins, in the order they were added.
1020 """
1021 return self._bins
1023 def IterateBins(self, predicate: Nullable[Callable[[Bin], bool]] = None) -> Generator[Bin, None, None]:
1024 """
1025 Iterate all or selected bins.
1027 The bins are iterated in the order they were added. If parameter ``predicate`` is not None, the given filter
1028 function is used to skip bins in the generator.
1030 :param predicate: Optional, filter function accepting any bin and returning a boolean. Default: ``None``.
1031 :returns: A generator of the bins.
1032 """
1033 if predicate is None:
1034 yield from self._bins
1035 else:
1036 for coverageBin in self._bins:
1037 if predicate(coverageBin):
1038 yield coverageBin
1040 def Aggregate(self) -> None:
1041 """
1042 Compute the counters and the bin coverage from the bins.
1043 """
1044 self._ResetCounters()
1045 for coverageBin in self._bins:
1046 kind = coverageBin._kind
1047 if kind is BinKind.Count:
1048 self._binCount += 1
1049 self._requiredHitCount += coverageBin._atLeast
1050 if coverageBin._count >= coverageBin._atLeast:
1051 self._coveredBinCount += 1
1052 self._hitCount += coverageBin._atLeast
1053 else:
1054 self._hitCount += coverageBin._count
1055 elif kind is BinKind.Ignore:
1056 self._ignoreBinCount += 1
1057 elif kind is BinKind.Illegal:
1058 self._illegalBinCount += 1
1059 self._illegalHitCount += coverageBin._count
1060 else:
1061 self._defaultBinCount += 1
1063 if self._binCount > 0:
1064 self._binCoverage = self._coveredBinCount / self._binCount
1066 def __repr__(self) -> str:
1067 """
1068 Return a representation of the coverage item for debugging.
1070 A coverpoint ``Length`` with one of two bins covered reads ``<Coverpoint Length: 1/2 bins, hits 60.0%>``.
1072 :returns: The coverage item's kind, name, its covered and counting bins, and its hit coverage.
1073 """
1074 return (
1075 f"<{self.__class__.__name__} {self._name}: {self._coveredBinCount}/{self._binCount} bins, "
1076 f"hits {self.HitCoverage:.1%}>"
1077 )
1080@export
1081class Coverpoint(CoverageItem):
1082 """
1083 A coverpoint: the bins counting the values of one sampled expression.
1085 Its bins are :class:`CoverpointBin` objects.
1086 """
1088 _expression: Nullable[str] #: The sampled expression, if the report says.
1090 def __init__(
1091 self,
1092 name: str,
1093 expression: Nullable[str] = None,
1094 weight: int = 1,
1095 goal: float = 100.0,
1096 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None,
1097 *,
1098 parent: Nullable[Covergroup] = None
1099 ) -> None:
1100 """
1101 Initializes a coverpoint, and adds it to its covergroup.
1103 Its bins are added by creating them with this coverpoint as their parent.
1105 :param name: Name of the coverpoint.
1106 :param expression: Optional, the sampled expression, e.g. a signal's name. Default: ``None``.
1107 :param weight: Optional, weight in computing the covergroup's coverage; ``0`` excludes it.
1108 Default: ``1``.
1109 :param goal: Optional, coverage goal, in percent. Default: ``100.0``.
1110 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``.
1111 :param parent: Optional, the covergroup containing the coverpoint. Default: ``None``.
1112 :raises ValueError: If parameter ``name`` is ``None``.
1113 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
1114 :raises ValueError: If parameter ``name`` is empty.
1115 :raises TypeError: If parameter ``expression`` isn't of type :class:`str`.
1116 :raises ValueError: If parameter ``expression`` is empty.
1117 :raises ValueError: If parameter ``weight`` is ``None``.
1118 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
1119 :raises ValueError: If parameter ``weight`` is negative.
1120 :raises ValueError: If parameter ``goal`` is ``None``.
1121 :raises TypeError: If parameter ``goal`` isn't of type :class:`float` or :class:`int`.
1122 :raises ValueError: If parameter ``goal`` is negative.
1123 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
1124 :raises TypeError: If parameter ``parent`` isn't of type :class:`Covergroup`.
1125 :raises DuplicateCoverpointError: If the covergroup already contains a coverpoint or cross of this name.
1126 """
1127 super().__init__(name, weight, goal, keyValuePairs=keyValuePairs, parent=parent)
1129 if expression is not None:
1130 if not isinstance(expression, str):
1131 ex = TypeError("Parameter 'expression' is not of type 'str'.")
1132 ex.add_note(f"Got type '{getFullyQualifiedName(expression)}'.")
1133 raise ex
1134 elif expression == "":
1135 raise ValueError("Parameter 'expression' is empty.")
1137 self._expression = expression
1139 if parent is not None:
1140 parent._AddElement(self)
1142 @readonly
1143 def Expression(self) -> Nullable[str]:
1144 """
1145 Read-only property to access the sampled expression (:attr:`_expression`).
1147 :returns: The expression, or ``None`` if the report doesn't say.
1148 """
1149 return self._expression
1152@export
1153class Cross(CoverageItem):
1154 """
1155 A cross: the bins counting combinations of values, one value per crossed item.
1157 Its bins are :class:`CrossBin` objects, each with one set of ranges per crossed item. The crossed items are named:
1158 the covergroup may contain them as coverpoints, as a SystemVerilog cross crosses coverpoints, or not, as an OSVVM
1159 coverage model has no coverpoints of its own.
1160 """
1162 _crossedItems: list[str] #: The names of the crossed items, e.g. of coverpoints, one per dimension.
1164 def __init__(
1165 self,
1166 name: str,
1167 crossedItems: Iterable[str],
1168 weight: int = 1,
1169 goal: float = 100.0,
1170 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None,
1171 *,
1172 parent: Nullable[Covergroup] = None
1173 ) -> None:
1174 """
1175 Initializes a cross, and adds it to its covergroup.
1177 Its bins are added by creating them with this cross as their parent.
1179 :param name: Name of the cross.
1180 :param crossedItems: The names of the crossed items, e.g. of coverpoints, one per dimension.
1181 :param weight: Optional, weight in computing the covergroup's coverage; ``0`` excludes it. Default:
1182 ``1``.
1183 :param goal: Optional, coverage goal, in percent. Default: ``100.0``.
1184 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``.
1185 :param parent: Optional, the covergroup containing the cross. Default: ``None``.
1186 :raises ValueError: If parameter ``name`` is ``None``.
1187 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
1188 :raises ValueError: If parameter ``name`` is empty.
1189 :raises ValueError: If parameter ``crossedItems`` is ``None``.
1190 :raises TypeError: If parameter ``crossedItems`` isn't iterable.
1191 :raises TypeError: If parameter ``crossedItems`` contains an element not of type :class:`str`.
1192 :raises ValueError: If parameter ``crossedItems`` contains an empty name.
1193 :raises ValueError: If parameter ``crossedItems`` contains less than two names.
1194 :raises ValueError: If parameter ``weight`` is ``None``.
1195 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
1196 :raises ValueError: If parameter ``weight`` is negative.
1197 :raises ValueError: If parameter ``goal`` is ``None``.
1198 :raises TypeError: If parameter ``goal`` isn't of type :class:`float` or :class:`int`.
1199 :raises ValueError: If parameter ``goal`` is negative.
1200 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
1201 :raises TypeError: If parameter ``parent`` isn't of type :class:`Covergroup`.
1202 :raises DuplicateCrossError: If the covergroup already contains a cross or coverpoint of this name.
1203 """
1204 super().__init__(name, weight, goal, keyValuePairs=keyValuePairs, parent=parent)
1206 if crossedItems is None:
1207 raise ValueError("Parameter 'crossedItems' is None.")
1208 elif not isinstance(crossedItems, Iterable):
1209 ex = TypeError("Parameter 'crossedItems' is not iterable.")
1210 ex.add_note(f"Got type '{getFullyQualifiedName(crossedItems)}'.")
1211 raise ex
1213 self._crossedItems = crossedItemList = []
1214 for crossedItem in crossedItems:
1215 if not isinstance(crossedItem, str):
1216 ex = TypeError("Parameter 'crossedItems' contains an element not of type 'str'.")
1217 ex.add_note(f"Got type '{getFullyQualifiedName(crossedItem)}'.")
1218 raise ex
1219 elif crossedItem == "":
1220 raise ValueError("Parameter 'crossedItems' contains an empty name.")
1222 crossedItemList.append(crossedItem)
1224 if len(crossedItemList) < 2:
1225 ex = ValueError("Parameter 'crossedItems' contains less than two names.")
1226 ex.add_note(f"Got {len(crossedItemList)} names.")
1227 raise ex
1229 if parent is not None:
1230 parent._AddElement(self)
1232 def _AddElement(self, coverageBin: CrossBin) -> None:
1233 """
1234 Add a cross bin, which names this cross as its parent.
1236 :param coverageBin: The cross bin.
1237 :raises FunctionalCoverageError: If the cross bin has not one set of ranges per crossed item.
1238 """
1239 if len(coverageBin._ranges) != len(self._crossedItems):
1240 ex = FunctionalCoverageError(f"Bin has not one set of ranges per crossed item of cross '{self._name}'.")
1241 ex.add_note(f"Got {len(coverageBin._ranges)} sets of ranges for {len(self._crossedItems)} crossed items.")
1242 raise ex from ValueError("Parameter 'ranges' doesn't match the crossed items.")
1244 self._bins.append(coverageBin)
1246 @readonly
1247 def CrossedItems(self) -> list[str]:
1248 """
1249 Read-only property to access the names of the crossed items (:attr:`_crossedItems`).
1251 :returns: The names, one per dimension.
1252 """
1253 return self._crossedItems
1255 @readonly
1256 def Dimensions(self) -> int:
1257 """
1258 Read-only property to return the number of crossed items.
1260 :returns: The number of crossed items; at least ``2``.
1261 """
1262 return len(self._crossedItems)
1265@export
1266@abstractclass
1267class Bin(Base):
1268 """
1269 Base-class of the bins of a coverage item - coverpoint bins and cross bins -: a name, a kind, how often it was hit,
1270 and how often it must be hit to be covered.
1271 """
1273 _name: Nullable[str] #: Name of the bin, if the report says.
1274 _kind: BinKind #: Kind of the bin.
1275 _count: int #: How often the bin was hit.
1276 _atLeast: int #: How often the bin must be hit to be covered.
1278 def __init__(
1279 self,
1280 name: Nullable[str],
1281 kind: BinKind,
1282 count: int,
1283 atLeast: int = 1,
1284 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None,
1285 *,
1286 parent: Nullable[CoverageItem] = None
1287 ) -> None:
1288 """
1289 Initializes the key-value pairs, the parent, the name, the kind and the counts.
1291 :param name: Name of the bin; ``None``, if the report doesn't say.
1292 :param kind: Kind of the bin.
1293 :param count: How often the bin was hit.
1294 :param atLeast: Optional, how often the bin must be hit to be covered. Default: ``1``.
1295 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``.
1296 :param parent: Optional, the coverage item containing the bin. Default: ``None``.
1297 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
1298 :raises ValueError: If parameter ``name`` is empty.
1299 :raises ValueError: If parameter ``kind`` is ``None``.
1300 :raises TypeError: If parameter ``kind`` isn't of type :class:`BinKind`.
1301 :raises ValueError: If parameter ``count`` is ``None``.
1302 :raises TypeError: If parameter ``count`` isn't of type :class:`int`.
1303 :raises ValueError: If parameter ``count`` is negative.
1304 :raises ValueError: If parameter ``atLeast`` is ``None``.
1305 :raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`.
1306 :raises ValueError: If parameter ``atLeast`` is negative.
1307 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
1308 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`.
1309 """
1310 super().__init__(keyValuePairs, parent=parent)
1312 if name is not None:
1313 if not isinstance(name, str):
1314 ex = TypeError("Parameter 'name' is not of type 'str'.")
1315 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.")
1316 raise ex
1317 elif name == "":
1318 raise ValueError("Parameter 'name' is empty.")
1320 if kind is None:
1321 raise ValueError("Parameter 'kind' is None.")
1322 elif not isinstance(kind, BinKind):
1323 ex = TypeError("Parameter 'kind' is not of type 'BinKind'.")
1324 ex.add_note(f"Got type '{getFullyQualifiedName(kind)}'.")
1325 raise ex
1327 for parameterName, value in (
1328 ("count", count),
1329 ("atLeast", atLeast)
1330 ):
1331 if value is None:
1332 raise ValueError(f"Parameter '{parameterName}' is None.")
1333 elif not isinstance(value, int) or isinstance(value, bool):
1334 ex = TypeError(f"Parameter '{parameterName}' is not of type 'int'.")
1335 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
1336 raise ex
1337 elif value < 0:
1338 ex = ValueError(f"Parameter '{parameterName}' is negative.")
1339 ex.add_note(f"Got value '{value}'.")
1340 raise ex
1342 self._name = name
1343 self._kind = kind
1344 self._count = count
1345 self._atLeast = atLeast
1347 @readonly
1348 def Name(self) -> Nullable[str]:
1349 """
1350 Read-only property to access the name of the bin (:attr:`_name`).
1352 :returns: The name, or ``None`` if the report doesn't say.
1353 """
1354 return self._name
1356 @readonly
1357 def Kind(self) -> BinKind:
1358 """
1359 Read-only property to access the kind of the bin (:attr:`_kind`).
1361 :returns: The kind.
1362 """
1363 return self._kind
1365 @readonly
1366 def Count(self) -> int:
1367 """
1368 Read-only property to access how often the bin was hit (:attr:`_count`).
1370 :returns: The number of hits.
1371 """
1372 return self._count
1374 @readonly
1375 def AtLeast(self) -> int:
1376 """
1377 Read-only property to access how often the bin must be hit to be covered (:attr:`_atLeast`).
1379 :returns: The number of hits required.
1380 """
1381 return self._atLeast
1383 @readonly
1384 def IsCovered(self) -> bool:
1385 """
1386 Read-only property to return whether the bin counts toward the coverage and reached its *at least* count.
1388 :returns: ``True``, if the bin is of kind :attr:`BinKind.Count` and :attr:`Count` is at least :attr:`AtLeast`.
1389 """
1390 return self._kind is BinKind.Count and self._count >= self._atLeast
1392 def __repr__(self) -> str:
1393 """
1394 Return a representation of the bin for debugging.
1396 An unnamed bin hit 19 of 80 times reads ``<CoverpointBin (unnamed) Count: 19/80>``.
1398 :returns: The bin's class, name, kind and counts.
1399 """
1400 name = "(unnamed)" if self._name is None else self._name
1401 return f"<{self.__class__.__name__} {name} {self._kind.name}: {self._count}/{self._atLeast}>"
1404@export
1405class CoverpointBin(Bin):
1406 """
1407 A bin of a coverpoint: the ranges of values it counts.
1409 Its parent is the :class:`Coverpoint` containing it.
1410 """
1412 _PARENT_TYPE: ClassVar[tuple[type, ...]] = (Coverpoint, ) #: A coverpoint bin is in a coverpoint.
1414 _ranges: list[ValueRange] #: The ranges of values the bin counts.
1416 def __init__(
1417 self,
1418 name: Nullable[str],
1419 kind: BinKind,
1420 ranges: Iterable[ValueRange],
1421 count: int,
1422 atLeast: int = 1,
1423 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None,
1424 *,
1425 parent: Nullable[Coverpoint] = None
1426 ) -> None:
1427 """
1428 Initializes a coverpoint bin, and adds it to its coverpoint.
1430 :param name: Name of the bin; ``None``, if the report doesn't say.
1431 :param kind: Kind of the bin.
1432 :param ranges: The ranges of values the bin counts; empty for a default bin.
1433 :param count: How often the bin was hit.
1434 :param atLeast: Optional, how often the bin must be hit to be covered. Default: ``1``.
1435 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``.
1436 :param parent: Optional, the coverpoint containing the bin. Default: ``None``.
1437 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
1438 :raises ValueError: If parameter ``name`` is empty.
1439 :raises ValueError: If parameter ``kind`` is ``None``.
1440 :raises TypeError: If parameter ``kind`` isn't of type :class:`BinKind`.
1441 :raises ValueError: If parameter ``ranges`` is ``None``.
1442 :raises TypeError: If parameter ``ranges`` isn't iterable.
1443 :raises TypeError: If parameter ``ranges`` contains an element not of type :class:`ValueRange`.
1444 :raises ValueError: If parameter ``count`` is ``None``.
1445 :raises TypeError: If parameter ``count`` isn't of type :class:`int`.
1446 :raises ValueError: If parameter ``count`` is negative.
1447 :raises ValueError: If parameter ``atLeast`` is ``None``.
1448 :raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`.
1449 :raises ValueError: If parameter ``atLeast`` is negative.
1450 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
1451 :raises TypeError: If parameter ``parent`` isn't of type :class:`Coverpoint`.
1452 """
1453 super().__init__(name, kind, count, atLeast, keyValuePairs=keyValuePairs, parent=parent)
1455 if ranges is None:
1456 raise ValueError("Parameter 'ranges' is None.")
1457 elif not isinstance(ranges, Iterable):
1458 ex = TypeError("Parameter 'ranges' is not iterable.")
1459 ex.add_note(f"Got type '{getFullyQualifiedName(ranges)}'.")
1460 raise ex
1462 self._ranges = rangeList = []
1463 for valueRange in ranges:
1464 if not isinstance(valueRange, ValueRange):
1465 ex = TypeError("Parameter 'ranges' contains an element not of type 'ValueRange'.")
1466 ex.add_note(f"Got type '{getFullyQualifiedName(valueRange)}'.")
1467 raise ex
1469 rangeList.append(valueRange)
1471 if parent is not None:
1472 parent._AddElement(self)
1474 @readonly
1475 def Ranges(self) -> list[ValueRange]:
1476 """
1477 Read-only property to access the ranges of values the bin counts (:attr:`_ranges`).
1479 :returns: The ranges; empty for a default bin.
1480 """
1481 return self._ranges
1484@export
1485class CrossBin(Bin):
1486 """
1487 A bin of a cross: the combinations of values it counts, one set of ranges per crossed item.
1489 Its parent is the :class:`Cross` containing it.
1490 """
1492 _PARENT_TYPE: ClassVar[tuple[type, ...]] = (Cross, ) #: A cross bin is in a cross.
1494 _ranges: list[tuple[ValueRange, ...]] #: Per crossed item, the ranges of values the bin counts.
1496 def __init__(
1497 self,
1498 name: Nullable[str],
1499 kind: BinKind,
1500 ranges: Iterable[Iterable[ValueRange]],
1501 count: int,
1502 atLeast: int = 1,
1503 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None,
1504 *,
1505 parent: Nullable[Cross] = None
1506 ) -> None:
1507 """
1508 Initializes a cross bin, and adds it to its cross.
1510 :param name: Name of the bin; ``None``, if the report doesn't say.
1511 :param kind: Kind of the bin.
1512 :param ranges: Per crossed item, the ranges of values the bin counts.
1513 :param count: How often the bin was hit.
1514 :param atLeast: Optional, how often the bin must be hit to be covered. Default: ``1``.
1515 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``.
1516 :param parent: Optional, the cross containing the bin. Default: ``None``.
1517 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
1518 :raises ValueError: If parameter ``name`` is empty.
1519 :raises ValueError: If parameter ``kind`` is ``None``.
1520 :raises TypeError: If parameter ``kind`` isn't of type :class:`BinKind`.
1521 :raises ValueError: If parameter ``ranges`` is ``None``.
1522 :raises TypeError: If parameter ``ranges`` isn't iterable, or contains an element, which isn't.
1523 :raises TypeError: If parameter ``ranges`` contains a range not of type :class:`ValueRange`.
1524 :raises FunctionalCoverageError: If parameter ``ranges`` has not one set of ranges per crossed item of the cross.
1525 :raises ValueError: If parameter ``count`` is ``None``.
1526 :raises TypeError: If parameter ``count`` isn't of type :class:`int`.
1527 :raises ValueError: If parameter ``count`` is negative.
1528 :raises ValueError: If parameter ``atLeast`` is ``None``.
1529 :raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`.
1530 :raises ValueError: If parameter ``atLeast`` is negative.
1531 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping.
1532 :raises TypeError: If parameter ``parent`` isn't of type :class:`Cross`.
1533 """
1534 super().__init__(name, kind, count, atLeast, keyValuePairs=keyValuePairs, parent=parent)
1536 if ranges is None:
1537 raise ValueError("Parameter 'ranges' is None.")
1538 elif not isinstance(ranges, Iterable):
1539 ex = TypeError("Parameter 'ranges' is not iterable.")
1540 ex.add_note(f"Got type '{getFullyQualifiedName(ranges)}'.")
1541 raise ex
1543 self._ranges = rangeList = []
1544 for itemRanges in ranges:
1545 if not isinstance(itemRanges, Iterable):
1546 ex = TypeError("Parameter 'ranges' contains an element, which isn't iterable.")
1547 ex.add_note(f"Got type '{getFullyQualifiedName(itemRanges)}'.")
1548 raise ex
1550 itemRanges = tuple(itemRanges)
1551 for valueRange in itemRanges:
1552 if not isinstance(valueRange, ValueRange):
1553 ex = TypeError("Parameter 'ranges' contains a range not of type 'ValueRange'.")
1554 ex.add_note(f"Got type '{getFullyQualifiedName(valueRange)}'.")
1555 raise ex
1557 rangeList.append(itemRanges)
1559 if parent is not None:
1560 parent._AddElement(self)
1562 @readonly
1563 def Ranges(self) -> list[tuple[ValueRange, ...]]:
1564 """
1565 Read-only property to access the ranges of values the bin counts, per crossed item (:attr:`_ranges`).
1567 :returns: Per crossed item, in the order of :attr:`Cross.CrossedItems`, the ranges of values.
1568 """
1569 return self._ranges
1572@export
1573@abstractclass
1574class Document(metaclass=ExtendedType, slots=True):
1575 """
1576 A functional coverage report file, which is read in two steps: analyzed, then converted.
1578 A format's document derives from this class and mixes in the root of the format's model.
1579 """
1581 _path: Path #: Path to the report file.
1582 _analysisDuration: Nullable[timedelta] #: Duration of :meth:`Analyze`, or ``None`` before it ran.
1583 _conversionDuration: Nullable[timedelta] #: Duration of :meth:`Convert`, or ``None`` before it ran.
1585 def __init__(self, reportFile: Path) -> None:
1586 """
1587 Initializes the report file.
1589 :param reportFile: Path to the report file.
1590 :raises ValueError: If parameter ``reportFile`` is ``None``.
1591 :raises TypeError: If parameter ``reportFile`` isn't of type :class:`~pathlib.Path`.
1592 """
1593 if reportFile is None:
1594 raise ValueError("Parameter 'reportFile' is None.")
1595 elif not isinstance(reportFile, Path):
1596 ex = TypeError("Parameter 'reportFile' is not of type 'Path'.")
1597 ex.add_note(f"Got type '{getFullyQualifiedName(reportFile)}'.")
1598 raise ex
1600 self._path = reportFile
1601 self._analysisDuration = None
1602 self._conversionDuration = None
1604 @readonly
1605 def ReportFile(self) -> Path:
1606 """
1607 Read-only property to access the path to the report file (:attr:`_path`).
1609 :returns: The report file's path.
1610 """
1611 return self._path
1613 @readonly
1614 def AnalysisDuration(self) -> Nullable[timedelta]:
1615 """
1616 Read-only property to access the duration of :meth:`Analyze`: reading, parsing and validating the file
1617 (:attr:`_analysisDuration`).
1619 :returns: The duration, or ``None`` if the file wasn't analyzed yet.
1620 """
1621 return self._analysisDuration
1623 @readonly
1624 def ModelConversionDuration(self) -> Nullable[timedelta]:
1625 """
1626 Read-only property to access the duration of :meth:`Convert`: building the report format's model
1627 (:attr:`_conversionDuration`).
1629 :returns: The duration, or ``None`` if the content wasn't converted yet.
1630 """
1631 return self._conversionDuration
1633 @abstractmethod
1634 def Analyze(self) -> None:
1635 """
1636 Read, parse and validate the report file.
1637 """
1639 @abstractmethod
1640 def Convert(self) -> None:
1641 """
1642 Convert the analyzed content to the report format's model.
1643 """
1645 @abstractmethod
1646 def ConvertToFunctionalCoverageReport(self) -> FunctionalCoverageReport:
1647 """
1648 Convert the report format's model to the functional coverage model, and aggregate it.
1650 :returns: The report's root of the functional coverage model.
1651 """