Coverage for pyEDAA/Reports/FunctionalCoverage/__init__.py: 100%

571 statements  

« 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""" 

32Abstract data model of functional coverage: covergroups, their coverpoints and crosses, and the bins counting how often 

33a value - or a combination of values - was sampled. 

34 

35The model is tool-neutral: it follows the terms of SystemVerilog covergroups and of the Accellera UCIS standard, and 

36an OSVVM coverage model maps to it too. 

37 

38* The root, :class:`FunctionalCoverageReport`, holds the covergroups of a report, e.g. of a testcase. 

39* A :class:`Covergroup` holds its coverage items, each by name: :class:`Coverpoint` - one sampled value - and 

40 :class:`Cross` - a combination of values, one per crossed item. 

41* A coverage item holds its bins: a :class:`CoverpointBin` counts the values of its :class:`ValueRange` objects, a 

42 :class:`CrossBin` the combinations of values, one set of ranges per crossed item. A bin's :class:`BinKind` says 

43 whether it counts toward the coverage, is ignored or must not be hit. 

44 

45A bin is covered, when its count reaches its *at least* count. :meth:`FunctionalCoverageReport.Aggregate` computes the 

46counters of every coverage item, covergroup and of the report, and two ratios: 

47 

48* :attr:`~BaseWithCoverage.BinCoverage` - the ratio of covered bins, weighted by the coverage items' and covergroups' 

49 weights, as SystemVerilog and UCIS compute coverage. 

50* :attr:`~BaseWithCoverage.HitCoverage` - the ratio of hits counted toward the bins' *at least* counts, as OSVVM 

51 computes coverage. 

52 

53Every element has key-value pairs (:class:`~pyTooling.DataModel.KeyValuePairsMixin`) for data the model doesn't name, 

54e.g. a tool's settings. 

55 

56A report format's model derives its document from :class:`Document`, which converts it to this model. 

57""" 

58from __future__ import annotations 

59 

60from datetime import timedelta 

61from enum import Enum 

62from pathlib import Path 

63from typing import Any, Callable, ClassVar, Generator, Hashable, Iterable, Mapping, Optional as Nullable 

64 

65from pyTooling.Common import getFullyQualifiedName 

66from pyTooling.DataModel import KeyValuePairsMixin 

67from pyTooling.Decorators import export, readonly 

68from pyTooling.Exceptions import AlreadyInHierarchyError, DuplicateElementError 

69from pyTooling.MetaClasses import ExtendedType, abstractclass, abstractmethod 

70 

71from pyEDAA.Reports import ReportException 

72 

73 

74@export 

75class FunctionalCoverageError(ReportException): 

76 """Base-exception of the functional coverage model.""" 

77 

78 

79@export 

80class AlreadyInFunctionalCoverageReportError(FunctionalCoverageError, AlreadyInHierarchyError): 

81 """The exception is raised when an element is added to an element, but it already has a parent.""" 

82 

83 

84@export 

85class DuplicateCoverpointError(FunctionalCoverageError, DuplicateElementError): 

86 """The exception is raised when a covergroup already contains a coverpoint or a cross of the same name.""" 

87 

88 

89@export 

90class DuplicateCrossError(FunctionalCoverageError, DuplicateElementError): 

91 """The exception is raised when a covergroup already contains a cross or a coverpoint of the same name.""" 

92 

93 

94@export 

95class BinKind(Enum): 

96 """The kind of a bin: whether it counts toward the coverage, is ignored or must not be hit.""" 

97 

98 Count = 0 #: Counts toward the coverage; covered, when its count reaches its *at least* count. 

99 Ignore = 1 #: Excluded from the coverage: its values aren't of interest. 

100 Illegal = 2 #: Excluded from the coverage: its values must not occur, a hit is an error. 

101 Default = 3 #: Excluded from the coverage: counts the values no other bin of the coverage item counts. 

102 

103 

104@export 

105class ValueRange(metaclass=ExtendedType, slots=True): 

106 """ 

107 A range of values - from a lowest to a highest value, both included - a bin counts. 

108 

109 A single value is a range from and to this value. It is a value, as a path is: it names no parent. 

110 """ 

111 

112 _low: int #: The lowest value of the range. 

113 _high: int #: The highest value of the range. 

114 

115 def __init__(self, low: int, high: int) -> None: 

116 """ 

117 Initializes a range of values. 

118 

119 :param low: The lowest value of the range. 

120 :param high: The highest value of the range. 

121 :raises ValueError: If parameter ``low`` is ``None``. 

122 :raises TypeError: If parameter ``low`` isn't of type :class:`int`. 

123 :raises ValueError: If parameter ``high`` is ``None``. 

124 :raises TypeError: If parameter ``high`` isn't of type :class:`int`. 

125 :raises ValueError: If parameter ``high`` is less than parameter ``low``. 

126 """ 

127 for parameterName, value in ( 

128 ("low", low), 

129 ("high", high) 

130 ): 

131 if value is None: 

132 raise ValueError(f"Parameter '{parameterName}' is None.") 

133 elif not isinstance(value, int) or isinstance(value, bool): 

134 ex = TypeError(f"Parameter '{parameterName}' is not of type 'int'.") 

135 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") 

136 raise ex 

137 

138 if high < low: 

139 ex = ValueError("Parameter 'high' is less than parameter 'low'.") 

140 ex.add_note(f"Got range {low} to {high}.") 

141 raise ex 

142 

143 self._low = low 

144 self._high = high 

145 

146 @readonly 

147 def Low(self) -> int: 

148 """ 

149 Read-only property to access the lowest value of the range (:attr:`_low`). 

150 

151 :returns: The lowest value. 

152 """ 

153 return self._low 

154 

155 @readonly 

156 def High(self) -> int: 

157 """ 

158 Read-only property to access the highest value of the range (:attr:`_high`). 

159 

160 :returns: The highest value. 

161 """ 

162 return self._high 

163 

164 def __eq__(self, other: Any) -> bool: 

165 """ 

166 Compare two ranges of values. 

167 

168 :param other: The other range. 

169 :returns: ``True``, if both ranges have the same lowest and highest value. 

170 :raises TypeError: If parameter ``other`` isn't of type :class:`ValueRange`. 

171 """ 

172 if not isinstance(other, ValueRange): 

173 ex = TypeError("Second operand is not supported by == operator.") 

174 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

175 ex.add_note("Supported types for second operand: ValueRange") 

176 raise ex 

177 

178 return self._low == other._low and self._high == other._high 

179 

180 def __hash__(self) -> int: 

181 """ 

182 Return a hash of the range of values, so equal ranges have the same hash. 

183 

184 :returns: The hash of the lowest and highest value. 

185 """ 

186 return hash((self._low, self._high)) 

187 

188 def __repr__(self) -> str: 

189 """ 

190 Return a representation of the range of values for debugging. 

191 

192 A range from 3 to 11 reads ``<ValueRange 3..11>``. 

193 

194 :returns: The lowest and the highest value. 

195 """ 

196 return f"<ValueRange {self._low}..{self._high}>" 

197 

198 

199@export 

200class Base(KeyValuePairsMixin, metaclass=ExtendedType, slots=True): 

201 """ 

202 Base-class of every element of the functional coverage model: key-value pairs, a reference to the element containing 

203 it, and to the report's root. 

204 """ 

205 

206 _PARENT_TYPE: ClassVar[tuple[type, ...]] #: The types a parent may have; assigned by each class having a parent. 

207 

208 _parent: Nullable[Base] #: The element containing this one, or ``None``. 

209 _root: Nullable[FunctionalCoverageReport] #: The report's root, or ``None`` while the element isn't part of one. 

210 

211 def __init__(self, keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, *, parent: Nullable[Base] = None) -> None: 

212 """ 

213 Initializes an element with its key-value pairs, its parent and the parent's root. 

214 

215 The parent doesn't list the element yet: the class setting the fields the parent checks - a name, the crossed 

216 items - adds it with ``parent._AddElement(self)``. 

217 

218 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

219 :param parent: Optional, the element containing this one. Default: ``None``. 

220 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

221 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`. 

222 """ 

223 super().__init__(keyValuePairs) 

224 

225 if parent is None: 

226 self._parent = None 

227 self._root = None 

228 elif not isinstance(parent, self._PARENT_TYPE): 

