Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Crosses.py: 70%
139 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 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 ValueError: If parameter ``name`` is ``None``.
90 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
91 :raises ValueError: If parameter ``key`` is ``None``.
92 :raises TypeError: If parameter ``key`` isn't of type :class:`str`.
93 :raises ValueError: If parameter ``options`` is ``None``.
94 :raises TypeError: If parameter ``options`` isn't of type
95 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions.CrossOptions`.
96 :raises TypeError: If parameter ``expressions`` isn't iterable.
97 :raises TypeError: If parameter ``expressions`` contains an element not of type :class:`str`.
98 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
99 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
100 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
101 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
102 :raises TypeError: If parameter ``parent`` isn't of type
103 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Covergroups.CovergroupInstance`.
104 """
105 from pyEDAA.Reports.CodeCoverage.UCIS.Covergroups import CovergroupInstance
107 super().__init__(userAttributes)
109 for parameterName, value in (
110 ("name", name),
111 ("key", key)
112 ):
113 if value is None: 113 ↛ 114line 113 didn't jump to line 114 because the condition on line 113 was never true
114 raise ValueError(f"Parameter '{parameterName}' is None.")
115 elif not isinstance(value, str): 115 ↛ 116line 115 didn't jump to line 116 because the condition on line 115 was never true
116 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
117 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
118 raise ex
120 if options is None: 120 ↛ 121line 120 didn't jump to line 121 because the condition on line 120 was never true
121 raise ValueError("Parameter 'options' is None.")
122 elif not isinstance(options, CrossOptions): 122 ↛ 123line 122 didn't jump to line 123 because the condition on line 122 was never true
123 ex = TypeError("Parameter 'options' is not of type 'CrossOptions'.")
124 ex.add_note(f"Got type '{getFullyQualifiedName(options)}'.")
125 raise ex
127 if alias is not None and not isinstance(alias, str): 127 ↛ 128line 127 didn't jump to line 128 because the condition on line 127 was never true
128 ex = TypeError("Parameter 'alias' is not of type 'str'.")
129 ex.add_note(f"Got type '{getFullyQualifiedName(alias)}'.")
130 raise ex
132 if parent is not None and not isinstance(parent, CovergroupInstance): 132 ↛ 133line 132 didn't jump to line 133 because the condition on line 132 was never true
133 ex = TypeError("Parameter 'parent' is not of type 'CovergroupInstance'.")
134 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
135 raise ex
137 self._parent = parent
138 self._name = name
139 self._key = key
140 self._options = options
141 self._alias = alias
142 self._expressions = expressionList = []
143 self._bins = []
145 if expressions is not None: 145 ↛ 159line 145 didn't jump to line 159 because the condition on line 145 was always true
146 if not isinstance(expressions, Iterable): 146 ↛ 147line 146 didn't jump to line 147 because the condition on line 146 was never true
147 ex = TypeError("Parameter 'expressions' is not iterable.")
148 ex.add_note(f"Got type '{getFullyQualifiedName(expressions)}'.")
149 raise ex
151 for expression in expressions:
152 if not isinstance(expression, str): 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true
153 ex = TypeError("Parameter 'expressions' contains an element not of type 'str'.")
154 ex.add_note(f"Got type '{getFullyQualifiedName(expression)}'.")
155 raise ex
157 expressionList.append(expression)
159 if parent is not None: 159 ↛ exitline 159 didn't return from function '__init__' because the condition on line 159 was always true
160 parent._crosses.append(self)
162 @classmethod
163 def Parse(cls, element: _Element, *, parent: Nullable[CovergroupInstance] = None) -> Self:
164 """
165 Parse a cross, its options and bins from its ``<cross>`` element.
167 :param element: The ``<cross>`` element.
168 :param parent: Optional, the covergroup instance the cross belongs to. Default: ``None``.
169 :returns: The cross.
170 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
171 """
172 cross = cls(
173 element.attrib["name"],
174 element.attrib["key"],
175 CrossOptions.Parse(element.find("{*}options")),
176 [(expressionElement.text or "").strip() for expressionElement in element.iterfind("{*}crossExpr")],
177 element.attrib.get("alias"),
178 cls._ParseUserAttributes(element),
179 parent=parent
180 )
182 for binElement in element.iterfind("{*}crossBin"):
183 CrossBin.Parse(binElement, parent=cross)
185 return cross
187 @readonly
188 def Parent(self) -> Nullable[CovergroupInstance]:
189 """
190 Read-only property to access the covergroup instance the cross belongs to (:attr:`_parent`).
192 :returns: The covergroup instance; ``None``, if the cross belongs to none.
193 """
194 return self._parent
196 @readonly
197 def Name(self) -> str:
198 """
199 Read-only property to access the name of the cross (:attr:`_name`).
201 :returns: The name, as declared.
202 """
203 return self._name
205 @readonly
206 def Key(self) -> str:
207 """
208 Read-only property to access the UCIS key of the cross (:attr:`_key`).
210 :returns: The key, as stated.
211 """
212 return self._key
214 @readonly
215 def Options(self) -> CrossOptions:
216 """
217 Read-only property to access the options of the cross (:attr:`_options`).
219 :returns: The options.
220 """
221 return self._options
223 @readonly
224 def Alias(self) -> Nullable[str]:
225 """
226 Read-only property to access the alias of the cross' name (:attr:`_alias`).
228 :returns: The alias; ``None``, if the report states none.
229 """
230 return self._alias
232 @readonly
233 def Expressions(self) -> list[str]:
234 """
235 Read-only property to access the crossed expressions (:attr:`_expressions`).
237 :returns: The expressions, e.g. coverpoints' names, in the order the report lists them.
238 """
239 return self._expressions
241 @readonly
242 def Bins(self) -> list[CrossBin]:
243 """
244 Read-only property to access the bins (:attr:`_bins`).
246 :returns: The bins, in the order the report lists them.
247 """
248 return self._bins
251@export
252class CrossBin(BinBase):
253 """
254 A ``<crossBin>`` of a cross: the indices of the crossed coverpoints' bins it combines, and how often the combination
255 was hit.
256 """
258 _parent: Nullable[Cross] #: The cross the bin belongs to.
259 _indices: list[int] #: The indices of the crossed coverpoints' bins, one per coverpoint.
260 _contents: BinContents #: How often the combination was hit.
262 def __init__(
263 self,
264 indices: Iterable[int],
265 contents: BinContents,
266 binType: str = "default",
267 alias: Nullable[str] = None,
268 name: Nullable[str] = None,
269 key: Nullable[str] = None,
270 userAttributes: Nullable[Iterable[UserAttribute]] = None,
271 *,
272 parent: Nullable[Cross] = None
273 ) -> None:
274 """
275 Initialize the cross bin, and append it to the bins of its cross.
277 :param indices: The indices of the crossed coverpoints' bins, one per coverpoint.
278 :param contents: How often the combination was hit.
279 :param binType: Optional, kind of the bin, e.g. ``default``, ``ignore`` or ``illegal``. Default:
280 ``"default"``.
281 :param alias: Optional, an alias of the bin's name. Default: ``None``.
282 :param name: Optional, name of the bin, as pyucis writes it. Default: ``None``.
283 :param key: Optional, UCIS key of the bin, as pyucis writes it. Default: ``None``.
284 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
285 :param parent: Optional, the cross the bin belongs to. Default: ``None``.
286 :raises ValueError: If parameter ``indices`` is ``None``.
287 :raises TypeError: If parameter ``indices`` isn't iterable.
288 :raises TypeError: If parameter ``indices`` contains an element not of type :class:`int`.
289 :raises ValueError: If parameter ``indices`` is empty.
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 ValueError: If parameter ``binType`` is ``None``.
294 :raises TypeError: If parameter ``binType`` isn't of type :class:`str`.
295 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
296 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
297 :raises TypeError: If parameter ``key`` isn't of type :class:`str`.
298 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
299 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
300 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
301 :raises TypeError: If parameter ``parent`` isn't of type :class:`Cross`.
302 """
303 super().__init__(binType, alias, name, key, userAttributes)
305 if contents is None: 305 ↛ 306line 305 didn't jump to line 306 because the condition on line 305 was never true
306 raise ValueError("Parameter 'contents' is None.")
307 elif not isinstance(contents, BinContents): 307 ↛ 308line 307 didn't jump to line 308 because the condition on line 307 was never true
308 ex = TypeError("Parameter 'contents' is not of type 'BinContents'.")
309 ex.add_note(f"Got type '{getFullyQualifiedName(contents)}'.")
310 raise ex
312 if parent is not None and not isinstance(parent, Cross): 312 ↛ 313line 312 didn't jump to line 313 because the condition on line 312 was never true
313 ex = TypeError("Parameter 'parent' is not of type 'Cross'.")
314 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
315 raise ex
317 self._parent = parent
318 self._indices = indexList = []
319 self._contents = contents
321 if indices is None: 321 ↛ 322line 321 didn't jump to line 322 because the condition on line 321 was never true
322 raise ValueError("Parameter 'indices' is None.")
323 elif not isinstance(indices, Iterable): 323 ↛ 324line 323 didn't jump to line 324 because the condition on line 323 was never true
324 ex = TypeError("Parameter 'indices' is not iterable.")
325 ex.add_note(f"Got type '{getFullyQualifiedName(indices)}'.")
326 raise ex
328 for index in indices:
329 if not isinstance(index, int) or isinstance(index, bool): 329 ↛ 330line 329 didn't jump to line 330 because the condition on line 329 was never true
330 ex = TypeError("Parameter 'indices' contains an element not of type 'int'.")
331 ex.add_note(f"Got type '{getFullyQualifiedName(index)}'.")
332 raise ex
334 indexList.append(index)
336 if len(self._indices) == 0:
337 raise ValueError("Parameter 'indices' is empty.")
339 if parent is not None: 339 ↛ exitline 339 didn't return from function '__init__' because the condition on line 339 was always true
340 parent._bins.append(self)
342 @classmethod
343 def Parse(cls, element: _Element, *, parent: Nullable[Cross] = None) -> Self:
344 """
345 Parse a cross bin from its ``<crossBin>`` element.
347 :param element: The ``<crossBin>`` element.
348 :param parent: Optional, the cross the bin belongs to. Default: ``None``.
349 :returns: The cross bin.
350 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
351 """
352 return cls(
353 [int(indexElement.text) for indexElement in element.iterfind("{*}index")],
354 BinContents.Parse(element.find("{*}contents")),
355 element.attrib.get("type", "default"),
356 element.attrib.get("alias"),
357 element.attrib.get("name"),
358 element.attrib.get("key"),
359 cls._ParseUserAttributes(element),
360 parent=parent
361 )
363 @readonly
364 def Parent(self) -> Nullable[Cross]:
365 """
366 Read-only property to access the cross the bin belongs to (:attr:`_parent`).
368 :returns: The cross; ``None``, if the bin belongs to none.
369 """
370 return self._parent
372 @readonly
373 def Indices(self) -> list[int]:
374 """
375 Read-only property to access the indices of the crossed coverpoints' bins (:attr:`_indices`).
377 :returns: The indices, one per crossed coverpoint.
378 """
379 return self._indices
381 @readonly
382 def Contents(self) -> BinContents:
383 """
384 Read-only property to access how often the combination was hit (:attr:`_contents`).
386 :returns: The contents: the count and the tests.
387 """
388 return self._contents