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

2354 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-20 22:02 +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, Enum 

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, cast 

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, Warning, 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 ProcessorWarnings(Warning): 

72 """ 

73 Base-class for warnings raised by processors parsing log outputs. 

74 """ 

75 

76 

77@export 

78class ProcessorCriticalWarning(CriticalWarning): 

79 """ 

80 Base-class for critical warning raised by processors parsing log outputs. 

81 """ 

82 

83 

84@export 

85class ClassificationException(ProcessorException): 

86 """ 

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

88 """ 

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

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

91 

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

93 """ 

94 Initializes a classification exception. 

95 

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

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

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

99 """ 

100 super().__init__(errorMessage) 

101 

102 self._lineNumber = lineNumber 

103 self._rawMessage = rawMessageLine 

104 

105 def __str__(self) -> str: 

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

107 

108 

109@export 

110class ParserStateException(ProcessorException): 

111 """ 

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

113 """ 

114 

115 

116@export 

117class NotPresentException(ProcessorException): 

118 pass 

119 

120 

121@export 

122class CommandNotPresentException(NotPresentException): 

123 pass 

124 

125 

126@export 

127class SectionNotPresentException(NotPresentException): 

128 pass 

129 

130 

131@export 

132class SubSectionNotPresentException(NotPresentException): 

133 pass 

134 

135 

136@export 

137class SubTaskNotPresentException(NotPresentException): 

138 pass 

139 

140 

141@export 

142class PhaseNotPresentException(NotPresentException): 

143 pass 

144 

145 

146@export 

147class NestedTaskNotPresentException(NotPresentException): 

148 pass 

149 

150 

151@export 

152class UndetectedEnd(ProcessorCriticalWarning): 

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 UnknownLine(ProcessorWarnings): 

167 _line: "VivadoLine" 

168 

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

170 super().__init__(message) 

171 

172 self._line = line 

173 

174 @readonly 

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

176 return self._line 

177 

178 

179@export 

180class UnknownTask(UnknownLine): 

181 pass 

182 

183 

184@export 

185class UnknownSubTask(UnknownLine): 

186 pass 

187 

188 

189@export 

190class UnknownSection(UnknownLine): 

191 pass 

192 

193 

194@export 

195class UnknownPhase(UnknownLine): 

196 pass 

197 

198 

199@export 

200class UnknownSubPhase(UnknownLine): 

201 pass 

202 

203 

204@export 

205class LineKind(Flag): 

206 """ 

207 Classification of a log message line. 

208 """ 

209 Unprocessed = 0 

210 ProcessorError = 2** 0 

211 Empty = 2** 1 

212 Delimiter = 2** 2 

213 

214 Success = 2** 3 

215 Failed = 2** 4 

216 

217 Verbose = 2**10 

218 Normal = 2**11 

219 Info = 2**12 

220 Warning = 2**13 

221 CriticalWarning = 2**14 

222 Error = 2**15 

223 Fatal = 2**16 

224 

225 Start = 2**20 

226 End = 2**21 

227 Header = 2**22 

228 Content = 2**23 

229 Time = 2**24 

230 Footer = 2**25 

231 

232 Last = 2**28 

233 

234 DateTimeLine = 2**29 

235 

236 Message = 2**30 

237 InfoMessage = Message | Info 

238 WarningMessage = Message | Warning 

239 CriticalWarningMessage = Message | CriticalWarning 

240 ErrorMessage = Message | Error 

241 

242 Launch = 2**31 

243 LaunchStart = Launch | Start 

244 LaunchArguments = Launch | Header 

245 LaunchFinished = Launch | End 

246 LaunchTime = Launch | Time 

247 

248 Task = 2**32 

249 TaskStart = Task | Start 

250 TaskEnd = Task | End 

251 TaskTime = Task | Time 

252 

253 Phase = 2**33 

254 PhaseDelimiter = Phase | Delimiter 

255 PhaseStart = Phase | Start 

256 PhaseEnd = Phase | End 

257 PhaseTime = Phase | Time 

258 PhaseFinal = Phase | Footer 

259 

260 SubPhase = 2**34 

261 SubPhaseStart = SubPhase | Start 

262 SubPhaseEnd = SubPhase | End 

263 SubPhaseTime = SubPhase | Time 

264 

265 SubSubPhase = 2**35 

266 SubSubPhaseStart = SubSubPhase | Start 

267 SubSubPhaseEnd = SubSubPhase | End 

268 SubSubPhaseTime = SubSubPhase | Time 

269 

270 SubSubSubPhase = 2**36 

271 SubSubSubPhaseStart = SubSubSubPhase | Start 

272 SubSubSubPhaseEnd = SubSubSubPhase | End 

273 SubSubSubPhaseTime = SubSubSubPhase | Time 

274 

275 NestedTask = 2**37 

276 NestedTaskStart = NestedTask | Start 

277 NestedTaskEnd = NestedTask | End 

278 

279 NestedPhase = 2**38 

280 NestedPhaseStart = NestedPhase | Start 

281 NestedPhaseEnd = NestedPhase | End 

282 

283 Section = 2**39 

284 SectionDelimiter = Section | Delimiter 

285 SectionStart = Section | Start 

286 SectionEnd = Section | End 

287 

288 SubSection = 2**40 

289 SubSectionDelimiter = SubSection | Delimiter 

290 SubSectionStart = SubSection | Start 

291 SubSectionEnd = SubSection | End 

292 

293 Paragraph = 2**41 

294 ParagraphHeadline = Paragraph | Header 

295 

296 Hierarchy = 2**42 

297 HierarchyStart = Hierarchy | Start 

298 HierarchyEnd = Hierarchy | End 

299 

300 XDC = 2**43 

301 XDCStart = XDC | Start 

302 XDCEnd = XDC | End 

303 

304 Table = 2**44 

305 TableFrame = Table | Delimiter 

306 TableHeader = Table | Header 

307 TableRow = Table | Content 

308 TableFooter = Table | Footer 

309 

310 TclCommand = 2**45 

311 GenericTclCommand = TclCommand | 2**0 

312 VivadoTclCommand = TclCommand | 2**1 

313 

314 

315@export 

316class LineAction(Flag): 

317 Default = 0 

318 Remove = 1 

319 

320 

321@export 

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

323 """ 

324 This class represents any line in a log file. 

325 

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

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

328 linked list. 

329 """ 

330 _processor: "Processor" 

331 _command: "Nullable[Command]" 

332 

333 def __init__( 

334 self, 

335 lineNumber: int, 

336 kind: LineKind, 

337 action: LineAction, 

338 message: str, 

339 previousLine: Nullable["VivadoLine"] = None 

340 ) -> None: 

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

342 

343 self._processor = None 

344 self._command = None 

345 

346 @readonly 

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

348 return self._processor 

349 

350 @readonly 

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

352 return self._command 

353 

354 @classmethod 

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

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

357 newLine._timestamp = line._timestamp 

358 return newLine 

359 

360 

361@export 

362class DateTimeLine(VivadoLine): 

363 _PREFIX: ClassVar[Pattern] = re_compile(r"\[(?P<datetime>\w+ \w+ ?\d{1,2} \d{1,2}:\d{1,2}:\d{1,2} \d{4})\] (?P<message>.*)") 

364 

365 _dateTime: datetime 

366 

367 def __init__( 

368 self, 

369 lineNumber: int, 

370 kind: LineKind, 

371 action: LineAction, 

372 dateTime: datetime, 

373 message: str, 

374 previousLine: Nullable[VivadoLine] = None 

375 ) -> None: 

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

377 

378 self._dateTime = dateTime 

379 

380 @readonly 

381 def DateTime(self) -> datetime: 

382 return self._dateTime 

383 

384 @classmethod 

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

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

387 newLine._timestamp = line._timestamp 

388 return newLine 

389 

390 def __str__(self) -> str: 

391 return f"[{self._dateTime:%a %b} {self._dateTime.day:2d} {self._dateTime:%H:%M:%S %Y}] {self._message}" 

392 

393 

394@export 

395class VivadoMessage(VivadoLine): 

396 """ 

397 This class represents an AMD/Xilinx Vivado message. 

398 

399 The usual message format is: 

400 

401 .. code-block:: text 

402 

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

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

405 

406 The following message severities are defined: 

407 

408 * ``INFO`` 

409 * ``WARNING`` 

410 * ``CRITICAL WARNING`` 

411 * ``ERROR`` 

412 

413 .. seealso:: 

414 

415 :class:`VivadoInfoMessage` 

416 Representing a Vivado info message. 

417 

418 :class:`VivadoWarningMessage` 

419 Representing a Vivado warning message. 

420 

421 :class:`VivadoCriticalWarningMessage` 

422 Representing a Vivado critical warning message. 

423 

424 :class:`VivadoErrorMessage` 

425 Representing a Vivado error message. 

426 """ 

427 # _MESSAGE_KIND: ClassVar[str] 

428 # _REGEXP: ClassVar[Pattern] 

429 

430 _toolName: Nullable[str] 

431 _toolID: Nullable[int] 

432 _messageKindID: Nullable[int] 

433 

434 def __init__( 

435 self, 

436 lineNumber: int, 

437 kind: LineKind, 

438 action: LineAction, 

439 message: str, 

440 toolName: Nullable[str] = None, 

441 toolID: Nullable[int] = None, 

442 messageKindID: Nullable[int] = None, 

443 previousLine: Nullable[VivadoLine] = None 

444 ) -> None: 

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

446 self._toolName = toolName 

447 self._toolID = toolID 

448 self._messageKindID = messageKindID 

449 

450 @readonly 

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

452 return self._toolName 

453 

454 @readonly 

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

456 return self._toolID 

457 

458 @readonly 

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

460 return self._messageKindID 

461 

462 @classmethod 

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

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

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

466 

467 return None 

468 

469 @classmethod 

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

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

472 newLine._timestamp = line._timestamp 

473 return newLine 

474 

475 def __str__(self) -> str: 

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

477 

478 

479@export 

480class VivadoInfoMessage(VivadoMessage, InfoMessage): 

481 """ 

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

483 

484 .. rubric:: Example 

485 

486 .. code-block:: 

487 

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

489 """ 

490 

491 _MESSAGE_KIND: ClassVar[str] = "INFO" 

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

493 

494 @classmethod 

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

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

497 

498 @classmethod 

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

500 message = cls( 

501 line._lineNumber, 

502 LineKind.InfoMessage, 

503 line._message, 

504 line._toolName, 

505 line._toolID, 

506 line._messageKindID, 

507 previousLine=line._previousLine) 

508 message._nextLine = line._nextLine 

509 line._nextLine._previousLine = message 

510 

511 return message 

512 

513 

514@export 

515class VivadoDRCInfoMessage(VivadoMessage, InfoMessage): 

516 """ 

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

518 

519 .. rubric:: Example 

520 

521 .. code-block:: 

522 

523 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'. 

524 """ 

525 

526 _MESSAGE_KIND: ClassVar[str] = "INFO" 

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

528 

529 _drcRuleName: str 

530 

531 def __init__( 

532 self, 

533 lineNumber: int, 

534 kind: LineKind, 

535 action: LineAction, 

536 drcRuleName: str, 

537 message: str, 

538 toolName: Nullable[str] = None, 

539 toolID: Nullable[int] = None, 

540 messageKindID: Nullable[int] = None, 

541 previousLine: Nullable[VivadoLine] = None 

542 ) -> None: 

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

544 

545 self._drcRuleName = drcRuleName 

546 

547 @readonly 

548 def DRCRuleName(self) -> str: 

549 return self._drcRuleName 

550 

551 @classmethod 

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

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

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

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

556 

557 return None 

558 

559 @classmethod 

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

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

562 newLine._timestamp = line._timestamp 

563 return newLine 

564 

565 def __str__(self) -> str: 

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

567 

568 

569@export 

570class VivadoIrregularInfoMessage(VivadoMessage, InfoMessage): 

571 """ 

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

573 

574 .. rubric:: Example 

575 

576 .. code-block:: 

577 

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

579 """ 

580 

581 _MESSAGE_KIND: ClassVar[str] = "INFO" 

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

583 

584 @classmethod 

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

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

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

588 

589 return None 

590 

591 def __str__(self) -> str: 

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

593 

594 

595@export 

596class VivadoStuntedInfoMessage(VivadoMessage, InfoMessage): 

597 """ 

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

599 

600 .. rubric:: Example 

601 

602 .. code-block:: 

603 

604 INFO: Helper process launched with PID 29056 

605 """ 

606 

607 _MESSAGE_KIND: ClassVar[str] = "INFO" 

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

609 

610 @classmethod 

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

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

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

614 

615 return None 

616 

617 def __str__(self) -> str: 

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

619 

620 

621@export 

622class VivadoWarningMessage(VivadoMessage, WarningMessage): 

623 """ 

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

625 

626 .. rubric:: Example 

627 

628 .. code-block:: 

629 

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

631 """ 

632 

633 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

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

635 

636 @classmethod 

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

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

639 

640 @classmethod 

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

642 message = cls( 

643 line._lineNumber, 

644 LineKind.WarningMessage, 

645 line._message, 

646 line._toolName, 

647 line._toolID, 

648 line._messageKindID, 

649 previousLine=line._previousLine) 

650 message._nextLine = line._nextLine 

651 line._nextLine._previousLine = message 

652 

653 return message 

654 

655 

656@export 

657class VivadoDRCWarningMessage(VivadoMessage, WarningMessage): 

658 """ 

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

660 

661 .. rubric:: Example 

662 

663 .. code-block:: 

664 

665 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. 

666 """ 

667 

668 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

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

670 

671 _drcRuleName: str 

672 

