Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Toggles.py: 77%
189 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 toggle coverage of the UCIS XML interchange format: an instance's ``<toggleCoverage>``, its metric modes, and its
33signals - toggle objects - with their dimensions.
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
42from pyTooling.MetaClasses import ExtendedType
44from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, MetricCoverage, ObjectAttributesMixin, StatementID
45from pyEDAA.Reports.CodeCoverage.UCIS.Elements import UserAttribute
46from pyEDAA.Reports.CodeCoverage.UCIS.ToggleBits import ToggleBit
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.Instances import InstanceCoverage
52@export
53class MetricMode(Base):
54 """
55 A ``<metricMode>`` of a toggle coverage: a mode, in which the coverage was measured, and its user-defined attributes.
56 """
58 _parent: Nullable[ToggleCoverage] #: The toggle coverage the metric mode belongs to.
59 _metricMode: str #: Name of the mode, e.g. ``2STOGGLE``.
61 def __init__(
62 self,
63 metricMode: str,
64 userAttributes: Nullable[Iterable[UserAttribute]] = None,
65 *,
66 parent: Nullable[ToggleCoverage] = None
67 ) -> None:
68 """
69 Initialize the metric mode, and append it to the metric modes of its toggle coverage.
71 :param metricMode: Name of the mode, e.g. ``2STOGGLE``.
72 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
73 :param parent: Optional, the toggle coverage the metric mode belongs to. Default: ``None``.
74 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
75 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
76 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
77 :raises ValueError: If parameter ``metricMode`` is ``None``.
78 :raises TypeError: If parameter ``metricMode`` isn't of type :class:`str`.
79 :raises TypeError: If parameter ``parent`` isn't of type :class:`ToggleCoverage`.
80 """
81 super().__init__(userAttributes)
83 if metricMode is None: 83 ↛ 84line 83 didn't jump to line 84 because the condition on line 83 was never true
84 raise ValueError("Parameter 'metricMode' is None.")
85 elif not isinstance(metricMode, str): 85 ↛ 86line 85 didn't jump to line 86 because the condition on line 85 was never true
86 ex = TypeError("Parameter 'metricMode' is not of type 'str'.")
87 ex.add_note(f"Got type '{getFullyQualifiedName(metricMode)}'.")
88 raise ex
90 if parent is not None and not isinstance(parent, ToggleCoverage): 90 ↛ 91line 90 didn't jump to line 91 because the condition on line 90 was never true
91 ex = TypeError("Parameter 'parent' is not of type 'ToggleCoverage'.")
92 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
93 raise ex
95 self._parent = parent
96 self._metricMode = metricMode
98 if parent is not None: 98 ↛ exitline 98 didn't return from function '__init__' because the condition on line 98 was always true
99 parent._metricModes.append(self)
101 @classmethod
102 def Parse(cls, element: _Element, *, parent: Nullable[ToggleCoverage] = None) -> Self:
103 """
104 Parse a metric mode from its ``<metricMode>`` element.
106 :param element: The ``<metricMode>`` element.
107 :param parent: Optional, the toggle coverage the metric mode belongs to. Default: ``None``.
108 :returns: The metric mode.
109 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
110 """
111 return cls(element.attrib["metricMode"], cls._ParseUserAttributes(element), parent=parent)
113 @readonly
114 def Parent(self) -> Nullable[ToggleCoverage]:
115 """
116 Read-only property to access the toggle coverage the metric mode belongs to (:attr:`_parent`).
118 :returns: The toggle coverage; ``None``, if the metric mode belongs to none.
119 """
120 return self._parent
122 @readonly
123 def MetricMode(self) -> str:
124 """
125 Read-only property to access the name of the mode (:attr:`_metricMode`).
127 :returns: The name, e.g. ``2STOGGLE``.
128 """
129 return self._metricMode
132@export
133class ToggleCoverage(MetricCoverage):
134 """
135 A ``<toggleCoverage>`` of an instance: its toggle objects and its metric modes.
136 """
138 _objects: list[ToggleObject] #: The toggle objects.
139 _metricModes: list[MetricMode] #: The metric modes.
141 def __init__(
142 self,
143 metricMode: Nullable[str] = None,
144 weight: int = 1,
145 userAttributes: Nullable[Iterable[UserAttribute]] = None,
146 *,
147 parent: Nullable[InstanceCoverage] = None
148 ) -> None:
149 """
150 Initialize the toggle coverage, and append it to the toggle coverages of its instance.
152 Its toggle objects and metric modes are added by creating them with this coverage as their parent.
154 :param metricMode: Optional, the mode in which the coverage was measured. Default: ``None``.
155 :param weight: Optional, weight of the mode in computing the coverage. Default: ``1``.
156 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
157 :param parent: Optional, the instance the coverage belongs to. Default: ``None``.
158 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
159 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
160 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
161 :raises TypeError: If parameter ``metricMode`` isn't of type :class:`str`.
162 :raises ValueError: If parameter ``weight`` is ``None``.
163 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
164 :raises ValueError: If parameter ``weight`` is negative.
165 :raises TypeError: If parameter ``parent`` isn't of type
166 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Instances.InstanceCoverage`.
167 """
168 super().__init__(metricMode, weight, userAttributes, parent=parent)
170 self._objects = []
171 self._metricModes = []
173 if parent is not None:
174 parent._toggleCoverages.append(self)
176 @classmethod
177 def Parse(cls, element: _Element, *, parent: Nullable[InstanceCoverage] = None) -> Self:
178 """
179 Parse a toggle coverage, its toggle objects and metric modes from its ``<toggleCoverage>`` element.
181 :param element: The ``<toggleCoverage>`` element.
182 :param parent: Optional, the instance the coverage belongs to. Default: ``None``.
183 :returns: The toggle coverage.
184 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
185 """
186 toggleCoverage = cls(
187 element.attrib.get("metricMode"),
188 int(element.attrib.get("weight", "1")),
189 cls._ParseUserAttributes(element),
190 parent=parent
191 )
193 for objectElement in element.iterfind("{*}toggleObject"):
194 ToggleObject.Parse(objectElement, parent=toggleCoverage)
196 for modeElement in element.iterfind("{*}metricMode"):
197 MetricMode.Parse(modeElement, parent=toggleCoverage)
199 return toggleCoverage
201 @readonly
202 def Objects(self) -> list[ToggleObject]:
203 """
204 Read-only property to access the toggle objects (:attr:`_objects`).
206 :returns: The toggle objects, in the order the report lists them.
207 """
208 return self._objects
210 @readonly
211 def MetricModes(self) -> list[MetricMode]:
212 """
213 Read-only property to access the metric modes (:attr:`_metricModes`).
215 :returns: The metric modes, in the order the report lists them.
216 """
217 return self._metricModes
220@export
221class Dimension(metaclass=ExtendedType, slots=True):
222 """
223 A ``<dimension>`` of a toggle object: its left and right index, and whether the index descends.
225 It is a value, as a path is: it names no parent.
226 """
228 _left: int #: The left index.
229 _right: int #: The right index.
230 _downTo: bool #: Whether the index descends from left to right.
232 def __init__(self, left: int, right: int, downTo: bool) -> None:
233 """
234 Initialize the dimension.
236 :param left: The left index.
237 :param right: The right index.
238 :param downTo: Whether the index descends from left to right, e.g. for ``[3:0]``.
239 :raises ValueError: If parameter ``left`` or ``right`` is ``None``.
240 :raises TypeError: If parameter ``left`` or ``right`` isn't of type :class:`int`.
241 :raises ValueError: If parameter ``downTo`` is ``None``.
242 :raises TypeError: If parameter ``downTo`` isn't of type :class:`bool`.
243 """
244 if left is None: 244 ↛ 245line 244 didn't jump to line 245 because the condition on line 244 was never true
245 raise ValueError("Parameter 'left' is None.")
246 elif not isinstance(left, int) or isinstance(left, bool): 246 ↛ 247line 246 didn't jump to line 247 because the condition on line 246 was never true
247 ex = TypeError("Parameter 'left' is not of type 'int'.")
248 ex.add_note(f"Got type '{getFullyQualifiedName(left)}'.")
249 raise ex
251 if right is None: 251 ↛ 252line 251 didn't jump to line 252 because the condition on line 251 was never true
252 raise ValueError("Parameter 'right' is None.")
253 elif not isinstance(right, int) or isinstance(right, bool): 253 ↛ 254line 253 didn't jump to line 254 because the condition on line 253 was never true
254 ex = TypeError("Parameter 'right' is not of type 'int'.")
255 ex.add_note(f"Got type '{getFullyQualifiedName(right)}'.")
256 raise ex
258 if downTo is None: 258 ↛ 259line 258 didn't jump to line 259 because the condition on line 258 was never true
259 raise ValueError("Parameter 'downTo' is None.")
260 elif not isinstance(downTo, bool):
261 ex = TypeError("Parameter 'downTo' is not of type 'bool'.")
262 ex.add_note(f"Got type '{getFullyQualifiedName(downTo)}'.")
263 raise ex
265 self._left = left
266 self._right = right
267 self._downTo = downTo
269 @classmethod
270 def Parse(cls, element: _Element) -> Self:
271 """
272 Parse a dimension from its ``<dimension>`` element.
274 :param element: The ``<dimension>`` element.
275 :returns: The dimension.
276 """
277 return cls(
278 int(element.attrib["left"]),
279 int(element.attrib["right"]),
280 Base._ParseBoolean(element.attrib["downto"])
281 )
283 @readonly
284 def Left(self) -> int:
285 """
286 Read-only property to access the left index (:attr:`_left`).
288 :returns: The left index.
289 """
290 return self._left
292 @readonly
293 def Right(self) -> int:
294 """
295 Read-only property to access the right index (:attr:`_right`).
297 :returns: The right index.
298 """
299 return self._right
301 @readonly
302 def DownTo(self) -> bool:
303 """
304 Read-only property to access whether the index descends from left to right (:attr:`_downTo`).
306 :returns: ``True``, if the index descends.
307 """
308 return self._downTo
311@export
312class ToggleObject(Base, ObjectAttributesMixin):
313 """
314 A ``<toggleObject>`` of a toggle coverage: a signal, its type, dimensions and bits, and where it is declared.
315 """
317 _parent: Nullable[ToggleCoverage] #: The toggle coverage the toggle object belongs to.
318 _name: str #: Name of the signal.
319 _key: str #: UCIS key of the toggle object.
320 _id: StatementID #: Where the signal is declared.
321 _type: Nullable[str] #: Declared type of the signal.
322 _portDirection: Nullable[str] #: Direction of the signal, if it is a port.
323 _dimensions: list[Dimension] #: The dimensions of the signal.
324 _bits: list[ToggleBit] #: The bits of the signal.
326 def __init__(
327 self,
328 name: str,
329 key: str,
330 statementID: StatementID,
331 objectType: Nullable[str] = None,
332 portDirection: Nullable[str] = None,
333 dimensions: Nullable[Iterable[Dimension]] = None,
334 alias: Nullable[str] = None,
335 excluded: bool = False,
336 excludedReason: Nullable[str] = None,
337 weight: int = 1,
338 userAttributes: Nullable[Iterable[UserAttribute]] = None,
339 *,
340 parent: Nullable[ToggleCoverage] = None
341 ) -> None:
342 """
343 Initialize the toggle object, and append it to the toggle objects of its toggle coverage.
345 Its bits are added by creating them with this toggle object as their parent.
347 :param name: Name of the signal, e.g. ``ff1``.
348 :param key: UCIS key of the toggle object.
349 :param statementID: Where the signal is declared.
350 :param objectType: Optional, declared type of the signal, e.g. ``wire``. Default: ``None``.
351 :param portDirection: Optional, direction of the signal, if it is a port, e.g. ``input``. Default: ``None``.
352 :param dimensions: Optional, the dimensions of the signal. Default: ``None``.
353 :param alias: Optional, an alias of the signal's name. Default: ``None``.
354 :param excluded: Optional, whether the signal is excluded from the coverage. Default: ``False``.
355 :param excludedReason: Optional, why the signal is excluded. Default: ``None``.
356 :param weight: Optional, weight of the signal in computing the coverage. Default: ``1``.
357 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
358 :param parent: Optional, the toggle coverage the toggle object belongs to. Default: ``None``.
359 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
360 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
361 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
362 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
363 :raises ValueError: If parameter ``excluded`` is ``None``.
364 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`.
365 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`.
366 :raises ValueError: If parameter ``weight`` is ``None``.
367 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
368 :raises ValueError: If parameter ``weight`` is negative.
369 :raises ValueError: If parameter ``name`` or ``key`` is ``None``.
370 :raises TypeError: If parameter ``name`` or ``key`` isn't of type :class:`str`.
371 :raises ValueError: If parameter ``statementID`` is ``None``.
372 :raises TypeError: If parameter ``statementID`` isn't of type
373 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.StatementID`.
374 :raises TypeError: If parameter ``objectType`` or ``portDirection`` isn't of type :class:`str`.
375 :raises TypeError: If parameter ``parent`` isn't of type :class:`ToggleCoverage`.
376 :raises TypeError: If parameter ``dimensions`` isn't iterable.
377 :raises TypeError: If parameter ``dimensions`` contains an element not of type :class:`Dimension`.
378 """
379 super().__init__(userAttributes)
380 ObjectAttributesMixin.__init__(self, alias, excluded, excludedReason, weight)
382 for parameterName, value in (
383 ("name", name),
384 ("key", key)
385 ):
386 if value is None: 386 ↛ 387line 386 didn't jump to line 387 because the condition on line 386 was never true
387 raise ValueError(f"Parameter '{parameterName}' is None.")
388 elif not isinstance(value, str): 388 ↛ 389line 388 didn't jump to line 389 because the condition on line 388 was never true
389 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
390 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
391 raise ex
393 if statementID is None: 393 ↛ 394line 393 didn't jump to line 394 because the condition on line 393 was never true
394 raise ValueError("Parameter 'statementID' is None.")
395 elif not isinstance(statementID, StatementID): 395 ↛ 396line 395 didn't jump to line 396 because the condition on line 395 was never true
396 ex = TypeError("Parameter 'statementID' is not of type 'StatementID'.")
397 ex.add_note(f"Got type '{getFullyQualifiedName(statementID)}'.")
398 raise ex
400 for parameterName, value in (
401 ("objectType", objectType),
402 ("portDirection", portDirection)
403 ):
404 if value is not None and not isinstance(value, str): 404 ↛ 405line 404 didn't jump to line 405 because the condition on line 404 was never true
405 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
406 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
407 raise ex
409 if parent is not None and not isinstance(parent, ToggleCoverage): 409 ↛ 410line 409 didn't jump to line 410 because the condition on line 409 was never true
410 ex = TypeError("Parameter 'parent' is not of type 'ToggleCoverage'.")
411 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
412 raise ex
414 self._parent = parent
415 self._name = name
416 self._key = key
417 self._id = statementID
418 self._type = objectType
419 self._portDirection = portDirection
420 self._dimensions = []
421 self._bits = []
423 if dimensions is not None: 423 ↛ 437line 423 didn't jump to line 437 because the condition on line 423 was always true
424 if not isinstance(dimensions, Iterable): 424 ↛ 425line 424 didn't jump to line 425 because the condition on line 424 was never true
425 ex = TypeError("Parameter 'dimensions' is not iterable.")
426 ex.add_note(f"Got type '{getFullyQualifiedName(dimensions)}'.")
427 raise ex
429 for dimension in dimensions:
430 if not isinstance(dimension, Dimension): 430 ↛ 431line 430 didn't jump to line 431 because the condition on line 430 was never true
431 ex = TypeError("Parameter 'dimensions' contains an element not of type 'Dimension'.")
432 ex.add_note(f"Got type '{getFullyQualifiedName(dimension)}'.")
433 raise ex
435 self._dimensions.append(dimension)
437 if parent is not None: 437 ↛ exitline 437 didn't return from function '__init__' because the condition on line 437 was always true
438 parent._objects.append(self)
440 @classmethod
441 def Parse(cls, element: _Element, *, parent: Nullable[ToggleCoverage] = None) -> Self:
442 """
443 Parse a toggle object and its bits from its ``<toggleObject>`` element.
445 :param element: The ``<toggleObject>`` element.
446 :param parent: Optional, the toggle coverage the toggle object belongs to. Default: ``None``.
447 :returns: The toggle object.
448 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
449 """
450 toggleObject = cls(
451 element.attrib["name"],
452 element.attrib["key"],
453 StatementID.Parse(element.find("{*}id")),
454 element.attrib.get("type"),
455 element.attrib.get("portDirection"),
456 [Dimension.Parse(dimensionElement) for dimensionElement in element.iterfind("{*}dimension")],
457 element.attrib.get("alias"),
458 cls._ParseBoolean(element.attrib.get("excluded", "false")),
459 element.attrib.get("excludedReason"),
460 int(element.attrib.get("weight", "1")),
461 cls._ParseUserAttributes(element),
462 parent=parent
463 )
465 for bitElement in element.iterfind("{*}toggleBit"):
466 ToggleBit.Parse(bitElement, parent=toggleObject)
468 return toggleObject
470 @readonly
471 def Parent(self) -> Nullable[ToggleCoverage]:
472 """
473 Read-only property to access the toggle coverage the toggle object belongs to (:attr:`_parent`).
475 :returns: The toggle coverage; ``None``, if the toggle object belongs to none.
476 """
477 return self._parent
479 @readonly
480 def Name(self) -> str:
481 """
482 Read-only property to access the name of the signal (:attr:`_name`).
484 :returns: The name, as declared.
485 """
486 return self._name
488 @readonly
489 def Key(self) -> str:
490 """
491 Read-only property to access the UCIS key of the toggle object (:attr:`_key`).
493 :returns: The key, as stated.
494 """
495 return self._key
497 @readonly
498 def ID(self) -> StatementID:
499 """
500 Read-only property to access where the signal is declared (:attr:`_id`).
502 :returns: The source statement identifier.
503 """
504 return self._id
506 @readonly
507 def Type(self) -> Nullable[str]:
508 """
509 Read-only property to access the declared type of the signal (:attr:`_type`).
511 :returns: The type, e.g. ``wire``; ``None``, if the report states none.
512 """
513 return self._type
515 @readonly
516 def PortDirection(self) -> Nullable[str]:
517 """
518 Read-only property to access the direction of the signal, if it is a port (:attr:`_portDirection`).
520 :returns: The direction, e.g. ``input``; ``None``, if the report states none.
521 """
522 return self._portDirection
524 @readonly
525 def Dimensions(self) -> list[Dimension]:
526 """
527 Read-only property to access the dimensions of the signal (:attr:`_dimensions`).
529 :returns: The dimensions, from the left one.
530 """
531 return self._dimensions
533 @readonly
534 def Bits(self) -> list[ToggleBit]:
535 """
536 Read-only property to access the bits of the signal (:attr:`_bits`).
538 :returns: The bits, in the order the report lists them.
539 """
540 return self._bits