Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Covergroups.py: 78%

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

32The covergroup coverage of the UCIS XML interchange format: an instance's ``<covergroupCoverage>``, its covergroup 

33instances with their coverpoints and crosses. 

34""" 

35from __future__ import annotations 

36 

37from typing import TYPE_CHECKING, Iterable, Mapping, Optional as Nullable, Self 

38 

39from lxml.etree import _Element 

40from pyTooling.Common import getFullyQualifiedName 

41from pyTooling.Decorators import export, readonly 

42from pyTooling.MetaClasses import ExtendedType 

43 

44from pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions import CovergroupOptions 

45from pyEDAA.Reports.CodeCoverage.UCIS.Coverpoints import Coverpoint 

46from pyEDAA.Reports.CodeCoverage.UCIS.Crosses import Cross 

47from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, MetricCoverage, StatementID, UserAttribute 

48 

49if TYPE_CHECKING: 49 ↛ 50line 49 didn't jump to line 50 because the condition on line 49 was never true

50 from pyEDAA.Reports.CodeCoverage.UCIS.Instances import InstanceCoverage 

51 

52 

53@export 

54class CovergroupCoverage(MetricCoverage): 

55 """ 

56 A ``<covergroupCoverage>`` of an instance: its covergroup instances. 

57 """ 

58 

59 _instances: list[CovergroupInstance] #: The covergroup instances. 

60 

61 def __init__( 

62 self, 

63 metricMode: Nullable[str] = None, 

64 weight: int = 1, 

65 userAttributes: Nullable[Iterable[UserAttribute]] = None, 

66 *, 

67 parent: Nullable[InstanceCoverage] = None 

68 ) -> None: 

69 """ 

70 Initialize the covergroup coverage, and append it to the covergroup coverages of its instance. 

71 

72 Its covergroup instances are added by creating them with this coverage as their parent. 

73 

74 :param metricMode: Optional, the mode in which the coverage was measured. Default: ``None``. 

75 :param weight: Optional, weight of the mode in computing the coverage. Default: ``1``. 

76 :param userAttributes: Optional, the user-defined attributes. Default: ``None``. 

77 :param parent: Optional, the instance the coverage belongs to. Default: ``None``. 

78 :raises TypeError: If parameter ``metricMode`` isn't of type :class:`str`. 

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

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

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

82 :raises TypeError: If parameter ``userAttributes`` isn't iterable. 

83 :raises TypeError: If parameter ``userAttributes`` contains an element not of type 

84 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`. 

85 :raises TypeError: If parameter ``parent`` isn't of type 

86 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Instances.InstanceCoverage`. 

87 """ 

88 super().__init__(metricMode, weight, userAttributes, parent=parent) 

89 

90 self._instances = [] 

91 

92 if parent is not None: 

93 parent._covergroupCoverages.append(self) 

94 

95 @classmethod 

96 def Parse(cls, element: _Element, *, parent: Nullable[InstanceCoverage] = None) -> Self: 

97 """ 

98 Parse a covergroup coverage and its covergroup instances from its ``<covergroupCoverage>`` element. 

99 

100 :param element: The ``<covergroupCoverage>`` element. 

101 :param parent: Optional, the instance the coverage belongs to. Default: ``None``. 

102 :returns: The covergroup coverage. 

103 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states. 

104 """ 

105 covergroupCoverage = cls( 

106 element.attrib.get("metricMode"), 

107 int(element.attrib.get("weight", "1")), 

108 cls._ParseUserAttributes(element), 

109 parent=parent 

110 ) 

111 

112 for instanceElement in element.iterfind("{*}cgInstance"): 

113 CovergroupInstance.Parse(instanceElement, parent=covergroupCoverage) 

114 

115 return covergroupCoverage 

116 

117 @readonly 

118 def Instances(self) -> list[CovergroupInstance]: 

119 """ 

120 Read-only property to access the covergroup instances (:attr:`_instances`). 

121 

122 :returns: The covergroup instances, in the order the report lists them. 

123 """ 

124 return self._instances 

125 

126 

127@export 

128class CovergroupID(metaclass=ExtendedType, slots=True): 

129 """ 