673 def __init__( 

674 self, 

675 lineNumber: int, 

676 kind: LineKind, 

677 action: LineAction, 

678 drcRuleName: str, 

679 message: str, 

680 toolName: Nullable[str] = None, 

681 toolID: Nullable[int] = None, 

682 messageKindID: Nullable[int] = None, 

683 previousLine: Nullable[VivadoLine] = None 

684 ) -> None: 

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

686 

687 self._drcRuleName = drcRuleName 

688 

689 @readonly 

690 def DRCRuleName(self) -> str: 

691 return self._drcRuleName 

692 

693 @classmethod 

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

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

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

697 

698 return None 

699 

700 @classmethod 

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

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

703 newLine._timestamp = line._timestamp 

704 return newLine 

705 

706 def __str__(self) -> str: 

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

708 

709 

710@export 

711class VivadoXPMWarningMessage(VivadoMessage, WarningMessage): 

712 """ 

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

714 

715 .. rubric:: Example 

716 

717 .. code-block:: 

718 

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

720 """ 

721 

722 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

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

724 

725 _xpmName: str 

726 

727 def __init__( 

728 self, 

729 lineNumber: int, 

730 kind: LineKind, 

731 action: LineAction, 

732 xpmName: str, 

733 message: str, 

734 toolName: Nullable[str] = None, 

735 toolID: Nullable[int] = None, 

736 messageKindID: Nullable[int] = None, 

737 previousLine: Nullable[VivadoLine] = None 

738 ) -> None: 

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

740 

741 self._xpmName = xpmName 

742 

743 @readonly 

744 def XPMName(self) -> str: 

745 return self._xpmName 

746 

747 @classmethod 

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

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

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

751 

752 return None 

753 

754 @classmethod 

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

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

757 newLine._timestamp = line._timestamp 

758 return newLine 

759 

760 def __str__(self) -> str: 

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

762 

763 

764@export 

765class VivadoStuntedWarningMessage(VivadoMessage, WarningMessage): 

766 """ 

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

768 

769 .. rubric:: Example 

770 

771 .. code-block:: 

772 

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

774 """ 

775 

776 _MESSAGE_KIND: ClassVar[str] = "WARNING" 

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

778 

779 @classmethod 

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

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

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

783 

784 return None 

785 

786 def __str__(self) -> str: 

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

788 

789 

790@export 

791class VivadoCriticalWarningMessage(VivadoMessage, CriticalWarningMessage): 

792 """ 

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

794 

795 .. rubric:: Example 

796 

797 .. code-block:: 

798 

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

800 """ 

801 

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

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

804 

805 @classmethod 

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

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

808 

809 @classmethod 

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

811 message = cls( 

812 line._lineNumber, 

813 LineKind.CriticalWarningMessage, 

814 line._message, 

815 line._toolName, 

816 line._toolID, 

817 line._messageKindID, 

818 previousLine=line._previousLine) 

819 message._nextLine = line._nextLine 

820 line._nextLine._previousLine = message 

821 

822 return message 

823 

824 

825@export 

826class VivadoErrorMessage(VivadoMessage, ErrorMessage): 

827 """ 

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

829 

830 .. rubric:: Example 

831 

832 .. code-block:: 

833 

834 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 

835 """ 

836 

837 _MESSAGE_KIND: ClassVar[str] = "ERROR" 

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

839 

840 @classmethod 

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

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

843 

844 @classmethod 

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

846 message = cls( 

847 line._lineNumber, 

848 LineKind.ErrorMessage, 

849 line._message, 

850 line._toolName, 

851 line._toolID, 

852 line._messageKindID, 

853 previousLine=line._previousLine) 

854 message._nextLine = line._nextLine 

855 line._nextLine._previousLine = message 

856 

857 return message 

858 

859 

860@export 

861class VHDLReportMessage(VivadoInfoMessage): 

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

863 

864 _reportMessage: str 

865 _sourceFile: Path 

866 _sourceLineNumber: int 

867 

868 def __init__( 

869 self, 

870 lineNumber: int, 

871 rawMessage: str, 

872 toolName: str, 

873 toolID: int, 

874 messageKindID: int, 

875 reportMessage: str, 

876 sourceFile: Path, 

877 sourceLineNumber: int, 

878 previousLine: Nullable[VivadoLine] = None 

879 ) -> None: 

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

881 

882 self._reportMessage = reportMessage 

883 self._sourceFile = sourceFile 

884 self._sourceLineNumber = sourceLineNumber 

885 

886 @classmethod 

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

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

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

890 

891 return None 

892 

893 @classmethod 

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

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

896 newLine._timestamp = line._timestamp 

897 return newLine 

898 

899@export 

900class VHDLAssertionMessage(VHDLReportMessage): 

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

902 

903 @classmethod 

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

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

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

907 

908 return None 

909 

910 

911@export 

912class TclCommand(VivadoLine): 

913 """ 

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

915 

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

917 arguments. 

918 """ 

919 _tclCommand: str 

920 _arguments: Tuple[str, ...] 

921 

922 def __init__( 

923 self, 

924 lineNumber: int, 

925 tclCommand: str, 

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

927 rawMessage: str, 

928 previousLine: Nullable[VivadoLine] = None 

929 ) -> None: 

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

931 

932 self._tclCommand = tclCommand 

933 self._arguments = arguments 

934 

935 @readonly 

936 def TCLCommand(self) -> str: 

937 return self._tclCommand 

938 

939 @readonly 

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

941 return self._arguments 

942 

943 @classmethod 

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

945 args = line._message.split() 

946 

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

948 

949 @classmethod 

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

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

952 newLine._timestamp = line._timestamp 

953 return newLine 

954 

955 def __str__(self) -> str: 

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

957 

958 

959@export 

960class VivadoTclCommand(TclCommand): 

961 """ 

962 Represents a Vivado specific TCL command. 

963 """ 

964 

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

966 

967 @classmethod 

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

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

970 args = tclCommand.split() 

971 

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

973 vivadoCommand._kind = LineKind.VivadoTclCommand 

974 return vivadoCommand 

975 

976 def __str__(self) -> str: 

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

978 

979 

980@export 

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

982 _infoMessages: List[VivadoInfoMessage] 

983 _warningMessages: List[VivadoWarningMessage] 

984 _criticalWarningMessages: List[VivadoCriticalWarningMessage] 

985 _errorMessages: List[VivadoErrorMessage] 

986 _toolIDs: Dict[int, str] 

987 _toolNames: Dict[str, int] 

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

989 

990 def __init__(self) -> None: 

991 self._infoMessages = [] 

992 self._warningMessages = [] 

993 self._criticalWarningMessages = [] 

994 self._errorMessages = [] 

995 self._toolIDs = {} 

996 self._toolNames = {} 

997 self._messagesByID = {} 

998 

999 @readonly 

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

1001 return self._toolIDs 

1002 

1003 @readonly 

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

1005 return self._toolNames 

1006 

1007 @readonly 

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

1009 return self._messagesByID 

1010 

1011 @readonly 

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

1013 return self._infoMessages 

1014 

1015 @readonly 

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

1017 return self._warningMessages 

1018 

1019 @readonly 

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

1021 return self._criticalWarningMessages 

1022 

1023 @readonly 

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

1025 return self._errorMessages 

1026 

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

1028 if isinstance(message, InfoMessage): 

1029 self._infoMessages.append(message) 

1030 elif isinstance(message, WarningMessage): 

1031 self._warningMessages.append(message) 

1032 elif isinstance(message, CriticalWarningMessage): 

1033 self._criticalWarningMessages.append(message) 

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

1035 self._errorMessages.append(message) 

1036 

1037 if message._toolID in self._messagesByID: 

1038 sub = self._messagesByID[message._toolID] 

1039 if message._messageKindID in sub: 

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

1041 else: 

1042 sub[message._messageKindID] = [message] 

1043 else: 

1044 if message._toolID is not None: 

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

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

1047 

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

1049 

1050 

1051# todo: check usage or merge with parser 

1052@export 

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

1054 def __init__(self) -> None: 

1055 super().__init__() 

1056 

1057 

1058@export 

1059class Parser(BaseParser): 

1060 _processor: "Processor" 

1061 

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

1063 super().__init__() 

1064 

1065 self._processor = processor 

1066 

1067 @readonly 

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

1069 return self._processor 

1070 

1071 

1072@export 

1073class PreambleFormat(Enum): 

1074 """ 

1075 An enumeration representing the preamble format (``Unknown``, ``Console`` or ``Logfile``). 

1076 """ 

1077 

1078 Unknown = 0 #: Vivado was called on console in batch or interactive mode. 

1079 Console = 1 #: Vivado was called on console in batch or interactive mode. 

1080 Logfile = 2 #: Vivado writes a logfile. 

1081 

1082 def __str__(self) -> str: 

1083 """ 

1084 Formats the preamble format to ``console`` or ``logfile``. 

1085 

1086 :returns: Formatted preamble format. 

1087 """ 

1088 return ("unknown", "console", "logfile")[cast(int, self.value)] # TODO: check performance 

1089 

1090 

1091@export 

1092class Preamble(Parser, VivadoMessagesMixin): 

1093 """ 

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

1095 

1096 .. rubric:: Extracted information 

1097 

1098 * Vivado tool version. |br| 

1099 See :data:`ToolVersion` 

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

1101 See :data:`StartDateTime` 

1102 

1103 .. rubric:: Examples 

1104 

1105 .. code-block:: 

1106 

1107 ****** Vivado v2024.2 (64-bit) 

1108 **** SW Build 5239630 on Fri Nov 08 22:35:27 MST 2024 

1109 **** IP Build 5239520 on Sun Nov 10 16:12:51 MST 2024 

1110 **** SharedData Build 5239561 on Fri Nov 08 14:39:27 MST 2024 

1111 **** Start of session at: Wed Jul 1 23:50:26 2026 

1112 ** Copyright 1986-2022 Xilinx, Inc. All Rights Reserved. 

1113 ** Copyright 2022-2024 Advanced Micro Devices, Inc. All Rights Reserved. 

1114 

1115 

1116 .. code-block:: 

1117 

1118 INFO: [Common 17-3922] A valid Vivado Design Suite ENTERPRISE license has been detected. Your current license is active and will expire on Permanent. 

1119 #----------------------------------------------------------- 

1120 # Vivado v2025.1 (64-bit) 

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

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

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

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

1125 # Process ID : 28856 

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

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

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

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

1130 # Running On : Paebbels 

1131 # Platform : Windows Server 2016 or Windows 10 

1132 # Operating System : 26100 

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

1134 # CPU Frequency : 2611 MHz 

1135 # CPU Physical cores : 8 

1136 # CPU Logical cores : 16 

1137 # Host memory : 34048 MB 

1138 # Swap memory : 28991 MB 

1139 # Total Virtual : 63039 MB 

1140 # Available Virtual : 29246 MB 

1141 #----------------------------------------------------------- 

1142 """ 

1143 _VERSION: ClassVar[Pattern] = re_compile(r"""(?P<prefix>#|\*\*\*\*\*\*) Vivado v(?P<version>\d+\.\d(\.\d)?) \(64-bit\)""") 

1144 _STARTTIME: ClassVar[Pattern] = re_compile(r"""(?P<prefix>#| \*\*\*\*) Start of session at: (?P<datetime>\w+ \w+ ?\d{1,2} \d{1,2}:\d{1,2}:\d{1,2} \d{4})""") 

1145 

1146 _preambleFormat: PreambleFormat #: Format of the preamble 

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

1148 _startDateTime: Nullable[datetime] #: Session start timestamp. 

1149 

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

1151 """ 

1152 Initializes a Vivado preamble parser. 

1153 

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

1155 """ 

1156 super().__init__(processor) 

1157 VivadoMessagesMixin.__init__(self) 

1158 

1159 self._preambleFormat = PreambleFormat.Unknown 

1160 self._toolVersion = None 

1161 self._startDateTime = None 

1162 

1163 @readonly 

1164 def PreambleFormat(self) -> PreambleFormat: 

1165 """ 

1166 Read-only property to access the preamble format. 

1167 

1168 :returns: The detected format of the preamble. 

1169 """ 

1170 return self._preambleFormat 

1171 

1172 @readonly 

1173 def ToolVersion(self) -> YearReleaseVersion: 

1174 """ 

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

1176 

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

1178 """ 

1179 if self._toolVersion is None: 1179 ↛ 1180line 1179 didn't jump to line 1180 because the condition on line 1179 was never true

1180 raise ProcessorException("No tool version extracted from preamble.") 

1181 

1182 return self._toolVersion 

1183 

1184 @readonly 

1185 def StartDateTime(self) -> datetime: 

1186 """ 

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

1188 

1189 :returns: Datetime when the session was started. 

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

1191 """ 

1192 if self._startDateTime is None: 1192 ↛ 1193line 1192 didn't jump to line 1193 because the condition on line 1192 was never true

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

1194 

1195 return self._startDateTime 

1196 

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

1198 """ 

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

1200 

1201 :param line: First line to process. 

1202 :returns: A generator processing log messages. 

1203 """ 

1204 delimiterCount = 0 

1205 preambleFormat = PreambleFormat.Unknown 

1206 

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

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

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

1210 preambleFormat = PreambleFormat.Logfile if match["prefix"] == "#" else PreambleFormat.Console 

1211 self._toolVersion = YearReleaseVersion.Parse(match["version"]) 

1212 line._kind = LineKind.Normal 

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

1214 preambleFormat = PreambleFormat.Logfile if match["prefix"] == "#" else PreambleFormat.Console 

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

1216 line._kind = LineKind.Normal 

1217 elif isinstance(line, VivadoMessage): 

1218 self._AddMessage(line) 

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

1220 preambleFormat = PreambleFormat.Logfile 

1221 line._kind = LineKind.SectionDelimiter 

