Coverage for pyEDAA/Reports/CodeCoverage/UCIS/CoverOptions.py: 66%

150 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 options of the covergroup coverage of the UCIS XML interchange format: the ``<options>`` of a covergroup instance, 

33a coverpoint or a cross, as SystemVerilog declares them. 

34""" 

35from __future__ import annotations 

36 

37from typing import Self 

38 

39from lxml.etree import _Element 

40from pyTooling.Common import getFullyQualifiedName 

41from pyTooling.Decorators import export, readonly 

42from pyTooling.MetaClasses import ExtendedType, abstractclass 

43 

44from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base 

45 

46 

47@export 

48@abstractclass 

49class Options(metaclass=ExtendedType, slots=True): 

50 """ 

51 Base-class of the ``<options>`` of a covergroup instance, a coverpoint or a cross: the options SystemVerilog declares 

52 for all three. 

53 

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

55 """ 

56 

57 _weight: int #: Weight in computing the coverage. 

58 _goal: int #: Coverage goal, in percent. 

59 _comment: str #: A comment. 

60 _atLeast: int #: How often a bin must be hit to be covered. 

61 

62 def __init__(self, weight: int = 1, goal: int = 100, comment: str = "", atLeast: int = 1) -> None: 

63 """ 

64 Initialize the options. 

65 

66 :param weight: Optional, weight in computing the coverage. Default: ``1``. 

67 :param goal: Optional, coverage goal, in percent. Default: ``100``. 

68 :param comment: Optional, a comment. Default: ``""``. 

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

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

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

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

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

74 :raises TypeError: If parameter ``goal`` isn't of type :class:`int`. 

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

76 :raises ValueError: If parameter ``comment`` is ``None``. 

77 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`. 

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

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

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

81 """ 

82 for parameterName, value in ( 

83 ("weight", weight), 

84 ("goal", goal), 

85 ("atLeast", atLeast) 

86 ): 

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

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

89 elif not isinstance(value, int) or isinstance(value, bool): 89 ↛ 90line 89 didn't jump to line 90 because the condition on line 89 was never true

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

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

92 raise ex 

93 elif value < 0: 

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

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

96 raise ex 

97 

98 if comment is None: 98 ↛ 99line 98 didn't jump to line 99 because the condition on line 98 was never true

99 raise ValueError("Parameter 'comment' is None.") 

100 elif not isinstance(comment, str): 100 ↛ 101line 100 didn't jump to line 101 because the condition on line 100 was never true

101 ex = TypeError("Parameter 'comment' is not of type 'str'.") 

102 ex.add_note(f"Got type '{getFullyQualifiedName(comment)}'.") 

103 raise ex 

104 

105 self._weight = weight 

106 self._goal = goal 

107 self._comment = comment 

108 self._atLeast = atLeast 

109 

110 @readonly 

111 def Weight(self) -> int: 

112 """ 

113 Read-only property to access the weight in computing the coverage (:attr:`_weight`). 

114 

115 :returns: The weight; ``1``, if the report states none. 

116 """ 

117 return self._weight 

118 

119 @readonly 

120 def Goal(self) -> int: 

121 """ 

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

123 

124 :returns: The goal, in percent; ``100``, if the report states none. 

125 """ 

126 return self._goal 

127 

128 @readonly 

129 def Comment(self) -> str: 

130 """ 

131 Read-only property to access the comment (:attr:`_comment`). 

132 

133 :returns: The comment; empty, if the report states none. 

134 """ 

135 return self._comment 

136 

137 @readonly 

138 def AtLeast(self) -> int: 

139 """ 

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

141 

142 :returns: The count; ``1``, if the report states none. 

143 """ 

144 return self._atLeast 

145 

146 

147@export 

148class CoverpointOptions(Options): 

149 """ 

150 The ``<options>`` of a coverpoint. 

151 """ 

152 

153 _detectOverlap: bool #: Whether to warn about overlapping bins. 

154 _autoBinMax: int #: Maximum number of bins created automatically. 

155 

156 def __init__( 

157 self, 

158 weight: int = 1, 

159 goal: int = 100, 

160 comment: str = "", 

161 atLeast: int = 1, 

162 detectOverlap: bool = False, 

163 autoBinMax: int = 64 

164 ) -> None: 

165 """ 

166 Initialize the options of a coverpoint. 

167 

168 :param weight: Optional, weight in computing the coverage. Default: ``1``. 

169 :param goal: Optional, coverage goal, in percent. Default: ``100``. 

170 :param comment: Optional, a comment. Default: ``""``. 

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

172 :param detectOverlap: Optional, whether to warn about overlapping bins. Default: ``False``. 

173 :param autoBinMax: Optional, maximum number of bins created automatically. Default: ``64``. 

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

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

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

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

178 :raises TypeError: If parameter ``goal`` isn't of type :class:`int`. 

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

180 :raises ValueError: If parameter ``comment`` is ``None``. 

181 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`. 

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

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

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