229 typeNames = " or ".join(f"'{parentType.__name__}'" for parentType in self._PARENT_TYPE) 

230 ex = TypeError(f"Parameter 'parent' is not of type {typeNames}.") 

231 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

232 raise ex 

233 else: 

234 self._parent = parent 

235 self._root = parent._root 

236 

237 @property 

238 def Parent(self) -> Nullable[Base]: 

239 """ 

240 Property to access the element containing this one (:attr:`_parent`). 

241 

242 Assigning a parent adds this element to it, and sets the parent's root as the root of this element and of every 

243 element below it. 

244 

245 :returns: The parent, or ``None``. 

246 :raises ValueError: If ``None`` is assigned. 

247 :raises TypeError: If the assigned parent isn't of a type this class declares in 

248 :attr:`_PARENT_TYPE`. 

249 :raises TypeError: If a parent is assigned to a :class:`FunctionalCoverageReport`, 

250 which has no parent. 

251 :raises AlreadyInFunctionalCoverageReportError: If the element already has a parent. 

252 :raises DuplicateCoverpointError: If the assigned covergroup already contains a coverpoint or a 

253 cross of the coverpoint's name. 

254 :raises DuplicateCrossError: If the assigned covergroup already contains a cross or a 

255 coverpoint of the cross' name. 

256 :raises FunctionalCoverageError: If a cross bin has not one set of ranges per crossed item of the 

257 assigned cross. 

258 """ 

259 return self._parent 

260 

261 @Parent.setter 

262 def Parent(self, parent: Base) -> None: 

263 if parent is None: 

264 raise ValueError("Parameter 'parent' is None.") 

265 elif not isinstance(parent, self._PARENT_TYPE): 

266 typeNames = " or ".join(f"'{parentType.__name__}'" for parentType in self._PARENT_TYPE) 

267 ex = TypeError(f"Parameter 'parent' is not of type {typeNames}.") 

268 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

269 raise ex 

270 elif self._parent is not None: 

271 ex = AlreadyInFunctionalCoverageReportError(f"{self.__class__.__name__} already has a parent.") 

272 ex.add_note(f"Got element '{self!r}'.") 

273 raise ex 

274 

275 parent._AddElement(self) 

276 self._parent = parent 

277 self._SetRoot(parent._root) 

278 

279 @readonly 

280 def Root(self) -> Nullable[FunctionalCoverageReport]: 

281 """ 

282 Read-only property to access the report's root (:attr:`_root`). 

283 

284 The root is maintained by :attr:`Parent`: assigning a parent sets it for this element and every element below it. 

285 

286 :returns: The root, or ``None`` while the element isn't part of a report. 

287 """ 

288 return self._root 

289 

290 def _SetRoot(self, root: Nullable[FunctionalCoverageReport]) -> None: 

291 """ 

292 Set the report's root of this element. 

293 

294 A class with child elements extends it to set the root of its children too. 

295 

296 :param root: The report's root, or ``None`` while the element isn't part of a report. 

297 """ 

298 self._root = root 

299 

300 

301@export 

302class BaseWithName(Base): 

303 """ 

304 Base-class of the named elements: the report, covergroups and coverage items. 

305 """ 

306 

307 _name: str #: Name of the element. 

308 

309 def __init__( 

310 self, 

311 name: str, 

312 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

313 *, 

314 parent: Nullable[Base] = None 

315 ) -> None: 

316 """ 

317 Initializes the key-value pairs, the parent and the name. 

318 

319 :param name: Name of the element. 

320 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

321 :param parent: Optional, the element containing this one. Default: ``None``. 

322 :raises ValueError: If parameter ``name`` is ``None``. 

323 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

324 :raises ValueError: If parameter ``name`` is empty. 

325 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

326 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`. 

327 """ 

328 super().__init__(keyValuePairs, parent=parent) 

329 

330 if name is None: 

331 raise ValueError("Parameter 'name' is None.") 

332 elif not isinstance(name, str): 

333 ex = TypeError("Parameter 'name' is not of type 'str'.") 

334 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

335 raise ex 

336 elif name == "": 

337 raise ValueError("Parameter 'name' is empty.") 

338 

339 self._name = name 

340 

341 @readonly 

342 def Name(self) -> str: 

343 """ 

344 Read-only property to access the name (:attr:`_name`). 

345 

346 :returns: The name. 

347 """ 

348 return self._name 

349 

350 

351@export 

352class BaseWithCoverage(BaseWithName): 

353 """ 

354 Base-class of the elements with coverage counters: the report, covergroups and coverage items. 

355 

356 The counters are zero until :meth:`Aggregate` computed them. A covergroup weights the hit counters of its coverage 

357 items by their weights, the report those of its covergroups; the bin counters are plain sums. 

358 """ 

359 

360 _binCount: int #: Number of bins counting toward the coverage. 

361 _coveredBinCount: int #: Number of bins counting toward the coverage, which reached their *at least* count. 

362 _ignoreBinCount: int #: Number of ignored bins. 

363 _illegalBinCount: int #: Number of illegal bins. 

364 _defaultBinCount: int #: Number of default bins. 

365 _illegalHitCount: int #: Number of hits of illegal bins. 

366 _hitCount: int #: Number of hits counted toward the *at least* counts, each bin's capped at its own. 

367 _requiredHitCount: int #: Number of hits required by the *at least* counts. 

368 _binCoverage: float #: The weighted ratio of covered bins. 

369 

370 def __init__( 

371 self, 

372 name: str, 

373 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

374 *, 

375 parent: Nullable[Base] = None 

376 ) -> None: 

377 """ 

378 Initializes the key-value pairs, the parent, the name and the counters. 

379 

380 :param name: Name of the element. 

381 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

382 :param parent: Optional, the element containing this one. Default: ``None``. 

383 :raises ValueError: If parameter ``name`` is ``None``. 

384 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

385 :raises ValueError: If parameter ``name`` is empty. 

386 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

387 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`. 

388 """ 

389 super().__init__(name, keyValuePairs=keyValuePairs, parent=parent) 

390 

391 self._ResetCounters() 

392 

393 def _ResetCounters(self) -> None: 

394 """ 

395 Set the counters to zero, and the bin coverage to ``1.0``. 

396 """ 

397 self._binCount = 0 

398 self._coveredBinCount = 0 

399 self._ignoreBinCount = 0 

400 self._illegalBinCount = 0 

401 self._defaultBinCount = 0 

402 self._illegalHitCount = 0 

403 self._hitCount = 0 

404 self._requiredHitCount = 0 

405 self._binCoverage = 1.0 

406 

407 def _AggregateCounters(self, others: Iterable[BaseWithGoal]) -> None: 

408 """ 

409 Set the counters to the sums of the counters of covergroups or coverage items, the hit counters weighted by their 

410 weights, and the bin coverage to the mean of theirs, weighted by their weights. 

411 

412 :param others: The aggregated covergroups or coverage items. 

413 """ 

414 self._ResetCounters() 

415 weightedBinCoverage = 0.0 

416 totalWeight = 0 

417 for other in others: 

418 self._binCount += other._binCount 

419 self._coveredBinCount += other._coveredBinCount 

420 self._ignoreBinCount += other._ignoreBinCount 

421 self._illegalBinCount += other._illegalBinCount 

422 self._defaultBinCount += other._defaultBinCount 

423 self._illegalHitCount += other._illegalHitCount 

424 self._hitCount += other._weight * other._hitCount 

425 self._requiredHitCount += other._weight * other._requiredHitCount 

426 weightedBinCoverage += other._weight * other._binCoverage 

427 totalWeight += other._weight 

428 

429 if totalWeight > 0: 

430 self._binCoverage = weightedBinCoverage / totalWeight 

431 

432 @readonly 

433 def BinCount(self) -> int: 

434 """ 

435 Read-only property to access the number of bins counting toward the coverage (:attr:`_binCount`). 

436 

437 :returns: The number of bins of kind :attr:`BinKind.Count`. 

438 """ 

439 return self._binCount 

440 

441 @readonly 

442 def CoveredBinCount(self) -> int: 

443 """ 

444 Read-only property to access the number of bins counting toward the coverage, which reached their *at least* count 

445 (:attr:`_coveredBinCount`). 

446 

447 :returns: The number of covered bins. 

448 """ 

449 return self._coveredBinCount 