1222 if (delimiterCount := delimiterCount + 1) == 2: 

1223 break 

1224 elif line == "": 

1225 preambleFormat = PreambleFormat.Console 

1226 line._kind = LineKind.SectionDelimiter 

1227 if (delimiterCount := delimiterCount + 1) == 2: 

1228 break 

1229 else: 

1230 line._kind = LineKind.Verbose 

1231 

1232 if self._preambleFormat is PreambleFormat.Unknown: 

1233 self._preambleFormat = preambleFormat 

1234 elif self._preambleFormat is not preambleFormat: 1234 ↛ 1235line 1234 didn't jump to line 1235 because the condition on line 1234 was never true

1235 raise ProcessorException(f"Preamble format is not consistent.") 

1236 

1237 line = yield line 

1238 else: 

1239 raise OutputFilterException(f"Preamble is longer than 30 lines or delimiter was not detected.") 

1240 

1241 if self._toolVersion is None: 1241 ↛ 1242line 1241 didn't jump to line 1242 because the condition on line 1241 was never true

1242 raise OutputFilterException(f"Tool version not found in preamble.") 

1243 elif self._startDateTime is None: 1243 ↛ 1244line 1243 didn't jump to line 1244 because the condition on line 1243 was never true

1244 raise OutputFilterException(f"Session start time and date not found in preamble.") 

1245 

1246 nextLine = yield line 

1247 return nextLine 

1248 

1249 def __str__(self) -> str: 

1250 return f"Vivado {self._toolVersion}: started at {self._startDateTime}" 

1251 

1252 

1253@export 

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

1255 """ 

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

1257 

1258 .. rubric:: Extracted information 

1259 

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

1261 See :data:`ExitDateTime` 

1262 

1263 .. rubric:: Example 

1264 

1265 .. code-block:: 

1266 

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

1268 

1269 """ 

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

1271 _ENDTIME: ClassVar[Pattern] = re_compile(r"""Exiting Vivado at (?P<datetime>\w+ \w+ ?\d{1,2} \d{1,2}:\d{1,2}:\d{1,2} \d{4})""") 

1272 

1273 _exitDateTime: Nullable[datetime] #: Session exit timestamp. 

1274 

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

1276 """ 

1277 Initializes a Vivado postamble parser. 

1278 

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

1280 """ 

1281 super().__init__(processor) 

1282 VivadoMessagesMixin.__init__(self) 

1283 

1284 self._exitDateTime = None 

1285 

1286 @readonly 

1287 def ExitDateTime(self) -> Nullable[datetime]: 

1288 """ 

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

1290 

1291 :returns: Datetime when the session was exited. 

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

1293 """ 

1294 if self._exitDateTime is None: 1294 ↛ 1295line 1294 didn't jump to line 1295 because the condition on line 1294 was never true

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

1296 

1297 return self._exitDateTime 

1298 

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

1300 """ 

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

1302 

1303 :param line: First line to process. 

1304 :returns: A generator processing log messages. 

1305 """ 

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

1307 self._AddMessage(line) 

1308 

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

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

1311 

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

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

1314 else: 

1315 pass 

1316 

1317 line = yield line 

1318 

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

1320 return line 

1321 

1322@export 

1323class Command(Parser): 

1324 """ 

1325 This parser parses outputs from Vivado TCL commands. 

1326 

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

1328 

1329 .. rubric:: Command subcategories 

1330 

1331 * :class:`CommandWithSections` 

1332 * :class:`CommandWithtasks` 

1333 

1334 .. rubric:: Supported commands 

1335 

1336 * :class:`SynthesizeDesign` 

1337 * :class:`LinkDesign` 

1338 * :class:`OptimizeDesign` 

1339 * :class:`PlaceDesign` 

1340 * :class:`PhysicalOptimizeDesign` 

1341 * :class:`RouteDesign` 

1342 * :class:`WriteBitstream` 

1343 * :class:`ReportDRC` 

1344 * :class:`ReportMethodology` 

1345 * :class:`ReportPower` 

1346 

1347 .. rubric:: Example 

1348 

1349 .. code-block:: 

1350 

1351 [...] 

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

1353 Starting synth_design 

1354 [...] 

1355 """ 

1356 

1357 # _TCL_COMMAND: ClassVar[str] 

1358 

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

1360 """ 

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

1362 

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

1364 

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

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

1367 """ 

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

1369 raise ProcessorException() # FIXME: add exception message 

1370 

1371 nextLine = yield line 

1372 return nextLine 

1373 

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

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

1376 line._kind |= LineKind.Success 

1377 else: 

1378 line._kind |= LineKind.Failed 

1379 

1380 line = yield line 

1381 

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

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

1384 

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

1386 # if line.StartsWith(end): 

1387 # line._kind = LineKind.TaskTime 

1388 # line = yield line 

1389 # break 

1390 # 

1391 # line = yield line 

1392 

1393 if line.StartsWith(end): 

1394 line._kind = LineKind.TaskTime 

1395 line = yield line 

1396 

1397 return line 

1398 

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

1400 line = yield from self._CommandStart(line) 

1401 

1402 end = f"{self._TCL_COMMAND}" 

1403 while True: 

1404 if line._kind is LineKind.Empty: 

1405 line = yield line 

1406 continue 

1407 elif isinstance(line, VivadoMessage): 

1408 self._AddMessage(line) 

1409 elif line.StartsWith(end): 

1410 nextLine = yield from self._CommandFinish(line) 

1411 return nextLine 

1412 

1413 line = yield line 

1414 

1415 def __str__(self) -> str: 

1416 return f"{self._TCL_COMMAND}" 

1417 

1418 

1419@export 

1420class CommandWithSections(Command): 

1421 """ 

1422 A Vivado command writing sections into the output log. 

1423 

1424 .. rubric:: Example 

1425 

1426 .. code-block:: 

1427 

1428 [...] 

1429 --------------------------------------------------------------------------------- 

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

1431 --------------------------------------------------------------------------------- 

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

1433 [...] 

1434 [...] 

1435 [...] 

1436 --------------------------------------------------------------------------------- 

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

1438 --------------------------------------------------------------------------------- 

1439 [...] 

1440 """ 

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

1442 

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

1444 

1445 

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

1447 super().__init__(processor) 

1448 

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

1450 

1451 @readonly 

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

1453 """ 

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

1455 

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

1457 """ 

1458 return self._sections 

1459 

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

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

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

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

1464 raise ex 

1465 

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

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

1468 return True 

1469 else: 

1470 return False 

1471 

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

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

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

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

1476 raise ex 

1477 

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

1479 if isinstance(section, key): 

1480 return section 

1481 else: 

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

1483 

1484 

1485@export 

1486class CommandWithTasks(Command): 

1487 """ 

1488 A Vivado command writing tasks into the output log. 

1489 

1490 .. rubric:: Example 

1491 

1492 .. code-block:: 

1493 

1494 [...] 

1495 Starting Cache Timing Information Task 

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

1497 [...] 

1498 [...] 

1499 Ending Cache Timing Information Task | Checksum: 19fe8cb97 

1500 [...] 

1501 """ 

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

1503 

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

1505 

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

1507 super().__init__(processor) 

1508 

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

1510 

1511 @readonly 

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

1513 """ 

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

1515 

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

1517 """ 

1518 return self._tasks 

1519 

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

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

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

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

1524 raise ex 

1525 

1526 return key in self._tasks 

1527 

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

1529 try: 

1530 return self._tasks[key] 

1531 except KeyError as ex: 

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

1533 

1534 

1535@export 

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

1537 @abstractmethod 

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

1539 pass 

1540 

1541 @abstractmethod 

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

1543 pass 

1544 

1545 @abstractmethod 

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

1547 pass 

1548 

1549 

1550@export 

1551class Section(BaseParser, BaseSection): 

1552 """ 

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

1554 """ 

1555 # _NAME: ClassVar[str] 

1556 # _START: ClassVar[str] 

1557 # _FINISH: ClassVar[str] 

1558 # _DUPLICATES: ClassVar[bool] 

1559 

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

1561 _next: Nullable["Section"] 

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

1563 

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

1565 """ 

1566 Initialized a section. 

1567 

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

1569 """ 

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

1571 

1572 self._command = command 

1573 self._next = None 

1574 self._duration = 0.0 

1575 

1576 @readonly 

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

1578 """ 

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

1580 

1581 :returns: Next section of same type. 

1582 """ 

1583 return self._next 

1584 

1585 @readonly 

1586 def Duration(self) -> float: 

1587 """ 

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

1589 section. 

1590 

1591 :returns: Synthesis step duration in seconds. 

1592 """ 

1593 return self._duration 

1594 

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

1596 section = self._next 

1597 while section is not None: 

1598 yield section 

1599 

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

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

1602 line._kind = LineKind.SectionStart 

1603 

1604 line = yield line 

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

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

1607 else: 

1608 line._kind |= LineKind.ProcessorError 

1609 

1610 nextLine = yield line 

1611 return nextLine 

1612 

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

1614 if not skipDashes: 

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

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

1617 else: 

1618 line._kind |= LineKind.ProcessorError 

1619 

1620 line = yield line 

1621 

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

1623 line._kind = LineKind.SectionEnd 

1624 else: 

1625 line._kind |= LineKind.ProcessorError 

1626 

1627 line = yield line 

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

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

1630 else: 

1631 line._kind |= LineKind.ProcessorError 

1632 

1633 nextLine = yield line 

1634 return nextLine 

1635 

1636 # @mustoverride 

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

1638 # if len(line) == 0: 

1639 # return ProcessingState.EmptyLine 

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

1641 # return ProcessingState.DelimiterLine 

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

1643 # return ProcessingState.Skipped 

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

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

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

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

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

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

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

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

1652 # 

1653 # return ProcessingState.Skipped | ProcessingState.Last 

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

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

1656 # return ProcessingState.Reprocess 

1657 # 

1658 # return ProcessingState.Skipped 

1659 

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

1661 line = yield from self._SectionStart(line) 

1662 

1663 while True: 

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

1665 line = yield line 

1666 continue 

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

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

1669 break 

1670 elif isinstance(line, VivadoMessage): 

1671 self._AddMessage(line) 

1672 else: 

1673 line._kind = LineKind.Verbose 

1674 

1675 line = yield line 

1676 

1677 # line = yield line 

1678 nextLine = yield from self._SectionFinish(line) 

1679 return nextLine 

1680 

1681 

1682@export 

1683class SubSection(BaseParser, BaseSection): 

1684 """ 

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

1686 """ 

1687 # _NAME: ClassVar[str] 

1688 

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

1690 

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

1692 """ 

1693 Initialized a subsection. 

1694 

1695 :param section: Reference to the parent section. 

1696 """ 

1697 super().__init__() 

1698 self._section = section 

1699 

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

1701 line._kind = LineKind.SubSectionStart 

1702 

1703 line = yield line 

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

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

1706 else: 

1707 line._kind |= LineKind.ProcessorError 

1708 

1709 nextLine = yield line 

1710 return nextLine 

1711 

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

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

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

1715 else: 

1716 line._kind |= LineKind.ProcessorError 

1717 

1718 line = yield line 

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

1720 line._kind = LineKind.SubSectionEnd 

1721 else: 

1722 line._kind |= LineKind.ProcessorError 

1723 

1724 line = yield line 

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

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

1727 else: 

1728 line._kind |= LineKind.ProcessorError 

1729 

1730 nextLine = yield line 

1731 return nextLine 

1732 

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

1734 line = yield from self._SectionStart(line) 

1735 

1736 while True: 

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

1738 line = yield line 

1739 continue 

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

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

1742 break 

1743 elif isinstance(line, VivadoMessage): 

1744 self._AddMessage(line) 

1745 else: 

1746 line._kind = LineKind.Verbose 

1747 

1748 line = yield line 

1749 

1750 nextLine = yield from self._SectionFinish(line) 

1751 return nextLine 

1752 

1753 

1754@export 

1755class SectionWithChildren(Section): 

1756 """ 

1757 Base-class for sections with subsections. 

1758 """ 

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

1760 

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

1762 super().__init__(command) 

1763 

1764 self._subsections = {} 

1765 

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

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

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

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

1770 raise ex 

1771 

1772 return key in self._subsections 

1773 

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

1775 try: 

1776 return self._subsections[item] 

1777 except KeyError as ex: 

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

1779 

1780 

1781@export 

1782class Task(BaseParser, VivadoMessagesMixin): 

1783 """ 

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

1785 

1786 .. rubric:: Extracted information 

1787 

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

1789 

1790 .. rubric:: Example 

1791 

1792 .. code-block:: 

1793 

1794 Starting Cache Timing Information Task 

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

1796 Ending Cache Timing Information Task | Checksum: 19fe8cb97 

1797 

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

1799 

1800 """ 

1801 # _NAME: ClassVar[str] 

1802 # _START: ClassVar[str] 

1803 # _FINISH: ClassVar[str] 

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

1805 

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

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

1808 

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

1810 """ 

1811 Initializes a task (without child elements). 

1812 

1813 :param command: Reference to the command. 

1814 """ 

1815 super().__init__() 

1816 VivadoMessagesMixin.__init__(self) 

1817 

1818 self._command = command 

1819 

1820 @readonly 

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

1822 """ 

1823 Read-only property to access the command. 

1824 

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

1826 """ 

1827 return self._command 

1828 

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

1830 """ 

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

1832 

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

1834 :returns: A generator processing log messages. 

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

1836 """ 

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

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

1839 

1840 line._kind = LineKind.TaskStart 

1841 nextLine = yield line 

1842 return nextLine 

1843 

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

1845 """ 

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

1847 

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

1849 :returns: A generator processing log messages. 

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

1851 """ 

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

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

1854 

