Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Crosses.py: 70%
139 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 crosses of the covergroup coverage of the UCIS XML interchange format: their options and bins, each a combination
33of the crossed coverpoints' bins.
34"""
35from __future__ import annotations
37from typing import TYPE_CHECKING, Iterable, Optional as Nullable, Self
39from lxml.etree import _Element
40from pyTooling.Common import getFullyQualifiedName
41from pyTooling.Decorators import export, readonly
43from pyEDAA.Reports.CodeCoverage.UCIS.Bins import BinContents
44from pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions import CrossOptions
45from pyEDAA.Reports.CodeCoverage.UCIS.Coverpoints import BinBase
46from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, UserAttribute
48if TYPE_CHECKING: 48 ↛ 49line 48 didn't jump to line 49 because the condition on line 48 was never true
49 from pyEDAA.Reports.CodeCoverage.UCIS.Covergroups import CovergroupInstance
52@export
53class Cross(Base):
54 """
55 A ``<cross>`` of a covergroup instance: the expressions - coverpoints - it crosses, its options and bins.
56 """
58 _parent: Nullable[CovergroupInstance] #: The covergroup instance the cross belongs to.
59 _name: str #: Name of the cross.
60 _key: str #: UCIS key of the cross.
61 _options: CrossOptions #: The options of the cross.
62 _alias: Nullable[str] #: An alias of the cross' name.
63 _expressions: list[str] #: The crossed expressions, e.g. coverpoints' names.
64 _bins: list[CrossBin] #: The bins.
66 def __init__(
67 self,
68 name: str,
69 key: str,
70 options: CrossOptions,
71 expressions: Nullable[Iterable[str]] = None,
72 alias: Nullable[str] = None,
73 userAttributes: Nullable[Iterable[UserAttribute]] = None,
74 *,
75 parent: Nullable[CovergroupInstance] = None
76 ) -> None:
77 """
78 Initialize the cross, and append it to the crosses of its covergroup instance.
80 Its bins are added by creating them with this cross as their parent.
82 :param name: Name of the cross, e.g. ``xab``.
83 :param key: UCIS key of the cross.
84 :param options: The options of the cross.
85 :param expressions: Optional, the crossed expressions, e.g. ``["cvpt_a", "cvpt_b"]``. Default: ``None``.
86 :param alias: Optional, an alias of the cross' name. Default: ``None``.
87 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
88 :param parent: Optional, the covergroup instance the cross belongs to. Default: ``None``.
89 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
90 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
91 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
92 :raises ValueError: If parameter ``name`` or ``key`` is ``None``.
93 :raises TypeError: If parameter ``name`` or ``key`` isn't of type :class:`str`.
94 :raises ValueError: If parameter ``options`` is ``None``.
95 :raises TypeError: If parameter ``options`` isn't of type
96 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions.CrossOptions`.
97 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
98 :raises TypeError: If parameter ``parent`` isn't of type
99 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Covergroups.CovergroupInstance`.
100 :raises TypeError: If parameter ``expressions`` isn't iterable.
101 :raises TypeError: If parameter ``expressions`` contains an element not of type :class:`str`.
102 """
103 from pyEDAA.Reports.CodeCoverage.UCIS.Covergroups import CovergroupInstance
105 super().__init__(userAttributes)
107 for parameterName, value in (
108 ("name", name),
109 ("key", key)
110 ):
111 if value is None: 111 ↛ 112line 111 didn't jump to line 112 because the condition on line 111 was never true
112 raise ValueError(f"Parameter '{parameterName}' is None.")
113 elif not isinstance(value, str): 113 ↛ 114line 113 didn't jump to line 114 because the condition on line 113 was never true
114 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
115 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
116 raise ex
118 if options is None: 118 ↛ 119line 118 didn't jump to line 119 because the condition on line 118 was never true
119 raise ValueError("Parameter 'options' is None.")
120 elif not isinstance(options, CrossOptions): 120 ↛ 121line 120 didn't jump to line 121 because the condition on line 120 was never true
121 ex = TypeError("Parameter 'options' is not of type 'CrossOptions'.")
122 ex.add_note(f"Got type '{getFullyQualifiedName(options)}'.")
123 raise ex
125 if alias is not None and not isinstance(alias, str): 125 ↛ 126line 125 didn't jump to line 126 because the condition on line 125 was never true
126 ex = TypeError("Parameter 'alias' is not of type 'str'.")
127 ex.add_note(f"Got type '{getFullyQualifiedName(alias)}'.")
128 raise ex
130 if parent is not None and not isinstance(parent, CovergroupInstance): 130 ↛ 131line 130 didn't jump to line 131 because the condition on line 130 was never true
131 ex = TypeError("Parameter 'parent' is not of type 'CovergroupInstance'.")
132 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
133 raise ex
135 self._parent = parent
136 self._name = name
137 self._key = key
138 self._options = options
139 self._alias = alias
140 self._expressions = []
141 self._bins = []
143 if expressions is not None: 143 ↛ 157line 143 didn't jump to line 157 because the condition on line 143 was always true
144 if not isinstance(expressions, Iterable): 144 ↛ 145line 144 didn't jump to line 145 because the condition on line 144 was never true
145 ex = TypeError("Parameter 'expressions' is not iterable.")
146 ex.add_note(f"Got type '{getFullyQualifiedName(expressions)}'.")
147 raise ex
149 for expression in expressions:
150 if not isinstance(expression, str): 150 ↛ 151line 150 didn't jump to line 151 because the condition on line 150 was never true
151 ex = TypeError("Parameter 'expressions' contains an element not of type 'str'.")
152 ex.add_note(f"Got type '{getFullyQualifiedName(expression)}'.")
153 raise ex
155 self._expressions.append(expression)
157 if parent is not None: 157 ↛ exitline 157 didn't return from function '__init__' because the condition on line 157 was always true
158 parent._crosses.append(self)
160 @classmethod
161 def Parse(cls, element: _Element, *, parent: Nullable[CovergroupInstance] = None) -> Self:
162 """
163 Parse a cross, its options and bins from its ``<cross>`` element.
165 :param element: The ``<cross>`` element.
166 :param parent: Optional, the covergroup instance the cross belongs to. Default: ``None``.
167 :returns: The cross.
168 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
169 """
170 cross = cls(
171 element.attrib["name"],
172 element.attrib["key"],
173 CrossOptions.Parse(element.find("{*}options")),
174 [(expressionElement.text or "").strip() for expressionElement in element.iterfind("{*}crossExpr")],
175 element.attrib.get("alias"),
176 cls._ParseUserAttributes(element),
177 parent=parent
178 )
180 for binElement in element.iterfind("{*}crossBin"):
181 CrossBin.Parse(binElement, parent=cross)
183 return cross
185 @readonly
186 def Parent(self) -> Nullable[CovergroupInstance]:
187 """
188 Read-only property to access the covergroup instance the cross belongs to (:attr:`_parent`).
190 :returns: The covergroup instance; ``None``, if the cross belongs to none.
191 """
192 return self._parent
194 @readonly
195 def Name(self) -> str:
196 """
197 Read-only property to access the name of the cross (:attr:`_name`).
199 :returns: The name, as declared.
200 """
201 return self._name
203 @readonly
204 def Key(self) -> str:
205 """
206 Read-only property to access the UCIS key of the cross (:attr:`_key`).
208 :returns: The key, as stated.
209 """
210 return self._key
212 @readonly
213 def Options(self) -> CrossOptions:
214 """
215 Read-only property to access the options of the cross (:attr:`_options`).
217 :returns: The options.
218 """
219 return self._options
221 @readonly
222 def Alias(self) -> Nullable[str]:
223 """
224 Read-only property to access the alias of the cross' name (:attr:`_alias`).
226 :returns: The alias; ``None``, if the report states none.
227 """
228 return self._alias
230 @readonly
231 def Expressions(self) -> list[str]:
232 """
233 Read-only property to access the crossed expressions (:attr:`_expressions`).
235 :returns: The expressions, e.g. coverpoints' names, in the order the report lists them.
236 """
237 return self._expressions
239 @readonly
240 def Bins(self) -> list[CrossBin]:
241 """
242 Read-only property to access the bins (:attr:`_bins`).
244 :returns: The bins, in the order the report lists them.
245 """
246 return self._bins
249@export
250class CrossBin(BinBase):
251 """
252 A ``<crossBin>`` of a cross: the indices of the crossed coverpoints' bins it combines, and how often the combination
253 was hit.
254 """
256 _parent: Nullable[Cross] #: The cross the bin belongs to.
257 _indices: list[int] #: The indices of the crossed coverpoints' bins, one per coverpoint.
258 _contents: BinContents #: How often the combination was hit.
260 def __init__(
261 self,
262 indices: Iterable[int],
263 contents: BinContents,
264 binType: str = "default",
265 alias: Nullable[str] = None,
266 name: Nullable[str] = None,
267 key: Nullable[str] = None,
268 userAttributes: Nullable[Iterable[UserAttribute]] = None,
269 *,
270 parent: Nullable[Cross] = None
271 ) -> None:
272 """
273 Initialize the cross bin, and append it to the bins of its cross.
275 :param indices: The indices of the crossed coverpoints' bins, one per coverpoint.
276 :param contents: How often the combination was hit.
277 :param binType: Optional, kind of the bin, e.g. ``default``, ``ignore`` or ``illegal``. Default:
278 ``"default"``.
279 :param alias: Optional, an alias of the bin's name. Default: ``None``.
280 :param name: Optional, name of the bin, as pyucis writes it. Default: ``None``.
281 :param key: Optional, UCIS key of the bin, as pyucis writes it. Default: ``None``.
282 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
283 :param parent: Optional, the cross the bin belongs to. Default: ``None``.
284 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
285 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
286 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
287 :raises ValueError: If parameter ``binType`` is ``None``.
288 :raises TypeError: If parameter ``binType`` isn't of type :class:`str`.
289 :raises TypeError: If parameter ``alias``, ``name`` or ``key`` isn't of type :class:`str`.
290 :raises ValueError: If parameter ``contents`` is ``None``.
291 :raises TypeError: If parameter ``contents`` isn't of type
292 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.BinContents`.
293 :raises TypeError: If parameter ``parent`` isn't of type :class:`Cross`.
294 :raises ValueError: If parameter ``indices`` is ``None``.
295 :raises TypeError: If parameter ``indices`` isn't iterable.
296 :raises TypeError: If parameter ``indices`` contains an element not of type :class:`int`.
297 :raises ValueError: If parameter ``indices`` is empty.
298 """
299 super().__init__(binType, alias, name, key, userAttributes)
301 if contents is None: 301 ↛ 302line 301 didn't jump to line 302 because the condition on line 301 was never true
302 raise ValueError("Parameter 'contents' is None.")
303 elif not isinstance(contents, BinContents): 303 ↛ 304line 303 didn't jump to line 304 because the condition on line 303 was never true
304 ex = TypeError("Parameter 'contents' is not of type 'BinContents'.")
305 ex.add_note(f"Got type '{getFullyQualifiedName(contents)}'.")
306 raise ex
308 if parent is not None and not isinstance(parent, Cross): 308 ↛ 309line 308 didn't jump to line 309 because the condition on line 308 was never true
309 ex = TypeError("Parameter 'parent' is not of type 'Cross'.")
310 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
311 raise ex
313 self._parent = parent
314 self._indices = []
315 self._contents = contents
317 if indices is None: 317 ↛ 318line 317 didn't jump to line 318 because the condition on line 317 was never true
318 raise ValueError("Parameter 'indices' is None.")
319 elif not isinstance(indices, Iterable): 319 ↛ 320line 319 didn't jump to line 320 because the condition on line 319 was never true
320 ex = TypeError("Parameter 'indices' is not iterable.")
321 ex.add_note(f"Got type '{getFullyQualifiedName(indices)}'.")
322 raise ex
324 for index in indices:
325 if not isinstance(index, int) or isinstance(index, bool): 325 ↛ 326line 325 didn't jump to line 326 because the condition on line 325 was never true
326 ex = TypeError("Parameter 'indices' contains an element not of type 'int'.")
327 ex.add_note(f"Got type '{getFullyQualifiedName(index)}'.")
328 raise ex
330 self._indices.append(index)
332 if len(self._indices) == 0:
333 raise ValueError("Parameter 'indices' is empty.")
335 if parent is not None: 335 ↛ exitline 335 didn't return from function '__init__' because the condition on line 335 was always true
336 parent._bins.append(self)
338 @classmethod
339 def Parse(cls, element: _Element, *, parent: Nullable[Cross] = None) -> Self:
340 """
341 Parse a cross bin from its ``<crossBin>`` element.
343 :param element: The ``<crossBin>`` element.
344 :param parent: Optional, the cross the bin belongs to. Default: ``None``.
345 :returns: The cross bin.
346 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
347 """
348 return cls(
349 [int(indexElement.text) for indexElement in element.iterfind("{*}index")],
350 BinContents.Parse(element.find("{*}contents")),
351 element.attrib.get("type", "default"),
352 element.attrib.get("alias"),
353 element.attrib.get("name"),
354 element.attrib.get("key"),
355 cls._ParseUserAttributes(element),
356 parent=parent
357 )
359 @readonly
360 def Parent(self) -> Nullable[Cross]:
361 """
362 Read-only property to access the cross the bin belongs to (:attr:`_parent`).
364 :returns: The cross; ``None``, if the bin belongs to none.
365 """
366 return self._parent
368 @readonly
369 def Indices(self) -> list[int]:
370 """
371 Read-only property to access the indices of the crossed coverpoints' bins (:attr:`_indices`).
373 :returns: The indices, one per crossed coverpoint.
374 """
375 return self._indices
377 @readonly
378 def Contents(self) -> BinContents:
379 """
380 Read-only property to access how often the combination was hit (:attr:`_contents`).
382 :returns: The contents: the count and the tests.
383 """
384 return self._contents