Coverage for pyEDAA/Reports/CodeCoverage/UCIS/CoverOptions.py: 66%
150 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-21 23:49 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-21 23:49 +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"""
32The options of the covergroup coverage of the UCIS XML interchange format: the ``<options>`` of a covergroup instance,
33a coverpoint or a cross, as SystemVerilog declares them.
34"""
35from __future__ import annotations
37from typing import Self
39from lxml.etree import _Element
40from pyTooling.Common import getFullyQualifiedName
41from pyTooling.Decorators import export, readonly
42from pyTooling.MetaClasses import ExtendedType, abstractclass
44from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base
47@export
48@abstractclass
49class Options(metaclass=ExtendedType, slots=True):
50 """
51 Base-class of the ``<options>`` of a covergroup instance, a coverpoint or a cross: the options SystemVerilog declares
52 for all three.
54 It is a value, as a path is: it names no parent.
55 """
57 _weight: int #: Weight in computing the coverage.
58 _goal: int #: Coverage goal, in percent.
59 _comment: str #: A comment.
60 _atLeast: int #: How often a bin must be hit to be covered.
62 def __init__(self, weight: int = 1, goal: int = 100, comment: str = "", atLeast: int = 1) -> None:
63 """
64 Initialize the options.
66 :param weight: Optional, weight in computing the coverage. Default: ``1``.
67 :param goal: Optional, coverage goal, in percent. Default: ``100``.
68 :param comment: Optional, a comment. Default: ``""``.
69 :param atLeast: Optional, how often a bin must be hit to be covered. Default: ``1``.
70 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is ``None``.
71 :raises TypeError: If parameter ``weight``, ``goal`` or ``atLeast`` isn't of type :class:`int`.
72 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is negative.
73 :raises ValueError: If parameter ``comment`` is ``None``.
74 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
75 """
76 for parameterName, value in (
77 ("weight", weight),
78 ("goal", goal),
79 ("atLeast", atLeast)
80 ):
81 if value is None: 81 ↛ 82line 81 didn't jump to line 82 because the condition on line 81 was never true
82 raise ValueError(f"Parameter '{parameterName}' is None.")
83 elif not isinstance(value, int) or isinstance(value, bool): 83 ↛ 84line 83 didn't jump to line 84 because the condition on line 83 was never true
84 ex = TypeError(f"Parameter '{parameterName}' is not of type 'int'.")
85 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
86 raise ex
87 elif value < 0:
88 ex = ValueError(f"Parameter '{parameterName}' is negative.")
89 ex.add_note(f"Got value '{value}'.")
90 raise ex
92 if comment is None: 92 ↛ 93line 92 didn't jump to line 93 because the condition on line 92 was never true
93 raise ValueError("Parameter 'comment' is None.")
94 elif not isinstance(comment, str): 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true
95 ex = TypeError("Parameter 'comment' is not of type 'str'.")
96 ex.add_note(f"Got type '{getFullyQualifiedName(comment)}'.")
97 raise ex
99 self._weight = weight
100 self._goal = goal
101 self._comment = comment
102 self._atLeast = atLeast
104 @readonly
105 def Weight(self) -> int:
106 """
107 Read-only property to access the weight in computing the coverage (:attr:`_weight`).
109 :returns: The weight; ``1``, if the report states none.
110 """
111 return self._weight
113 @readonly
114 def Goal(self) -> int:
115 """
116 Read-only property to access the coverage goal (:attr:`_goal`).
118 :returns: The goal, in percent; ``100``, if the report states none.
119 """
120 return self._goal
122 @readonly
123 def Comment(self) -> str:
124 """
125 Read-only property to access the comment (:attr:`_comment`).
127 :returns: The comment; empty, if the report states none.
128 """
129 return self._comment
131 @readonly
132 def AtLeast(self) -> int:
133 """
134 Read-only property to access how often a bin must be hit to be covered (:attr:`_atLeast`).
136 :returns: The count; ``1``, if the report states none.
137 """
138 return self._atLeast
141@export
142class CoverpointOptions(Options):
143 """
144 The ``<options>`` of a coverpoint.
145 """
147 _detectOverlap: bool #: Whether to warn about overlapping bins.
148 _autoBinMax: int #: Maximum number of bins created automatically.
150 def __init__(
151 self,
152 weight: int = 1,
153 goal: int = 100,
154 comment: str = "",
155 atLeast: int = 1,
156 detectOverlap: bool = False,
157 autoBinMax: int = 64
158 ) -> None:
159 """
160 Initialize the options of a coverpoint.
162 :param weight: Optional, weight in computing the coverage. Default: ``1``.
163 :param goal: Optional, coverage goal, in percent. Default: ``100``.
164 :param comment: Optional, a comment. Default: ``""``.
165 :param atLeast: Optional, how often a bin must be hit to be covered. Default: ``1``.
166 :param detectOverlap: Optional, whether to warn about overlapping bins. Default: ``False``.
167 :param autoBinMax: Optional, maximum number of bins created automatically. Default: ``64``.
168 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is ``None``.
169 :raises TypeError: If parameter ``weight``, ``goal`` or ``atLeast`` isn't of type :class:`int`.
170 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is negative.
171 :raises ValueError: If parameter ``comment`` is ``None``.
172 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
173 :raises ValueError: If parameter ``detectOverlap`` is ``None``.
174 :raises TypeError: If parameter ``detectOverlap`` isn't of type :class:`bool`.
175 :raises ValueError: If parameter ``autoBinMax`` is ``None``.
176 :raises TypeError: If parameter ``autoBinMax`` isn't of type :class:`int`.
177 :raises ValueError: If parameter ``autoBinMax`` is negative.
178 """
179 super().__init__(weight, goal, comment, atLeast)
181 if detectOverlap is None: 181 ↛ 182line 181 didn't jump to line 182 because the condition on line 181 was never true
182 raise ValueError("Parameter 'detectOverlap' is None.")
183 elif not isinstance(detectOverlap, bool): 183 ↛ 184line 183 didn't jump to line 184 because the condition on line 183 was never true
184 ex = TypeError("Parameter 'detectOverlap' is not of type 'bool'.")
185 ex.add_note(f"Got type '{getFullyQualifiedName(detectOverlap)}'.")
186 raise ex
188 if autoBinMax is None: 188 ↛ 189line 188 didn't jump to line 189 because the condition on line 188 was never true
189 raise ValueError("Parameter 'autoBinMax' is None.")
190 elif not isinstance(autoBinMax, int) or isinstance(autoBinMax, bool): 190 ↛ 191line 190 didn't jump to line 191 because the condition on line 190 was never true
191 ex = TypeError("Parameter 'autoBinMax' is not of type 'int'.")
192 ex.add_note(f"Got type '{getFullyQualifiedName(autoBinMax)}'.")
193 raise ex
194 elif autoBinMax < 0: 194 ↛ 195line 194 didn't jump to line 195 because the condition on line 194 was never true
195 ex = ValueError("Parameter 'autoBinMax' is negative.")
196 ex.add_note(f"Got value '{autoBinMax}'.")
197 raise ex
199 self._detectOverlap = detectOverlap
200 self._autoBinMax = autoBinMax
202 @classmethod
203 def Parse(cls, element: _Element) -> Self:
204 """
205 Parse the options of a coverpoint from its ``<options>`` element; an option it doesn't state has its default.
207 :param element: The ``<options>`` element.
208 :returns: The options.
209 """
210 attributes = element.attrib
211 return cls(
212 int(attributes.get("weight", "1")),
213 int(attributes.get("goal", "100")),
214 attributes.get("comment", ""),
215 int(attributes.get("at_least", "1")),
216 Base._ParseBoolean(attributes.get("detect_overlap", "false")),
217 int(attributes.get("auto_bin_max", "64"))
218 )
220 @readonly
221 def DetectOverlap(self) -> bool:
222 """
223 Read-only property to access whether to warn about overlapping bins (:attr:`_detectOverlap`).
225 :returns: ``True``, if overlapping bins are reported.
226 """
227 return self._detectOverlap
229 @readonly
230 def AutoBinMax(self) -> int:
231 """
232 Read-only property to access the maximum number of bins created automatically (:attr:`_autoBinMax`).
234 :returns: The maximum; ``64``, if the report states none.
235 """
236 return self._autoBinMax
239@export
240class CrossOptions(Options):
241 """
242 The ``<options>`` of a cross.
243 """
245 _crossNumPrintMissing: int #: Number of uncovered cross bins to save and report.
247 def __init__(
248 self,
249 weight: int = 1,
250 goal: int = 100,
251 comment: str = "",
252 atLeast: int = 1,
253 crossNumPrintMissing: int = 0
254 ) -> None:
255 """
256 Initialize the options of a cross.
258 :param weight: Optional, weight in computing the coverage. Default: ``1``.
259 :param goal: Optional, coverage goal, in percent. Default: ``100``.
260 :param comment: Optional, a comment. Default: ``""``.
261 :param atLeast: Optional, how often a bin must be hit to be covered. Default: ``1``.
262 :param crossNumPrintMissing: Optional, number of uncovered cross bins to save and report. Default: ``0``.
263 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is ``None``.
264 :raises TypeError: If parameter ``weight``, ``goal`` or ``atLeast`` isn't of type :class:`int`.
265 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is negative.
266 :raises ValueError: If parameter ``comment`` is ``None``.
267 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
268 :raises ValueError: If parameter ``crossNumPrintMissing`` is ``None``.
269 :raises TypeError: If parameter ``crossNumPrintMissing`` isn't of type :class:`int`.
270 :raises ValueError: If parameter ``crossNumPrintMissing`` is negative.
271 """
272 super().__init__(weight, goal, comment, atLeast)
274 if crossNumPrintMissing is None: 274 ↛ 275line 274 didn't jump to line 275 because the condition on line 274 was never true
275 raise ValueError("Parameter 'crossNumPrintMissing' is None.")
276 elif not isinstance(crossNumPrintMissing, int) or isinstance(crossNumPrintMissing, bool): 276 ↛ 277line 276 didn't jump to line 277 because the condition on line 276 was never true
277 ex = TypeError("Parameter 'crossNumPrintMissing' is not of type 'int'.")
278 ex.add_note(f"Got type '{getFullyQualifiedName(crossNumPrintMissing)}'.")
279 raise ex
280 elif crossNumPrintMissing < 0: 280 ↛ 281line 280 didn't jump to line 281 because the condition on line 280 was never true
281 ex = ValueError("Parameter 'crossNumPrintMissing' is negative.")
282 ex.add_note(f"Got value '{crossNumPrintMissing}'.")
283 raise ex
285 self._crossNumPrintMissing = crossNumPrintMissing
287 @classmethod
288 def Parse(cls, element: _Element) -> Self:
289 """
290 Parse the options of a cross from its ``<options>`` element; an option it doesn't state has its default.
292 :param element: The ``<options>`` element.
293 :returns: The options.
294 """
295 attributes = element.attrib
296 return cls(
297 int(attributes.get("weight", "1")),
298 int(attributes.get("goal", "100")),
299 attributes.get("comment", ""),
300 int(attributes.get("at_least", "1")),
301 int(attributes.get("cross_num_print_missing", "0"))
302 )
304 @readonly
305 def CrossNumPrintMissing(self) -> int:
306 """
307 Read-only property to access the number of uncovered cross bins to save and report (:attr:`_crossNumPrintMissing`).
309 :returns: The number; ``0``, if the report states none.
310 """
311 return self._crossNumPrintMissing
314@export
315class CovergroupOptions(CoverpointOptions):
316 """
317 The ``<options>`` of a covergroup instance.
318 """
320 _crossNumPrintMissing: int #: Number of uncovered cross bins to save and report.
321 _perInstance: bool #: Whether the instance's coverage is kept on its own.
322 _mergeInstances: bool #: Whether the covergroup's coverage merges its instances.
324 def __init__(
325 self,
326 weight: int = 1,
327 goal: int = 100,
328 comment: str = "",
329 atLeast: int = 1,
330 detectOverlap: bool = False,
331 autoBinMax: int = 64,
332 crossNumPrintMissing: int = 0,
333 perInstance: bool = False,
334 mergeInstances: bool = False
335 ) -> None:
336 """
337 Initialize the options of a covergroup instance.
339 :param weight: Optional, weight in computing the coverage. Default: ``1``.
340 :param goal: Optional, coverage goal, in percent. Default: ``100``.
341 :param comment: Optional, a comment. Default: ``""``.
342 :param atLeast: Optional, how often a bin must be hit to be covered. Default: ``1``.
343 :param detectOverlap: Optional, whether to warn about overlapping bins. Default: ``False``.
344 :param autoBinMax: Optional, maximum number of bins created automatically. Default: ``64``.
345 :param crossNumPrintMissing: Optional, number of uncovered cross bins to save and report. Default: ``0``.
346 :param perInstance: Optional, whether the instance's coverage is kept on its own. Default: ``False``.
347 :param mergeInstances: Optional, whether the covergroup's coverage merges its instances. Default:
348 ``False``.
349 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is ``None``.
350 :raises TypeError: If parameter ``weight``, ``goal`` or ``atLeast`` isn't of type :class:`int`.
351 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is negative.
352 :raises ValueError: If parameter ``comment`` is ``None``.
353 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
354 :raises ValueError: If parameter ``detectOverlap`` is ``None``.
355 :raises TypeError: If parameter ``detectOverlap`` isn't of type :class:`bool`.
356 :raises ValueError: If parameter ``autoBinMax`` is ``None``.
357 :raises TypeError: If parameter ``autoBinMax`` isn't of type :class:`int`.
358 :raises ValueError: If parameter ``autoBinMax`` is negative.
359 :raises ValueError: If parameter ``crossNumPrintMissing`` is ``None``.
360 :raises TypeError: If parameter ``crossNumPrintMissing`` isn't of type :class:`int`.
361 :raises ValueError: If parameter ``crossNumPrintMissing`` is negative.
362 :raises ValueError: If parameter ``perInstance`` or ``mergeInstances`` is ``None``.
363 :raises TypeError: If parameter ``perInstance`` or ``mergeInstances`` isn't of type :class:`bool`.
364 """
365 super().__init__(weight, goal, comment, atLeast, detectOverlap, autoBinMax)
367 if crossNumPrintMissing is None: 367 ↛ 368line 367 didn't jump to line 368 because the condition on line 367 was never true
368 raise ValueError("Parameter 'crossNumPrintMissing' is None.")
369 elif not isinstance(crossNumPrintMissing, int) or isinstance(crossNumPrintMissing, bool): 369 ↛ 370line 369 didn't jump to line 370 because the condition on line 369 was never true
370 ex = TypeError("Parameter 'crossNumPrintMissing' is not of type 'int'.")
371 ex.add_note(f"Got type '{getFullyQualifiedName(crossNumPrintMissing)}'.")
372 raise ex
373 elif crossNumPrintMissing < 0: 373 ↛ 374line 373 didn't jump to line 374 because the condition on line 373 was never true
374 ex = ValueError("Parameter 'crossNumPrintMissing' is negative.")
375 ex.add_note(f"Got value '{crossNumPrintMissing}'.")
376 raise ex
378 if perInstance is None: 378 ↛ 379line 378 didn't jump to line 379 because the condition on line 378 was never true
379 raise ValueError("Parameter 'perInstance' is None.")
380 elif not isinstance(perInstance, bool): 380 ↛ 381line 380 didn't jump to line 381 because the condition on line 380 was never true
381 ex = TypeError("Parameter 'perInstance' is not of type 'bool'.")
382 ex.add_note(f"Got type '{getFullyQualifiedName(perInstance)}'.")
383 raise ex
385 if mergeInstances is None: 385 ↛ 386line 385 didn't jump to line 386 because the condition on line 385 was never true
386 raise ValueError("Parameter 'mergeInstances' is None.")
387 elif not isinstance(mergeInstances, bool): 387 ↛ 388line 387 didn't jump to line 388 because the condition on line 387 was never true
388 ex = TypeError("Parameter 'mergeInstances' is not of type 'bool'.")
389 ex.add_note(f"Got type '{getFullyQualifiedName(mergeInstances)}'.")
390 raise ex
392 self._crossNumPrintMissing = crossNumPrintMissing
393 self._perInstance = perInstance
394 self._mergeInstances = mergeInstances
396 @classmethod
397 def Parse(cls, element: _Element) -> Self:
398 """
399 Parse the options of a covergroup instance from its ``<options>`` element; an option it doesn't state has its
400 default.
402 :param element: The ``<options>`` element.
403 :returns: The options.
404 """
405 attributes = element.attrib
406 return cls(
407 int(attributes.get("weight", "1")),
408 int(attributes.get("goal", "100")),
409 attributes.get("comment", ""),
410 int(attributes.get("at_least", "1")),
411 Base._ParseBoolean(attributes.get("detect_overlap", "false")),
412 int(attributes.get("auto_bin_max", "64")),
413 int(attributes.get("cross_num_print_missing", "0")),
414 Base._ParseBoolean(attributes.get("per_instance", "false")),
415 Base._ParseBoolean(attributes.get("merge_instances", "false"))
416 )
418 @readonly
419 def CrossNumPrintMissing(self) -> int:
420 """
421 Read-only property to access the number of uncovered cross bins to save and report (:attr:`_crossNumPrintMissing`).
423 :returns: The number; ``0``, if the report states none.
424 """
425 return self._crossNumPrintMissing
427 @readonly
428 def PerInstance(self) -> bool:
429 """
430 Read-only property to access whether the instance's coverage is kept on its own (:attr:`_perInstance`).
432 :returns: ``True``, if kept per instance.
433 """
434 return self._perInstance
436 @readonly
437 def MergeInstances(self) -> bool:
438 """
439 Read-only property to access whether the covergroup's coverage merges its instances (:attr:`_mergeInstances`).
441 :returns: ``True``, if the instances are merged; otherwise, it is their weighted average.
442 """
443 return self._mergeInstances