1855 line._kind = LineKind.TaskEnd 

1856 line = yield line 

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

1858 if line.StartsWith(self._TIME): 

1859 line._kind = LineKind.TaskTime 

1860 break 

1861 

1862 line = yield line 

1863 

1864 nextLine = yield line 

1865 return nextLine 

1866 

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

1868 """ 

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

1870 

1871 .. rubric:: Algorithm 

1872 

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

1874 2. Process body lines 

1875 

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

1877 * Check for *task finish* pattern. 

1878 * Check for *time* pattern. 

1879 

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

1881 

1882 :param line: First line to process. 

1883 :returns: A generator processing log messages. 

1884 """ 

1885 line = yield from self._TaskStart(line) 

1886 

1887 while True: 

1888 if line._kind is LineKind.Empty: 

1889 line = yield line 

1890 continue 

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

1892 break 

1893 elif isinstance(line, VivadoMessage): 

1894 self._AddMessage(line) 

1895 elif line.StartsWith(self._TIME): 

1896 line._kind = LineKind.TaskTime 

1897 nextLine = yield line 

1898 return nextLine 

1899 

1900 line = yield line 

1901 

1902 nextLine = yield from self._TaskFinish(line) 

1903 return nextLine 

1904 

1905 def __str__(self) -> str: 

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

1907 

1908 

1909@export 

1910class TaskWithSubTasks(Task): 

1911 """ 

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

1913 

1914 .. rubric:: Extracted information 

1915 

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

1917 * Subtasks 

1918 

1919 .. rubric:: Example 

1920 

1921 .. code-block:: 

1922 

1923 Starting Cache Timing Information Task 

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

1925 Ending Cache Timing Information Task | Checksum: 19fe8cb97 

1926 

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

1928 

1929 """ 

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

1931 

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

1933 

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

1935 super().__init__(command) 

1936 

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

1938 

1939 @readonly 

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

1941 return self._subtasks 

1942 

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

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

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

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

1947 raise ex 

1948 

1949 return key in self._subtasks 

1950 

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

1952 try: 

1953 return self._subtasks[key] 

1954 except KeyError as ex: 

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

1956 

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

1958 line = yield from self._TaskStart(line) 

1959 

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

1961 

1962 while True: 

1963 while True: 

1964 if line._kind is LineKind.Empty: 

1965 line = yield line 

1966 continue 

1967 elif isinstance(line, VivadoMessage): 

1968 self._AddMessage(line) 

1969 elif line.StartsWith("Starting "): 

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

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

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

1973 break 

1974 else: 

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

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

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

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

1979 raise ex 

1980 break 

1981 elif line.StartsWith("Ending"): 

1982 nextLine = yield from self._TaskFinish(line) 

1983 return nextLine 

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

1985 line._kind = LineKind.TaskTime 

1986 nextLine = yield line 

1987 return nextLine 

1988 

1989 line = yield line 

1990 

1991 while True: 

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

1993 

1994 try: 

1995 processedLine = subtask.send(line) 

1996 

1997 if isinstance(processedLine, VivadoMessage): 

1998 self._AddMessage(processedLine) 

1999 

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

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

2002 line = yield processedLine 

2003 break 

2004 except StopIteration as ex: 

2005 activeParsers.remove(parser) 

2006 line = ex.value 

2007 break 

2008 

2009 line = yield processedLine 

2010 

2011 

2012@export 

2013class SubTask(BaseParser, VivadoMessagesMixin): 

2014 # _NAME: ClassVar[str] 

2015 # _START: ClassVar[str] 

2016 # _FINISH: ClassVar[str] 

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

2018 

2019 _task: TaskWithSubTasks 

2020 _duration: float 

2021 

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

2023 super().__init__() 

2024 VivadoMessagesMixin.__init__(self) 

2025 

2026 self._task = task 

2027 

2028 @readonly 

2029 def Task(self) -> TaskWithSubTasks: 

2030 return self._task 

2031 

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

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

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

2035 

2036 line._kind = LineKind.TaskStart 

2037 nextLine = yield line 

2038 return nextLine 

2039 

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

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

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

2043 

2044 line._kind = LineKind.TaskEnd 

2045 line = yield line 

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

2047 if line.StartsWith(self._TIME): 

2048 line._kind = LineKind.TaskTime 

2049 break 

2050 

2051 line = yield line 

2052 

2053 nextLine = yield line 

2054 return nextLine 

2055 

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

2057 line = yield from self._TaskStart(line) 

2058 

2059 while True: 

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

2061 line = yield line 

2062 continue 

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

2064 break 

2065 elif isinstance(line, VivadoMessage): 

2066 self._AddMessage(line) 

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

2068 line._kind = LineKind.TaskTime 

2069 nextLine = yield line 

2070 return nextLine 

2071 

2072 line = yield line 

2073 

2074 nextLine = yield from self._TaskFinish(line) 

2075 return nextLine 

2076 

2077 def __str__(self) -> str: 

2078 return self._NAME 

2079 

2080 

2081@export 

2082class TaskWithPhases(Task): 

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

2084 

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

2086 

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

2088 super().__init__(command) 

2089 

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

2091 

2092 @readonly 

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

2094 return self._phases 

2095 

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

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

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

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

2100 raise ex 

2101 

2102 return key in self._phases 

2103 

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

2105 try: 

2106 return self._phases[key] 

2107 except KeyError as ex: 

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

2109 

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

2111 line = yield from self._TaskStart(line) 

2112 

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

2114 

2115 while True: 

2116 while True: 

2117 if line._kind is LineKind.Empty: 

2118 line = yield line 

2119 continue 

2120 elif isinstance(line, VivadoMessage): 

2121 self._AddMessage(line) 

2122 elif line.StartsWith("Phase "): 

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

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

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

2126 break 

2127 else: 

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

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

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

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

2132 raise ex 

2133 break 

2134 elif line.StartsWith("Ending"): 

2135 nextLine = yield from self._TaskFinish(line) 

2136 return nextLine 

2137 elif line.StartsWith(self._TIME): 

2138 line._kind = LineKind.TaskTime 

2139 nextLine = yield line 

2140 return nextLine 

2141 

2142 line = yield line 

2143 

2144 while True: 

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

2146 

2147 try: 

2148 processedLine = phase.send(line) 

2149 

2150 if isinstance(processedLine, VivadoMessage): 

2151 self._AddMessage(processedLine) 

2152 

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

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

2155 line = yield processedLine 

2156 break 

2157 except StopIteration as ex: 

2158 activeParsers.remove(parser) 

2159 line = ex.value 

2160 break 

2161 

2162 line = yield processedLine 

2163 

2164 

2165@export 

2166class Phase(BaseParser, VivadoMessagesMixin): 

2167 # _NAME: ClassVar[str] 

2168 # _START: ClassVar[str] 

2169 # _FINISH: ClassVar[str] 

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

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

2172 

2173 _task: TaskWithPhases 

2174 _phaseIndex: int 

2175 _duration: float 

2176 

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

2178 super().__init__() 

2179 VivadoMessagesMixin.__init__(self) 

2180 

2181 self._task = task 

2182 self._phaseIndex = None 

2183 

2184 @readonly 

2185 def Task(self) -> TaskWithPhases: 

2186 return self._task 

2187 

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

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

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

2191 

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

2193 

2194 line._kind = LineKind.PhaseStart 

2195 nextLine = yield line 

2196 return nextLine 

2197 

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

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

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

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

2202 

2203 line._kind = LineKind.PhaseEnd 

2204 line = yield line 

2205 

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

2207 while True: 

2208 if line.StartsWith(self._TIME): 

2209 line._kind = LineKind.PhaseTime 

2210 break 

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

2212 self._AddMessage(line) 

2213 

2214 line = yield line 

2215 

2216 line = yield line 

2217 

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

2219 while True: 

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

2221 line._kind = LineKind.PhaseFinal 

2222 break 

2223 elif isinstance(line, VivadoMessage): 

2224 self._AddMessage(line) 

2225 

2226 line = yield line 

2227 

2228 line = yield line 

2229 

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

2231 

2232 return line 

2233 

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

2235 line = yield from self._PhaseStart(line) 

2236 

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

2238 

2239 while True: 

2240 if line._kind is LineKind.Empty: 

2241 line = yield line 

2242 continue 

2243 elif isinstance(line, VivadoMessage): 

2244 self._AddMessage(line) 

2245 elif line.StartsWith(FINISH): 

2246 break 

2247 

2248 line = yield line 

2249 

2250 nextLine = yield from self._PhaseFinish(line) 

2251 return nextLine 

2252 

2253 def __str__(self) -> str: 

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

2255 

2256 

2257@export 

2258class PhaseWithChildren(Phase): 

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

2260 

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

2262 

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

2264 super().__init__(task) 

2265 

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

2267 

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

2269 if not issubclass(key, SubPhase): 

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

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

2272 raise ex 

2273 

2274 return key in self._subPhases 

2275 

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

2277 try: 

2278 return self._subPhases[key] 

2279 except KeyError as ex: 

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

2281 

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

2283 line = yield from self._PhaseStart(line) 

2284 

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

2286 

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

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

2289 

2290 while True: 

2291 while True: 

2292 if line._kind is LineKind.Empty: 

2293 line = yield line 

2294 continue 

2295 elif isinstance(line, VivadoMessage): 

2296 self._AddMessage(line) 

2297 elif line.StartsWith(SUBPHASE_PREFIX): 

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

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

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

2301 break 

2302 else: 

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

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

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

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

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

2308 raise ex 

2309 break 

2310 elif line.StartsWith(FINISH): 

2311 nextLine = yield from self._PhaseFinish(line) 

2312 return nextLine 

2313 

2314 line = yield line 

2315 

2316 while True: 

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

2318 

2319 try: 

2320 processedLine = phase.send(line) 

2321 

2322 if isinstance(processedLine, VivadoMessage): 

2323 self._AddMessage(processedLine) 

2324 

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

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

2327 line = yield processedLine 

2328 break 

2329 except StopIteration as ex: 

2330 activeParsers.remove(parser) 

2331 line = ex.value 

2332 break 

2333 

2334 line = yield processedLine 

2335 

2336 

2337@export 

2338class SubPhase(BaseParser, VivadoMessagesMixin): 

2339 # _NAME: ClassVar[str] 

2340 # _START: ClassVar[str] 

2341 # _FINISH: ClassVar[str] 

2342 

2343 _phase: Phase 

2344 _phaseIndex: int 

2345 _subPhaseIndex: int 

2346 _duration: float 

2347 

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

2349 super().__init__() 

2350 VivadoMessagesMixin.__init__(self) 

2351 

2352 self._phase = phase 

2353 self._phaseIndex = None 

2354 self._subPhaseIndex = None 

2355 

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

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

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

2359 

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

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

2362 

2363 line._kind = LineKind.SubPhaseStart 

2364 nextLine = yield line 

2365 return nextLine 

2366 

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

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

2369 

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

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

2372 

2373 if self._TIME is None: 

2374 line._kind = LineKind.SubPhaseTime 

2375 else: 

2376 line._kind = LineKind.SubPhaseEnd 

2377 

2378 line = yield line 

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

2380 if line.StartsWith(self._TIME): 

2381 line._kind = LineKind.SubPhaseTime 

2382 break 

2383 

2384 line = yield line 

2385 

2386 nextLine = yield line 

2387 return nextLine 

2388 

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

2390 line = yield from self._SubPhaseStart(line) 

2391 

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

2393 

2394 while True: 

2395 if line._kind is LineKind.Empty: 

2396 line = yield line 

2397 continue 

2398 elif line.StartsWith(FINISH): 

2399 break 

2400 elif isinstance(line, VivadoMessage): 

2401 self._AddMessage(line) 

2402 

2403 line = yield line 

2404 

2405 nextLine = yield from self._SubPhaseFinish(line) 

2406 return nextLine 

2407 

2408 def __str__(self) -> str: 

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

2410 

2411 

2412@export 

2413class SubPhaseWithChildren(SubPhase): 

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

2415 

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

2417 super().__init__(phase) 

2418 

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

2420 

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

2422 if not issubclass(key, SubSubPhase): 

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

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

2425 raise ex 

2426 

2427 return key in self._subSubPhases 

2428 

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

2430 try: 

2431 return self._subSubPhases[key] 

2432 except KeyError as ex: 

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

2434 

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

2436 line = yield from self._SubPhaseStart(line) 

2437 

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

2439 

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

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

2442 

2443 while True: 

2444 while True: 

2445 if line._kind is LineKind.Empty: 

2446 line = yield line 

2447 continue 

2448 elif isinstance(line, VivadoMessage): 

2449 self._AddMessage(line) 

2450 elif line.StartsWith(START_PREFIX): 

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

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

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

2454 break 

2455 else: 

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

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

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

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

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

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

2462 raise ex 

2463 break 

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

2465 nextLine = yield from self._SubPhaseFinish(line) 

2466 return nextLine 

2467 

2468 line = yield line 

2469 

2470 while True: 

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

2472 

2473 try: 

2474 processedLine = phase.send(line) 

2475 

2476 if isinstance(processedLine, VivadoMessage): 

2477 self._AddMessage(processedLine) 

2478 

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

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

2481 line = yield processedLine 

2482 break 

2483 except StopIteration as ex: 

2484 activeParsers.remove(parser) 

2485 line = ex.value 

2486 break 

2487 

2488 line = yield processedLine 

2489 

2490 

2491@export 

2492class SubSubPhase(BaseParser, VivadoMessagesMixin): 

2493 # _NAME: ClassVar[str] 

2494 # _START: ClassVar[str] 

2495 # _FINISH: ClassVar[str] 

2496 

2497 _subphase: SubPhase 

2498 _phaseIndex: int 

2499 _subPhaseIndex: int 

