Coverage for pyEDAA/OutputFilter/Xilinx/__init__.py: 80%

2160 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-26 23:00 +0000

1# ==================================================================================================================== # 

2# _____ ____ _ _ ___ _ _ _____ _ _ _ # 

3# _ __ _ _| ____| _ \ / \ / \ / _ \ _ _| |_ _ __ _ _| |_| ___(_) | |_ ___ _ __ # 

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | | __| '_ \| | | | __| |_ | | | __/ _ \ '__| # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| | |_| | |_| |_) | |_| | |_| _| | | | || __/ | # 

6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/ \__,_|\__| .__/ \__,_|\__|_| |_|_|\__\___|_| # 

7# |_| |___/ |_| # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2025-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"""Basic classes for outputs from AMD/Xilinx Vivado.""" 

32from datetime import datetime 

33from enum import Flag 

34from pathlib import Path 

35from re import Pattern, compile as re_compile 

36from typing import Optional as Nullable, Self, Type, ClassVar, Tuple, List, Dict, Generator, Union, Any, Iterator 

37 

38from pyTooling.Decorators import export, readonly 

39from pyTooling.MetaClasses import ExtendedType, abstractmethod 

40from pyTooling.Common import getFullyQualifiedName 

41from pyTooling.Stopwatch import Stopwatch 

42from pyTooling.Versioning import YearReleaseVersion 

43from pyTooling.Warning import WarningCollector, CriticalWarning 

44 

45from pyEDAA.OutputFilter import Line, OutputFilterException 

46from pyEDAA.OutputFilter import InfoMessage, WarningMessage, CriticalWarningMessage, ErrorMessage 

47 

48 

49__all__ = ["MAJOR", "MAJOR_MINOR", "MAJOR_MINOR_MICRO", "MAJOR_MINOR_MICRO_NANO"] 

50 

51MAJOR = r"(?P<major>\d+)" 

52MAJOR_MINOR = r"(?P<major>\d+)\.(?P<minor>\d+)" 

53MAJOR_MINOR_MICRO = r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<micro>\d+)" 

54MAJOR_MINOR_MICRO_NANO = r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<micro>\d+)\.(?P<nano>\d+)" 

55 

56 

57@export 

58def timestampIterator(iterator: Iterator[str], timestamp: datetime) -> Iterator[Tuple[datetime, str]]: 

59 for line in iterator: 

60 yield timestamp, line 

61 

62 

63@export 

64class ProcessorException(OutputFilterException): 

65 """ 

66 Base-class for exceptions raised by processors parsing log outputs. 

67 """ 

68 

69 

70@export 

71class ClassificationException(ProcessorException): 

72 """ 

73 Raised if a log output line couldn't be classified. 

74 """ 

75 _lineNumber: int #: Line number of the unclassified line. 

76 _rawMessage: str #: Raw message of the unclassified line. 

77 

78 def __init__(self, errorMessage: str, lineNumber: int, rawMessageLine: str) -> None: 

79 """ 

80 Initializes a classification exception. 

81 

82 :param errorMessage: Error message why the line couldn't be classified. 

83 :param lineNumber: Line number of the unclassified line. 

84 :param rawMessageLine: Raw message of the unclassified line. 

85 """ 

86 super().__init__(errorMessage) 

87 

88 self._lineNumber = lineNumber 

89 self._rawMessage = rawMessageLine 

90 

91 def __str__(self) -> str: 

92 return f"{self.message}: {self._rawMessage} (at line {self._lineNumber})" 

93 

94 

95@export 

96class ParserStateException(ProcessorException): 

97 """ 

98 Raised if a log output parser has a broken state. 

99 """ 

100 

101 

102@export 

103class NotPresentException(ProcessorException): 

104 pass 

105 

106 

107@export 

108class CommandNotPresentException(NotPresentException): 

109 pass 

110 

111 

112@export 

113class SectionNotPresentException(NotPresentException): 

114 pass 

115 

116 

117@export 

118class SubSectionNotPresentException(NotPresentException): 

119 pass 

120 

121 

122@export 

123class SubTaskNotPresentException(NotPresentException): 

124 pass 

125 

126 

127@export 

128class PhaseNotPresentException(NotPresentException): 

129 pass 

130 

131 

132@export 

133class NestedTaskNotPresentException(NotPresentException): 

134 pass 

135 

136 

137@export 

138class UndetectedEnd(CriticalWarning): 

139 _line: "VivadoLine" 

140 

141 def __init__(self, message: str, line: "VivadoLine") -> None: 

142 super().__init__(message) 

143 

144 self._line = line 

145 

146 @readonly 

147 def Line(self) -> "VivadoLine": 

148 return self._line 

149 

150 

151@export 

152class UnknownLine(Warning): 

153 _line: "VivadoLine" 

154 

155 def __init__(self, message: str, line: "VivadoLine") -> None: 

156 super().__init__(message) 

157 

158 self._line = line 

159 

160 @readonly 

161 def Line(self) -> "VivadoLine": 

162 return self._line 

163 

164 

165@export 

166class UnknownTask(UnknownLine): 

167 pass 

168 

169 

170@export 

171class UnknownSubTask(UnknownLine): 

172 pass 

173 

174 

175@export 

176class UnknownSection(UnknownLine): 

177 pass 

178 

179 

180@export 

181class UnknownPhase(UnknownLine): 

182 pass 

183 

184 

185@export 

186class UnknownSubPhase(UnknownLine): 

187 pass 

188 

189 

190@export 

191class LineKind(Flag): 

192 """ 

193 Classification of a log message line. 

194 """ 

195 Unprocessed = 0 

196 ProcessorError = 2** 0 

197 Empty = 2** 1 

198 Delimiter = 2** 2 

199 

200 Success = 2** 3 

201 Failed = 2** 4 

202 

203 Verbose = 2**10 

204 Normal = 2**11 

205 Info = 2**12 

206 Warning = 2**13 

207 CriticalWarning = 2**14 

208 Error = 2**15 

209 Fatal = 2**16 

210 

211 Start = 2**20 

212 End = 2**21 

213 Header = 2**22 

214 Content = 2**23 

215 Time = 2**24 

216 Footer = 2**25 

217 

218 Last = 2**29 

219 

220 Message = 2**30 

221 InfoMessage = Message | Info 

222 WarningMessage = Message | Warning 

223 CriticalWarningMessage = Message | CriticalWarning 

224 ErrorMessage = Message | Error 

225 

226 Task = 2**31 

227 TaskStart = Task | Start 

228 TaskEnd = Task | End 

229 TaskTime = Task | Time 

230 

231 Phase = 2**32 

232 PhaseDelimiter = Phase | Delimiter 

233 PhaseStart = Phase | Start 

234 PhaseEnd = Phase | End 

235 PhaseTime = Phase | Time 

236 PhaseFinal = Phase | Footer 

237 

238 SubPhase = 2**33 

239 SubPhaseStart = SubPhase | Start 

240 SubPhaseEnd = SubPhase | End 

241 SubPhaseTime = SubPhase | Time 

242 

243 SubSubPhase = 2**34 

244 SubSubPhaseStart = SubSubPhase | Start 

245 SubSubPhaseEnd = SubSubPhase | End 

246 SubSubPhaseTime = SubSubPhase | Time 

247 

248 SubSubSubPhase = 2**35 

249 SubSubSubPhaseStart = SubSubSubPhase | Start 

250 SubSubSubPhaseEnd = SubSubSubPhase | End 

251 SubSubSubPhaseTime = SubSubSubPhase | Time 

252 

253 NestedTask = 2**36 

254 NestedTaskStart = NestedTask | Start 

255 NestedTaskEnd = NestedTask | End 

256 

257 NestedPhase = 2**37 

258 NestedPhaseStart = NestedPhase | Start 

259 NestedPhaseEnd = NestedPhase | End 

260 

261 Section = 2**38 

262 SectionDelimiter = Section | Delimiter 

263 SectionStart = Section | Start 

264 SectionEnd = Section | End 

265 

266 SubSection = 2**39 

267 SubSectionDelimiter = SubSection | Delimiter 

268 SubSectionStart = SubSection | Start 

269 SubSectionEnd = SubSection | End 

270 

271 Paragraph = 2**40 

272 ParagraphHeadline = Paragraph | Header 

273 

274 Hierarchy = 2**41 

275 HierarchyStart = Hierarchy | Start 

276 HierarchyEnd = Hierarchy | End 

277 

278 XDC = 2**42 

279 XDCStart = XDC | Start 

280 XDCEnd = XDC | End 

281 

282 Table = 2**43 

283 TableFrame = Table | Delimiter 

284 TableHeader = Table | Header 

285 TableRow = Table | Content 

286 TableFooter = Table | Footer 

287 

288 TclCommand = 2**44 

289 GenericTclCommand = TclCommand | 2**0 

290 VivadoTclCommand = TclCommand | 2**1 

291 

292 

293@export 

294class LineAction(Flag): 

295 Default = 0 

296 Remove = 1 

297 

298 

299@export 

300class VivadoLine(Line[LineKind, LineAction]): 

301 """ 

302 This class represents any line in a log file. 

303 

304 A line has a line number (:attr:`_lineNumber`), a message (:attr:`__message`) and a message kind (:attr:`__kind`). In 

305 addition, all line objects in a log file form a doubly 

306 linked list. 

307 """ 

308 _processor: "Processor" 

309 _command: "Nullable[Command]" 

310 

311 def __init__( 

312 self, 

313 lineNumber: int, 

314 kind: LineKind, 

315 action: LineAction, 

316 message: str, 

317 previousLine: Nullable["VivadoLine"] = None 

318 ) -> None: 

319 super().__init__(lineNumber, kind, action, message, previousLine) 

320 

321 self._processor = None 

322 self._command = None 

323 

324 @readonly 

325 def Processor(self) -> "Processor": 

326 return self._processor 

327 

328 @readonly 

329 def Command(self) -> "Nullable[Command]": 

330 return self._command 

331 

332 @classmethod 

333 def Copy(cls, line: "VivadoLine", previousLine: "VivadoLine") -> "VivadoLine": 

334 newLine = cls(line._lineNumber, line._kind, line._action, line._message, previousLine) 

335 newLine._timestamp = line._timestamp 

336 return newLine 

337 

338 

339@export 

340class VivadoMessage(VivadoLine): 

341 """ 

342 This class represents an AMD/Xilinx Vivado message. 

343 

344 The usual message format is: 

345 

346 .. code-block:: text 

347 

348 INFO: [Synth 8-7079] Multithreading enabled for synth_design using a maximum of 2 processes. 

349 WARNING: [Synth 8-3332] Sequential element (gen[0].Sync/FF2) is unused and will be removed from module sync_Bits_Xilinx. 

350 

351 The following message severities are defined: 

352 

353 * ``INFO`` 

354 * ``WARNING`` 

355 * ``CRITICAL WARNING`` 

356 * ``ERROR`` 

357 

358 .. seealso:: 

359 

360 :class:`VivadoInfoMessage` 

361 Representing a Vivado info message. 

362 

363 :class:`VivadoWarningMessage` 

364 Representing a Vivado warning message. 

365 

366 :class:`VivadoCriticalWarningMessage` 

367 Representing a Vivado critical warning message. 

368 

369 :class:`VivadoErrorMessage` 

370 Representing a Vivado error message. 

371 """ 

372 # _MESSAGE_KIND: ClassVar[str] 

373 # _REGEXP: ClassVar[Pattern] 

374 

375 _toolName: Nullable[str] 

376 _toolID: Nullable[int] 

377 _messageKindID: Nullable[int] 

378 

379 def __init__( 

380 self, 

381 lineNumber: int, 

382 kind: LineKind, 

383 action: LineAction, 

384 message: str, 

385 toolName: Nullable[str] = None, 

386 toolID: Nullable[int] = None, 

387 messageKindID: Nullable[int] = None, 

388 previousLine: Nullable[VivadoLine] = None 

389 ) -> None: 

390 super().__init__(lineNumber, kind, action, message, previousLine) 

391 self._toolName = toolName 

392 self._toolID = toolID 

393 self._messageKindID = messageKindID 

394 

395 @readonly 

396 def ToolName(self) -> Nullable[str]: 

397 return self._toolName 

398 

399 @readonly 

400 def ToolID(self) -> Nullable[int]: 

401 return self._toolID 

402 

403 @readonly 

404 def MessageKindID(self) -> Nullable[int]: 

405 return self._messageKindID 

406 

407 @classmethod 

408 def Parse(cls, lineNumber: int, kind: LineKind, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

409 if (match := cls._REGEXP.match(rawMessage)) is not None: 

410 return cls(lineNumber, kind, LineAction.Default, match[4], match[1], int(match[2]), int(match[3]), previousLine) 

411 

412 return None 

413 

414 @classmethod 

415 def Copy(cls, line: "VivadoMessage", previousLine: "VivadoLine") -> "VivadoMessage": 

416 newLine = cls(line._lineNumber, line._kind, line._action, line._message, line._toolName, line._toolID, line._messageKindID, previousLine) 

417 newLine._timestamp = line._timestamp 

418 return newLine 

419 

420 def __str__(self) -> str: 

421 return f"{self._MESSAGE_KIND}: [{self._toolName} {self._toolID}-{self._messageKindID}] {self._message}" 

422 

423 

424@export 

425class VivadoInfoMessage(VivadoMessage, InfoMessage): 

426 """ 

427 This class represents an AMD/Xilinx Vivado info message. 

428 

429 .. rubric:: Example 

430 

431 .. code-block:: 

432 

433 INFO: [Common 17-83] 66-Releasing license: Synthesis 

434 """ 

435 

436 _MESSAGE_KIND: ClassVar[str] = "INFO" 

437 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+) (\d+)-(\d+)\] (.*)""") 

438 

439 @classmethod 

440 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

441 return super().Parse(lineNumber, LineKind.InfoMessage, rawMessage, previousLine) 

442 

443 @classmethod 

444 def FromMessage(cls, line: VivadoMessage) -> Self: 

445 message = cls( 

446 line._lineNumber, 

447 LineKind.InfoMessage, 

448 line._message, 

449 line._toolName, 

450 line._toolID, 

451 line._messageKindID, 

452 previousLine=line._previousLine) 

453 message._nextLine = line._nextLine 

454 line._nextLine._previousLine = message 

455 

456 return message 

457 

458 

459@export 

460class VivadoDRCInfoMessage(VivadoMessage, InfoMessage): 

461 """ 

462 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) info message. 

463 

464 .. rubric:: Example 

465 

466 .. code-block:: 

467 

468 INFO: [DRC AVAL-4] enum_USE_DPORT_FALSE_enum_DREG_ADREG_0_connects_CED_CEAD_RSTD_GND: i_system/xbip_dsp48_macro_0/U0/i_synth/i_synth_option.i_synth_model/opt_7series.i_uniwrap/i_primitive: DSP48E1 is not using the D port (USE_DPORT = FALSE). For improved power characteristics, set DREG and ADREG to '1', tie CED, CEAD, and RSTD to logic '0'. 

469 """ 

470 

471 _MESSAGE_KIND: ClassVar[str] = "INFO" 

472 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[DRC (\w+)-(\d+)\] (.*)""") 

473 

474 _drcRuleName: str 

475 

476 def __init__( 

477 self, 

478 lineNumber: int, 

479 kind: LineKind, 

480 action: LineAction, 

481 drcRuleName: str, 

482 message: str, 

483 toolName: Nullable[str] = None, 

484 toolID: Nullable[int] = None, 

485 messageKindID: Nullable[int] = None, 

486 previousLine: Nullable[VivadoLine] = None 

487 ) -> None: 

488 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine) 

489 

490 self._drcRuleName = drcRuleName 

491 

492 @readonly 

493 def DRCRuleName(self) -> str: 

494 return self._drcRuleName 

495 

496 @classmethod 

497 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

498 if (match := cls._REGEXP.match(rawMessage)) is not None: 

499 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[3], toolName="DRC", toolID=None, 

500 messageKindID=int(match[2]), previousLine=previousLine) 

501 

502 return None 

503 

504 @classmethod 

505 def Copy(cls, line: "VivadoDRCInfoMessage", previousLine: "VivadoLine") -> "VivadoDRCInfoMessage": 

506 newLine = cls(line._lineNumber, line._kind, line._action, line._drcRuleName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine) 

507 newLine._timestamp = line._timestamp 

508 return newLine 

509 

510 def __str__(self) -> str: 

511 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}" 

512 

513 

514@export 

515class VivadoIrregularInfoMessage(VivadoMessage, InfoMessage): 

516 """ 

517 This class represents an irregular AMD/Xilinx Vivado info message. 

518 

519 .. rubric:: Example 

520 

521 .. code-block:: 

522 

523 INFO: [runtcl-4] Executing : report_io -file system_top_io_placed.rpt 

524 """ 

525 

526 _MESSAGE_KIND: ClassVar[str] = "INFO" 

527 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+)-(\d+)\] (.*)""") 

528 

529 @classmethod 

530 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

531 if (match := cls._REGEXP.match(rawMessage)) is not None: 

532 return cls(lineNumber, LineKind.InfoMessage, LineAction.Default, match[3], toolName=match[1], messageKindID=int(match[2]), previousLine=previousLine) 

533 

534 return None 

535 

536 def __str__(self) -> str: 

537 return f"{self._MESSAGE_KIND}: [{self._toolName}-{self._messageKindID}] {self._message}" 

538 

539 

540@export 

541class VivadoStuntedInfoMessage(VivadoMessage, InfoMessage): 

542 """ 

543 This class represents a stunted AMD/Xilinx Vivado info message. 

544 

545 .. rubric:: Example 

546 

547 .. code-block:: 

548 

549 INFO: Helper process launched with PID 29056 

550 """ 

551 

552 _MESSAGE_KIND: ClassVar[str] = "INFO" 

553 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: ([^\[].*)""") 

554 

555 @classmethod 

556 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

557 if (match := cls._REGEXP.match(rawMessage)) is not None: 557 ↛ 560line 557 didn't jump to line 560 because the condition on line 557 was always true

558 return cls(lineNumber, LineKind.InfoMessage, LineAction.Default, match[1], previousLine=previousLine) 

559 

560 return None 

561 

562 def __str__(self) -> str: 

563 return f"{self._MESSAGE_KIND}: {self._message}" 

564 

565 

566@export 

567class VivadoWarningMessage(VivadoMessage, WarningMessage): 

568 """ 

569 This class represents an AMD/Xilinx Vivado warning message. 

570 

571 .. rubric:: Example 

572 

573 .. code-block:: 

574 

575 WARNING: [Synth 8-7080] Parallel synthesis criteria is not met 

576 """ 

577 

578 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

579 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(\w+(?: \w+)*?) (\d+)-(\d+)\] (.*)""") 

580 

581 @classmethod 

582 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

583 return super().Parse(lineNumber, LineKind.WarningMessage, rawMessage, previousLine=previousLine) 

584 

585 @classmethod 

586 def FromMessage(cls, line: VivadoMessage) -> Self: 

587 message = cls( 

588 line._lineNumber, 

589 LineKind.WarningMessage, 

590 line._message, 

591 line._toolName, 

592 line._toolID, 

593 line._messageKindID, 

594 previousLine=line._previousLine) 

595 message._nextLine = line._nextLine 

596 line._nextLine._previousLine = message 

597 

598 return message 

599 

600 

601@export 

602class VivadoDRCWarningMessage(VivadoMessage, WarningMessage): 

603 """ 

604 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) warning message. 

605 

