Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Crosses.py: 70%

139 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 crosses of the covergroup coverage of the UCIS XML interchange format: their options and bins, each a combination 

33of the crossed coverpoints' bins. 

34""" 

35from __future__ import annotations 

36 

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

38 

39from lxml.etree import _Element 

40from pyTooling.Common import getFullyQualifiedName 

41from pyTooling.Decorators import export, readonly 

42 

43from pyEDAA.Reports.CodeCoverage.UCIS.Bins import BinContents 

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

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

46from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, UserAttribute 

47 

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

49 from pyEDAA.Reports.CodeCoverage.UCIS.Covergroups import CovergroupInstance 

50 

51 

52@export 

53class Cross(Base): 

54 """ 

55 A ``<cross>`` of a covergroup instance: the expressions - coverpoints - it crosses, its options and bins. 

56 """ 

57 

58 _parent: Nullable[CovergroupInstance] #: The covergroup instance the cross belongs to. 

59 _name: str #: Name of the cross. 

60 _key: str #: UCIS key of the cross. 

61 _options: CrossOptions #: The options of the cross. 

62 _alias: Nullable[str] #: An alias of the cross' name. 

63 _expressions: list[str] #: The crossed expressions, e.g. coverpoints' names. 

64 _bins: list[CrossBin] #: The bins. 

65 

66 def __init__( 

67 self, 

68 name: str, 

69 key: str, 

70 options: CrossOptions, 

71 expressions: Nullable[Iterable[str]] = None, 

72 alias: Nullable[str] = None, 

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

74 *, 

75 parent: Nullable[CovergroupInstance] = None 

76 ) -> None: 

77 """ 

78 Initialize the cross, and append it to the crosses of its covergroup instance. 

79 

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

81 

82 :param name: Name of the cross, e.g. ``xab``. 

83 :param key: UCIS key of the cross. 

84 :param options: The options of the cross. 

85 :param expressions: Optional, the crossed expressions, e.g. ``["cvpt_a", "cvpt_b"]``. Default: ``None``. 

86 :param alias: Optional, an alias of the cross' name. Default: ``None``. 

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

88 :param parent: Optional, the covergroup instance the cross belongs to. Default: ``None``. 

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

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

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

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

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

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

95 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.CoverOptions.CrossOptions`. 

96 :raises TypeError: If parameter ``expressions`` isn't iterable. 

97 :raises TypeError: If parameter ``expressions`` contains an element not of type :class:`str`. 

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

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

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

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

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

103 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Covergroups.CovergroupInstance`. 

104 """ 

105 from pyEDAA.Reports.CodeCoverage.UCIS.Covergroups import CovergroupInstance 

106 

107 super().__init__(userAttributes) 

108 

109 for parameterName, value in ( 

110 ("name", name), 

111 ("key", key) 

112 ): 

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

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

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

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

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

118 raise ex 

119 

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

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

122 elif not isinstance(options, CrossOptions): 122 ↛ 123line 122 didn't jump to line 123 because the condition on line 122 was never true

123 ex = TypeError("Parameter 'options' is not of type 'CrossOptions'.") 

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

125 raise ex 

126 

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

128 ex = TypeError("Parameter 'alias' is not of type 'str'.") 

129 ex.add_note(f"Got type '{getFullyQualifiedName(alias)}'.") 

130 raise ex 

131 

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

133 ex = TypeError("Parameter 'parent' is not of type 'CovergroupInstance'.") 

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

135 raise ex 

136 

137 self._parent = parent 

138 self._name = name 

139 self._key = key 

140 self._options = options 

141 self._alias = alias 

142 self._expressions = expressionList = [] 

143 self._bins = [] 

144 

145 if expressions is not None: 145 ↛ 159line 145 didn't jump to line 159 because the condition on line 145 was always true

146 if not isinstance(expressions, Iterable): 146 ↛ 147line 146 didn't jump to line 147 because the condition on line 146 was never true

147 ex = TypeError("Parameter 'expressions' is not iterable.") 

148 ex.add_note(f"Got type '{getFullyQualifiedName(expressions)}'.") 

149 raise ex 

150 

151 for expression in expressions: 

152 if not isinstance(expression, str): 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true

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

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

155 raise ex 

156 

157 expressionList.append(expression) 

158 

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

160 parent._crosses.append(self) 

161 

162 @classmethod 

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

164 """ 