2500 _subSubPhaseIndex: int 

2501 _duration: float 

2502 

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

2504 super().__init__() 

2505 VivadoMessagesMixin.__init__(self) 

2506 

2507 self._subphase = subphase 

2508 self._phaseIndex = None 

2509 self._subPhaseIndex = None 

2510 self._subSubPhaseIndex = None 

2511 

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

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

2514 raise ProcessorException() 

2515 

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

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

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

2519 

2520 line._kind = LineKind.SubSubPhaseStart 

2521 nextLine = yield line 

2522 return nextLine 

2523 

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

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

2526 

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

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

2529 

2530 line._kind = LineKind.SubSubPhaseEnd 

2531 line = yield line 

2532 

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

2534 if line.StartsWith(self._TIME): 

2535 line._kind = LineKind.SubSubPhaseTime 

2536 break 

2537 

2538 line = yield line 

2539 

2540 nextLine = yield line 

2541 return nextLine 

2542 

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

2544 line = yield from self._SubSubPhaseStart(line) 

2545 

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

2547 

2548 while True: 

2549 if line._kind is LineKind.Empty: 

2550 line = yield line 

2551 continue 

2552 elif line.StartsWith(FINISH): 

2553 break 

2554 elif isinstance(line, VivadoMessage): 

2555 self._AddMessage(line) 

2556 

2557 line = yield line 

2558 

2559 nextLine = yield from self._SubSubPhaseFinish(line) 

2560 return nextLine 

2561 

2562 def __str__(self) -> str: 

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

2564 

2565 

2566@export 

2567class SubSubPhaseWithChildren(SubSubPhase): 

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

2569 

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

2571 super().__init__(subphase) 

2572 

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

2574 

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

2576 if not issubclass(key, SubSubSubPhase): 

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

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

2579 raise ex 

2580 

2581 return key in self._subSubSubPhases 

2582 

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

2584 try: 

2585 return self._subSubSubPhases[key] 

2586 except KeyError as ex: 

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

2588 

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

2590 line = yield from self._SubSubPhaseStart(line) 

2591 

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

2593 

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

2595 

2596 while True: 

2597 while True: 

2598 if line._kind is LineKind.Empty: 

2599 line = yield line 

2600 continue 

2601 elif isinstance(line, VivadoMessage): 

2602 self._AddMessage(line) 

2603 elif line.StartsWith(START_PREFIX): 

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

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

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

2607 break 

2608 else: 

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

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

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

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

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

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

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

2616 raise ex 

2617 break 

2618 elif line.StartsWith(self._TIME): 

2619 line._kind = LineKind.SubSubPhaseTime 

2620 nextLine = yield line 

2621 return nextLine 

2622 

2623 line = yield line 

2624 

2625 while True: 

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

2627 

2628 try: 

2629 processedLine = phase.send(line) 

2630 

2631 if isinstance(processedLine, VivadoMessage): 

2632 self._AddMessage(processedLine) 

2633 

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

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

2636 line = yield processedLine 

2637 break 

2638 except StopIteration as ex: 

2639 activeParsers.remove(parser) 

2640 line = ex.value 

2641 break 

2642 

2643 line = yield processedLine 

2644 

2645 

2646@export 

2647class SubSubSubPhase(BaseParser, VivadoMessagesMixin): 

2648 # _NAME: ClassVar[str] 

2649 # _START: ClassVar[str] 

2650 # _FINISH: ClassVar[str] 

2651 

2652 _subsubphase: SubSubPhase 

2653 _phaseIndex: int 

2654 _subPhaseIndex: int 

2655 _subSubPhaseIndex: int 

2656 _subSubSubPhaseIndex: int 

2657 _duration: float 

2658 

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

2660 super().__init__() 

2661 VivadoMessagesMixin.__init__(self) 

2662 

2663 self._subsubphase = subsubphase 

2664 self._phaseIndex = None 

2665 self._subPhaseIndex = None 

2666 self._subSubPhaseIndex = None 

2667 self._subSubSubPhaseIndex = None 

2668 

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

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

2671 raise ProcessorException() 

2672 

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

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

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

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

2677 

2678 line._kind = LineKind.SubSubSubPhaseStart 

2679 nextLine = yield line 

2680 return nextLine 

2681 

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

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

2684 

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

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

2687 

2688 line._kind = LineKind.SubSubSubPhaseEnd 

2689 line = yield line 

2690 

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

2692 if line.StartsWith(self._TIME): 

2693 line._kind = LineKind.SubSubSubPhaseTime 

2694 break 

2695 

2696 line = yield line 

2697 

2698 nextLine = yield line 

2699 return nextLine 

2700 

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

2702 line = yield from self._SubSubSubPhaseStart(line) 

2703 

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

2705 

2706 while True: 

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

2708 line = yield line 

2709 continue 

2710 elif line.StartsWith(FINISH): 

2711 break 

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

2713 self._AddMessage(line) 

2714 

2715 line = yield line 

2716 

2717 nextLine = yield from self._SubSubSubPhaseFinish(line) 

2718 return nextLine 

2719 

2720 def __str__(self) -> str: 

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

2722 

2723 

2724@export 

2725class SubSubSubPhaseWithTasks(SubSubSubPhase): 

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

2727 

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

2729 super().__init__(subsubphase) 

2730 

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

2732 

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

2734 if not issubclass(key, NestedTask): 

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

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

2737 raise ex 

2738 

2739 return key in self._nestedTasks 

2740 

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

2742 try: 

2743 return self._nestedTasks[key] 

2744 except KeyError as ex: 

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

2746 

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

2748 line = yield from self._SubSubSubPhaseStart(line) 

2749 

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

2751 

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

2753 

2754 while True: 

2755 while True: 

2756 if line._kind is LineKind.Empty: 

2757 line = yield line 

2758 continue 

2759 elif isinstance(line, VivadoMessage): 

2760 self._AddMessage(line) 

2761 elif line.StartsWith("Starting "): 

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

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

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

2765 break 

2766 else: 

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

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

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

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

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

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

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

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

2775 raise ex 

2776 break 

2777 elif line.StartsWith(self._TIME): 

2778 line._kind = LineKind.SubSubSubPhaseTime 

2779 nextLine = yield line 

2780 return nextLine 

2781 

2782 line = yield line 

2783 

2784 while True: 

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

2786 

2787 try: 

2788 processedLine = phase.send(line) 

2789 

2790 if isinstance(processedLine, VivadoMessage): 

2791 self._AddMessage(processedLine) 

2792 

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

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

2795 line = yield processedLine 

2796 break 

2797 except StopIteration as ex: 

2798 activeParsers.remove(parser) 

2799 line = ex.value 

2800 break 

2801 

2802 line = yield processedLine 

2803 

2804 

2805@export 

2806class NestedTask(BaseParser, VivadoMessagesMixin): 

2807 # _NAME: ClassVar[str] 

2808 # _START: ClassVar[str] 

2809 # _FINISH: ClassVar[str] 

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

2811 

2812 _subsubsubphase: SubSubSubPhaseWithTasks 

2813 _duration: float 

2814 

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

2816 super().__init__() 

2817 VivadoMessagesMixin.__init__(self) 

2818 

2819 self._subsubsubphase = subsubsubphase 

2820 

2821 @readonly 

2822 def SubSubSubPhase(self) -> SubSubSubPhaseWithTasks: 

2823 return self._subsubsubphase 

2824 

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

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

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

2828 

2829 line._kind = LineKind.NestedTaskStart 

2830 nextLine = yield line 

2831 return nextLine 

2832 

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

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

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

2836 

2837 line._kind = LineKind.NestedTaskEnd 

2838 line = yield line 

2839 

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

2841 while True: 

2842 if line.StartsWith(self._TIME): 

2843 line._kind = LineKind.TaskTime 

2844 line = yield line 

2845 break 

2846 

2847 line = yield line 

2848 

2849 return line 

2850 

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

2852 line = yield from self._NestedTaskStart(line) 

2853 

2854 while True: 

2855 if line._kind is LineKind.Empty: 

2856 line = yield line 

2857 continue 

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

2859 break 

2860 elif isinstance(line, VivadoMessage): 

2861 self._AddMessage(line) 

2862 elif line.StartsWith(self._TIME): 

2863 line._kind = LineKind.TaskTime 

2864 nextLine = yield line 

2865 return nextLine 

2866 

2867 line = yield line 

2868 

2869 nextLine = yield from self._NestedTaskFinish(line) 

2870 return nextLine 

2871 

2872 def __str__(self) -> str: 

2873 return self._NAME 

2874 

2875 

2876@export 

2877class NestedTaskWithPhases(NestedTask): 

2878 """ 

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

2880 

2881 .. rubric:: Extracted information 

2882 

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

2884 * Nested phases 

2885 

2886 .. rubric:: Example 

2887 

2888 .. code-block:: 

2889 

2890 Phase 4.1.1.1 BUFG Insertion 

2891 

2892 Starting Physical Synthesis Task 

2893 

2894 Phase 1 Physical Synthesis Initialization 

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

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

2897 Phase 1 Physical Synthesis Initialization | Checksum: 1818afcc0 

2898 

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

2900 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. 

2901 Ending Physical Synthesis Task | Checksum: 22839c186 

2902 

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

2904 Phase 4.1.1.1 BUFG Insertion | Checksum: 1a8cbaaf2 

2905 """ 

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

2907 

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

2909 

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

2911 super().__init__(subsubsubPhase) 

2912 

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

2914 

2915 @readonly 

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

2917 return self._nestedPhases 

2918 

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

2920 if not issubclass(key, NestedPhase): 

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

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

2923 raise ex 

2924 

2925 return key in self._nestedPhases 

2926 

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

2928 try: 

2929 return self._nestedPhases[key] 

2930 except KeyError as ex: 

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

2932 

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

2934 line = yield from self._NestedTaskStart(line) 

2935 

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

2937 

2938 while True: 

2939 while True: 

2940 if line._kind is LineKind.Empty: 

2941 line = yield line 

2942 continue 

2943 elif isinstance(line, VivadoMessage): 

2944 self._AddMessage(line) 

2945 elif line.StartsWith("Phase "): 

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

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

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

2949 break 

2950 else: 

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

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

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

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

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

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

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

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

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

2960 raise ex 

2961 break 

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

2963 nextLine = yield from self._NestedTaskFinish(line) 

2964 return nextLine 

2965 elif line.StartsWith(self._TIME): 

2966 line._kind = LineKind.TaskTime 

2967 nextLine = yield line 

2968 return nextLine 

2969 

2970 line = yield line 

2971 

2972 while True: 

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

2974 

2975 try: 

2976 processedLine = phase.send(line) 

2977 

2978 if isinstance(processedLine, VivadoMessage): 

2979 self._AddMessage(processedLine) 

2980 

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

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

2983 line = yield processedLine 

2984 break 

2985 except StopIteration as ex: 

2986 activeParsers.remove(parser) 

2987 line = ex.value 

2988 break 

2989 

2990 line = yield processedLine 

2991 

2992 

2993@export 

2994class NestedPhase(BaseParser, VivadoMessagesMixin): 

2995 # _NAME: ClassVar[str] 

2996 # _START: ClassVar[str] 

2997 # _FINISH: ClassVar[str] 

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

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

3000 

3001 _nestedTask: NestedTaskWithPhases 

3002 _phaseIndex: int 

3003 _duration: float 

3004 

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

3006 super().__init__() 

3007 VivadoMessagesMixin.__init__(self) 

3008 

3009 self._nestedTask = nestedTask 

3010 self._phaseIndex = None 

3011 

3012 @readonly 

3013 def NestedTask(self) -> NestedTaskWithPhases: 

3014 return self._nestedTask 

3015 

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

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

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

3019 

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

3021 

3022 line._kind = LineKind.NestedPhaseStart 

3023 nextLine = yield line 

3024 return nextLine 

3025 

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

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

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

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

3030 

3031 line._kind = LineKind.NestedPhaseEnd 

3032 line = yield line 

3033 

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

3035 while True: 

3036 if line.StartsWith(self._TIME): 

3037 line._kind = LineKind.PhaseTime 

3038 break 

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

3040 self._AddMessage(line) 

3041 

3042 line = yield line 

3043 

3044 line = yield line 

3045 

3046 return line 

3047 

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

3049 line = yield from self._NestedPhaseStart(line) 

3050 

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

3052 

3053 while True: 

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

3055 line = yield line 

3056 continue 

3057 elif isinstance(line, VivadoMessage): 

3058 self._AddMessage(line) 

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

3060 break 

3061 

3062 line = yield line 

3063 

3064 nextLine = yield from self._NestedPhaseFinish(line) 

3065 return nextLine 

3066 

3067 def __str__(self) -> str: 

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

3069 

3070 

3071@export 

3072class Synth_Design(CommandWithSections): 

3073 """ 

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

3075 """ 

3076 from . import SynthesizeDesign as _SynthDesign 

3077 

3078 _TCL_COMMAND: ClassVar[str] = "synth_design" 

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

3080 _SynthDesign.RTLElaboration, 

3081 _SynthDesign.HandlingCustomAttributes, 

3082 _SynthDesign.ConstraintValidation, 

3083 _SynthDesign.LoadingPart, 

3084 _SynthDesign.ApplySetPropertyXDCConstraints, 

3085 _SynthDesign.RTLComponentStatistics, 

3086 _SynthDesign.RTLHierarchicalComponentStatistics, 

3087 _SynthDesign.PartResourceSummary, 

3088 _SynthDesign.CrossBoundaryAndAreaOptimization, 

3089 _SynthDesign.ROM_RAM_DSP_SR_Retiming, 

3090 _SynthDesign.ApplyingXDCTimingConstraints, 

3091 _SynthDesign.TimingOptimization, 