450 

451 @readonly 

452 def MissingBinCount(self) -> int: 

453 """ 

454 Read-only property to return the number of bins counting toward the coverage, which missed their *at least* count. 

455 

456 :returns: :attr:`BinCount` minus :attr:`CoveredBinCount`. 

457 """ 

458 return self._binCount - self._coveredBinCount 

459 

460 @readonly 

461 def IgnoreBinCount(self) -> int: 

462 """ 

463 Read-only property to access the number of ignored bins (:attr:`_ignoreBinCount`). 

464 

465 :returns: The number of bins of kind :attr:`BinKind.Ignore`. 

466 """ 

467 return self._ignoreBinCount 

468 

469 @readonly 

470 def IllegalBinCount(self) -> int: 

471 """ 

472 Read-only property to access the number of illegal bins (:attr:`_illegalBinCount`). 

473 

474 :returns: The number of bins of kind :attr:`BinKind.Illegal`. 

475 """ 

476 return self._illegalBinCount 

477 

478 @readonly 

479 def DefaultBinCount(self) -> int: 

480 """ 

481 Read-only property to access the number of default bins (:attr:`_defaultBinCount`). 

482 

483 :returns: The number of bins of kind :attr:`BinKind.Default`. 

484 """ 

485 return self._defaultBinCount 

486 

487 @readonly 

488 def IllegalHitCount(self) -> int: 

489 """ 

490 Read-only property to access the number of hits of illegal bins (:attr:`_illegalHitCount`). 

491 

492 :returns: The sum of the counts of the illegal bins; ``0``, if no illegal value occurred. 

493 """ 

494 return self._illegalHitCount 

495 

496 @readonly 

497 def HitCount(self) -> int: 

498 """ 

499 Read-only property to access the number of hits counted toward the *at least* counts (:attr:`_hitCount`). 

500 

501 A bin's hits count up to its *at least* count, so extra hits of one bin don't make up for missing hits of another. 

502 

503 :returns: The number of hits, weighted by the weights of the coverage items and covergroups. 

504 """ 

505 return self._hitCount 

506 

507 @readonly 

508 def RequiredHitCount(self) -> int: 

509 """ 

510 Read-only property to access the number of hits required by the *at least* counts (:attr:`_requiredHitCount`). 

511 

512 :returns: The sum of the *at least* counts, weighted by the weights of the coverage items and covergroups. 

513 """ 

514 return self._requiredHitCount 

515 

516 @readonly 

517 def BinCoverage(self) -> float: 

518 """ 

519 Read-only property to access the ratio of covered bins (:attr:`_binCoverage`). 

520 

521 It is the coverage as SystemVerilog and UCIS compute it: of a coverage item, its covered bins divided by its bins 

522 counting toward the coverage; of a covergroup, the mean of its coverage items' bin coverage, weighted by their 

523 weights; of the report, the same over the covergroups. 

524 

525 :returns: The bin coverage in range 0.0..1.0; ``1.0`` if there is nothing to cover. 

526 """ 

527 return self._binCoverage 

528 

529 @readonly 

530 def HitCoverage(self) -> float: 

531 """ 

532 Read-only property to return the ratio of hits counted toward the *at least* counts. 

533 

534 It is the coverage as OSVVM computes it. 

535 

536 :returns: :attr:`HitCount` divided by :attr:`RequiredHitCount`, in range 0.0..1.0; ``1.0`` if no hit is 

537 required. 

538 """ 

539 return 1.0 if self._requiredHitCount == 0 else self._hitCount / self._requiredHitCount 

540 

541 @abstractmethod 

542 def Aggregate(self) -> None: 

543 """ 

544 Compute the counters. 

545 """ 

546 

547 

548@export 

549class BaseWithGoal(BaseWithCoverage): 

550 """ 

551 Base-class of the elements with a weight and a goal: covergroups and coverage items. 

552 """ 

553 

554 _weight: int #: Weight in computing the coverage of the element containing this one. 

555 _goal: float #: Coverage goal, in percent. 

556 

557 def __init__( 

558 self, 

559 name: str, 

560 weight: int = 1, 

561 goal: float = 100.0, 

562 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

563 *, 

564 parent: Nullable[Base] = None 

565 ) -> None: 

566 """ 

567 Initializes the key-value pairs, the parent, the name, the counters, the weight and the goal. 

568 

569 :param name: Name of the element. 

570 :param weight: Optional, weight in computing the coverage of the element containing this one; ``0`` 

571 excludes it. Default: ``1``. 

572 :param goal: Optional, coverage goal, in percent. Default: ``100.0``. 

573 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

574 :param parent: Optional, the element containing this one. Default: ``None``. 

575 :raises ValueError: If parameter ``name`` is ``None``. 

576 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

577 :raises ValueError: If parameter ``name`` is empty. 

578 :raises ValueError: If parameter ``weight`` is ``None``. 

579 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`. 

580 :raises ValueError: If parameter ``weight`` is negative. 

581 :raises ValueError: If parameter ``goal`` is ``None``. 

582 :raises TypeError: If parameter ``goal`` isn't of type :class:`float` or :class:`int`. 

583 :raises ValueError: If parameter ``goal`` is negative. 

584 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

585 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`. 

586 """ 

587 super().__init__(name, keyValuePairs=keyValuePairs, parent=parent) 

588 

589 if weight is None: 

590 raise ValueError("Parameter 'weight' is None.") 

591 elif not isinstance(weight, int) or isinstance(weight, bool): 

592 ex = TypeError("Parameter 'weight' is not of type 'int'.") 

593 ex.add_note(f"Got type '{getFullyQualifiedName(weight)}'.") 

594 raise ex 

595 elif weight < 0: 

596 ex = ValueError("Parameter 'weight' is negative.") 

597 ex.add_note(f"Got value '{weight}'.") 

598 raise ex 

599 

600 if goal is None: 

601 raise ValueError("Parameter 'goal' is None.") 

602 elif not isinstance(goal, (float, int)) or isinstance(goal, bool): 

603 ex = TypeError("Parameter 'goal' is not of type 'float' or 'int'.") 

604 ex.add_note(f"Got type '{getFullyQualifiedName(goal)}'.") 

605 raise ex 

606 elif goal < 0: 

607 ex = ValueError("Parameter 'goal' is negative.") 

608 ex.add_note(f"Got value '{goal}'.") 

609 raise ex 

610 

611 self._weight = weight 

612 self._goal = float(goal) 

613 

614 @readonly 

615 def Weight(self) -> int: 

616 """ 

617 Read-only property to access the weight in computing the coverage of the element containing this one 

618 (:attr:`_weight`). 

619 

620 :returns: The weight; ``0``, if the element is excluded from the coverage of the element containing it. 

621 """ 

622 return self._weight 

623 

624 @readonly 

625 def Goal(self) -> float: 

626 """ 

627 Read-only property to access the coverage goal (:attr:`_goal`). 

628 

629 :returns: The goal, in percent. 

630 """ 

631 return self._goal 

632 

633 

634@export 

635class FunctionalCoverageReport(BaseWithCoverage): 

636 """ 

637 The root of a functional coverage report: its covergroups. 

638 

639 It is its own root, and has no parent. A covergroup joins it by naming it as its parent, in the covergroup's 

640 constructor or by :attr:`~Base.Parent`. The names of covergroups needn't be unique: several instances of a covergroup 

641 may share a name. 

642 """ 

643 

644 _covergroups: list[Covergroup] #: The covergroups, in the order they were added. 

645 

646 def __init__(self, name: str, keyValuePairs: Nullable[Mapping[Hashable, Any]] = None) -> None: 

647 """ 

648 Initializes the root of a functional coverage report. 

649 

650 :param name: Name of the report, e.g. of the testcase measured. 

651 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

652 :raises ValueError: If parameter ``name`` is ``None``. 

653 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

654 :raises ValueError: If parameter ``name`` is empty. 

655 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

656 """ 

657 super().__init__(name, keyValuePairs=keyValuePairs) 

658 

659 self._root = self 

660 self._covergroups = [] 

661 

662 @Base.Parent.setter 

663 def Parent(self, parent: None) -> None: 

664 ex = TypeError(f"A '{getFullyQualifiedName(self)}' has no parent.") 

665 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

666 raise ex 

667 

668 def _AddElement(self, covergroup: Covergroup) -> None: 

