Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Covergroups.py: 78%
184 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 covergroup coverage of the UCIS XML interchange format: an instance's ``<covergroupCoverage>``, its covergroup
33instances with their coverpoints and crosses.
34"""
35from __future__ import annotations
37from typing import TYPE_CHECKING, Iterable, Mapping, Optional as Nullable, Self
39from lxml.etree import _Element
40from pyTooling.Common import getFullyQualifiedName
41from pyTooling.Decorators import export, readonly
42from pyTooling.MetaClasses import ExtendedType
44from pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions import CovergroupOptions
45from pyEDAA.Reports.CodeCoverage.UCIS.Coverpoints import Coverpoint
46from pyEDAA.Reports.CodeCoverage.UCIS.Crosses import Cross
47from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, MetricCoverage, StatementID, UserAttribute
49if TYPE_CHECKING: 49 ↛ 50line 49 didn't jump to line 50 because the condition on line 49 was never true
50 from pyEDAA.Reports.CodeCoverage.UCIS.Instances import InstanceCoverage
53@export
54class CovergroupCoverage(MetricCoverage):
55 """
56 A ``<covergroupCoverage>`` of an instance: its covergroup instances.
57 """
59 _instances: list[CovergroupInstance] #: The covergroup instances.
61 def __init__(
62 self,
63 metricMode: Nullable[str] = None,
64 weight: int = 1,
65 userAttributes: Nullable[Iterable[UserAttribute]] = None,
66 *,
67 parent: Nullable[InstanceCoverage] = None
68 ) -> None:
69 """
70 Initialize the covergroup coverage, and append it to the covergroup coverages of its instance.
72 Its covergroup instances are added by creating them with this coverage as their parent.
74 :param metricMode: Optional, the mode in which the coverage was measured. Default: ``None``.
75 :param weight: Optional, weight of the mode in computing the coverage. Default: ``1``.
76 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
77 :param parent: Optional, the instance the coverage belongs to. Default: ``None``.
78 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
79 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
80 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
81 :raises TypeError: If parameter ``metricMode`` isn't of type :class:`str`.
82 :raises ValueError: If parameter ``weight`` is ``None``.
83 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
84 :raises ValueError: If parameter ``weight`` is negative.
85 :raises TypeError: If parameter ``parent`` isn't of type
86 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Instances.InstanceCoverage`.
87 """
88 super().__init__(metricMode, weight, userAttributes, parent=parent)
90 self._instances = []
92 if parent is not None:
93 parent._covergroupCoverages.append(self)
95 @classmethod
96 def Parse(cls, element: _Element, *, parent: Nullable[InstanceCoverage] = None) -> Self:
97 """
98 Parse a covergroup coverage and its covergroup instances from its ``<covergroupCoverage>`` element.
100 :param element: The ``<covergroupCoverage>`` element.
101 :param parent: Optional, the instance the coverage belongs to. Default: ``None``.
102 :returns: The covergroup coverage.
103 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
104 """
105 covergroupCoverage = cls(
106 element.attrib.get("metricMode"),
107 int(element.attrib.get("weight", "1")),
108 cls._ParseUserAttributes(element),
109 parent=parent
110 )
112 for instanceElement in element.iterfind("{*}cgInstance"):
113 CovergroupInstance.Parse(instanceElement, parent=covergroupCoverage)
115 return covergroupCoverage
117 @readonly
118 def Instances(self) -> list[CovergroupInstance]:
119 """
120 Read-only property to access the covergroup instances (:attr:`_instances`).
122 :returns: The covergroup instances, in the order the report lists them.
123 """
124 return self._instances
127@export
128class CovergroupID(metaclass=ExtendedType, slots=True):
129 """
130 A ``<cgId>`` of a covergroup instance: the covergroup's name, the module or package declaring it, and where the
131 covergroup and the instance are declared.
133 It is a value, as a path is: it names no parent.
134 """
136 _name: str #: Name of the covergroup.
137 _moduleName: str #: Name of the module or package declaring the covergroup.
138 _instanceSourceID: StatementID #: Where the covergroup instance is declared.
139 _sourceID: StatementID #: Where the covergroup is declared.
141 def __init__(self, name: str, moduleName: str, instanceSourceID: StatementID, sourceID: StatementID) -> None:
142 """
143 Initialize the covergroup identifier.
145 :param name: Name of the covergroup, e.g. ``cg``.
146 :param moduleName: Name of the module or package declaring the covergroup.
147 :param instanceSourceID: Where the covergroup instance is declared.
148 :param sourceID: Where the covergroup is declared.
149 :raises ValueError: If parameter ``name`` or ``moduleName`` is ``None``.
150 :raises TypeError: If parameter ``name`` or ``moduleName`` isn't of type :class:`str`.
151 :raises ValueError: If parameter ``instanceSourceID`` or ``sourceID`` is ``None``.
152 :raises TypeError: If parameter ``instanceSourceID`` or ``sourceID`` isn't of type
153 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.StatementID`.
154 """
155 for parameterName, value in (
156 ("name", name),
157 ("moduleName", moduleName)
158 ):
159 if value is None: 159 ↛ 160line 159 didn't jump to line 160 because the condition on line 159 was never true
160 raise ValueError(f"Parameter '{parameterName}' is None.")
161 elif not isinstance(value, str): 161 ↛ 162line 161 didn't jump to line 162 because the condition on line 161 was never true
162 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
163 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
164 raise ex
166 for parameterName, value in (
167 ("instanceSourceID", instanceSourceID),
168 ("sourceID", sourceID)
169 ):
170 if value is None: 170 ↛ 171line 170 didn't jump to line 171 because the condition on line 170 was never true
171 raise ValueError(f"Parameter '{parameterName}' is None.")
172 elif not isinstance(value, StatementID): 172 ↛ 173line 172 didn't jump to line 173 because the condition on line 172 was never true
173 ex = TypeError(f"Parameter '{parameterName}' is not of type 'StatementID'.")
174 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
175 raise ex
177 self._name = name
178 self._moduleName = moduleName
179 self._instanceSourceID = instanceSourceID
180 self._sourceID = sourceID
182 @classmethod
183 def Parse(cls, element: _Element) -> Self:
184 """
185 Parse a covergroup identifier from its ``<cgId>`` element.
187 :param element: The ``<cgId>`` element.
188 :returns: The covergroup identifier.
189 """
190 return cls(
191 element.attrib["cgName"],
192 element.attrib["moduleName"],
193 StatementID.Parse(element.find("{*}cginstSourceId")),
194 StatementID.Parse(element.find("{*}cgSourceId"))
195 )
197 @readonly
198 def Name(self) -> str:
199 """
200 Read-only property to access the name of the covergroup (:attr:`_name`).
202 :returns: The name, as declared.
203 """
204 return self._name
206 @readonly
207 def ModuleName(self) -> str:
208 """
209 Read-only property to access the name of the module or package declaring the covergroup (:attr:`_moduleName`).
211 :returns: The name, as stated.
212 """
213 return self._moduleName
215 @readonly
216 def InstanceSourceID(self) -> StatementID:
217 """
218 Read-only property to access where the covergroup instance is declared (:attr:`_instanceSourceID`).
220 :returns: The source statement identifier.
221 """
222 return self._instanceSourceID
224 @readonly
225 def SourceID(self) -> StatementID:
226 """
227 Read-only property to access where the covergroup is declared (:attr:`_sourceID`).
229 :returns: The source statement identifier.
230 """
231 return self._sourceID
234# A class with a property named like a class - ``CovergroupID`` - can't name that class in the annotation of a field:
235# the class body's namespace, where annotations are evaluated, binds the name to the property.
236_CovergroupID = CovergroupID
239@export
240class CovergroupInstance(Base):
241 """
242 A ``<cgInstance>`` of a covergroup coverage: an instance of a covergroup - or the covergroup as a whole -, its
243 options and parameters, its coverpoints and crosses.
244 """
246 _parent: Nullable[CovergroupCoverage] #: The covergroup coverage the covergroup instance belongs to.
247 _name: str #: Name of the covergroup instance.
248 _key: str #: UCIS key of the covergroup instance.
249 _options: CovergroupOptions #: The options of the covergroup instance.
250 _covergroupID: _CovergroupID #: The covergroup and where it and the instance are declared.
251 _parameters: dict[str, str] #: The parameters of the covergroup instance, by name.
252 _alias: Nullable[str] #: An alias of the covergroup instance's name.
253 _isExcluded: bool #: Whether the covergroup instance is excluded from the coverage.
254 _excludedReason: Nullable[str] #: Why the covergroup instance is excluded.
255 _coverpoints: list[Coverpoint] #: The coverpoints.
256 _crosses: list[Cross] #: The crosses.
258 def __init__(
259 self,
260 name: str,
261 key: str,
262 options: CovergroupOptions,
263 covergroupID: CovergroupID,
264 parameters: Nullable[Mapping[str, str]] = None,
265 alias: Nullable[str] = None,
266 excluded: bool = False,
267 excludedReason: Nullable[str] = None,
268 userAttributes: Nullable[Iterable[UserAttribute]] = None,
269 *,
270 parent: Nullable[CovergroupCoverage] = None
271 ) -> None:
272 """
273 Initialize the covergroup instance, and append it to the covergroup instances of its covergroup coverage.
275 Its coverpoints and crosses are added by creating them with this covergroup instance as their parent.
277 :param name: Name of the covergroup instance, e.g. ``top.cv``.
278 :param key: UCIS key of the covergroup instance.
279 :param options: The options of the covergroup instance.
280 :param covergroupID: The covergroup and where it and the instance are declared.
281 :param parameters: Optional, the parameters of the covergroup instance, by name. Default: ``None``.
282 :param alias: Optional, an alias of the covergroup instance's name. Default: ``None``.
283 :param excluded: Optional, whether the covergroup instance is excluded from the coverage. Default:
284 ``False``.
285 :param excludedReason: Optional, why the covergroup instance is excluded. Default: ``None``.
286 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
287 :param parent: Optional, the covergroup coverage the covergroup instance belongs to. Default: ``None``.
288 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
289 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
290 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
291 :raises ValueError: If parameter ``name`` or ``key`` is ``None``.
292 :raises TypeError: If parameter ``name`` or ``key`` isn't of type :class:`str`.
293 :raises ValueError: If parameter ``options`` is ``None``.
294 :raises TypeError: If parameter ``options`` isn't of type
295 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions.CovergroupOptions`.
296 :raises ValueError: If parameter ``covergroupID`` is ``None``.
297 :raises TypeError: If parameter ``covergroupID`` isn't of type :class:`CovergroupID`.
298 :raises TypeError: If parameter ``alias`` or ``excludedReason`` isn't of type :class:`str`.
299 :raises ValueError: If parameter ``excluded`` is ``None``.
300 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`.
301 :raises TypeError: If parameter ``parent`` isn't of type :class:`CovergroupCoverage`.
302 :raises TypeError: If parameter ``parameters`` isn't a mapping.
303 :raises TypeError: If parameter ``parameters`` contains a name or value not of type :class:`str`.
304 """
305 super().__init__(userAttributes)
307 for parameterName, value in (
308 ("name", name),
309 ("key", key)
310 ):
311 if value is None: 311 ↛ 312line 311 didn't jump to line 312 because the condition on line 311 was never true
312 raise ValueError(f"Parameter '{parameterName}' is None.")
313 elif not isinstance(value, str): 313 ↛ 314line 313 didn't jump to line 314 because the condition on line 313 was never true
314 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
315 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
316 raise ex
318 if options is None: 318 ↛ 319line 318 didn't jump to line 319 because the condition on line 318 was never true
319 raise ValueError("Parameter 'options' is None.")
320 elif not isinstance(options, CovergroupOptions):
321 ex = TypeError("Parameter 'options' is not of type 'CovergroupOptions'.")
322 ex.add_note(f"Got type '{getFullyQualifiedName(options)}'.")
323 raise ex
325 if covergroupID is None: 325 ↛ 326line 325 didn't jump to line 326 because the condition on line 325 was never true
326 raise ValueError("Parameter 'covergroupID' is None.")
327 elif not isinstance(covergroupID, CovergroupID): 327 ↛ 328line 327 didn't jump to line 328 because the condition on line 327 was never true
328 ex = TypeError("Parameter 'covergroupID' is not of type 'CovergroupID'.")
329 ex.add_note(f"Got type '{getFullyQualifiedName(covergroupID)}'.")
330 raise ex
332 for parameterName, value in (
333 ("alias", alias),
334 ("excludedReason", excludedReason)
335 ):
336 if value is not None and not isinstance(value, str): 336 ↛ 337line 336 didn't jump to line 337 because the condition on line 336 was never true
337 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
338 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
339 raise ex
341 if excluded is None: 341 ↛ 342line 341 didn't jump to line 342 because the condition on line 341 was never true
342 raise ValueError("Parameter 'excluded' is None.")
343 elif not isinstance(excluded, bool): 343 ↛ 344line 343 didn't jump to line 344 because the condition on line 343 was never true
344 ex = TypeError("Parameter 'excluded' is not of type 'bool'.")
345 ex.add_note(f"Got type '{getFullyQualifiedName(excluded)}'.")
346 raise ex
348 if parent is not None and not isinstance(parent, CovergroupCoverage): 348 ↛ 349line 348 didn't jump to line 349 because the condition on line 348 was never true
349 ex = TypeError("Parameter 'parent' is not of type 'CovergroupCoverage'.")
350 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
351 raise ex
353 self._parent = parent
354 self._name = name
355 self._key = key
356 self._options = options
357 self._covergroupID = covergroupID
358 self._parameters = {}
359 self._alias = alias
360 self._isExcluded = excluded
361 self._excludedReason = excludedReason
362 self._coverpoints = []
363 self._crosses = []
365 if parameters is not None:
366 if not isinstance(parameters, Mapping): 366 ↛ 367line 366 didn't jump to line 367 because the condition on line 366 was never true
367 ex = TypeError("Parameter 'parameters' is not a mapping.")
368 ex.add_note(f"Got type '{getFullyQualifiedName(parameters)}'.")
369 raise ex
371 for parameterName, value in parameters.items():
372 if not isinstance(parameterName, str) or not isinstance(value, str): 372 ↛ 373line 372 didn't jump to line 373 because the condition on line 372 was never true
373 ex = TypeError("Parameter 'parameters' contains a name or value not of type 'str'.")
374 ex.add_note(f"Got types '{getFullyQualifiedName(parameterName)}' and '{getFullyQualifiedName(value)}'.")
375 raise ex
377 self._parameters[parameterName] = value
379 if parent is not None: 379 ↛ exitline 379 didn't return from function '__init__' because the condition on line 379 was always true
380 parent._instances.append(self)
382 @classmethod
383 def Parse(cls, element: _Element, *, parent: Nullable[CovergroupCoverage] = None) -> Self:
384 """
385 Parse a covergroup instance, its coverpoints and crosses from its ``<cgInstance>`` element.
387 :param element: The ``<cgInstance>`` element.
388 :param parent: Optional, the covergroup coverage the covergroup instance belongs to. Default: ``None``.
389 :returns: The covergroup instance.
390 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
391 """
392 instance = cls(
393 element.attrib["name"],
394 element.attrib["key"],
395 CovergroupOptions.Parse(element.find("{*}options")),
396 CovergroupID.Parse(element.find("{*}cgId")),
397 {
398 parameterElement.findtext("{*}name"): parameterElement.findtext("{*}value")
399 for parameterElement in element.iterfind("{*}cgParms")
400 },
401 element.attrib.get("alias"),
402 cls._ParseBoolean(element.attrib.get("excluded", "false")),
403 element.attrib.get("excludedReason"),
404 cls._ParseUserAttributes(element),
405 parent=parent
406 )
408 for coverpointElement in element.iterfind("{*}coverpoint"):
409 Coverpoint.Parse(coverpointElement, parent=instance)
411 for crossElement in element.iterfind("{*}cross"):
412 Cross.Parse(crossElement, parent=instance)
414 return instance
416 @readonly
417 def Parent(self) -> Nullable[CovergroupCoverage]:
418 """
419 Read-only property to access the covergroup coverage the covergroup instance belongs to (:attr:`_parent`).
421 :returns: The covergroup coverage; ``None``, if the covergroup instance belongs to none.
422 """
423 return self._parent
425 @readonly
426 def Name(self) -> str:
427 """
428 Read-only property to access the name of the covergroup instance (:attr:`_name`).
430 :returns: The name, as stated.
431 """
432 return self._name
434 @readonly
435 def Key(self) -> str:
436 """
437 Read-only property to access the UCIS key of the covergroup instance (:attr:`_key`).
439 :returns: The key, as stated.
440 """
441 return self._key
443 @readonly
444 def Options(self) -> CovergroupOptions:
445 """
446 Read-only property to access the options of the covergroup instance (:attr:`_options`).
448 :returns: The options.
449 """
450 return self._options
452 @readonly
453 def CovergroupID(self) -> CovergroupID:
454 """
455 Read-only property to access the covergroup and where it and the instance are declared (:attr:`_covergroupID`).
457 :returns: The covergroup identifier.
458 """
459 return self._covergroupID
461 @readonly
462 def Parameters(self) -> dict[str, str]:
463 """
464 Read-only property to access the parameters of the covergroup instance (:attr:`_parameters`).
466 :returns: The parameters' values, by name.
467 """
468 return self._parameters
470 @readonly
471 def Alias(self) -> Nullable[str]:
472 """
473 Read-only property to access the alias of the covergroup instance's name (:attr:`_alias`).
475 :returns: The alias; ``None``, if the report states none.
476 """
477 return self._alias
479 @readonly
480 def IsExcluded(self) -> bool:
481 """
482 Read-only property to access whether the covergroup instance is excluded from the coverage (:attr:`_isExcluded`).
484 :returns: ``True``, if excluded.
485 """
486 return self._isExcluded
488 @readonly
489 def ExcludedReason(self) -> Nullable[str]:
490 """
491 Read-only property to access why the covergroup instance is excluded (:attr:`_excludedReason`).
493 :returns: The reason; ``None``, if the report states none.
494 """
495 return self._excludedReason
497 @readonly
498 def Coverpoints(self) -> list[Coverpoint]:
499 """
500 Read-only property to access the coverpoints (:attr:`_coverpoints`).
502 :returns: The coverpoints, in the order the report lists them.
503 """
504 return self._coverpoints
506 @readonly
507 def Crosses(self) -> list[Cross]:
508 """
509 Read-only property to access the crosses (:attr:`_crosses`).
511 :returns: The crosses, in the order the report lists them.
512 """
513 return self._crosses