3092 _SynthDesign.TechnologyMapping, 

3093 _SynthDesign.IOInsertion, 

3094 _SynthDesign.FlatteningBeforeIOInsertion, 

3095 _SynthDesign.FinalNetlistCleanup, 

3096 _SynthDesign.RenamingGeneratedInstances, 

3097 _SynthDesign.RebuildingUserHierarchy, 

3098 _SynthDesign.RenamingGeneratedPorts, 

3099 _SynthDesign.RenamingGeneratedNets, 

3100 _SynthDesign.WritingSynthesisReport, 

3101 ) 

3102 

3103 @readonly 

3104 def HasLatches(self) -> bool: 

3105 """ 

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

3107 

3108 Latch detection is based on: 

3109 

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

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

3112 

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

3114 """ 

3115 from .SynthesizeDesign import WritingSynthesisReport 

3116 

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

3118 return True 

3119 

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

3121 

3122 @readonly 

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

3124 """ 

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

3126 

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

3128 

3129 .. note:: 

3130 

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

3132 

3133 .. code-block:: 

3134 

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

3136 """ 

3137 if 8 in self._messagesByID: 

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

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

3140 

3141 return [] 

3142 

3143 @readonly 

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

3145 """ 

3146 

3147 :returns: undocumented 

3148 

3149 .. note:: 

3150 

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

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

3153 """ 

3154 

3155 @readonly 

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

3157 """ 

3158 

3159 :returns: undocumented 

3160 """ 

3161 

3162 @readonly 

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

3164 """ 

3165 

3166 :returns: undocumented 

3167 """ 

3168 

3169 

3170 @readonly 

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

3172 """ 

3173 

3174 :returns: undocumented 

3175 """ 

3176 

3177 @readonly 

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

3179 """ 

3180 

3181 :returns: undocumented 

3182 

3183 .. note:: 

3184 

3185 Static Shift Register Report: 

3186 Dynamic Shift Register Report: 

3187 """ 

3188 

3189 @readonly 

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

3191 """ 

3192 

3193 :returns: undocumented 

3194 

3195 .. note:: 

3196 

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

3198 """ 

3199 

3200 # ignored instructions 

3201 # 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. 

3202 

3203 @readonly 

3204 def HasBlackboxes(self) -> bool: 

3205 """ 

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

3207 

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

3209 """ 

3210 from .SynthesizeDesign import WritingSynthesisReport 

3211 

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

3213 

3214 @readonly 

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

3216 """ 

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

3218 

3219 :returns: The dictionary of found blackbox statistics. 

3220 """ 

3221 from .SynthesizeDesign import WritingSynthesisReport 

3222 

3223 return self._sections[WritingSynthesisReport]._blackboxes 

3224 

3225 @readonly 

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

3227 """ 

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

3229 

3230 :returns: The dictionary of used cell statistics. 

3231 """ 

3232 from .SynthesizeDesign import WritingSynthesisReport 

3233 

3234 return self._sections[WritingSynthesisReport]._cells 

3235 

3236 @readonly 

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

3238 """ 

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

3240 

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

3242 

3243 .. note:: 

3244 

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

3246 

3247 .. code-block:: 

3248 

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

3250 """ 

3251 if 8 in self._messagesByID: 

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

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

3254 

3255 return [] 

3256 

3257 @readonly 

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

3259 """ 

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

3261 

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

3263 

3264 .. note:: 

3265 

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

3267 

3268 .. code-block:: 

3269 

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

3271 """ 

3272 if 8 in self._messagesByID: 

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

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

3275 

3276 return [] 

3277 

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

3279 from .SynthesizeDesign import RTLElaboration 

3280 

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

3282 raise ProcessorException() # FIXME: add exception message 

3283 

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

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

3286 

3287 line = yield line 

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

3289 line._kind = LineKind.Verbose 

3290 else: 

3291 raise ProcessorException() # FIXME: add exception message 

3292 

3293 line = yield line 

3294 while True: 

3295 while True: 

3296 if line._kind is LineKind.Empty: 

3297 line = yield line 

3298 continue 

3299 elif isinstance(line, VivadoMessage): 

3300 self._AddMessage(line) 

3301 elif line.StartsWith("Start "): 

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

3303 if line.StartsWith(parser._START): 

3304 # Found a suitable section parser. 

3305 # Add section parser to list of found sections. 

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

3307 if parser not in self._sections: 

3308 self._sections.append(parser) 

3309 else: 

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

3311 parser = newParser 

3312 

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

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

3315 break 

3316 else: 

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

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

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

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

3321 raise ex 

3322 break 

3323 elif line.StartsWith("Starting "): 

3324 if line.StartsWith(rtlElaboration._START): 

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

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

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

3328 break 

3329 elif line.StartsWith(self._TCL_COMMAND): 

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

3331 line._kind |= LineKind.Success 

3332 

3333 # FIXME: use similar style like for _TIME 

3334 line = yield line 

3335 lastLine = yield line 

3336 return lastLine 

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

3338 line._kind = LineKind.PhaseEnd 

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

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

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

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

3343 

3344 line = yield line 

3345 

3346 line = yield line 

3347 

3348 while True: 

3349 if line.StartsWith("Finished"): 

3350 l = line[9:] 

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

3352 line = yield section.send(line) 

3353 break 

3354 

3355 if isinstance(line, VivadoMessage): 

3356 self._AddMessage(line) 

3357 

3358 line = yield section.send(line) 

3359 

3360 line = yield section.send(line) 

3361 

3362 if not parser._DUPLICATES: 

3363 activeParsers.remove(parser) 

3364 

3365 

3366@export 

3367class Link_Design(Command): 

3368 """ 

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

3370 """ 

3371 _TCL_COMMAND: ClassVar[str] = "link_design" 

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

3373 

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

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

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

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

3378 

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

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

3381 

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

3383 super().__init__(processor) 

3384 

3385 self._commonXDCFiles = {} 

3386 self._perCellXDCFiles = {} 

3387 

3388 @readonly 

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

3390 return self._commonXDCFiles 

3391 

3392 @readonly 

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

3394 return self._perCellXDCFiles 

3395 

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

3397 line = yield from self._CommandStart(line) 

3398 

3399 end = f"{self._TCL_COMMAND} " 

3400 while True: 

3401 if line._kind is LineKind.Empty: 

3402 line = yield line 

3403 continue 

3404 elif isinstance(line, VivadoMessage): 

3405 self._AddMessage(line) 

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

3407 line._kind = LineKind.Normal 

3408 

3409 path = Path(match[1]) 

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

3411 

3412 line = yield line 

3413 while True: 

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

3415 line = yield line 

3416 continue 

3417 elif isinstance(line, VivadoMessage): 

3418 self._AddMessage(line) 

3419 messages.append(line) 

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

3421 line._kind = LineKind.Normal 

3422 break 

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

3424 line._kind = LineKind.ProcessorError 

3425 break 

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

3427 break 

3428 

3429 line = yield line 

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

3431 line._kind = LineKind.Normal 

3432 

3433 path = Path(match[1]) 

3434 cell = match[2] 

3435 if path in self._perCellXDCFiles: 

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

3437 else: 

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

3439 

3440 line = yield line 

3441 while True: 

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

3443 line = yield line 

3444 continue 

3445 elif isinstance(line, VivadoMessage): 

3446 self._AddMessage(line) 

3447 messages.append(line) 

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

3449 line._kind = LineKind.Normal 

3450 break 

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

3452 line._kind = LineKind.ProcessorError 

3453 break 

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

3455 break 

3456 

3457 line = yield line 

3458 

3459 if line.StartsWith(end): 

3460 nextLine = yield from self._CommandFinish(line) 

3461 return nextLine 

3462 

3463 line = yield line 

3464 

3465 

3466@export 

3467class Opt_Design(CommandWithTasks): 

3468 """ 

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

3470 """ 

3471 from . import OptimizeDesign as _OptDesign 

3472 

3473 _TCL_COMMAND: ClassVar[str] = "opt_design" 

3474 _TIME: ClassVar[str] = None 

3475 

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

3477 _OptDesign.DRCTask, 

3478 _OptDesign.CacheTimingInformationTask, 

3479 _OptDesign.LogicOptimizationTask, 

3480 _OptDesign.PowerOptimizationTask, 

3481 _OptDesign.FinalCleanupTask, 

3482 _OptDesign.NetlistObfuscationTask 

3483 ) 

3484 

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

3486 line = yield from self._CommandStart(line) 

3487 

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

3489 

3490 while True: 

3491 while True: 

3492 if line._kind is LineKind.Empty: 

3493 line = yield line 

3494 continue 

3495 elif isinstance(line, VivadoMessage): 

3496 self._AddMessage(line) 

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

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

3499 if line.StartsWith(parser._START): 

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

3501 break 

3502 else: 

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

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

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

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

3507 raise ex 

3508 break 

3509 elif line.StartsWith(self._TCL_COMMAND): 

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

3511 line._kind |= LineKind.Success 

3512 

3513 # FIXME: use similar style like for _TIME 

3514 line = yield line 

3515 lastLine = yield line 

3516 return lastLine 

3517 # line._kind = LineKind.Unprocessed 

3518 

3519 line = yield line 

3520 

3521 while True: 

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

3523 # line = yield task.send(line) 

3524 # break 

3525 

3526 if isinstance(line, VivadoMessage): 

3527 self._AddMessage(line) 

3528 

3529 try: 

3530 line = yield task.send(line) 

3531 except StopIteration as ex: 

3532 task = None 

3533 line = ex.value 

3534 

3535 if isinstance(line, VivadoMessage): 

3536 line = yield line 

3537 

3538 break 

3539 

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

3541 line = yield task.send(line) 

3542 

3543 activeParsers.remove(parser) 

3544 

3545 

3546@export 

3547class Place_Design(CommandWithTasks): 

3548 """ 

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

3550 """ 

3551 from . import PlaceDesign as _PlaceDesign 

3552 

3553 _TCL_COMMAND: ClassVar[str] = "place_design" 

3554 _TIME: ClassVar[str] = None 

3555 

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

3557 _PlaceDesign.PlacerTask, 

3558 ) 

3559 

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

3561 line = yield from self._CommandStart(line) 

3562 

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

3564 

3565 while True: 

3566 while True: 

3567 if line._kind is LineKind.Empty: 

3568 line = yield line 

3569 continue 

3570 elif isinstance(line, VivadoMessage): 

3571 self._AddMessage(line) 

3572 elif line.StartsWith("Starting "): 

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

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

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

3576 break 

3577 else: 

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

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

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

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

3582 raise ex 

3583 break 

3584 elif line.StartsWith(self._TCL_COMMAND): 

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

3586 line._kind |= LineKind.Success 

3587 

3588 # FIXME: use similar style like for _TIME 

3589 line = yield line 

3590 lastLine = yield line 

3591 return lastLine 

3592 # line._kind = LineKind.Unprocessed 

3593 

3594 line = yield line 

3595 

3596 while True: 

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

3598 # line = yield task.send(line) 

3599 # break 

3600 

3601 if isinstance(line, VivadoMessage): 

3602 self._AddMessage(line) 

3603 

3604 try: 

3605 line = yield task.send(line) 

3606 except StopIteration as ex: 

3607 task = None 

3608 line = ex.value 

3609 

3610 if isinstance(line, VivadoMessage): 

3611 line = yield line 

3612 

3613 break 

3614 

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

3616 line = yield task.send(line) 

3617 

3618 activeParsers.remove(parser) 

3619 

3620 

3621@export 

3622class PhyOpt_Design(CommandWithTasks): 

3623 """ 

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

3625 """ 

3626 from . import PhysicalOptimizeDesign as _PhyOptDesign 

3627 

3628 _TCL_COMMAND: ClassVar[str] = "phys_opt_design" 

3629 _TIME: ClassVar[str] = None 

3630 

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

3632 _PhyOptDesign.InitialUpdateTimingTask, 

3633 _PhyOptDesign.PhysicalSynthesisTask 

3634 ) 

3635 

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

3637 line = yield from self._CommandStart(line) 

3638 

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

3640 

3641 while True: 

3642 while True: 

3643 if line._kind is LineKind.Empty: 

3644 line = yield line 

3645 continue 

3646 elif isinstance(line, VivadoMessage): 

3647 self._AddMessage(line) 

3648 elif line.StartsWith("Starting "): 

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

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

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

3652 break 

3653 else: 

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

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

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

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

3658 raise ex 

3659 break 

3660 elif line.StartsWith(self._TCL_COMMAND): 

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

3662 line._kind |= LineKind.Success 

3663 

3664 # FIXME: use similar style like for _TIME 

3665 line = yield line 

3666 lastLine = yield line 

3667 return lastLine 

3668 # line._kind = LineKind.Unprocessed 

3669 

3670 line = yield line 

3671 

3672 while True: 

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

3674 # line = yield task.send(line) 

3675 # break 

3676 

3677 if isinstance(line, VivadoMessage): 

3678 self._AddMessage(line) 

3679 

3680 try: 

3681 line = yield task.send(line) 

3682 except StopIteration as ex: 

3683 task = None 

3684 line = ex.value 

3685 

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

3687 line = yield line 

3688 

3689 break 

3690 

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

3692 line = yield task.send(line) 

3693 

3694 activeParsers.remove(parser) 

3695 

3696 

3697@export 

3698class Route_Design(CommandWithTasks): 

3699 """ 

3700 A Vivado command output parser for ``route_design``. 

3701 """ 

3702 from . import RouteDesign as _RouteDesign 

3703 

3704 _TCL_COMMAND: ClassVar[str] = "route_design" 

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

3706 

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

3708 _RouteDesign.RoutingTask, 

3709 ) 

3710 

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