669 """ 

670 Add a covergroup, which names the report as its parent. 

671 

672 :param covergroup: The covergroup. 

673 """ 

674 self._covergroups.append(covergroup) 

675 

676 @readonly 

677 def Covergroups(self) -> list[Covergroup]: 

678 """ 

679 Read-only property to access the covergroups (:attr:`_covergroups`). 

680 

681 :returns: The covergroups, in the order they were added. 

682 """ 

683 return self._covergroups 

684 

685 @readonly 

686 def CovergroupCount(self) -> int: 

687 """ 

688 Read-only property to return the number of covergroups. 

689 

690 :returns: The number of covergroups. 

691 """ 

692 return len(self._covergroups) 

693 

694 def IterateCovergroups( 

695 self, 

696 predicate: Nullable[Callable[[Covergroup], bool]] = None 

697 ) -> Generator[Covergroup, None, None]: 

698 """ 

699 Iterate all or selected covergroups. 

700 

701 The covergroups are iterated in the order they were added. If parameter ``predicate`` is not None, the given filter 

702 function is used to skip covergroups in the generator. 

703 

704 :param predicate: Optional, filter function accepting any covergroup and returning a boolean. Default: ``None``. 

705 :returns: A generator of the covergroups. 

706 """ 

707 if predicate is None: 

708 yield from self._covergroups 

709 else: 

710 for covergroup in self._covergroups: 

711 if predicate(covergroup): 

712 yield covergroup 

713 

714 def Aggregate(self) -> None: 

715 """ 

716 Aggregate the covergroups, then sum their counters, weighted by their weights. 

717 """ 

718 for covergroup in self._covergroups: 

719 covergroup.Aggregate() 

720 

721 self._AggregateCounters(self._covergroups) 

722 

723 def __repr__(self) -> str: 

724 """ 

725 Return a representation of the report for debugging. 

726 

727 A report ``TbStream_SendGetRandom1`` of 6 covergroups reads 

728 ``<FunctionalCoverageReport TbStream_SendGetRandom1: 6 covergroups, bins 50.0%, hits 47.6%>``. 

729 

730 :returns: The report's name, its number of covergroups and its coverage. 

731 """ 

732 return ( 

733 f"<FunctionalCoverageReport {self._name}: {len(self._covergroups)} covergroups, bins {self.BinCoverage:.1%}, " 

734 f"hits {self.HitCoverage:.1%}>" 

735 ) 

736 

737 

738@export 

739class Covergroup(BaseWithGoal): 

740 """ 

741 A covergroup: its coverage items - coverpoints and crosses -, each by name, its weight in the report's coverage, its 

742 goal, and the testcases, which sampled it. 

743 

744 Its parent is the :class:`FunctionalCoverageReport`. Coverpoints and crosses share one namespace. 

745 """ 

746 

747 _PARENT_TYPE: ClassVar[tuple[type, ...]] = (FunctionalCoverageReport, ) #: A covergroup is in a report. 

748 

749 _testcases: list[str] #: The names of the testcases, which sampled the covergroup. 

750 _coverpoints: dict[str, Coverpoint] #: The coverpoints, by name. 

751 _crosses: dict[str, Cross] #: The crosses, by name. 

752 

753 def __init__( 

754 self, 

755 name: str, 

756 weight: int = 1, 

757 goal: float = 100.0, 

758 testcases: Nullable[Iterable[str]] = None, 

759 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

760 *, 

761 parent: Nullable[FunctionalCoverageReport] = None 

762 ) -> None: 

763 """ 

764 Initializes a covergroup, and adds it to its report. 

765 

766 Its coverpoints and crosses are added by creating them with this covergroup as their parent. 

767 

768 :param name: Name of the covergroup. 

769 :param weight: Optional, weight in computing the report's coverage; ``0`` excludes it. Default: ``1``. 

770 :param goal: Optional, coverage goal, in percent. Default: ``100.0``. 

771 :param testcases: Optional, the names of the testcases, which sampled the covergroup. Default: ``None``. 

772 :param keyValuePairs: Optional, the key-value pairs, e.g. a tool's settings. Default: ``None``. 

773 :param parent: Optional, the report containing the covergroup. Default: ``None``. 

774 :raises ValueError: If parameter ``name`` is ``None``. 

775 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

776 :raises ValueError: If parameter ``name`` is empty. 

777 :raises ValueError: If parameter ``weight`` is ``None``. 

778 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`. 

779 :raises ValueError: If parameter ``weight`` is negative. 

780 :raises ValueError: If parameter ``goal`` is ``None``. 

781 :raises TypeError: If parameter ``goal`` isn't of type :class:`float` or :class:`int`. 

782 :raises ValueError: If parameter ``goal`` is negative. 

783 :raises TypeError: If parameter ``testcases`` isn't iterable. 

784 :raises TypeError: If parameter ``testcases`` contains an element not of type :class:`str`. 

785 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

786 :raises TypeError: If parameter ``parent`` isn't of type :class:`FunctionalCoverageReport`. 

787 """ 

788 super().__init__(name, weight, goal, keyValuePairs=keyValuePairs, parent=parent) 

789 

790 self._testcases = testcaseList = [] 

791 self._coverpoints = {} 

792 self._crosses = {} 

793 

794 if testcases is not None: 

795 if not isinstance(testcases, Iterable): 

796 ex = TypeError("Parameter 'testcases' is not iterable.") 

797 ex.add_note(f"Got type '{getFullyQualifiedName(testcases)}'.") 

798 raise ex 

799 

800 for testcase in testcases: 

801 if not isinstance(testcase, str): 

802 ex = TypeError("Parameter 'testcases' contains an element not of type 'str'.") 

803 ex.add_note(f"Got type '{getFullyQualifiedName(testcase)}'.") 

804 raise ex 

805 

806 testcaseList.append(testcase) 

807 

808 if parent is not None: 

809 parent._AddElement(self) 

810 

811 def _AddElement(self, item: Coverpoint | Cross) -> None: 

812 """ 

813 Add a coverpoint or a cross, which names this covergroup as its parent. 

814 

815 :param item: The coverpoint or cross. 

816 :raises DuplicateCoverpointError: If this covergroup already contains a coverpoint or cross of the coverpoint's 

817 name. 

818 :raises DuplicateCrossError: If this covergroup already contains a cross or coverpoint of the cross' name. 

819 """ 

820 if isinstance(item, Coverpoint): 

821 items = self._coverpoints 

822 exception = DuplicateCoverpointError 

823 else: 

824 items = self._crosses 

825 exception = DuplicateCrossError 

826 

827 if item._name in self._coverpoints or item._name in self._crosses: 

828 raise exception(f"Covergroup '{self._name}' already contains '{item._name}'.") 

829 

830 items[item._name] = item 

831 

832 def _SetRoot(self, root: Nullable[FunctionalCoverageReport]) -> None: 

833 """ 

834 Set the report's root of this covergroup, and of its coverpoints and crosses. 

835 

836 :param root: The report's root, or ``None`` while the covergroup isn't part of a report. 

837 """ 

838 super()._SetRoot(root) 

839 for item in (*self._coverpoints.values(), *self._crosses.values()): 

840 item._SetRoot(root) 

841 

842 @readonly 

843 def Testcases(self) -> list[str]: 

844 """ 

845 Read-only property to access the names of the testcases, which sampled the covergroup (:attr:`_testcases`). 

846 

847 :returns: The names of the testcases; empty, if the report doesn't say. 

848 """ 

849 return self._testcases 

850 

851 @readonly 

852 def Coverpoints(self) -> dict[str, Coverpoint]: 

853 """ 

854 Read-only property to access the coverpoints (:attr:`_coverpoints`). 

855 

856 :returns: The coverpoints, by name. 

857 """ 

858 return self._coverpoints 

859 

860 @readonly 

861 def CoverpointCount(self) -> int: 

862 """ 

863 Read-only property to return the number of coverpoints. 

864 

865 :returns: The number of coverpoints. 

866 """ 

867 return len(self._coverpoints) 

868 

869 @readonly 

870 def Crosses(self) -> dict[str, Cross]: 

871 """ 

872 Read-only property to access the crosses (:attr:`_crosses`). 

873 

874 :returns: The crosses, by name. 

875 """ 

876 return self._crosses 

877 