185 :raises ValueError: If parameter ``detectOverlap`` is ``None``. 

186 :raises TypeError: If parameter ``detectOverlap`` isn't of type :class:`bool`. 

187 :raises ValueError: If parameter ``autoBinMax`` is ``None``. 

188 :raises TypeError: If parameter ``autoBinMax`` isn't of type :class:`int`. 

189 :raises ValueError: If parameter ``autoBinMax`` is negative. 

190 """ 

191 super().__init__(weight, goal, comment, atLeast) 

192 

193 if detectOverlap is None: 193 ↛ 194line 193 didn't jump to line 194 because the condition on line 193 was never true

194 raise ValueError("Parameter 'detectOverlap' is None.") 

195 elif not isinstance(detectOverlap, bool): 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true

196 ex = TypeError("Parameter 'detectOverlap' is not of type 'bool'.") 

197 ex.add_note(f"Got type '{getFullyQualifiedName(detectOverlap)}'.") 

198 raise ex 

199 

200 if autoBinMax is None: 200 ↛ 201line 200 didn't jump to line 201 because the condition on line 200 was never true

201 raise ValueError("Parameter 'autoBinMax' is None.") 

202 elif not isinstance(autoBinMax, int) or isinstance(autoBinMax, bool): 202 ↛ 203line 202 didn't jump to line 203 because the condition on line 202 was never true

203 ex = TypeError("Parameter 'autoBinMax' is not of type 'int'.") 

204 ex.add_note(f"Got type '{getFullyQualifiedName(autoBinMax)}'.") 

205 raise ex 

206 elif autoBinMax < 0: 206 ↛ 207line 206 didn't jump to line 207 because the condition on line 206 was never true

207 ex = ValueError("Parameter 'autoBinMax' is negative.") 

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

209 raise ex 

210 

211 self._detectOverlap = detectOverlap 

212 self._autoBinMax = autoBinMax 

213 

214 @classmethod 

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

216 """ 

217 Parse the options of a coverpoint from its ``<options>`` element; an option it doesn't state has its default. 

218 

219 :param element: The ``<options>`` element. 

220 :returns: The options. 

221 """ 

222 attributes = element.attrib 

223 return cls( 

224 int(attributes.get("weight", "1")), 

225 int(attributes.get("goal", "100")), 

226 attributes.get("comment", ""), 

227 int(attributes.get("at_least", "1")), 

228 Base._ParseBoolean(attributes.get("detect_overlap", "false")), 

229 int(attributes.get("auto_bin_max", "64")) 

230 ) 

231 

232 @readonly 

233 def DetectOverlap(self) -> bool: 

234 """ 

235 Read-only property to access whether to warn about overlapping bins (:attr:`_detectOverlap`). 

236 

237 :returns: ``True``, if overlapping bins are reported. 

238 """ 

239 return self._detectOverlap 

240 

241 @readonly 

242 def AutoBinMax(self) -> int: 

243 """ 

244 Read-only property to access the maximum number of bins created automatically (:attr:`_autoBinMax`). 

245 

246 :returns: The maximum; ``64``, if the report states none. 

247 """ 

248 return self._autoBinMax 

249 

250 

251@export 

252class CrossOptions(Options): 

253 """ 

254 The ``<options>`` of a cross. 

255 """ 

256 

257 _crossNumPrintMissing: int #: Number of uncovered cross bins to save and report. 

258 

259 def __init__( 

260 self, 

261 weight: int = 1, 

262 goal: int = 100, 

263 comment: str = "", 

264 atLeast: int = 1, 

265 crossNumPrintMissing: int = 0 

266 ) -> None: 

267 """ 

268 Initialize the options of a cross. 

269 

270 :param weight: Optional, weight in computing the coverage. Default: ``1``. 

271 :param goal: Optional, coverage goal, in percent. Default: ``100``. 

272 :param comment: Optional, a comment. Default: ``""``. 

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

274 :param crossNumPrintMissing: Optional, number of uncovered cross bins to save and report. Default: ``0``. 

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

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

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

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

279 :raises TypeError: If parameter ``goal`` isn't of type :class:`int`. 

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

281 :raises ValueError: If parameter ``comment`` is ``None``. 