3712 line = yield from self._CommandStart(line) 

3713 

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

3715 

3716 while True: 

3717 while True: 

3718 if line._kind is LineKind.Empty: 

3719 line = yield line 

3720 continue 

3721 elif isinstance(line, VivadoMessage): 

3722 self._AddMessage(line) 

3723 elif line.StartsWith("Starting "): 

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

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

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

3727 break 

3728 else: 

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

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

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

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

3733 raise ex 

3734 break 

3735 elif line.StartsWith(self._TCL_COMMAND): 

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

3737 line._kind |= LineKind.Success 

3738 

3739 # FIXME: use similar style like for _TIME 

3740 line = yield line 

3741 lastLine = yield line 

3742 return lastLine 

3743 # line._kind = LineKind.Unprocessed 

3744 

3745 line = yield line 

3746 

3747 while True: 

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

3749 # line = yield task.send(line) 

3750 # break 

3751 

3752 if isinstance(line, VivadoMessage): 

3753 self._AddMessage(line) 

3754 

3755 try: 

3756 line = yield task.send(line) 

3757 except StopIteration as ex: 

3758 task = None 

3759 line = ex.value 

3760 

3761 if isinstance(line, VivadoMessage): 3761 ↛ 3762line 3761 didn't jump to line 3762 because the condition on line 3761 was never true

3762 line = yield line 

3763 

3764 break 

3765 

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

3767 line = yield task.send(line) 

3768 

3769 activeParsers.remove(parser) 

3770 

3771 

3772@export 

3773class Write_Bitstream(Command): 

3774 """ 

3775 A Vivado command output parser for ``write_bitstream``. 

3776 """ 

3777 _TCL_COMMAND: ClassVar[str] = "write_bitstream" 

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

3779 

3780 

3781@export 

3782class Report_DRC(Command): 

3783 """ 

3784 A Vivado command output parser for ``report_drc``. 

3785 """ 

3786 _TCL_COMMAND: ClassVar[str] = "report_drc" 

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

3788 

3789 

3790@export 

3791class Report_Methodology(Command): 

3792 """ 

3793 A Vivado command output parser for ``report_methodology``. 

3794 """ 

3795 _TCL_COMMAND: ClassVar[str] = "report_methodology" 

3796 _TIME: ClassVar[str] = None 

3797 

3798 

3799@export 

3800class Report_Power(Command): 

3801 """ 

3802 A Vivado command output parser for ``report_power``. 

3803 """ 

3804 _TCL_COMMAND: ClassVar[str] = "report_power" 

3805 _TIME: ClassVar[str] = None 

3806 

3807 

3808@export 

3809class Open_Checkpoint(Command): 

3810 """ 

3811 A Vivado command output parser for ``open_checkpoint``. 

3812 """ 

3813 _TCL_COMMAND: ClassVar[str] = "open_checkpoint" 

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

3815 

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

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

3818 

3819 if line.StartsWith(end): 3819 ↛ 3823line 3819 didn't jump to line 3823 because the condition on line 3819 was always true

3820 line._kind = LineKind.TaskTime 

3821 line = yield line 

3822 else: 

3823 pass # FIXME: error 

3824 

3825 return line 

3826 

3827 

3828@export 

3829class VivadoProcessor(VivadoMessagesMixin, mixin=True): 

3830 """ 

3831 A processor for Vivado log outputs. 

3832 

3833 Each output line from Vivado gets processed and converted into a :class:`ProcessedLine` objects. Such lines form a 

3834 doubly-linked list. 

3835 """ 

3836 _duration: float #: Duration of the observed process (e.g. start to end of synthesis). 

3837 _processingDuration: float #: Duration for the log output processor to parse all log messages. 

3838 

3839 _lines: List[VivadoLine] #: A list of processed log message lines. 

3840 _preamble: Nullable[Preamble] #: Reference to the Vivado preamble written after tool startup. 

3841 _postamble: Nullable[Postamble] #: Reference to the Vivado postamble written after tool startup. 

3842 _commands: Dict[Type[Command], Command] #: A dictionary of processed Vivado commands. 

3843 

3844 def __init__(self) -> None: 

3845 """ 

3846 Initializes a Vivado log output processor. 

3847 """ 

3848 super().__init__() 

3849 

3850 self._duration = 0.0 

3851 self._processingDuration = 0.0 

3852 

3853 self._lines = [] 

3854 self._preamble = None 

3855 self._postamble = None 

3856 self._commands = {} 

3857 

3858 @readonly 

3859 def Lines(self) -> List[VivadoLine]: 

3860 """ 

3861 Read-only property to access the list of processed and classified log lines (messages). 

3862 

3863 :returns: A list of processed lines. 

3864 """ 

3865 return self._lines 

3866 

3867 @readonly 

3868 def Preamble(self) -> Nullable[Preamble]: 

3869 """ 

3870 Read-only property to access the parsed preamble information. 

3871 

3872 :returns: The log's output preamble. 

3873 """ 

3874 return self._preamble 

3875 

3876 @readonly 

3877 def Postamble(self) -> Nullable[Postamble]: 

3878 """ 

3879 Read-only property to access the parsed postamble information. 

3880 

3881 :returns: The log's output postamble. 

3882 """ 

3883 return self._postamble 

3884 

3885 @readonly 

3886 def Commands(self) -> Dict[Type[Command], Command]: 

3887 """ 

3888 Read-only property to access the dictionary of processed Vivado commands. 

3889 

3890 :returns: The dictionary of processed Vivado commands. 

3891 """ 

3892 return self._commands 

3893 

3894 @readonly 

3895 def StartDateTime(self) -> datetime: 

3896 if self._preamble is None: 3896 ↛ 3897line 3896 didn't jump to line 3897 because the condition on line 3896 was never true

3897 raise ValueError(f"Preamble was not found when parsing the Vivado log outputs.") 

3898 

3899 return self._preamble.StartDateTime 

3900 

3901 @readonly 

3902 def ExitDateTime(self) -> datetime: 

3903 if self._postamble is None: 3903 ↛ 3904line 3903 didn't jump to line 3904 because the condition on line 3903 was never true

3904 raise ValueError(f"Postamble was not found when parsing the Vivado log outputs.") 

3905 

3906 return self._postamble.ExitDateTime 

3907 

3908 @readonly 

3909 def Duration(self) -> float: 

3910 """ 

3911 Duration of the observed process (e.g. start to end of synthesis). 

3912 

3913 :returns: The observed process' execution duration in seconds. 

3914 """ 

3915 return (self.ExitDateTime - self.StartDateTime).total_seconds() 

3916 

3917 @readonly 

3918 def ProcessingDuration(self) -> float: 

3919 """ 

3920 Processing duration for the log output processor to parse all log messages. 

3921 

3922 :returns: The processing duration in seconds. 

3923 """ 

3924 return self._processingDuration 

3925 

3926 def __contains__(self, key: Type[Command]) -> bool: 

3927 """ 

3928 Returns True, if log outputs where found for the given command. 

3929 

3930 :param key: Vivado command (class). 

3931 :returns: True, if the Vivado command's outputs were found in log outputs. 

3932 """ 

3933 if not issubclass(key, Command): 3933 ↛ 3934line 3933 didn't jump to line 3934 because the condition on line 3933 was never true

3934 ex = TypeError(f"Parameter 'key' is not a Command.") 

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

3936 raise ex 

3937 

3938 return key in self._commands 

3939 

3940 def __getitem__(self, key: Type[Command]) -> Command: 

3941 """ 

3942 Access Vivado command specific log outputs and parsed data by the command. 

3943 

3944 :param key: Vivado command (class) to access. 

3945 :returns: A Vivado command instance with parsed log messages and extracted data. 

3946 """ 

3947 if not issubclass(key, Command): 3947 ↛ 3948line 3947 didn't jump to line 3948 because the condition on line 3947 was never true

3948 ex = TypeError(f"Parameter 'key' is not a Command.") 

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

3950 raise ex 

3951 

3952 try: 

3953 return self._commands[key] 

3954 except KeyError as ex: 

3955 raise CommandNotPresentException(F"Command '{key._TCL_COMMAND}' not present in '{self._logfile}'.") from ex 

3956 

3957 @readonly 

3958 def IsIncompleteLog(self) -> bool: 

3959 """ 

3960 Read-only property returning true if the processed Vivado log output is incomplete. 

3961 

3962 A log can be incomplete, because: 

3963 

3964 * Vivado disabled messages, because too many messages of the same kind appeared. Usually, a message type is disabled 

3965 after 100 messages of that type. This is indicated by message ``[Common 17-14]``. 

3966 

3967 :returns: True, if messages where silenced by Vivado. 

3968 

3969 .. note:: 

3970 

3971 .. code-block:: 

3972 

3973 INFO: [Common 17-14] Message 'Synth 8-3321' appears 100 times and further instances of the messages will be 

3974 disabled. Use the Tcl command set_msg_config to change the current settings. 

3975 """ 

3976 return 17 in self._messagesByID and 14 in self._messagesByID[17] 

3977 

3978 def CommandFinder(self, line: Nullable[VivadoLine] = None) -> Generator[VivadoLine, VivadoLine, None]: 

3979 tclProcedures = {"source"} 

3980 

3981 self._preamble = Preamble(self) 

3982 self._postamble = Postamble(self) 

3983 

3984 # wait for first line 

3985 if line is None: 

3986 line = yield 

3987 

3988 # process preamble 

3989 line = yield from self._preamble.Generator(line) 

3990 

3991 while True: 

3992 while True: 

3993 if line._kind is LineKind.Empty: 

3994 line = yield line 

3995 continue 

3996 elif isinstance(line, VivadoInfoMessage): 

3997 if line.ToolID == 17 and line.MessageKindID == 206: 

3998 lastLine = yield from self._postamble.Generator(line) 

3999 return lastLine 

4000 elif isinstance(line, VivadoTclCommand): 

4001 if line._tclCommand == Synth_Design._TCL_COMMAND: 

4002 self._commands[Synth_Design] = (cmd := Synth_Design(self)) 

4003 line = yield next(gen := cmd.SectionDetector(line)) 

4004 break 

4005 elif line._tclCommand == Link_Design._TCL_COMMAND: 

4006 self._commands[Link_Design] = (cmd := Link_Design(self)) 

4007 line = yield next(gen := cmd.SectionDetector(line)) 

4008 break 

4009 elif line._tclCommand == Opt_Design._TCL_COMMAND: 

4010 self._commands[Opt_Design] = (cmd := Opt_Design(self)) 

4011 line = yield next(gen := cmd.SectionDetector(line)) 

4012 break 

4013 elif line._tclCommand == Place_Design._TCL_COMMAND: 

4014 self._commands[Place_Design] = (cmd := Place_Design(self)) 

4015 line = yield next(gen := cmd.SectionDetector(line)) 

4016 break 

4017 elif line._tclCommand == PhyOpt_Design._TCL_COMMAND: 

4018 self._commands[PhyOpt_Design] = (cmd := PhyOpt_Design(self)) 

4019 line = yield next(gen := cmd.SectionDetector(line)) 

4020 break 

4021 elif line._tclCommand == Route_Design._TCL_COMMAND: 

4022 self._commands[Route_Design] = (cmd := Route_Design(self)) 

4023 line = yield next(gen := cmd.SectionDetector(line)) 

4024 break 

4025 elif line._tclCommand == Write_Bitstream._TCL_COMMAND: 

4026 self._commands[Write_Bitstream] = (cmd := Write_Bitstream(self)) 

4027 line = yield next(gen := cmd.SectionDetector(line)) 

4028 break 

4029 elif line._tclCommand == Open_Checkpoint._TCL_COMMAND: 

4030 self._commands[Open_Checkpoint] = (cmd := Open_Checkpoint(self)) 

4031 line = yield next(gen := cmd.SectionDetector(line)) 

4032 break 

4033 elif line._tclCommand == Report_DRC._TCL_COMMAND: 

4034 self._commands[Report_DRC] = (cmd := Report_DRC(self)) 

4035 line = yield next(gen := cmd.SectionDetector(line)) 

4036 break 

4037 elif line._tclCommand == Report_Methodology._TCL_COMMAND: 

4038 self._commands[Report_Methodology] = (cmd := Report_Methodology(self)) 

4039 line = yield next(gen := cmd.SectionDetector(line)) 

4040 break 

4041 elif line._tclCommand == Report_Power._TCL_COMMAND: 

4042 self._commands[Report_Power] = (cmd := Report_Power(self)) 

4043 line = yield next(gen := cmd.SectionDetector(line)) 

4044 break 

4045 elif isinstance(line, DateTimeLine): 

4046 if (match := Launch._LAUNCHED.match(line._message)) is not None: 4046 ↛ 4053line 4046 didn't jump to line 4053 because the condition on line 4046 was always true

4047 launchName = match["launchName"] 

4048 self._nestedLaunches.append(launch := Launch(launchName, parent=self)) 

4049 

4050 line = yield next(gen := launch.Parser(line)) 

4051 break 

4052 else: 

4053 pass 

4054 

4055 firstWord = line.Partition(" ")[0] 

4056 if firstWord in tclProcedures: 

4057 line = TclCommand.FromLine(line) 

4058 

4059 line = yield line 

4060 

4061 # end = f"{cmd._TCL_COMMAND} completed successfully" 

4062 

4063 while True: 

4064 # if line.StartsWith(end): 

4065 # # line._kind |= LineKind.Success 

4066 # lastLine = gen.send(line) 

4067 # if LineKind.Last in line._kind: 

4068 # line._kind ^= LineKind.Last 

4069 # line = yield lastLine 

4070 # break 

4071 

4072 try: 

4073 line = yield gen.send(line) 

4074 except StopIteration as ex: 

4075 line = ex.value 

4076 break 