606 .. rubric:: Example 

607 

608 .. code-block:: 

609 

610 WARNING: [DRC PDCN-1569] LUT equation term check: Used physical LUT pin 'A1' of cell ps/path/to/cell (pin ps/path/to/cell/I0) is not included in the LUT equation: 'O6=(A6+~A6)*((A3*A2)+(A3*(~A2)*A5)+((~A3)*A4*A5)+((~A3)*(~A4)*A2)+((~A3)*(~A4)*(~A2)*A5))'. If this cell is a user instantiated LUT in the design, please remove connectivity to the pin or change the equation and/or INIT string of the LUT to prevent this issue. If the cell is inferred or IP created LUT, please regenerate the IP and/or resynthesize the design to attempt to correct the issue. 

611 """ 

612 

613 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

614 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[DRC (\w+)-(\d+)\] (.*)""") 

615 

616 _drcRuleName: str 

617 

618 def __init__( 

619 self, 

620 lineNumber: int, 

621 kind: LineKind, 

622 action: LineAction, 

623 drcRuleName: str, 

624 message: str, 

625 toolName: Nullable[str] = None, 

626 toolID: Nullable[int] = None, 

627 messageKindID: Nullable[int] = None, 

628 previousLine: Nullable[VivadoLine] = None 

629 ) -> None: 

630 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine) 

631 

632 self._drcRuleName = drcRuleName 

633 

634 @readonly 

635 def DRCRuleName(self) -> str: 

636 return self._drcRuleName 

637 

638 @classmethod 

639 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

640 if (match := cls._REGEXP.match(rawMessage)) is not None: 640 ↛ 641line 640 didn't jump to line 641 because the condition on line 640 was never true

641 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[3], toolName="DRC", toolID=None, messageKindID=int(match[2]), previousLine=previousLine) 

642 

643 return None 

644 

645 @classmethod 

646 def Copy(cls, line: "VivadoDRCWarningMessage", previousLine: "VivadoLine") -> "VivadoDRCWarningMessage": 

647 newLine = cls(line._lineNumber, line._kind, line._action, line._drcRuleName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine) 

648 newLine._timestamp = line._timestamp 

649 return newLine 

650 

651 def __str__(self) -> str: 

652 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}" 

653 

654 

655@export 

656class VivadoXPMWarningMessage(VivadoMessage, WarningMessage): 

657 """ 

658 This class represents an AMD/Xilinx Vivado XPM warning message. 

659 

660 .. rubric:: Example 

661 

662 .. code-block:: 

663 

664 WARNING: [XPM_CDC_GRAY: TCL-1000] The source and destination clocks are the same. 

665 """ 

666 

667 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

668 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(XPM_\w+): (\w+)-(\d+)\] (.*)""") 

669 

670 _xpmName: str 

671 

672 def __init__( 

673 self, 

674 lineNumber: int, 

675 kind: LineKind, 

676 action: LineAction, 

677 xpmName: str, 

678 message: str, 

679 toolName: Nullable[str] = None, 

680 toolID: Nullable[int] = None, 

681 messageKindID: Nullable[int] = None, 

682 previousLine: Nullable[VivadoLine] = None 

683 ) -> None: 

684 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine) 

685 

686 self._xpmName = xpmName 

687 

688 @readonly 

689 def XPMName(self) -> str: 

690 return self._xpmName 

691 

692 @classmethod 

693 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

694 if (match := cls._REGEXP.match(rawMessage)) is not None: 694 ↛ 697line 694 didn't jump to line 697 because the condition on line 694 was always true

695 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[4], toolName=match[2], toolID=None, messageKindID=int(match[3]), previousLine=previousLine) 

696 

697 return None 

698 

699 @classmethod 

700 def Copy(cls, line: "VivadoXPMWarningMessage", previousLine: "VivadoLine") -> "VivadoXPMWarningMessage": 

701 newLine = cls(line._lineNumber, line._kind, line._action, line._xpmName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine) 

702 newLine._timestamp = line._timestamp 

703 return newLine 

704 

705 def __str__(self) -> str: 

706 return f"{self._MESSAGE_KIND}: [{self._xpmName}: {self._toolName}-{self._messageKindID}] {self._message}" 

707 

708 

709@export 

710class VivadoStuntedWarningMessage(VivadoMessage, WarningMessage): 

711 """ 

712 This class represents a stunted AMD/Xilinx Vivado warning message. 

713 

714 .. rubric:: Example 

715 

716 .. code-block:: 

717 

718 WARNING: set_property ASYNC_REG could not find object (constraint file /path/to/sync_Bits_Xilinx.xdc, line 5). 

719 """ 

720 

721 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

722 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: ([^\[].*)""") 

723 

724 @classmethod 

725 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

726 if (match := cls._REGEXP.match(rawMessage)) is not None: 726 ↛ 729line 726 didn't jump to line 729 because the condition on line 726 was always true

727 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], previousLine=previousLine) 

728 

729 return None 

730 

731 def __str__(self) -> str: 

732 return f"{self._MESSAGE_KIND}: {self._message}" 

733 

734 

735@export 

736class VivadoCriticalWarningMessage(VivadoMessage, CriticalWarningMessage): 

737 """ 

738 This class represents an AMD/Xilinx Vivado critical warning message. 

739 

740 .. rubric:: Example 

741 

742 .. code-block:: 

743 

744 CRITICAL WARNING: [Constraints 18-1056] Clock 'RefClkA_SFP_Quad' completely overrides clock 'USRCLKA_SFP[P]'. 

745 """ 

746 

747 _MESSAGE_KIND: ClassVar[str] = "CRITICAL WARNING" 

748 _REGEXP: ClassVar[Pattern] = re_compile(r"""CRITICAL WARNING: \[(\w+) (\d+)-(\d+)\] (.*)""") 

749 

750 @classmethod 

751 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

752 return super().Parse(lineNumber, LineKind.CriticalWarningMessage, rawMessage, previousLine) 

753 

754 @classmethod 

755 def FromMessage(cls, line: VivadoMessage) -> Self: 

756 message = cls( 

757 line._lineNumber, 

758 LineKind.CriticalWarningMessage, 

759 line._message, 

760 line._toolName, 

761 line._toolID, 

762 line._messageKindID, 

763 previousLine=line._previousLine) 

764 message._nextLine = line._nextLine 

765 line._nextLine._previousLine = message 

766 

767 return message 

768 

769 

770@export 

771class VivadoErrorMessage(VivadoMessage, ErrorMessage): 

772 """ 

773 This class represents an AMD/Xilinx Vivado error message. 

774 

775 .. rubric:: Example 

776 

777 .. code-block:: 

778 

779 ERROR: [Memdata 28-96] Could not find a BMM_INFO_DESIGN property in the design. Could not generate the merged BMM file: C:/Users/username/git/design.runs/impl_1/system_top_bd.bmm 

780 """ 

781 

782 _MESSAGE_KIND: ClassVar[str] = "ERROR" 

783 _REGEXP: ClassVar[Pattern] = re_compile(r"""ERROR: \[(\w+) (\d+)-(\d+)\] (.*)""") 

784 

785 @classmethod 

786 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

787 return super().Parse(lineNumber, LineKind.ErrorMessage, rawMessage, previousLine) 

788 

789 @classmethod 

790 def FromMessage(cls, line: VivadoMessage) -> Self: 

791 message = cls( 

792 line._lineNumber, 

793 LineKind.ErrorMessage, 

794 line._message, 

795 line._toolName, 

796 line._toolID, 

797 line._messageKindID, 

798 previousLine=line._previousLine) 

799 message._nextLine = line._nextLine 

800 line._nextLine._previousLine = message 

801 

802 return message 

803 

804 

805@export 

806class VHDLReportMessage(VivadoInfoMessage): 

807 _REGEXP2: ClassVar[Pattern ] = re_compile(r"""RTL report: "(.*)" \[(.*):(\d+)\]""") # todo: workaround for ClassVar problem 

808 

809 _reportMessage: str 

810 _sourceFile: Path 

811 _sourceLineNumber: int 

812 

813 def __init__( 

814 self, 

815 lineNumber: int, 

816 rawMessage: str, 

817 toolName: str, 

818 toolID: int, 

819 messageKindID: int, 

820 reportMessage: str, 

821 sourceFile: Path, 

822 sourceLineNumber: int, 

823 previousLine: Nullable[VivadoLine] = None 

824 ) -> None: 

825 super().__init__(lineNumber, LineKind.InfoMessage, LineAction.Default, rawMessage, toolName, toolID, messageKindID, previousLine) 

826 

827 self._reportMessage = reportMessage 

828 self._sourceFile = sourceFile 

829 self._sourceLineNumber = sourceLineNumber 

830 

831 @classmethod 

832 def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]: 

833 if (match := cls._REGEXP2.match(line._message)) is not None: 833 ↛ 836line 833 didn't jump to line 836 because the condition on line 833 was always true

834 return cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, match[1], Path(match[2]), int(match[3]), previousLine=line._previousLine) 

835 

836 return None 

837 

838 @classmethod 

839 def Copy(cls, line: "VHDLReportMessage", previousLine: "VivadoLine") -> "VHDLReportMessage": 

840 newLine = cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, line._reportMessage, line._sourceFile, line._sourceLineNumber, previousLine) 

841 newLine._timestamp = line._timestamp 

842 return newLine 

843 

844@export 

845class VHDLAssertionMessage(VHDLReportMessage): 

846 _REGEXP3: ClassVar[Pattern ] = re_compile(r"""RTL assertion: "(.*)" \[(.*):(\d+)\]""") # todo: workaround for ClassVar problem 

847 

848 @classmethod 

849 def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]: 

850 if (match := cls._REGEXP3.match(line._message)) is not None: 850 ↛ 853line 850 didn't jump to line 853 because the condition on line 850 was always true

851 return cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, match[1], Path(match[2]), int(match[3]), previousLine=line._previousLine) 

852 

853 return None 

854 

855 

856@export 

857class TclCommand(VivadoLine): 

858 """ 

859 Represents a TCL command found in a Vivado log output. 

860 

861 Besides the full log message (:class:`Line`), this class splits the TCL command into the command name and its 

862 arguments. 

863 """ 

864 _tclCommand: str 

865 _arguments: Tuple[str, ...] 

866 

867 def __init__( 

868 self, 

869 lineNumber: int, 

870 tclCommand: str, 

871 arguments: Tuple[str, ...], 

872 rawMessage: str, 

873 previousLine: Nullable[VivadoLine] = None 

874 ) -> None: 

875 super().__init__(lineNumber, LineKind.GenericTclCommand, LineAction.Default, rawMessage, previousLine) 

876 

877 self._tclCommand = tclCommand 

878 self._arguments = arguments 

879 

880 @readonly 

881 def TCLCommand(self) -> str: 

882 return self._tclCommand 

883 

884 @readonly 

885 def Arguments(self) -> Tuple[str, ...]: 

886 return self._arguments 

887 

888 @classmethod 

889 def FromLine(cls, line: VivadoLine) -> Nullable[Self]: 

890 args = line._message.split() 

891 

892 return cls(line._lineNumber, args[0], tuple(args[1:]), line._message, previousLine=line._previousLine) 

893 

894 @classmethod 

895 def Copy(cls, line: "TclCommand", previousLine: VivadoLine) -> "TclCommand": 

896 newLine = cls(line._lineNumber, line._tclCommand, line._arguments, line._message, previousLine) 

897 newLine._timestamp = line._timestamp 

898 return newLine 

899 

900 def __str__(self) -> str: 

901 return f"{self._tclCommand} {' '.join(self._arguments)}" 

902 

903 

904@export 

905class VivadoTclCommand(TclCommand): 

906 """ 

907 Represents a Vivado specific TCL command. 

908 """ 

909 

910 _PREFIX: ClassVar[str] = "Command:" 

911 

912 @classmethod 

913 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]: 

914 tclCommand = rawMessage[len(cls._PREFIX) + 1:] 

915 args = tclCommand.split() 

916 

917 vivadoCommand = cls(lineNumber, args[0], tuple(args[1:]), rawMessage, previousLine) 

918 vivadoCommand._kind = LineKind.VivadoTclCommand 

919 return vivadoCommand 

920 

921 def __str__(self) -> str: 

922 return f"{self._PREFIX} {self._tclCommand} {' '.join(self._arguments)}" 

923 

924 

925@export 

926class VivadoMessagesMixin(metaclass=ExtendedType, mixin=True): 

927 _infoMessages: List[VivadoInfoMessage] 

928 _warningMessages: List[VivadoWarningMessage] 

929 _criticalWarningMessages: List[VivadoCriticalWarningMessage] 

930 _errorMessages: List[VivadoErrorMessage] 

931 _toolIDs: Dict[int, str] 

932 _toolNames: Dict[str, int] 

933 _messagesByID: Dict[int, Dict[int, List[VivadoMessage]]] 

934 

935 def __init__(self) -> None: 

936 self._infoMessages = [] 

937 self._warningMessages = [] 

938 self._criticalWarningMessages = [] 

939 self._errorMessages = [] 

940 self._toolIDs = {} 

941 self._toolNames = {} 

942 self._messagesByID = {} 

943 

944 @readonly 

945 def ToolIDs(self) -> Dict[int, str]: 

946 return self._toolIDs 

947 

948 @readonly 

949 def ToolNames(self) -> Dict[str, int]: 

950 return self._toolNames 

951 

952 @readonly 

953 def MessagesByID(self) -> Dict[int, Dict[int, List[VivadoMessage]]]: 

954 return self._messagesByID 

955 

956 @readonly 

957 def InfoMessages(self) -> List[VivadoInfoMessage]: 

958 return self._infoMessages 

959 

960 @readonly 

961 def WarningMessages(self) -> List[VivadoWarningMessage]: 

962 return self._warningMessages 

963 

964 @readonly 

965 def CriticalWarningMessages(self) -> List[VivadoCriticalWarningMessage]: 

966 return self._criticalWarningMessages 

967 

968 @readonly 

969 def ErrorMessages(self) -> List[VivadoErrorMessage]: 

970 return self._errorMessages 

971 

972 def _AddMessage(self, message: VivadoMessage) -> None: 

973 if isinstance(message, InfoMessage): 

974 self._infoMessages.append(message) 

975 elif isinstance(message, WarningMessage): 

976 self._warningMessages.append(message) 

977 elif isinstance(message, CriticalWarningMessage): 

978 self._criticalWarningMessages.append(message) 

979 elif isinstance(message, ErrorMessage): 979 ↛ 982line 979 didn't jump to line 982 because the condition on line 979 was always true

980 self._errorMessages.append(message) 

981 

982 if message._toolID in self._messagesByID: 

983 sub = self._messagesByID[message._toolID] 

984 if message._messageKindID in sub: 

985 sub[message._messageKindID].append(message) 

986 else: 

987 sub[message._messageKindID] = [message] 

988 else: 

989 if message._toolID is not None: 

990 self._toolIDs[message._toolID] = message._toolName 

991 self._toolNames[message._toolName] = message._toolID 

992 

993 self._messagesByID[message._toolID] = {message._messageKindID: [message]} 

994 

995 

996# todo: check usage or merge with parser 

997@export 

998class BaseParser(VivadoMessagesMixin, metaclass=ExtendedType, slots=True): 

999 def __init__(self) -> None: 

1000 super().__init__() 

1001 

1002 

1003@export 

1004class Parser(BaseParser): 

1005 _processor: "Processor" 

1006 

1007 def __init__(self, processor: "Processor") -> None: 

1008 super().__init__() 

1009 

1010 self._processor = processor 

1011 

1012 @readonly 

1013 def Processor(self) -> "Processor": 

1014 return self._processor 

1015 

1016 

1017@export 

1018class Preamble(Parser): # todo: uses parser, which has vivado messages 

1019 """ 

1020 A parser for the preamble emitted by Vivado at session start. 

1021 

1022 .. rubric:: Extracted information 

1023 

1024 * Vivado tool version. |br| 

1025 See :data:`ToolVersion` 

1026 * Session start timestamp (date and time). |br| 

1027 See :data:`StartDatetime` 

1028 

1029 .. rubric:: Example 

1030 

1031 .. code-block:: 

1032 

1033 #----------------------------------------------------------- 

1034 # Vivado v2025.1 (64-bit) 

1035 # SW Build 6140274 on Thu May 22 00:12:29 MDT 2025 

1036 # IP Build 6138677 on Thu May 22 03:10:11 MDT 2025 

1037 # SharedData Build 6139179 on Tue May 20 17:58:58 MDT 2025 

1038 # Start of session at: Thu Jun 12 18:39:05 2025 

1039 # Process ID : 28856 

1040 # Current directory : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1 

1041 # Command line : vivado.exe -log toplevel.vdi -applog -product Vivado -messageDb vivado.pb -mode batch -source toplevel.tcl -notrace 

1042 # Log file : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1/toplevel.vdi 

1043 # Journal file : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1\vivado.jou 

1044 # Running On : Paebbels 

1045 # Platform : Windows Server 2016 or Windows 10 

1046 # Operating System : 26100 

1047 # Processor Detail : 11th Gen Intel(R) Core(TM) i9-11950H @ 2.60GHz 

1048 # CPU Frequency : 2611 MHz 

1049 # CPU Physical cores : 8 

1050 # CPU Logical cores : 16 

1051 # Host memory : 34048 MB 

1052 # Swap memory : 28991 MB 

1053 # Total Virtual : 63039 MB 

1054 # Available Virtual : 29246 MB 

1055 #----------------------------------------------------------- 

1056 """ 

1057 _VERSION: ClassVar[Pattern] = re_compile(r"""# Vivado v(\d+\.\d(\.\d)?) \(64-bit\)""") 

1058 _STARTTIME: ClassVar[Pattern] = re_compile(r"""# Start of session at: (\w+\s+\w+\s+\d+\s+\d+:\d+:\d+\s+\d+)""") 

1059 

1060 _toolVersion: Nullable[YearReleaseVersion] #: Used Vivado version. 

1061 _startDatetime: Nullable[datetime] #: Session start timestamp. 

1062 

1063 def __init__(self, processor: "BaseProcessor") -> None: 

1064 """ 

1065 Initializes a Vivado preamble parser. 

1066 

1067 :param processor: Reference to the Vivado log processor. 

1068 """ 

1069 super().__init__(processor) 

1070 

1071 self._toolVersion = None 

1072 self._startDatetime = None 

1073 

1074 @readonly 

1075 def ToolVersion(self) -> YearReleaseVersion: 

1076 """ 

1077 Read-only property to access the extracted Vivado tool version. 

1078 

1079 :returns: The used Vivado version as reported in the Vivado log messages. 

1080 """ 

1081 return self._toolVersion 

1082 

1083 @readonly 

1084 def StartDatetime(self) -> datetime: 

1085 """ 

1086 Read-only property to access the date and time when the Vivado session was started. 

1087 

1088 :returns: Datatime when the session was started. 

1089 :raises ProcessorException: When start timestamp wasn't extracted from preamble. 

1090 """ 

1091 if self._startDatetime is None: 1091 ↛ 1092line 1091 didn't jump to line 1092 because the condition on line 1091 was never true

1092 raise ProcessorException("No start timestamp extracted from preamble.") 

1093 

1094 return self._startDatetime 

1095 

1096 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1097 """ 

1098 A generator for processing the Vivado session preamble line-by-line. 

1099 

1100 :param line: First line to process. 

1101 :returns: A generator processing log messages. 

1102 """ 

