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

187 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 FSM coverage of the UCIS XML interchange format: an instance's ``<fsmCoverage>``, its finite state machines, their 

33states and transitions. 

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 Bin 

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

45 

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

47 from pyEDAA.Reports.CodeCoverage.UCIS.Instances import InstanceCoverage 

48 

49 

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

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

52_Bin = Bin 

53 

54 

55@export 

56class FSMCoverage(MetricCoverage): 

57 """ 

58 An ``<fsmCoverage>`` of an instance: its finite state machines. 

59 """ 

60 

61 _fsms: list[FSM] #: The finite state machines. 

62 

63 def __init__( 

64 self, 

65 metricMode: Nullable[str] = None, 

66 weight: int = 1, 

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

68 *, 

69 parent: Nullable[InstanceCoverage] = None 

70 ) -> None: 

71 """ 

72 Initialize the FSM coverage, and append it to the FSM coverages of its instance. 

73 

74 Its finite state machines are added by creating them with this coverage as their parent. 

75 

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

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

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

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

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

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

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

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

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

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

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

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

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

89 """ 

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

91 

92 self._fsms = [] 

93 

94 if parent is not None: 

95 parent._fsmCoverages.append(self) 

96 

97 @classmethod 

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

99 """ 

100 Parse an FSM coverage and its finite state machines from its ``<fsmCoverage>`` element. 

101 

102 :param element: The ``<fsmCoverage>`` element. 

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

104 :returns: The FSM coverage. 

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

106 """ 

107 fsmCoverage = cls( 

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

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

110 cls._ParseUserAttributes(element), 

111 parent=parent 

112 ) 

113 

114 for fsmElement in element.iterfind("{*}fsm"): 

115 FSM.Parse(fsmElement, parent=fsmCoverage) 

116 

117 return fsmCoverage 

118 

119 @readonly 

120 def FSMs(self) -> list[FSM]: 

121 """ 

122 Read-only property to access the finite state machines (:attr:`_fsms`). 

123 

124 :returns: The finite state machines, in the order the report lists them. 

125 """ 

126 return self._fsms 

127 

128 

129@export 

130class FSM(Base, ObjectAttributesMixin): 

131 """ 

132 An ``<fsm>`` of an FSM coverage: a finite state machine, the signal holding its state, its states and transitions. 

133 """ 

134 

135 _parent: Nullable[FSMCoverage] #: The FSM coverage the finite state machine belongs to. 

136 _name: Nullable[str] #: Name of the signal holding the state. 

137 _type: Nullable[str] #: Declared type of the signal holding the state. 

138 _width: Nullable[int] #: Bit width of the signal holding the state. 

139 _states: list[FSMState] #: The states. 

140 _transitions: list[FSMTransition] #: The transitions. 

141 

142 def __init__( 

143 self, 

144 name: Nullable[str] = None, 

145 objectType: Nullable[str] = None, 

146 width: Nullable[int] = None, 

147 alias: Nullable[str] = None, 

148 excluded: bool = False, 

149 excludedReason: Nullable[str] = None, 

150 weight: int = 1, 

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

152 *, 

153 parent: Nullable[FSMCoverage] = None 

154 ) -> None: 

155 """ 

156 Initialize the finite state machine, and append it to the finite state machines of its FSM coverage. 

157 

158 Its states and transitions are added by creating them with this finite state machine as their parent. 

159 

160 :param name: Optional, name of the signal holding the state, e.g. ``ctrl``. Default: ``None``. 

161 :param objectType: Optional, declared type of the signal holding the state, e.g. ``reg``. Default: ``None``. 

162 :param width: Optional, bit width of the signal holding the state. Default: ``None``. 

163 :param alias: Optional, an alias of the finite state machine's name. Default: ``None``. 

164 :param excluded: Optional, whether the finite state machine is excluded from the coverage. Default: 

165 ``False``. 

166 :param excludedReason: Optional, why the finite state machine is excluded. Default: ``None``. 

167 :param weight: Optional, weight of the finite state machine in computing the coverage. Default: ``1``. 

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

169 :param parent: Optional, the FSM coverage the finite state machine belongs to. Default: ``None``. 

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

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

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

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

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

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

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

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

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

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

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

181 :raises TypeError: If parameter ``width`` isn't of type :class:`int`. 

182 :raises ValueError: If parameter ``width`` is less than 1. 

183 :raises TypeError: If parameter ``parent`` isn't of type :class:`FSMCoverage`. 

184 """ 