4077 

4078 

4079@export 

4080class Processor(VivadoProcessor): 

4081 _nestedLaunches: List["Launch"] #: Nested Vivado launches (e.g. ``synth_1``/``impl_1``) 

4082 

4083 def __init__(self) -> None: 

4084 """ 

4085 Initializes a Vivado log output processor (toplevel processor). 

4086 """ 

4087 super().__init__() 

4088 

4089 self._nestedLaunches = [] 

4090 

4091 @readonly 

4092 def HasNestedLaunches(self) -> bool: 

4093 """ 

4094 Read-only property returnning true, if this processor encountered nested launches. 

4095 

4096 :returns: True, if nested launches were found. 

4097 

4098 .. seealso:: 

4099 

4100 :data:`NestedLaunches` 

4101 Access the list of nested launches. 

4102 """ 

4103 return len(self._nestedLaunches) > 0 

4104 

4105 @readonly 

4106 def NestedLaunches(self) -> List["Launch"]: 

4107 """ 

4108 Read-only property to access nested launches. 

4109 

4110 .. hint:: 

4111 

4112 A nested launch gets created when the outer Vivado instance starts another nested Vivado instance and waits on 

4113 its completion. Examples are typically ``synth_1`` and ``impl_1``. 

4114 

4115 :returns: The list of nested launches. 

4116 

4117 .. seealso:: 

4118 

4119 :data:`HasNestedLaunches` 

4120 Check if nested launches have been found. 

4121 """ 

4122 return self._nestedLaunches 

4123 

4124 def LineClassification(self, inputStream: Iterator[Tuple[datetime, str]]) -> Generator[VivadoLine, None, None]: 

4125 # Instantiate and initialize CommandFinder 

4126 next(cmdFinder := self.CommandFinder()) 

4127 

4128 lastLine = None 

4129 lineNumber = 0 

4130 _errorMessage = "Unknown processing error" 

4131 

4132 try: 

4133 while (tup := next(inputStream)) is not None: 4133 ↛ exitline 4133 didn't return from function 'LineClassification' because the condition on line 4133 was always true

4134 timestamp, rawMessageLine = tup 

4135 lineNumber += 1 

4136 rawMessageLine = rawMessageLine.rstrip() 

4137 errorMessage = _errorMessage 

4138 

4139 if len(rawMessageLine) == 0: 

4140 line = VivadoLine(lineNumber, LineKind.Empty, LineAction.Default, rawMessageLine, previousLine=lastLine) 

4141 elif rawMessageLine.startswith("INFO"): 

4142 if (line := VivadoInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 

4143 if (line := VivadoDRCInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 

4144 if (line := VivadoIrregularInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 

4145 line = VivadoStuntedInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine) 

4146 

4147 errorMessage = f"Line starting with 'INFO' was not a VivadoInfoMessage." 

4148 elif rawMessageLine.startswith("WARNING"): 

4149 if (line := VivadoWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 

4150 if (line := VivadoDRCWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 4150 ↛ 4154line 4150 didn't jump to line 4154 because the condition on line 4150 was always true

4151 if (line := VivadoXPMWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 4151 ↛ 4152line 4151 didn't jump to line 4152 because the condition on line 4151 was never true

4152 line = VivadoStuntedWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine) 

4153 

4154 errorMessage = f"Line starting with 'WARNING' was not a VivadoWarningMessage." 

4155 elif rawMessageLine.startswith("CRITICAL WARNING"): 

4156 line = VivadoCriticalWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine) 

4157 

4158 errorMessage = f"Line starting with 'CRITICAL WARNING' was not a VivadoCriticalWarningMessage." 

4159 elif rawMessageLine.startswith("ERROR"): 

4160 line = VivadoErrorMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine) 

4161 

4162 errorMessage = f"Line starting with 'ERROR' was not a VivadoErrorMessage." 

4163 elif rawMessageLine.startswith("Command: "): 

4164 line = VivadoTclCommand.Parse(lineNumber, rawMessageLine, previousLine=lastLine) 

4165 

4166 errorMessage = "Line starting with 'Command:' was not a VivadoTclCommand." 

4167 elif (match := DateTimeLine._PREFIX.match(rawMessageLine)) is not None: 

4168 dateTime = datetime.strptime(match["datetime"], "%a %b %d %H:%M:%S %Y") 

4169 line = DateTimeLine(lineNumber, LineKind.DateTimeLine, LineAction.Default, dateTime, match["message"], previousLine=lastLine) 

4170 else: 

4171 line = VivadoLine(lineNumber, LineKind.Unprocessed, LineAction.Default, rawMessageLine, previousLine=lastLine) 

4172 

4173 if line.StartsWith("Resolution:") and isinstance(lastLine, VivadoMessage): 

4174 line._kind = LineKind.Verbose 

4175 

4176 if line is None: 4176 ↛ 4178line 4176 didn't jump to line 4178 because the condition on line 4176 was never true

4177 # TODO: what to do with this line? attache to exception? 

4178 line = VivadoLine(lineNumber, LineKind.ProcessorError, LineAction.Default, rawMessageLine, previousLine=lastLine) 

4179 

4180 raise ClassificationException(errorMessage, lineNumber, rawMessageLine) 

4181 

4182 if isinstance(line, VivadoMessage): 

4183 self._AddMessage(line) 

4184 

4185 line = cmdFinder.send(line) 

4186 

4187 if line._kind is LineKind.ProcessorError: 4187 ↛ 4188line 4187 didn't jump to line 4188 because the condition on line 4187 was never true

4188 line = ClassificationException(errorMessage, lineNumber, rawMessageLine) 

4189 

4190 # TODO: find a better solution/location to assign the timestamp. 

4191 line._timestamp = timestamp 

4192 

4193 self._lines.append(line) 

4194 

4195 lastLine = line 

4196 yield line 

4197 

4198 except StopIteration: 

4199 pass 

4200 

4201 

4202@export 

4203class Document(Processor): 

4204 """ 

4205 A Vivado log output processor for a log file. 

4206 

4207 This processor represents a Vivado log file (e.g. ``*.vds`` or ``*.vdi``). It processees its content line-by-line 

4208 while classifying each line as a message. The processing duration is available via :data:`ProcessingDuration`. 

4209 """ 

4210 _logfile: Path #: Path to the processed logfile. 

4211 

4212 # FIXME: parse=True parameter 

4213 def __init__(self, logfile: Path) -> None: 

4214 """ 

4215 Initializes a log file. 

4216 

4217 :param logfile: Path to the log file. 

4218 """ 

4219 super().__init__() 

4220 

4221 # FIXME: check if path 

4222 self._logfile = logfile 

4223 

4224 @readonly 

4225 def Logfile(self) -> Path: 

4226 """ 

4227 Read-only property to access the document's path. 

4228 

4229 :returns: Path to the log file. 

4230 """ 

4231 return self._logfile 

4232 

4233 def Parse(self) -> None: 

4234 with Stopwatch() as sw: 

4235 timestamp = datetime.fromtimestamp(self._logfile.stat().st_mtime) 

4236 with self._logfile.open("r", encoding="utf-8") as file: 

4237 for line in self.LineClassification(timestampIterator(file, timestamp)): 

4238 pass 

4239 

4240 self._processingDuration = sw.Duration 

4241 

4242 

4243@export 

4244class Launch(Parser, VivadoProcessor): 

4245 _LAUNCHED: ClassVar[Pattern] = re_compile(r"^Launched (?P<launchName>.+?)\.\.\.") 

4246 _LOGFILE: ClassVar[Pattern] = re_compile(r"^Run output will be captured here: (?P<logfile>.+)") 

4247 _WAITING: ClassVar[Pattern] = re_compile(r"^Waiting for (?P<launchName>.+?) to finish(?: \(timeout in (?P<timeout>\d+) minutes\))?\.\.\.") 

4248 _RUNNING: ClassVar[Pattern] = re_compile(r"^\*\*\*+\s+Running vivado") 

4249 _WITH_ARGS: ClassVar[Pattern] = re_compile(r"^\s+with args (?P<arguments>.+)") 

4250 _FINISHED: ClassVar[Pattern] = re_compile(r"^(?P<launchName>.+?) finished") 

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

4252 

4253 _name: str #: Name of the launch. 

4254 _logfile: Nullable[Path] #: Logfile used by nested Vivado instance. 

4255 _vivadoArguments: Nullable[List[str]] #: Launch parameters passed to nested Vivado instance. 

4256 _timeout: int #: Timeout in minutes. 

4257 _startDateTime: datetime #: Date and time when the nested Vivado instance was started. 

4258 _finishDateTime: datetime #: Date and time when the nested Vivado instance finished. 

4259 

4260 def __init__(self, name: str, parent: VivadoProcessor) -> None: 

4261 super().__init__(parent) 

4262 VivadoProcessor.__init__(self) 

4263 

4264 self._name = name 

4265 self._logfile = None 

4266 self._vivadoArguments = None 

4267 self._timeout = 0 

4268 self._startDateTime = None 

4269 self._finishDateTime = None 

4270 

4271 @readonly 

4272 def Name(self) -> str: 

4273 return self._name 

4274 

4275 @readonly 

4276 def Logfile(self) -> Path: 

4277 return self._logfile 

4278 

4279 @readonly 

4280 def VivadoArguments(self) -> List[str]: 

4281 return self._vivadoArguments 

4282 

4283 @readonly 

4284 def Timeout(self) -> int: 

4285 return self._timeout 

4286 

4287 @readonly 

4288 def LaunchDateTime(self) -> datetime: 

4289 return self._startDateTime 

4290 

4291 @readonly 

4292 def FinishDateTime(self) -> datetime: 

4293 return self._finishDateTime 

4294 

4295 @readonly 

4296 def Duration(self) -> float: 

4297 """ 

4298 Duration of the observed nested Vivado instance. 

4299 

4300 :returns: The observed nested Vivado instance's execution duration in seconds. 

4301 """ 

4302 return (self.FinishDateTime - self.LaunchDateTime).total_seconds() 

4303 

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

4305 if isinstance(line, DateTimeLine): 4305 ↛ 4309line 4305 didn't jump to line 4309 because the condition on line 4305 was always true

4306 line._kind = LineKind.LaunchStart 

4307 self._startDateTime = line._dateTime 

4308 else: 

4309 raise TypeError(f"Expected type DateTimeLine for first line.") 

4310 

4311 if self._LAUNCHED.match(line._message) is None: 4311 ↛ 4312line 4311 didn't jump to line 4312 because the condition on line 4311 was never true

4312 raise ValueError(f"Expected a 'Launched xxx...' line, but git '{line._message}'.") 

4313 

4314 while True: 

4315 line = yield line 

4316 

4317 if isinstance(line, DateTimeLine): 

4318 if (match := self._WAITING.match(line._message)) is not None: 4318 ↛ 4328line 4318 didn't jump to line 4328 because the condition on line 4318 was always true

4319 line._kind = LineKind.LaunchStart 

4320 if (launchName := match["launchName"]) != self._name: 4320 ↛ 4321line 4320 didn't jump to line 4321 because the condition on line 4320 was never true

4321 WarningCollector.Raise(ProcessorCriticalWarning(f"Detected launch name '{launchName}' doesn't match current's launch's name '{self._name}'.")) 

4322 return line 

4323 

4324 if (timeout := match["timeout"]) is not None: 

4325 self._timeout = int(timeout) 

4326 

4327 else: 

4328 pass 

4329 elif (match := self._LOGFILE.match(line._message)) is not None: 

4330 line._kind = LineKind.Normal 

4331 self._logfile = Path(match["logfile"]) 

4332 elif (match := self._RUNNING.match(line._message)) is not None: 

4333 line._kind = LineKind.Normal 

4334 elif (match := self._WITH_ARGS.match(line._message)) is not None: 

4335 line._kind = LineKind.LaunchArguments 

4336 self._vivadoArguments = match["arguments"].split(" ") 

4337 break 

4338 

4339 # Consume 3 empty lines 

4340 line = yield line 

4341 line = yield line 

4342 line = yield line 

4343 

4344 try: 

4345 processedLine = next(gen := self.CommandFinder(line)) # create + prime 

4346 while True: 

4347 line = yield processedLine 

4348 if isinstance(line, VivadoMessage): 

4349 self._AddMessage(line) 

4350 

4351 processedLine = gen.send(line) 

4352 except StopIteration as ex: 

4353 line = ex.value 

4354 

4355 # Check for 'finished' line 

4356 if isinstance(line, DateTimeLine): 4356 ↛ 4365line 4356 didn't jump to line 4365 because the condition on line 4356 was always true

4357 if (match := self._FINISHED.match(line._message)) is not None: 4357 ↛ 4363line 4357 didn't jump to line 4363 because the condition on line 4357 was always true

4358 line._kind = LineKind.LaunchFinished 

4359 self._finishDateTime = line._dateTime 

4360 

4361 # TODO: check launchName 

4362 else: 

4363 pass # FIXME: raise error 

4364 else: 

4365 pass # FIXME: raise error 

4366 

4367 line = yield line 

4368 

4369 # Check for 'wait_on_runs' line 

4370 if line.StartsWith(self._TIME): 4370 ↛ 4373line 4370 didn't jump to line 4373 because the condition on line 4370 was always true

4371 line._kind = LineKind.LaunchTime 

4372 

4373 line = yield line 

4374 

4375 if isinstance(line, VivadoMessage): 

4376 if not (line._toolID == 17 and line._messageKindID == 206): # Exiting Vivado 

4377 self._AddMessage(line) 

4378 

4379 line = yield line 

4380 

4381 return line 

4382 

4383 def __str__(self) -> str: 

4384 return f"Launch: {self._name} (timeout: {self._timeout} minutes)"