1103 if line.StartsWith("#----"): 1103 ↛ 1106line 1103 didn't jump to line 1106 because the condition on line 1103 was always true

1104 line._kind = LineKind.SectionDelimiter 

1105 else: 

1106 line._kind |= LineKind.ProcessorError # TODO: throw / return error 

1107 

1108 line = yield line 

1109 

1110 # a normal preamble has up to 23 lines including both delimiter lines. 

1111 for _ in range(30): 1111 ↛ 1126line 1111 didn't jump to line 1126 because the loop on line 1111 didn't complete

1112 if (match := self._VERSION.match(line._message)) is not None: 

1113 self._toolVersion = YearReleaseVersion.Parse(match[1]) 

1114 line._kind = LineKind.Normal 

1115 elif (match := self._STARTTIME.match(line._message)) is not None: 

1116 self._startDatetime = datetime.strptime(match[1], "%a %b %d %H:%M:%S %Y") 

1117 line._kind = LineKind.Normal 

1118 elif line.StartsWith("#----"): 

1119 line._kind = LineKind.SectionDelimiter 

1120 break 

1121 else: 

1122 line._kind = LineKind.Verbose 

1123 

1124 line = yield line 

1125 else: 

1126 line._kind |= LineKind.ProcessorError # TODO: throw / return error 

1127 

1128 nextLine = yield line 

1129 return nextLine 

1130 

1131 

1132@export 

1133class Postamble(Parser, VivadoMessagesMixin): # todo: double mixin? 

1134 """ 

1135 A parser for the postamble emitted by Vivado at session end. 

1136 

1137 .. rubric:: Extracted information 

1138 

1139 * Session exit timestamp (date and time). |br| 

1140 See :data:`ExitDatetime` 

1141 

1142 .. rubric:: Example 

1143 

1144 .. code-block:: 

1145 

1146 INFO: [Common 17-206] Exiting Vivado at Tue Sep 2 08:46:23 2025... 

1147 

1148 """ 

1149 _INFO: Tuple[int, int] = (17, 206) 

1150 _ENDTIME: ClassVar[Pattern] = re_compile(r"""Exiting Vivado at (\w+\s+\w+\s+\d+\s+\d+:\d+:\d+\s+\d+)""") 

1151 

1152 _exitDatetime: Nullable[datetime] #: Session exit timestamp. 

1153 

1154 def __init__(self, processor: "BaseProcessor") -> None: 

1155 """ 

1156 Initializes a Vivado postamble parser. 

1157 

1158 :param processor: Reference to the Vivado log processor. 

1159 """ 

1160 super().__init__(processor) 

1161 VivadoMessagesMixin.__init__(self) 

1162 

1163 self._exitDatetime = None 

1164 

1165 @readonly 

1166 def ExitDatetime(self) -> Nullable[datetime]: 

1167 """ 

1168 Read-only property to access the date and time when the Vivado session was exited. 

1169 

1170 :returns: Datatime when the session was exited. 

1171 :raises ProcessorException: When exit timestamp wasn't extracted from postamble. 

1172 """ 

1173 if self._exitDatetime is None: 1173 ↛ 1174line 1173 didn't jump to line 1174 because the condition on line 1173 was never true

1174 raise ProcessorException("No exit timestamp extracted from postamble.") 

1175 

1176 return self._exitDatetime 

1177 

1178 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1179 """ 

1180 A generator for processing the Vivado session preamble line-by-line. 

1181 

1182 :param line: First line to process. 

1183 :returns: A generator processing log messages. 

1184 """ 

1185 if isinstance(line, VivadoMessage): 1185 ↛ 1191line 1185 didn't jump to line 1191 because the condition on line 1185 was always true

1186 self._AddMessage(line) 

1187 

1188 if not isinstance(line, VivadoInfoMessage): 1188 ↛ 1189line 1188 didn't jump to line 1189 because the condition on line 1188 was never true

1189 raise ProcessorException(f"{self.__class__.__name__}.Generator(): Expected '{self._ENDTIME}' at line {line._lineNumber}.") 

1190 

1191 if (match := self._ENDTIME.match(line._message)) is not None: 

1192 self._exitDatetime = datetime.strptime(match[1], "%a %b %d %H:%M:%S %Y") 

1193 else: 

1194 pass 

1195 

1196 line = yield line 

1197 

1198 # todo: should we receive and expect an ned-token like None? 

1199 return line 

1200 

1201@export 

1202class Command(Parser): 

1203 """ 

1204 This parser parses outputs from Vivado TCL commands. 

1205 

1206 Depending on the command's output (and how it's implemented), they use different subcategories. 

1207 

1208 .. rubric:: Command subcategories 

1209 

1210 * :class:`CommandWithSections` 

1211 * :class:`CommandWithtasks` 

1212 

1213 .. rubric:: Supported commands 

1214 

1215 * :class:`SynthesizeDesign` 

1216 * :class:`LinkDesign` 

1217 * :class:`OptimizeDesign` 

1218 * :class:`PlaceDesign` 

1219 * :class:`PhysicalOptimizeDesign` 

1220 * :class:`RouteDesign` 

1221 * :class:`WriteBitstream` 

1222 * :class:`ReportDRC` 

1223 * :class:`ReportMethodology` 

1224 * :class:`ReportPower` 

1225 

1226 .. rubric:: Example 

1227 

1228 .. code-block:: 

1229 

1230 [...] 

1231 Command: synth_design -top system_top -part xc7z015clg485-2 

1232 Starting synth_design 

1233 [...] 

1234 """ 

1235 

1236 # _TCL_COMMAND: ClassVar[str] 

1237 

1238 def _CommandStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1239 """ 

1240 A generator accepting a line containing the expected Vivado TCL command. 

1241 

1242 When the generator exits, the returned line is the successor line to the line containing the Vivado TCL command. 

1243 

1244 :param line: The first line for the generator to process. 

1245 :returns: A generator processing Vivado output log lines. 

1246 """ 

1247 if not (isinstance(line, VivadoTclCommand) and line._tclCommand == self._TCL_COMMAND): 1247 ↛ 1248line 1247 didn't jump to line 1248 because the condition on line 1247 was never true

1248 raise ProcessorException() # FIXME: add exception message 

1249 

1250 nextLine = yield line 

1251 return nextLine 

1252 

1253 def _CommandFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1254 if line.StartsWith(f"{self._TCL_COMMAND} completed successfully"): 1254 ↛ 1257line 1254 didn't jump to line 1257 because the condition on line 1254 was always true

1255 line._kind |= LineKind.Success 

1256 else: 

1257 line._kind |= LineKind.Failed 

1258 

1259 line = yield line 

1260 

1261 if self._TIME is not None: # and self._processor._preamble._toolVersion > "2022.2": 

1262 end = f"{self._TCL_COMMAND}: {self._TIME}" 

1263 

1264 # while True: # TODO: limit search for time to 10 lines 

1265 # if line.StartsWith(end): 

1266 # line._kind = LineKind.TaskTime 

1267 # line = yield line 

1268 # break 

1269 # 

1270 # line = yield line 

1271 

1272 if line.StartsWith(end): 

1273 line._kind = LineKind.TaskTime 

1274 line = yield line 

1275 

1276 return line 

1277 

1278 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, None]: 

1279 line = yield from self._CommandStart(line) 

1280 

1281 end = f"{self._TCL_COMMAND} " 

1282 while True: 

1283 if line._kind is LineKind.Empty: 

1284 line = yield line 

1285 continue 

1286 elif isinstance(line, VivadoMessage): 

1287 self._AddMessage(line) 

1288 elif line.StartsWith(end): 

1289 nextLine = yield from self._CommandFinish(line) 

1290 return nextLine 

1291 

1292 line = yield line 

1293 

1294 def __str__(self) -> str: 

1295 return f"{self._TCL_COMMAND}" 

1296 

1297 

1298@export 

1299class CommandWithSections(Command): 

1300 """ 

1301 A Vivado command writing sections into the output log. 

1302 

1303 .. rubric:: Example 

1304 

1305 .. code-block:: 

1306 

1307 [...] 

1308 --------------------------------------------------------------------------------- 

1309 Starting RTL Elaboration : Time (s): cpu = 00:00:03 ; elapsed = 00:00:03 . Memory (MB): peak = 847.230 ; gain = 176.500 

1310 --------------------------------------------------------------------------------- 

1311 INFO: [Synth 8-638] synthesizing module 'system_top' [C:/Users/tgomes/git/2019_1/src/system_top_PE1.vhd:257] 

1312 [...] 

1313 [...] 

1314 [...] 

1315 --------------------------------------------------------------------------------- 

1316 Finished RTL Elaboration : Time (s): cpu = 00:00:04 ; elapsed = 00:00:04 . Memory (MB): peak = 917.641 ; gain = 246.910 

1317 --------------------------------------------------------------------------------- 

1318 [...] 

1319 """ 

1320 # _PARSERS: ClassVar[Tuple[Type[Section], ...]] 

1321 

1322 _sections: List["Section"] # Dict[Type["Section"], "Section"] 

1323 

1324 

1325 def __init__(self, processor: "Processor") -> None: 

1326 super().__init__(processor) 

1327 

1328 self._sections = [] # p: p(self) for p in self._PARSERS} 

1329 

1330 @readonly 

1331 def Sections(self) -> List["Section"]: # Dict[Type["Section"], "Section"]: 

1332 """ 

1333 Read-only property to access a dictionary of found sections within the TCL command's output. 

1334 

1335 :returns: A dictionary of found :class:`~pyEDAA.OutputFilter.Xilinx.SynthesizeDesign.Section`s. 

1336 """ 

1337 return self._sections 

1338 

1339 def __contains__(self, key: Any) -> bool: 

1340 if not issubclass(key, Section): 1340 ↛ 1341line 1340 didn't jump to line 1341 because the condition on line 1340 was never true

1341 ex = TypeError(f"Parameter 'key' is not a Section.") 

1342 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

1343 raise ex 

1344 

1345 for section in self._sections: 1345 ↛ 1349line 1345 didn't jump to line 1349 because the loop on line 1345 didn't complete

1346 if isinstance(section, key): 1346 ↛ 1345line 1346 didn't jump to line 1345 because the condition on line 1346 was always true

1347 return True 

1348 else: 

1349 return False 

1350 

1351 def __getitem__(self, key: Type["Section"]) -> "Section": 

1352 if not issubclass(key, Section): 1352 ↛ 1353line 1352 didn't jump to line 1353 because the condition on line 1352 was never true

1353 ex = TypeError(f"Parameter 'key' is not a Section.") 

1354 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

1355 raise ex 

1356 

1357 for section in self._sections: 1357 ↛ 1361line 1357 didn't jump to line 1361 because the loop on line 1357 didn't complete

1358 if isinstance(section, key): 

1359 return section 

1360 else: 

1361 raise SectionNotPresentException(F"Section '{key._NAME}' not present in '{self._parent.logfile}'.") 

1362 

1363 

1364@export 

1365class CommandWithTasks(Command): 

1366 """ 

1367 A Vivado command writing tasks into the output log. 

1368 

1369 .. rubric:: Example 

1370 

1371 .. code-block:: 

1372 

1373 [...] 

1374 Starting Cache Timing Information Task 

1375 INFO: [Timing 38-35] 79-Done setting XDC timing constraints. 

1376 [...] 

1377 [...] 

1378 Ending Cache Timing Information Task | Checksum: 19fe8cb97 

1379 [...] 

1380 """ 

1381 # _PARSERS: Tuple[Type[Task], ...] 

1382 

1383 _tasks: Dict[Type["Task"], "Task"] 

1384 

1385 def __init__(self, processor: "Processor") -> None: 

1386 super().__init__(processor) 

1387 

1388 self._tasks = {p: p(self) for p in self._PARSERS} 

1389 

1390 @readonly 

1391 def Tasks(self) -> Dict[Type["Task"], "Task"]: 

1392 """ 

1393 Read-only property to access a dictionary of found tasks within the TCL command's output. 

1394 

1395 :returns: A dictionary of found :class:`~pyEDAA.OutputFilter.Xilinx.Common2.Task`s. 

1396 """ 

1397 return self._tasks 

1398 

1399 def __contains__(self, key: Any) -> bool: 

1400 if not issubclass(key, Task): 1400 ↛ 1401line 1400 didn't jump to line 1401 because the condition on line 1400 was never true

1401 ex = TypeError(f"Parameter 'key' is not a Task.") 

1402 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

1403 raise ex 

1404 

1405 return key in self._tasks 

1406 

1407 def __getitem__(self, key: Type["Task"]) -> "Task": 

1408 try: 

1409 return self._tasks[key] 

1410 except KeyError as ex: 

1411 raise SectionNotPresentException(F"Task '{key._NAME}' not present in '{self._parent.logfile}'.") from ex 

1412 

1413 

1414@export 

1415class BaseSection(metaclass=ExtendedType, mixin=True): 

1416 @abstractmethod 

1417 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1418 pass 

1419 

1420 @abstractmethod 

1421 def _SectionFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]: 

1422 pass 

1423 

1424 @abstractmethod 

1425 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1426 pass 

1427 

1428 

1429@export 

1430class Section(BaseParser, BaseSection): 

1431 """ 

1432 Base-class for sections within log outputs from *synthesize design*. 

1433 """ 

1434 # _NAME: ClassVar[str] 

1435 # _START: ClassVar[str] 

1436 # _FINISH: ClassVar[str] 

1437 # _DUPLICATES: ClassVar[bool] 

1438 

1439 _command: "Command" #: Reference to the command (parent). 

1440 _next: Nullable["Section"] 

1441 _duration: float #: Duration synthesis spent in processing a synthesis step logged in this log output section. 

1442 

1443 def __init__(self, command: "Command") -> None: 

1444 """ 

1445 Initialized a section. 

1446 

1447 :param command: Reference to the parent TCL command. 

1448 """ 

1449 super().__init__() #command._processor) 

1450 

1451 self._command = command 

1452 self._next = None 

1453 self._duration = 0.0 

1454 

1455 @readonly 

1456 def Next(self) -> Nullable["Section"]: 

1457 """ 

1458 Read-only property to access the next section in case the section appeared multiple times. 

1459 

1460 :returns: Next section of same type. 

1461 """ 

1462 return self._next 

1463 

1464 @readonly 

1465 def Duration(self) -> float: 

1466 """ 

1467 Read-only property to access the duration synthesis spent in processing a synthesis step logged in this log output 

1468 section. 

1469 

1470 :returns: Synthesis step duration in seconds. 

1471 """ 

1472 return self._duration 

1473 

1474 def __iter__(self) -> Generator["Section", None, None]: 

1475 section = self._next 

1476 while section is not None: 

1477 yield section 

1478 

1479 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1480 line._kind = LineKind.SectionStart 

1481 

1482 line = yield line 

1483 if line.StartsWith("----"): 1483 ↛ 1486line 1483 didn't jump to line 1486 because the condition on line 1483 was always true

1484 line._kind = LineKind.SectionStart | LineKind.SectionDelimiter 

1485 else: 

1486 line._kind |= LineKind.ProcessorError 

1487 

1488 nextLine = yield line 

1489 return nextLine 

1490 

1491 def _SectionFinish(self, line: VivadoLine, skipDashes: bool = False) -> Generator[VivadoLine, VivadoLine, None]: 

1492 if not skipDashes: 

1493 if line.StartsWith("----"): 1493 ↛ 1496line 1493 didn't jump to line 1496 because the condition on line 1493 was always true

1494 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter 

1495 else: 

1496 line._kind |= LineKind.ProcessorError 

1497 

1498 line = yield line 

1499 

1500 if line.StartsWith(self._FINISH): 1500 ↛ 1503line 1500 didn't jump to line 1503 because the condition on line 1500 was always true

1501 line._kind = LineKind.SectionEnd 

1502 else: 

1503 line._kind |= LineKind.ProcessorError 

1504 

1505 line = yield line 

1506 if line.StartsWith("----"): 1506 ↛ 1509line 1506 didn't jump to line 1509 because the condition on line 1506 was always true

1507 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter 

1508 else: 

1509 line._kind |= LineKind.ProcessorError 

1510 

1511 nextLine = yield line 

1512 return nextLine 

1513 

1514 # @mustoverride 

1515 # def ParseLine(self, lineNumber: int, line: str) -> ProcessingState: 

1516 # if len(line) == 0: 

1517 # return ProcessingState.EmptyLine 

1518 # elif line.startswith("----"): 

1519 # return ProcessingState.DelimiterLine 

1520 # elif line.startswith(self._START): 

1521 # return ProcessingState.Skipped 

1522 # elif line.startswith(self._FINISH): 

1523 # l = line[len(self._FINISH):] 

1524 # if (match := TIME_MEMORY_PATTERN.match(l)) is not None: 

1525 # # cpuParts = match[1].split(":") 

1526 # elapsedParts = match[2].split(":") 

1527 # # peakMemory = float(match[3]) 

1528 # # gainMemory = float(match[4]) 

1529 # self._duration = int(elapsedParts[0]) * 3600 + int(elapsedParts[1]) * 60 + int(elapsedParts[2]) 

1530 # 

1531 # return ProcessingState.Skipped | ProcessingState.Last 

1532 # elif line.startswith("Start") or line.startswith("Starting"): 

1533 # print(f"ERROR: didn't find finish\n {line}") 

1534 # return ProcessingState.Reprocess 

1535 # 

1536 # return ProcessingState.Skipped 

1537 

1538 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1539 line = yield from self._SectionStart(line) 

1540 

1541 while True: 

1542 if line._kind is LineKind.Empty: 1542 ↛ 1543line 1542 didn't jump to line 1543 because the condition on line 1542 was never true

1543 line = yield line 

1544 continue 

1545 elif line.StartsWith("----"): 

1546 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter 

1547 break 

1548 elif isinstance(line, VivadoMessage): 

1549 self._AddMessage(line) 

1550 else: 

1551 line._kind = LineKind.Verbose 

1552 

1553 line = yield line 

1554 

1555 # line = yield line 

1556 nextLine = yield from self._SectionFinish(line) 

1557 return nextLine 

1558 

1559 

1560@export 

1561class SubSection(BaseParser, BaseSection): 

1562 """ 

1563 Base-class for subsections within log outputs from *synthesize design*. 

1564 """ 

1565 # _NAME: ClassVar[str] 

1566 

1567 _section: Section #: Reference to the section (parent). 

1568 

1569 def __init__(self, section: Section) -> None: 

1570 """ 

1571 Initialized a subsection. 

1572 

1573 :param section: Reference to the parent section. 

1574 """ 

1575 super().__init__() 

1576 self._section = section 

1577 

1578 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1579 line._kind = LineKind.SubSectionStart 

1580 

1581 line = yield line 

1582 if line.StartsWith("----"): 1582 ↛ 1585line 1582 didn't jump to line 1585 because the condition on line 1582 was always true

1583 line._kind = LineKind.SubSectionStart | LineKind.SubSectionDelimiter 

1584 else: 

1585 line._kind |= LineKind.ProcessorError 

1586 

1587 nextLine = yield line 

1588 return nextLine 

1589 

1590 def _SectionFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]: 

1591 if line.StartsWith("----"): 1591 ↛ 1594line 1591 didn't jump to line 1594 because the condition on line 1591 was always true

1592 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter 

1593 else: 

1594 line._kind |= LineKind.ProcessorError 

1595 

1596 line = yield line 

1597 if line.StartsWith(self._FINISH): 1597 ↛ 1600line 1597 didn't jump to line 1600 because the condition on line 1597 was always true