878 @readonly 

879 def CrossCount(self) -> int: 

880 """ 

881 Read-only property to return the number of crosses. 

882 

883 :returns: The number of crosses. 

884 """ 

885 return len(self._crosses) 

886 

887 def IterateCoverpoints( 

888 self, 

889 predicate: Nullable[Callable[[Coverpoint], bool]] = None 

890 ) -> Generator[Coverpoint, None, None]: 

891 """ 

892 Iterate all or selected coverpoints. 

893 

894 The coverpoints are iterated in the order they were added. If parameter ``predicate`` is not None, the given filter 

895 function is used to skip coverpoints in the generator. 

896 

897 :param predicate: Optional, filter function accepting any coverpoint and returning a boolean. Default: ``None``. 

898 :returns: A generator of the coverpoints. 

899 """ 

900 if predicate is None: 

901 yield from self._coverpoints.values() 

902 else: 

903 for coverpoint in self._coverpoints.values(): 

904 if predicate(coverpoint): 

905 yield coverpoint 

906 

907 def IterateCrosses(self, predicate: Nullable[Callable[[Cross], bool]] = None) -> Generator[Cross, None, None]: 

908 """ 

909 Iterate all or selected crosses. 

910 

911 The crosses are iterated in the order they were added. If parameter ``predicate`` is not None, the given filter 

912 function is used to skip crosses in the generator. 

913 

914 :param predicate: Optional, filter function accepting any cross and returning a boolean. Default: ``None``. 

915 :returns: A generator of the crosses. 

916 """ 

917 if predicate is None: 

918 yield from self._crosses.values() 

919 else: 

920 for cross in self._crosses.values(): 

921 if predicate(cross): 

922 yield cross 

923 

924 def Aggregate(self) -> None: 

925 """ 

926 Aggregate the coverpoints and crosses, then sum their counters, weighted by their weights. 

927 """ 

928 items = (*self._coverpoints.values(), *self._crosses.values()) 

929 for item in items: 

930 item.Aggregate() 

931 

932 self._AggregateCounters(items) 

933 

934 def __repr__(self) -> str: 

935 """ 

936 Return a representation of the covergroup for debugging. 

937 

938 A covergroup ``DelayCov`` of one coverpoint reads ``<Covergroup DelayCov: 1 coverpoints, 0 crosses, bins 0.0%, 

939 hits 22.0%>``. 

940 

941 :returns: The covergroup's name, its number of coverpoints and crosses, and its coverage. 

942 """ 

943 return ( 

944 f"<Covergroup {self._name}: {len(self._coverpoints)} coverpoints, {len(self._crosses)} crosses, " 

945 f"bins {self.BinCoverage:.1%}, hits {self.HitCoverage:.1%}>" 

946 ) 

947 

948 

949@export 

950@abstractclass 

951class CoverageItem(BaseWithGoal): 

952 """ 

953 Base-class of the coverage items of a covergroup - coverpoints and crosses -: their bins, their weight in the 

954 covergroup's coverage and their goal. 

955 

956 Its parent is the :class:`Covergroup` containing it. 

957 """ 

958 

959 _PARENT_TYPE: ClassVar[tuple[type, ...]] = (Covergroup, ) #: A coverage item is in a covergroup. 

960 

961 _bins: list[Bin] #: The bins, in the order they were added. 

962 

963 def __init__( 

964 self, 

965 name: str, 

966 weight: int = 1, 

967 goal: float = 100.0, 

968 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

969 *, 

970 parent: Nullable[Covergroup] = None 

971 ) -> None: 

972 """ 

973 Initializes the key-value pairs, the parent, the name, the counters, the weight, the goal and the bins. 

974 

975 :param name: Name of the coverage item. 

976 :param weight: Optional, weight in computing the covergroup's coverage; ``0`` excludes it. Default: ``1``. 

977 :param goal: Optional, coverage goal, in percent. Default: ``100.0``. 

978 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

979 :param parent: Optional, the covergroup containing the coverage item. Default: ``None``. 

980 :raises ValueError: If parameter ``name`` is ``None``. 

981 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

982 :raises ValueError: If parameter ``name`` is empty. 

983 :raises ValueError: If parameter ``weight`` is ``None``. 

984 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`. 

985 :raises ValueError: If parameter ``weight`` is negative. 

986 :raises ValueError: If parameter ``goal`` is ``None``. 

987 :raises TypeError: If parameter ``goal`` isn't of type :class:`float` or :class:`int`. 

988 :raises ValueError: If parameter ``goal`` is negative. 

989 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

990 :raises TypeError: If parameter ``parent`` isn't of type :class:`Covergroup`. 

991 """ 

992 super().__init__(name, weight, goal, keyValuePairs=keyValuePairs, parent=parent) 

993 

994 self._bins = [] 

995 

996 def _AddElement(self, coverageBin: Bin) -> None: 

997 """ 

998 Add a bin, which names this coverage item as its parent. 

999 

1000 :param coverageBin: The bin. 

1001 """ 

1002 self._bins.append(coverageBin) 

1003 

1004 def _SetRoot(self, root: Nullable[FunctionalCoverageReport]) -> None: 

1005 """ 

1006 Set the report's root of this coverage item, and of its bins. 

1007 

1008 :param root: The report's root, or ``None`` while the coverage item isn't part of a report. 

1009 """ 

1010 super()._SetRoot(root) 

1011 for coverageBin in self._bins: 

1012 coverageBin._SetRoot(root) 

1013 

1014 @readonly 

1015 def Bins(self) -> list[Bin]: 

1016 """ 

1017 Read-only property to access the bins (:attr:`_bins`). 

1018 

1019 :returns: The bins, in the order they were added. 

1020 """ 

1021 return self._bins 

1022 

1023 def IterateBins(self, predicate: Nullable[Callable[[Bin], bool]] = None) -> Generator[Bin, None, None]: 

1024 """ 

1025 Iterate all or selected bins. 

1026 

1027 The bins are iterated in the order they were added. If parameter ``predicate`` is not None, the given filter 

1028 function is used to skip bins in the generator. 

1029 

1030 :param predicate: Optional, filter function accepting any bin and returning a boolean. Default: ``None``. 

1031 :returns: A generator of the bins. 

1032 """ 

1033 if predicate is None: 

1034 yield from self._bins 

1035 else: 

1036 for coverageBin in self._bins: 

1037 if predicate(coverageBin): 

1038 yield coverageBin 

1039 

1040 def Aggregate(self) -> None: 

1041 """ 

1042 Compute the counters and the bin coverage from the bins. 

1043 """ 

1044 self._ResetCounters() 

1045 for coverageBin in self._bins: 

1046 kind = coverageBin._kind 

1047 if kind is BinKind.Count: 

1048 self._binCount += 1 

1049 self._requiredHitCount += coverageBin._atLeast 

1050 if coverageBin._count >= coverageBin._atLeast: 

1051 self._coveredBinCount += 1 

1052 self._hitCount += coverageBin._atLeast 

1053 else: 

1054 self._hitCount += coverageBin._count 

1055 elif kind is BinKind.Ignore: 

1056 self._ignoreBinCount += 1 

1057 elif kind is BinKind.Illegal: 

1058 self._illegalBinCount += 1 

1059 self._illegalHitCount += coverageBin._count 

1060 else: 

1061 self._defaultBinCount += 1 

1062 

1063 if self._binCount > 0: 

1064 self._binCoverage = self._coveredBinCount / self._binCount 

1065 

1066 def __repr__(self) -> str: 

1067 """ 

1068 Return a representation of the coverage item for debugging. 

1069 

1070 A coverpoint ``Length`` with one of two bins covered reads ``<Coverpoint Length: 1/2 bins, hits 60.0%>``. 

1071 

1072 :returns: The coverage item's kind, name, its covered and counting bins, and its hit coverage. 

1073 """ 

1074 return ( 

1075 f"<{self.__class__.__name__} {self._name}: {self._coveredBinCount}/{self._binCount} bins, " 

1076 f"hits {self.HitCoverage:.1%}>" 

1077 ) 

1078 

1079 

1080@export 

1081class Coverpoint(CoverageItem): 

1082 """ 

1083 A coverpoint: the bins counting the values of one sampled expression. 

1084 

1085 Its bins are :class:`CoverpointBin` objects. 

1086 """ 

