Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Toggles.py: 77%

188 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 toggle coverage of the UCIS XML interchange format: an instance's ``<toggleCoverage>``, its metric modes, and its 

33signals - toggle objects - with their dimensions. 

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 

42from pyTooling.MetaClasses import ExtendedType 

43 

44from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, MetricCoverage, ObjectAttributesMixin, StatementID, \ 

45 UserAttribute 

46from pyEDAA.Reports.CodeCoverage.UCIS.ToggleBits import ToggleBit 

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.Instances import InstanceCoverage 

50 

51 

52@export 

53class MetricMode(Base): 

54 """ 

55 A ``<metricMode>`` of a toggle coverage: a mode, in which the coverage was measured, and its user-defined attributes. 

56 """ 

57 

58 _parent: Nullable[ToggleCoverage] #: The toggle coverage the metric mode belongs to. 

59 _metricMode: str #: Name of the mode, e.g. ``2STOGGLE``. 

60 

61 def __init__( 

62 self, 

63 metricMode: str, 

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

65 *, 

66 parent: Nullable[ToggleCoverage] = None 

67 ) -> None: 

68 """ 

69 Initialize the metric mode, and append it to the metric modes of its toggle coverage. 

70 

71 :param metricMode: Name of the mode, e.g. ``2STOGGLE``. 

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

73 :param parent: Optional, the toggle coverage the metric mode belongs to. Default: ``None``. 

74 :raises ValueError: If parameter ``metricMode`` is ``None``. 

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

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

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

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

79 :raises TypeError: If parameter ``parent`` isn't of type :class:`ToggleCoverage`. 

80 """ 

81 super().__init__(userAttributes) 

82 

83 if metricMode is None: 83 ↛ 84line 83 didn't jump to line 84 because the condition on line 83 was never true

84 raise ValueError("Parameter 'metricMode' is None.") 

85 elif not isinstance(metricMode, str): 85 ↛ 86line 85 didn't jump to line 86 because the condition on line 85 was never true

86 ex = TypeError("Parameter 'metricMode' is not of type 'str'.") 

87 ex.add_note(f"Got type '{getFullyQualifiedName(metricMode)}'.") 

88 raise ex 

89 

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

91 ex = TypeError("Parameter 'parent' is not of type 'ToggleCoverage'.") 

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

93 raise ex 

94 

95 self._parent = parent 

96 self._metricMode = metricMode 

97 

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

99 parent._metricModes.append(self) 

100 

101 @classmethod 

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

103 """ 

104 Parse a metric mode from its ``<metricMode>`` element. 

105 

106 :param element: The ``<metricMode>`` element. 

107 :param parent: Optional, the toggle coverage the metric mode belongs to. Default: ``None``. 

108 :returns: The metric mode. 

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

110 """ 

111 return cls(element.attrib["metricMode"], cls._ParseUserAttributes(element), parent=parent) 

112 

113 @readonly 

114 def Parent(self) -> Nullable[ToggleCoverage]: 

115 """ 

116 Read-only property to access the toggle coverage the metric mode belongs to (:attr:`_parent`). 

117 

118 :returns: The toggle coverage; ``None``, if the metric mode belongs to none. 

119 """ 

120 return self._parent 

121 

122 @readonly 

123 def MetricMode(self) -> str: 

124 """ 

125 Read-only property to access the name of the mode (:attr:`_metricMode`). 

126 

127 :returns: The name, e.g. ``2STOGGLE``. 

128 """ 

129 return self._metricMode 

130 

131 

132@export 

133class ToggleCoverage(MetricCoverage): 

134 """ 

135 A ``<toggleCoverage>`` of an instance: its toggle objects and its metric modes. 

136 """ 

137 

138 _objects: list[ToggleObject] #: The toggle objects. 

139 _metricModes: list[MetricMode] #: The metric modes. 

140 

141 def __init__( 

142 self, 

143 metricMode: Nullable[str] = None, 

144 weight: int = 1, 

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

146 *, 

147 parent: Nullable[InstanceCoverage] = None 

148 ) -> None: 

149 """ 

150 Initialize the toggle coverage, and append it to the toggle coverages of its instance. 

151 

152 Its toggle objects and metric modes are added by creating them with this coverage as their parent. 

153 

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

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

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

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

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

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

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

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

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

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

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

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

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

167 """ 

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

169 

170 self._objects = [] 

171 self._metricModes = [] 

172 

173 if parent is not None: 