1598 line._kind = LineKind.SubSectionEnd 

1599 else: 

1600 line._kind |= LineKind.ProcessorError 

1601 

1602 line = yield line 

1603 if line.StartsWith("----"): 1603 ↛ 1606line 1603 didn't jump to line 1606 because the condition on line 1603 was always true

1604 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter 

1605 else: 

1606 line._kind |= LineKind.ProcessorError 

1607 

1608 nextLine = yield line 

1609 return nextLine 

1610 

1611 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1612 line = yield from self._SectionStart(line) 

1613 

1614 while True: 

1615 if line._kind is LineKind.Empty: 1615 ↛ 1616line 1615 didn't jump to line 1616 because the condition on line 1615 was never true

1616 line = yield line 

1617 continue 

1618 elif line.StartsWith("----"): 1618 ↛ 1621line 1618 didn't jump to line 1621 because the condition on line 1618 was always true

1619 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter 

1620 break 

1621 elif isinstance(line, VivadoMessage): 

1622 self._AddMessage(line) 

1623 else: 

1624 line._kind = LineKind.Verbose 

1625 

1626 line = yield line 

1627 

1628 nextLine = yield from self._SectionFinish(line) 

1629 return nextLine 

1630 

1631 

1632@export 

1633class SectionWithChildren(Section): 

1634 """ 

1635 Base-class for sections with subsections. 

1636 """ 

1637 _subsections: Dict[Type[SubSection], SubSection] 

1638 

1639 def __init__(self, command: "Command") -> None: 

1640 super().__init__(command) 

1641 

1642 self._subsections = {} 

1643 

1644 def __contains__(self, key: Any) -> bool: 

1645 if not issubclass(key, SubSection): 1645 ↛ 1646line 1645 didn't jump to line 1646 because the condition on line 1645 was never true

1646 ex = TypeError(f"Parameter 'item' is not a SubSection.") 

1647 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

1648 raise ex 

1649 

1650 return key in self._subsections 

1651 

1652 def __getitem__(self, item: Type[SubSection]) -> SubSection: 

1653 try: 

1654 return self._subsections[item] 

1655 except KeyError as ex: 

1656 raise SubSectionNotPresentException(f"SubSection '{item._NAME}' not present in '{self._parent._parent.logfile}'.") from ex 

1657 

1658 

1659@export 

1660class Task(BaseParser, VivadoMessagesMixin): 

1661 """ 

1662 A task's output emitted by a Vivado command. 

1663 

1664 .. rubric:: Extracted information 

1665 

1666 * Vivado messages (info, warning, critical warning, error). 

1667 

1668 .. rubric:: Example 

1669 

1670 .. code-block:: 

1671 

1672 Starting Cache Timing Information Task 

1673 INFO: [Timing 38-35] 79-Done setting XDC timing constraints. 

1674 Ending Cache Timing Information Task | Checksum: 19fe8cb97 

1675 

1676 Time (s): cpu = 00:00:09 ; elapsed = 00:00:09 . Memory (MB): peak = 1370.594 ; gain = 493.266 

1677 

1678 """ 

1679 # _NAME: ClassVar[str] 

1680 # _START: ClassVar[str] 

1681 # _FINISH: ClassVar[str] 

1682 _TIME: ClassVar[str] = "Time (s):" 

1683 

1684 _command: "Command" #: Reference to the command (parent). 

1685 _duration: float #: Duration of a task according to reported times by Vivado. 

1686 

1687 def __init__(self, command: "Command") -> None: 

1688 """ 

1689 Initializes a task (without child elements). 

1690 

1691 :param command: Reference to the command. 

1692 """ 

1693 super().__init__() 

1694 VivadoMessagesMixin.__init__(self) 

1695 

1696 self._command = command 

1697 

1698 @readonly 

1699 def Command(self) -> "Command": 

1700 """ 

1701 Read-only property to access the command. 

1702 

1703 :returns: The command this task's output was logged for. 

1704 """ 

1705 return self._command 

1706 

1707 def _TaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1708 """ 

1709 A generator for processing a task start (single line). 

1710 

1711 :param line: First line to process (task start). 

1712 :returns: A generator processing log messages. 

1713 :raises ProcessorException: If first line doesn't conform to the *task start* pattern. 

1714 """ 

1715 if not line.StartsWith(self._START): 1715 ↛ 1716line 1715 didn't jump to line 1716 because the condition on line 1715 was never true

1716 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.") 

1717 

1718 line._kind = LineKind.TaskStart 

1719 nextLine = yield line 

1720 return nextLine 

1721 

1722 def _TaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1723 """ 

1724 A generator for processing a task finish line-by-line. 

1725 

1726 :param line: First line to process (task finish). 

1727 :returns: A generator processing log messages. 

1728 :raises ProcessorException: If finish line doesn't conform to the *task finish* pattern. 

1729 """ 

1730 if not line.StartsWith(self._FINISH): 1730 ↛ 1731line 1730 didn't jump to line 1731 because the condition on line 1730 was never true

1731 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.") 

1732 

1733 line._kind = LineKind.TaskEnd 

1734 line = yield line 

1735 while self._TIME is not None: # TODO: limit search for time pattern to XX lines 1735 ↛ 1742line 1735 didn't jump to line 1742 because the condition on line 1735 was always true

1736 if line.StartsWith(self._TIME): 

1737 line._kind = LineKind.TaskTime 

1738 break 

1739 

1740 line = yield line 

1741 

1742 nextLine = yield line 

1743 return nextLine 

1744 

1745 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1746 """ 

1747 A generator for processing a task without child elements line-by-line. 

1748 

1749 .. rubric:: Algorithm 

1750 

1751 1. Send first line to :meth:`_TaskStart`. 

1752 2. Process body lines 

1753 

1754 * Collect Vivado messages (info, warning, critical warning, error). 

1755 * Check for *task finish* pattern. 

1756 * Check for *time* pattern. 

1757 

1758 3. Send last lines to :meth:`_TaskFinish`. 

1759 

1760 :param line: First line to process. 

1761 :returns: A generator processing log messages. 

1762 """ 

1763 line = yield from self._TaskStart(line) 

1764 

1765 while True: 

1766 if line._kind is LineKind.Empty: 

1767 line = yield line 

1768 continue 

1769 elif self._FINISH is not None and line.StartsWith("Ending"): 

1770 break 

1771 elif isinstance(line, VivadoMessage): 

1772 self._AddMessage(line) 

1773 elif line.StartsWith(self._TIME): 

1774 line._kind = LineKind.TaskTime 

1775 nextLine = yield line 

1776 return nextLine 

1777 

1778 line = yield line 

1779 

1780 nextLine = yield from self._TaskFinish(line) 

1781 return nextLine 

1782 

1783 def __str__(self) -> str: 

1784 return f"{self.__class__.__name__}: {self._START}" 

1785 

1786 

1787@export 

1788class TaskWithSubTasks(Task): 

1789 """ 

1790 A task's output emitted by a Vivado command. 

1791 

1792 .. rubric:: Extracted information 

1793 

1794 * Vivado messages (info, warning, critical warning, error). 

1795 * Subtasks 

1796 

1797 .. rubric:: Example 

1798 

1799 .. code-block:: 

1800 

1801 Starting Cache Timing Information Task 

1802 INFO: [Timing 38-35] 79-Done setting XDC timing constraints. 

1803 Ending Cache Timing Information Task | Checksum: 19fe8cb97 

1804 

1805 Time (s): cpu = 00:00:09 ; elapsed = 00:00:09 . Memory (MB): peak = 1370.594 ; gain = 493.266 

1806 

1807 """ 

1808 # _PARSERS: ClassVar[Tuple[Type["SubTask"], ...]] 

1809 

1810 _subtasks: Dict[Type["SubTask"], "SubTask"] 

1811 

1812 def __init__(self, command: "Command") -> None: 

1813 super().__init__(command) 

1814 

1815 self._subtasks = {p: p(self) for p in self._PARSERS} 

1816 

1817 @readonly 

1818 def SubTasks(self) -> Dict[Type["SubTask"], "SubTask"]: 

1819 return self._subtasks 

1820 

1821 def __contains__(self, key: Any) -> bool: 

1822 if not issubclass(key, SubTask): 1822 ↛ 1823line 1822 didn't jump to line 1823 because the condition on line 1822 was never true

1823 ex = TypeError(f"Parameter 'key' is not a Subtask.") 

1824 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

1825 raise ex 

1826 

1827 return key in self._subtasks 

1828 

1829 def __getitem__(self, key: Type["SubTask"]) -> "SubTask": 

1830 try: 

1831 return self._subtasks[key] 

1832 except KeyError as ex: 

1833 raise SubTaskNotPresentException(F"Subtask '{key._NAME}' not present in '{self._parent.logfile}'.") from ex 

1834 

1835 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1836 line = yield from self._TaskStart(line) 

1837 

1838 activeParsers: List[Phase] = list(self._subtasks.values()) 

1839 

1840 while True: 

1841 while True: 

1842 if line._kind is LineKind.Empty: 

1843 line = yield line 

1844 continue 

1845 elif isinstance(line, VivadoMessage): 

1846 self._AddMessage(line) 

1847 elif line.StartsWith("Starting "): 

1848 for parser in activeParsers: # type: SubTask 1848 ↛ 1853line 1848 didn't jump to line 1853 because the loop on line 1848 didn't complete

1849 if line.StartsWith(parser._START): 1849 ↛ 1848line 1849 didn't jump to line 1848 because the condition on line 1849 was always true

1850 line = yield next(subtask := parser.Generator(line)) 

1851 break 

1852 else: 

1853 WarningCollector.Raise(UnknownSubTask(f"Unknown subtask: '{line!r}'", line)) 

1854 ex = Exception(f"How to recover from here? Unknown subtask: '{line!r}'") 

1855 ex.add_note(f"Current task: start pattern='{self}'") 

1856 ex.add_note(f"Current command: {self._command}") 

1857 raise ex 

1858 break 

1859 elif line.StartsWith("Ending"): 

1860 nextLine = yield from self._TaskFinish(line) 

1861 return nextLine 

1862 elif line.StartsWith(self._TIME): 1862 ↛ 1863line 1862 didn't jump to line 1863 because the condition on line 1862 was never true

1863 line._kind = LineKind.TaskTime 

1864 nextLine = yield line 

1865 return nextLine 

1866 

1867 line = yield line 

1868 

1869 while True: 

1870 isFinish = False # line.StartsWith("Ending") # FIXME: detect end, but time might come later 

1871 

1872 try: 

1873 processedLine = subtask.send(line) 

1874 

1875 if isinstance(processedLine, VivadoMessage): 

1876 self._AddMessage(processedLine) 

1877 

1878 if isFinish: 1878 ↛ 1879line 1878 didn't jump to line 1879 because the condition on line 1878 was never true

1879 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine)) 

1880 line = yield processedLine 

1881 break 

1882 except StopIteration as ex: 

1883 activeParsers.remove(parser) 

1884 line = ex.value 

1885 break 

1886 

1887 line = yield processedLine 

1888 

1889 

1890@export 

1891class SubTask(BaseParser, VivadoMessagesMixin): 

1892 # _NAME: ClassVar[str] 

1893 # _START: ClassVar[str] 

1894 # _FINISH: ClassVar[str] 

1895 _TIME: ClassVar[str] = "Time (s):" 

1896 

1897 _task: TaskWithSubTasks 

1898 _duration: float 

1899 

1900 def __init__(self, task: TaskWithSubTasks) -> None: 

1901 super().__init__() 

1902 VivadoMessagesMixin.__init__(self) 

1903 

1904 self._task = task 

1905 

1906 @readonly 

1907 def Task(self) -> TaskWithSubTasks: 

1908 return self._task 

1909 

1910 def _TaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1911 if not line.StartsWith(self._START): 1911 ↛ 1912line 1911 didn't jump to line 1912 because the condition on line 1911 was never true

1912 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.") 

1913 

1914 line._kind = LineKind.TaskStart 

1915 nextLine = yield line 

1916 return nextLine 

1917 

1918 def _TaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1919 if not line.StartsWith(self._FINISH): 1919 ↛ 1920line 1919 didn't jump to line 1920 because the condition on line 1919 was never true

1920 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.") 

1921 

1922 line._kind = LineKind.TaskEnd 

1923 line = yield line 

1924 while self._TIME is not None: 1924 ↛ 1931line 1924 didn't jump to line 1931 because the condition on line 1924 was always true

1925 if line.StartsWith(self._TIME): 

1926 line._kind = LineKind.TaskTime 

1927 break 

1928 

1929 line = yield line 

1930 

1931 nextLine = yield line 

1932 return nextLine 

1933 

1934 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1935 line = yield from self._TaskStart(line) 

1936 

1937 while True: 

1938 if line._kind is LineKind.Empty: 1938 ↛ 1939line 1938 didn't jump to line 1939 because the condition on line 1938 was never true

1939 line = yield line 

1940 continue 

1941 elif self._FINISH is not None and line.StartsWith("Ending"): 

1942 break 

1943 elif isinstance(line, VivadoMessage): 

1944 self._AddMessage(line) 

1945 elif line.StartsWith(self._TIME): 1945 ↛ 1946line 1945 didn't jump to line 1946 because the condition on line 1945 was never true

1946 line._kind = LineKind.TaskTime 

1947 nextLine = yield line 

1948 return nextLine 

1949 

1950 line = yield line 

1951 

1952 nextLine = yield from self._TaskFinish(line) 

1953 return nextLine 

1954 

1955 def __str__(self) -> str: 

1956 return self._NAME 

1957 

1958 

1959@export 

1960class TaskWithPhases(Task): 

1961 # _PARSERS: ClassVar[Tuple[Type["Phase"], ...]] 

1962 

1963 _phases: Dict[Type["Phase"], "Phase"] 

1964 

1965 def __init__(self, command: "Command") -> None: 

1966 super().__init__(command) 

1967 

1968 self._phases = {p: p(self) for p in self._PARSERS} 

1969 

1970 @readonly 

1971 def Phases(self) -> Dict[Type["Phase"], "Phase"]: 

1972 return self._phases 

1973 

1974 def __contains__(self, key: Any) -> bool: 

1975 if not issubclass(key, Phase): 1975 ↛ 1976line 1975 didn't jump to line 1976 because the condition on line 1975 was never true

1976 ex = TypeError(f"Parameter 'key' is not a Phase.") 

1977 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

1978 raise ex 

1979 

1980 return key in self._phases 

1981 

1982 def __getitem__(self, key: Type["Phase"]) -> "Phase": 

1983 try: 

1984 return self._phases[key] 

1985 except KeyError as ex: 

1986 raise PhaseNotPresentException(F"Phase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex 

1987 

1988 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

1989 line = yield from self._TaskStart(line) 

1990 

1991 activeParsers: List[Phase] = list(self._phases.values()) 

1992 

1993 while True: 

1994 while True: 

1995 if line._kind is LineKind.Empty: 

1996 line = yield line 

1997 continue 

1998 elif isinstance(line, VivadoMessage): 

1999 self._AddMessage(line) 

2000 elif line.StartsWith("Phase "): 

2001 for parser in activeParsers: # type: Phase 2001 ↛ 2006line 2001 didn't jump to line 2006 because the loop on line 2001 didn't complete

2002 if (match := parser._START.match(line._message)) is not None: 

2003 line = yield next(phase := parser.Generator(line)) 

2004 break 

2005 else: 

2006 WarningCollector.Raise(UnknownPhase(f"Unknown phase: '{line!r}'", line)) 

2007 ex = Exception(f"How to recover from here? Unknown phase: '{line!r}'") 

2008 ex.add_note(f"Current task: start pattern='{self}'") 

2009 ex.add_note(f"Current command: {self._command}") 

2010 raise ex 

2011 break 

2012 elif line.StartsWith("Ending"): 

2013 nextLine = yield from self._TaskFinish(line) 

2014 return nextLine 

2015 elif line.StartsWith(self._TIME): 

2016 line._kind = LineKind.TaskTime 

2017 nextLine = yield line 

2018 return nextLine 

2019 

2020 line = yield line 

2021 

2022 while True: 

2023 isFinish = False #line.StartsWith("Ending") 

2024 

2025 try: 

2026 processedLine = phase.send(line) 

2027 

2028 if isinstance(processedLine, VivadoMessage): 

2029 self._AddMessage(processedLine) 

2030 

2031 if isFinish: 2031 ↛ 2032line 2031 didn't jump to line 2032 because the condition on line 2031 was never true

2032 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine)) 

2033 line = yield processedLine 

2034 break 

2035 except StopIteration as ex: 

2036 activeParsers.remove(parser) 

2037 line = ex.value 

2038 break 

2039 

2040 line = yield processedLine 

2041 

2042 

2043@export 

2044class Phase(BaseParser, VivadoMessagesMixin): 

2045 # _NAME: ClassVar[str] 

2046 # _START: ClassVar[str] 

2047 # _FINISH: ClassVar[str] 

2048 # _TIME: ClassVar[str] = "Time (s):" 

2049 # _FINAL: ClassVar[Nullable[str]] = None 

2050 

2051 _task: TaskWithPhases 

2052 _phaseIndex: int 

2053 _duration: float 

2054 

2055 def __init__(self, task: TaskWithPhases) -> None: 

2056 super().__init__() 

2057 VivadoMessagesMixin.__init__(self) 

2058 

2059 self._task = task 

2060 self._phaseIndex = None 

2061 

2062 @readonly 

2063 def Task(self) -> TaskWithPhases: 

2064 return self._task 

2065 

2066 def _PhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2067 if (match := self._START.match(line._message)) is None: 2067 ↛ 2068line 2067 didn't jump to line 2068 because the condition on line 2067 was never true

2068 raise ProcessorException(f"{self.__class__.__name__}._PhaseStart(): Expected '{self._START}' at line {line._lineNumber}.") 

2069 

2070 self._phaseIndex = int(match["major"]) 

2071 

2072 line._kind = LineKind.PhaseStart 

2073 nextLine = yield line 

2074 return nextLine 

2075 

2076 def _PhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]: 

2077 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex) 

2078 if not line.StartsWith(FINISH): 2078 ↛ 2079line 2078 didn't jump to line 2079 because the condition on line 2078 was never true

2079 raise ProcessorException(f"{self.__class__.__name__}._PhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.") 

2080 

2081 line._kind = LineKind.PhaseEnd 

2082 line = yield line 

2083 

2084 if self._TIME is not None: 2084 ↛ 2096line 2084 didn't jump to line 2096 because the condition on line 2084 was always true

2085 while True: 

2086 if line.StartsWith(self._TIME): 

2087 line._kind = LineKind.PhaseTime 

2088 break 

2089 elif isinstance(line, VivadoMessage): 2089 ↛ 2090line 2089 didn't jump to line 2090 because the condition on line 2089 was never true

2090 self._AddMessage(line) 

2091 

2092 line = yield line 

2093 

2094 line = yield line 

2095 

2096 if self._FINAL is not None and self._task._command._processor._preamble._toolVersion >= "2023.2": 

2097 while True: 

2098 if line.StartsWith(self._FINAL): 2098 ↛ 2101line 2098 didn't jump to line 2101 because the condition on line 2098 was always true

2099 line._kind = LineKind.PhaseFinal 

2100 break 

2101 elif isinstance(line, VivadoMessage): 

2102 self._AddMessage(line) 

2103 

2104 line = yield line 

2105 

2106 line = yield line 

2107 

2108 # TODO: optionally collect following INFO messages like 31-389, 31-1021, 31-662 