185 super().__init__(userAttributes) 

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

187 

188 for parameterName, value in ( 

189 ("name", name), 

190 ("objectType", objectType) 

191 ): 

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

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

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

195 raise ex 

196 

197 if width is not None: 197 ↛ 207line 197 didn't jump to line 207 because the condition on line 197 was always true

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

199 ex = TypeError("Parameter 'width' is not of type 'int'.") 

200 ex.add_note(f"Got type '{getFullyQualifiedName(width)}'.") 

201 raise ex 

202 elif width < 1: 

203 ex = ValueError("Parameter 'width' is less than 1.") 

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

205 raise ex 

206 

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

208 ex = TypeError("Parameter 'parent' is not of type 'FSMCoverage'.") 

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

210 raise ex 

211 

212 self._parent = parent 

213 self._name = name 

214 self._type = objectType 

215 self._width = width 

216 self._states = [] 

217 self._transitions = [] 

218 

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

220 parent._fsms.append(self) 

221 

222 @classmethod 

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

224 """ 

225 Parse a finite state machine, its states and transitions from its ``<fsm>`` element. 

226 

227 :param element: The ``<fsm>`` element. 

228 :param parent: Optional, the FSM coverage the finite state machine belongs to. Default: ``None``. 

229 :returns: The finite state machine. 

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

231 """ 

232 width = element.attrib.get("width") 

233 fsm = cls( 

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

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

236 None if width is None else int(width), 

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

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

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

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

241 cls._ParseUserAttributes(element), 

242 parent=parent 

243 ) 

244 

245 for stateElement in element.iterfind("{*}state"): 

246 FSMState.Parse(stateElement, parent=fsm) 

247 

248 for transitionElement in element.iterfind("{*}stateTransition"): 

249 FSMTransition.Parse(transitionElement, parent=fsm) 

250 

251 return fsm 

252 

253 @readonly 

254 def Parent(self) -> Nullable[FSMCoverage]: 

255 """ 

256 Read-only property to access the FSM coverage the finite state machine belongs to (:attr:`_parent`). 

257 

258 :returns: The FSM coverage; ``None``, if the finite state machine belongs to none. 

259 """ 

260 return self._parent 

261 

262 @readonly 

263 def Name(self) -> Nullable[str]: 

264 """ 

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

266 

267 :returns: The name, e.g. ``ctrl``; ``None``, if the report states none. 

268 """ 

269 return self._name 

270 

271 @readonly 

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

273 """ 

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

275 

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

277 """ 

278 return self._type 

279 

280 @readonly 

281 def Width(self) -> Nullable[int]: 

282 """ 

283 Read-only property to access the bit width of the signal holding the state (:attr:`_width`). 

284 

285 :returns: The width; ``None``, if the report states none. 

286 """ 

287 return self._width 

288 

289 @readonly 

290 def States(self) -> list[FSMState]: 

291 """ 

292 Read-only property to access the states (:attr:`_states`). 

293 

294 :returns: The states, in the order the report lists them. 

295 """ 

296 return self._states 

297 

298 @readonly 

299 def Transitions(self) -> list[FSMTransition]: 

300 """ 

301 Read-only property to access the transitions (:attr:`_transitions`). 

302 

303 :returns: The transitions, in the order the report lists them. 

304 """ 

305 return self._transitions 

306 

307 

308@export 

309class FSMState(Base): 

310 """ 

311 A ``<state>`` of a finite state machine: its name, value and bin - how often the machine was in it. 

312 """ 

313 

314 _parent: Nullable[FSM] #: The finite state machine the state belongs to. 

315 _name: Nullable[str] #: Name of the state. 

316 _value: Nullable[str] #: Value representing the state. 

317 _bin: _Bin #: How often the machine was in the state. 

318 

