Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Covergroups.py: 78%
184 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 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 ``metricMode`` isn't of type :class:`str`.
79 :raises ValueError: If parameter ``weight`` is ``None``.
80 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
81 :raises ValueError: If parameter ``weight`` is negative.
82 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
83 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
84 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
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`` is ``None``.
150 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
151 :raises ValueError: If parameter ``moduleName`` is ``None``.
152 :raises TypeError: If parameter ``moduleName`` isn't of type :class:`str`.
153 :raises ValueError: If parameter ``instanceSourceID`` is ``None``.
154 :raises TypeError: If parameter ``instanceSourceID`` isn't of type
155 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.StatementID`.
156 :raises ValueError: If parameter ``sourceID`` is ``None``.
157 :raises TypeError: If parameter ``sourceID`` isn't of type
158 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.StatementID`.
159 """
160 for parameterName, value in (
161 ("name", name),
162 ("moduleName", moduleName)
163 ):
164 if value is None: 164 ↛ 165line 164 didn't jump to line 165 because the condition on line 164 was never true
165 raise ValueError(f"Parameter '{parameterName}' is None.")
166 elif not isinstance(value, str): 166 ↛ 167line 166 didn't jump to line 167 because the condition on line 166 was never true
167 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
168 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
169 raise ex
171 for parameterName, value in (
172 ("instanceSourceID", instanceSourceID),
173 ("sourceID", sourceID)
174 ):
175 if value is None: 175 ↛ 176line 175 didn't jump to line 176 because the condition on line 175 was never true
176 raise ValueError(f"Parameter '{parameterName}' is None.")
177 elif not isinstance(value, StatementID): 177 ↛ 178line 177 didn't jump to line 178 because the condition on line 177 was never true
178 ex = TypeError(f"Parameter '{parameterName}' is not of type 'StatementID'.")
179 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
180 raise ex
182 self._name = name
183 self._moduleName = moduleName
184 self._instanceSourceID = instanceSourceID
185 self._sourceID = sourceID
187 @classmethod
188 def Parse(cls, element: _Element) -> Self:
189 """
190 Parse a covergroup identifier from its ``<cgId>`` element.
192 :param element: The ``<cgId>`` element.
193 :returns: The covergroup identifier.
194 """
195 return cls(
196 element.attrib["cgName"],
197 element.attrib["moduleName"],
198 StatementID.Parse(element.find("{*}cginstSourceId")),
199 StatementID.Parse(element.find("{*}cgSourceId"))
200 )
202 @readonly
203 def Name(self) -> str:
204 """
205 Read-only property to access the name of the covergroup (:attr:`_name`).
207 :returns: The name, as declared.
208 """
209 return self._name
211 @readonly
212 def ModuleName(self) -> str:
213 """
214 Read-only property to access the name of the module or package declaring the covergroup (:attr:`_moduleName`).
216 :returns: The name, as stated.
217 """
218 return self._moduleName
220 @readonly
221 def InstanceSourceID(self) -> StatementID:
222 """
223 Read-only property to access where the covergroup instance is declared (:attr:`_instanceSourceID`).
225 :returns: The source statement identifier.
226 """
227 return self._instanceSourceID
229 @readonly
230 def SourceID(self) -> StatementID:
231 """
232 Read-only property to access where the covergroup is declared (:attr:`_sourceID`).
234 :returns: The source statement identifier.
235 """
236 return self._sourceID
239# A class with a property named like a class - ``CovergroupID`` - can't name that class in the annotation of a field:
240# the class body's namespace, where annotations are evaluated, binds the name to the property.
241_CovergroupID = CovergroupID
244@export
245class CovergroupInstance(Base):
246 """
247 A ``<cgInstance>`` of a covergroup coverage: an instance of a covergroup - or the covergroup as a whole -, its
248 options and parameters, its coverpoints and crosses.
249 """
251 _parent: Nullable[CovergroupCoverage] #: The covergroup coverage the covergroup instance belongs to.
252 _name: str #: Name of the covergroup instance.
253 _key: str #: UCIS key of the covergroup instance.
254 _options: CovergroupOptions #: The options of the covergroup instance.
255 _covergroupID: _CovergroupID #: The covergroup and where it and the instance are declared.
256 _parameters: dict[str, str] #: The parameters of the covergroup instance, by name.
257 _alias: Nullable[str] #: An alias of the covergroup instance's name.
258 _isExcluded: bool #: Whether the covergroup instance is excluded from the coverage.
259 _excludedReason: Nullable[str] #: Why the covergroup instance is excluded.
260 _coverpoints: list[Coverpoint] #: The coverpoints.
261 _crosses: list[Cross] #: The crosses.
263 def __init__(
264 self,
265 name: str,
266 key: str,
267 options: CovergroupOptions,
268 covergroupID: CovergroupID,
269 parameters: Nullable[Mapping[str, str]] = None,
270 alias: Nullable[str] = None,
271 excluded: bool = False,
272 excludedReason: Nullable[str] = None,
273 userAttributes: Nullable[Iterable[UserAttribute]] = None,
274 *,
275 parent: Nullable[CovergroupCoverage] = None
276 ) -> None:
277 """
278 Initialize the covergroup instance, and append it to the covergroup instances of its covergroup coverage.
280 Its coverpoints and crosses are added by creating them with this covergroup instance as their parent.
282 :param name: Name of the covergroup instance, e.g. ``top.cv``.
283 :param key: UCIS key of the covergroup instance.
284 :param options: The options of the covergroup instance.
285 :param covergroupID: The covergroup and where it and the instance are declared.
286 :param parameters: Optional, the parameters of the covergroup instance, by name. Default: ``None``.
287 :param alias: Optional, an alias of the covergroup instance's name. Default: ``None``.
288 :param excluded: Optional, whether the covergroup instance is excluded from the coverage. Default:
289 ``False``.
290 :param excludedReason: Optional, why the covergroup instance is excluded. Default: ``None``.
291 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
292 :param parent: Optional, the covergroup coverage the covergroup instance belongs to. Default: ``None``.
293 :raises ValueError: If parameter ``name`` is ``None``.
294 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
295 :raises ValueError: If parameter ``key`` is ``None``.
296 :raises TypeError: If parameter ``key`` isn't of type :class:`str`.
297 :raises ValueError: If parameter ``options`` is ``None``.
298 :raises TypeError: If parameter ``options`` isn't of type
299 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions.CovergroupOptions`.
300 :raises ValueError: If parameter ``covergroupID`` is ``None``.
301 :raises TypeError: If parameter ``covergroupID`` isn't of type :class:`CovergroupID`.
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 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
305 :raises ValueError: If parameter ``excluded`` is ``None``.
306 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`.
307 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`.
308 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
309 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
310 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
311 :raises TypeError: If parameter ``parent`` isn't of type :class:`CovergroupCoverage`.
312 """
313 super().__init__(userAttributes)
315 for parameterName, value in (
316 ("name", name),
317 ("key", key)
318 ):
319 if value is None: 319 ↛ 320line 319 didn't jump to line 320 because the condition on line 319 was never true
320 raise ValueError(f"Parameter '{parameterName}' is None.")
321 elif not isinstance(value, str): 321 ↛ 322line 321 didn't jump to line 322 because the condition on line 321 was never true
322 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
323 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
324 raise ex
326 if options is None: 326 ↛ 327line 326 didn't jump to line 327 because the condition on line 326 was never true
327 raise ValueError("Parameter 'options' is None.")
328 elif not isinstance(options, CovergroupOptions):
329 ex = TypeError("Parameter 'options' is not of type 'CovergroupOptions'.")
330 ex.add_note(f"Got type '{getFullyQualifiedName(options)}'.")
331 raise ex
333 if covergroupID is None: 333 ↛ 334line 333 didn't jump to line 334 because the condition on line 333 was never true
334 raise ValueError("Parameter 'covergroupID' is None.")
335 elif not isinstance(covergroupID, CovergroupID): 335 ↛ 336line 335 didn't jump to line 336 because the condition on line 335 was never true
336 ex = TypeError("Parameter 'covergroupID' is not of type 'CovergroupID'.")
337 ex.add_note(f"Got type '{getFullyQualifiedName(covergroupID)}'.")
338 raise ex
340 for parameterName, value in (
341 ("alias", alias),
342 ("excludedReason", excludedReason)
343 ):
344 if value is not None and not isinstance(value, str): 344 ↛ 345line 344 didn't jump to line 345 because the condition on line 344 was never true
345 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
346 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
347 raise ex
349 if excluded is None: 349 ↛ 350line 349 didn't jump to line 350 because the condition on line 349 was never true
350 raise ValueError("Parameter 'excluded' is None.")
351 elif not isinstance(excluded, bool): 351 ↛ 352line 351 didn't jump to line 352 because the condition on line 351 was never true
352 ex = TypeError("Parameter 'excluded' is not of type 'bool'.")
353 ex.add_note(f"Got type '{getFullyQualifiedName(excluded)}'.")
354 raise ex
356 if parent is not None and not isinstance(parent, CovergroupCoverage): 356 ↛ 357line 356 didn't jump to line 357 because the condition on line 356 was never true
357 ex = TypeError("Parameter 'parent' is not of type 'CovergroupCoverage'.")
358 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
359 raise ex
361 self._parent = parent
362 self._name = name
363 self._key = key
364 self._options = options
365 self._covergroupID = covergroupID
366 self._parameters = {}
367 self._alias = alias
368 self._isExcluded = excluded
369 self._excludedReason = excludedReason
370 self._coverpoints = []
371 self._crosses = []
373 if parameters is not None:
374 if not isinstance(parameters, Mapping): 374 ↛ 375line 374 didn't jump to line 375 because the condition on line 374 was never true
375 ex = TypeError("Parameter 'parameters' is not a mapping.")
376 ex.add_note(f"Got type '{getFullyQualifiedName(parameters)}'.")
377 raise ex
379 for parameterName, value in parameters.items():
380 if not isinstance(parameterName, str) or not isinstance(value, str): 380 ↛ 381line 380 didn't jump to line 381 because the condition on line 380 was never true
381 ex = TypeError("Parameter 'parameters' contains a name or value not of type 'str'.")
382 ex.add_note(f"Got types '{getFullyQualifiedName(parameterName)}' and '{getFullyQualifiedName(value)}'.")
383 raise ex
385 self._parameters[parameterName] = value
387 if parent is not None: 387 ↛ exitline 387 didn't return from function '__init__' because the condition on line 387 was always true
388 parent._instances.append(self)
390 @classmethod
391 def Parse(cls, element: _Element, *, parent: Nullable[CovergroupCoverage] = None) -> Self:
392 """
393 Parse a covergroup instance, its coverpoints and crosses from its ``<cgInstance>`` element.
395 :param element: The ``<cgInstance>`` element.
396 :param parent: Optional, the covergroup coverage the covergroup instance belongs to. Default: ``None``.
397 :returns: The covergroup instance.
398 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
399 """
400 instance = cls(
401 element.attrib["name"],
402 element.attrib["key"],
403 CovergroupOptions.Parse(element.find("{*}options")),
404 CovergroupID.Parse(element.find("{*}cgId")),
405 {
406 parameterElement.findtext("{*}name"): parameterElement.findtext("{*}value")
407 for parameterElement in element.iterfind("{*}cgParms")
408 },
409 element.attrib.get("alias"),
410 cls._ParseBoolean(element.attrib.get("excluded", "false")),
411 element.attrib.get("excludedReason"),
412 cls._ParseUserAttributes(element),
413 parent=parent
414 )
416 for coverpointElement in element.iterfind("{*}coverpoint"):
417 Coverpoint.Parse(coverpointElement, parent=instance)
419 for crossElement in element.iterfind("{*}cross"):
420 Cross.Parse(crossElement, parent=instance)
422 return instance
424 @readonly
425 def Parent(self) -> Nullable[CovergroupCoverage]:
426 """
427 Read-only property to access the covergroup coverage the covergroup instance belongs to (:attr:`_parent`).
429 :returns: The covergroup coverage; ``None``, if the covergroup instance belongs to none.
430 """
431 return self._parent
433 @readonly
434 def Name(self) -> str:
435 """
436 Read-only property to access the name of the covergroup instance (:attr:`_name`).
438 :returns: The name, as stated.
439 """
440 return self._name
442 @readonly
443 def Key(self) -> str:
444 """
445 Read-only property to access the UCIS key of the covergroup instance (:attr:`_key`).
447 :returns: The key, as stated.
448 """
449 return self._key
451 @readonly
452 def Options(self) -> CovergroupOptions:
453 """
454 Read-only property to access the options of the covergroup instance (:attr:`_options`).
456 :returns: The options.
457 """
458 return self._options
460 @readonly
461 def CovergroupID(self) -> CovergroupID:
462 """
463 Read-only property to access the covergroup and where it and the instance are declared (:attr:`_covergroupID`).
465 :returns: The covergroup identifier.
466 """
467 return self._covergroupID
469 @readonly
470 def Parameters(self) -> dict[str, str]:
471 """
472 Read-only property to access the parameters of the covergroup instance (:attr:`_parameters`).
474 :returns: The parameters' values, by name.
475 """
476 return self._parameters
478 @readonly
479 def Alias(self) -> Nullable[str]:
480 """
481 Read-only property to access the alias of the covergroup instance's name (:attr:`_alias`).
483 :returns: The alias; ``None``, if the report states none.
484 """
485 return self._alias
487 @readonly
488 def IsExcluded(self) -> bool:
489 """
490 Read-only property to access whether the covergroup instance is excluded from the coverage (:attr:`_isExcluded`).
492 :returns: ``True``, if excluded.
493 """
494 return self._isExcluded
496 @readonly
497 def ExcludedReason(self) -> Nullable[str]:
498 """
499 Read-only property to access why the covergroup instance is excluded (:attr:`_excludedReason`).
501 :returns: The reason; ``None``, if the report states none.
502 """
503 return self._excludedReason
505 @readonly
506 def Coverpoints(self) -> list[Coverpoint]:
507 """
508 Read-only property to access the coverpoints (:attr:`_coverpoints`).
510 :returns: The coverpoints, in the order the report lists them.
511 """
512 return self._coverpoints
514 @readonly
515 def Crosses(self) -> list[Cross]:
516 """
517 Read-only property to access the crosses (:attr:`_crosses`).
519 :returns: The crosses, in the order the report lists them.
520 """
521 return self._crosses