2109 

2110 return line 

2111 

2112 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2113 line = yield from self._PhaseStart(line) 

2114 

2115 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex) 

2116 

2117 while True: 

2118 if line._kind is LineKind.Empty: 

2119 line = yield line 

2120 continue 

2121 elif isinstance(line, VivadoMessage): 

2122 self._AddMessage(line) 

2123 elif line.StartsWith(FINISH): 

2124 break 

2125 

2126 line = yield line 

2127 

2128 nextLine = yield from self._PhaseFinish(line) 

2129 return nextLine 

2130 

2131 def __str__(self) -> str: 

2132 return f"{self.__class__.__name__}: {self._START.pattern}" 

2133 

2134 

2135@export 

2136class PhaseWithChildren(Phase): 

2137 _SUBPHASE_PREFIX: ClassVar[str] = "Phase {phaseIndex}." 

2138 

2139 _subPhases: Dict[Type["SubPhase"], "SubPhase"] 

2140 

2141 def __init__(self, task: TaskWithPhases) -> None: 

2142 super().__init__(task) 

2143 

2144 self._subPhases = {p: p(self) for p in self._PARSERS} 

2145 

2146 def __contains__(self, key: Any) -> bool: 

2147 if not issubclass(key, SubPhase): 

2148 ex = TypeError(f"Parameter 'item' is not a SubPhase.") 

2149 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

2150 raise ex 

2151 

2152 return key in self._subPhases 

2153 

2154 def __getitem__(self, key: Type["SubPhase"]) -> "SubPhase": 

2155 try: 

2156 return self._subPhases[key] 

2157 except KeyError as ex: 

2158 raise PhaseNotPresentException(F"SubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex 

2159 

2160 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2161 line = yield from self._PhaseStart(line) 

2162 

2163 activeParsers: List[SubPhase] = list(self._subPhases.values()) 

2164 

2165 SUBPHASE_PREFIX = self._SUBPHASE_PREFIX.format(phaseIndex=self._phaseIndex) 

2166 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex) 

2167 

2168 while True: 

2169 while True: 

2170 if line._kind is LineKind.Empty: 

2171 line = yield line 

2172 continue 

2173 elif isinstance(line, VivadoMessage): 

2174 self._AddMessage(line) 

2175 elif line.StartsWith(SUBPHASE_PREFIX): 

2176 for parser in activeParsers: # type: Section 2176 ↛ 2181line 2176 didn't jump to line 2181 because the loop on line 2176 didn't complete

2177 if (match := parser._START.match(line._message)) is not None: 

2178 line = yield next(phase := parser.Generator(line)) 

2179 break 

2180 else: 

2181 WarningCollector.Raise(UnknownSubPhase(f"Unknown subphase: '{line!r}'", line)) 

2182 ex = Exception(f"How to recover from here? Unknown subphase: '{line!r}'") 

2183 ex.add_note(f"Current phase: start pattern='{self}'") 

2184 ex.add_note(f"Current task: start pattern='{self._task}'") 

2185 ex.add_note(f"Current command: {self._task._command}") 

2186 raise ex 

2187 break 

2188 elif line.StartsWith(FINISH): 

2189 nextLine = yield from self._PhaseFinish(line) 

2190 return nextLine 

2191 

2192 line = yield line 

2193 

2194 while True: 

2195 isFinish = False # line.StartsWith(SUBPHASE_PREFIX) # FIXME: detect end, but end (e.g. time) is later then ending text 

2196 

2197 try: 

2198 processedLine = phase.send(line) 

2199 

2200 if isinstance(processedLine, VivadoMessage): 

2201 self._AddMessage(processedLine) 

2202 

2203 if isFinish: 2203 ↛ 2204line 2203 didn't jump to line 2204 because the condition on line 2203 was never true

2204 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine)) 

2205 line = yield processedLine 

2206 break 

2207 except StopIteration as ex: 

2208 activeParsers.remove(parser) 

2209 line = ex.value 

2210 break 

2211 

2212 line = yield processedLine 

2213 

2214 

2215@export 

2216class SubPhase(BaseParser, VivadoMessagesMixin): 

2217 # _NAME: ClassVar[str] 

2218 # _START: ClassVar[str] 

2219 # _FINISH: ClassVar[str] 

2220 

2221 _phase: Phase 

2222 _phaseIndex: int 

2223 _subPhaseIndex: int 

2224 _duration: float 

2225 

2226 def __init__(self, phase: Phase) -> None: 

2227 super().__init__() 

2228 VivadoMessagesMixin.__init__(self) 

2229 

2230 self._phase = phase 

2231 self._phaseIndex = None 

2232 self._subPhaseIndex = None 

2233 

2234 def _SubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2235 if (match := self._START.match(line._message)) is None: 2235 ↛ 2236line 2235 didn't jump to line 2236 because the condition on line 2235 was never true

2236 raise ProcessorException(f"{self.__class__.__name__}._SubPhaseStart(): Expected '{self._START}' at line {line._lineNumber}.") 

2237 

2238 self._phaseIndex = int(match["major"]) 

2239 self._subPhaseIndex = int(match["minor"]) 

2240 

2241 line._kind = LineKind.SubPhaseStart 

2242 nextLine = yield line 

2243 return nextLine 

2244 

2245 def _SubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]: 

2246 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex) 

2247 

2248 if not line.StartsWith(FINISH): 2248 ↛ 2249line 2248 didn't jump to line 2249 because the condition on line 2248 was never true

2249 raise ProcessorException(f"{self.__class__.__name__}._SubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.") 

2250 

2251 if self._TIME is None: 

2252 line._kind = LineKind.SubPhaseTime 

2253 else: 

2254 line._kind = LineKind.SubPhaseEnd 

2255 

2256 line = yield line 

2257 while self._TIME is not None: 2257 ↛ 2264line 2257 didn't jump to line 2264 because the condition on line 2257 was always true

2258 if line.StartsWith(self._TIME): 

2259 line._kind = LineKind.SubPhaseTime 

2260 break 

2261 

2262 line = yield line 

2263 

2264 nextLine = yield line 

2265 return nextLine 

2266 

2267 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2268 line = yield from self._SubPhaseStart(line) 

2269 

2270 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex) 

2271 

2272 while True: 

2273 if line._kind is LineKind.Empty: 

2274 line = yield line 

2275 continue 

2276 elif line.StartsWith(FINISH): 

2277 break 

2278 elif isinstance(line, VivadoMessage): 

2279 self._AddMessage(line) 

2280 

2281 line = yield line 

2282 

2283 nextLine = yield from self._SubPhaseFinish(line) 

2284 return nextLine 

2285 

2286 def __str__(self) -> str: 

2287 return f"{self.__class__.__name__}: {self._START.pattern}" 

2288 

2289 

2290@export 

2291class SubPhaseWithChildren(SubPhase): 

2292 _subSubPhases: Dict[Type["SubSubPhase"], "SubSubPhase"] 

2293 

2294 def __init__(self, phase: Phase) -> None: 

2295 super().__init__(phase) 

2296 

2297 self._subSubPhases = {p: p(self) for p in self._PARSERS} 

2298 

2299 def __contains__(self, key: Any) -> bool: 

2300 if not issubclass(key, SubSubPhase): 

2301 ex = TypeError(f"Parameter 'item' is not a SubSubPhase.") 

2302 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

2303 raise ex 

2304 

2305 return key in self._subSubPhases 

2306 

2307 def __getitem__(self, key: Type["SubSubPhase"]) -> "SubSubPhase": 

2308 try: 

2309 return self._subSubPhases[key] 

2310 except KeyError as ex: 

2311 raise PhaseNotPresentException(F"SubSubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex 

2312 

2313 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2314 line = yield from self._SubPhaseStart(line) 

2315 

2316 activeParsers: List["SubSubPhase"] = list(self._subSubPhases.values()) 

2317 

2318 START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}." 

2319 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex) 

2320 

2321 while True: 

2322 while True: 

2323 if line._kind is LineKind.Empty: 

2324 line = yield line 

2325 continue 

2326 elif isinstance(line, VivadoMessage): 

2327 self._AddMessage(line) 

2328 elif line.StartsWith(START_PREFIX): 

2329 for parser in activeParsers: # type: SubSubPhase 2329 ↛ 2334line 2329 didn't jump to line 2334 because the loop on line 2329 didn't complete

2330 if (match := parser._START.match(line._message)) is not None: 

2331 line = yield next(phase := parser.Generator(line)) 

2332 break 

2333 else: 

2334 WarningCollector.Raise(UnknownSubPhase(f"Unknown subsubphase: '{line!r}'", line)) 

2335 ex = Exception(f"How to recover from here? Unknown subsubphase: '{line!r}'") 

2336 ex.add_note(f"Current subphase: start pattern='{self}'") 

2337 ex.add_note(f"Current phase: start pattern='{self._phase}'") 

2338 ex.add_note(f"Current task: start pattern='{self._phase._task}'") 

2339 ex.add_note(f"Current cmd: {self._phase._task._command}") 

2340 raise ex 

2341 break 

2342 elif line.StartsWith(FINISH): 2342 ↛ 2346line 2342 didn't jump to line 2346 because the condition on line 2342 was always true

2343 nextLine = yield from self._SubPhaseFinish(line) 

2344 return nextLine 

2345 

2346 line = yield line 

2347 

2348 while True: 

2349 isFinish = False # line.StartsWith("Ending") 

2350 

2351 try: 

2352 processedLine = phase.send(line) 

2353 

2354 if isinstance(processedLine, VivadoMessage): 

2355 self._AddMessage(processedLine) 

2356 

2357 if isFinish: 2357 ↛ 2358line 2357 didn't jump to line 2358 because the condition on line 2357 was never true

2358 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine)) 

2359 line = yield processedLine 

2360 break 

2361 except StopIteration as ex: 

2362 activeParsers.remove(parser) 

2363 line = ex.value 

2364 break 

2365 

2366 line = yield processedLine 

2367 

2368 

2369@export 

2370class SubSubPhase(BaseParser, VivadoMessagesMixin): 

2371 # _NAME: ClassVar[str] 

2372 # _START: ClassVar[str] 

2373 # _FINISH: ClassVar[str] 

2374 

2375 _subphase: SubPhase 

2376 _phaseIndex: int 

2377 _subPhaseIndex: int 

2378 _subSubPhaseIndex: int 

2379 _duration: float 

2380 

2381 def __init__(self, subphase: SubPhase) -> None: 

2382 super().__init__() 

2383 VivadoMessagesMixin.__init__(self) 

2384 

2385 self._subphase = subphase 

2386 self._phaseIndex = None 

2387 self._subPhaseIndex = None 

2388 self._subSubPhaseIndex = None 

2389 

2390 def _SubSubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2391 if (match := self._START.match(line._message)) is None: 2391 ↛ 2392line 2391 didn't jump to line 2392 because the condition on line 2391 was never true

2392 raise ProcessorException() 

2393 

2394 self._phaseIndex = int(match["major"]) 

2395 self._subPhaseIndex = int(match["minor"]) 

2396 self._subSubPhaseIndex = int(match["micro"]) 

2397 

2398 line._kind = LineKind.SubSubPhaseStart 

2399 nextLine = yield line 

2400 return nextLine 

2401 

2402 def _SubSubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]: 

2403 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex) 

2404 

2405 if not line.StartsWith(FINISH): 2405 ↛ 2406line 2405 didn't jump to line 2406 because the condition on line 2405 was never true

2406 raise ProcessorException(f"{self.__class__.__name__}._SubSubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.") 

2407 

2408 line._kind = LineKind.SubSubPhaseEnd 

2409 line = yield line 

2410 

2411 while self._TIME is not None: 2411 ↛ 2418line 2411 didn't jump to line 2418 because the condition on line 2411 was always true

2412 if line.StartsWith(self._TIME): 

2413 line._kind = LineKind.SubSubPhaseTime 

2414 break 

2415 

2416 line = yield line 

2417 

2418 nextLine = yield line 

2419 return nextLine 

2420 

2421 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2422 line = yield from self._SubSubPhaseStart(line) 

2423 

2424 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex) 

2425 

2426 while True: 

2427 if line._kind is LineKind.Empty: 

2428 line = yield line 

2429 continue 

2430 elif line.StartsWith(FINISH): 

2431 break 

2432 elif isinstance(line, VivadoMessage): 

2433 self._AddMessage(line) 

2434 

2435 line = yield line 

2436 

2437 nextLine = yield from self._SubSubPhaseFinish(line) 

2438 return nextLine 

2439 

2440 def __str__(self) -> str: 

2441 return f"{self.__class__.__name__}: {self._START.pattern}" 

2442 

2443 

2444@export 

2445class SubSubPhaseWithChildren(SubSubPhase): 

2446 _subSubSubPhases: Dict[Type["SubSubSubPhase"], "SubSubSubPhase"] 

2447 

2448 def __init__(self, subphase: SubPhase) -> None: 

2449 super().__init__(subphase) 

2450 

2451 self._subSubSubPhases = {p: p(self) for p in self._PARSERS} 

2452 

2453 def __contains__(self, key: Any) -> bool: 

2454 if not issubclass(key, SubSubSubPhase): 

2455 ex = TypeError(f"Parameter 'item' is not a SubSubSubPhase.") 

2456 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

2457 raise ex 

2458 

2459 return key in self._subSubSubPhases 

2460 

2461 def __getitem__(self, key: Type["SubSubSubPhase"]) -> "SubSubSubPhase": 

2462 try: 

2463 return self._subSubSubPhases[key] 

2464 except KeyError as ex: 

2465 raise PhaseNotPresentException(F"SubSubSubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex 

2466 

2467 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2468 line = yield from self._SubSubPhaseStart(line) 

2469 

2470 activeParsers: List["SubSubSubPhase"] = list(self._subSubSubPhases.values()) 

2471 

2472 START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}.{self._subSubPhaseIndex}." 

2473 

2474 while True: 

2475 while True: 

2476 if line._kind is LineKind.Empty: 

2477 line = yield line 

2478 continue 

2479 elif isinstance(line, VivadoMessage): 

2480 self._AddMessage(line) 

2481 elif line.StartsWith(START_PREFIX): 

2482 for parser in activeParsers: # type: SubSubSubPhase 2482 ↛ 2487line 2482 didn't jump to line 2487 because the loop on line 2482 didn't complete

2483 if (match := parser._START.match(line._message)) is not None: 2483 ↛ 2482line 2483 didn't jump to line 2482 because the condition on line 2483 was always true

2484 line = yield next(phase := parser.Generator(line)) 

2485 break 

2486 else: 

2487 WarningCollector.Raise(UnknownSubPhase(f"Unknown subsubsubphase: '{line!r}'", line)) 

2488 ex = Exception(f"How to recover from here? Unknown subsubsubphase: '{line!r}'") 

2489 ex.add_note(f"Current subsubphase: start pattern='{self}'") 

2490 ex.add_note(f"Current subphase: start pattern='{self._subphase}'") 

2491 ex.add_note(f"Current phase: start pattern='{self._subphase._phase}'") 

2492 ex.add_note(f"Current task: start pattern='{self._subphase._phase._task}'") 

2493 ex.add_note(f"Current cmd: {self._subphase._phase._task._command}") 

2494 raise ex 

2495 break 

2496 elif line.StartsWith(self._TIME): 

2497 line._kind = LineKind.SubSubPhaseTime 

2498 nextLine = yield line 

2499 return nextLine 

2500 

2501 line = yield line 

2502 

2503 while True: 

2504 isFinish = False # line.StartsWith("Ending") 

2505 

2506 try: 

2507 processedLine = phase.send(line) 

2508 

2509 if isinstance(processedLine, VivadoMessage): 

2510 self._AddMessage(processedLine) 

2511 

2512 if isFinish: 2512 ↛ 2513line 2512 didn't jump to line 2513 because the condition on line 2512 was never true

2513 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine)) 

2514 line = yield processedLine 

2515 break 

2516 except StopIteration as ex: 

2517 activeParsers.remove(parser) 

2518 line = ex.value 

2519 break 

2520 

2521 line = yield processedLine 

2522 

2523 

2524@export 

2525class SubSubSubPhase(BaseParser, VivadoMessagesMixin): 

2526 # _NAME: ClassVar[str] 

2527 # _START: ClassVar[str] 

2528 # _FINISH: ClassVar[str] 

2529 

2530 _subsubphase: SubSubPhase 

2531 _phaseIndex: int 

2532 _subPhaseIndex: int 

2533 _subSubPhaseIndex: int 

2534 _subSubSubPhaseIndex: int 

2535 _duration: float 

2536 

2537 def __init__(self, subsubphase: SubSubPhase) -> None: 

2538 super().__init__() 

2539 VivadoMessagesMixin.__init__(self) 

2540 

2541 self._subsubphase = subsubphase 

2542 self._phaseIndex = None 

2543 self._subPhaseIndex = None 

2544 self._subSubPhaseIndex = None 

2545 self._subSubSubPhaseIndex = None 

2546 

2547 def _SubSubSubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2548 if (match := self._START.match(line._message)) is None: 2548 ↛ 2549line 2548 didn't jump to line 2549 because the condition on line 2548 was never true

2549 raise ProcessorException() 

2550 

2551 self._phaseIndex = int(match["major"]) 

2552 self._subPhaseIndex = int(match["minor"]) 

2553 self._subSubPhaseIndex = int(match["micro"]) 

2554 self._subSubSubPhaseIndex = int(match["nano"]) 

2555 

2556 line._kind = LineKind.SubSubSubPhaseStart 

2557 nextLine = yield line 

2558 return nextLine 

2559 

2560 def _SubSubSubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]: 

2561 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex, subSubSubPhaseIndex=self._subSubSubPhaseIndex) 

2562 

2563 if not line.StartsWith(FINISH): 2563 ↛ 2564line 2563 didn't jump to line 2564 because the condition on line 2563 was never true

2564 raise ProcessorException(f"{self.__class__.__name__}._SubSubSubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.") 

2565 

2566 line._kind = LineKind.SubSubSubPhaseEnd 

2567 line = yield line 

2568 

2569 while self._TIME is not None: 2569 ↛ 2576line 2569 didn't jump to line 2576 because the condition on line 2569 was always true

2570 if line.StartsWith(self._TIME): 

2571 line._kind = LineKind.SubSubSubPhaseTime 

2572 break 

2573 

2574 line = yield line 

2575 

2576 nextLine = yield line 

2577 return nextLine 

2578 

2579 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2580 line = yield from self._SubSubSubPhaseStart(line) 

2581 

2582 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex, subSubSubPhaseIndex=self._subSubSubPhaseIndex) 

2583 

2584 while True: 

2585 if line._kind is LineKind.Empty: 2585 ↛ 2586line 2585 didn't jump to line 2586 because the condition on line 2585 was never true

2586 line = yield line 

2587 continue 

2588 elif line.StartsWith(FINISH): 

2589 break 

2590 elif isinstance(line, VivadoMessage): 2590 ↛ 2593line 2590 didn't jump to line 2593 because the condition on line 2590 was always true

2591 self._AddMessage(line) 

2592 

2593 line = yield line 

2594 

2595 nextLine = yield from self._SubSubSubPhaseFinish(line) 

2596 return nextLine 

2597 

2598 def __str__(self) -> str: 

2599 return f"{self.__class__.__name__}: {self._START.pattern}" 

2600 

2601 

2602@export 

2603class SubSubSubPhaseWithTasks(SubSubSubPhase): 