1087 

1088 _expression: Nullable[str] #: The sampled expression, if the report says. 

1089 

1090 def __init__( 

1091 self, 

1092 name: str, 

1093 expression: Nullable[str] = None, 

1094 weight: int = 1, 

1095 goal: float = 100.0, 

1096 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

1097 *, 

1098 parent: Nullable[Covergroup] = None 

1099 ) -> None: 

1100 """ 

1101 Initializes a coverpoint, and adds it to its covergroup. 

1102 

1103 Its bins are added by creating them with this coverpoint as their parent. 

1104 

1105 :param name: Name of the coverpoint. 

1106 :param expression: Optional, the sampled expression, e.g. a signal's name. Default: ``None``. 

1107 :param weight: Optional, weight in computing the covergroup's coverage; ``0`` excludes it. 

1108 Default: ``1``. 

1109 :param goal: Optional, coverage goal, in percent. Default: ``100.0``. 

1110 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

1111 :param parent: Optional, the covergroup containing the coverpoint. Default: ``None``. 

1112 :raises ValueError: If parameter ``name`` is ``None``. 

1113 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

1114 :raises ValueError: If parameter ``name`` is empty. 

1115 :raises TypeError: If parameter ``expression`` isn't of type :class:`str`. 

1116 :raises ValueError: If parameter ``expression`` is empty. 

1117 :raises ValueError: If parameter ``weight`` is ``None``. 

1118 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`. 

1119 :raises ValueError: If parameter ``weight`` is negative. 

1120 :raises ValueError: If parameter ``goal`` is ``None``. 

1121 :raises TypeError: If parameter ``goal`` isn't of type :class:`float` or :class:`int`. 

1122 :raises ValueError: If parameter ``goal`` is negative. 

1123 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

1124 :raises TypeError: If parameter ``parent`` isn't of type :class:`Covergroup`. 

1125 :raises DuplicateCoverpointError: If the covergroup already contains a coverpoint or cross of this name. 

1126 """ 

1127 super().__init__(name, weight, goal, keyValuePairs=keyValuePairs, parent=parent) 

1128 

1129 if expression is not None: 

1130 if not isinstance(expression, str): 

1131 ex = TypeError("Parameter 'expression' is not of type 'str'.") 

1132 ex.add_note(f"Got type '{getFullyQualifiedName(expression)}'.") 

1133 raise ex 

1134 elif expression == "": 

1135 raise ValueError("Parameter 'expression' is empty.") 

1136 

1137 self._expression = expression 

1138 

1139 if parent is not None: 

1140 parent._AddElement(self) 

1141 

1142 @readonly 

1143 def Expression(self) -> Nullable[str]: 

1144 """ 

1145 Read-only property to access the sampled expression (:attr:`_expression`). 

1146 

1147 :returns: The expression, or ``None`` if the report doesn't say. 

1148 """ 

1149 return self._expression 

1150 

1151 

1152@export 

1153class Cross(CoverageItem): 

1154 """ 

1155 A cross: the bins counting combinations of values, one value per crossed item. 

1156 

1157 Its bins are :class:`CrossBin` objects, each with one set of ranges per crossed item. The crossed items are named: 

1158 the covergroup may contain them as coverpoints, as a SystemVerilog cross crosses coverpoints, or not, as an OSVVM 

1159 coverage model has no coverpoints of its own. 

1160 """ 

1161 

1162 _crossedItems: list[str] #: The names of the crossed items, e.g. of coverpoints, one per dimension. 

1163 

1164 def __init__( 

1165 self, 

1166 name: str, 

1167 crossedItems: Iterable[str], 

1168 weight: int = 1, 

1169 goal: float = 100.0, 

1170 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

1171 *, 

1172 parent: Nullable[Covergroup] = None 

1173 ) -> None: 

1174 """ 

1175 Initializes a cross, and adds it to its covergroup. 

1176 

1177 Its bins are added by creating them with this cross as their parent. 

1178 

1179 :param name: Name of the cross. 

1180 :param crossedItems: The names of the crossed items, e.g. of coverpoints, one per dimension. 

1181 :param weight: Optional, weight in computing the covergroup's coverage; ``0`` excludes it. Default: 

1182 ``1``. 

1183 :param goal: Optional, coverage goal, in percent. Default: ``100.0``. 

1184 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

1185 :param parent: Optional, the covergroup containing the cross. Default: ``None``. 

1186 :raises ValueError: If parameter ``name`` is ``None``. 

1187 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

1188 :raises ValueError: If parameter ``name`` is empty. 

1189 :raises ValueError: If parameter ``crossedItems`` is ``None``. 

1190 :raises TypeError: If parameter ``crossedItems`` isn't iterable. 

1191 :raises TypeError: If parameter ``crossedItems`` contains an element not of type :class:`str`. 

1192 :raises ValueError: If parameter ``crossedItems`` contains an empty name. 

1193 :raises ValueError: If parameter ``crossedItems`` contains less than two names. 

1194 :raises ValueError: If parameter ``weight`` is ``None``. 

1195 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`. 

1196 :raises ValueError: If parameter ``weight`` is negative. 

1197 :raises ValueError: If parameter ``goal`` is ``None``. 

1198 :raises TypeError: If parameter ``goal`` isn't of type :class:`float` or :class:`int`. 

1199 :raises ValueError: If parameter ``goal`` is negative. 

1200 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

1201 :raises TypeError: If parameter ``parent`` isn't of type :class:`Covergroup`. 

1202 :raises DuplicateCrossError: If the covergroup already contains a cross or coverpoint of this name. 

1203 """ 

1204 super().__init__(name, weight, goal, keyValuePairs=keyValuePairs, parent=parent) 

1205 

1206 if crossedItems is None: 

1207 raise ValueError("Parameter 'crossedItems' is None.") 

1208 elif not isinstance(crossedItems, Iterable): 

1209 ex = TypeError("Parameter 'crossedItems' is not iterable.") 

1210 ex.add_note(f"Got type '{getFullyQualifiedName(crossedItems)}'.") 

1211 raise ex 

1212 

1213 self._crossedItems = crossedItemList = [] 

1214 for crossedItem in crossedItems: 

1215 if not isinstance(crossedItem, str): 

1216 ex = TypeError("Parameter 'crossedItems' contains an element not of type 'str'.") 

1217 ex.add_note(f"Got type '{getFullyQualifiedName(crossedItem)}'.") 

1218 raise ex 

1219 elif crossedItem == "": 

1220 raise ValueError("Parameter 'crossedItems' contains an empty name.") 

1221 

1222 crossedItemList.append(crossedItem) 

1223 

1224 if len(crossedItemList) < 2: 

1225 ex = ValueError("Parameter 'crossedItems' contains less than two names.") 

1226 ex.add_note(f"Got {len(crossedItemList)} names.") 

1227 raise ex 

1228 

1229 if parent is not None: 

1230 parent._AddElement(self) 

1231 

1232 def _AddElement(self, coverageBin: CrossBin) -> None: 

1233 """ 

1234 Add a cross bin, which names this cross as its parent. 

1235 

1236 :param coverageBin: The cross bin. 

1237 :raises FunctionalCoverageError: If the cross bin has not one set of ranges per crossed item. 

1238 """ 

1239 if len(coverageBin._ranges) != len(self._crossedItems): 

1240 ex = FunctionalCoverageError(f"Bin has not one set of ranges per crossed item of cross '{self._name}'.") 

1241 ex.add_note(f"Got {len(coverageBin._ranges)} sets of ranges for {len(self._crossedItems)} crossed items.") 

1242 raise ex from ValueError("Parameter 'ranges' doesn't match the crossed items.") 

1243 

1244 self._bins.append(coverageBin) 

1245 

1246 @readonly 

1247 def CrossedItems(self) -> list[str]: 

1248 """ 

1249 Read-only property to access the names of the crossed items (:attr:`_crossedItems`). 

1250 

1251 :returns: The names, one per dimension. 

1252 """ 

1253 return self._crossedItems 

1254 

1255 @readonly 

1256 def Dimensions(self) -> int: 

1257 """ 

1258 Read-only property to return the number of crossed items. 

1259 

1260 :returns: The number of crossed items; at least ``2``. 

1261 """ 

1262 return len(self._crossedItems) 

1263 

1264 

1265@export 