165 Parse a cross, its options and bins from its ``<cross>`` element. 

166 

167 :param element: The ``<cross>`` element. 

168 :param parent: Optional, the covergroup instance the cross belongs to. Default: ``None``. 

169 :returns: The cross. 

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

171 """ 

172 cross = cls( 

173 element.attrib["name"], 

174 element.attrib["key"], 

175 CrossOptions.Parse(element.find("{*}options")), 

176 [(expressionElement.text or "").strip() for expressionElement in element.iterfind("{*}crossExpr")], 

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

178 cls._ParseUserAttributes(element), 

179 parent=parent 

180 ) 

181 

182 for binElement in element.iterfind("{*}crossBin"): 

183 CrossBin.Parse(binElement, parent=cross) 

184 

185 return cross 

186 

187 @readonly 

188 def Parent(self) -> Nullable[CovergroupInstance]: 

189 """ 

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

191 

192 :returns: The covergroup instance; ``None``, if the cross belongs to none. 

193 """ 

194 return self._parent 

195 

196 @readonly 

197 def Name(self) -> str: 

198 """ 

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

200 

201 :returns: The name, as declared. 

202 """ 

203 return self._name 

204 

205 @readonly 

206 def Key(self) -> str: 

207 """ 

208 Read-only property to access the UCIS key of the cross (:attr:`_key`). 

209 

210 :returns: The key, as stated. 

211 """ 

212 return self._key 

213 

214 @readonly 

215 def Options(self) -> CrossOptions: 

216 """ 

217 Read-only property to access the options of the cross (:attr:`_options`). 

218 

219 :returns: The options. 

220 """ 

221 return self._options 

222 

223 @readonly 

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

225 """ 

226 Read-only property to access the alias of the cross' name (:attr:`_alias`). 

227 

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

229 """ 

230 return self._alias 

231 

232 @readonly 

233 def Expressions(self) -> list[str]: 

234 """ 

235 Read-only property to access the crossed expressions (:attr:`_expressions`). 

236 

237 :returns: The expressions, e.g. coverpoints' names, in the order the report lists them. 

238 """ 

239 return self._expressions 

240 

241 @readonly 

242 def Bins(self) -> list[CrossBin]: 

243 """ 

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

245 

246 :returns: The bins, in the order the report lists them. 

247 """ 

248 return self._bins 

249 

250 

251@export 

252class CrossBin(BinBase): 

253 """ 

254 A ``<crossBin>`` of a cross: the indices of the crossed coverpoints' bins it combines, and how often the combination 

255 was hit. 

256 """ 

257 

258 _parent: Nullable[Cross] #: The cross the bin belongs to. 

259 _indices: list[int] #: The indices of the crossed coverpoints' bins, one per coverpoint. 

260 _contents: BinContents #: How often the combination was hit. 

261 

262 def __init__( 

263 self, 

264 indices: Iterable[int], 

265 contents: BinContents, 

266 binType: str = "default", 

267 alias: Nullable[str] = None, 

268 name: Nullable[str] = None, 

269 key: Nullable[str] = None, 

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

271 *, 

272 parent: Nullable[Cross] = None 

273 ) -> None: 

274 """ 

275 Initialize the cross bin, and append it to the bins of its cross. 

276 

277 :param indices: The indices of the crossed coverpoints' bins, one per coverpoint. 

278 :param contents: How often the combination was hit. 

279 :param binType: Optional, kind of the bin, e.g. ``default``, ``ignore`` or ``illegal``. Default: 

280 ``"default"``. 

281 :param alias: Optional, an alias of the bin's name. Default: ``None``. 

282 :param name: Optional, name of the bin, as pyucis writes it. Default: ``None``. 