282 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`. 

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

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

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

286 :raises ValueError: If parameter ``crossNumPrintMissing`` is ``None``. 

287 :raises TypeError: If parameter ``crossNumPrintMissing`` isn't of type :class:`int`. 

288 :raises ValueError: If parameter ``crossNumPrintMissing`` is negative. 

289 """ 

290 super().__init__(weight, goal, comment, atLeast) 

291 

292 if crossNumPrintMissing is None: 292 ↛ 293line 292 didn't jump to line 293 because the condition on line 292 was never true

293 raise ValueError("Parameter 'crossNumPrintMissing' is None.") 

294 elif not isinstance(crossNumPrintMissing, int) or isinstance(crossNumPrintMissing, bool): 294 ↛ 295line 294 didn't jump to line 295 because the condition on line 294 was never true

295 ex = TypeError("Parameter 'crossNumPrintMissing' is not of type 'int'.") 

296 ex.add_note(f"Got type '{getFullyQualifiedName(crossNumPrintMissing)}'.") 

297 raise ex 

298 elif crossNumPrintMissing < 0: 298 ↛ 299line 298 didn't jump to line 299 because the condition on line 298 was never true

299 ex = ValueError("Parameter 'crossNumPrintMissing' is negative.") 

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

301 raise ex 

302 

303 self._crossNumPrintMissing = crossNumPrintMissing 

304 

305 @classmethod 

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

307 """ 

308 Parse the options of a cross from its ``<options>`` element; an option it doesn't state has its default. 

309 

310 :param element: The ``<options>`` element. 

311 :returns: The options. 

312 """ 

313 attributes = element.attrib 

314 return cls( 

315 int(attributes.get("weight", "1")), 

316 int(attributes.get("goal", "100")), 

317 attributes.get("comment", ""), 

318 int(attributes.get("at_least", "1")), 

319 int(attributes.get("cross_num_print_missing", "0")) 

320 ) 

321 

322 @readonly 

323 def CrossNumPrintMissing(self) -> int: 

324 """ 

325 Read-only property to access the number of uncovered cross bins to save and report (:attr:`_crossNumPrintMissing`). 

326 

327 :returns: The number; ``0``, if the report states none. 

328 """ 

329 return self._crossNumPrintMissing 

330 

331 

332@export 

333class CovergroupOptions(CoverpointOptions): 

334 """ 

335 The ``<options>`` of a covergroup instance. 

336 """ 

337 

338 _crossNumPrintMissing: int #: Number of uncovered cross bins to save and report. 

339 _perInstance: bool #: Whether the instance's coverage is kept on its own. 

340 _mergeInstances: bool #: Whether the covergroup's coverage merges its instances. 

341 

342 def __init__( 

343 self, 

344 weight: int = 1, 

345 goal: int = 100, 

346 comment: str = "", 

347 atLeast: int = 1, 

348 detectOverlap: bool = False, 

349 autoBinMax: int = 64, 

350 crossNumPrintMissing: int = 0, 

351 perInstance: bool = False, 

352 mergeInstances: bool = False 

353 ) -> None: 

354 """ 

355 Initialize the options of a covergroup instance. 

356 

357 :param weight: Optional, weight in computing the coverage. Default: ``1``. 

358 :param goal: Optional, coverage goal, in percent. Default: ``100``. 

359 :param comment: Optional, a comment. Default: ``""``. 

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

361 :param detectOverlap: Optional, whether to warn about overlapping bins. Default: ``False``. 

362 :param autoBinMax: Optional, maximum number of bins created automatically. Default: ``64``. 

363 :param crossNumPrintMissing: Optional, number of uncovered cross bins to save and report. Default: ``0``. 

364 :param perInstance: Optional, whether the instance's coverage is kept on its own. Default: ``False``. 

365 :param mergeInstances: Optional, whether the covergroup's coverage merges its instances. Default: 

366 ``False``. 

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

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

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

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

371 :raises TypeError: If parameter ``goal`` isn't of type :class:`int`. 

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

373 :raises ValueError: If parameter ``comment`` is ``None``. 

374 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`. 

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

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

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

378 :raises ValueError: If parameter ``detectOverlap`` is ``None``. 

379 :raises TypeError: If parameter ``detectOverlap`` isn't of type :class:`bool`. 

380 :raises ValueError: If parameter ``autoBinMax`` is ``None``. 

381 :raises TypeError: If parameter ``autoBinMax`` isn't of type :class:`int`. 

382 :raises ValueError: If parameter ``autoBinMax`` is negative. 

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