130 A ``<cgId>`` of a covergroup instance: the covergroup's name, the module or package declaring it, and where the 

131 covergroup and the instance are declared. 

132 

133 It is a value, as a path is: it names no parent. 

134 """ 

135 

136 _name: str #: Name of the covergroup. 

137 _moduleName: str #: Name of the module or package declaring the covergroup. 

138 _instanceSourceID: StatementID #: Where the covergroup instance is declared. 

139 _sourceID: StatementID #: Where the covergroup is declared. 

140 

141 def __init__(self, name: str, moduleName: str, instanceSourceID: StatementID, sourceID: StatementID) -> None: 

142 """ 

143 Initialize the covergroup identifier. 

144 

145 :param name: Name of the covergroup, e.g. ``cg``. 

146 :param moduleName: Name of the module or package declaring the covergroup. 

147 :param instanceSourceID: Where the covergroup instance is declared. 

148 :param sourceID: Where the covergroup is declared. 

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

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

151 :raises ValueError: If parameter ``moduleName`` is ``None``. 

152 :raises TypeError: If parameter ``moduleName`` isn't of type :class:`str`. 

153 :raises ValueError: If parameter ``instanceSourceID`` is ``None``. 

154 :raises TypeError: If parameter ``instanceSourceID`` isn't of type 

155 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.StatementID`. 

156 :raises ValueError: If parameter ``sourceID`` is ``None``. 

157 :raises TypeError: If parameter ``sourceID`` isn't of type 

158 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.StatementID`. 

159 """ 

160 for parameterName, value in ( 

161 ("name", name), 

162 ("moduleName", moduleName) 

163 ): 

164 if value is None: 164 ↛ 165line 164 didn't jump to line 165 because the condition on line 164 was never true

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

166 elif not isinstance(value, str): 166 ↛ 167line 166 didn't jump to line 167 because the condition on line 166 was never true

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

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

169 raise ex 

170 

171 for parameterName, value in ( 

172 ("instanceSourceID", instanceSourceID), 

173 ("sourceID", sourceID) 

174 ): 

175 if value is None: 175 ↛ 176line 175 didn't jump to line 176 because the condition on line 175 was never true

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

177 elif not isinstance(value, StatementID): 177 ↛ 178line 177 didn't jump to line 178 because the condition on line 177 was never true

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

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

180 raise ex 

181 

182 self._name = name 

183 self._moduleName = moduleName 

184 self._instanceSourceID = instanceSourceID 

185 self._sourceID = sourceID 

186 

187 @classmethod 

188 def Parse(cls, element: _Element) -> Self: 

189 """ 

190 Parse a covergroup identifier from its ``<cgId>`` element. 

191 

192 :param element: The ``<cgId>`` element. 

193 :returns: The covergroup identifier. 

194 """ 

195 return cls( 

196 element.attrib["cgName"], 

197 element.attrib["moduleName"], 

198 StatementID.Parse(element.find("{*}cginstSourceId")), 

199 StatementID.Parse(element.find("{*}cgSourceId")) 

200 ) 

201 

202 @readonly 

203 def Name(self) -> str: 

204 """ 

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

206 

207 :returns: The name, as declared. 

208 """ 

209 return self._name 

210 

211 @readonly 

212 def ModuleName(self) -> str: 

213 """ 

214 Read-only property to access the name of the module or package declaring the covergroup (:attr:`_moduleName`). 

215 

216 :returns: The name, as stated. 

217 """ 

218 return self._moduleName 

219 

220 @readonly 

221 def InstanceSourceID(self) -> StatementID: 

222 """ 

223 Read-only property to access where the covergroup instance is declared (:attr:`_instanceSourceID`). 

224 

225 :returns: The source statement identifier. 

226 """ 

227 return self._instanceSourceID 

228 

229 @readonly 

230 def SourceID(self) -> StatementID: 

231 """ 

232 Read-only property to access where the covergroup is declared (:attr:`_sourceID`). 

233 

234 :returns: The source statement identifier. 