319 def __init__( 

320 self, 

321 coverBin: Bin, 

322 name: Nullable[str] = None, 

323 value: Nullable[str] = None, 

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

325 *, 

326 parent: Nullable[FSM] = None 

327 ) -> None: 

328 """ 

329 Initialize the state, become its bin's parent, and append it to the states of its finite state machine. 

330 

331 :param coverBin: How often the machine was in the state. 

332 :param name: Optional, name of the state, e.g. ``IDLE``. Default: ``None``. 

333 :param value: Optional, value representing the state, e.g. ``4``. Default: ``None``. 

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

335 :param parent: Optional, the finite state machine the state belongs to. Default: ``None``. 

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

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

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

339 :raises ValueError: If parameter ``coverBin`` is ``None``. 

340 :raises TypeError: If parameter ``coverBin`` isn't of type :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.Bin`. 

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

342 :raises TypeError: If parameter ``parent`` isn't of type :class:`FSM`. 

343 """ 

344 super().__init__(userAttributes) 

345 

346 if coverBin is None: 346 ↛ 347line 346 didn't jump to line 347 because the condition on line 346 was never true

347 raise ValueError("Parameter 'coverBin' is None.") 

348 elif not isinstance(coverBin, Bin): 348 ↛ 349line 348 didn't jump to line 349 because the condition on line 348 was never true

349 ex = TypeError("Parameter 'coverBin' is not of type 'Bin'.") 

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

351 raise ex 

352 

353 for parameterName, parameterValue in ( 

354 ("name", name), 

355 ("value", value) 

356 ): 

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

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

359 ex.add_note(f"Got type '{getFullyQualifiedName(parameterValue)}'.") 

360 raise ex 

361 

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

363 ex = TypeError("Parameter 'parent' is not of type 'FSM'.") 

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

365 raise ex 

366 

367 self._parent = parent 

368 self._name = name 

369 self._value = value 

370 self._bin = coverBin 

371 coverBin._parent = self 

372 

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

374 parent._states.append(self) 

375 

376 @classmethod 

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

378 """ 

379 Parse a state and its bin from its ``<state>`` element. 

380 

381 :param element: The ``<state>`` element. 

382 :param parent: Optional, the finite state machine the state belongs to. Default: ``None``. 

383 :returns: The state. 

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

385 """ 

386 return cls( 

387 Bin.Parse(element.find("{*}stateBin")), 

388 element.attrib.get("stateName"), 

389 element.attrib.get("stateValue"), 

390 cls._ParseUserAttributes(element), 

391 parent=parent 

392 ) 

393 

394 @readonly 

395 def Parent(self) -> Nullable[FSM]: 

396 """ 

397 Read-only property to access the finite state machine the state belongs to (:attr:`_parent`). 

398 

399 :returns: The finite state machine; ``None``, if the state belongs to none. 

400 """ 

401 return self._parent 

402 

403 @readonly 

404 def Name(self) -> Nullable[str]: 

405 """ 

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

407 

408 :returns: The name, e.g. ``IDLE``; ``None``, if the report states none. 

409 """ 

410 return self._name 

411 

412 @readonly 

413 def Value(self) -> Nullable[str]: 

414 """ 

415 Read-only property to access the value representing the state (:attr:`_value`). 

416 

417 :returns: The value, as stated; ``None``, if the report states none. 

418 """ 

419 return self._value 

420 

421 @readonly 

422 def Bin(self) -> Bin: 

423 """ 

424 Read-only property to access the state's bin (:attr:`_bin`). 

425 

426 :returns: The bin, stating how often the machine was in the state. 

427 """ 

428 return self._bin 

429 

430 

431@export 

432class FSMTransition(Base): 

433 """ 

434 A ``<stateTransition>`` of a finite state machine: the states it passes, and its bin - how often it happened. 

435 """ 

436 

437 _parent: Nullable[FSM] #: The finite state machine the transition belongs to. 

438 _states: list[str] #: The states the transition passes, from the first one. 

439 _bin: _Bin #: How often the transition happened. 

440 

441 def __init__( 

442 self, 

443 states: Iterable[str], 

444 coverBin: Bin, 

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

446 *, 

447 parent: Nullable[FSM] = None 

448 ) -> None: 