384 :raises TypeError: If parameter ``crossNumPrintMissing`` isn't of type :class:`int`. 

385 :raises ValueError: If parameter ``crossNumPrintMissing`` is negative. 

386 :raises ValueError: If parameter ``perInstance`` is ``None``. 

387 :raises TypeError: If parameter ``perInstance`` isn't of type :class:`bool`. 

388 :raises ValueError: If parameter ``mergeInstances`` is ``None``. 

389 :raises TypeError: If parameter ``mergeInstances`` isn't of type :class:`bool`. 

390 """ 

391 super().__init__(weight, goal, comment, atLeast, detectOverlap, autoBinMax) 

392 

393 if crossNumPrintMissing is None: 393 ↛ 394line 393 didn't jump to line 394 because the condition on line 393 was never true

394 raise ValueError("Parameter 'crossNumPrintMissing' is None.") 

395 elif not isinstance(crossNumPrintMissing, int) or isinstance(crossNumPrintMissing, bool): 395 ↛ 396line 395 didn't jump to line 396 because the condition on line 395 was never true

396 ex = TypeError("Parameter 'crossNumPrintMissing' is not of type 'int'.") 

397 ex.add_note(f"Got type '{getFullyQualifiedName(crossNumPrintMissing)}'.") 

398 raise ex 

399 elif crossNumPrintMissing < 0: 399 ↛ 400line 399 didn't jump to line 400 because the condition on line 399 was never true

400 ex = ValueError("Parameter 'crossNumPrintMissing' is negative.") 

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

402 raise ex 

403 

404 if perInstance is None: 404 ↛ 405line 404 didn't jump to line 405 because the condition on line 404 was never true

405 raise ValueError("Parameter 'perInstance' is None.") 

406 elif not isinstance(perInstance, bool): 406 ↛ 407line 406 didn't jump to line 407 because the condition on line 406 was never true

407 ex = TypeError("Parameter 'perInstance' is not of type 'bool'.") 

408 ex.add_note(f"Got type '{getFullyQualifiedName(perInstance)}'.") 

409 raise ex 

410 

411 if mergeInstances is None: 411 ↛ 412line 411 didn't jump to line 412 because the condition on line 411 was never true

412 raise ValueError("Parameter 'mergeInstances' is None.") 

413 elif not isinstance(mergeInstances, bool): 413 ↛ 414line 413 didn't jump to line 414 because the condition on line 413 was never true

414 ex = TypeError("Parameter 'mergeInstances' is not of type 'bool'.") 

415 ex.add_note(f"Got type '{getFullyQualifiedName(mergeInstances)}'.") 

416 raise ex 

417 

418 self._crossNumPrintMissing = crossNumPrintMissing 

419 self._perInstance = perInstance 

420 self._mergeInstances = mergeInstances 

421 

422 @classmethod 

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

424 """ 

425 Parse the options of a covergroup instance from its ``<options>`` element; an option it doesn't state has its 

426 default. 

427 

428 :param element: The ``<options>`` element. 

429 :returns: The options. 

430 """ 

431 attributes = element.attrib 

432 return cls( 

433 int(attributes.get("weight", "1")), 

434 int(attributes.get("goal", "100")), 

435 attributes.get("comment", ""), 

436 int(attributes.get("at_least", "1")), 

437 Base._ParseBoolean(attributes.get("detect_overlap", "false")), 

438 int(attributes.get("auto_bin_max", "64")), 

439 int(attributes.get("cross_num_print_missing", "0")), 

440 Base._ParseBoolean(attributes.get("per_instance", "false")), 

441 Base._ParseBoolean(attributes.get("merge_instances", "false")) 

442 ) 

443 

444 @readonly 

445 def CrossNumPrintMissing(self) -> int: 

446 """ 

447 Read-only property to access the number of uncovered cross bins to save and report (:attr:`_crossNumPrintMissing`). 

448 

449 :returns: The number; ``0``, if the report states none. 

450 """ 

451 return self._crossNumPrintMissing 

452 

453 @readonly 

454 def PerInstance(self) -> bool: 

455 """ 

456 Read-only property to access whether the instance's coverage is kept on its own (:attr:`_perInstance`). 

457 

458 :returns: ``True``, if kept per instance. 

459 """ 

460 return self._perInstance 

461 

462 @readonly 

463 def MergeInstances(self) -> bool: 

464 """ 

465 Read-only property to access whether the covergroup's coverage merges its instances (:attr:`_mergeInstances`). 

466 

467 :returns: ``True``, if the instances are merged; otherwise, it is their weighted average. 

468 """ 

469 return self._mergeInstances