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

150 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 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``, ``goal`` or ``atLeast`` is ``None``. 

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

72 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is negative. 

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

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

75 """ 

76 for parameterName, value in ( 

77 ("weight", weight), 

78 ("goal", goal), 

79 ("atLeast", atLeast) 

80 ): 

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

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

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

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

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

86 raise ex 

87 elif value < 0: 

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

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

90 raise ex 

91 

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

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

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

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

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

97 raise ex 

98 

99 self._weight = weight 

100 self._goal = goal 

101 self._comment = comment 

102 self._atLeast = atLeast 

103 

104 @readonly 

105 def Weight(self) -> int: 

106 """ 

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

108 

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

110 """ 

111 return self._weight 

112 

113 @readonly 

114 def Goal(self) -> int: 

115 """ 

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

117 

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

119 """ 

120 return self._goal 

121 

122 @readonly 

123 def Comment(self) -> str: 

124 """ 

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

126 

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

128 """ 

129 return self._comment 

130 

131 @readonly 

132 def AtLeast(self) -> int: 

133 """ 

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

135 

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

137 """ 

138 return self._atLeast 

139 

140 

141@export 

142class CoverpointOptions(Options): 

143 """ 

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

145 """ 

146 

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

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

149 

150 def __init__( 

151 self, 

152 weight: int = 1, 

153 goal: int = 100, 

154 comment: str = "", 

155 atLeast: int = 1, 

156 detectOverlap: bool = False, 

157 autoBinMax: int = 64 

158 ) -> None: 

159 """ 

160 Initialize the options of a coverpoint. 

161 

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

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

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

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

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

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

168 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is ``None``. 

169 :raises TypeError: If parameter ``weight``, ``goal`` or ``atLeast`` isn't of type :class:`int`. 

170 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is negative. 

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

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

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

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

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

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

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

178 """ 

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

180 

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

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

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

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

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

186 raise ex 

187 

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

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

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

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

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

193 raise ex 

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

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

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

197 raise ex 

198 

199 self._detectOverlap = detectOverlap 

200 self._autoBinMax = autoBinMax 

201 

202 @classmethod 

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

204 """ 

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

206 

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

208 :returns: The options. 

209 """ 

210 attributes = element.attrib 

211 return cls( 

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

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

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

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

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

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

218 ) 

219 

220 @readonly 

221 def DetectOverlap(self) -> bool: 

222 """ 

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

224 

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

226 """ 

227 return self._detectOverlap 

228 

229 @readonly 

230 def AutoBinMax(self) -> int: 

231 """ 

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

233 

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

235 """ 

236 return self._autoBinMax 

237 

238 

239@export 

240class CrossOptions(Options): 

241 """ 

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

243 """ 

244 

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

246 

247 def __init__( 

248 self, 

249 weight: int = 1, 

250 goal: int = 100, 

251 comment: str = "", 

252 atLeast: int = 1, 

253 crossNumPrintMissing: int = 0 

254 ) -> None: 

255 """ 

256 Initialize the options of a cross. 

257 

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

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

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

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

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

263 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is ``None``. 

264 :raises TypeError: If parameter ``weight``, ``goal`` or ``atLeast`` isn't of type :class:`int`. 

265 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is negative. 

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

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

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

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

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

271 """ 

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

273 

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

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

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

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

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

279 raise ex 

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

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

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

283 raise ex 

284 

285 self._crossNumPrintMissing = crossNumPrintMissing 

286 

287 @classmethod 

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

289 """ 

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

291 

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

293 :returns: The options. 

294 """ 

295 attributes = element.attrib 

296 return cls( 

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

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

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

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

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

302 ) 

303 

304 @readonly 

305 def CrossNumPrintMissing(self) -> int: 

306 """ 

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

308 

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

310 """ 

311 return self._crossNumPrintMissing 

312 

313 

314@export 

315class CovergroupOptions(CoverpointOptions): 

316 """ 

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

318 """ 

319 

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

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

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

323 

324 def __init__( 

325 self, 

326 weight: int = 1, 

327 goal: int = 100, 

328 comment: str = "", 

329 atLeast: int = 1, 

330 detectOverlap: bool = False, 

331 autoBinMax: int = 64, 

332 crossNumPrintMissing: int = 0, 

333 perInstance: bool = False, 

334 mergeInstances: bool = False 

335 ) -> None: 

336 """ 

337 Initialize the options of a covergroup instance. 

338 

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

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

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

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

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

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

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

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

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

348 ``False``. 

349 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is ``None``. 

350 :raises TypeError: If parameter ``weight``, ``goal`` or ``atLeast`` isn't of type :class:`int`. 

351 :raises ValueError: If parameter ``weight``, ``goal`` or ``atLeast`` is negative. 

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

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

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

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

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

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

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

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

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

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

362 :raises ValueError: If parameter ``perInstance`` or ``mergeInstances`` is ``None``. 

363 :raises TypeError: If parameter ``perInstance`` or ``mergeInstances`` isn't of type :class:`bool`. 

364 """ 

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

366 

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

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

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

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

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

372 raise ex 

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

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

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

376 raise ex 

377 

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

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

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

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

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

383 raise ex 

384 

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

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

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

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

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

390 raise ex 

391 

392 self._crossNumPrintMissing = crossNumPrintMissing 

393 self._perInstance = perInstance 

394 self._mergeInstances = mergeInstances 

395 

396 @classmethod 

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

398 """ 

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

400 default. 

401 

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

403 :returns: The options. 

404 """ 

405 attributes = element.attrib 

406 return cls( 

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

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

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

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

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

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

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

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

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

416 ) 

417 

418 @readonly 

419 def CrossNumPrintMissing(self) -> int: 

420 """ 

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

422 

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

424 """ 

425 return self._crossNumPrintMissing 

426 

427 @readonly 

428 def PerInstance(self) -> bool: 

429 """ 

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

431 

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

433 """ 

434 return self._perInstance 

435 

436 @readonly 

437 def MergeInstances(self) -> bool: 

438 """ 

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

440 

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

442 """ 

443 return self._mergeInstances