283 :param key: Optional, UCIS key of the bin, as pyucis writes it. Default: ``None``. 

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

285 :param parent: Optional, the cross the bin belongs to. Default: ``None``. 

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

287 :raises TypeError: If parameter ``indices`` isn't iterable. 

288 :raises TypeError: If parameter ``indices`` contains an element not of type :class:`int`. 

289 :raises ValueError: If parameter ``indices`` is empty. 

290 :raises ValueError: If parameter ``contents`` is ``None``. 

291 :raises TypeError: If parameter ``contents`` isn't of type 

292 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.BinContents`. 

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

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

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

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

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

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

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

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

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

302 """ 

303 super().__init__(binType, alias, name, key, userAttributes) 

304 

305 if contents is None: 305 ↛ 306line 305 didn't jump to line 306 because the condition on line 305 was never true

306 raise ValueError("Parameter 'contents' is None.") 

307 elif not isinstance(contents, BinContents): 307 ↛ 308line 307 didn't jump to line 308 because the condition on line 307 was never true

308 ex = TypeError("Parameter 'contents' is not of type 'BinContents'.") 

309 ex.add_note(f"Got type '{getFullyQualifiedName(contents)}'.") 

310 raise ex 

311 

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

313 ex = TypeError("Parameter 'parent' is not of type 'Cross'.") 

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

315 raise ex 

316 

317 self._parent = parent 

318 self._indices = indexList = [] 

319 self._contents = contents 

320 

321 if indices is None: 321 ↛ 322line 321 didn't jump to line 322 because the condition on line 321 was never true

322 raise ValueError("Parameter 'indices' is None.") 

323 elif not isinstance(indices, Iterable): 323 ↛ 324line 323 didn't jump to line 324 because the condition on line 323 was never true

324 ex = TypeError("Parameter 'indices' is not iterable.") 

325 ex.add_note(f"Got type '{getFullyQualifiedName(indices)}'.") 

326 raise ex 

327 

328 for index in indices: 

329 if not isinstance(index, int) or isinstance(index, bool): 329 ↛ 330line 329 didn't jump to line 330 because the condition on line 329 was never true

330 ex = TypeError("Parameter 'indices' contains an element not of type 'int'.") 

331 ex.add_note(f"Got type '{getFullyQualifiedName(index)}'.") 

332 raise ex 

333 

334 indexList.append(index) 

335 

336 if len(self._indices) == 0: 

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

338 

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

340 parent._bins.append(self) 

341 

342 @classmethod 

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

344 """ 

345 Parse a cross bin from its ``<crossBin>`` element. 

346 

347 :param element: The ``<crossBin>`` element. 

348 :param parent: Optional, the cross the bin belongs to. Default: ``None``. 

349 :returns: The cross bin. 

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

351 """ 

352 return cls( 

353 [int(indexElement.text) for indexElement in element.iterfind("{*}index")], 

354 BinContents.Parse(element.find("{*}contents")), 

355 element.attrib.get("type", "default"), 

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

357 element.attrib.get("name"), 

358 element.attrib.get("key"), 

359 cls._ParseUserAttributes(element), 

360 parent=parent 

361 ) 

362 

363 @readonly 

364 def Parent(self) -> Nullable[Cross]: 

365 """ 

366 Read-only property to access the cross the bin belongs to (:attr:`_parent`). 

367 

368 :returns: The cross; ``None``, if the bin belongs to none. 

369 """ 

370 return self._parent 

371 

372 @readonly 

373 def Indices(self) -> list[int]: 

374 """ 

375 Read-only property to access the indices of the crossed coverpoints' bins (:attr:`_indices`). 

376 

377 :returns: The indices, one per crossed coverpoint. 

378 """ 

379 return self._indices 

380 

381 @readonly 

382 def Contents(self) -> BinContents: 

383 """ 

384 Read-only property to access how often the combination was hit (:attr:`_contents`). 

385 

386 :returns: The contents: the count and the tests. 

387 """ 

388 return self._contents