174 parent._toggleCoverages.append(self) 

175 

176 @classmethod 

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

178 """ 

179 Parse a toggle coverage, its toggle objects and metric modes from its ``<toggleCoverage>`` element. 

180 

181 :param element: The ``<toggleCoverage>`` element. 

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

183 :returns: The toggle coverage. 

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

185 """ 

186 toggleCoverage = cls( 

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

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

189 cls._ParseUserAttributes(element), 

190 parent=parent 

191 ) 

192 

193 for objectElement in element.iterfind("{*}toggleObject"): 

194 ToggleObject.Parse(objectElement, parent=toggleCoverage) 

195 

196 for modeElement in element.iterfind("{*}metricMode"): 

197 MetricMode.Parse(modeElement, parent=toggleCoverage) 

198 

199 return toggleCoverage 

200 

201 @readonly 

202 def Objects(self) -> list[ToggleObject]: 

203 """ 

204 Read-only property to access the toggle objects (:attr:`_objects`). 

205 

206 :returns: The toggle objects, in the order the report lists them. 

207 """ 

208 return self._objects 

209 

210 @readonly 

211 def MetricModes(self) -> list[MetricMode]: 

212 """ 

213 Read-only property to access the metric modes (:attr:`_metricModes`). 

214 

215 :returns: The metric modes, in the order the report lists them. 

216 """ 

217 return self._metricModes 

218 

219 

220@export 

221class Dimension(metaclass=ExtendedType, slots=True): 

222 """ 

223 A ``<dimension>`` of a toggle object: its left and right index, and whether the index descends. 

224 

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

226 """ 

227 

228 _left: int #: The left index. 

229 _right: int #: The right index. 

230 _downTo: bool #: Whether the index descends from left to right. 

231 

232 def __init__(self, left: int, right: int, downTo: bool) -> None: 

233 """ 

234 Initialize the dimension. 

235 

236 :param left: The left index. 

237 :param right: The right index. 

238 :param downTo: Whether the index descends from left to right, e.g. for ``[3:0]``. 

239 :raises ValueError: If parameter ``left`` is ``None``. 

240 :raises TypeError: If parameter ``left`` isn't of type :class:`int`. 

241 :raises ValueError: If parameter ``right`` is ``None``. 

242 :raises TypeError: If parameter ``right`` isn't of type :class:`int`. 

243 :raises ValueError: If parameter ``downTo`` is ``None``. 

244 :raises TypeError: If parameter ``downTo`` isn't of type :class:`bool`. 

245 """ 

246 if left is None: 246 ↛ 247line 246 didn't jump to line 247 because the condition on line 246 was never true

247 raise ValueError("Parameter 'left' is None.") 

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

249 ex = TypeError("Parameter 'left' is not of type 'int'.") 

250 ex.add_note(f"Got type '{getFullyQualifiedName(left)}'.") 

251 raise ex 

252 

253 if right is None: 253 ↛ 254line 253 didn't jump to line 254 because the condition on line 253 was never true

254 raise ValueError("Parameter 'right' is None.") 

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

256 ex = TypeError("Parameter 'right' is not of type 'int'.") 

257 ex.add_note(f"Got type '{getFullyQualifiedName(right)}'.") 

258 raise ex 

259 

260 if downTo is None: 260 ↛ 261line 260 didn't jump to line 261 because the condition on line 260 was never true

261 raise ValueError("Parameter 'downTo' is None.") 

262 elif not isinstance(downTo, bool): 

263 ex = TypeError("Parameter 'downTo' is not of type 'bool'.") 

264 ex.add_note(f"Got type '{getFullyQualifiedName(downTo)}'.") 

265 raise ex 

266 

267 self._left = left 

268 self._right = right 

269 self._downTo = downTo 

270 

271 @classmethod 

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

273 """ 

274 Parse a dimension from its ``<dimension>`` element. 

275 

276 :param element: The ``<dimension>`` element. 

277 :returns: The dimension. 

278 """ 

279 return cls( 

280 int(element.attrib["left"]), 

281 int(element.attrib["right"]), 

282 Base._ParseBoolean(element.attrib["downto"]) 

283 ) 

284 

285 @readonly 

286 def Left(self) -> int: 

287 """ 

288 Read-only property to access the left index (:attr:`_left`). 

289 

290 :returns: The left index. 

291 """ 

292 return self._left 

293 

294 @readonly 

295 def Right(self) -> int: 

