Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Toggles.py: 77%
188 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 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, \
45 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 ValueError: If parameter ``metricMode`` is ``None``.
75 :raises TypeError: If parameter ``metricMode`` isn't of type :class:`str`.
76 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
77 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
78 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
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 ``metricMode`` isn't of type :class:`str`.
159 :raises ValueError: If parameter ``weight`` is ``None``.
160 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
161 :raises ValueError: If parameter ``weight`` is negative.
162 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
163 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
164 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
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`` is ``None``.
240 :raises TypeError: If parameter ``left`` isn't of type :class:`int`.
241 :raises ValueError: If parameter ``right`` is ``None``.
242 :raises TypeError: If parameter ``right`` isn't of type :class:`int`.
243 :raises ValueError: If parameter ``downTo`` is ``None``.
244 :raises TypeError: If parameter ``downTo`` isn't of type :class:`bool`.
245 """
246 if left is None: 246 ↛ 247line 246 didn't jump to line 247 because the condition on line 246 was never true
247 raise ValueError("Parameter 'left' is None.")
248 elif not isinstance(left, int) or isinstance(left, bool): 248 ↛ 249line 248 didn't jump to line 249 because the condition on line 248 was never true
249 ex = TypeError("Parameter 'left' is not of type 'int'.")
250 ex.add_note(f"Got type '{getFullyQualifiedName(left)}'.")
251 raise ex
253 if right is None: 253 ↛ 254line 253 didn't jump to line 254 because the condition on line 253 was never true
254 raise ValueError("Parameter 'right' is None.")
255 elif not isinstance(right, int) or isinstance(right, bool): 255 ↛ 256line 255 didn't jump to line 256 because the condition on line 255 was never true
256 ex = TypeError("Parameter 'right' is not of type 'int'.")
257 ex.add_note(f"Got type '{getFullyQualifiedName(right)}'.")
258 raise ex
260 if downTo is None: 260 ↛ 261line 260 didn't jump to line 261 because the condition on line 260 was never true
261 raise ValueError("Parameter 'downTo' is None.")
262 elif not isinstance(downTo, bool):
263 ex = TypeError("Parameter 'downTo' is not of type 'bool'.")
264 ex.add_note(f"Got type '{getFullyQualifiedName(downTo)}'.")
265 raise ex
267 self._left = left
268 self._right = right
269 self._downTo = downTo
271 @classmethod
272 def Parse(cls, element: _Element) -> Self:
273 """
274 Parse a dimension from its ``<dimension>`` element.
276 :param element: The ``<dimension>`` element.
277 :returns: The dimension.
278 """
279 return cls(
280 int(element.attrib["left"]),
281 int(element.attrib["right"]),
282 Base._ParseBoolean(element.attrib["downto"])
283 )
285 @readonly
286 def Left(self) -> int:
287 """
288 Read-only property to access the left index (:attr:`_left`).
290 :returns: The left index.
291 """
292 return self._left
294 @readonly
295 def Right(self) -> int:
296 """
297 Read-only property to access the right index (:attr:`_right`).
299 :returns: The right index.
300 """
301 return self._right
303 @readonly
304 def DownTo(self) -> bool:
305 """
306 Read-only property to access whether the index descends from left to right (:attr:`_downTo`).
308 :returns: ``True``, if the index descends.
309 """
310 return self._downTo
313@export
314class ToggleObject(Base, ObjectAttributesMixin):
315 """
316 A ``<toggleObject>`` of a toggle coverage: a signal, its type, dimensions and bits, and where it is declared.
317 """
319 _parent: Nullable[ToggleCoverage] #: The toggle coverage the toggle object belongs to.
320 _name: str #: Name of the signal.
321 _key: str #: UCIS key of the toggle object.
322 _id: StatementID #: Where the signal is declared.
323 _type: Nullable[str] #: Declared type of the signal.
324 _portDirection: Nullable[str] #: Direction of the signal, if it is a port.
325 _dimensions: list[Dimension] #: The dimensions of the signal.
326 _bits: list[ToggleBit] #: The bits of the signal.
328 def __init__(
329 self,
330 name: str,
331 key: str,
332 statementID: StatementID,
333 objectType: Nullable[str] = None,
334 portDirection: Nullable[str] = None,
335 dimensions: Nullable[Iterable[Dimension]] = None,
336 alias: Nullable[str] = None,
337 excluded: bool = False,
338 excludedReason: Nullable[str] = None,
339 weight: int = 1,
340 userAttributes: Nullable[Iterable[UserAttribute]] = None,
341 *,
342 parent: Nullable[ToggleCoverage] = None
343 ) -> None:
344 """
345 Initialize the toggle object, and append it to the toggle objects of its toggle coverage.
347 Its bits are added by creating them with this toggle object as their parent.
349 :param name: Name of the signal, e.g. ``ff1``.
350 :param key: UCIS key of the toggle object.
351 :param statementID: Where the signal is declared.
352 :param objectType: Optional, declared type of the signal, e.g. ``wire``. Default: ``None``.
353 :param portDirection: Optional, direction of the signal, if it is a port, e.g. ``input``. Default: ``None``.
354 :param dimensions: Optional, the dimensions of the signal. Default: ``None``.
355 :param alias: Optional, an alias of the signal's name. Default: ``None``.
356 :param excluded: Optional, whether the signal is excluded from the coverage. Default: ``False``.
357 :param excludedReason: Optional, why the signal is excluded. Default: ``None``.
358 :param weight: Optional, weight of the signal in computing the coverage. Default: ``1``.
359 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
360 :param parent: Optional, the toggle coverage the toggle object belongs to. Default: ``None``.
361 :raises ValueError: If parameter ``name`` is ``None``.
362 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
363 :raises ValueError: If parameter ``key`` is ``None``.
364 :raises TypeError: If parameter ``key`` isn't of type :class:`str`.
365 :raises ValueError: If parameter ``statementID`` is ``None``.
366 :raises TypeError: If parameter ``statementID`` isn't of type
367 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.StatementID`.
368 :raises TypeError: If parameter ``objectType`` isn't of type :class:`str`.
369 :raises TypeError: If parameter ``portDirection`` isn't of type :class:`str`.
370 :raises TypeError: If parameter ``dimensions`` isn't iterable.
371 :raises TypeError: If parameter ``dimensions`` contains an element not of type :class:`Dimension`.
372 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
373 :raises ValueError: If parameter ``excluded`` is ``None``.
374 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`.
375 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`.
376 :raises ValueError: If parameter ``weight`` is ``None``.
377 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
378 :raises ValueError: If parameter ``weight`` is negative.
379 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
380 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
381 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
382 :raises TypeError: If parameter ``parent`` isn't of type :class:`ToggleCoverage`.
383 """
384 super().__init__(userAttributes)
385 ObjectAttributesMixin.__init__(self, alias, excluded, excludedReason, weight)
387 for parameterName, value in (
388 ("name", name),
389 ("key", key)
390 ):
391 if value is None: 391 ↛ 392line 391 didn't jump to line 392 because the condition on line 391 was never true
392 raise ValueError(f"Parameter '{parameterName}' is None.")
393 elif not isinstance(value, str): 393 ↛ 394line 393 didn't jump to line 394 because the condition on line 393 was never true
394 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
395 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
396 raise ex
398 if statementID is None: 398 ↛ 399line 398 didn't jump to line 399 because the condition on line 398 was never true
399 raise ValueError("Parameter 'statementID' is None.")
400 elif not isinstance(statementID, StatementID): 400 ↛ 401line 400 didn't jump to line 401 because the condition on line 400 was never true
401 ex = TypeError("Parameter 'statementID' is not of type 'StatementID'.")
402 ex.add_note(f"Got type '{getFullyQualifiedName(statementID)}'.")
403 raise ex
405 for parameterName, value in (
406 ("objectType", objectType),
407 ("portDirection", portDirection)
408 ):
409 if value is not None and not isinstance(value, str): 409 ↛ 410line 409 didn't jump to line 410 because the condition on line 409 was never true
410 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
411 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
412 raise ex
414 if parent is not None and not isinstance(parent, ToggleCoverage): 414 ↛ 415line 414 didn't jump to line 415 because the condition on line 414 was never true
415 ex = TypeError("Parameter 'parent' is not of type 'ToggleCoverage'.")
416 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
417 raise ex
419 self._parent = parent
420 self._name = name
421 self._key = key
422 self._id = statementID
423 self._type = objectType
424 self._portDirection = portDirection
425 self._dimensions = dimensionList = []
426 self._bits = []
428 if dimensions is not None: 428 ↛ 442line 428 didn't jump to line 442 because the condition on line 428 was always true
429 if not isinstance(dimensions, Iterable): 429 ↛ 430line 429 didn't jump to line 430 because the condition on line 429 was never true
430 ex = TypeError("Parameter 'dimensions' is not iterable.")
431 ex.add_note(f"Got type '{getFullyQualifiedName(dimensions)}'.")
432 raise ex
434 for dimension in dimensions:
435 if not isinstance(dimension, Dimension): 435 ↛ 436line 435 didn't jump to line 436 because the condition on line 435 was never true
436 ex = TypeError("Parameter 'dimensions' contains an element not of type 'Dimension'.")
437 ex.add_note(f"Got type '{getFullyQualifiedName(dimension)}'.")
438 raise ex
440 dimensionList.append(dimension)
442 if parent is not None: 442 ↛ exitline 442 didn't return from function '__init__' because the condition on line 442 was always true
443 parent._objects.append(self)
445 @classmethod
446 def Parse(cls, element: _Element, *, parent: Nullable[ToggleCoverage] = None) -> Self:
447 """
448 Parse a toggle object and its bits from its ``<toggleObject>`` element.
450 :param element: The ``<toggleObject>`` element.
451 :param parent: Optional, the toggle coverage the toggle object belongs to. Default: ``None``.
452 :returns: The toggle object.
453 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
454 """
455 toggleObject = cls(
456 element.attrib["name"],
457 element.attrib["key"],
458 StatementID.Parse(element.find("{*}id")),
459 element.attrib.get("type"),
460 element.attrib.get("portDirection"),
461 [Dimension.Parse(dimensionElement) for dimensionElement in element.iterfind("{*}dimension")],
462 element.attrib.get("alias"),
463 cls._ParseBoolean(element.attrib.get("excluded", "false")),
464 element.attrib.get("excludedReason"),
465 int(element.attrib.get("weight", "1")),
466 cls._ParseUserAttributes(element),
467 parent=parent
468 )
470 for bitElement in element.iterfind("{*}toggleBit"):
471 ToggleBit.Parse(bitElement, parent=toggleObject)
473 return toggleObject
475 @readonly
476 def Parent(self) -> Nullable[ToggleCoverage]:
477 """
478 Read-only property to access the toggle coverage the toggle object belongs to (:attr:`_parent`).
480 :returns: The toggle coverage; ``None``, if the toggle object belongs to none.
481 """
482 return self._parent
484 @readonly
485 def Name(self) -> str:
486 """
487 Read-only property to access the name of the signal (:attr:`_name`).
489 :returns: The name, as declared.
490 """
491 return self._name
493 @readonly
494 def Key(self) -> str:
495 """
496 Read-only property to access the UCIS key of the toggle object (:attr:`_key`).
498 :returns: The key, as stated.
499 """
500 return self._key
502 @readonly
503 def ID(self) -> StatementID:
504 """
505 Read-only property to access where the signal is declared (:attr:`_id`).
507 :returns: The source statement identifier.
508 """
509 return self._id
511 @readonly
512 def Type(self) -> Nullable[str]:
513 """
514 Read-only property to access the declared type of the signal (:attr:`_type`).
516 :returns: The type, e.g. ``wire``; ``None``, if the report states none.
517 """
518 return self._type
520 @readonly
521 def PortDirection(self) -> Nullable[str]:
522 """
523 Read-only property to access the direction of the signal, if it is a port (:attr:`_portDirection`).
525 :returns: The direction, e.g. ``input``; ``None``, if the report states none.
526 """
527 return self._portDirection
529 @readonly
530 def Dimensions(self) -> list[Dimension]:
531 """
532 Read-only property to access the dimensions of the signal (:attr:`_dimensions`).
534 :returns: The dimensions, from the left one.
535 """
536 return self._dimensions
538 @readonly
539 def Bits(self) -> list[ToggleBit]:
540 """
541 Read-only property to access the bits of the signal (:attr:`_bits`).
543 :returns: The bits, in the order the report lists them.
544 """
545 return self._bits