449 """ 

450 Initialize the transition, become its bin's parent, and append it to the transitions of its finite state machine. 

451 

452 :param states: The states the transition passes, from the first one, e.g. ``["IDLE", "ACTIVE"]``. 

453 :param coverBin: How often the transition happened. 

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

455 :param parent: Optional, the finite state machine the transition belongs to. Default: ``None``. 

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

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

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

459 :raises ValueError: If parameter ``coverBin`` is ``None``. 

460 :raises TypeError: If parameter ``coverBin`` isn't of type :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.Bin`. 

461 :raises TypeError: If parameter ``parent`` isn't of type :class:`FSM`. 

462 :raises ValueError: If parameter ``states`` is ``None``. 

463 :raises TypeError: If parameter ``states`` isn't iterable. 

464 :raises TypeError: If parameter ``states`` contains an element not of type :class:`str`. 

465 :raises ValueError: If parameter ``states`` has less than two states. 

466 """ 

467 super().__init__(userAttributes) 

468 

469 if coverBin is None: 469 ↛ 470line 469 didn't jump to line 470 because the condition on line 469 was never true

470 raise ValueError("Parameter 'coverBin' is None.") 

471 elif not isinstance(coverBin, Bin): 471 ↛ 472line 471 didn't jump to line 472 because the condition on line 471 was never true

472 ex = TypeError("Parameter 'coverBin' is not of type 'Bin'.") 

473 ex.add_note(f"Got type '{getFullyQualifiedName(coverBin)}'.") 

474 raise ex 

475 

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

477 ex = TypeError("Parameter 'parent' is not of type 'FSM'.") 

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

479 raise ex 

480 

481 self._parent = parent 

482 self._states = [] 

483 self._bin = coverBin 

484 coverBin._parent = self 

485 

486 if states is None: 486 ↛ 487line 486 didn't jump to line 487 because the condition on line 486 was never true

487 raise ValueError("Parameter 'states' is None.") 

488 elif not isinstance(states, Iterable): 488 ↛ 489line 488 didn't jump to line 489 because the condition on line 488 was never true

489 ex = TypeError("Parameter 'states' is not iterable.") 

490 ex.add_note(f"Got type '{getFullyQualifiedName(states)}'.") 

491 raise ex 

492 

493 for state in states: 

494 if not isinstance(state, str): 494 ↛ 495line 494 didn't jump to line 495 because the condition on line 494 was never true

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

496 ex.add_note(f"Got type '{getFullyQualifiedName(state)}'.") 

497 raise ex 

498 

499 self._states.append(state) 

500 

501 if len(self._states) < 2: 

502 ex = ValueError("Parameter 'states' has less than two states.") 

503 ex.add_note(f"Got {len(self._states)} state(s).") 

504 raise ex 

505 

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

507 parent._transitions.append(self) 

508 

509 @classmethod 

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

511 """ 

512 Parse a transition and its bin from its ``<stateTransition>`` element. 

513 

514 :param element: The ``<stateTransition>`` element. 

515 :param parent: Optional, the finite state machine the transition belongs to. Default: ``None``. 

516 :returns: The transition. 

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

518 """ 

519 return cls( 

520 [stateElement.text or "" for stateElement in element.iterfind("{*}state")], 

521 Bin.Parse(element.find("{*}transitionBin")), 

522 cls._ParseUserAttributes(element), 

523 parent=parent 

524 ) 

525 

526 @readonly 

527 def Parent(self) -> Nullable[FSM]: 

528 """ 

529 Read-only property to access the finite state machine the transition belongs to (:attr:`_parent`). 

530 

531 :returns: The finite state machine; ``None``, if the transition belongs to none. 

532 """ 

533 return self._parent 

534 

535 @readonly 

536 def States(self) -> list[str]: 

537 """ 

538 Read-only property to access the states the transition passes (:attr:`_states`). 

539 

540 :returns: The states, from the first one, as the report states them. 

541 """ 

542 return self._states 

543 

544 @readonly 

545 def Bin(self) -> Bin: 

546 """ 

547 Read-only property to access the transition's bin (:attr:`_bin`). 

548 

549 :returns: The bin, stating how often the transition happened. 

550 """ 

551 return self._bin