1266@abstractclass 

1267class Bin(Base): 

1268 """ 

1269 Base-class of the bins of a coverage item - coverpoint bins and cross bins -: a name, a kind, how often it was hit, 

1270 and how often it must be hit to be covered. 

1271 """ 

1272 

1273 _name: Nullable[str] #: Name of the bin, if the report says. 

1274 _kind: BinKind #: Kind of the bin. 

1275 _count: int #: How often the bin was hit. 

1276 _atLeast: int #: How often the bin must be hit to be covered. 

1277 

1278 def __init__( 

1279 self, 

1280 name: Nullable[str], 

1281 kind: BinKind, 

1282 count: int, 

1283 atLeast: int = 1, 

1284 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

1285 *, 

1286 parent: Nullable[CoverageItem] = None 

1287 ) -> None: 

1288 """ 

1289 Initializes the key-value pairs, the parent, the name, the kind and the counts. 

1290 

1291 :param name: Name of the bin; ``None``, if the report doesn't say. 

1292 :param kind: Kind of the bin. 

1293 :param count: How often the bin was hit. 

1294 :param atLeast: Optional, how often the bin must be hit to be covered. Default: ``1``. 

1295 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

1296 :param parent: Optional, the coverage item containing the bin. Default: ``None``. 

1297 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

1298 :raises ValueError: If parameter ``name`` is empty. 

1299 :raises ValueError: If parameter ``kind`` is ``None``. 

1300 :raises TypeError: If parameter ``kind`` isn't of type :class:`BinKind`. 

1301 :raises ValueError: If parameter ``count`` is ``None``. 

1302 :raises TypeError: If parameter ``count`` isn't of type :class:`int`. 

1303 :raises ValueError: If parameter ``count`` is negative. 

1304 :raises ValueError: If parameter ``atLeast`` is ``None``. 

1305 :raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`. 

1306 :raises ValueError: If parameter ``atLeast`` is negative. 

1307 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

1308 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`. 

1309 """ 

1310 super().__init__(keyValuePairs, parent=parent) 

1311 

1312 if name is not None: 

1313 if not isinstance(name, str): 

1314 ex = TypeError("Parameter 'name' is not of type 'str'.") 

1315 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

1316 raise ex 

1317 elif name == "": 

1318 raise ValueError("Parameter 'name' is empty.") 

1319 

1320 if kind is None: 

1321 raise ValueError("Parameter 'kind' is None.") 

1322 elif not isinstance(kind, BinKind): 

1323 ex = TypeError("Parameter 'kind' is not of type 'BinKind'.") 

1324 ex.add_note(f"Got type '{getFullyQualifiedName(kind)}'.") 

1325 raise ex 

1326 

1327 for parameterName, value in ( 

1328 ("count", count), 

1329 ("atLeast", atLeast) 

1330 ): 

1331 if value is None: 

1332 raise ValueError(f"Parameter '{parameterName}' is None.") 

1333 elif not isinstance(value, int) or isinstance(value, bool): 

1334 ex = TypeError(f"Parameter '{parameterName}' is not of type 'int'.") 

1335 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") 

1336 raise ex 

1337 elif value < 0: 

1338 ex = ValueError(f"Parameter '{parameterName}' is negative.") 

1339 ex.add_note(f"Got value '{value}'.") 

1340 raise ex 

1341 

1342 self._name = name 

1343 self._kind = kind 

1344 self._count = count 

1345 self._atLeast = atLeast 

1346 

1347 @readonly 

1348 def Name(self) -> Nullable[str]: 

1349 """ 

1350 Read-only property to access the name of the bin (:attr:`_name`). 

1351 

1352 :returns: The name, or ``None`` if the report doesn't say. 

1353 """ 

1354 return self._name 

1355 

1356 @readonly 

1357 def Kind(self) -> BinKind: 

1358 """ 

1359 Read-only property to access the kind of the bin (:attr:`_kind`). 

1360 

1361 :returns: The kind. 

1362 """ 

1363 return self._kind 

1364 

1365 @readonly 

1366 def Count(self) -> int: 

1367 """ 

1368 Read-only property to access how often the bin was hit (:attr:`_count`). 

1369 

1370 :returns: The number of hits. 

1371 """ 

1372 return self._count 

1373 

1374 @readonly 

1375 def AtLeast(self) -> int: 

1376 """ 

1377 Read-only property to access how often the bin must be hit to be covered (:attr:`_atLeast`). 

1378 

1379 :returns: The number of hits required. 

1380 """ 

1381 return self._atLeast 

1382 

1383 @readonly 

1384 def IsCovered(self) -> bool: 

1385 """ 

1386 Read-only property to return whether the bin counts toward the coverage and reached its *at least* count. 

1387 

1388 :returns: ``True``, if the bin is of kind :attr:`BinKind.Count` and :attr:`Count` is at least :attr:`AtLeast`. 

1389 """ 

1390 return self._kind is BinKind.Count and self._count >= self._atLeast 

1391 

1392 def __repr__(self) -> str: 

1393 """ 

1394 Return a representation of the bin for debugging. 

1395 

1396 An unnamed bin hit 19 of 80 times reads ``<CoverpointBin (unnamed) Count: 19/80>``. 

1397 

1398 :returns: The bin's class, name, kind and counts. 

1399 """ 

1400 name = "(unnamed)" if self._name is None else self._name 

1401 return f"<{self.__class__.__name__} {name} {self._kind.name}: {self._count}/{self._atLeast}>" 

1402 

1403 

1404@export 

1405class CoverpointBin(Bin): 

1406 """ 

1407 A bin of a coverpoint: the ranges of values it counts. 

1408 

1409 Its parent is the :class:`Coverpoint` containing it. 

1410 """ 

1411 

1412 _PARENT_TYPE: ClassVar[tuple[type, ...]] = (Coverpoint, ) #: A coverpoint bin is in a coverpoint. 

1413 

1414 _ranges: list[ValueRange] #: The ranges of values the bin counts. 

1415 

1416 def __init__( 

1417 self, 

1418 name: Nullable[str], 

1419 kind: BinKind, 

1420 ranges: Iterable[ValueRange], 

1421 count: int, 

1422 atLeast: int = 1, 

1423 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

1424 *, 

1425 parent: Nullable[Coverpoint] = None 

1426 ) -> None: 

1427 """ 

1428 Initializes a coverpoint bin, and adds it to its coverpoint. 

1429 

1430 :param name: Name of the bin; ``None``, if the report doesn't say. 

1431 :param kind: Kind of the bin. 

1432 :param ranges: The ranges of values the bin counts; empty for a default bin. 

1433 :param count: How often the bin was hit. 

1434 :param atLeast: Optional, how often the bin must be hit to be covered. Default: ``1``. 

1435 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

1436 :param parent: Optional, the coverpoint containing the bin. Default: ``None``. 

1437 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

1438 :raises ValueError: If parameter ``name`` is empty. 

1439 :raises ValueError: If parameter ``kind`` is ``None``. 

1440 :raises TypeError: If parameter ``kind`` isn't of type :class:`BinKind`. 

1441 :raises ValueError: If parameter ``ranges`` is ``None``. 

1442 :raises TypeError: If parameter ``ranges`` isn't iterable. 

1443 :raises TypeError: If parameter ``ranges`` contains an element not of type :class:`ValueRange`. 

1444 :raises ValueError: If parameter ``count`` is ``None``. 

1445 :raises TypeError: If parameter ``count`` isn't of type :class:`int`. 

1446 :raises ValueError: If parameter ``count`` is negative. 

1447 :raises ValueError: If parameter ``atLeast`` is ``None``. 

1448 :raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`. 

1449 :raises ValueError: If parameter ``atLeast`` is negative. 

1450 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

1451 :raises TypeError: If parameter ``parent`` isn't of type :class:`Coverpoint`. 

1452 """ 

1453 super().__init__(name, kind, count, atLeast, keyValuePairs=keyValuePairs, parent=parent) 

1454 

1455 if ranges is None: 

1456 raise ValueError("Parameter 'ranges' is None.") 

1457 elif not isinstance(ranges, Iterable): 

1458 ex = TypeError("Parameter 'ranges' is not iterable.") 

1459 ex.add_note(f"Got type '{getFullyQualifiedName(ranges)}'.") 

1460 raise ex 

1461 