296 """ 

297 Read-only property to access the right index (:attr:`_right`). 

298 

299 :returns: The right index. 

300 """ 

301 return self._right 

302 

303 @readonly 

304 def DownTo(self) -> bool: 

305 """ 

306 Read-only property to access whether the index descends from left to right (:attr:`_downTo`). 

307 

308 :returns: ``True``, if the index descends. 

309 """ 

310 return self._downTo 

311 

312 

313@export 

314class ToggleObject(Base, ObjectAttributesMixin): 

315 """ 

316 A ``<toggleObject>`` of a toggle coverage: a signal, its type, dimensions and bits, and where it is declared. 

317 """ 

318 

319 _parent: Nullable[ToggleCoverage] #: The toggle coverage the toggle object belongs to. 

320 _name: str #: Name of the signal. 

321 _key: str #: UCIS key of the toggle object. 

322 _id: StatementID #: Where the signal is declared. 

323 _type: Nullable[str] #: Declared type of the signal. 

324 _portDirection: Nullable[str] #: Direction of the signal, if it is a port. 

325 _dimensions: list[Dimension] #: The dimensions of the signal. 

326 _bits: list[ToggleBit] #: The bits of the signal. 

327 

328 def __init__( 

329 self, 

330 name: str, 

331 key: str, 

332 statementID: StatementID, 

333 objectType: Nullable[str] = None, 

334 portDirection: Nullable[str] = None, 

335 dimensions: Nullable[Iterable[Dimension]] = None, 

336 alias: Nullable[str] = None, 

337 excluded: bool = False, 

338 excludedReason: Nullable[str] = None, 

339 weight: int = 1, 

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

341 *, 

342 parent: Nullable[ToggleCoverage] = None 

343 ) -> None: 

344 """ 

345 Initialize the toggle object, and append it to the toggle objects of its toggle coverage. 

346 

347 Its bits are added by creating them with this toggle object as their parent. 

348 

349 :param name: Name of the signal, e.g. ``ff1``. 

350 :param key: UCIS key of the toggle object. 

351 :param statementID: Where the signal is declared. 

352 :param objectType: Optional, declared type of the signal, e.g. ``wire``. Default: ``None``. 

353 :param portDirection: Optional, direction of the signal, if it is a port, e.g. ``input``. Default: ``None``. 

354 :param dimensions: Optional, the dimensions of the signal. Default: ``None``. 

355 :param alias: Optional, an alias of the signal's name. Default: ``None``. 

356 :param excluded: Optional, whether the signal is excluded from the coverage. Default: ``False``. 

357 :param excludedReason: Optional, why the signal is excluded. Default: ``None``. 

358 :param weight: Optional, weight of the signal in computing the coverage. Default: ``1``. 

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

360 :param parent: Optional, the toggle coverage the toggle object belongs to. Default: ``None``. 

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

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

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

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

365 :raises ValueError: If parameter ``statementID`` is ``None``. 

366 :raises TypeError: If parameter ``statementID`` isn't of type 

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

368 :raises TypeError: If parameter ``objectType`` isn't of type :class:`str`. 

369 :raises TypeError: If parameter ``portDirection`` isn't of type :class:`str`. 

370 :raises TypeError: If parameter ``dimensions`` isn't iterable. 

371 :raises TypeError: If parameter ``dimensions`` contains an element not of type :class:`Dimension`. 

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

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

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

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

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

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

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

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

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

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

382 :raises TypeError: If parameter ``parent`` isn't of type :class:`ToggleCoverage`. 

383 """ 

384 super().__init__(userAttributes) 

385 ObjectAttributesMixin.__init__(self, alias, excluded, excludedReason, weight) 

386 

387 for parameterName, value in ( 

388 ("name", name), 

389 ("key", key) 

390 ): 

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

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

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

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

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

396 raise ex 

397 

398 if statementID is None: 398 ↛ 399line 398 didn't jump to line 399 because the condition on line 398 was never true

399 raise ValueError("Parameter 'statementID' is None.") 

400 elif not isinstance(statementID, StatementID): 400 ↛ 401line 400 didn't jump to line 401 because the condition on line 400 was never true

401 ex = TypeError("Parameter 'statementID' is not of type 'StatementID'.") 

402 ex.add_note(f"Got type '{getFullyQualifiedName(statementID)}'.") 

403 raise ex 

404 

405 for parameterName, value in ( 

406 ("objectType", objectType), 

407 ("portDirection", portDirection) 

408 ): 

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

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

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

412 raise ex 

