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

184 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-21 23:49 +0000

1# ==================================================================================================================== # 

2# _____ ____ _ _ ____ _ # 

3# _ __ _ _| ____| _ \ / \ / \ | _ \ ___ _ __ ___ _ __| |_ ___ # 

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | |_) / _ \ '_ \ / _ \| '__| __/ __| # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ _| _ < __/ |_) | (_) | | | |_\__ \ # 

6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)_| \_\___| .__/ \___/|_| \__|___/ # 

7# |_| |___/ |_| # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2026-2026 Electronic Design Automation Abstraction (EDA²) # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31""" 

32The 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 ``userAttributes`` isn't iterable. 

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

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

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

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

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

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

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`` or ``moduleName`` is ``None``. 

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

151 :raises ValueError: If parameter ``instanceSourceID`` or ``sourceID`` is ``None``. 

152 :raises TypeError: If parameter ``instanceSourceID`` or ``sourceID`` isn't of type 

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

154 """ 

155 for parameterName, value in ( 

156 ("name", name), 

157 ("moduleName", moduleName) 

158 ): 

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

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

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

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

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

164 raise ex 

165 

166 for parameterName, value in ( 

167 ("instanceSourceID", instanceSourceID), 

168 ("sourceID", sourceID) 

169 ): 

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

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

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

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

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

175 raise ex 

176 

177 self._name = name 

178 self._moduleName = moduleName 

179 self._instanceSourceID = instanceSourceID 

180 self._sourceID = sourceID 

181 

182 @classmethod 

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

184 """ 

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

186 

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

188 :returns: The covergroup identifier. 

189 """ 

190 return cls( 

191 element.attrib["cgName"], 

192 element.attrib["moduleName"], 

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

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

195 ) 

196 

197 @readonly 

198 def Name(self) -> str: 

199 """ 

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

201 

202 :returns: The name, as declared. 

203 """ 

204 return self._name 

205 

206 @readonly 

207 def ModuleName(self) -> str: 

208 """ 

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

210 

211 :returns: The name, as stated. 

212 """ 

213 return self._moduleName 

214 

215 @readonly 

216 def InstanceSourceID(self) -> StatementID: 

217 """ 

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

219 

220 :returns: The source statement identifier. 

221 """ 

222 return self._instanceSourceID 

223 

224 @readonly 

225 def SourceID(self) -> StatementID: 

226 """ 

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

228 

229 :returns: The source statement identifier. 

230 """ 

231 return self._sourceID 

232 

233 

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

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

236_CovergroupID = CovergroupID 

237 

238 

239@export 

240class CovergroupInstance(Base): 

241 """ 

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

243 options and parameters, its coverpoints and crosses. 

244 """ 

245 

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

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

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

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

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

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

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

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

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

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

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

257 

258 def __init__( 

259 self, 

260 name: str, 

261 key: str, 

262 options: CovergroupOptions, 

263 covergroupID: CovergroupID, 

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

265 alias: Nullable[str] = None, 

266 excluded: bool = False, 

267 excludedReason: Nullable[str] = None, 

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

269 *, 

270 parent: Nullable[CovergroupCoverage] = None 

271 ) -> None: 

272 """ 

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

274 

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

276 

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

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

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

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

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

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

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

284 ``False``. 

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

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

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

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

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

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

291 :raises ValueError: If parameter ``name`` or ``key`` is ``None``. 

292 :raises TypeError: If parameter ``name`` or ``key`` isn't of type :class:`str`. 

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

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

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

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

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

298 :raises TypeError: If parameter ``alias`` or ``excludedReason`` isn't of type :class:`str`. 

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

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

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

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

305 super().__init__(userAttributes) 

306 

307 for parameterName, value in ( 

308 ("name", name), 

309 ("key", key) 

310 ): 

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

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

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

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

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

316 raise ex 

317 

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

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

320 elif not isinstance(options, CovergroupOptions): 

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

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

323 raise ex 

324 

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

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

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

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

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

330 raise ex 

331 

332 for parameterName, value in ( 

333 ("alias", alias), 

334 ("excludedReason", excludedReason) 

335 ): 

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

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

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

339 raise ex 

340 

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

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

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

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

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

346 raise ex 

347 

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

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

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

351 raise ex 

352 

353 self._parent = parent 

354 self._name = name 

355 self._key = key 

356 self._options = options 

357 self._covergroupID = covergroupID 

358 self._parameters = {} 

359 self._alias = alias 

360 self._isExcluded = excluded 

361 self._excludedReason = excludedReason 

362 self._coverpoints = [] 

363 self._crosses = [] 

364 

365 if parameters is not None: 

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

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

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

369 raise ex 

370 

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

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

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

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

375 raise ex 

376 

377 self._parameters[parameterName] = value 

378 

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

380 parent._instances.append(self) 

381 

382 @classmethod 

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

384 """ 

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

386 

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

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

389 :returns: The covergroup instance. 

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

391 """ 

392 instance = cls( 

393 element.attrib["name"], 

394 element.attrib["key"], 

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

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

397 { 

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

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

400 }, 

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

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

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

404 cls._ParseUserAttributes(element), 

405 parent=parent 

406 ) 

407 

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

409 Coverpoint.Parse(coverpointElement, parent=instance) 

410 

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

412 Cross.Parse(crossElement, parent=instance) 

413 

414 return instance 

415 

416 @readonly 

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

418 """ 

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

420 

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

422 """ 

423 return self._parent 

424 

425 @readonly 

426 def Name(self) -> str: 

427 """ 

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

429 

430 :returns: The name, as stated. 

431 """ 

432 return self._name 

433 

434 @readonly 

435 def Key(self) -> str: 

436 """ 

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

438 

439 :returns: The key, as stated. 

440 """ 

441 return self._key 

442 

443 @readonly 

444 def Options(self) -> CovergroupOptions: 

445 """ 

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

447 

448 :returns: The options. 

449 """ 

450 return self._options 

451 

452 @readonly 

453 def CovergroupID(self) -> CovergroupID: 

454 """ 

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

456 

457 :returns: The covergroup identifier. 

458 """ 

459 return self._covergroupID 

460 

461 @readonly 

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

463 """ 

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

465 

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

467 """ 

468 return self._parameters 

469 

470 @readonly 

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

472 """ 

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

474 

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

476 """ 

477 return self._alias 

478 

479 @readonly 

480 def IsExcluded(self) -> bool: 

481 """ 

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

483 

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

485 """ 

486 return self._isExcluded 

487 

488 @readonly 

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

490 """ 

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

492 

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

494 """ 

495 return self._excludedReason 

496 

497 @readonly 

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

499 """ 

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

501 

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

503 """ 

504 return self._coverpoints 

505 

506 @readonly 

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

508 """ 

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

510 

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

512 """ 

513 return self._crosses