1462 self._ranges = rangeList = [] 

1463 for valueRange in ranges: 

1464 if not isinstance(valueRange, ValueRange): 

1465 ex = TypeError("Parameter 'ranges' contains an element not of type 'ValueRange'.") 

1466 ex.add_note(f"Got type '{getFullyQualifiedName(valueRange)}'.") 

1467 raise ex 

1468 

1469 rangeList.append(valueRange) 

1470 

1471 if parent is not None: 

1472 parent._AddElement(self) 

1473 

1474 @readonly 

1475 def Ranges(self) -> list[ValueRange]: 

1476 """ 

1477 Read-only property to access the ranges of values the bin counts (:attr:`_ranges`). 

1478 

1479 :returns: The ranges; empty for a default bin. 

1480 """ 

1481 return self._ranges 

1482 

1483 

1484@export 

1485class CrossBin(Bin): 

1486 """ 

1487 A bin of a cross: the combinations of values it counts, one set of ranges per crossed item. 

1488 

1489 Its parent is the :class:`Cross` containing it. 

1490 """ 

1491 

1492 _PARENT_TYPE: ClassVar[tuple[type, ...]] = (Cross, ) #: A cross bin is in a cross. 

1493 

1494 _ranges: list[tuple[ValueRange, ...]] #: Per crossed item, the ranges of values the bin counts. 

1495 

1496 def __init__( 

1497 self, 

1498 name: Nullable[str], 

1499 kind: BinKind, 

1500 ranges: Iterable[Iterable[ValueRange]], 

1501 count: int, 

1502 atLeast: int = 1, 

1503 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

1504 *, 

1505 parent: Nullable[Cross] = None 

1506 ) -> None: 

1507 """ 

1508 Initializes a cross bin, and adds it to its cross. 

1509 

1510 :param name: Name of the bin; ``None``, if the report doesn't say. 

1511 :param kind: Kind of the bin. 

1512 :param ranges: Per crossed item, the ranges of values the bin counts. 

1513 :param count: How often the bin was hit. 

1514 :param atLeast: Optional, how often the bin must be hit to be covered. Default: ``1``. 

1515 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

1516 :param parent: Optional, the cross containing the bin. Default: ``None``. 

1517 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

1518 :raises ValueError: If parameter ``name`` is empty. 

1519 :raises ValueError: If parameter ``kind`` is ``None``. 

1520 :raises TypeError: If parameter ``kind`` isn't of type :class:`BinKind`. 

1521 :raises ValueError: If parameter ``ranges`` is ``None``. 

1522 :raises TypeError: If parameter ``ranges`` isn't iterable, or contains an element, which isn't. 

1523 :raises TypeError: If parameter ``ranges`` contains a range not of type :class:`ValueRange`. 

1524 :raises FunctionalCoverageError: If parameter ``ranges`` has not one set of ranges per crossed item of the cross. 

1525 :raises ValueError: If parameter ``count`` is ``None``. 

1526 :raises TypeError: If parameter ``count`` isn't of type :class:`int`. 

1527 :raises ValueError: If parameter ``count`` is negative. 

1528 :raises ValueError: If parameter ``atLeast`` is ``None``. 

1529 :raises TypeError: If parameter ``atLeast`` isn't of type :class:`int`. 

1530 :raises ValueError: If parameter ``atLeast`` is negative. 

1531 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

1532 :raises TypeError: If parameter ``parent`` isn't of type :class:`Cross`. 

1533 """ 

1534 super().__init__(name, kind, count, atLeast, keyValuePairs=keyValuePairs, parent=parent) 

1535 

1536 if ranges is None: 

1537 raise ValueError("Parameter 'ranges' is None.") 

1538 elif not isinstance(ranges, Iterable): 

1539 ex = TypeError("Parameter 'ranges' is not iterable.") 

1540 ex.add_note(f"Got type '{getFullyQualifiedName(ranges)}'.") 

1541 raise ex 

1542 

1543 self._ranges = rangeList = [] 

1544 for itemRanges in ranges: 

1545 if not isinstance(itemRanges, Iterable): 

1546 ex = TypeError("Parameter 'ranges' contains an element, which isn't iterable.") 

1547 ex.add_note(f"Got type '{getFullyQualifiedName(itemRanges)}'.") 

1548 raise ex 

1549 

1550 itemRanges = tuple(itemRanges) 

1551 for valueRange in itemRanges: 

1552 if not isinstance(valueRange, ValueRange): 

1553 ex = TypeError("Parameter 'ranges' contains a range not of type 'ValueRange'.") 

1554 ex.add_note(f"Got type '{getFullyQualifiedName(valueRange)}'.") 

1555 raise ex 

1556 

1557 rangeList.append(itemRanges) 

1558 

1559 if parent is not None: 

1560 parent._AddElement(self) 

1561 

1562 @readonly 

1563 def Ranges(self) -> list[tuple[ValueRange, ...]]: 

1564 """ 

1565 Read-only property to access the ranges of values the bin counts, per crossed item (:attr:`_ranges`). 

1566 

1567 :returns: Per crossed item, in the order of :attr:`Cross.CrossedItems`, the ranges of values. 

1568 """ 

1569 return self._ranges 

1570 

1571 

1572@export 

1573@abstractclass 

1574class Document(metaclass=ExtendedType, slots=True): 

1575 """ 

1576 A functional coverage report file, which is read in two steps: analyzed, then converted. 

1577 

1578 A format's document derives from this class and mixes in the root of the format's model. 

1579 """ 

1580 

1581 _path: Path #: Path to the report file. 

1582 _analysisDuration: Nullable[timedelta] #: Duration of :meth:`Analyze`, or ``None`` before it ran. 

1583 _conversionDuration: Nullable[timedelta] #: Duration of :meth:`Convert`, or ``None`` before it ran. 

1584 

1585 def __init__(self, reportFile: Path) -> None: 

1586 """ 

1587 Initializes the report file. 

1588 

1589 :param reportFile: Path to the report file. 

1590 :raises ValueError: If parameter ``reportFile`` is ``None``. 

1591 :raises TypeError: If parameter ``reportFile`` isn't of type :class:`~pathlib.Path`. 

1592 """ 

1593 if reportFile is None: 

1594 raise ValueError("Parameter 'reportFile' is None.") 

1595 elif not isinstance(reportFile, Path): 

1596 ex = TypeError("Parameter 'reportFile' is not of type 'Path'.") 

1597 ex.add_note(f"Got type '{getFullyQualifiedName(reportFile)}'.") 

1598 raise ex 

1599 

1600 self._path = reportFile 

1601 self._analysisDuration = None 

1602 self._conversionDuration = None 

1603 

1604 @readonly 

1605 def ReportFile(self) -> Path: 

1606 """ 

1607 Read-only property to access the path to the report file (:attr:`_path`). 

1608 

1609 :returns: The report file's path. 

1610 """ 

1611 return self._path 

1612 

1613 @readonly 

1614 def AnalysisDuration(self) -> Nullable[timedelta]: 

1615 """ 

1616 Read-only property to access the duration of :meth:`Analyze`: reading, parsing and validating the file 

1617 (:attr:`_analysisDuration`). 

1618 

1619 :returns: The duration, or ``None`` if the file wasn't analyzed yet. 

1620 """ 

1621 return self._analysisDuration 

1622 

1623 @readonly 

1624 def ModelConversionDuration(self) -> Nullable[timedelta]: 

1625 """ 

1626 Read-only property to access the duration of :meth:`Convert`: building the report format's model 

1627 (:attr:`_conversionDuration`). 

1628 

1629 :returns: The duration, or ``None`` if the content wasn't converted yet. 

1630 """ 

1631 return self._conversionDuration 

1632 

1633 @abstractmethod 

1634 def Analyze(self) -> None: 

1635 """ 

1636 Read, parse and validate the report file. 

1637 """ 

1638 

1639 @abstractmethod 

1640 def Convert(self) -> None: 

1641 """ 

1642 Convert the analyzed content to the report format's model. 

1643 """ 

1644 

1645 @abstractmethod 

1646 def ConvertToFunctionalCoverageReport(self) -> FunctionalCoverageReport: 

1647 """ 

1648 Convert the report format's model to the functional coverage model, and aggregate it. 

1649 

1650 :returns: The report's root of the functional coverage model. 

1651 """