235 """ 

236 return self._sourceID 

237 

238 

239# A class with a property named like a class - ``CovergroupID`` - can't name that class in the annotation of a field: 

240# the class body's namespace, where annotations are evaluated, binds the name to the property. 

241_CovergroupID = CovergroupID 

242 

243 

244@export 

245class CovergroupInstance(Base): 

246 """ 

247 A ``<cgInstance>`` of a covergroup coverage: an instance of a covergroup - or the covergroup as a whole -, its 

248 options and parameters, its coverpoints and crosses. 

249 """ 

250 

251 _parent: Nullable[CovergroupCoverage] #: The covergroup coverage the covergroup instance belongs to. 

252 _name: str #: Name of the covergroup instance. 

253 _key: str #: UCIS key of the covergroup instance. 

254 _options: CovergroupOptions #: The options of the covergroup instance. 

255 _covergroupID: _CovergroupID #: The covergroup and where it and the instance are declared. 

256 _parameters: dict[str, str] #: The parameters of the covergroup instance, by name. 

257 _alias: Nullable[str] #: An alias of the covergroup instance's name. 

258 _isExcluded: bool #: Whether the covergroup instance is excluded from the coverage. 

259 _excludedReason: Nullable[str] #: Why the covergroup instance is excluded. 

260 _coverpoints: list[Coverpoint] #: The coverpoints. 

261 _crosses: list[Cross] #: The crosses. 

262 

263 def __init__( 

264 self, 

265 name: str, 

266 key: str, 

267 options: CovergroupOptions, 

268 covergroupID: CovergroupID, 

269 parameters: Nullable[Mapping[str, str]] = None, 

270 alias: Nullable[str] = None, 

271 excluded: bool = False, 

272 excludedReason: Nullable[str] = None, 

273 userAttributes: Nullable[Iterable[UserAttribute]] = None, 

274 *, 

275 parent: Nullable[CovergroupCoverage] = None 

276 ) -> None: 

277 """ 

278 Initialize the covergroup instance, and append it to the covergroup instances of its covergroup coverage. 

279 

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

281 

282 :param name: Name of the covergroup instance, e.g. ``top.cv``. 

283 :param key: UCIS key of the covergroup instance. 

284 :param options: The options of the covergroup instance. 

285 :param covergroupID: The covergroup and where it and the instance are declared. 

286 :param parameters: Optional, the parameters of the covergroup instance, by name. Default: ``None``. 

287 :param alias: Optional, an alias of the covergroup instance's name. Default: ``None``. 

288 :param excluded: Optional, whether the covergroup instance is excluded from the coverage. Default: 

289 ``False``. 

290 :param excludedReason: Optional, why the covergroup instance is excluded. Default: ``None``. 

291 :param userAttributes: Optional, the user-defined attributes. Default: ``None``. 

292 :param parent: Optional, the covergroup coverage the covergroup instance belongs to. Default: ``None``. 

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

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

295 :raises ValueError: If parameter ``key`` is ``None``. 

296 :raises TypeError: If parameter ``key`` isn't of type :class:`str`. 

297 :raises ValueError: If parameter ``options`` is ``None``. 

298 :raises TypeError: If parameter ``options`` isn't of type 

299 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions.CovergroupOptions`. 

300 :raises ValueError: If parameter ``covergroupID`` is ``None``. 

301 :raises TypeError: If parameter ``covergroupID`` isn't of type :class:`CovergroupID`. 

302 :raises TypeError: If parameter ``parameters`` isn't a mapping. 

303 :raises TypeError: If parameter ``parameters`` contains a name or value not of type :class:`str`. 

304 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`. 

305 :raises ValueError: If parameter ``excluded`` is ``None``. 

306 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`. 

307 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`. 

308 :raises TypeError: If parameter ``userAttributes`` isn't iterable. 

309 :raises TypeError: If parameter ``userAttributes`` contains an element not of type 