413 

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

415 ex = TypeError("Parameter 'parent' is not of type 'ToggleCoverage'.") 

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

417 raise ex 

418 

419 self._parent = parent 

420 self._name = name 

421 self._key = key 

422 self._id = statementID 

423 self._type = objectType 

424 self._portDirection = portDirection 

425 self._dimensions = dimensionList = [] 

426 self._bits = [] 

427 

428 if dimensions is not None: 428 ↛ 442line 428 didn't jump to line 442 because the condition on line 428 was always true

429 if not isinstance(dimensions, Iterable): 429 ↛ 430line 429 didn't jump to line 430 because the condition on line 429 was never true

430 ex = TypeError("Parameter 'dimensions' is not iterable.") 

431 ex.add_note(f"Got type '{getFullyQualifiedName(dimensions)}'.") 

432 raise ex 

433 

434 for dimension in dimensions: 

435 if not isinstance(dimension, Dimension): 435 ↛ 436line 435 didn't jump to line 436 because the condition on line 435 was never true

436 ex = TypeError("Parameter 'dimensions' contains an element not of type 'Dimension'.") 

437 ex.add_note(f"Got type '{getFullyQualifiedName(dimension)}'.") 

438 raise ex 

439 

440 dimensionList.append(dimension) 

441 

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

443 parent._objects.append(self) 

444 

445 @classmethod 

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

447 """ 

448 Parse a toggle object and its bits from its ``<toggleObject>`` element. 

449 

450 :param element: The ``<toggleObject>`` element. 

451 :param parent: Optional, the toggle coverage the toggle object belongs to. Default: ``None``. 

452 :returns: The toggle object. 

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

454 """ 

455 toggleObject = cls( 

456 element.attrib["name"], 

457 element.attrib["key"], 

458 StatementID.Parse(element.find("{*}id")), 

459 element.attrib.get("type"), 

460 element.attrib.get("portDirection"), 

461 [Dimension.Parse(dimensionElement) for dimensionElement in element.iterfind("{*}dimension")], 

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

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

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

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

466 cls._ParseUserAttributes(element), 

467 parent=parent 

468 ) 

469 

470 for bitElement in element.iterfind("{*}toggleBit"): 

471 ToggleBit.Parse(bitElement, parent=toggleObject) 

472 

473 return toggleObject 

474 

475 @readonly 

476 def Parent(self) -> Nullable[ToggleCoverage]: 

477 """ 

478 Read-only property to access the toggle coverage the toggle object belongs to (:attr:`_parent`). 

479 

480 :returns: The toggle coverage; ``None``, if the toggle object belongs to none. 

481 """ 

482 return self._parent 

483 

484 @readonly 

485 def Name(self) -> str: 

486 """ 

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

488 

489 :returns: The name, as declared. 

490 """ 

491 return self._name 

492 

493 @readonly 

494 def Key(self) -> str: 

495 """ 

496 Read-only property to access the UCIS key of the toggle object (:attr:`_key`). 

497 

498 :returns: The key, as stated. 

499 """ 

500 return self._key 

501 

502 @readonly 

503 def ID(self) -> StatementID: 

504 """ 

505 Read-only property to access where the signal is declared (:attr:`_id`). 

506 

507 :returns: The source statement identifier. 

508 """ 

509 return self._id 

510 

511 @readonly 

512 def Type(self) -> Nullable[str]: 

513 """ 

514 Read-only property to access the declared type of the signal (:attr:`_type`). 

515 

516 :returns: The type, e.g. ``wire``; ``None``, if the report states none. 

517 """ 

518 return self._type 

519 

520 @readonly 

521 def PortDirection(self) -> Nullable[str]: 

522 """ 

523 Read-only property to access the direction of the signal, if it is a port (:attr:`_portDirection`). 

524 

525 :returns: The direction, e.g. ``input``; ``None``, if the report states none. 

526 """ 

527 return self._portDirection 

528 

529 @readonly 

530 def Dimensions(self) -> list[Dimension]: 

531 """ 

532 Read-only property to access the dimensions of the signal (:attr:`_dimensions`). 

533 

534 :returns: The dimensions, from the left one. 

535 """ 

536 return self._dimensions 

537 

538 @readonly 

539 def Bits(self) -> list[ToggleBit]: 

540 """ 

541 Read-only property to access the bits of the signal (:attr:`_bits`). 

542 

543 :returns: The bits, in the order the report lists them. 

544 """ 

545 return self._bits