Coverage for pyEDAA/Reports/CodeCoverage/UCIS/CoverOptions.py: 66%
150 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"""
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`` is ``None``.
71 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
72 :raises ValueError: If parameter ``weight`` is negative.
73 :raises ValueError: If parameter ``goal`` is ``None``.
74 :raises TypeError: If parameter ``goal`` isn't of type :class:`int`.
75 :raises ValueError: If parameter ``goal`` is negative.
76 :raises ValueError: If parameter ``comment`` is ``None``.
77 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
78 :raises ValueError: If parameter ``atLeast`` is ``None``.
79 :raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`.
80 :raises ValueError: If parameter ``atLeast`` is negative.
81 """
82 for parameterName, value in (
83 ("weight", weight),
84 ("goal", goal),
85 ("atLeast", atLeast)
86 ):
87 if value is None: 87 ↛ 88line 87 didn't jump to line 88 because the condition on line 87 was never true
88 raise ValueError(f"Parameter '{parameterName}' is None.")
89 elif not isinstance(value, int) or isinstance(value, bool): 89 ↛ 90line 89 didn't jump to line 90 because the condition on line 89 was never true
90 ex = TypeError(f"Parameter '{parameterName}' is not of type 'int'.")
91 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
92 raise ex
93 elif value < 0:
94 ex = ValueError(f"Parameter '{parameterName}' is negative.")
95 ex.add_note(f"Got value '{value}'.")
96 raise ex
98 if comment is None: 98 ↛ 99line 98 didn't jump to line 99 because the condition on line 98 was never true
99 raise ValueError("Parameter 'comment' is None.")
100 elif not isinstance(comment, str): 100 ↛ 101line 100 didn't jump to line 101 because the condition on line 100 was never true
101 ex = TypeError("Parameter 'comment' is not of type 'str'.")
102 ex.add_note(f"Got type '{getFullyQualifiedName(comment)}'.")
103 raise ex
105 self._weight = weight
106 self._goal = goal
107 self._comment = comment
108 self._atLeast = atLeast
110 @readonly
111 def Weight(self) -> int:
112 """
113 Read-only property to access the weight in computing the coverage (:attr:`_weight`).
115 :returns: The weight; ``1``, if the report states none.
116 """
117 return self._weight
119 @readonly
120 def Goal(self) -> int:
121 """
122 Read-only property to access the coverage goal (:attr:`_goal`).
124 :returns: The goal, in percent; ``100``, if the report states none.
125 """
126 return self._goal
128 @readonly
129 def Comment(self) -> str:
130 """
131 Read-only property to access the comment (:attr:`_comment`).
133 :returns: The comment; empty, if the report states none.
134 """
135 return self._comment
137 @readonly
138 def AtLeast(self) -> int:
139 """
140 Read-only property to access how often a bin must be hit to be covered (:attr:`_atLeast`).
142 :returns: The count; ``1``, if the report states none.
143 """
144 return self._atLeast
147@export
148class CoverpointOptions(Options):
149 """
150 The ``<options>`` of a coverpoint.
151 """
153 _detectOverlap: bool #: Whether to warn about overlapping bins.
154 _autoBinMax: int #: Maximum number of bins created automatically.
156 def __init__(
157 self,
158 weight: int = 1,
159 goal: int = 100,
160 comment: str = "",
161 atLeast: int = 1,
162 detectOverlap: bool = False,
163 autoBinMax: int = 64
164 ) -> None:
165 """
166 Initialize the options of a coverpoint.
168 :param weight: Optional, weight in computing the coverage. Default: ``1``.
169 :param goal: Optional, coverage goal, in percent. Default: ``100``.
170 :param comment: Optional, a comment. Default: ``""``.
171 :param atLeast: Optional, how often a bin must be hit to be covered. Default: ``1``.
172 :param detectOverlap: Optional, whether to warn about overlapping bins. Default: ``False``.
173 :param autoBinMax: Optional, maximum number of bins created automatically. Default: ``64``.
174 :raises ValueError: If parameter ``weight`` is ``None``.
175 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
176 :raises ValueError: If parameter ``weight`` is negative.
177 :raises ValueError: If parameter ``goal`` is ``None``.
178 :raises TypeError: If parameter ``goal`` isn't of type :class:`int`.
179 :raises ValueError: If parameter ``goal`` is negative.
180 :raises ValueError: If parameter ``comment`` is ``None``.
181 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
182 :raises ValueError: If parameter ``atLeast`` is ``None``.
183 :raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`.
184 :raises ValueError: If parameter ``atLeast`` is negative.
185 :raises ValueError: If parameter ``detectOverlap`` is ``None``.
186 :raises TypeError: If parameter ``detectOverlap`` isn't of type :class:`bool`.
187 :raises ValueError: If parameter ``autoBinMax`` is ``None``.
188 :raises TypeError: If parameter ``autoBinMax`` isn't of type :class:`int`.
189 :raises ValueError: If parameter ``autoBinMax`` is negative.
190 """
191 super().__init__(weight, goal, comment, atLeast)
193 if detectOverlap is None: 193 ↛ 194line 193 didn't jump to line 194 because the condition on line 193 was never true
194 raise ValueError("Parameter 'detectOverlap' is None.")
195 elif not isinstance(detectOverlap, bool): 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true
196 ex = TypeError("Parameter 'detectOverlap' is not of type 'bool'.")
197 ex.add_note(f"Got type '{getFullyQualifiedName(detectOverlap)}'.")
198 raise ex
200 if autoBinMax is None: 200 ↛ 201line 200 didn't jump to line 201 because the condition on line 200 was never true
201 raise ValueError("Parameter 'autoBinMax' is None.")
202 elif not isinstance(autoBinMax, int) or isinstance(autoBinMax, bool): 202 ↛ 203line 202 didn't jump to line 203 because the condition on line 202 was never true
203 ex = TypeError("Parameter 'autoBinMax' is not of type 'int'.")
204 ex.add_note(f"Got type '{getFullyQualifiedName(autoBinMax)}'.")
205 raise ex
206 elif autoBinMax < 0: 206 ↛ 207line 206 didn't jump to line 207 because the condition on line 206 was never true
207 ex = ValueError("Parameter 'autoBinMax' is negative.")
208 ex.add_note(f"Got value '{autoBinMax}'.")
209 raise ex
211 self._detectOverlap = detectOverlap
212 self._autoBinMax = autoBinMax
214 @classmethod
215 def Parse(cls, element: _Element) -> Self:
216 """
217 Parse the options of a coverpoint from its ``<options>`` element; an option it doesn't state has its default.
219 :param element: The ``<options>`` element.
220 :returns: The options.
221 """
222 attributes = element.attrib
223 return cls(
224 int(attributes.get("weight", "1")),
225 int(attributes.get("goal", "100")),
226 attributes.get("comment", ""),
227 int(attributes.get("at_least", "1")),
228 Base._ParseBoolean(attributes.get("detect_overlap", "false")),
229 int(attributes.get("auto_bin_max", "64"))
230 )
232 @readonly
233 def DetectOverlap(self) -> bool:
234 """
235 Read-only property to access whether to warn about overlapping bins (:attr:`_detectOverlap`).
237 :returns: ``True``, if overlapping bins are reported.
238 """
239 return self._detectOverlap
241 @readonly
242 def AutoBinMax(self) -> int:
243 """
244 Read-only property to access the maximum number of bins created automatically (:attr:`_autoBinMax`).
246 :returns: The maximum; ``64``, if the report states none.
247 """
248 return self._autoBinMax
251@export
252class CrossOptions(Options):
253 """
254 The ``<options>`` of a cross.
255 """
257 _crossNumPrintMissing: int #: Number of uncovered cross bins to save and report.
259 def __init__(
260 self,
261 weight: int = 1,
262 goal: int = 100,
263 comment: str = "",
264 atLeast: int = 1,
265 crossNumPrintMissing: int = 0
266 ) -> None:
267 """
268 Initialize the options of a cross.
270 :param weight: Optional, weight in computing the coverage. Default: ``1``.
271 :param goal: Optional, coverage goal, in percent. Default: ``100``.
272 :param comment: Optional, a comment. Default: ``""``.
273 :param atLeast: Optional, how often a bin must be hit to be covered. Default: ``1``.
274 :param crossNumPrintMissing: Optional, number of uncovered cross bins to save and report. Default: ``0``.
275 :raises ValueError: If parameter ``weight`` is ``None``.
276 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
277 :raises ValueError: If parameter ``weight`` is negative.
278 :raises ValueError: If parameter ``goal`` is ``None``.
279 :raises TypeError: If parameter ``goal`` isn't of type :class:`int`.
280 :raises ValueError: If parameter ``goal`` is negative.
281 :raises ValueError: If parameter ``comment`` is ``None``.
282 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
283 :raises ValueError: If parameter ``atLeast`` is ``None``.
284 :raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`.
285 :raises ValueError: If parameter ``atLeast`` is negative.
286 :raises ValueError: If parameter ``crossNumPrintMissing`` is ``None``.
287 :raises TypeError: If parameter ``crossNumPrintMissing`` isn't of type :class:`int`.
288 :raises ValueError: If parameter ``crossNumPrintMissing`` is negative.
289 """
290 super().__init__(weight, goal, comment, atLeast)
292 if crossNumPrintMissing is None: 292 ↛ 293line 292 didn't jump to line 293 because the condition on line 292 was never true
293 raise ValueError("Parameter 'crossNumPrintMissing' is None.")
294 elif not isinstance(crossNumPrintMissing, int) or isinstance(crossNumPrintMissing, bool): 294 ↛ 295line 294 didn't jump to line 295 because the condition on line 294 was never true
295 ex = TypeError("Parameter 'crossNumPrintMissing' is not of type 'int'.")
296 ex.add_note(f"Got type '{getFullyQualifiedName(crossNumPrintMissing)}'.")
297 raise ex
298 elif crossNumPrintMissing < 0: 298 ↛ 299line 298 didn't jump to line 299 because the condition on line 298 was never true
299 ex = ValueError("Parameter 'crossNumPrintMissing' is negative.")
300 ex.add_note(f"Got value '{crossNumPrintMissing}'.")
301 raise ex
303 self._crossNumPrintMissing = crossNumPrintMissing
305 @classmethod
306 def Parse(cls, element: _Element) -> Self:
307 """
308 Parse the options of a cross from its ``<options>`` element; an option it doesn't state has its default.
310 :param element: The ``<options>`` element.
311 :returns: The options.
312 """
313 attributes = element.attrib
314 return cls(
315 int(attributes.get("weight", "1")),
316 int(attributes.get("goal", "100")),
317 attributes.get("comment", ""),
318 int(attributes.get("at_least", "1")),
319 int(attributes.get("cross_num_print_missing", "0"))
320 )
322 @readonly
323 def CrossNumPrintMissing(self) -> int:
324 """
325 Read-only property to access the number of uncovered cross bins to save and report (:attr:`_crossNumPrintMissing`).
327 :returns: The number; ``0``, if the report states none.
328 """
329 return self._crossNumPrintMissing
332@export
333class CovergroupOptions(CoverpointOptions):
334 """
335 The ``<options>`` of a covergroup instance.
336 """
338 _crossNumPrintMissing: int #: Number of uncovered cross bins to save and report.
339 _perInstance: bool #: Whether the instance's coverage is kept on its own.
340 _mergeInstances: bool #: Whether the covergroup's coverage merges its instances.
342 def __init__(
343 self,
344 weight: int = 1,
345 goal: int = 100,
346 comment: str = "",
347 atLeast: int = 1,
348 detectOverlap: bool = False,
349 autoBinMax: int = 64,
350 crossNumPrintMissing: int = 0,
351 perInstance: bool = False,
352 mergeInstances: bool = False
353 ) -> None:
354 """
355 Initialize the options of a covergroup instance.
357 :param weight: Optional, weight in computing the coverage. Default: ``1``.
358 :param goal: Optional, coverage goal, in percent. Default: ``100``.
359 :param comment: Optional, a comment. Default: ``""``.
360 :param atLeast: Optional, how often a bin must be hit to be covered. Default: ``1``.
361 :param detectOverlap: Optional, whether to warn about overlapping bins. Default: ``False``.
362 :param autoBinMax: Optional, maximum number of bins created automatically. Default: ``64``.
363 :param crossNumPrintMissing: Optional, number of uncovered cross bins to save and report. Default: ``0``.
364 :param perInstance: Optional, whether the instance's coverage is kept on its own. Default: ``False``.
365 :param mergeInstances: Optional, whether the covergroup's coverage merges its instances. Default:
366 ``False``.
367 :raises ValueError: If parameter ``weight`` is ``None``.
368 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
369 :raises ValueError: If parameter ``weight`` is negative.
370 :raises ValueError: If parameter ``goal`` is ``None``.
371 :raises TypeError: If parameter ``goal`` isn't of type :class:`int`.
372 :raises ValueError: If parameter ``goal`` is negative.
373 :raises ValueError: If parameter ``comment`` is ``None``.
374 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
375 :raises ValueError: If parameter ``atLeast`` is ``None``.
376 :raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`.
377 :raises ValueError: If parameter ``atLeast`` is negative.
378 :raises ValueError: If parameter ``detectOverlap`` is ``None``.
379 :raises TypeError: If parameter ``detectOverlap`` isn't of type :class:`bool`.
380 :raises ValueError: If parameter ``autoBinMax`` is ``None``.
381 :raises TypeError: If parameter ``autoBinMax`` isn't of type :class:`int`.
382 :raises ValueError: If parameter ``autoBinMax`` is negative.
383 :raises ValueError: If parameter ``crossNumPrintMissing`` is ``None``.
384 :raises TypeError: If parameter ``crossNumPrintMissing`` isn't of type :class:`int`.
385 :raises ValueError: If parameter ``crossNumPrintMissing`` is negative.
386 :raises ValueError: If parameter ``perInstance`` is ``None``.
387 :raises TypeError: If parameter ``perInstance`` isn't of type :class:`bool`.
388 :raises ValueError: If parameter ``mergeInstances`` is ``None``.
389 :raises TypeError: If parameter ``mergeInstances`` isn't of type :class:`bool`.
390 """
391 super().__init__(weight, goal, comment, atLeast, detectOverlap, autoBinMax)
393 if crossNumPrintMissing is None: 393 ↛ 394line 393 didn't jump to line 394 because the condition on line 393 was never true
394 raise ValueError("Parameter 'crossNumPrintMissing' is None.")
395 elif not isinstance(crossNumPrintMissing, int) or isinstance(crossNumPrintMissing, bool): 395 ↛ 396line 395 didn't jump to line 396 because the condition on line 395 was never true
396 ex = TypeError("Parameter 'crossNumPrintMissing' is not of type 'int'.")
397 ex.add_note(f"Got type '{getFullyQualifiedName(crossNumPrintMissing)}'.")
398 raise ex
399 elif crossNumPrintMissing < 0: 399 ↛ 400line 399 didn't jump to line 400 because the condition on line 399 was never true
400 ex = ValueError("Parameter 'crossNumPrintMissing' is negative.")
401 ex.add_note(f"Got value '{crossNumPrintMissing}'.")
402 raise ex
404 if perInstance is None: 404 ↛ 405line 404 didn't jump to line 405 because the condition on line 404 was never true
405 raise ValueError("Parameter 'perInstance' is None.")
406 elif not isinstance(perInstance, bool): 406 ↛ 407line 406 didn't jump to line 407 because the condition on line 406 was never true
407 ex = TypeError("Parameter 'perInstance' is not of type 'bool'.")
408 ex.add_note(f"Got type '{getFullyQualifiedName(perInstance)}'.")
409 raise ex
411 if mergeInstances is None: 411 ↛ 412line 411 didn't jump to line 412 because the condition on line 411 was never true
412 raise ValueError("Parameter 'mergeInstances' is None.")
413 elif not isinstance(mergeInstances, bool): 413 ↛ 414line 413 didn't jump to line 414 because the condition on line 413 was never true
414 ex = TypeError("Parameter 'mergeInstances' is not of type 'bool'.")
415 ex.add_note(f"Got type '{getFullyQualifiedName(mergeInstances)}'.")
416 raise ex
418 self._crossNumPrintMissing = crossNumPrintMissing
419 self._perInstance = perInstance
420 self._mergeInstances = mergeInstances
422 @classmethod
423 def Parse(cls, element: _Element) -> Self:
424 """
425 Parse the options of a covergroup instance from its ``<options>`` element; an option it doesn't state has its
426 default.
428 :param element: The ``<options>`` element.
429 :returns: The options.
430 """
431 attributes = element.attrib
432 return cls(
433 int(attributes.get("weight", "1")),
434 int(attributes.get("goal", "100")),
435 attributes.get("comment", ""),
436 int(attributes.get("at_least", "1")),
437 Base._ParseBoolean(attributes.get("detect_overlap", "false")),
438 int(attributes.get("auto_bin_max", "64")),
439 int(attributes.get("cross_num_print_missing", "0")),
440 Base._ParseBoolean(attributes.get("per_instance", "false")),
441 Base._ParseBoolean(attributes.get("merge_instances", "false"))
442 )
444 @readonly
445 def CrossNumPrintMissing(self) -> int:
446 """
447 Read-only property to access the number of uncovered cross bins to save and report (:attr:`_crossNumPrintMissing`).
449 :returns: The number; ``0``, if the report states none.
450 """
451 return self._crossNumPrintMissing
453 @readonly
454 def PerInstance(self) -> bool:
455 """
456 Read-only property to access whether the instance's coverage is kept on its own (:attr:`_perInstance`).
458 :returns: ``True``, if kept per instance.
459 """
460 return self._perInstance
462 @readonly
463 def MergeInstances(self) -> bool:
464 """
465 Read-only property to access whether the covergroup's coverage merges its instances (:attr:`_mergeInstances`).
467 :returns: ``True``, if the instances are merged; otherwise, it is their weighted average.
468 """
469 return self._mergeInstances