310 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`. 

311 :raises TypeError: If parameter ``parent`` isn't of type :class:`CovergroupCoverage`. 

312 """ 

313 super().__init__(userAttributes) 

314 

315 for parameterName, value in ( 

316 ("name", name), 

317 ("key", key) 

318 ): 

319 if value is None: 319 ↛ 320line 319 didn't jump to line 320 because the condition on line 319 was never true

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

321 elif not isinstance(value, str): 321 ↛ 322line 321 didn't jump to line 322 because the condition on line 321 was never true

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

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

324 raise ex 

325 

326 if options is None: 326 ↛ 327line 326 didn't jump to line 327 because the condition on line 326 was never true

327 raise ValueError("Parameter 'options' is None.") 

328 elif not isinstance(options, CovergroupOptions): 

329 ex = TypeError("Parameter 'options' is not of type 'CovergroupOptions'.") 

330 ex.add_note(f"Got type '{getFullyQualifiedName(options)}'.") 

331 raise ex 

332 

333 if covergroupID is None: 333 ↛ 334line 333 didn't jump to line 334 because the condition on line 333 was never true

334 raise ValueError("Parameter 'covergroupID' is None.") 

335 elif not isinstance(covergroupID, CovergroupID): 335 ↛ 336line 335 didn't jump to line 336 because the condition on line 335 was never true

336 ex = TypeError("Parameter 'covergroupID' is not of type 'CovergroupID'.") 

337 ex.add_note(f"Got type '{getFullyQualifiedName(covergroupID)}'.") 

338 raise ex 

339 

340 for parameterName, value in ( 

341 ("alias", alias), 

342 ("excludedReason", excludedReason) 

343 ): 

344 if value is not None and not isinstance(value, str): 344 ↛ 345line 344 didn't jump to line 345 because the condition on line 344 was never true

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

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

347 raise ex 

348 

349 if excluded is None: 349 ↛ 350line 349 didn't jump to line 350 because the condition on line 349 was never true

350 raise ValueError("Parameter 'excluded' is None.") 

351 elif not isinstance(excluded, bool): 351 ↛ 352line 351 didn't jump to line 352 because the condition on line 351 was never true

352 ex = TypeError("Parameter 'excluded' is not of type 'bool'.") 

353 ex.add_note(f"Got type '{getFullyQualifiedName(excluded)}'.") 

354 raise ex 

355 

356 if parent is not None and not isinstance(parent, CovergroupCoverage): 356 ↛ 357line 356 didn't jump to line 357 because the condition on line 356 was never true

357 ex = TypeError("Parameter 'parent' is not of type 'CovergroupCoverage'.") 

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

359 raise ex 

360 

361 self._parent = parent 

362 self._name = name 

363 self._key = key 

364 self._options = options 

365 self._covergroupID = covergroupID 

366 self._parameters = {} 

367 self._alias = alias 

368 self._isExcluded = excluded 

369 self._excludedReason = excludedReason 

370 self._coverpoints = [] 

371 self._crosses = [] 

372 

373 if parameters is not None: 

374 if not isinstance(parameters, Mapping): 374 ↛ 375line 374 didn't jump to line 375 because the condition on line 374 was never true

375 ex = TypeError("Parameter 'parameters' is not a mapping.") 

376 ex.add_note(f"Got type '{getFullyQualifiedName(parameters)}'.") 

377 raise ex 

378 

379 for parameterName, value in parameters.items(): 

380 if not isinstance(parameterName, str) or not isinstance(value, str): 380 ↛ 381line 380 didn't jump to line 381 because the condition on line 380 was never true

381 ex = TypeError("Parameter 'parameters' contains a name or value not of type 'str'.") 

382 ex.add_note(f"Got types '{getFullyQualifiedName(parameterName)}' and '{getFullyQualifiedName(value)}'.") 

383 raise ex 

384 

385 self._parameters[parameterName] = value 

386 

387 if parent is not None: 387 ↛ exitline 387 didn't return from function '__init__' because the condition on line 387 was always true

388 parent._instances.append(self) 

389 

390 @classmethod 

391 def Parse(cls, element: _Element, *, parent: Nullable[CovergroupCoverage] = None) -> Self: 

392 """ 

393 Parse a covergroup instance, its coverpoints and crosses from its ``<cgInstance>`` element. 

394 

395 :param element: The ``<cgInstance>`` element. 

396 :param parent: Optional, the covergroup coverage the covergroup instance belongs to. Default: ``None``. 

397 :returns: The covergroup instance. 

398 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states. 

399 """ 

400 instance = cls( 

401 element.attrib["name"], 

402 element.attrib["key"], 

403 CovergroupOptions.Parse(element.find("{*}options")), 

404 CovergroupID.Parse(element.find("{*}cgId")), 

405 { 

406 parameterElement.findtext("{*}name"): parameterElement.findtext("{*}value") 

407 for parameterElement in element.iterfind("{*}cgParms") 

408 }, 

409 element.attrib.get("alias"), 

410 cls._ParseBoolean(element.attrib.get("excluded", "false")), 

411 element.attrib.get("excludedReason"), 

412 cls._ParseUserAttributes(element), 

413 parent=parent 

414 ) 

415 

416 for coverpointElement in element.iterfind("{*}coverpoint"): 

417 Coverpoint.Parse(coverpointElement, parent=instance) 

418 

419 for crossElement in element.iterfind("{*}cross"): 

420 Cross.Parse(crossElement, parent=instance) 

421 

422 return instance 

423 

424 @readonly 

425 def Parent(self) -> Nullable[CovergroupCoverage]: 

426 """ 

427 Read-only property to access the covergroup coverage the covergroup instance belongs to (:attr:`_parent`). 

428 

429 :returns: The covergroup coverage; ``None``, if the covergroup instance belongs to none. 

430 """ 

431 return self._parent 

432 

433 @readonly 

434 def Name(self) -> str: 

435 """ 

436 Read-only property to access the name of the covergroup instance (:attr:`_name`). 

437 

438 :returns: The name, as stated. 

439 """ 

440 return self._name 

441 

442 @readonly 

443 def Key(self) -> str: 

444 """ 

445 Read-only property to access the UCIS key of the covergroup instance (:attr:`_key`). 

446 

447 :returns: The key, as stated. 

448 """ 

449 return self._key 

450 

451 @readonly 

452 def Options(self) -> CovergroupOptions: 

453 """ 

454 Read-only property to access the options of the covergroup instance (:attr:`_options`). 

455 

456 :returns: The options. 

457 """ 

458 return self._options 

459 

460 @readonly 

461 def CovergroupID(self) -> CovergroupID: 

462 """ 

463 Read-only property to access the covergroup and where it and the instance are declared (:attr:`_covergroupID`). 

464 

465 :returns: The covergroup identifier. 

466 """ 

467 return self._covergroupID 

468 

469 @readonly 

470 def Parameters(self) -> dict[str, str]: 

471 """ 

472 Read-only property to access the parameters of the covergroup instance (:attr:`_parameters`). 

473 

474 :returns: The parameters' values, by name. 

475 """ 

476 return self._parameters 

477 

478 @readonly 

479 def Alias(self) -> Nullable[str]: 

480 """ 

481 Read-only property to access the alias of the covergroup instance's name (:attr:`_alias`). 

482 

483 :returns: The alias; ``None``, if the report states none. 

484 """ 

485 return self._alias 

486 

487 @readonly 

488 def IsExcluded(self) -> bool: 

489 """ 

490 Read-only property to access whether the covergroup instance is excluded from the coverage (:attr:`_isExcluded`). 

491 

492 :returns: ``True``, if excluded. 

493 """ 

494 return self._isExcluded 

495 

496 @readonly 

497 def ExcludedReason(self) -> Nullable[str]: 

498 """ 

499 Read-only property to access why the covergroup instance is excluded (:attr:`_excludedReason`). 

500 

501 :returns: The reason; ``None``, if the report states none. 

502 """ 

503 return self._excludedReason 

504 

505 @readonly 

506 def Coverpoints(self) -> list[Coverpoint]: 

507 """ 

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

509 

510 :returns: The coverpoints, in the order the report lists them. 

511 """ 

512 return self._coverpoints 

513 

514 @readonly 

515 def Crosses(self) -> list[Cross]: 

516 """ 

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

518 

519 :returns: The crosses, in the order the report lists them. 

520 """ 

521 return self._crosses