2604 _nestedTasks: Dict[Type["NestedTask"], "NestedTask"] 

2605 

2606 def __init__(self, subsubphase: SubSubPhase) -> None: 

2607 super().__init__(subsubphase) 

2608 

2609 self._nestedTasks = {p: p(self) for p in self._PARSERS} 

2610 

2611 def __contains__(self, key: Any) -> bool: 

2612 if not issubclass(key, NestedTask): 

2613 ex = TypeError(f"Parameter 'key' is not a NestedTask.") 

2614 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

2615 raise ex 

2616 

2617 return key in self._nestedTasks 

2618 

2619 def __getitem__(self, key: Type["NestedTask"]) -> "NestedTask": 

2620 try: 

2621 return self._nestedTasks[key] 

2622 except KeyError as ex: 

2623 raise NestedTaskNotPresentException(F"NestedTask '{key._NAME}' not present in '{self._parent.logfile}'.") from ex 

2624 

2625 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2626 line = yield from self._SubSubSubPhaseStart(line) 

2627 

2628 activeParsers: List["NestedTask"] = list(self._nestedTasks.values()) 

2629 

2630 # START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}.{self._subSubPhaseIndex}." 

2631 

2632 while True: 

2633 while True: 

2634 if line._kind is LineKind.Empty: 

2635 line = yield line 

2636 continue 

2637 elif isinstance(line, VivadoMessage): 

2638 self._AddMessage(line) 

2639 elif line.StartsWith("Starting "): 

2640 for parser in activeParsers: # type: NestedTask 2640 ↛ 2645line 2640 didn't jump to line 2645 because the loop on line 2640 didn't complete

2641 if line.StartsWith(parser._START): 2641 ↛ 2640line 2641 didn't jump to line 2640 because the condition on line 2641 was always true

2642 line = yield next(phase := parser.Generator(line)) 

2643 break 

2644 else: 

2645 WarningCollector.Raise(UnknownSubPhase(f"Unknown NestedTask: '{line!r}'", line)) 

2646 ex = Exception(f"How to recover from here? Unknown NestedTask: '{line!r}'") 

2647 ex.add_note(f"Current subsubsubphase: start pattern='{self}'") 

2648 ex.add_note(f"Current subsubphase: start pattern='{self._subsubphase}'") 

2649 ex.add_note(f"Current subphase: start pattern='{self._subsubphase._subphase}'") 

2650 ex.add_note(f"Current phase: start pattern='{self._subsubphase._subphase._phase}'") 

2651 ex.add_note(f"Current task: start pattern='{self._subsubphase._subphase._phase._task}'") 

2652 ex.add_note(f"Current cmd: {self._subsubphase._subphase._phase._task._command}") 

2653 raise ex 

2654 break 

2655 elif line.StartsWith(self._TIME): 

2656 line._kind = LineKind.SubSubSubPhaseTime 

2657 nextLine = yield line 

2658 return nextLine 

2659 

2660 line = yield line 

2661 

2662 while True: 

2663 isFinish = False # line.StartsWith("Ending") 

2664 

2665 try: 

2666 processedLine = phase.send(line) 

2667 

2668 if isinstance(processedLine, VivadoMessage): 

2669 self._AddMessage(processedLine) 

2670 

2671 if isFinish: 2671 ↛ 2672line 2671 didn't jump to line 2672 because the condition on line 2671 was never true

2672 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine)) 

2673 line = yield processedLine 

2674 break 

2675 except StopIteration as ex: 

2676 activeParsers.remove(parser) 

2677 line = ex.value 

2678 break 

2679 

2680 line = yield processedLine 

2681 

2682 

2683@export 

2684class NestedTask(BaseParser, VivadoMessagesMixin): 

2685 # _NAME: ClassVar[str] 

2686 # _START: ClassVar[str] 

2687 # _FINISH: ClassVar[str] 

2688 # _TIME: ClassVar[str] = "Time (s):" 

2689 

2690 _subsubsubphase: SubSubSubPhaseWithTasks 

2691 _duration: float 

2692 

2693 def __init__(self, subsubsubphase: SubSubSubPhaseWithTasks) -> None: 

2694 super().__init__() 

2695 VivadoMessagesMixin.__init__(self) 

2696 

2697 self._subsubsubphase = subsubsubphase 

2698 

2699 @readonly 

2700 def SubSubSubPhase(self) -> SubSubSubPhaseWithTasks: 

2701 return self._subsubsubphase 

2702 

2703 def _NestedTaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2704 if not line.StartsWith(self._START): 2704 ↛ 2705line 2704 didn't jump to line 2705 because the condition on line 2704 was never true

2705 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.") 

2706 

2707 line._kind = LineKind.NestedTaskStart 

2708 nextLine = yield line 

2709 return nextLine 

2710 

2711 def _NestedTaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2712 if not line.StartsWith(self._FINISH): 2712 ↛ 2713line 2712 didn't jump to line 2713 because the condition on line 2712 was never true

2713 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.") 

2714 

2715 line._kind = LineKind.NestedTaskEnd 

2716 line = yield line 

2717 

2718 if self._TIME is not None: 2718 ↛ 2727line 2718 didn't jump to line 2727 because the condition on line 2718 was always true

2719 while True: 

2720 if line.StartsWith(self._TIME): 

2721 line._kind = LineKind.TaskTime 

2722 line = yield line 

2723 break 

2724 

2725 line = yield line 

2726 

2727 return line 

2728 

2729 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2730 line = yield from self._NestedTaskStart(line) 

2731 

2732 while True: 

2733 if line._kind is LineKind.Empty: 

2734 line = yield line 

2735 continue 

2736 elif self._FINISH is not None and line.StartsWith("Ending"): 

2737 break 

2738 elif isinstance(line, VivadoMessage): 

2739 self._AddMessage(line) 

2740 elif line.StartsWith(self._TIME): 

2741 line._kind = LineKind.TaskTime 

2742 nextLine = yield line 

2743 return nextLine 

2744 

2745 line = yield line 

2746 

2747 nextLine = yield from self._NestedTaskFinish(line) 

2748 return nextLine 

2749 

2750 def __str__(self) -> str: 

2751 return self._NAME 

2752 

2753 

2754@export 

2755class NestedTaskWithPhases(NestedTask): 

2756 """ 

2757 A task's output emitted by a Vivado command. 

2758 

2759 .. rubric:: Extracted information 

2760 

2761 * Vivado messages (info, warning, critical warning, error). 

2762 * Nested phases 

2763 

2764 .. rubric:: Example 

2765 

2766 .. code-block:: 

2767 

2768 Phase 4.1.1.1 BUFG Insertion 

2769 

2770 Starting Physical Synthesis Task 

2771 

2772 Phase 1 Physical Synthesis Initialization 

2773 INFO: [Physopt 32-721] Multithreading enabled for phys_opt_design using a maximum of 2 CPUs 

2774 INFO: [Physopt 32-619] Estimated Timing Summary | WNS=-0.733 | TNS=-0.936 | 

2775 Phase 1 Physical Synthesis Initialization | Checksum: 1818afcc0 

2776 

2777 Time (s): cpu = 00:00:00 ; elapsed = 00:00:00.014 . Memory (MB): peak = 1865.645 ; gain = 0.000 

2778 INFO: [Place 46-56] BUFG insertion identified 0 candidate nets. Inserted BUFG: 0, Replicated BUFG Driver: 0, Skipped due to Placement/Routing Conflicts: 0, Skipped due to Timing Degradation: 0, Skipped due to netlist editing failed: 0. 

2779 Ending Physical Synthesis Task | Checksum: 22839c186 

2780 

2781 Time (s): cpu = 00:00:00 ; elapsed = 00:00:00.016 . Memory (MB): peak = 1865.645 ; gain = 0.000 

2782 Phase 4.1.1.1 BUFG Insertion | Checksum: 1a8cbaaf2 

2783 """ 

2784 # _PARSERS: ClassVar[Tuple[Type["SubTask"], ...]] 

2785 

2786 _nestedPhases: Dict[Type["NestedPhase"], "NestedPhase"] 

2787 

2788 def __init__(self, subsubsubPhase: SubSubSubPhaseWithTasks) -> None: 

2789 super().__init__(subsubsubPhase) 

2790 

2791 self._nestedPhases = {p: p(self) for p in self._PARSERS} 

2792 

2793 @readonly 

2794 def NestedPhases(self) -> Dict[Type["NestedPhase"], "NestedPhase"]: 

2795 return self._nestedPhases 

2796 

2797 def __contains__(self, key: Any) -> bool: 

2798 if not issubclass(key, NestedPhase): 

2799 ex = TypeError(f"Parameter 'key' is not a NestedPhase.") 

2800 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

2801 raise ex 

2802 

2803 return key in self._nestedPhases 

2804 

2805 def __getitem__(self, key: Type["NestedPhase"]) -> "NestedPhase": 

2806 try: 

2807 return self._nestedPhases[key] 

2808 except KeyError as ex: 

2809 raise SubTaskNotPresentException(F"NestedPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex 

2810 

2811 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2812 line = yield from self._NestedTaskStart(line) 

2813 

2814 activeParsers: List[Phase] = list(self._nestedPhases.values()) 

2815 

2816 while True: 

2817 while True: 

2818 if line._kind is LineKind.Empty: 

2819 line = yield line 

2820 continue 

2821 elif isinstance(line, VivadoMessage): 

2822 self._AddMessage(line) 

2823 elif line.StartsWith("Phase "): 

2824 for parser in activeParsers: # type: NestedPhase 2824 ↛ 2829line 2824 didn't jump to line 2829 because the loop on line 2824 didn't complete

2825 if (match := parser._START.match(line._message)) is not None: 2825 ↛ 2824line 2825 didn't jump to line 2824 because the condition on line 2825 was always true

2826 line = yield next(phase := parser.Generator(line)) 

2827 break 

2828 else: 

2829 WarningCollector.Raise(UnknownSubPhase(f"Unknown NestedPhase: '{line!r}'", line)) 

2830 ex = Exception(f"How to recover from here? Unknown NestedPhase: '{line!r}'") 

2831 ex.add_note(f"Current nestedtask: start pattern='{self}'") 

2832 ex.add_note(f"Current subsubsubphase: start pattern='{self._subsubsubphase}'") 

2833 ex.add_note(f"Current subsubphase: start pattern='{self._subsubsubphase._subsubphase}'") 

2834 ex.add_note(f"Current subphase: start pattern='{self._subsubsubphase._subsubphase._subphase}'") 

2835 ex.add_note(f"Current phase: start pattern='{self._subsubsubphase._subsubphase._subphase._phase}'") 

2836 ex.add_note(f"Current task: start pattern='{self._subsubsubphase._subsubphase._subphase._phase._task}'") 

2837 ex.add_note(f"Current cmd: {self._subsubsubphase._subsubphase._subphase._phase._task._command}") 

2838 raise ex 

2839 break 

2840 elif line.StartsWith("Ending"): 2840 ↛ 2843line 2840 didn't jump to line 2843 because the condition on line 2840 was always true

2841 nextLine = yield from self._NestedTaskFinish(line) 

2842 return nextLine 

2843 elif line.StartsWith(self._TIME): 

2844 line._kind = LineKind.TaskTime 

2845 nextLine = yield line 

2846 return nextLine 

2847 

2848 line = yield line 

2849 

2850 while True: 

2851 isFinish = False # line.StartsWith("Ending") # FIXME: detect end, but time might come later 

2852 

2853 try: 

2854 processedLine = phase.send(line) 

2855 

2856 if isinstance(processedLine, VivadoMessage): 

2857 self._AddMessage(processedLine) 

2858 

2859 if isFinish: 2859 ↛ 2860line 2859 didn't jump to line 2860 because the condition on line 2859 was never true

2860 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine)) 

2861 line = yield processedLine 

2862 break 

2863 except StopIteration as ex: 

2864 activeParsers.remove(parser) 

2865 line = ex.value 

2866 break 

2867 

2868 line = yield processedLine 

2869 

2870 

2871@export 

2872class NestedPhase(BaseParser, VivadoMessagesMixin): 

2873 # _NAME: ClassVar[str] 

2874 # _START: ClassVar[str] 

2875 # _FINISH: ClassVar[str] 

2876 # _TIME: ClassVar[str] = "Time (s):" 

2877 _FINAL: ClassVar[Nullable[str]] = None 

2878 

2879 _nestedTask: NestedTaskWithPhases 

2880 _phaseIndex: int 

2881 _duration: float 

2882 

2883 def __init__(self, nestedTask: TaskWithPhases) -> None: 

2884 super().__init__() 

2885 VivadoMessagesMixin.__init__(self) 

2886 

2887 self._nestedTask = nestedTask 

2888 self._phaseIndex = None 

2889 

2890 @readonly 

2891 def NestedTask(self) -> NestedTaskWithPhases: 

2892 return self._nestedTask 

2893 

2894 def _NestedPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2895 if (match := self._START.match(line._message)) is None: 2895 ↛ 2896line 2895 didn't jump to line 2896 because the condition on line 2895 was never true

2896 raise ProcessorException(f"{self.__class__.__name__}._PhaseStart(): Expected '{self._START}' at line {line._lineNumber}.") 

2897 

2898 self._phaseIndex = int(match["major"]) 

2899 

2900 line._kind = LineKind.NestedPhaseStart 

2901 nextLine = yield line 

2902 return nextLine 

2903 

2904 def _NestedPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]: 

2905 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex) 

2906 if not line.StartsWith(FINISH): 2906 ↛ 2907line 2906 didn't jump to line 2907 because the condition on line 2906 was never true

2907 raise ProcessorException(f"{self.__class__.__name__}._PhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.") 

2908 

2909 line._kind = LineKind.NestedPhaseEnd 

2910 line = yield line 

2911 

2912 if self._TIME is not None: 2912 ↛ 2924line 2912 didn't jump to line 2924 because the condition on line 2912 was always true

2913 while True: 

2914 if line.StartsWith(self._TIME): 

2915 line._kind = LineKind.PhaseTime 

2916 break 

2917 elif isinstance(line, VivadoMessage): 2917 ↛ 2918line 2917 didn't jump to line 2918 because the condition on line 2917 was never true

2918 self._AddMessage(line) 

2919 

2920 line = yield line 

2921 

2922 line = yield line 

2923 

2924 return line 

2925 

2926 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]: 

2927 line = yield from self._NestedPhaseStart(line) 

2928 

2929 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex) 

2930 

2931 while True: 

2932 if line._kind is LineKind.Empty: 2932 ↛ 2933line 2932 didn't jump to line 2933 because the condition on line 2932 was never true

2933 line = yield line 

2934 continue 

2935 elif isinstance(line, VivadoMessage): 

2936 self._AddMessage(line) 

2937 elif line.StartsWith(FINISH): 2937 ↛ 2940line 2937 didn't jump to line 2940 because the condition on line 2937 was always true

2938 break 

2939 

2940 line = yield line 

2941 

2942 nextLine = yield from self._NestedPhaseFinish(line) 

2943 return nextLine 

2944 

2945 def __str__(self) -> str: 

2946 return f"{self.__class__.__name__}: {self._START.pattern}" 

2947 

2948 

2949@export 

2950class Synth_Design(CommandWithSections): 

2951 """ 

2952 A Vivado command output parser for ``synth_design``. 

2953 """ 

2954 from . import SynthesizeDesign as _SynthDesign 

2955 

2956 _TCL_COMMAND: ClassVar[str] = "synth_design" 

2957 _PARSERS: ClassVar[Tuple[Type[Section], ...]] = ( 

2958 _SynthDesign.RTLElaboration, 

2959 _SynthDesign.HandlingCustomAttributes, 

2960 _SynthDesign.ConstraintValidation, 

2961 _SynthDesign.LoadingPart, 

2962 _SynthDesign.ApplySetPropertyXDCConstraints, 

2963 _SynthDesign.RTLComponentStatistics, 

2964 _SynthDesign.RTLHierarchicalComponentStatistics, 

2965 _SynthDesign.PartResourceSummary, 

2966 _SynthDesign.CrossBoundaryAndAreaOptimization, 

2967 _SynthDesign.ROM_RAM_DSP_SR_Retiming, 

2968 _SynthDesign.ApplyingXDCTimingConstraints, 

2969 _SynthDesign.TimingOptimization, 

2970 _SynthDesign.TechnologyMapping, 

2971 _SynthDesign.IOInsertion, 

2972 _SynthDesign.FlatteningBeforeIOInsertion, 

2973 _SynthDesign.FinalNetlistCleanup, 

2974 _SynthDesign.RenamingGeneratedInstances, 

2975 _SynthDesign.RebuildingUserHierarchy, 

2976 _SynthDesign.RenamingGeneratedPorts, 

2977 _SynthDesign.RenamingGeneratedNets, 

2978 _SynthDesign.WritingSynthesisReport, 

2979 ) 

2980 

2981 @readonly 

2982 def HasLatches(self) -> bool: 

2983 """ 

2984 Read-only property returning if synthesis inferred latches into the design. 

2985 

2986 Latch detection is based on: 

2987 

2988 * Vivado message ``synth 8-327`` 

2989 * Cells of lind ``LD`` listed in the *Cell Usage* report. 

2990 

2991 :returns: True, if the design contains latches. 

2992 """ 

2993 from .SynthesizeDesign import WritingSynthesisReport 

2994 

2995 if (8 in self._messagesByID) and (327 in self._messagesByID[8]): 

2996 return True 

2997 

2998 return "LD" in self._sections[WritingSynthesisReport]._cells 

2999 

3000 @readonly 

3001 def Latches(self) -> List[VivadoMessage]: 

3002 """ 

3003 Read-only property to access a list of Vivado output messages for inferred latches. 

3004 

3005 :returns: A list of Vivado messages for interred latches. 

3006 

3007 .. note:: 

3008 

3009 This returns ``[Synth 8-327]`` messages. 

3010 

3011 .. code-block:: 

3012 

3013 WARNING: [Synth 8-327] inferring latch for variable 'Q_reg' 

3014 """ 

3015 if 8 in self._messagesByID: 

3016 if 327 in (synthMessages := self._messagesByID[8]): 

3017 return [message for message in synthMessages[327]] 

3018 

3019 return [] 

3020 

3021 @readonly 

3022 def Statemachines(self) -> Dict[str, List[str]]: 

3023 """ 

3024 

3025 :returns: undocumented 

3026 

3027 .. note:: 

3028 

3029 INFO: [Synth 8-802] inferred FSM for state register 'State_reg' in module 'stream_Padder' 

3030 INFO: [Synth 8-802] inferred FSM for state register 'RX_blk.blkRXFSM.State_reg' in module 'eth_XGEMAC_XGMII' 

3031 """ 

3032 

3033 @readonly 

3034 def DistributedRAMs(self) -> Dict[str, Any]: 

3035 """ 

3036 

3037 :returns: undocumented 

3038 """ 

3039 

3040 @readonly 

3041 def BlockRAMs(self) -> Dict[str, Any]: 

3042 """ 

3043 

3044 :returns: undocumented 

3045 """ 

3046 

3047 

3048 @readonly 

3049 def UltraRAMs(self) -> Dict[str, Any]: 

3050 """ 

3051 

3052 :returns: undocumented 

3053 """ 

3054 

3055 @readonly 

3056 def ShiftRegister(self) -> Dict[str, Dict[str, Any]]: 

3057 """ 

3058 

3059 :returns: undocumented 

3060 

3061 .. note:: 

3062 

3063 Static Shift Register Report: 

3064 Dynamic Shift Register Report: 

3065 """ 

3066 

3067 @readonly 

3068 def UndrivenPins(self) -> Dict[str, str]: 

3069 """ 

3070 

3071 :returns: undocumented 

3072 

3073 .. note:: 

3074 

3075 WARNING: [Synth 8-3295] tying undriven pin AXI4FullPipeLine_M2S[AWID]_inferred:in0 to constant 0 

3076 """ 

3077 

3078 # ignored instructions 

3079 # ignored ram_style WARNING: [Synth 8-5791] The ram_style = "ultra" set on RAM "ocram_sdp__parameterized4:/gInfer.ram_reg" is ignored because clocks on ports do not match. 

3080 

3081 @readonly 

3082 def HasBlackboxes(self) -> bool: 

3083 """ 

3084 Read-only property returning if the design contains black-boxes. 

3085 

3086 :returns: True, if the design contains black-boxes. 

3087 """ 

3088 from .SynthesizeDesign import WritingSynthesisReport 

3089 

3090 return len(self._sections[WritingSynthesisReport]._blackboxes) > 0 

3091 

3092 @readonly 

3093 def Blackboxes(self) -> Dict[str, int]: 

3094 """ 

3095 Read-only property to access the dictionary of found blackbox statistics. 

3096 

3097 :returns: The dictionary of found blackbox statistics. 

3098 """ 

3099 from .SynthesizeDesign import WritingSynthesisReport 

3100 

3101 return self._sections[WritingSynthesisReport]._blackboxes 

3102 

3103 @readonly 

3104 def Cells(self) -> Dict[str, int]: 

3105 """ 

3106 Read-only property to access the dictionary of synthesized cell statistics. 

3107 

3108 :returns: The dictionary of used cell statistics. 

3109 """ 

3110 from .SynthesizeDesign import WritingSynthesisReport 

3111 

3112 return self._sections[WritingSynthesisReport]._cells 

3113 

3114 @readonly 

3115 def VHDLReportMessages(self) -> List[VHDLReportMessage]: 

3116 """ 

3117 Read-only property to access a list of Vivado output messages generated by VHDL report statement. 

3118 

3119 :returns: A list of VHDL report statement outputs. 

3120 

3121 .. note:: 

3122 

3123 This returns ``[Synth 8-6031]`` messages. 

3124 

3125 .. code-block:: 

3126 

3127 INFO: [Synth 8-6031] RTL report: "TimingToCycles(time, freq): period=10.000000 ns -- 0.000000 fs" [C:/[...]/StopWatch/src/Utilities.pkg.vhdl:118] 

3128 """ 

3129 if 8 in self._messagesByID: 

3130 if 6031 in (synthMessages := self._messagesByID[8]): 

3131 return [message for message in synthMessages[6031]] 

3132 

3133 return [] 

3134 

3135 @readonly 

3136 def VHDLAssertMessages(self) -> List[VHDLReportMessage]: 

3137 """ 

3138 Read-only property to access a list of Vivado output messages generated by VHDL assert statement. 

3139 

3140 :returns: A list of VHDL assert statement outputs. 

3141 

3142 .. note:: 

3143 

3144 This returns ``[Synth 8-63]`` messages. 

3145 

3146 .. code-block:: 

3147 

3148 INFO: [Synth 8-63] RTL assertion: "CLOCK_FREQ: 100.000000 ns" [C:/[...]/StopWatch/src/Debouncer.vhdl:28] 

3149 """ 

3150 if 8 in self._messagesByID: 

3151 if 63 in (synthMessages := self._messagesByID[8]): 

3152 return [message for message in synthMessages[63]] 

3153 

3154 return [] 

3155 

3156 def SectionDetector(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]: 

3157 from .SynthesizeDesign import RTLElaboration 

3158 

3159 if not (isinstance(line, VivadoTclCommand) and line._tclCommand == self._TCL_COMMAND): 3159 ↛ 3160line 3159 didn't jump to line 3160 because the condition on line 3159 was never true

3160 raise ProcessorException() # FIXME: add exception message 

3161 

3162 activeParsers: List[Section] = [p(self) for p in self._PARSERS] 

3163 rtlElaboration: RTLElaboration = next(p for p in activeParsers if isinstance(p, RTLElaboration)) 

3164 

3165 line = yield line 

3166 if line == "Starting synth_design": 3166 ↛ 3169line 3166 didn't jump to line 3169 because the condition on line 3166 was always true

3167 line._kind = LineKind.Verbose 

3168 else: 

3169 raise ProcessorException() # FIXME: add exception message 

3170 

3171 line = yield line 

3172 while True: 

3173 while True: 

3174 if line._kind is LineKind.Empty: 

3175 line = yield line 

3176 continue 

3177 elif isinstance(line, VivadoMessage): 

3178 self._AddMessage(line) 

3179 elif line.StartsWith("Start "): 

3180 for parser in activeParsers: # type: Section 3180 ↛ 3195line 3180 didn't jump to line 3195 because the loop on line 3180 didn't complete

3181 if line.StartsWith(parser._START): 

3182 # Found a suitable section parser. 

3183 # Add section parser to list of found sections. 

3184 # In case of duplicates, create a chain of psrer instances. 

3185 if parser not in self._sections: 

3186 self._sections.append(parser) 

3187 else: 

3188 parser._next = (newParser := parser.__class__(self)) 

3189 parser = newParser 

3190 

3191 line = next(section := parser.Generator(line)) 

3192 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter 

3193 break 

3194 else: 

3195 WarningCollector.Raise(UnknownSection(f"Unknown section: '{line!r}'", line)) 

3196 ex = Exception(f"How to recover from here? Unknown section: '{line!r}'") 

3197 # ex.add_note(f"Current task: start pattern='{self._task}'") 

3198 ex.add_note(f"Current cmd: {self}") 

3199 raise ex 

3200 break 

3201 elif line.StartsWith("Starting "): 

3202 if line.StartsWith(rtlElaboration._START): 3202 ↛ 3222line 3202 didn't jump to line 3222 because the condition on line 3202 was always true

3203 self._sections.append(parser := rtlElaboration) 

3204 line = next(section := parser.Generator(line)) 

3205 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter 

3206 break 

3207 elif line.StartsWith(self._TCL_COMMAND): 

3208 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3208 ↛ 3222line 3208 didn't jump to line 3222 because the condition on line 3208 was always true

3209 line._kind |= LineKind.Success 

3210 

3211 # FIXME: use similar style like for _TIME 

3212 line = yield line 

3213 lastLine = yield line 

3214 return lastLine 

3215 elif line.StartsWith("Finished RTL Optimization Phase"): 

3216 line._kind = LineKind.PhaseEnd 

3217 line._previousLine._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter 

3218 elif line.StartsWith("----"): 

3219 if LineKind.Phase in line._previousLine._kind: 

3220 line._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter 

3221 

3222 line = yield line 

3223 

3224 line = yield line 

3225 

3226 while True: 

3227 if line.StartsWith("Finished"): 

3228 l = line[9:] 

3229 if not (l.startswith("Flattening") or l.startswith("Final")): 

3230 line = yield section.send(line) 

3231 break 

3232 

3233 if isinstance(line, VivadoMessage): 

3234 self._AddMessage(line) 

3235 

3236 line = yield section.send(line) 

3237 

3238 line = yield section.send(line) 

3239 

3240 if not parser._DUPLICATES: 

3241 activeParsers.remove(parser) 

3242 

3243 

3244@export 

3245class Link_Design(Command): 

3246 """ 

3247 A Vivado command output parser for ``link_design``. 

3248 """ 

3249 _TCL_COMMAND: ClassVar[str] = "link_design" 

3250 _TIME: ClassVar[str] = "Time (s):" 

3251 

3252 _ParsingXDCFile_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\]$""") 

3253 _FinishedParsingXDCFile_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\]$""") 

3254 _ParsingXDCFileForCell_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\] for cell '(.*)'$""") 

3255 _FinishedParsingXDCFileForCell_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\] for cell '(.*)'$""") 

3256 

3257 _commonXDCFiles: Dict[Path, List[VivadoMessage]] 

3258 _perCellXDCFiles: Dict[Path, Dict[str, List[VivadoMessage]]] 

3259 

3260 def __init__(self, processor: "Processor") -> None: 

3261 super().__init__(processor) 

3262 

3263 self._commonXDCFiles = {} 

3264 self._perCellXDCFiles = {} 

3265 

3266 @readonly 

3267 def CommonXDCFiles(self) -> Dict[Path, List[VivadoMessage]]: 

3268 return self._commonXDCFiles 

3269 

3270 @readonly 

3271 def PerCellXDCFiles(self) -> Dict[Path, Dict[str, List[VivadoMessage]]]: 

3272 return self._perCellXDCFiles 

3273 

3274 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]: 

3275 line = yield from self._CommandStart(line) 

3276 

3277 end = f"{self._TCL_COMMAND} " 

3278 while True: 

3279 if line._kind is LineKind.Empty: 

3280 line = yield line 

3281 continue 

3282 elif isinstance(line, VivadoMessage): 

3283 self._AddMessage(line) 

3284 elif (match := self._ParsingXDCFile_Pattern.match(line._message)) is not None: 

3285 line._kind = LineKind.Normal 

3286 

3287 path = Path(match[1]) 

3288 self._commonXDCFiles[path] = (messages := []) 

3289 

3290 line = yield line 

3291 while True: 

3292 if line._kind is LineKind.Empty: 3292 ↛ 3293line 3292 didn't jump to line 3293 because the condition on line 3292 was never true

3293 line = yield line 

3294 continue 

3295 elif isinstance(line, VivadoMessage): 

3296 self._AddMessage(line) 

3297 messages.append(line) 

3298 elif (match := self._FinishedParsingXDCFile_Pattern.match(line._message)) is not None and path == Path(match[1]): 

3299 line._kind = LineKind.Normal 

3300 break 

3301 elif line.StartsWith("Finished Parsing XDC File"): 3301 ↛ 3302line 3301 didn't jump to line 3302 because the condition on line 3301 was never true

3302 line._kind = LineKind.ProcessorError 

3303 break 

3304 elif line.StartsWith(end): 3304 ↛ 3305line 3304 didn't jump to line 3305 because the condition on line 3304 was never true

3305 break 

3306 

3307 line = yield line 

3308 elif (match := self._ParsingXDCFileForCell_Pattern.match(line._message)) is not None: 

3309 line._kind = LineKind.Normal 

3310 

3311 path = Path(match[1]) 

3312 cell = match[2] 

3313 if path in self._perCellXDCFiles: 

3314 self._perCellXDCFiles[path][cell] = (messages := []) 

3315 else: 

3316 self._perCellXDCFiles[path] = {cell: (messages := [])} 

3317 

3318 line = yield line 

3319 while True: 

3320 if line._kind is LineKind.Empty: 3320 ↛ 3321line 3320 didn't jump to line 3321 because the condition on line 3320 was never true

3321 line = yield line 

3322 continue 

3323 elif isinstance(line, VivadoMessage): 

3324 self._AddMessage(line) 

3325 messages.append(line) 

3326 elif (match := self._FinishedParsingXDCFileForCell_Pattern.match(line._message)) is not None and path == Path(match[1]) and cell == match[2]: 

3327 line._kind = LineKind.Normal 

3328 break 

3329 elif line.StartsWith("Finished Parsing XDC File"): 3329 ↛ 3330line 3329 didn't jump to line 3330 because the condition on line 3329 was never true

3330 line._kind = LineKind.ProcessorError 

3331 break 

3332 elif line.StartsWith(end): 3332 ↛ 3333line 3332 didn't jump to line 3333 because the condition on line 3332 was never true

3333 break 

3334 

3335 line = yield line 

3336 

3337 if line.StartsWith(end): 

3338 nextLine = yield from self._CommandFinish(line) 

3339 return nextLine 

3340 

3341 line = yield line 

3342 

3343 

3344@export 

3345class Opt_Design(CommandWithTasks): 

3346 """ 

3347 A Vivado command output parser for ``opt_design``. 

3348 """ 

3349 from . import OptimizeDesign as _OptDesign 

3350 

3351 _TCL_COMMAND: ClassVar[str] = "opt_design" 

3352 _TIME: ClassVar[str] = None 

3353 

3354 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = ( 

3355 _OptDesign.DRCTask, 

3356 _OptDesign.CacheTimingInformationTask, 

3357 _OptDesign.LogicOptimizationTask, 

3358 _OptDesign.PowerOptimizationTask, 

3359 _OptDesign.FinalCleanupTask, 

3360 _OptDesign.NetlistObfuscationTask 

3361 ) 

3362 

3363 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]: 

3364 line = yield from self._CommandStart(line) 

3365 

3366 activeParsers: List[Task] = list(self._tasks.values()) 

3367 

3368 while True: 

3369 while True: 

3370 if line._kind is LineKind.Empty: 

3371 line = yield line 

3372 continue 

3373 elif isinstance(line, VivadoMessage): 

3374 self._AddMessage(line) 

3375 elif line.StartsWith("Starting ") and not line.StartsWith("Starting Connectivity Check Task"): 

3376 for parser in activeParsers: # type: Section 3376 ↛ 3381line 3376 didn't jump to line 3381 because the loop on line 3376 didn't complete

3377 if line.StartsWith(parser._START): 

3378 line = yield next(task := parser.Generator(line)) 

3379 break 

3380 else: 

3381 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line)) 

3382 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'") 

3383 # ex.add_note(f"Current task: start pattern='{self._task}'") 

3384 ex.add_note(f"Current cmd: {self}") 

3385 raise ex 

3386 break 

3387 elif line.StartsWith(self._TCL_COMMAND): 

3388 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3388 ↛ 3397line 3388 didn't jump to line 3397 because the condition on line 3388 was always true

3389 line._kind |= LineKind.Success 

3390 

3391 # FIXME: use similar style like for _TIME 

3392 line = yield line 

3393 lastLine = yield line 

3394 return lastLine 

3395 # line._kind = LineKind.Unprocessed 

3396 

3397 line = yield line 

3398 

3399 while True: 

3400 # if line.StartsWith("Ending"): 

3401 # line = yield task.send(line) 

3402 # break 

3403 

3404 if isinstance(line, VivadoMessage): 

3405 self._AddMessage(line) 

3406 

3407 try: 

3408 line = yield task.send(line) 

3409 except StopIteration as ex: 

3410 task = None 

3411 line = ex.value 

3412 

3413 if isinstance(line, VivadoMessage): 

3414 line = yield line 

3415 

3416 break 

3417 

3418 if task is not None: 3418 ↛ 3419line 3418 didn't jump to line 3419 because the condition on line 3418 was never true

3419 line = yield task.send(line) 

3420 

3421 activeParsers.remove(parser) 

3422 

3423 

3424@export 

3425class Place_Design(CommandWithTasks): 

3426 """ 

3427 A Vivado command output parser for ``place_design``. 

3428 """ 

3429 from . import PlaceDesign as _PlaceDesign 

3430 

3431 _TCL_COMMAND: ClassVar[str] = "place_design" 

3432 _TIME: ClassVar[str] = None 

3433 

3434 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = ( 

3435 _PlaceDesign.PlacerTask, 

3436 ) 

3437 

3438 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]: 

3439 line = yield from self._CommandStart(line) 

3440 

3441 activeParsers: List[Task] = list(self._tasks.values()) 

3442 

3443 while True: 

3444 while True: 

3445 if line._kind is LineKind.Empty: 

3446 line = yield line 

3447 continue 

3448 elif isinstance(line, VivadoMessage): 

3449 self._AddMessage(line) 

3450 elif line.StartsWith("Starting "): 

3451 for parser in activeParsers: # type: Section 3451 ↛ 3456line 3451 didn't jump to line 3456 because the loop on line 3451 didn't complete

3452 if line.StartsWith(parser._START): 3452 ↛ 3451line 3452 didn't jump to line 3451 because the condition on line 3452 was always true

3453 line = yield next(task := parser.Generator(line)) 

3454 break 

3455 else: 

3456 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line)) 

3457 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'") 

3458 # ex.add_note(f"Current task: start pattern='{self._task}'") 

3459 ex.add_note(f"Current cmd: {self}") 

3460 raise ex 

3461 break 

3462 elif line.StartsWith(self._TCL_COMMAND): 

3463 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3463 ↛ 3472line 3463 didn't jump to line 3472 because the condition on line 3463 was always true

3464 line._kind |= LineKind.Success 

3465 

3466 # FIXME: use similar style like for _TIME 

3467 line = yield line 

3468 lastLine = yield line 

3469 return lastLine 

3470 # line._kind = LineKind.Unprocessed 

3471 

3472 line = yield line 

3473 

3474 while True: 

3475 # if line.StartsWith("Ending"): 

3476 # line = yield task.send(line) 

3477 # break 

3478 

3479 if isinstance(line, VivadoMessage): 

3480 self._AddMessage(line) 

3481 

3482 try: 

3483 line = yield task.send(line) 

3484 except StopIteration as ex: 

3485 task = None 

3486 line = ex.value 

3487 

3488 if isinstance(line, VivadoMessage): 

3489 line = yield line 

3490 

3491 break 

3492 

3493 if task is not None: 3493 ↛ 3494line 3493 didn't jump to line 3494 because the condition on line 3493 was never true

3494 line = yield task.send(line) 

3495 

3496 activeParsers.remove(parser) 

3497 

3498 

3499@export 

3500class PhyOpt_Design(CommandWithTasks): 

3501 """ 

3502 A Vivado command output parser for ``phy_opt_design``. 

3503 """ 

3504 from . import PhysicalOptimizeDesign as _PhyOptDesign 

3505 

3506 _TCL_COMMAND: ClassVar[str] = "phys_opt_design" 

3507 _TIME: ClassVar[str] = None 

3508 

3509 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = ( 

3510 _PhyOptDesign.InitialUpdateTimingTask, 

3511 _PhyOptDesign.PhysicalSynthesisTask 

3512 ) 

3513 

3514 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]: 

3515 line = yield from self._CommandStart(line) 

3516 

3517 activeParsers: List[Task] = list(self._tasks.values()) 

3518 

3519 while True: 

3520 while True: 

3521 if line._kind is LineKind.Empty: 

3522 line = yield line 

3523 continue 

3524 elif isinstance(line, VivadoMessage): 

3525 self._AddMessage(line) 

3526 elif line.StartsWith("Starting "): 

3527 for parser in activeParsers: # type: Section 3527 ↛ 3532line 3527 didn't jump to line 3532 because the loop on line 3527 didn't complete

3528 if line.StartsWith(parser._START): 3528 ↛ 3527line 3528 didn't jump to line 3527 because the condition on line 3528 was always true

3529 line = yield next(task := parser.Generator(line)) 

3530 break 

3531 else: 

3532 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line)) 

3533 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'") 

3534 # ex.add_note(f"Current task: start pattern='{self._task}'") 

3535 ex.add_note(f"Current cmd: {self}") 

3536 raise ex 

3537 break 

3538 elif line.StartsWith(self._TCL_COMMAND): 

3539 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3539 ↛ 3548line 3539 didn't jump to line 3548 because the condition on line 3539 was always true

3540 line._kind |= LineKind.Success 

3541 

3542 # FIXME: use similar style like for _TIME 

3543 line = yield line 

3544 lastLine = yield line 

3545 return lastLine 

3546 # line._kind = LineKind.Unprocessed 

3547 

3548 line = yield line 

3549 

3550 while True: 

3551 # if line.StartsWith("Ending"): 

3552 # line = yield task.send(line) 

3553 # break 

3554 

3555 if isinstance(line, VivadoMessage): 

3556 self._AddMessage(line) 

3557 

3558 try: 

3559 line = yield task.send(line) 

3560 except StopIteration as ex: 

3561 task = None 

3562 line = ex.value 

3563 

3564 if isinstance(line, VivadoMessage): 3564 ↛ 3567line 3564 didn't jump to line 3567 because the condition on line 3564 was always true

3565 line = yield line 

3566 

3567 break 

3568 

3569 if task is not None: 3569 ↛ 3570line 3569 didn't jump to line 3570 because the condition on line 3569 was never true

3570 line = yield task.send(line) 

3571 

3572 activeParsers.remove(parser) 

3573 

3574 

3575@export 

3576class Route_Design(CommandWithTasks): 

3577 """ 

3578 A Vivado command output parser for ``route_design``. 

3579 """ 

3580 from . import RouteDesign as _RouteDesign 

3581 

3582 _TCL_COMMAND: ClassVar[str] = "route_design" 

3583 _TIME: ClassVar[str] = "Time (s):" 

3584 

3585 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = ( 

3586 _RouteDesign.RoutingTask, 

3587 ) 

3588 

3589 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]: 

3590 line = yield from self._CommandStart(line) 

3591 

3592 activeParsers: List[Task] = list(self._tasks.values()) 

3593 

3594 while True: 

3595 while True: 

3596 if line._kind is LineKind.Empty: 

3597 line = yield line 

3598 continue 

3599 elif isinstance(line, VivadoMessage): 

3600 self._AddMessage(line) 

3601 elif line.StartsWith("Starting "): 

3602 for parser in activeParsers: # type: Section 3602 ↛ 3607line 3602 didn't jump to line 3607 because the loop on line 3602 didn't complete

3603 if line.StartsWith(parser._START): 3603 ↛ 3602line 3603 didn't jump to line 3602 because the condition on line 3603 was always true

3604 line = yield next(task := parser.Generator(line)) 

3605 break 

3606 else: 

3607 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line)) 

3608 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'") 

3609 # ex.add_note(f"Current task: start pattern='{self._task}'") 

3610 ex.add_note(f"Current cmd: {self}") 

3611 raise ex 

3612 break 

3613 elif line.StartsWith(self._TCL_COMMAND): 

3614 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3614 ↛ 3623line 3614 didn't jump to line 3623 because the condition on line 3614 was always true

3615 line._kind |= LineKind.Success 

3616 

3617 # FIXME: use similar style like for _TIME 

3618 line = yield line 

3619 lastLine = yield line 

3620 return lastLine 

3621 # line._kind = LineKind.Unprocessed 

3622 

3623 line = yield line 

3624 

3625 while True: 

3626 # if line.StartsWith("Ending"): 

3627 # line = yield task.send(line) 

3628 # break 

3629 

3630 if isinstance(line, VivadoMessage): 

3631 self._AddMessage(line) 

3632 

3633 try: 

3634 line = yield task.send(line) 

3635 except StopIteration as ex: 

3636 task = None 

3637 line = ex.value 

3638 

3639 if isinstance(line, VivadoMessage): 3639 ↛ 3640line 3639 didn't jump to line 3640 because the condition on line 3639 was never true

3640 line = yield line 

3641 

3642 break 

3643 

3644 if task is not None: 3644 ↛ 3645line 3644 didn't jump to line 3645 because the condition on line 3644 was never true

3645 line = yield task.send(line) 

3646 

3647 activeParsers.remove(parser) 

3648 

3649 

3650@export 

3651class Write_Bitstream(Command): 

3652 """ 

3653 A Vivado command output parser for ``write_bitstream``. 

3654 """ 

3655 _TCL_COMMAND: ClassVar[str] = "write_bitstream" 

3656 _TIME: ClassVar[str] = "Time (s):" 

3657 

3658 

3659@export 

3660class Report_DRC(Command): 

3661 """ 

3662 A Vivado command output parser for ``report_drc``. 

3663 """ 

3664 _TCL_COMMAND: ClassVar[str] = "report_drc" 

3665 _TIME: ClassVar[str] = "Time (s):" 

3666 

3667 

3668@export 

3669class Report_Methodology(Command): 

3670 """ 

3671 A Vivado command output parser for ``report_methodology``. 

3672 """ 

3673 _TCL_COMMAND: ClassVar[str] = "report_methodology" 

3674 _TIME: ClassVar[str] = None 

3675 

3676 

3677@export 

3678class Report_Power(Command): 

3679 """ 

3680 A Vivado command output parser for ``report_power``. 

3681 """ 

3682 _TCL_COMMAND: ClassVar[str] = "report_power" 

3683 _TIME: ClassVar[str] = None 

3684 

3685 

3686@export 

3687class Processor(VivadoMessagesMixin, metaclass=ExtendedType, slots=True): 

3688 """ 

3689 A processor for Vivado log outputs. 

3690 

3691 Each output line from Vivado gets processed and converted into a :class:`ProcessedLine` objects. Such lines form a 

3692 doubly-linked list. 

3693 """ 

3694 _duration: float #: Duration of the observed process (e.g. start to end of synthesis). 

3695 _processingDuration: float #: Duration for the log output processor to parse all log messages. 

3696 

3697 _lines: List[VivadoLine] #: A list of processed log message lines. 

3698 _preamble: Preamble #: Reference to the Vivado preamble written after tool startup. 

3699 _postamble: Postamble #: Reference to the Vivado postamble written after tool startup. 

3700 _commands: Dict[Type[Command], Command] #: A dictionary of processed Vivado commands. 

3701 

3702 def __init__(self) -> None: 

3703 """ 

3704 Initializes a Vivado log output processor. 

3705 """ 

3706 super().__init__() 

3707 

3708 self._duration = 0.0 

3709 self._processingDuration = 0.0 

3710 

3711 self._lines = [] 

3712 self._preamble = None 

3713 self._postamble = None 

3714 self._commands = {} 

3715 

3716 @readonly 

3717 def Lines(self) -> List[VivadoLine]: 

3718 """ 

3719 Read-only property to access the list of processed and classified log lines (messages). 

3720 

3721 :returns: A list of processed lines. 

3722 """ 

3723 return self._lines 

3724 

3725 @readonly 

3726 def Preamble(self) -> Preamble: 

3727 """ 

3728 Read-only property to access the parsed preamble information. 

3729 

3730 :returns: The log output preamble. 

3731 """ 

3732 return self._preamble 

3733 

3734 @readonly 

3735 def Postamble(self) -> Postamble: 

3736 """ 

3737 Read-only property to access the parsed postamble information. 

3738 

3739 :returns: The log output postamble. 

3740 """ 

3741 return self._postamble 

3742 

3743 @readonly 

3744 def Commands(self) -> Dict[Type[Command], Command]: 

3745 """ 

3746 Read-only property to access the dictionary of processed Vivado commands. 

3747 

3748 :returns: The dictionary of processed Vivado commands. 

3749 """ 

3750 return self._commands 

3751 

3752 @readonly 

3753 def StartDateTime(self) -> datetime: 

3754 return self._preamble.StartDatetime 

3755 

3756 @readonly 

3757 def ExitDateTime(self) -> datetime: 

3758 return self._postamble.ExitDatetime 

3759 

3760 @readonly 

3761 def Duration(self) -> float: 

3762 """ 

3763 Duration of the observed process (e.g. start to end of synthesis). 

3764 

3765 :returns: The observed process' execution duration in seconds. 

3766 """ 

3767 startTime = self._preamble.StartDatetime 

3768 exitTime = self._postamble.ExitDatetime 

3769 

3770 return (exitTime - startTime).total_seconds() 

3771 

3772 @readonly 

3773 def ProcessingDuration(self) -> float: 

3774 """ 

3775 Processing duration for the log output processor to parse all log messages. 

3776 

3777 :returns: The processing duration in seconds. 

3778 """ 

3779 return self._processingDuration 

3780 

3781 def __contains__(self, key: Type[Command]) -> bool: 

3782 """ 

3783 Returns True, if log outputs where found for the given command. 

3784 

3785 :param key: Vivado command (class). 

3786 :returns: True, if the Vivado command's outputs were found in log outputs. 

3787 """ 

3788 if not issubclass(key, Command): 3788 ↛ 3789line 3788 didn't jump to line 3789 because the condition on line 3788 was never true

3789 ex = TypeError(f"Parameter 'key' is not a Command.") 

3790 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

3791 raise ex 

3792 

3793 return key in self._commands 

3794 

3795 def __getitem__(self, key: Type[Command]) -> Command: 

3796 """ 

3797 Access Vivado command specific log outputs and parsed data by the command. 

3798 

3799 :param key: Vivado command (class) to access. 

3800 :returns: A Vivado command instance with parsed log messages and extracted data. 

3801 """ 

3802 if not issubclass(key, Command): 3802 ↛ 3803line 3802 didn't jump to line 3803 because the condition on line 3802 was never true

3803 ex = TypeError(f"Parameter 'key' is not a Command.") 

3804 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

3805 raise ex 

3806 

3807 try: 

3808 return self._commands[key] 

3809 except KeyError as ex: 

3810 raise CommandNotPresentException(F"Command '{key._TCL_COMMAND}' not present in '{self._logfile}'.") from ex 

3811 

3812 @readonly 

3813 def IsIncompleteLog(self) -> bool: 

3814 """ 

3815 Read-only property returning true if the processed Vivado log output is incomplete. 

3816 

3817 A log can be incomplete, because: 

3818 

3819 * Vivado disabled messages, because too many messages of the same kind appeared. Usually, a message type is disabled 

3820 after 100 messages of that type. This is indicated by message ``[Common 17-14]``. 

3821 

3822 :returns: True, if messages where silenced by Vivado. 

3823 

3824 .. note:: 

3825 

3826 .. code-block:: 

3827 

3828 INFO: [Common 17-14] Message 'Synth 8-3321' appears 100 times and further instances of the messages will be 

3829 disabled. Use the Tcl command set_msg_config to change the current settings. 

3830 """ 

3831 return 17 in self._messagesByID and 14 in self._messagesByID[17] 

3832 

3833 def LineClassification(self, inputStream: Iterator[Tuple[datetime, str]]) -> Generator[VivadoLine, None, None]: 

3834 

3835 # Instantiate and initialize CommandFinder 

3836 next(cmdFinder := self.CommandFinder()) 

3837 

3838 lastLine = None 

3839 lineNumber = 0 

3840 _errorMessage = "Unknown processing error" 

3841 

3842 try: 

3843 while (tup := next(inputStream)) is not None: 3843 ↛ exitline 3843 didn't return from function 'LineClassification' because the condition on line 3843 was always true

3844 timestamp, rawMessageLine = tup 

3845 lineNumber += 1 

3846 rawMessageLine = rawMessageLine.rstrip() 

3847 errorMessage = _errorMessage 

3848 

3849 if len(rawMessageLine) == 0: 

3850 line = VivadoLine(lineNumber, LineKind.Empty, LineAction.Default, rawMessageLine, previousLine=lastLine) 

3851 elif rawMessageLine.startswith("INFO"): 

3852 if (line := VivadoInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 

3853 if (line := VivadoDRCInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 

3854 if (line := VivadoIrregularInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 

3855 line = VivadoStuntedInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine) 

3856 

3857 errorMessage = f"Line starting with 'INFO' was not a VivadoInfoMessage." 

3858 elif rawMessageLine.startswith("WARNING"): 

3859 if (line := VivadoWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 

3860 if (line := VivadoDRCWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 3860 ↛ 3864line 3860 didn't jump to line 3864 because the condition on line 3860 was always true

3861 if (line := VivadoXPMWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 3861 ↛ 3862line 3861 didn't jump to line 3862 because the condition on line 3861 was never true

3862 line = VivadoStuntedWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine) 

3863 

3864 errorMessage = f"Line starting with 'WARNING' was not a VivadoWarningMessage." 

3865 elif rawMessageLine.startswith("CRITICAL WARNING"): 

3866 line = VivadoCriticalWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine) 

3867 

3868 errorMessage = f"Line starting with 'CRITICAL WARNING' was not a VivadoCriticalWarningMessage." 

3869 elif rawMessageLine.startswith("ERROR"): 

3870 line = VivadoErrorMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine) 

3871 

3872 errorMessage = f"Line starting with 'ERROR' was not a VivadoErrorMessage." 

3873 elif rawMessageLine.startswith("Command: "): 

3874 line = VivadoTclCommand.Parse(lineNumber, rawMessageLine, previousLine=lastLine) 

3875 

3876 errorMessage = "Line starting with 'Command:' was not a VivadoTclCommand." 

3877 else: 

3878 line = VivadoLine(lineNumber, LineKind.Unprocessed, LineAction.Default, rawMessageLine, previousLine=lastLine) 

3879 

3880 if line.StartsWith("Resolution:") and isinstance(lastLine, VivadoMessage): 

3881 line._kind = LineKind.Verbose 

3882 

3883 if line is None: 3883 ↛ 3885line 3883 didn't jump to line 3885 because the condition on line 3883 was never true

3884 # TODO: what to do with this line? attache to exception? 

3885 line = VivadoLine(lineNumber, LineKind.ProcessorError, rawMessageLine, previousLine=lastLine) 

3886 

3887 raise ClassificationException(errorMessage, lineNumber, rawMessageLine) 

3888 

3889 if isinstance(line, VivadoMessage): 

3890 self._AddMessage(line) 

3891 

3892 line = cmdFinder.send(line) 

3893 

3894 if line._kind is LineKind.ProcessorError: 3894 ↛ 3895line 3894 didn't jump to line 3895 because the condition on line 3894 was never true

3895 line = ClassificationException(errorMessage, lineNumber, rawMessageLine) 

3896 

3897 # TODO: find a better solution/location to assign the timestamp. 

3898 line._timestamp = timestamp 

3899 

3900 self._lines.append(line) 

3901 

3902 lastLine = line 

3903 yield line 

3904 

3905 except StopIteration: 

3906 pass 

3907 

3908 def CommandFinder(self) -> Generator[VivadoLine, VivadoLine, None]: 

3909 self._preamble = Preamble(self) 

3910 self._postamble = Postamble(self) 

3911 

3912 tclProcedures = {"source"} 

3913 

3914 # wait for first line 

3915 line = yield 

3916 

3917 # process preamble 

3918 line = yield from self._preamble.Generator(line) 

3919 

3920 while True: 

3921 while True: 

3922 if line._kind is LineKind.Empty: 3922 ↛ 3923line 3922 didn't jump to line 3923 because the condition on line 3922 was never true

3923 line = yield line 

3924 continue 

3925 elif isinstance(line, VivadoInfoMessage): 

3926 if line.ToolID == 17 and line.MessageKindID == 206: 

3927 lastLine = yield from self._postamble.Generator(line) 

3928 return lastLine 

3929 elif isinstance(line, VivadoTclCommand): 

3930 if line._tclCommand == Synth_Design._TCL_COMMAND: 

3931 self._commands[Synth_Design] = (cmd := Synth_Design(self)) 

3932 line = yield next(gen := cmd.SectionDetector(line)) 

3933 break 

3934 elif line._tclCommand == Link_Design._TCL_COMMAND: 

3935 self._commands[Link_Design] = (cmd := Link_Design(self)) 

3936 line = yield next(gen := cmd.SectionDetector(line)) 

3937 break 

3938 elif line._tclCommand == Opt_Design._TCL_COMMAND: 

3939 self._commands[Opt_Design] = (cmd := Opt_Design(self)) 

3940 line = yield next(gen := cmd.SectionDetector(line)) 

3941 break 

3942 elif line._tclCommand == Place_Design._TCL_COMMAND: 

3943 self._commands[Place_Design] = (cmd := Place_Design(self)) 

3944 line = yield next(gen := cmd.SectionDetector(line)) 

3945 break 

3946 elif line._tclCommand == PhyOpt_Design._TCL_COMMAND: 

3947 self._commands[PhyOpt_Design] = (cmd := PhyOpt_Design(self)) 

3948 line = yield next(gen := cmd.SectionDetector(line)) 

3949 break 

3950 elif line._tclCommand == Route_Design._TCL_COMMAND: 

3951 self._commands[Route_Design] = (cmd := Route_Design(self)) 

3952 line = yield next(gen := cmd.SectionDetector(line)) 

3953 break 

3954 elif line._tclCommand == Write_Bitstream._TCL_COMMAND: 

3955 self._commands[Write_Bitstream] = (cmd := Write_Bitstream(self)) 

3956 line = yield next(gen := cmd.SectionDetector(line)) 

3957 break 

3958 elif line._tclCommand == Report_DRC._TCL_COMMAND: 

3959 self._commands[Report_DRC] = (cmd := Report_DRC(self)) 

3960 line = yield next(gen := cmd.SectionDetector(line)) 

3961 break 

3962 elif line._tclCommand == Report_Methodology._TCL_COMMAND: 

3963 self._commands[Report_Methodology] = (cmd := Report_Methodology(self)) 

3964 line = yield next(gen := cmd.SectionDetector(line)) 

3965 break 

3966 elif line._tclCommand == Report_Power._TCL_COMMAND: 

3967 self._commands[Report_Power] = (cmd := Report_Power(self)) 

3968 line = yield next(gen := cmd.SectionDetector(line)) 

3969 break 

3970 

3971 firstWord = line.Partition(" ")[0] 

3972 if firstWord in tclProcedures: 

3973 line = TclCommand.FromLine(line) 

3974 

3975 line = yield line 

3976 

3977 # end = f"{cmd._TCL_COMMAND} completed successfully" 

3978 

3979 while True: 

3980 # if line.StartsWith(end): 

3981 # # line._kind |= LineKind.Success 

3982 # lastLine = gen.send(line) 

3983 # if LineKind.Last in line._kind: 

3984 # line._kind ^= LineKind.Last 

3985 # line = yield lastLine 

3986 # break 

3987 

3988 try: 

3989 line = yield gen.send(line) 

3990 except StopIteration as ex: 

3991 line = ex.value 

3992 break 

3993 

3994 

3995@export 

3996class Document(Processor): 

3997 """ 

3998 A Vivado log output processor for a log file. 

3999 

4000 This processor represents a Vivado log file (e.g. ``*.vds`` or ``*.vdi``). It processees its content line-by-line 

4001 while classifying each line as a message. The processing duration is available via :data:`ProcessingDuration`. 

4002 """ 

4003 _logfile: Path #: Path to the processed logfile. 

4004 

4005 # FIXME: parse=True parameter 

4006 def __init__(self, logfile: Path) -> None: 

4007 """ 

4008 Initializes a log file. 

4009 

4010 :param logfile: Path to the log file. 

4011 """ 

4012 super().__init__() 

4013 

4014 # FIXME: check if path 

4015 self._logfile = logfile 

4016 

4017 @readonly 

4018 def Logfile(self) -> Path: 

4019 """ 

4020 Read-only property to access the document's path. 

4021 

4022 :returns: Path to the log file. 

4023 """ 

4024 return self._logfile 

4025 

4026 def Parse(self) -> None: 

4027 with Stopwatch() as sw: 

4028 timestamp = datetime.fromtimestamp(self._logfile.stat().st_mtime) 

4029 with self._logfile.open("r", encoding="utf-8") as file: 

4030 for line in self.LineClassification(timestampIterator(file, timestamp)): 

4031 pass 

4032 

4033 self._processingDuration = sw.Duration