Coverage for pyEDAA/OutputFilter/Xilinx/__init__.py: 80%
2160 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-26 23:00 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-26 23:00 +0000
1# ==================================================================================================================== #
2# _____ ____ _ _ ___ _ _ _____ _ _ _ #
3# _ __ _ _| ____| _ \ / \ / \ / _ \ _ _| |_ _ __ _ _| |_| ___(_) | |_ ___ _ __ #
4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | | __| '_ \| | | | __| |_ | | | __/ _ \ '__| #
5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| | |_| | |_| |_) | |_| | |_| _| | | | || __/ | #
6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/ \__,_|\__| .__/ \__,_|\__|_| |_|_|\__\___|_| #
7# |_| |___/ |_| #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2025-2026 Electronic Design Automation Abstraction (EDA²) #
15# #
16# Licensed under the Apache License, Version 2.0 (the "License"); #
17# you may not use this file except in compliance with the License. #
18# You may obtain a copy of the License at #
19# #
20# http://www.apache.org/licenses/LICENSE-2.0 #
21# #
22# Unless required by applicable law or agreed to in writing, software #
23# distributed under the License is distributed on an "AS IS" BASIS, #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25# See the License for the specific language governing permissions and #
26# limitations under the License. #
27# #
28# SPDX-License-Identifier: Apache-2.0 #
29# ==================================================================================================================== #
30#
31"""Basic classes for outputs from AMD/Xilinx Vivado."""
32from datetime import datetime
33from enum import Flag
34from pathlib import Path
35from re import Pattern, compile as re_compile
36from typing import Optional as Nullable, Self, Type, ClassVar, Tuple, List, Dict, Generator, Union, Any, Iterator
38from pyTooling.Decorators import export, readonly
39from pyTooling.MetaClasses import ExtendedType, abstractmethod
40from pyTooling.Common import getFullyQualifiedName
41from pyTooling.Stopwatch import Stopwatch
42from pyTooling.Versioning import YearReleaseVersion
43from pyTooling.Warning import WarningCollector, CriticalWarning
45from pyEDAA.OutputFilter import Line, OutputFilterException
46from pyEDAA.OutputFilter import InfoMessage, WarningMessage, CriticalWarningMessage, ErrorMessage
49__all__ = ["MAJOR", "MAJOR_MINOR", "MAJOR_MINOR_MICRO", "MAJOR_MINOR_MICRO_NANO"]
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+)"
57@export
58def timestampIterator(iterator: Iterator[str], timestamp: datetime) -> Iterator[Tuple[datetime, str]]:
59 for line in iterator:
60 yield timestamp, line
63@export
64class ProcessorException(OutputFilterException):
65 """
66 Base-class for exceptions raised by processors parsing log outputs.
67 """
70@export
71class ClassificationException(ProcessorException):
72 """
73 Raised if a log output line couldn't be classified.
74 """
75 _lineNumber: int #: Line number of the unclassified line.
76 _rawMessage: str #: Raw message of the unclassified line.
78 def __init__(self, errorMessage: str, lineNumber: int, rawMessageLine: str) -> None:
79 """
80 Initializes a classification exception.
82 :param errorMessage: Error message why the line couldn't be classified.
83 :param lineNumber: Line number of the unclassified line.
84 :param rawMessageLine: Raw message of the unclassified line.
85 """
86 super().__init__(errorMessage)
88 self._lineNumber = lineNumber
89 self._rawMessage = rawMessageLine
91 def __str__(self) -> str:
92 return f"{self.message}: {self._rawMessage} (at line {self._lineNumber})"
95@export
96class ParserStateException(ProcessorException):
97 """
98 Raised if a log output parser has a broken state.
99 """
102@export
103class NotPresentException(ProcessorException):
104 pass
107@export
108class CommandNotPresentException(NotPresentException):
109 pass
112@export
113class SectionNotPresentException(NotPresentException):
114 pass
117@export
118class SubSectionNotPresentException(NotPresentException):
119 pass
122@export
123class SubTaskNotPresentException(NotPresentException):
124 pass
127@export
128class PhaseNotPresentException(NotPresentException):
129 pass
132@export
133class NestedTaskNotPresentException(NotPresentException):
134 pass
137@export
138class UndetectedEnd(CriticalWarning):
139 _line: "VivadoLine"
141 def __init__(self, message: str, line: "VivadoLine") -> None:
142 super().__init__(message)
144 self._line = line
146 @readonly
147 def Line(self) -> "VivadoLine":
148 return self._line
151@export
152class UnknownLine(Warning):
153 _line: "VivadoLine"
155 def __init__(self, message: str, line: "VivadoLine") -> None:
156 super().__init__(message)
158 self._line = line
160 @readonly
161 def Line(self) -> "VivadoLine":
162 return self._line
165@export
166class UnknownTask(UnknownLine):
167 pass
170@export
171class UnknownSubTask(UnknownLine):
172 pass
175@export
176class UnknownSection(UnknownLine):
177 pass
180@export
181class UnknownPhase(UnknownLine):
182 pass
185@export
186class UnknownSubPhase(UnknownLine):
187 pass
190@export
191class LineKind(Flag):
192 """
193 Classification of a log message line.
194 """
195 Unprocessed = 0
196 ProcessorError = 2** 0
197 Empty = 2** 1
198 Delimiter = 2** 2
200 Success = 2** 3
201 Failed = 2** 4
203 Verbose = 2**10
204 Normal = 2**11
205 Info = 2**12
206 Warning = 2**13
207 CriticalWarning = 2**14
208 Error = 2**15
209 Fatal = 2**16
211 Start = 2**20
212 End = 2**21
213 Header = 2**22
214 Content = 2**23
215 Time = 2**24
216 Footer = 2**25
218 Last = 2**29
220 Message = 2**30
221 InfoMessage = Message | Info
222 WarningMessage = Message | Warning
223 CriticalWarningMessage = Message | CriticalWarning
224 ErrorMessage = Message | Error
226 Task = 2**31
227 TaskStart = Task | Start
228 TaskEnd = Task | End
229 TaskTime = Task | Time
231 Phase = 2**32
232 PhaseDelimiter = Phase | Delimiter
233 PhaseStart = Phase | Start
234 PhaseEnd = Phase | End
235 PhaseTime = Phase | Time
236 PhaseFinal = Phase | Footer
238 SubPhase = 2**33
239 SubPhaseStart = SubPhase | Start
240 SubPhaseEnd = SubPhase | End
241 SubPhaseTime = SubPhase | Time
243 SubSubPhase = 2**34
244 SubSubPhaseStart = SubSubPhase | Start
245 SubSubPhaseEnd = SubSubPhase | End
246 SubSubPhaseTime = SubSubPhase | Time
248 SubSubSubPhase = 2**35
249 SubSubSubPhaseStart = SubSubSubPhase | Start
250 SubSubSubPhaseEnd = SubSubSubPhase | End
251 SubSubSubPhaseTime = SubSubSubPhase | Time
253 NestedTask = 2**36
254 NestedTaskStart = NestedTask | Start
255 NestedTaskEnd = NestedTask | End
257 NestedPhase = 2**37
258 NestedPhaseStart = NestedPhase | Start
259 NestedPhaseEnd = NestedPhase | End
261 Section = 2**38
262 SectionDelimiter = Section | Delimiter
263 SectionStart = Section | Start
264 SectionEnd = Section | End
266 SubSection = 2**39
267 SubSectionDelimiter = SubSection | Delimiter
268 SubSectionStart = SubSection | Start
269 SubSectionEnd = SubSection | End
271 Paragraph = 2**40
272 ParagraphHeadline = Paragraph | Header
274 Hierarchy = 2**41
275 HierarchyStart = Hierarchy | Start
276 HierarchyEnd = Hierarchy | End
278 XDC = 2**42
279 XDCStart = XDC | Start
280 XDCEnd = XDC | End
282 Table = 2**43
283 TableFrame = Table | Delimiter
284 TableHeader = Table | Header
285 TableRow = Table | Content
286 TableFooter = Table | Footer
288 TclCommand = 2**44
289 GenericTclCommand = TclCommand | 2**0
290 VivadoTclCommand = TclCommand | 2**1
293@export
294class LineAction(Flag):
295 Default = 0
296 Remove = 1
299@export
300class VivadoLine(Line[LineKind, LineAction]):
301 """
302 This class represents any line in a log file.
304 A line has a line number (:attr:`_lineNumber`), a message (:attr:`__message`) and a message kind (:attr:`__kind`). In
305 addition, all line objects in a log file form a doubly
306 linked list.
307 """
308 _processor: "Processor"
309 _command: "Nullable[Command]"
311 def __init__(
312 self,
313 lineNumber: int,
314 kind: LineKind,
315 action: LineAction,
316 message: str,
317 previousLine: Nullable["VivadoLine"] = None
318 ) -> None:
319 super().__init__(lineNumber, kind, action, message, previousLine)
321 self._processor = None
322 self._command = None
324 @readonly
325 def Processor(self) -> "Processor":
326 return self._processor
328 @readonly
329 def Command(self) -> "Nullable[Command]":
330 return self._command
332 @classmethod
333 def Copy(cls, line: "VivadoLine", previousLine: "VivadoLine") -> "VivadoLine":
334 newLine = cls(line._lineNumber, line._kind, line._action, line._message, previousLine)
335 newLine._timestamp = line._timestamp
336 return newLine
339@export
340class VivadoMessage(VivadoLine):
341 """
342 This class represents an AMD/Xilinx Vivado message.
344 The usual message format is:
346 .. code-block:: text
348 INFO: [Synth 8-7079] Multithreading enabled for synth_design using a maximum of 2 processes.
349 WARNING: [Synth 8-3332] Sequential element (gen[0].Sync/FF2) is unused and will be removed from module sync_Bits_Xilinx.
351 The following message severities are defined:
353 * ``INFO``
354 * ``WARNING``
355 * ``CRITICAL WARNING``
356 * ``ERROR``
358 .. seealso::
360 :class:`VivadoInfoMessage`
361 Representing a Vivado info message.
363 :class:`VivadoWarningMessage`
364 Representing a Vivado warning message.
366 :class:`VivadoCriticalWarningMessage`
367 Representing a Vivado critical warning message.
369 :class:`VivadoErrorMessage`
370 Representing a Vivado error message.
371 """
372 # _MESSAGE_KIND: ClassVar[str]
373 # _REGEXP: ClassVar[Pattern]
375 _toolName: Nullable[str]
376 _toolID: Nullable[int]
377 _messageKindID: Nullable[int]
379 def __init__(
380 self,
381 lineNumber: int,
382 kind: LineKind,
383 action: LineAction,
384 message: str,
385 toolName: Nullable[str] = None,
386 toolID: Nullable[int] = None,
387 messageKindID: Nullable[int] = None,
388 previousLine: Nullable[VivadoLine] = None
389 ) -> None:
390 super().__init__(lineNumber, kind, action, message, previousLine)
391 self._toolName = toolName
392 self._toolID = toolID
393 self._messageKindID = messageKindID
395 @readonly
396 def ToolName(self) -> Nullable[str]:
397 return self._toolName
399 @readonly
400 def ToolID(self) -> Nullable[int]:
401 return self._toolID
403 @readonly
404 def MessageKindID(self) -> Nullable[int]:
405 return self._messageKindID
407 @classmethod
408 def Parse(cls, lineNumber: int, kind: LineKind, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
409 if (match := cls._REGEXP.match(rawMessage)) is not None:
410 return cls(lineNumber, kind, LineAction.Default, match[4], match[1], int(match[2]), int(match[3]), previousLine)
412 return None
414 @classmethod
415 def Copy(cls, line: "VivadoMessage", previousLine: "VivadoLine") -> "VivadoMessage":
416 newLine = cls(line._lineNumber, line._kind, line._action, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
417 newLine._timestamp = line._timestamp
418 return newLine
420 def __str__(self) -> str:
421 return f"{self._MESSAGE_KIND}: [{self._toolName} {self._toolID}-{self._messageKindID}] {self._message}"
424@export
425class VivadoInfoMessage(VivadoMessage, InfoMessage):
426 """
427 This class represents an AMD/Xilinx Vivado info message.
429 .. rubric:: Example
431 .. code-block::
433 INFO: [Common 17-83] 66-Releasing license: Synthesis
434 """
436 _MESSAGE_KIND: ClassVar[str] = "INFO"
437 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+) (\d+)-(\d+)\] (.*)""")
439 @classmethod
440 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
441 return super().Parse(lineNumber, LineKind.InfoMessage, rawMessage, previousLine)
443 @classmethod
444 def FromMessage(cls, line: VivadoMessage) -> Self:
445 message = cls(
446 line._lineNumber,
447 LineKind.InfoMessage,
448 line._message,
449 line._toolName,
450 line._toolID,
451 line._messageKindID,
452 previousLine=line._previousLine)
453 message._nextLine = line._nextLine
454 line._nextLine._previousLine = message
456 return message
459@export
460class VivadoDRCInfoMessage(VivadoMessage, InfoMessage):
461 """
462 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) info message.
464 .. rubric:: Example
466 .. code-block::
468 INFO: [DRC AVAL-4] enum_USE_DPORT_FALSE_enum_DREG_ADREG_0_connects_CED_CEAD_RSTD_GND: i_system/xbip_dsp48_macro_0/U0/i_synth/i_synth_option.i_synth_model/opt_7series.i_uniwrap/i_primitive: DSP48E1 is not using the D port (USE_DPORT = FALSE). For improved power characteristics, set DREG and ADREG to '1', tie CED, CEAD, and RSTD to logic '0'.
469 """
471 _MESSAGE_KIND: ClassVar[str] = "INFO"
472 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[DRC (\w+)-(\d+)\] (.*)""")
474 _drcRuleName: str
476 def __init__(
477 self,
478 lineNumber: int,
479 kind: LineKind,
480 action: LineAction,
481 drcRuleName: str,
482 message: str,
483 toolName: Nullable[str] = None,
484 toolID: Nullable[int] = None,
485 messageKindID: Nullable[int] = None,
486 previousLine: Nullable[VivadoLine] = None
487 ) -> None:
488 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine)
490 self._drcRuleName = drcRuleName
492 @readonly
493 def DRCRuleName(self) -> str:
494 return self._drcRuleName
496 @classmethod
497 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
498 if (match := cls._REGEXP.match(rawMessage)) is not None:
499 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[3], toolName="DRC", toolID=None,
500 messageKindID=int(match[2]), previousLine=previousLine)
502 return None
504 @classmethod
505 def Copy(cls, line: "VivadoDRCInfoMessage", previousLine: "VivadoLine") -> "VivadoDRCInfoMessage":
506 newLine = cls(line._lineNumber, line._kind, line._action, line._drcRuleName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
507 newLine._timestamp = line._timestamp
508 return newLine
510 def __str__(self) -> str:
511 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
514@export
515class VivadoIrregularInfoMessage(VivadoMessage, InfoMessage):
516 """
517 This class represents an irregular AMD/Xilinx Vivado info message.
519 .. rubric:: Example
521 .. code-block::
523 INFO: [runtcl-4] Executing : report_io -file system_top_io_placed.rpt
524 """
526 _MESSAGE_KIND: ClassVar[str] = "INFO"
527 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+)-(\d+)\] (.*)""")
529 @classmethod
530 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
531 if (match := cls._REGEXP.match(rawMessage)) is not None:
532 return cls(lineNumber, LineKind.InfoMessage, LineAction.Default, match[3], toolName=match[1], messageKindID=int(match[2]), previousLine=previousLine)
534 return None
536 def __str__(self) -> str:
537 return f"{self._MESSAGE_KIND}: [{self._toolName}-{self._messageKindID}] {self._message}"
540@export
541class VivadoStuntedInfoMessage(VivadoMessage, InfoMessage):
542 """
543 This class represents a stunted AMD/Xilinx Vivado info message.
545 .. rubric:: Example
547 .. code-block::
549 INFO: Helper process launched with PID 29056
550 """
552 _MESSAGE_KIND: ClassVar[str] = "INFO"
553 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: ([^\[].*)""")
555 @classmethod
556 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
557 if (match := cls._REGEXP.match(rawMessage)) is not None: 557 ↛ 560line 557 didn't jump to line 560 because the condition on line 557 was always true
558 return cls(lineNumber, LineKind.InfoMessage, LineAction.Default, match[1], previousLine=previousLine)
560 return None
562 def __str__(self) -> str:
563 return f"{self._MESSAGE_KIND}: {self._message}"
566@export
567class VivadoWarningMessage(VivadoMessage, WarningMessage):
568 """
569 This class represents an AMD/Xilinx Vivado warning message.
571 .. rubric:: Example
573 .. code-block::
575 WARNING: [Synth 8-7080] Parallel synthesis criteria is not met
576 """
578 _MESSAGE_KIND: ClassVar[str] = "WARNING"
579 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(\w+(?: \w+)*?) (\d+)-(\d+)\] (.*)""")
581 @classmethod
582 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
583 return super().Parse(lineNumber, LineKind.WarningMessage, rawMessage, previousLine=previousLine)
585 @classmethod
586 def FromMessage(cls, line: VivadoMessage) -> Self:
587 message = cls(
588 line._lineNumber,
589 LineKind.WarningMessage,
590 line._message,
591 line._toolName,
592 line._toolID,
593 line._messageKindID,
594 previousLine=line._previousLine)
595 message._nextLine = line._nextLine
596 line._nextLine._previousLine = message
598 return message
601@export
602class VivadoDRCWarningMessage(VivadoMessage, WarningMessage):
603 """
604 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) warning message.
606 .. rubric:: Example
608 .. code-block::
610 WARNING: [DRC PDCN-1569] LUT equation term check: Used physical LUT pin 'A1' of cell ps/path/to/cell (pin ps/path/to/cell/I0) is not included in the LUT equation: 'O6=(A6+~A6)*((A3*A2)+(A3*(~A2)*A5)+((~A3)*A4*A5)+((~A3)*(~A4)*A2)+((~A3)*(~A4)*(~A2)*A5))'. If this cell is a user instantiated LUT in the design, please remove connectivity to the pin or change the equation and/or INIT string of the LUT to prevent this issue. If the cell is inferred or IP created LUT, please regenerate the IP and/or resynthesize the design to attempt to correct the issue.
611 """
613 _MESSAGE_KIND: ClassVar[str] = "WARNING"
614 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[DRC (\w+)-(\d+)\] (.*)""")
616 _drcRuleName: str
618 def __init__(
619 self,
620 lineNumber: int,
621 kind: LineKind,
622 action: LineAction,
623 drcRuleName: str,
624 message: str,
625 toolName: Nullable[str] = None,
626 toolID: Nullable[int] = None,
627 messageKindID: Nullable[int] = None,
628 previousLine: Nullable[VivadoLine] = None
629 ) -> None:
630 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine)
632 self._drcRuleName = drcRuleName
634 @readonly
635 def DRCRuleName(self) -> str:
636 return self._drcRuleName
638 @classmethod
639 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
640 if (match := cls._REGEXP.match(rawMessage)) is not None: 640 ↛ 641line 640 didn't jump to line 641 because the condition on line 640 was never true
641 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[3], toolName="DRC", toolID=None, messageKindID=int(match[2]), previousLine=previousLine)
643 return None
645 @classmethod
646 def Copy(cls, line: "VivadoDRCWarningMessage", previousLine: "VivadoLine") -> "VivadoDRCWarningMessage":
647 newLine = cls(line._lineNumber, line._kind, line._action, line._drcRuleName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
648 newLine._timestamp = line._timestamp
649 return newLine
651 def __str__(self) -> str:
652 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
655@export
656class VivadoXPMWarningMessage(VivadoMessage, WarningMessage):
657 """
658 This class represents an AMD/Xilinx Vivado XPM warning message.
660 .. rubric:: Example
662 .. code-block::
664 WARNING: [XPM_CDC_GRAY: TCL-1000] The source and destination clocks are the same.
665 """
667 _MESSAGE_KIND: ClassVar[str] = "WARNING"
668 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(XPM_\w+): (\w+)-(\d+)\] (.*)""")
670 _xpmName: str
672 def __init__(
673 self,
674 lineNumber: int,
675 kind: LineKind,
676 action: LineAction,
677 xpmName: str,
678 message: str,
679 toolName: Nullable[str] = None,
680 toolID: Nullable[int] = None,
681 messageKindID: Nullable[int] = None,
682 previousLine: Nullable[VivadoLine] = None
683 ) -> None:
684 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine)
686 self._xpmName = xpmName
688 @readonly
689 def XPMName(self) -> str:
690 return self._xpmName
692 @classmethod
693 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
694 if (match := cls._REGEXP.match(rawMessage)) is not None: 694 ↛ 697line 694 didn't jump to line 697 because the condition on line 694 was always true
695 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[4], toolName=match[2], toolID=None, messageKindID=int(match[3]), previousLine=previousLine)
697 return None
699 @classmethod
700 def Copy(cls, line: "VivadoXPMWarningMessage", previousLine: "VivadoLine") -> "VivadoXPMWarningMessage":
701 newLine = cls(line._lineNumber, line._kind, line._action, line._xpmName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
702 newLine._timestamp = line._timestamp
703 return newLine
705 def __str__(self) -> str:
706 return f"{self._MESSAGE_KIND}: [{self._xpmName}: {self._toolName}-{self._messageKindID}] {self._message}"
709@export
710class VivadoStuntedWarningMessage(VivadoMessage, WarningMessage):
711 """
712 This class represents a stunted AMD/Xilinx Vivado warning message.
714 .. rubric:: Example
716 .. code-block::
718 WARNING: set_property ASYNC_REG could not find object (constraint file /path/to/sync_Bits_Xilinx.xdc, line 5).
719 """
721 _MESSAGE_KIND: ClassVar[str] = "WARNING"
722 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: ([^\[].*)""")
724 @classmethod
725 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
726 if (match := cls._REGEXP.match(rawMessage)) is not None: 726 ↛ 729line 726 didn't jump to line 729 because the condition on line 726 was always true
727 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], previousLine=previousLine)
729 return None
731 def __str__(self) -> str:
732 return f"{self._MESSAGE_KIND}: {self._message}"
735@export
736class VivadoCriticalWarningMessage(VivadoMessage, CriticalWarningMessage):
737 """
738 This class represents an AMD/Xilinx Vivado critical warning message.
740 .. rubric:: Example
742 .. code-block::
744 CRITICAL WARNING: [Constraints 18-1056] Clock 'RefClkA_SFP_Quad' completely overrides clock 'USRCLKA_SFP[P]'.
745 """
747 _MESSAGE_KIND: ClassVar[str] = "CRITICAL WARNING"
748 _REGEXP: ClassVar[Pattern] = re_compile(r"""CRITICAL WARNING: \[(\w+) (\d+)-(\d+)\] (.*)""")
750 @classmethod
751 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
752 return super().Parse(lineNumber, LineKind.CriticalWarningMessage, rawMessage, previousLine)
754 @classmethod
755 def FromMessage(cls, line: VivadoMessage) -> Self:
756 message = cls(
757 line._lineNumber,
758 LineKind.CriticalWarningMessage,
759 line._message,
760 line._toolName,
761 line._toolID,
762 line._messageKindID,
763 previousLine=line._previousLine)
764 message._nextLine = line._nextLine
765 line._nextLine._previousLine = message
767 return message
770@export
771class VivadoErrorMessage(VivadoMessage, ErrorMessage):
772 """
773 This class represents an AMD/Xilinx Vivado error message.
775 .. rubric:: Example
777 .. code-block::
779 ERROR: [Memdata 28-96] Could not find a BMM_INFO_DESIGN property in the design. Could not generate the merged BMM file: C:/Users/username/git/design.runs/impl_1/system_top_bd.bmm
780 """
782 _MESSAGE_KIND: ClassVar[str] = "ERROR"
783 _REGEXP: ClassVar[Pattern] = re_compile(r"""ERROR: \[(\w+) (\d+)-(\d+)\] (.*)""")
785 @classmethod
786 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
787 return super().Parse(lineNumber, LineKind.ErrorMessage, rawMessage, previousLine)
789 @classmethod
790 def FromMessage(cls, line: VivadoMessage) -> Self:
791 message = cls(
792 line._lineNumber,
793 LineKind.ErrorMessage,
794 line._message,
795 line._toolName,
796 line._toolID,
797 line._messageKindID,
798 previousLine=line._previousLine)
799 message._nextLine = line._nextLine
800 line._nextLine._previousLine = message
802 return message
805@export
806class VHDLReportMessage(VivadoInfoMessage):
807 _REGEXP2: ClassVar[Pattern ] = re_compile(r"""RTL report: "(.*)" \[(.*):(\d+)\]""") # todo: workaround for ClassVar problem
809 _reportMessage: str
810 _sourceFile: Path
811 _sourceLineNumber: int
813 def __init__(
814 self,
815 lineNumber: int,
816 rawMessage: str,
817 toolName: str,
818 toolID: int,
819 messageKindID: int,
820 reportMessage: str,
821 sourceFile: Path,
822 sourceLineNumber: int,
823 previousLine: Nullable[VivadoLine] = None
824 ) -> None:
825 super().__init__(lineNumber, LineKind.InfoMessage, LineAction.Default, rawMessage, toolName, toolID, messageKindID, previousLine)
827 self._reportMessage = reportMessage
828 self._sourceFile = sourceFile
829 self._sourceLineNumber = sourceLineNumber
831 @classmethod
832 def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]:
833 if (match := cls._REGEXP2.match(line._message)) is not None: 833 ↛ 836line 833 didn't jump to line 836 because the condition on line 833 was always true
834 return cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, match[1], Path(match[2]), int(match[3]), previousLine=line._previousLine)
836 return None
838 @classmethod
839 def Copy(cls, line: "VHDLReportMessage", previousLine: "VivadoLine") -> "VHDLReportMessage":
840 newLine = cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, line._reportMessage, line._sourceFile, line._sourceLineNumber, previousLine)
841 newLine._timestamp = line._timestamp
842 return newLine
844@export
845class VHDLAssertionMessage(VHDLReportMessage):
846 _REGEXP3: ClassVar[Pattern ] = re_compile(r"""RTL assertion: "(.*)" \[(.*):(\d+)\]""") # todo: workaround for ClassVar problem
848 @classmethod
849 def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]:
850 if (match := cls._REGEXP3.match(line._message)) is not None: 850 ↛ 853line 850 didn't jump to line 853 because the condition on line 850 was always true
851 return cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, match[1], Path(match[2]), int(match[3]), previousLine=line._previousLine)
853 return None
856@export
857class TclCommand(VivadoLine):
858 """
859 Represents a TCL command found in a Vivado log output.
861 Besides the full log message (:class:`Line`), this class splits the TCL command into the command name and its
862 arguments.
863 """
864 _tclCommand: str
865 _arguments: Tuple[str, ...]
867 def __init__(
868 self,
869 lineNumber: int,
870 tclCommand: str,
871 arguments: Tuple[str, ...],
872 rawMessage: str,
873 previousLine: Nullable[VivadoLine] = None
874 ) -> None:
875 super().__init__(lineNumber, LineKind.GenericTclCommand, LineAction.Default, rawMessage, previousLine)
877 self._tclCommand = tclCommand
878 self._arguments = arguments
880 @readonly
881 def TCLCommand(self) -> str:
882 return self._tclCommand
884 @readonly
885 def Arguments(self) -> Tuple[str, ...]:
886 return self._arguments
888 @classmethod
889 def FromLine(cls, line: VivadoLine) -> Nullable[Self]:
890 args = line._message.split()
892 return cls(line._lineNumber, args[0], tuple(args[1:]), line._message, previousLine=line._previousLine)
894 @classmethod
895 def Copy(cls, line: "TclCommand", previousLine: VivadoLine) -> "TclCommand":
896 newLine = cls(line._lineNumber, line._tclCommand, line._arguments, line._message, previousLine)
897 newLine._timestamp = line._timestamp
898 return newLine
900 def __str__(self) -> str:
901 return f"{self._tclCommand} {' '.join(self._arguments)}"
904@export
905class VivadoTclCommand(TclCommand):
906 """
907 Represents a Vivado specific TCL command.
908 """
910 _PREFIX: ClassVar[str] = "Command:"
912 @classmethod
913 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
914 tclCommand = rawMessage[len(cls._PREFIX) + 1:]
915 args = tclCommand.split()
917 vivadoCommand = cls(lineNumber, args[0], tuple(args[1:]), rawMessage, previousLine)
918 vivadoCommand._kind = LineKind.VivadoTclCommand
919 return vivadoCommand
921 def __str__(self) -> str:
922 return f"{self._PREFIX} {self._tclCommand} {' '.join(self._arguments)}"
925@export
926class VivadoMessagesMixin(metaclass=ExtendedType, mixin=True):
927 _infoMessages: List[VivadoInfoMessage]
928 _warningMessages: List[VivadoWarningMessage]
929 _criticalWarningMessages: List[VivadoCriticalWarningMessage]
930 _errorMessages: List[VivadoErrorMessage]
931 _toolIDs: Dict[int, str]
932 _toolNames: Dict[str, int]
933 _messagesByID: Dict[int, Dict[int, List[VivadoMessage]]]
935 def __init__(self) -> None:
936 self._infoMessages = []
937 self._warningMessages = []
938 self._criticalWarningMessages = []
939 self._errorMessages = []
940 self._toolIDs = {}
941 self._toolNames = {}
942 self._messagesByID = {}
944 @readonly
945 def ToolIDs(self) -> Dict[int, str]:
946 return self._toolIDs
948 @readonly
949 def ToolNames(self) -> Dict[str, int]:
950 return self._toolNames
952 @readonly
953 def MessagesByID(self) -> Dict[int, Dict[int, List[VivadoMessage]]]:
954 return self._messagesByID
956 @readonly
957 def InfoMessages(self) -> List[VivadoInfoMessage]:
958 return self._infoMessages
960 @readonly
961 def WarningMessages(self) -> List[VivadoWarningMessage]:
962 return self._warningMessages
964 @readonly
965 def CriticalWarningMessages(self) -> List[VivadoCriticalWarningMessage]:
966 return self._criticalWarningMessages
968 @readonly
969 def ErrorMessages(self) -> List[VivadoErrorMessage]:
970 return self._errorMessages
972 def _AddMessage(self, message: VivadoMessage) -> None:
973 if isinstance(message, InfoMessage):
974 self._infoMessages.append(message)
975 elif isinstance(message, WarningMessage):
976 self._warningMessages.append(message)
977 elif isinstance(message, CriticalWarningMessage):
978 self._criticalWarningMessages.append(message)
979 elif isinstance(message, ErrorMessage): 979 ↛ 982line 979 didn't jump to line 982 because the condition on line 979 was always true
980 self._errorMessages.append(message)
982 if message._toolID in self._messagesByID:
983 sub = self._messagesByID[message._toolID]
984 if message._messageKindID in sub:
985 sub[message._messageKindID].append(message)
986 else:
987 sub[message._messageKindID] = [message]
988 else:
989 if message._toolID is not None:
990 self._toolIDs[message._toolID] = message._toolName
991 self._toolNames[message._toolName] = message._toolID
993 self._messagesByID[message._toolID] = {message._messageKindID: [message]}
996# todo: check usage or merge with parser
997@export
998class BaseParser(VivadoMessagesMixin, metaclass=ExtendedType, slots=True):
999 def __init__(self) -> None:
1000 super().__init__()
1003@export
1004class Parser(BaseParser):
1005 _processor: "Processor"
1007 def __init__(self, processor: "Processor") -> None:
1008 super().__init__()
1010 self._processor = processor
1012 @readonly
1013 def Processor(self) -> "Processor":
1014 return self._processor
1017@export
1018class Preamble(Parser): # todo: uses parser, which has vivado messages
1019 """
1020 A parser for the preamble emitted by Vivado at session start.
1022 .. rubric:: Extracted information
1024 * Vivado tool version. |br|
1025 See :data:`ToolVersion`
1026 * Session start timestamp (date and time). |br|
1027 See :data:`StartDatetime`
1029 .. rubric:: Example
1031 .. code-block::
1033 #-----------------------------------------------------------
1034 # Vivado v2025.1 (64-bit)
1035 # SW Build 6140274 on Thu May 22 00:12:29 MDT 2025
1036 # IP Build 6138677 on Thu May 22 03:10:11 MDT 2025
1037 # SharedData Build 6139179 on Tue May 20 17:58:58 MDT 2025
1038 # Start of session at: Thu Jun 12 18:39:05 2025
1039 # Process ID : 28856
1040 # Current directory : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1
1041 # Command line : vivado.exe -log toplevel.vdi -applog -product Vivado -messageDb vivado.pb -mode batch -source toplevel.tcl -notrace
1042 # Log file : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1/toplevel.vdi
1043 # Journal file : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1\vivado.jou
1044 # Running On : Paebbels
1045 # Platform : Windows Server 2016 or Windows 10
1046 # Operating System : 26100
1047 # Processor Detail : 11th Gen Intel(R) Core(TM) i9-11950H @ 2.60GHz
1048 # CPU Frequency : 2611 MHz
1049 # CPU Physical cores : 8
1050 # CPU Logical cores : 16
1051 # Host memory : 34048 MB
1052 # Swap memory : 28991 MB
1053 # Total Virtual : 63039 MB
1054 # Available Virtual : 29246 MB
1055 #-----------------------------------------------------------
1056 """
1057 _VERSION: ClassVar[Pattern] = re_compile(r"""# Vivado v(\d+\.\d(\.\d)?) \(64-bit\)""")
1058 _STARTTIME: ClassVar[Pattern] = re_compile(r"""# Start of session at: (\w+\s+\w+\s+\d+\s+\d+:\d+:\d+\s+\d+)""")
1060 _toolVersion: Nullable[YearReleaseVersion] #: Used Vivado version.
1061 _startDatetime: Nullable[datetime] #: Session start timestamp.
1063 def __init__(self, processor: "BaseProcessor") -> None:
1064 """
1065 Initializes a Vivado preamble parser.
1067 :param processor: Reference to the Vivado log processor.
1068 """
1069 super().__init__(processor)
1071 self._toolVersion = None
1072 self._startDatetime = None
1074 @readonly
1075 def ToolVersion(self) -> YearReleaseVersion:
1076 """
1077 Read-only property to access the extracted Vivado tool version.
1079 :returns: The used Vivado version as reported in the Vivado log messages.
1080 """
1081 return self._toolVersion
1083 @readonly
1084 def StartDatetime(self) -> datetime:
1085 """
1086 Read-only property to access the date and time when the Vivado session was started.
1088 :returns: Datatime when the session was started.
1089 :raises ProcessorException: When start timestamp wasn't extracted from preamble.
1090 """
1091 if self._startDatetime is None: 1091 ↛ 1092line 1091 didn't jump to line 1092 because the condition on line 1091 was never true
1092 raise ProcessorException("No start timestamp extracted from preamble.")
1094 return self._startDatetime
1096 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1097 """
1098 A generator for processing the Vivado session preamble line-by-line.
1100 :param line: First line to process.
1101 :returns: A generator processing log messages.
1102 """
1103 if line.StartsWith("#----"): 1103 ↛ 1106line 1103 didn't jump to line 1106 because the condition on line 1103 was always true
1104 line._kind = LineKind.SectionDelimiter
1105 else:
1106 line._kind |= LineKind.ProcessorError # TODO: throw / return error
1108 line = yield line
1110 # a normal preamble has up to 23 lines including both delimiter lines.
1111 for _ in range(30): 1111 ↛ 1126line 1111 didn't jump to line 1126 because the loop on line 1111 didn't complete
1112 if (match := self._VERSION.match(line._message)) is not None:
1113 self._toolVersion = YearReleaseVersion.Parse(match[1])
1114 line._kind = LineKind.Normal
1115 elif (match := self._STARTTIME.match(line._message)) is not None:
1116 self._startDatetime = datetime.strptime(match[1], "%a %b %d %H:%M:%S %Y")
1117 line._kind = LineKind.Normal
1118 elif line.StartsWith("#----"):
1119 line._kind = LineKind.SectionDelimiter
1120 break
1121 else:
1122 line._kind = LineKind.Verbose
1124 line = yield line
1125 else:
1126 line._kind |= LineKind.ProcessorError # TODO: throw / return error
1128 nextLine = yield line
1129 return nextLine
1132@export
1133class Postamble(Parser, VivadoMessagesMixin): # todo: double mixin?
1134 """
1135 A parser for the postamble emitted by Vivado at session end.
1137 .. rubric:: Extracted information
1139 * Session exit timestamp (date and time). |br|
1140 See :data:`ExitDatetime`
1142 .. rubric:: Example
1144 .. code-block::
1146 INFO: [Common 17-206] Exiting Vivado at Tue Sep 2 08:46:23 2025...
1148 """
1149 _INFO: Tuple[int, int] = (17, 206)
1150 _ENDTIME: ClassVar[Pattern] = re_compile(r"""Exiting Vivado at (\w+\s+\w+\s+\d+\s+\d+:\d+:\d+\s+\d+)""")
1152 _exitDatetime: Nullable[datetime] #: Session exit timestamp.
1154 def __init__(self, processor: "BaseProcessor") -> None:
1155 """
1156 Initializes a Vivado postamble parser.
1158 :param processor: Reference to the Vivado log processor.
1159 """
1160 super().__init__(processor)
1161 VivadoMessagesMixin.__init__(self)
1163 self._exitDatetime = None
1165 @readonly
1166 def ExitDatetime(self) -> Nullable[datetime]:
1167 """
1168 Read-only property to access the date and time when the Vivado session was exited.
1170 :returns: Datatime when the session was exited.
1171 :raises ProcessorException: When exit timestamp wasn't extracted from postamble.
1172 """
1173 if self._exitDatetime is None: 1173 ↛ 1174line 1173 didn't jump to line 1174 because the condition on line 1173 was never true
1174 raise ProcessorException("No exit timestamp extracted from postamble.")
1176 return self._exitDatetime
1178 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1179 """
1180 A generator for processing the Vivado session preamble line-by-line.
1182 :param line: First line to process.
1183 :returns: A generator processing log messages.
1184 """
1185 if isinstance(line, VivadoMessage): 1185 ↛ 1191line 1185 didn't jump to line 1191 because the condition on line 1185 was always true
1186 self._AddMessage(line)
1188 if not isinstance(line, VivadoInfoMessage): 1188 ↛ 1189line 1188 didn't jump to line 1189 because the condition on line 1188 was never true
1189 raise ProcessorException(f"{self.__class__.__name__}.Generator(): Expected '{self._ENDTIME}' at line {line._lineNumber}.")
1191 if (match := self._ENDTIME.match(line._message)) is not None:
1192 self._exitDatetime = datetime.strptime(match[1], "%a %b %d %H:%M:%S %Y")
1193 else:
1194 pass
1196 line = yield line
1198 # todo: should we receive and expect an ned-token like None?
1199 return line
1201@export
1202class Command(Parser):
1203 """
1204 This parser parses outputs from Vivado TCL commands.
1206 Depending on the command's output (and how it's implemented), they use different subcategories.
1208 .. rubric:: Command subcategories
1210 * :class:`CommandWithSections`
1211 * :class:`CommandWithtasks`
1213 .. rubric:: Supported commands
1215 * :class:`SynthesizeDesign`
1216 * :class:`LinkDesign`
1217 * :class:`OptimizeDesign`
1218 * :class:`PlaceDesign`
1219 * :class:`PhysicalOptimizeDesign`
1220 * :class:`RouteDesign`
1221 * :class:`WriteBitstream`
1222 * :class:`ReportDRC`
1223 * :class:`ReportMethodology`
1224 * :class:`ReportPower`
1226 .. rubric:: Example
1228 .. code-block::
1230 [...]
1231 Command: synth_design -top system_top -part xc7z015clg485-2
1232 Starting synth_design
1233 [...]
1234 """
1236 # _TCL_COMMAND: ClassVar[str]
1238 def _CommandStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1239 """
1240 A generator accepting a line containing the expected Vivado TCL command.
1242 When the generator exits, the returned line is the successor line to the line containing the Vivado TCL command.
1244 :param line: The first line for the generator to process.
1245 :returns: A generator processing Vivado output log lines.
1246 """
1247 if not (isinstance(line, VivadoTclCommand) and line._tclCommand == self._TCL_COMMAND): 1247 ↛ 1248line 1247 didn't jump to line 1248 because the condition on line 1247 was never true
1248 raise ProcessorException() # FIXME: add exception message
1250 nextLine = yield line
1251 return nextLine
1253 def _CommandFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1254 if line.StartsWith(f"{self._TCL_COMMAND} completed successfully"): 1254 ↛ 1257line 1254 didn't jump to line 1257 because the condition on line 1254 was always true
1255 line._kind |= LineKind.Success
1256 else:
1257 line._kind |= LineKind.Failed
1259 line = yield line
1261 if self._TIME is not None: # and self._processor._preamble._toolVersion > "2022.2":
1262 end = f"{self._TCL_COMMAND}: {self._TIME}"
1264 # while True: # TODO: limit search for time to 10 lines
1265 # if line.StartsWith(end):
1266 # line._kind = LineKind.TaskTime
1267 # line = yield line
1268 # break
1269 #
1270 # line = yield line
1272 if line.StartsWith(end):
1273 line._kind = LineKind.TaskTime
1274 line = yield line
1276 return line
1278 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, None]:
1279 line = yield from self._CommandStart(line)
1281 end = f"{self._TCL_COMMAND} "
1282 while True:
1283 if line._kind is LineKind.Empty:
1284 line = yield line
1285 continue
1286 elif isinstance(line, VivadoMessage):
1287 self._AddMessage(line)
1288 elif line.StartsWith(end):
1289 nextLine = yield from self._CommandFinish(line)
1290 return nextLine
1292 line = yield line
1294 def __str__(self) -> str:
1295 return f"{self._TCL_COMMAND}"
1298@export
1299class CommandWithSections(Command):
1300 """
1301 A Vivado command writing sections into the output log.
1303 .. rubric:: Example
1305 .. code-block::
1307 [...]
1308 ---------------------------------------------------------------------------------
1309 Starting RTL Elaboration : Time (s): cpu = 00:00:03 ; elapsed = 00:00:03 . Memory (MB): peak = 847.230 ; gain = 176.500
1310 ---------------------------------------------------------------------------------
1311 INFO: [Synth 8-638] synthesizing module 'system_top' [C:/Users/tgomes/git/2019_1/src/system_top_PE1.vhd:257]
1312 [...]
1313 [...]
1314 [...]
1315 ---------------------------------------------------------------------------------
1316 Finished RTL Elaboration : Time (s): cpu = 00:00:04 ; elapsed = 00:00:04 . Memory (MB): peak = 917.641 ; gain = 246.910
1317 ---------------------------------------------------------------------------------
1318 [...]
1319 """
1320 # _PARSERS: ClassVar[Tuple[Type[Section], ...]]
1322 _sections: List["Section"] # Dict[Type["Section"], "Section"]
1325 def __init__(self, processor: "Processor") -> None:
1326 super().__init__(processor)
1328 self._sections = [] # p: p(self) for p in self._PARSERS}
1330 @readonly
1331 def Sections(self) -> List["Section"]: # Dict[Type["Section"], "Section"]:
1332 """
1333 Read-only property to access a dictionary of found sections within the TCL command's output.
1335 :returns: A dictionary of found :class:`~pyEDAA.OutputFilter.Xilinx.SynthesizeDesign.Section`s.
1336 """
1337 return self._sections
1339 def __contains__(self, key: Any) -> bool:
1340 if not issubclass(key, Section): 1340 ↛ 1341line 1340 didn't jump to line 1341 because the condition on line 1340 was never true
1341 ex = TypeError(f"Parameter 'key' is not a Section.")
1342 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1343 raise ex
1345 for section in self._sections: 1345 ↛ 1349line 1345 didn't jump to line 1349 because the loop on line 1345 didn't complete
1346 if isinstance(section, key): 1346 ↛ 1345line 1346 didn't jump to line 1345 because the condition on line 1346 was always true
1347 return True
1348 else:
1349 return False
1351 def __getitem__(self, key: Type["Section"]) -> "Section":
1352 if not issubclass(key, Section): 1352 ↛ 1353line 1352 didn't jump to line 1353 because the condition on line 1352 was never true
1353 ex = TypeError(f"Parameter 'key' is not a Section.")
1354 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1355 raise ex
1357 for section in self._sections: 1357 ↛ 1361line 1357 didn't jump to line 1361 because the loop on line 1357 didn't complete
1358 if isinstance(section, key):
1359 return section
1360 else:
1361 raise SectionNotPresentException(F"Section '{key._NAME}' not present in '{self._parent.logfile}'.")
1364@export
1365class CommandWithTasks(Command):
1366 """
1367 A Vivado command writing tasks into the output log.
1369 .. rubric:: Example
1371 .. code-block::
1373 [...]
1374 Starting Cache Timing Information Task
1375 INFO: [Timing 38-35] 79-Done setting XDC timing constraints.
1376 [...]
1377 [...]
1378 Ending Cache Timing Information Task | Checksum: 19fe8cb97
1379 [...]
1380 """
1381 # _PARSERS: Tuple[Type[Task], ...]
1383 _tasks: Dict[Type["Task"], "Task"]
1385 def __init__(self, processor: "Processor") -> None:
1386 super().__init__(processor)
1388 self._tasks = {p: p(self) for p in self._PARSERS}
1390 @readonly
1391 def Tasks(self) -> Dict[Type["Task"], "Task"]:
1392 """
1393 Read-only property to access a dictionary of found tasks within the TCL command's output.
1395 :returns: A dictionary of found :class:`~pyEDAA.OutputFilter.Xilinx.Common2.Task`s.
1396 """
1397 return self._tasks
1399 def __contains__(self, key: Any) -> bool:
1400 if not issubclass(key, Task): 1400 ↛ 1401line 1400 didn't jump to line 1401 because the condition on line 1400 was never true
1401 ex = TypeError(f"Parameter 'key' is not a Task.")
1402 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1403 raise ex
1405 return key in self._tasks
1407 def __getitem__(self, key: Type["Task"]) -> "Task":
1408 try:
1409 return self._tasks[key]
1410 except KeyError as ex:
1411 raise SectionNotPresentException(F"Task '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
1414@export
1415class BaseSection(metaclass=ExtendedType, mixin=True):
1416 @abstractmethod
1417 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1418 pass
1420 @abstractmethod
1421 def _SectionFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
1422 pass
1424 @abstractmethod
1425 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1426 pass
1429@export
1430class Section(BaseParser, BaseSection):
1431 """
1432 Base-class for sections within log outputs from *synthesize design*.
1433 """
1434 # _NAME: ClassVar[str]
1435 # _START: ClassVar[str]
1436 # _FINISH: ClassVar[str]
1437 # _DUPLICATES: ClassVar[bool]
1439 _command: "Command" #: Reference to the command (parent).
1440 _next: Nullable["Section"]
1441 _duration: float #: Duration synthesis spent in processing a synthesis step logged in this log output section.
1443 def __init__(self, command: "Command") -> None:
1444 """
1445 Initialized a section.
1447 :param command: Reference to the parent TCL command.
1448 """
1449 super().__init__() #command._processor)
1451 self._command = command
1452 self._next = None
1453 self._duration = 0.0
1455 @readonly
1456 def Next(self) -> Nullable["Section"]:
1457 """
1458 Read-only property to access the next section in case the section appeared multiple times.
1460 :returns: Next section of same type.
1461 """
1462 return self._next
1464 @readonly
1465 def Duration(self) -> float:
1466 """
1467 Read-only property to access the duration synthesis spent in processing a synthesis step logged in this log output
1468 section.
1470 :returns: Synthesis step duration in seconds.
1471 """
1472 return self._duration
1474 def __iter__(self) -> Generator["Section", None, None]:
1475 section = self._next
1476 while section is not None:
1477 yield section
1479 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1480 line._kind = LineKind.SectionStart
1482 line = yield line
1483 if line.StartsWith("----"): 1483 ↛ 1486line 1483 didn't jump to line 1486 because the condition on line 1483 was always true
1484 line._kind = LineKind.SectionStart | LineKind.SectionDelimiter
1485 else:
1486 line._kind |= LineKind.ProcessorError
1488 nextLine = yield line
1489 return nextLine
1491 def _SectionFinish(self, line: VivadoLine, skipDashes: bool = False) -> Generator[VivadoLine, VivadoLine, None]:
1492 if not skipDashes:
1493 if line.StartsWith("----"): 1493 ↛ 1496line 1493 didn't jump to line 1496 because the condition on line 1493 was always true
1494 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
1495 else:
1496 line._kind |= LineKind.ProcessorError
1498 line = yield line
1500 if line.StartsWith(self._FINISH): 1500 ↛ 1503line 1500 didn't jump to line 1503 because the condition on line 1500 was always true
1501 line._kind = LineKind.SectionEnd
1502 else:
1503 line._kind |= LineKind.ProcessorError
1505 line = yield line
1506 if line.StartsWith("----"): 1506 ↛ 1509line 1506 didn't jump to line 1509 because the condition on line 1506 was always true
1507 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
1508 else:
1509 line._kind |= LineKind.ProcessorError
1511 nextLine = yield line
1512 return nextLine
1514 # @mustoverride
1515 # def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
1516 # if len(line) == 0:
1517 # return ProcessingState.EmptyLine
1518 # elif line.startswith("----"):
1519 # return ProcessingState.DelimiterLine
1520 # elif line.startswith(self._START):
1521 # return ProcessingState.Skipped
1522 # elif line.startswith(self._FINISH):
1523 # l = line[len(self._FINISH):]
1524 # if (match := TIME_MEMORY_PATTERN.match(l)) is not None:
1525 # # cpuParts = match[1].split(":")
1526 # elapsedParts = match[2].split(":")
1527 # # peakMemory = float(match[3])
1528 # # gainMemory = float(match[4])
1529 # self._duration = int(elapsedParts[0]) * 3600 + int(elapsedParts[1]) * 60 + int(elapsedParts[2])
1530 #
1531 # return ProcessingState.Skipped | ProcessingState.Last
1532 # elif line.startswith("Start") or line.startswith("Starting"):
1533 # print(f"ERROR: didn't find finish\n {line}")
1534 # return ProcessingState.Reprocess
1535 #
1536 # return ProcessingState.Skipped
1538 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1539 line = yield from self._SectionStart(line)
1541 while True:
1542 if line._kind is LineKind.Empty: 1542 ↛ 1543line 1542 didn't jump to line 1543 because the condition on line 1542 was never true
1543 line = yield line
1544 continue
1545 elif line.StartsWith("----"):
1546 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
1547 break
1548 elif isinstance(line, VivadoMessage):
1549 self._AddMessage(line)
1550 else:
1551 line._kind = LineKind.Verbose
1553 line = yield line
1555 # line = yield line
1556 nextLine = yield from self._SectionFinish(line)
1557 return nextLine
1560@export
1561class SubSection(BaseParser, BaseSection):
1562 """
1563 Base-class for subsections within log outputs from *synthesize design*.
1564 """
1565 # _NAME: ClassVar[str]
1567 _section: Section #: Reference to the section (parent).
1569 def __init__(self, section: Section) -> None:
1570 """
1571 Initialized a subsection.
1573 :param section: Reference to the parent section.
1574 """
1575 super().__init__()
1576 self._section = section
1578 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1579 line._kind = LineKind.SubSectionStart
1581 line = yield line
1582 if line.StartsWith("----"): 1582 ↛ 1585line 1582 didn't jump to line 1585 because the condition on line 1582 was always true
1583 line._kind = LineKind.SubSectionStart | LineKind.SubSectionDelimiter
1584 else:
1585 line._kind |= LineKind.ProcessorError
1587 nextLine = yield line
1588 return nextLine
1590 def _SectionFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
1591 if line.StartsWith("----"): 1591 ↛ 1594line 1591 didn't jump to line 1594 because the condition on line 1591 was always true
1592 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter
1593 else:
1594 line._kind |= LineKind.ProcessorError
1596 line = yield line
1597 if line.StartsWith(self._FINISH): 1597 ↛ 1600line 1597 didn't jump to line 1600 because the condition on line 1597 was always true
1598 line._kind = LineKind.SubSectionEnd
1599 else:
1600 line._kind |= LineKind.ProcessorError
1602 line = yield line
1603 if line.StartsWith("----"): 1603 ↛ 1606line 1603 didn't jump to line 1606 because the condition on line 1603 was always true
1604 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter
1605 else:
1606 line._kind |= LineKind.ProcessorError
1608 nextLine = yield line
1609 return nextLine
1611 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1612 line = yield from self._SectionStart(line)
1614 while True:
1615 if line._kind is LineKind.Empty: 1615 ↛ 1616line 1615 didn't jump to line 1616 because the condition on line 1615 was never true
1616 line = yield line
1617 continue
1618 elif line.StartsWith("----"): 1618 ↛ 1621line 1618 didn't jump to line 1621 because the condition on line 1618 was always true
1619 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter
1620 break
1621 elif isinstance(line, VivadoMessage):
1622 self._AddMessage(line)
1623 else:
1624 line._kind = LineKind.Verbose
1626 line = yield line
1628 nextLine = yield from self._SectionFinish(line)
1629 return nextLine
1632@export
1633class SectionWithChildren(Section):
1634 """
1635 Base-class for sections with subsections.
1636 """
1637 _subsections: Dict[Type[SubSection], SubSection]
1639 def __init__(self, command: "Command") -> None:
1640 super().__init__(command)
1642 self._subsections = {}
1644 def __contains__(self, key: Any) -> bool:
1645 if not issubclass(key, SubSection): 1645 ↛ 1646line 1645 didn't jump to line 1646 because the condition on line 1645 was never true
1646 ex = TypeError(f"Parameter 'item' is not a SubSection.")
1647 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1648 raise ex
1650 return key in self._subsections
1652 def __getitem__(self, item: Type[SubSection]) -> SubSection:
1653 try:
1654 return self._subsections[item]
1655 except KeyError as ex:
1656 raise SubSectionNotPresentException(f"SubSection '{item._NAME}' not present in '{self._parent._parent.logfile}'.") from ex
1659@export
1660class Task(BaseParser, VivadoMessagesMixin):
1661 """
1662 A task's output emitted by a Vivado command.
1664 .. rubric:: Extracted information
1666 * Vivado messages (info, warning, critical warning, error).
1668 .. rubric:: Example
1670 .. code-block::
1672 Starting Cache Timing Information Task
1673 INFO: [Timing 38-35] 79-Done setting XDC timing constraints.
1674 Ending Cache Timing Information Task | Checksum: 19fe8cb97
1676 Time (s): cpu = 00:00:09 ; elapsed = 00:00:09 . Memory (MB): peak = 1370.594 ; gain = 493.266
1678 """
1679 # _NAME: ClassVar[str]
1680 # _START: ClassVar[str]
1681 # _FINISH: ClassVar[str]
1682 _TIME: ClassVar[str] = "Time (s):"
1684 _command: "Command" #: Reference to the command (parent).
1685 _duration: float #: Duration of a task according to reported times by Vivado.
1687 def __init__(self, command: "Command") -> None:
1688 """
1689 Initializes a task (without child elements).
1691 :param command: Reference to the command.
1692 """
1693 super().__init__()
1694 VivadoMessagesMixin.__init__(self)
1696 self._command = command
1698 @readonly
1699 def Command(self) -> "Command":
1700 """
1701 Read-only property to access the command.
1703 :returns: The command this task's output was logged for.
1704 """
1705 return self._command
1707 def _TaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1708 """
1709 A generator for processing a task start (single line).
1711 :param line: First line to process (task start).
1712 :returns: A generator processing log messages.
1713 :raises ProcessorException: If first line doesn't conform to the *task start* pattern.
1714 """
1715 if not line.StartsWith(self._START): 1715 ↛ 1716line 1715 didn't jump to line 1716 because the condition on line 1715 was never true
1716 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.")
1718 line._kind = LineKind.TaskStart
1719 nextLine = yield line
1720 return nextLine
1722 def _TaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1723 """
1724 A generator for processing a task finish line-by-line.
1726 :param line: First line to process (task finish).
1727 :returns: A generator processing log messages.
1728 :raises ProcessorException: If finish line doesn't conform to the *task finish* pattern.
1729 """
1730 if not line.StartsWith(self._FINISH): 1730 ↛ 1731line 1730 didn't jump to line 1731 because the condition on line 1730 was never true
1731 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.")
1733 line._kind = LineKind.TaskEnd
1734 line = yield line
1735 while self._TIME is not None: # TODO: limit search for time pattern to XX lines 1735 ↛ 1742line 1735 didn't jump to line 1742 because the condition on line 1735 was always true
1736 if line.StartsWith(self._TIME):
1737 line._kind = LineKind.TaskTime
1738 break
1740 line = yield line
1742 nextLine = yield line
1743 return nextLine
1745 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1746 """
1747 A generator for processing a task without child elements line-by-line.
1749 .. rubric:: Algorithm
1751 1. Send first line to :meth:`_TaskStart`.
1752 2. Process body lines
1754 * Collect Vivado messages (info, warning, critical warning, error).
1755 * Check for *task finish* pattern.
1756 * Check for *time* pattern.
1758 3. Send last lines to :meth:`_TaskFinish`.
1760 :param line: First line to process.
1761 :returns: A generator processing log messages.
1762 """
1763 line = yield from self._TaskStart(line)
1765 while True:
1766 if line._kind is LineKind.Empty:
1767 line = yield line
1768 continue
1769 elif self._FINISH is not None and line.StartsWith("Ending"):
1770 break
1771 elif isinstance(line, VivadoMessage):
1772 self._AddMessage(line)
1773 elif line.StartsWith(self._TIME):
1774 line._kind = LineKind.TaskTime
1775 nextLine = yield line
1776 return nextLine
1778 line = yield line
1780 nextLine = yield from self._TaskFinish(line)
1781 return nextLine
1783 def __str__(self) -> str:
1784 return f"{self.__class__.__name__}: {self._START}"
1787@export
1788class TaskWithSubTasks(Task):
1789 """
1790 A task's output emitted by a Vivado command.
1792 .. rubric:: Extracted information
1794 * Vivado messages (info, warning, critical warning, error).
1795 * Subtasks
1797 .. rubric:: Example
1799 .. code-block::
1801 Starting Cache Timing Information Task
1802 INFO: [Timing 38-35] 79-Done setting XDC timing constraints.
1803 Ending Cache Timing Information Task | Checksum: 19fe8cb97
1805 Time (s): cpu = 00:00:09 ; elapsed = 00:00:09 . Memory (MB): peak = 1370.594 ; gain = 493.266
1807 """
1808 # _PARSERS: ClassVar[Tuple[Type["SubTask"], ...]]
1810 _subtasks: Dict[Type["SubTask"], "SubTask"]
1812 def __init__(self, command: "Command") -> None:
1813 super().__init__(command)
1815 self._subtasks = {p: p(self) for p in self._PARSERS}
1817 @readonly
1818 def SubTasks(self) -> Dict[Type["SubTask"], "SubTask"]:
1819 return self._subtasks
1821 def __contains__(self, key: Any) -> bool:
1822 if not issubclass(key, SubTask): 1822 ↛ 1823line 1822 didn't jump to line 1823 because the condition on line 1822 was never true
1823 ex = TypeError(f"Parameter 'key' is not a Subtask.")
1824 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1825 raise ex
1827 return key in self._subtasks
1829 def __getitem__(self, key: Type["SubTask"]) -> "SubTask":
1830 try:
1831 return self._subtasks[key]
1832 except KeyError as ex:
1833 raise SubTaskNotPresentException(F"Subtask '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
1835 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1836 line = yield from self._TaskStart(line)
1838 activeParsers: List[Phase] = list(self._subtasks.values())
1840 while True:
1841 while True:
1842 if line._kind is LineKind.Empty:
1843 line = yield line
1844 continue
1845 elif isinstance(line, VivadoMessage):
1846 self._AddMessage(line)
1847 elif line.StartsWith("Starting "):
1848 for parser in activeParsers: # type: SubTask 1848 ↛ 1853line 1848 didn't jump to line 1853 because the loop on line 1848 didn't complete
1849 if line.StartsWith(parser._START): 1849 ↛ 1848line 1849 didn't jump to line 1848 because the condition on line 1849 was always true
1850 line = yield next(subtask := parser.Generator(line))
1851 break
1852 else:
1853 WarningCollector.Raise(UnknownSubTask(f"Unknown subtask: '{line!r}'", line))
1854 ex = Exception(f"How to recover from here? Unknown subtask: '{line!r}'")
1855 ex.add_note(f"Current task: start pattern='{self}'")
1856 ex.add_note(f"Current command: {self._command}")
1857 raise ex
1858 break
1859 elif line.StartsWith("Ending"):
1860 nextLine = yield from self._TaskFinish(line)
1861 return nextLine
1862 elif line.StartsWith(self._TIME): 1862 ↛ 1863line 1862 didn't jump to line 1863 because the condition on line 1862 was never true
1863 line._kind = LineKind.TaskTime
1864 nextLine = yield line
1865 return nextLine
1867 line = yield line
1869 while True:
1870 isFinish = False # line.StartsWith("Ending") # FIXME: detect end, but time might come later
1872 try:
1873 processedLine = subtask.send(line)
1875 if isinstance(processedLine, VivadoMessage):
1876 self._AddMessage(processedLine)
1878 if isFinish: 1878 ↛ 1879line 1878 didn't jump to line 1879 because the condition on line 1878 was never true
1879 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
1880 line = yield processedLine
1881 break
1882 except StopIteration as ex:
1883 activeParsers.remove(parser)
1884 line = ex.value
1885 break
1887 line = yield processedLine
1890@export
1891class SubTask(BaseParser, VivadoMessagesMixin):
1892 # _NAME: ClassVar[str]
1893 # _START: ClassVar[str]
1894 # _FINISH: ClassVar[str]
1895 _TIME: ClassVar[str] = "Time (s):"
1897 _task: TaskWithSubTasks
1898 _duration: float
1900 def __init__(self, task: TaskWithSubTasks) -> None:
1901 super().__init__()
1902 VivadoMessagesMixin.__init__(self)
1904 self._task = task
1906 @readonly
1907 def Task(self) -> TaskWithSubTasks:
1908 return self._task
1910 def _TaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1911 if not line.StartsWith(self._START): 1911 ↛ 1912line 1911 didn't jump to line 1912 because the condition on line 1911 was never true
1912 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.")
1914 line._kind = LineKind.TaskStart
1915 nextLine = yield line
1916 return nextLine
1918 def _TaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1919 if not line.StartsWith(self._FINISH): 1919 ↛ 1920line 1919 didn't jump to line 1920 because the condition on line 1919 was never true
1920 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.")
1922 line._kind = LineKind.TaskEnd
1923 line = yield line
1924 while self._TIME is not None: 1924 ↛ 1931line 1924 didn't jump to line 1931 because the condition on line 1924 was always true
1925 if line.StartsWith(self._TIME):
1926 line._kind = LineKind.TaskTime
1927 break
1929 line = yield line
1931 nextLine = yield line
1932 return nextLine
1934 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1935 line = yield from self._TaskStart(line)
1937 while True:
1938 if line._kind is LineKind.Empty: 1938 ↛ 1939line 1938 didn't jump to line 1939 because the condition on line 1938 was never true
1939 line = yield line
1940 continue
1941 elif self._FINISH is not None and line.StartsWith("Ending"):
1942 break
1943 elif isinstance(line, VivadoMessage):
1944 self._AddMessage(line)
1945 elif line.StartsWith(self._TIME): 1945 ↛ 1946line 1945 didn't jump to line 1946 because the condition on line 1945 was never true
1946 line._kind = LineKind.TaskTime
1947 nextLine = yield line
1948 return nextLine
1950 line = yield line
1952 nextLine = yield from self._TaskFinish(line)
1953 return nextLine
1955 def __str__(self) -> str:
1956 return self._NAME
1959@export
1960class TaskWithPhases(Task):
1961 # _PARSERS: ClassVar[Tuple[Type["Phase"], ...]]
1963 _phases: Dict[Type["Phase"], "Phase"]
1965 def __init__(self, command: "Command") -> None:
1966 super().__init__(command)
1968 self._phases = {p: p(self) for p in self._PARSERS}
1970 @readonly
1971 def Phases(self) -> Dict[Type["Phase"], "Phase"]:
1972 return self._phases
1974 def __contains__(self, key: Any) -> bool:
1975 if not issubclass(key, Phase): 1975 ↛ 1976line 1975 didn't jump to line 1976 because the condition on line 1975 was never true
1976 ex = TypeError(f"Parameter 'key' is not a Phase.")
1977 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1978 raise ex
1980 return key in self._phases
1982 def __getitem__(self, key: Type["Phase"]) -> "Phase":
1983 try:
1984 return self._phases[key]
1985 except KeyError as ex:
1986 raise PhaseNotPresentException(F"Phase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
1988 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1989 line = yield from self._TaskStart(line)
1991 activeParsers: List[Phase] = list(self._phases.values())
1993 while True:
1994 while True:
1995 if line._kind is LineKind.Empty:
1996 line = yield line
1997 continue
1998 elif isinstance(line, VivadoMessage):
1999 self._AddMessage(line)
2000 elif line.StartsWith("Phase "):
2001 for parser in activeParsers: # type: Phase 2001 ↛ 2006line 2001 didn't jump to line 2006 because the loop on line 2001 didn't complete
2002 if (match := parser._START.match(line._message)) is not None:
2003 line = yield next(phase := parser.Generator(line))
2004 break
2005 else:
2006 WarningCollector.Raise(UnknownPhase(f"Unknown phase: '{line!r}'", line))
2007 ex = Exception(f"How to recover from here? Unknown phase: '{line!r}'")
2008 ex.add_note(f"Current task: start pattern='{self}'")
2009 ex.add_note(f"Current command: {self._command}")
2010 raise ex
2011 break
2012 elif line.StartsWith("Ending"):
2013 nextLine = yield from self._TaskFinish(line)
2014 return nextLine
2015 elif line.StartsWith(self._TIME):
2016 line._kind = LineKind.TaskTime
2017 nextLine = yield line
2018 return nextLine
2020 line = yield line
2022 while True:
2023 isFinish = False #line.StartsWith("Ending")
2025 try:
2026 processedLine = phase.send(line)
2028 if isinstance(processedLine, VivadoMessage):
2029 self._AddMessage(processedLine)
2031 if isFinish: 2031 ↛ 2032line 2031 didn't jump to line 2032 because the condition on line 2031 was never true
2032 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2033 line = yield processedLine
2034 break
2035 except StopIteration as ex:
2036 activeParsers.remove(parser)
2037 line = ex.value
2038 break
2040 line = yield processedLine
2043@export
2044class Phase(BaseParser, VivadoMessagesMixin):
2045 # _NAME: ClassVar[str]
2046 # _START: ClassVar[str]
2047 # _FINISH: ClassVar[str]
2048 # _TIME: ClassVar[str] = "Time (s):"
2049 # _FINAL: ClassVar[Nullable[str]] = None
2051 _task: TaskWithPhases
2052 _phaseIndex: int
2053 _duration: float
2055 def __init__(self, task: TaskWithPhases) -> None:
2056 super().__init__()
2057 VivadoMessagesMixin.__init__(self)
2059 self._task = task
2060 self._phaseIndex = None
2062 @readonly
2063 def Task(self) -> TaskWithPhases:
2064 return self._task
2066 def _PhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2067 if (match := self._START.match(line._message)) is None: 2067 ↛ 2068line 2067 didn't jump to line 2068 because the condition on line 2067 was never true
2068 raise ProcessorException(f"{self.__class__.__name__}._PhaseStart(): Expected '{self._START}' at line {line._lineNumber}.")
2070 self._phaseIndex = int(match["major"])
2072 line._kind = LineKind.PhaseStart
2073 nextLine = yield line
2074 return nextLine
2076 def _PhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2077 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
2078 if not line.StartsWith(FINISH): 2078 ↛ 2079line 2078 didn't jump to line 2079 because the condition on line 2078 was never true
2079 raise ProcessorException(f"{self.__class__.__name__}._PhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2081 line._kind = LineKind.PhaseEnd
2082 line = yield line
2084 if self._TIME is not None: 2084 ↛ 2096line 2084 didn't jump to line 2096 because the condition on line 2084 was always true
2085 while True:
2086 if line.StartsWith(self._TIME):
2087 line._kind = LineKind.PhaseTime
2088 break
2089 elif isinstance(line, VivadoMessage): 2089 ↛ 2090line 2089 didn't jump to line 2090 because the condition on line 2089 was never true
2090 self._AddMessage(line)
2092 line = yield line
2094 line = yield line
2096 if self._FINAL is not None and self._task._command._processor._preamble._toolVersion >= "2023.2":
2097 while True:
2098 if line.StartsWith(self._FINAL): 2098 ↛ 2101line 2098 didn't jump to line 2101 because the condition on line 2098 was always true
2099 line._kind = LineKind.PhaseFinal
2100 break
2101 elif isinstance(line, VivadoMessage):
2102 self._AddMessage(line)
2104 line = yield line
2106 line = yield line
2108 # TODO: optionally collect following INFO messages like 31-389, 31-1021, 31-662
2110 return line
2112 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2113 line = yield from self._PhaseStart(line)
2115 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
2117 while True:
2118 if line._kind is LineKind.Empty:
2119 line = yield line
2120 continue
2121 elif isinstance(line, VivadoMessage):
2122 self._AddMessage(line)
2123 elif line.StartsWith(FINISH):
2124 break
2126 line = yield line
2128 nextLine = yield from self._PhaseFinish(line)
2129 return nextLine
2131 def __str__(self) -> str:
2132 return f"{self.__class__.__name__}: {self._START.pattern}"
2135@export
2136class PhaseWithChildren(Phase):
2137 _SUBPHASE_PREFIX: ClassVar[str] = "Phase {phaseIndex}."
2139 _subPhases: Dict[Type["SubPhase"], "SubPhase"]
2141 def __init__(self, task: TaskWithPhases) -> None:
2142 super().__init__(task)
2144 self._subPhases = {p: p(self) for p in self._PARSERS}
2146 def __contains__(self, key: Any) -> bool:
2147 if not issubclass(key, SubPhase):
2148 ex = TypeError(f"Parameter 'item' is not a SubPhase.")
2149 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2150 raise ex
2152 return key in self._subPhases
2154 def __getitem__(self, key: Type["SubPhase"]) -> "SubPhase":
2155 try:
2156 return self._subPhases[key]
2157 except KeyError as ex:
2158 raise PhaseNotPresentException(F"SubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2160 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2161 line = yield from self._PhaseStart(line)
2163 activeParsers: List[SubPhase] = list(self._subPhases.values())
2165 SUBPHASE_PREFIX = self._SUBPHASE_PREFIX.format(phaseIndex=self._phaseIndex)
2166 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
2168 while True:
2169 while True:
2170 if line._kind is LineKind.Empty:
2171 line = yield line
2172 continue
2173 elif isinstance(line, VivadoMessage):
2174 self._AddMessage(line)
2175 elif line.StartsWith(SUBPHASE_PREFIX):
2176 for parser in activeParsers: # type: Section 2176 ↛ 2181line 2176 didn't jump to line 2181 because the loop on line 2176 didn't complete
2177 if (match := parser._START.match(line._message)) is not None:
2178 line = yield next(phase := parser.Generator(line))
2179 break
2180 else:
2181 WarningCollector.Raise(UnknownSubPhase(f"Unknown subphase: '{line!r}'", line))
2182 ex = Exception(f"How to recover from here? Unknown subphase: '{line!r}'")
2183 ex.add_note(f"Current phase: start pattern='{self}'")
2184 ex.add_note(f"Current task: start pattern='{self._task}'")
2185 ex.add_note(f"Current command: {self._task._command}")
2186 raise ex
2187 break
2188 elif line.StartsWith(FINISH):
2189 nextLine = yield from self._PhaseFinish(line)
2190 return nextLine
2192 line = yield line
2194 while True:
2195 isFinish = False # line.StartsWith(SUBPHASE_PREFIX) # FIXME: detect end, but end (e.g. time) is later then ending text
2197 try:
2198 processedLine = phase.send(line)
2200 if isinstance(processedLine, VivadoMessage):
2201 self._AddMessage(processedLine)
2203 if isFinish: 2203 ↛ 2204line 2203 didn't jump to line 2204 because the condition on line 2203 was never true
2204 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2205 line = yield processedLine
2206 break
2207 except StopIteration as ex:
2208 activeParsers.remove(parser)
2209 line = ex.value
2210 break
2212 line = yield processedLine
2215@export
2216class SubPhase(BaseParser, VivadoMessagesMixin):
2217 # _NAME: ClassVar[str]
2218 # _START: ClassVar[str]
2219 # _FINISH: ClassVar[str]
2221 _phase: Phase
2222 _phaseIndex: int
2223 _subPhaseIndex: int
2224 _duration: float
2226 def __init__(self, phase: Phase) -> None:
2227 super().__init__()
2228 VivadoMessagesMixin.__init__(self)
2230 self._phase = phase
2231 self._phaseIndex = None
2232 self._subPhaseIndex = None
2234 def _SubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2235 if (match := self._START.match(line._message)) is None: 2235 ↛ 2236line 2235 didn't jump to line 2236 because the condition on line 2235 was never true
2236 raise ProcessorException(f"{self.__class__.__name__}._SubPhaseStart(): Expected '{self._START}' at line {line._lineNumber}.")
2238 self._phaseIndex = int(match["major"])
2239 self._subPhaseIndex = int(match["minor"])
2241 line._kind = LineKind.SubPhaseStart
2242 nextLine = yield line
2243 return nextLine
2245 def _SubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2246 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex)
2248 if not line.StartsWith(FINISH): 2248 ↛ 2249line 2248 didn't jump to line 2249 because the condition on line 2248 was never true
2249 raise ProcessorException(f"{self.__class__.__name__}._SubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2251 if self._TIME is None:
2252 line._kind = LineKind.SubPhaseTime
2253 else:
2254 line._kind = LineKind.SubPhaseEnd
2256 line = yield line
2257 while self._TIME is not None: 2257 ↛ 2264line 2257 didn't jump to line 2264 because the condition on line 2257 was always true
2258 if line.StartsWith(self._TIME):
2259 line._kind = LineKind.SubPhaseTime
2260 break
2262 line = yield line
2264 nextLine = yield line
2265 return nextLine
2267 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2268 line = yield from self._SubPhaseStart(line)
2270 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex)
2272 while True:
2273 if line._kind is LineKind.Empty:
2274 line = yield line
2275 continue
2276 elif line.StartsWith(FINISH):
2277 break
2278 elif isinstance(line, VivadoMessage):
2279 self._AddMessage(line)
2281 line = yield line
2283 nextLine = yield from self._SubPhaseFinish(line)
2284 return nextLine
2286 def __str__(self) -> str:
2287 return f"{self.__class__.__name__}: {self._START.pattern}"
2290@export
2291class SubPhaseWithChildren(SubPhase):
2292 _subSubPhases: Dict[Type["SubSubPhase"], "SubSubPhase"]
2294 def __init__(self, phase: Phase) -> None:
2295 super().__init__(phase)
2297 self._subSubPhases = {p: p(self) for p in self._PARSERS}
2299 def __contains__(self, key: Any) -> bool:
2300 if not issubclass(key, SubSubPhase):
2301 ex = TypeError(f"Parameter 'item' is not a SubSubPhase.")
2302 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2303 raise ex
2305 return key in self._subSubPhases
2307 def __getitem__(self, key: Type["SubSubPhase"]) -> "SubSubPhase":
2308 try:
2309 return self._subSubPhases[key]
2310 except KeyError as ex:
2311 raise PhaseNotPresentException(F"SubSubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2313 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2314 line = yield from self._SubPhaseStart(line)
2316 activeParsers: List["SubSubPhase"] = list(self._subSubPhases.values())
2318 START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}."
2319 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex)
2321 while True:
2322 while True:
2323 if line._kind is LineKind.Empty:
2324 line = yield line
2325 continue
2326 elif isinstance(line, VivadoMessage):
2327 self._AddMessage(line)
2328 elif line.StartsWith(START_PREFIX):
2329 for parser in activeParsers: # type: SubSubPhase 2329 ↛ 2334line 2329 didn't jump to line 2334 because the loop on line 2329 didn't complete
2330 if (match := parser._START.match(line._message)) is not None:
2331 line = yield next(phase := parser.Generator(line))
2332 break
2333 else:
2334 WarningCollector.Raise(UnknownSubPhase(f"Unknown subsubphase: '{line!r}'", line))
2335 ex = Exception(f"How to recover from here? Unknown subsubphase: '{line!r}'")
2336 ex.add_note(f"Current subphase: start pattern='{self}'")
2337 ex.add_note(f"Current phase: start pattern='{self._phase}'")
2338 ex.add_note(f"Current task: start pattern='{self._phase._task}'")
2339 ex.add_note(f"Current cmd: {self._phase._task._command}")
2340 raise ex
2341 break
2342 elif line.StartsWith(FINISH): 2342 ↛ 2346line 2342 didn't jump to line 2346 because the condition on line 2342 was always true
2343 nextLine = yield from self._SubPhaseFinish(line)
2344 return nextLine
2346 line = yield line
2348 while True:
2349 isFinish = False # line.StartsWith("Ending")
2351 try:
2352 processedLine = phase.send(line)
2354 if isinstance(processedLine, VivadoMessage):
2355 self._AddMessage(processedLine)
2357 if isFinish: 2357 ↛ 2358line 2357 didn't jump to line 2358 because the condition on line 2357 was never true
2358 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2359 line = yield processedLine
2360 break
2361 except StopIteration as ex:
2362 activeParsers.remove(parser)
2363 line = ex.value
2364 break
2366 line = yield processedLine
2369@export
2370class SubSubPhase(BaseParser, VivadoMessagesMixin):
2371 # _NAME: ClassVar[str]
2372 # _START: ClassVar[str]
2373 # _FINISH: ClassVar[str]
2375 _subphase: SubPhase
2376 _phaseIndex: int
2377 _subPhaseIndex: int
2378 _subSubPhaseIndex: int
2379 _duration: float
2381 def __init__(self, subphase: SubPhase) -> None:
2382 super().__init__()
2383 VivadoMessagesMixin.__init__(self)
2385 self._subphase = subphase
2386 self._phaseIndex = None
2387 self._subPhaseIndex = None
2388 self._subSubPhaseIndex = None
2390 def _SubSubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2391 if (match := self._START.match(line._message)) is None: 2391 ↛ 2392line 2391 didn't jump to line 2392 because the condition on line 2391 was never true
2392 raise ProcessorException()
2394 self._phaseIndex = int(match["major"])
2395 self._subPhaseIndex = int(match["minor"])
2396 self._subSubPhaseIndex = int(match["micro"])
2398 line._kind = LineKind.SubSubPhaseStart
2399 nextLine = yield line
2400 return nextLine
2402 def _SubSubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2403 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex)
2405 if not line.StartsWith(FINISH): 2405 ↛ 2406line 2405 didn't jump to line 2406 because the condition on line 2405 was never true
2406 raise ProcessorException(f"{self.__class__.__name__}._SubSubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2408 line._kind = LineKind.SubSubPhaseEnd
2409 line = yield line
2411 while self._TIME is not None: 2411 ↛ 2418line 2411 didn't jump to line 2418 because the condition on line 2411 was always true
2412 if line.StartsWith(self._TIME):
2413 line._kind = LineKind.SubSubPhaseTime
2414 break
2416 line = yield line
2418 nextLine = yield line
2419 return nextLine
2421 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2422 line = yield from self._SubSubPhaseStart(line)
2424 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex)
2426 while True:
2427 if line._kind is LineKind.Empty:
2428 line = yield line
2429 continue
2430 elif line.StartsWith(FINISH):
2431 break
2432 elif isinstance(line, VivadoMessage):
2433 self._AddMessage(line)
2435 line = yield line
2437 nextLine = yield from self._SubSubPhaseFinish(line)
2438 return nextLine
2440 def __str__(self) -> str:
2441 return f"{self.__class__.__name__}: {self._START.pattern}"
2444@export
2445class SubSubPhaseWithChildren(SubSubPhase):
2446 _subSubSubPhases: Dict[Type["SubSubSubPhase"], "SubSubSubPhase"]
2448 def __init__(self, subphase: SubPhase) -> None:
2449 super().__init__(subphase)
2451 self._subSubSubPhases = {p: p(self) for p in self._PARSERS}
2453 def __contains__(self, key: Any) -> bool:
2454 if not issubclass(key, SubSubSubPhase):
2455 ex = TypeError(f"Parameter 'item' is not a SubSubSubPhase.")
2456 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2457 raise ex
2459 return key in self._subSubSubPhases
2461 def __getitem__(self, key: Type["SubSubSubPhase"]) -> "SubSubSubPhase":
2462 try:
2463 return self._subSubSubPhases[key]
2464 except KeyError as ex:
2465 raise PhaseNotPresentException(F"SubSubSubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2467 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2468 line = yield from self._SubSubPhaseStart(line)
2470 activeParsers: List["SubSubSubPhase"] = list(self._subSubSubPhases.values())
2472 START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}.{self._subSubPhaseIndex}."
2474 while True:
2475 while True:
2476 if line._kind is LineKind.Empty:
2477 line = yield line
2478 continue
2479 elif isinstance(line, VivadoMessage):
2480 self._AddMessage(line)
2481 elif line.StartsWith(START_PREFIX):
2482 for parser in activeParsers: # type: SubSubSubPhase 2482 ↛ 2487line 2482 didn't jump to line 2487 because the loop on line 2482 didn't complete
2483 if (match := parser._START.match(line._message)) is not None: 2483 ↛ 2482line 2483 didn't jump to line 2482 because the condition on line 2483 was always true
2484 line = yield next(phase := parser.Generator(line))
2485 break
2486 else:
2487 WarningCollector.Raise(UnknownSubPhase(f"Unknown subsubsubphase: '{line!r}'", line))
2488 ex = Exception(f"How to recover from here? Unknown subsubsubphase: '{line!r}'")
2489 ex.add_note(f"Current subsubphase: start pattern='{self}'")
2490 ex.add_note(f"Current subphase: start pattern='{self._subphase}'")
2491 ex.add_note(f"Current phase: start pattern='{self._subphase._phase}'")
2492 ex.add_note(f"Current task: start pattern='{self._subphase._phase._task}'")
2493 ex.add_note(f"Current cmd: {self._subphase._phase._task._command}")
2494 raise ex
2495 break
2496 elif line.StartsWith(self._TIME):
2497 line._kind = LineKind.SubSubPhaseTime
2498 nextLine = yield line
2499 return nextLine
2501 line = yield line
2503 while True:
2504 isFinish = False # line.StartsWith("Ending")
2506 try:
2507 processedLine = phase.send(line)
2509 if isinstance(processedLine, VivadoMessage):
2510 self._AddMessage(processedLine)
2512 if isFinish: 2512 ↛ 2513line 2512 didn't jump to line 2513 because the condition on line 2512 was never true
2513 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2514 line = yield processedLine
2515 break
2516 except StopIteration as ex:
2517 activeParsers.remove(parser)
2518 line = ex.value
2519 break
2521 line = yield processedLine
2524@export
2525class SubSubSubPhase(BaseParser, VivadoMessagesMixin):
2526 # _NAME: ClassVar[str]
2527 # _START: ClassVar[str]
2528 # _FINISH: ClassVar[str]
2530 _subsubphase: SubSubPhase
2531 _phaseIndex: int
2532 _subPhaseIndex: int
2533 _subSubPhaseIndex: int
2534 _subSubSubPhaseIndex: int
2535 _duration: float
2537 def __init__(self, subsubphase: SubSubPhase) -> None:
2538 super().__init__()
2539 VivadoMessagesMixin.__init__(self)
2541 self._subsubphase = subsubphase
2542 self._phaseIndex = None
2543 self._subPhaseIndex = None
2544 self._subSubPhaseIndex = None
2545 self._subSubSubPhaseIndex = None
2547 def _SubSubSubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2548 if (match := self._START.match(line._message)) is None: 2548 ↛ 2549line 2548 didn't jump to line 2549 because the condition on line 2548 was never true
2549 raise ProcessorException()
2551 self._phaseIndex = int(match["major"])
2552 self._subPhaseIndex = int(match["minor"])
2553 self._subSubPhaseIndex = int(match["micro"])
2554 self._subSubSubPhaseIndex = int(match["nano"])
2556 line._kind = LineKind.SubSubSubPhaseStart
2557 nextLine = yield line
2558 return nextLine
2560 def _SubSubSubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2561 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex, subSubSubPhaseIndex=self._subSubSubPhaseIndex)
2563 if not line.StartsWith(FINISH): 2563 ↛ 2564line 2563 didn't jump to line 2564 because the condition on line 2563 was never true
2564 raise ProcessorException(f"{self.__class__.__name__}._SubSubSubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2566 line._kind = LineKind.SubSubSubPhaseEnd
2567 line = yield line
2569 while self._TIME is not None: 2569 ↛ 2576line 2569 didn't jump to line 2576 because the condition on line 2569 was always true
2570 if line.StartsWith(self._TIME):
2571 line._kind = LineKind.SubSubSubPhaseTime
2572 break
2574 line = yield line
2576 nextLine = yield line
2577 return nextLine
2579 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2580 line = yield from self._SubSubSubPhaseStart(line)
2582 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex, subSubSubPhaseIndex=self._subSubSubPhaseIndex)
2584 while True:
2585 if line._kind is LineKind.Empty: 2585 ↛ 2586line 2585 didn't jump to line 2586 because the condition on line 2585 was never true
2586 line = yield line
2587 continue
2588 elif line.StartsWith(FINISH):
2589 break
2590 elif isinstance(line, VivadoMessage): 2590 ↛ 2593line 2590 didn't jump to line 2593 because the condition on line 2590 was always true
2591 self._AddMessage(line)
2593 line = yield line
2595 nextLine = yield from self._SubSubSubPhaseFinish(line)
2596 return nextLine
2598 def __str__(self) -> str:
2599 return f"{self.__class__.__name__}: {self._START.pattern}"
2602@export
2603class SubSubSubPhaseWithTasks(SubSubSubPhase):
2604 _nestedTasks: Dict[Type["NestedTask"], "NestedTask"]
2606 def __init__(self, subsubphase: SubSubPhase) -> None:
2607 super().__init__(subsubphase)
2609 self._nestedTasks = {p: p(self) for p in self._PARSERS}
2611 def __contains__(self, key: Any) -> bool:
2612 if not issubclass(key, NestedTask):
2613 ex = TypeError(f"Parameter 'key' is not a NestedTask.")
2614 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2615 raise ex
2617 return key in self._nestedTasks
2619 def __getitem__(self, key: Type["NestedTask"]) -> "NestedTask":
2620 try:
2621 return self._nestedTasks[key]
2622 except KeyError as ex:
2623 raise NestedTaskNotPresentException(F"NestedTask '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2625 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2626 line = yield from self._SubSubSubPhaseStart(line)
2628 activeParsers: List["NestedTask"] = list(self._nestedTasks.values())
2630 # START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}.{self._subSubPhaseIndex}."
2632 while True:
2633 while True:
2634 if line._kind is LineKind.Empty:
2635 line = yield line
2636 continue
2637 elif isinstance(line, VivadoMessage):
2638 self._AddMessage(line)
2639 elif line.StartsWith("Starting "):
2640 for parser in activeParsers: # type: NestedTask 2640 ↛ 2645line 2640 didn't jump to line 2645 because the loop on line 2640 didn't complete
2641 if line.StartsWith(parser._START): 2641 ↛ 2640line 2641 didn't jump to line 2640 because the condition on line 2641 was always true
2642 line = yield next(phase := parser.Generator(line))
2643 break
2644 else:
2645 WarningCollector.Raise(UnknownSubPhase(f"Unknown NestedTask: '{line!r}'", line))
2646 ex = Exception(f"How to recover from here? Unknown NestedTask: '{line!r}'")
2647 ex.add_note(f"Current subsubsubphase: start pattern='{self}'")
2648 ex.add_note(f"Current subsubphase: start pattern='{self._subsubphase}'")
2649 ex.add_note(f"Current subphase: start pattern='{self._subsubphase._subphase}'")
2650 ex.add_note(f"Current phase: start pattern='{self._subsubphase._subphase._phase}'")
2651 ex.add_note(f"Current task: start pattern='{self._subsubphase._subphase._phase._task}'")
2652 ex.add_note(f"Current cmd: {self._subsubphase._subphase._phase._task._command}")
2653 raise ex
2654 break
2655 elif line.StartsWith(self._TIME):
2656 line._kind = LineKind.SubSubSubPhaseTime
2657 nextLine = yield line
2658 return nextLine
2660 line = yield line
2662 while True:
2663 isFinish = False # line.StartsWith("Ending")
2665 try:
2666 processedLine = phase.send(line)
2668 if isinstance(processedLine, VivadoMessage):
2669 self._AddMessage(processedLine)
2671 if isFinish: 2671 ↛ 2672line 2671 didn't jump to line 2672 because the condition on line 2671 was never true
2672 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2673 line = yield processedLine
2674 break
2675 except StopIteration as ex:
2676 activeParsers.remove(parser)
2677 line = ex.value
2678 break
2680 line = yield processedLine
2683@export
2684class NestedTask(BaseParser, VivadoMessagesMixin):
2685 # _NAME: ClassVar[str]
2686 # _START: ClassVar[str]
2687 # _FINISH: ClassVar[str]
2688 # _TIME: ClassVar[str] = "Time (s):"
2690 _subsubsubphase: SubSubSubPhaseWithTasks
2691 _duration: float
2693 def __init__(self, subsubsubphase: SubSubSubPhaseWithTasks) -> None:
2694 super().__init__()
2695 VivadoMessagesMixin.__init__(self)
2697 self._subsubsubphase = subsubsubphase
2699 @readonly
2700 def SubSubSubPhase(self) -> SubSubSubPhaseWithTasks:
2701 return self._subsubsubphase
2703 def _NestedTaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2704 if not line.StartsWith(self._START): 2704 ↛ 2705line 2704 didn't jump to line 2705 because the condition on line 2704 was never true
2705 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.")
2707 line._kind = LineKind.NestedTaskStart
2708 nextLine = yield line
2709 return nextLine
2711 def _NestedTaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2712 if not line.StartsWith(self._FINISH): 2712 ↛ 2713line 2712 didn't jump to line 2713 because the condition on line 2712 was never true
2713 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.")
2715 line._kind = LineKind.NestedTaskEnd
2716 line = yield line
2718 if self._TIME is not None: 2718 ↛ 2727line 2718 didn't jump to line 2727 because the condition on line 2718 was always true
2719 while True:
2720 if line.StartsWith(self._TIME):
2721 line._kind = LineKind.TaskTime
2722 line = yield line
2723 break
2725 line = yield line
2727 return line
2729 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2730 line = yield from self._NestedTaskStart(line)
2732 while True:
2733 if line._kind is LineKind.Empty:
2734 line = yield line
2735 continue
2736 elif self._FINISH is not None and line.StartsWith("Ending"):
2737 break
2738 elif isinstance(line, VivadoMessage):
2739 self._AddMessage(line)
2740 elif line.StartsWith(self._TIME):
2741 line._kind = LineKind.TaskTime
2742 nextLine = yield line
2743 return nextLine
2745 line = yield line
2747 nextLine = yield from self._NestedTaskFinish(line)
2748 return nextLine
2750 def __str__(self) -> str:
2751 return self._NAME
2754@export
2755class NestedTaskWithPhases(NestedTask):
2756 """
2757 A task's output emitted by a Vivado command.
2759 .. rubric:: Extracted information
2761 * Vivado messages (info, warning, critical warning, error).
2762 * Nested phases
2764 .. rubric:: Example
2766 .. code-block::
2768 Phase 4.1.1.1 BUFG Insertion
2770 Starting Physical Synthesis Task
2772 Phase 1 Physical Synthesis Initialization
2773 INFO: [Physopt 32-721] Multithreading enabled for phys_opt_design using a maximum of 2 CPUs
2774 INFO: [Physopt 32-619] Estimated Timing Summary | WNS=-0.733 | TNS=-0.936 |
2775 Phase 1 Physical Synthesis Initialization | Checksum: 1818afcc0
2777 Time (s): cpu = 00:00:00 ; elapsed = 00:00:00.014 . Memory (MB): peak = 1865.645 ; gain = 0.000
2778 INFO: [Place 46-56] BUFG insertion identified 0 candidate nets. Inserted BUFG: 0, Replicated BUFG Driver: 0, Skipped due to Placement/Routing Conflicts: 0, Skipped due to Timing Degradation: 0, Skipped due to netlist editing failed: 0.
2779 Ending Physical Synthesis Task | Checksum: 22839c186
2781 Time (s): cpu = 00:00:00 ; elapsed = 00:00:00.016 . Memory (MB): peak = 1865.645 ; gain = 0.000
2782 Phase 4.1.1.1 BUFG Insertion | Checksum: 1a8cbaaf2
2783 """
2784 # _PARSERS: ClassVar[Tuple[Type["SubTask"], ...]]
2786 _nestedPhases: Dict[Type["NestedPhase"], "NestedPhase"]
2788 def __init__(self, subsubsubPhase: SubSubSubPhaseWithTasks) -> None:
2789 super().__init__(subsubsubPhase)
2791 self._nestedPhases = {p: p(self) for p in self._PARSERS}
2793 @readonly
2794 def NestedPhases(self) -> Dict[Type["NestedPhase"], "NestedPhase"]:
2795 return self._nestedPhases
2797 def __contains__(self, key: Any) -> bool:
2798 if not issubclass(key, NestedPhase):
2799 ex = TypeError(f"Parameter 'key' is not a NestedPhase.")
2800 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2801 raise ex
2803 return key in self._nestedPhases
2805 def __getitem__(self, key: Type["NestedPhase"]) -> "NestedPhase":
2806 try:
2807 return self._nestedPhases[key]
2808 except KeyError as ex:
2809 raise SubTaskNotPresentException(F"NestedPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2811 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2812 line = yield from self._NestedTaskStart(line)
2814 activeParsers: List[Phase] = list(self._nestedPhases.values())
2816 while True:
2817 while True:
2818 if line._kind is LineKind.Empty:
2819 line = yield line
2820 continue
2821 elif isinstance(line, VivadoMessage):
2822 self._AddMessage(line)
2823 elif line.StartsWith("Phase "):
2824 for parser in activeParsers: # type: NestedPhase 2824 ↛ 2829line 2824 didn't jump to line 2829 because the loop on line 2824 didn't complete
2825 if (match := parser._START.match(line._message)) is not None: 2825 ↛ 2824line 2825 didn't jump to line 2824 because the condition on line 2825 was always true
2826 line = yield next(phase := parser.Generator(line))
2827 break
2828 else:
2829 WarningCollector.Raise(UnknownSubPhase(f"Unknown NestedPhase: '{line!r}'", line))
2830 ex = Exception(f"How to recover from here? Unknown NestedPhase: '{line!r}'")
2831 ex.add_note(f"Current nestedtask: start pattern='{self}'")
2832 ex.add_note(f"Current subsubsubphase: start pattern='{self._subsubsubphase}'")
2833 ex.add_note(f"Current subsubphase: start pattern='{self._subsubsubphase._subsubphase}'")
2834 ex.add_note(f"Current subphase: start pattern='{self._subsubsubphase._subsubphase._subphase}'")
2835 ex.add_note(f"Current phase: start pattern='{self._subsubsubphase._subsubphase._subphase._phase}'")
2836 ex.add_note(f"Current task: start pattern='{self._subsubsubphase._subsubphase._subphase._phase._task}'")
2837 ex.add_note(f"Current cmd: {self._subsubsubphase._subsubphase._subphase._phase._task._command}")
2838 raise ex
2839 break
2840 elif line.StartsWith("Ending"): 2840 ↛ 2843line 2840 didn't jump to line 2843 because the condition on line 2840 was always true
2841 nextLine = yield from self._NestedTaskFinish(line)
2842 return nextLine
2843 elif line.StartsWith(self._TIME):
2844 line._kind = LineKind.TaskTime
2845 nextLine = yield line
2846 return nextLine
2848 line = yield line
2850 while True:
2851 isFinish = False # line.StartsWith("Ending") # FIXME: detect end, but time might come later
2853 try:
2854 processedLine = phase.send(line)
2856 if isinstance(processedLine, VivadoMessage):
2857 self._AddMessage(processedLine)
2859 if isFinish: 2859 ↛ 2860line 2859 didn't jump to line 2860 because the condition on line 2859 was never true
2860 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2861 line = yield processedLine
2862 break
2863 except StopIteration as ex:
2864 activeParsers.remove(parser)
2865 line = ex.value
2866 break
2868 line = yield processedLine
2871@export
2872class NestedPhase(BaseParser, VivadoMessagesMixin):
2873 # _NAME: ClassVar[str]
2874 # _START: ClassVar[str]
2875 # _FINISH: ClassVar[str]
2876 # _TIME: ClassVar[str] = "Time (s):"
2877 _FINAL: ClassVar[Nullable[str]] = None
2879 _nestedTask: NestedTaskWithPhases
2880 _phaseIndex: int
2881 _duration: float
2883 def __init__(self, nestedTask: TaskWithPhases) -> None:
2884 super().__init__()
2885 VivadoMessagesMixin.__init__(self)
2887 self._nestedTask = nestedTask
2888 self._phaseIndex = None
2890 @readonly
2891 def NestedTask(self) -> NestedTaskWithPhases:
2892 return self._nestedTask
2894 def _NestedPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2895 if (match := self._START.match(line._message)) is None: 2895 ↛ 2896line 2895 didn't jump to line 2896 because the condition on line 2895 was never true
2896 raise ProcessorException(f"{self.__class__.__name__}._PhaseStart(): Expected '{self._START}' at line {line._lineNumber}.")
2898 self._phaseIndex = int(match["major"])
2900 line._kind = LineKind.NestedPhaseStart
2901 nextLine = yield line
2902 return nextLine
2904 def _NestedPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2905 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
2906 if not line.StartsWith(FINISH): 2906 ↛ 2907line 2906 didn't jump to line 2907 because the condition on line 2906 was never true
2907 raise ProcessorException(f"{self.__class__.__name__}._PhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2909 line._kind = LineKind.NestedPhaseEnd
2910 line = yield line
2912 if self._TIME is not None: 2912 ↛ 2924line 2912 didn't jump to line 2924 because the condition on line 2912 was always true
2913 while True:
2914 if line.StartsWith(self._TIME):
2915 line._kind = LineKind.PhaseTime
2916 break
2917 elif isinstance(line, VivadoMessage): 2917 ↛ 2918line 2917 didn't jump to line 2918 because the condition on line 2917 was never true
2918 self._AddMessage(line)
2920 line = yield line
2922 line = yield line
2924 return line
2926 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2927 line = yield from self._NestedPhaseStart(line)
2929 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
2931 while True:
2932 if line._kind is LineKind.Empty: 2932 ↛ 2933line 2932 didn't jump to line 2933 because the condition on line 2932 was never true
2933 line = yield line
2934 continue
2935 elif isinstance(line, VivadoMessage):
2936 self._AddMessage(line)
2937 elif line.StartsWith(FINISH): 2937 ↛ 2940line 2937 didn't jump to line 2940 because the condition on line 2937 was always true
2938 break
2940 line = yield line
2942 nextLine = yield from self._NestedPhaseFinish(line)
2943 return nextLine
2945 def __str__(self) -> str:
2946 return f"{self.__class__.__name__}: {self._START.pattern}"
2949@export
2950class Synth_Design(CommandWithSections):
2951 """
2952 A Vivado command output parser for ``synth_design``.
2953 """
2954 from . import SynthesizeDesign as _SynthDesign
2956 _TCL_COMMAND: ClassVar[str] = "synth_design"
2957 _PARSERS: ClassVar[Tuple[Type[Section], ...]] = (
2958 _SynthDesign.RTLElaboration,
2959 _SynthDesign.HandlingCustomAttributes,
2960 _SynthDesign.ConstraintValidation,
2961 _SynthDesign.LoadingPart,
2962 _SynthDesign.ApplySetPropertyXDCConstraints,
2963 _SynthDesign.RTLComponentStatistics,
2964 _SynthDesign.RTLHierarchicalComponentStatistics,
2965 _SynthDesign.PartResourceSummary,
2966 _SynthDesign.CrossBoundaryAndAreaOptimization,
2967 _SynthDesign.ROM_RAM_DSP_SR_Retiming,
2968 _SynthDesign.ApplyingXDCTimingConstraints,
2969 _SynthDesign.TimingOptimization,
2970 _SynthDesign.TechnologyMapping,
2971 _SynthDesign.IOInsertion,
2972 _SynthDesign.FlatteningBeforeIOInsertion,
2973 _SynthDesign.FinalNetlistCleanup,
2974 _SynthDesign.RenamingGeneratedInstances,
2975 _SynthDesign.RebuildingUserHierarchy,
2976 _SynthDesign.RenamingGeneratedPorts,
2977 _SynthDesign.RenamingGeneratedNets,
2978 _SynthDesign.WritingSynthesisReport,
2979 )
2981 @readonly
2982 def HasLatches(self) -> bool:
2983 """
2984 Read-only property returning if synthesis inferred latches into the design.
2986 Latch detection is based on:
2988 * Vivado message ``synth 8-327``
2989 * Cells of lind ``LD`` listed in the *Cell Usage* report.
2991 :returns: True, if the design contains latches.
2992 """
2993 from .SynthesizeDesign import WritingSynthesisReport
2995 if (8 in self._messagesByID) and (327 in self._messagesByID[8]):
2996 return True
2998 return "LD" in self._sections[WritingSynthesisReport]._cells
3000 @readonly
3001 def Latches(self) -> List[VivadoMessage]:
3002 """
3003 Read-only property to access a list of Vivado output messages for inferred latches.
3005 :returns: A list of Vivado messages for interred latches.
3007 .. note::
3009 This returns ``[Synth 8-327]`` messages.
3011 .. code-block::
3013 WARNING: [Synth 8-327] inferring latch for variable 'Q_reg'
3014 """
3015 if 8 in self._messagesByID:
3016 if 327 in (synthMessages := self._messagesByID[8]):
3017 return [message for message in synthMessages[327]]
3019 return []
3021 @readonly
3022 def Statemachines(self) -> Dict[str, List[str]]:
3023 """
3025 :returns: undocumented
3027 .. note::
3029 INFO: [Synth 8-802] inferred FSM for state register 'State_reg' in module 'stream_Padder'
3030 INFO: [Synth 8-802] inferred FSM for state register 'RX_blk.blkRXFSM.State_reg' in module 'eth_XGEMAC_XGMII'
3031 """
3033 @readonly
3034 def DistributedRAMs(self) -> Dict[str, Any]:
3035 """
3037 :returns: undocumented
3038 """
3040 @readonly
3041 def BlockRAMs(self) -> Dict[str, Any]:
3042 """
3044 :returns: undocumented
3045 """
3048 @readonly
3049 def UltraRAMs(self) -> Dict[str, Any]:
3050 """
3052 :returns: undocumented
3053 """
3055 @readonly
3056 def ShiftRegister(self) -> Dict[str, Dict[str, Any]]:
3057 """
3059 :returns: undocumented
3061 .. note::
3063 Static Shift Register Report:
3064 Dynamic Shift Register Report:
3065 """
3067 @readonly
3068 def UndrivenPins(self) -> Dict[str, str]:
3069 """
3071 :returns: undocumented
3073 .. note::
3075 WARNING: [Synth 8-3295] tying undriven pin AXI4FullPipeLine_M2S[AWID]_inferred:in0 to constant 0
3076 """
3078 # ignored instructions
3079 # ignored ram_style WARNING: [Synth 8-5791] The ram_style = "ultra" set on RAM "ocram_sdp__parameterized4:/gInfer.ram_reg" is ignored because clocks on ports do not match.
3081 @readonly
3082 def HasBlackboxes(self) -> bool:
3083 """
3084 Read-only property returning if the design contains black-boxes.
3086 :returns: True, if the design contains black-boxes.
3087 """
3088 from .SynthesizeDesign import WritingSynthesisReport
3090 return len(self._sections[WritingSynthesisReport]._blackboxes) > 0
3092 @readonly
3093 def Blackboxes(self) -> Dict[str, int]:
3094 """
3095 Read-only property to access the dictionary of found blackbox statistics.
3097 :returns: The dictionary of found blackbox statistics.
3098 """
3099 from .SynthesizeDesign import WritingSynthesisReport
3101 return self._sections[WritingSynthesisReport]._blackboxes
3103 @readonly
3104 def Cells(self) -> Dict[str, int]:
3105 """
3106 Read-only property to access the dictionary of synthesized cell statistics.
3108 :returns: The dictionary of used cell statistics.
3109 """
3110 from .SynthesizeDesign import WritingSynthesisReport
3112 return self._sections[WritingSynthesisReport]._cells
3114 @readonly
3115 def VHDLReportMessages(self) -> List[VHDLReportMessage]:
3116 """
3117 Read-only property to access a list of Vivado output messages generated by VHDL report statement.
3119 :returns: A list of VHDL report statement outputs.
3121 .. note::
3123 This returns ``[Synth 8-6031]`` messages.
3125 .. code-block::
3127 INFO: [Synth 8-6031] RTL report: "TimingToCycles(time, freq): period=10.000000 ns -- 0.000000 fs" [C:/[...]/StopWatch/src/Utilities.pkg.vhdl:118]
3128 """
3129 if 8 in self._messagesByID:
3130 if 6031 in (synthMessages := self._messagesByID[8]):
3131 return [message for message in synthMessages[6031]]
3133 return []
3135 @readonly
3136 def VHDLAssertMessages(self) -> List[VHDLReportMessage]:
3137 """
3138 Read-only property to access a list of Vivado output messages generated by VHDL assert statement.
3140 :returns: A list of VHDL assert statement outputs.
3142 .. note::
3144 This returns ``[Synth 8-63]`` messages.
3146 .. code-block::
3148 INFO: [Synth 8-63] RTL assertion: "CLOCK_FREQ: 100.000000 ns" [C:/[...]/StopWatch/src/Debouncer.vhdl:28]
3149 """
3150 if 8 in self._messagesByID:
3151 if 63 in (synthMessages := self._messagesByID[8]):
3152 return [message for message in synthMessages[63]]
3154 return []
3156 def SectionDetector(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
3157 from .SynthesizeDesign import RTLElaboration
3159 if not (isinstance(line, VivadoTclCommand) and line._tclCommand == self._TCL_COMMAND): 3159 ↛ 3160line 3159 didn't jump to line 3160 because the condition on line 3159 was never true
3160 raise ProcessorException() # FIXME: add exception message
3162 activeParsers: List[Section] = [p(self) for p in self._PARSERS]
3163 rtlElaboration: RTLElaboration = next(p for p in activeParsers if isinstance(p, RTLElaboration))
3165 line = yield line
3166 if line == "Starting synth_design": 3166 ↛ 3169line 3166 didn't jump to line 3169 because the condition on line 3166 was always true
3167 line._kind = LineKind.Verbose
3168 else:
3169 raise ProcessorException() # FIXME: add exception message
3171 line = yield line
3172 while True:
3173 while True:
3174 if line._kind is LineKind.Empty:
3175 line = yield line
3176 continue
3177 elif isinstance(line, VivadoMessage):
3178 self._AddMessage(line)
3179 elif line.StartsWith("Start "):
3180 for parser in activeParsers: # type: Section 3180 ↛ 3195line 3180 didn't jump to line 3195 because the loop on line 3180 didn't complete
3181 if line.StartsWith(parser._START):
3182 # Found a suitable section parser.
3183 # Add section parser to list of found sections.
3184 # In case of duplicates, create a chain of psrer instances.
3185 if parser not in self._sections:
3186 self._sections.append(parser)
3187 else:
3188 parser._next = (newParser := parser.__class__(self))
3189 parser = newParser
3191 line = next(section := parser.Generator(line))
3192 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter
3193 break
3194 else:
3195 WarningCollector.Raise(UnknownSection(f"Unknown section: '{line!r}'", line))
3196 ex = Exception(f"How to recover from here? Unknown section: '{line!r}'")
3197 # ex.add_note(f"Current task: start pattern='{self._task}'")
3198 ex.add_note(f"Current cmd: {self}")
3199 raise ex
3200 break
3201 elif line.StartsWith("Starting "):
3202 if line.StartsWith(rtlElaboration._START): 3202 ↛ 3222line 3202 didn't jump to line 3222 because the condition on line 3202 was always true
3203 self._sections.append(parser := rtlElaboration)
3204 line = next(section := parser.Generator(line))
3205 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter
3206 break
3207 elif line.StartsWith(self._TCL_COMMAND):
3208 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3208 ↛ 3222line 3208 didn't jump to line 3222 because the condition on line 3208 was always true
3209 line._kind |= LineKind.Success
3211 # FIXME: use similar style like for _TIME
3212 line = yield line
3213 lastLine = yield line
3214 return lastLine
3215 elif line.StartsWith("Finished RTL Optimization Phase"):
3216 line._kind = LineKind.PhaseEnd
3217 line._previousLine._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter
3218 elif line.StartsWith("----"):
3219 if LineKind.Phase in line._previousLine._kind:
3220 line._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter
3222 line = yield line
3224 line = yield line
3226 while True:
3227 if line.StartsWith("Finished"):
3228 l = line[9:]
3229 if not (l.startswith("Flattening") or l.startswith("Final")):
3230 line = yield section.send(line)
3231 break
3233 if isinstance(line, VivadoMessage):
3234 self._AddMessage(line)
3236 line = yield section.send(line)
3238 line = yield section.send(line)
3240 if not parser._DUPLICATES:
3241 activeParsers.remove(parser)
3244@export
3245class Link_Design(Command):
3246 """
3247 A Vivado command output parser for ``link_design``.
3248 """
3249 _TCL_COMMAND: ClassVar[str] = "link_design"
3250 _TIME: ClassVar[str] = "Time (s):"
3252 _ParsingXDCFile_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\]$""")
3253 _FinishedParsingXDCFile_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\]$""")
3254 _ParsingXDCFileForCell_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\] for cell '(.*)'$""")
3255 _FinishedParsingXDCFileForCell_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\] for cell '(.*)'$""")
3257 _commonXDCFiles: Dict[Path, List[VivadoMessage]]
3258 _perCellXDCFiles: Dict[Path, Dict[str, List[VivadoMessage]]]
3260 def __init__(self, processor: "Processor") -> None:
3261 super().__init__(processor)
3263 self._commonXDCFiles = {}
3264 self._perCellXDCFiles = {}
3266 @readonly
3267 def CommonXDCFiles(self) -> Dict[Path, List[VivadoMessage]]:
3268 return self._commonXDCFiles
3270 @readonly
3271 def PerCellXDCFiles(self) -> Dict[Path, Dict[str, List[VivadoMessage]]]:
3272 return self._perCellXDCFiles
3274 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3275 line = yield from self._CommandStart(line)
3277 end = f"{self._TCL_COMMAND} "
3278 while True:
3279 if line._kind is LineKind.Empty:
3280 line = yield line
3281 continue
3282 elif isinstance(line, VivadoMessage):
3283 self._AddMessage(line)
3284 elif (match := self._ParsingXDCFile_Pattern.match(line._message)) is not None:
3285 line._kind = LineKind.Normal
3287 path = Path(match[1])
3288 self._commonXDCFiles[path] = (messages := [])
3290 line = yield line
3291 while True:
3292 if line._kind is LineKind.Empty: 3292 ↛ 3293line 3292 didn't jump to line 3293 because the condition on line 3292 was never true
3293 line = yield line
3294 continue
3295 elif isinstance(line, VivadoMessage):
3296 self._AddMessage(line)
3297 messages.append(line)
3298 elif (match := self._FinishedParsingXDCFile_Pattern.match(line._message)) is not None and path == Path(match[1]):
3299 line._kind = LineKind.Normal
3300 break
3301 elif line.StartsWith("Finished Parsing XDC File"): 3301 ↛ 3302line 3301 didn't jump to line 3302 because the condition on line 3301 was never true
3302 line._kind = LineKind.ProcessorError
3303 break
3304 elif line.StartsWith(end): 3304 ↛ 3305line 3304 didn't jump to line 3305 because the condition on line 3304 was never true
3305 break
3307 line = yield line
3308 elif (match := self._ParsingXDCFileForCell_Pattern.match(line._message)) is not None:
3309 line._kind = LineKind.Normal
3311 path = Path(match[1])
3312 cell = match[2]
3313 if path in self._perCellXDCFiles:
3314 self._perCellXDCFiles[path][cell] = (messages := [])
3315 else:
3316 self._perCellXDCFiles[path] = {cell: (messages := [])}
3318 line = yield line
3319 while True:
3320 if line._kind is LineKind.Empty: 3320 ↛ 3321line 3320 didn't jump to line 3321 because the condition on line 3320 was never true
3321 line = yield line
3322 continue
3323 elif isinstance(line, VivadoMessage):
3324 self._AddMessage(line)
3325 messages.append(line)
3326 elif (match := self._FinishedParsingXDCFileForCell_Pattern.match(line._message)) is not None and path == Path(match[1]) and cell == match[2]:
3327 line._kind = LineKind.Normal
3328 break
3329 elif line.StartsWith("Finished Parsing XDC File"): 3329 ↛ 3330line 3329 didn't jump to line 3330 because the condition on line 3329 was never true
3330 line._kind = LineKind.ProcessorError
3331 break
3332 elif line.StartsWith(end): 3332 ↛ 3333line 3332 didn't jump to line 3333 because the condition on line 3332 was never true
3333 break
3335 line = yield line
3337 if line.StartsWith(end):
3338 nextLine = yield from self._CommandFinish(line)
3339 return nextLine
3341 line = yield line
3344@export
3345class Opt_Design(CommandWithTasks):
3346 """
3347 A Vivado command output parser for ``opt_design``.
3348 """
3349 from . import OptimizeDesign as _OptDesign
3351 _TCL_COMMAND: ClassVar[str] = "opt_design"
3352 _TIME: ClassVar[str] = None
3354 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3355 _OptDesign.DRCTask,
3356 _OptDesign.CacheTimingInformationTask,
3357 _OptDesign.LogicOptimizationTask,
3358 _OptDesign.PowerOptimizationTask,
3359 _OptDesign.FinalCleanupTask,
3360 _OptDesign.NetlistObfuscationTask
3361 )
3363 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3364 line = yield from self._CommandStart(line)
3366 activeParsers: List[Task] = list(self._tasks.values())
3368 while True:
3369 while True:
3370 if line._kind is LineKind.Empty:
3371 line = yield line
3372 continue
3373 elif isinstance(line, VivadoMessage):
3374 self._AddMessage(line)
3375 elif line.StartsWith("Starting ") and not line.StartsWith("Starting Connectivity Check Task"):
3376 for parser in activeParsers: # type: Section 3376 ↛ 3381line 3376 didn't jump to line 3381 because the loop on line 3376 didn't complete
3377 if line.StartsWith(parser._START):
3378 line = yield next(task := parser.Generator(line))
3379 break
3380 else:
3381 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3382 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3383 # ex.add_note(f"Current task: start pattern='{self._task}'")
3384 ex.add_note(f"Current cmd: {self}")
3385 raise ex
3386 break
3387 elif line.StartsWith(self._TCL_COMMAND):
3388 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3388 ↛ 3397line 3388 didn't jump to line 3397 because the condition on line 3388 was always true
3389 line._kind |= LineKind.Success
3391 # FIXME: use similar style like for _TIME
3392 line = yield line
3393 lastLine = yield line
3394 return lastLine
3395 # line._kind = LineKind.Unprocessed
3397 line = yield line
3399 while True:
3400 # if line.StartsWith("Ending"):
3401 # line = yield task.send(line)
3402 # break
3404 if isinstance(line, VivadoMessage):
3405 self._AddMessage(line)
3407 try:
3408 line = yield task.send(line)
3409 except StopIteration as ex:
3410 task = None
3411 line = ex.value
3413 if isinstance(line, VivadoMessage):
3414 line = yield line
3416 break
3418 if task is not None: 3418 ↛ 3419line 3418 didn't jump to line 3419 because the condition on line 3418 was never true
3419 line = yield task.send(line)
3421 activeParsers.remove(parser)
3424@export
3425class Place_Design(CommandWithTasks):
3426 """
3427 A Vivado command output parser for ``place_design``.
3428 """
3429 from . import PlaceDesign as _PlaceDesign
3431 _TCL_COMMAND: ClassVar[str] = "place_design"
3432 _TIME: ClassVar[str] = None
3434 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3435 _PlaceDesign.PlacerTask,
3436 )
3438 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3439 line = yield from self._CommandStart(line)
3441 activeParsers: List[Task] = list(self._tasks.values())
3443 while True:
3444 while True:
3445 if line._kind is LineKind.Empty:
3446 line = yield line
3447 continue
3448 elif isinstance(line, VivadoMessage):
3449 self._AddMessage(line)
3450 elif line.StartsWith("Starting "):
3451 for parser in activeParsers: # type: Section 3451 ↛ 3456line 3451 didn't jump to line 3456 because the loop on line 3451 didn't complete
3452 if line.StartsWith(parser._START): 3452 ↛ 3451line 3452 didn't jump to line 3451 because the condition on line 3452 was always true
3453 line = yield next(task := parser.Generator(line))
3454 break
3455 else:
3456 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3457 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3458 # ex.add_note(f"Current task: start pattern='{self._task}'")
3459 ex.add_note(f"Current cmd: {self}")
3460 raise ex
3461 break
3462 elif line.StartsWith(self._TCL_COMMAND):
3463 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3463 ↛ 3472line 3463 didn't jump to line 3472 because the condition on line 3463 was always true
3464 line._kind |= LineKind.Success
3466 # FIXME: use similar style like for _TIME
3467 line = yield line
3468 lastLine = yield line
3469 return lastLine
3470 # line._kind = LineKind.Unprocessed
3472 line = yield line
3474 while True:
3475 # if line.StartsWith("Ending"):
3476 # line = yield task.send(line)
3477 # break
3479 if isinstance(line, VivadoMessage):
3480 self._AddMessage(line)
3482 try:
3483 line = yield task.send(line)
3484 except StopIteration as ex:
3485 task = None
3486 line = ex.value
3488 if isinstance(line, VivadoMessage):
3489 line = yield line
3491 break
3493 if task is not None: 3493 ↛ 3494line 3493 didn't jump to line 3494 because the condition on line 3493 was never true
3494 line = yield task.send(line)
3496 activeParsers.remove(parser)
3499@export
3500class PhyOpt_Design(CommandWithTasks):
3501 """
3502 A Vivado command output parser for ``phy_opt_design``.
3503 """
3504 from . import PhysicalOptimizeDesign as _PhyOptDesign
3506 _TCL_COMMAND: ClassVar[str] = "phys_opt_design"
3507 _TIME: ClassVar[str] = None
3509 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3510 _PhyOptDesign.InitialUpdateTimingTask,
3511 _PhyOptDesign.PhysicalSynthesisTask
3512 )
3514 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3515 line = yield from self._CommandStart(line)
3517 activeParsers: List[Task] = list(self._tasks.values())
3519 while True:
3520 while True:
3521 if line._kind is LineKind.Empty:
3522 line = yield line
3523 continue
3524 elif isinstance(line, VivadoMessage):
3525 self._AddMessage(line)
3526 elif line.StartsWith("Starting "):
3527 for parser in activeParsers: # type: Section 3527 ↛ 3532line 3527 didn't jump to line 3532 because the loop on line 3527 didn't complete
3528 if line.StartsWith(parser._START): 3528 ↛ 3527line 3528 didn't jump to line 3527 because the condition on line 3528 was always true
3529 line = yield next(task := parser.Generator(line))
3530 break
3531 else:
3532 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3533 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3534 # ex.add_note(f"Current task: start pattern='{self._task}'")
3535 ex.add_note(f"Current cmd: {self}")
3536 raise ex
3537 break
3538 elif line.StartsWith(self._TCL_COMMAND):
3539 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3539 ↛ 3548line 3539 didn't jump to line 3548 because the condition on line 3539 was always true
3540 line._kind |= LineKind.Success
3542 # FIXME: use similar style like for _TIME
3543 line = yield line
3544 lastLine = yield line
3545 return lastLine
3546 # line._kind = LineKind.Unprocessed
3548 line = yield line
3550 while True:
3551 # if line.StartsWith("Ending"):
3552 # line = yield task.send(line)
3553 # break
3555 if isinstance(line, VivadoMessage):
3556 self._AddMessage(line)
3558 try:
3559 line = yield task.send(line)
3560 except StopIteration as ex:
3561 task = None
3562 line = ex.value
3564 if isinstance(line, VivadoMessage): 3564 ↛ 3567line 3564 didn't jump to line 3567 because the condition on line 3564 was always true
3565 line = yield line
3567 break
3569 if task is not None: 3569 ↛ 3570line 3569 didn't jump to line 3570 because the condition on line 3569 was never true
3570 line = yield task.send(line)
3572 activeParsers.remove(parser)
3575@export
3576class Route_Design(CommandWithTasks):
3577 """
3578 A Vivado command output parser for ``route_design``.
3579 """
3580 from . import RouteDesign as _RouteDesign
3582 _TCL_COMMAND: ClassVar[str] = "route_design"
3583 _TIME: ClassVar[str] = "Time (s):"
3585 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3586 _RouteDesign.RoutingTask,
3587 )
3589 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3590 line = yield from self._CommandStart(line)
3592 activeParsers: List[Task] = list(self._tasks.values())
3594 while True:
3595 while True:
3596 if line._kind is LineKind.Empty:
3597 line = yield line
3598 continue
3599 elif isinstance(line, VivadoMessage):
3600 self._AddMessage(line)
3601 elif line.StartsWith("Starting "):
3602 for parser in activeParsers: # type: Section 3602 ↛ 3607line 3602 didn't jump to line 3607 because the loop on line 3602 didn't complete
3603 if line.StartsWith(parser._START): 3603 ↛ 3602line 3603 didn't jump to line 3602 because the condition on line 3603 was always true
3604 line = yield next(task := parser.Generator(line))
3605 break
3606 else:
3607 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3608 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3609 # ex.add_note(f"Current task: start pattern='{self._task}'")
3610 ex.add_note(f"Current cmd: {self}")
3611 raise ex
3612 break
3613 elif line.StartsWith(self._TCL_COMMAND):
3614 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3614 ↛ 3623line 3614 didn't jump to line 3623 because the condition on line 3614 was always true
3615 line._kind |= LineKind.Success
3617 # FIXME: use similar style like for _TIME
3618 line = yield line
3619 lastLine = yield line
3620 return lastLine
3621 # line._kind = LineKind.Unprocessed
3623 line = yield line
3625 while True:
3626 # if line.StartsWith("Ending"):
3627 # line = yield task.send(line)
3628 # break
3630 if isinstance(line, VivadoMessage):
3631 self._AddMessage(line)
3633 try:
3634 line = yield task.send(line)
3635 except StopIteration as ex:
3636 task = None
3637 line = ex.value
3639 if isinstance(line, VivadoMessage): 3639 ↛ 3640line 3639 didn't jump to line 3640 because the condition on line 3639 was never true
3640 line = yield line
3642 break
3644 if task is not None: 3644 ↛ 3645line 3644 didn't jump to line 3645 because the condition on line 3644 was never true
3645 line = yield task.send(line)
3647 activeParsers.remove(parser)
3650@export
3651class Write_Bitstream(Command):
3652 """
3653 A Vivado command output parser for ``write_bitstream``.
3654 """
3655 _TCL_COMMAND: ClassVar[str] = "write_bitstream"
3656 _TIME: ClassVar[str] = "Time (s):"
3659@export
3660class Report_DRC(Command):
3661 """
3662 A Vivado command output parser for ``report_drc``.
3663 """
3664 _TCL_COMMAND: ClassVar[str] = "report_drc"
3665 _TIME: ClassVar[str] = "Time (s):"
3668@export
3669class Report_Methodology(Command):
3670 """
3671 A Vivado command output parser for ``report_methodology``.
3672 """
3673 _TCL_COMMAND: ClassVar[str] = "report_methodology"
3674 _TIME: ClassVar[str] = None
3677@export
3678class Report_Power(Command):
3679 """
3680 A Vivado command output parser for ``report_power``.
3681 """
3682 _TCL_COMMAND: ClassVar[str] = "report_power"
3683 _TIME: ClassVar[str] = None
3686@export
3687class Processor(VivadoMessagesMixin, metaclass=ExtendedType, slots=True):
3688 """
3689 A processor for Vivado log outputs.
3691 Each output line from Vivado gets processed and converted into a :class:`ProcessedLine` objects. Such lines form a
3692 doubly-linked list.
3693 """
3694 _duration: float #: Duration of the observed process (e.g. start to end of synthesis).
3695 _processingDuration: float #: Duration for the log output processor to parse all log messages.
3697 _lines: List[VivadoLine] #: A list of processed log message lines.
3698 _preamble: Preamble #: Reference to the Vivado preamble written after tool startup.
3699 _postamble: Postamble #: Reference to the Vivado postamble written after tool startup.
3700 _commands: Dict[Type[Command], Command] #: A dictionary of processed Vivado commands.
3702 def __init__(self) -> None:
3703 """
3704 Initializes a Vivado log output processor.
3705 """
3706 super().__init__()
3708 self._duration = 0.0
3709 self._processingDuration = 0.0
3711 self._lines = []
3712 self._preamble = None
3713 self._postamble = None
3714 self._commands = {}
3716 @readonly
3717 def Lines(self) -> List[VivadoLine]:
3718 """
3719 Read-only property to access the list of processed and classified log lines (messages).
3721 :returns: A list of processed lines.
3722 """
3723 return self._lines
3725 @readonly
3726 def Preamble(self) -> Preamble:
3727 """
3728 Read-only property to access the parsed preamble information.
3730 :returns: The log output preamble.
3731 """
3732 return self._preamble
3734 @readonly
3735 def Postamble(self) -> Postamble:
3736 """
3737 Read-only property to access the parsed postamble information.
3739 :returns: The log output postamble.
3740 """
3741 return self._postamble
3743 @readonly
3744 def Commands(self) -> Dict[Type[Command], Command]:
3745 """
3746 Read-only property to access the dictionary of processed Vivado commands.
3748 :returns: The dictionary of processed Vivado commands.
3749 """
3750 return self._commands
3752 @readonly
3753 def StartDateTime(self) -> datetime:
3754 return self._preamble.StartDatetime
3756 @readonly
3757 def ExitDateTime(self) -> datetime:
3758 return self._postamble.ExitDatetime
3760 @readonly
3761 def Duration(self) -> float:
3762 """
3763 Duration of the observed process (e.g. start to end of synthesis).
3765 :returns: The observed process' execution duration in seconds.
3766 """
3767 startTime = self._preamble.StartDatetime
3768 exitTime = self._postamble.ExitDatetime
3770 return (exitTime - startTime).total_seconds()
3772 @readonly
3773 def ProcessingDuration(self) -> float:
3774 """
3775 Processing duration for the log output processor to parse all log messages.
3777 :returns: The processing duration in seconds.
3778 """
3779 return self._processingDuration
3781 def __contains__(self, key: Type[Command]) -> bool:
3782 """
3783 Returns True, if log outputs where found for the given command.
3785 :param key: Vivado command (class).
3786 :returns: True, if the Vivado command's outputs were found in log outputs.
3787 """
3788 if not issubclass(key, Command): 3788 ↛ 3789line 3788 didn't jump to line 3789 because the condition on line 3788 was never true
3789 ex = TypeError(f"Parameter 'key' is not a Command.")
3790 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
3791 raise ex
3793 return key in self._commands
3795 def __getitem__(self, key: Type[Command]) -> Command:
3796 """
3797 Access Vivado command specific log outputs and parsed data by the command.
3799 :param key: Vivado command (class) to access.
3800 :returns: A Vivado command instance with parsed log messages and extracted data.
3801 """
3802 if not issubclass(key, Command): 3802 ↛ 3803line 3802 didn't jump to line 3803 because the condition on line 3802 was never true
3803 ex = TypeError(f"Parameter 'key' is not a Command.")
3804 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
3805 raise ex
3807 try:
3808 return self._commands[key]
3809 except KeyError as ex:
3810 raise CommandNotPresentException(F"Command '{key._TCL_COMMAND}' not present in '{self._logfile}'.") from ex
3812 @readonly
3813 def IsIncompleteLog(self) -> bool:
3814 """
3815 Read-only property returning true if the processed Vivado log output is incomplete.
3817 A log can be incomplete, because:
3819 * Vivado disabled messages, because too many messages of the same kind appeared. Usually, a message type is disabled
3820 after 100 messages of that type. This is indicated by message ``[Common 17-14]``.
3822 :returns: True, if messages where silenced by Vivado.
3824 .. note::
3826 .. code-block::
3828 INFO: [Common 17-14] Message 'Synth 8-3321' appears 100 times and further instances of the messages will be
3829 disabled. Use the Tcl command set_msg_config to change the current settings.
3830 """
3831 return 17 in self._messagesByID and 14 in self._messagesByID[17]
3833 def LineClassification(self, inputStream: Iterator[Tuple[datetime, str]]) -> Generator[VivadoLine, None, None]:
3835 # Instantiate and initialize CommandFinder
3836 next(cmdFinder := self.CommandFinder())
3838 lastLine = None
3839 lineNumber = 0
3840 _errorMessage = "Unknown processing error"
3842 try:
3843 while (tup := next(inputStream)) is not None: 3843 ↛ exitline 3843 didn't return from function 'LineClassification' because the condition on line 3843 was always true
3844 timestamp, rawMessageLine = tup
3845 lineNumber += 1
3846 rawMessageLine = rawMessageLine.rstrip()
3847 errorMessage = _errorMessage
3849 if len(rawMessageLine) == 0:
3850 line = VivadoLine(lineNumber, LineKind.Empty, LineAction.Default, rawMessageLine, previousLine=lastLine)
3851 elif rawMessageLine.startswith("INFO"):
3852 if (line := VivadoInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
3853 if (line := VivadoDRCInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
3854 if (line := VivadoIrregularInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
3855 line = VivadoStuntedInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
3857 errorMessage = f"Line starting with 'INFO' was not a VivadoInfoMessage."
3858 elif rawMessageLine.startswith("WARNING"):
3859 if (line := VivadoWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
3860 if (line := VivadoDRCWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 3860 ↛ 3864line 3860 didn't jump to line 3864 because the condition on line 3860 was always true
3861 if (line := VivadoXPMWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 3861 ↛ 3862line 3861 didn't jump to line 3862 because the condition on line 3861 was never true
3862 line = VivadoStuntedWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
3864 errorMessage = f"Line starting with 'WARNING' was not a VivadoWarningMessage."
3865 elif rawMessageLine.startswith("CRITICAL WARNING"):
3866 line = VivadoCriticalWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
3868 errorMessage = f"Line starting with 'CRITICAL WARNING' was not a VivadoCriticalWarningMessage."
3869 elif rawMessageLine.startswith("ERROR"):
3870 line = VivadoErrorMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
3872 errorMessage = f"Line starting with 'ERROR' was not a VivadoErrorMessage."
3873 elif rawMessageLine.startswith("Command: "):
3874 line = VivadoTclCommand.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
3876 errorMessage = "Line starting with 'Command:' was not a VivadoTclCommand."
3877 else:
3878 line = VivadoLine(lineNumber, LineKind.Unprocessed, LineAction.Default, rawMessageLine, previousLine=lastLine)
3880 if line.StartsWith("Resolution:") and isinstance(lastLine, VivadoMessage):
3881 line._kind = LineKind.Verbose
3883 if line is None: 3883 ↛ 3885line 3883 didn't jump to line 3885 because the condition on line 3883 was never true
3884 # TODO: what to do with this line? attache to exception?
3885 line = VivadoLine(lineNumber, LineKind.ProcessorError, rawMessageLine, previousLine=lastLine)
3887 raise ClassificationException(errorMessage, lineNumber, rawMessageLine)
3889 if isinstance(line, VivadoMessage):
3890 self._AddMessage(line)
3892 line = cmdFinder.send(line)
3894 if line._kind is LineKind.ProcessorError: 3894 ↛ 3895line 3894 didn't jump to line 3895 because the condition on line 3894 was never true
3895 line = ClassificationException(errorMessage, lineNumber, rawMessageLine)
3897 # TODO: find a better solution/location to assign the timestamp.
3898 line._timestamp = timestamp
3900 self._lines.append(line)
3902 lastLine = line
3903 yield line
3905 except StopIteration:
3906 pass
3908 def CommandFinder(self) -> Generator[VivadoLine, VivadoLine, None]:
3909 self._preamble = Preamble(self)
3910 self._postamble = Postamble(self)
3912 tclProcedures = {"source"}
3914 # wait for first line
3915 line = yield
3917 # process preamble
3918 line = yield from self._preamble.Generator(line)
3920 while True:
3921 while True:
3922 if line._kind is LineKind.Empty: 3922 ↛ 3923line 3922 didn't jump to line 3923 because the condition on line 3922 was never true
3923 line = yield line
3924 continue
3925 elif isinstance(line, VivadoInfoMessage):
3926 if line.ToolID == 17 and line.MessageKindID == 206:
3927 lastLine = yield from self._postamble.Generator(line)
3928 return lastLine
3929 elif isinstance(line, VivadoTclCommand):
3930 if line._tclCommand == Synth_Design._TCL_COMMAND:
3931 self._commands[Synth_Design] = (cmd := Synth_Design(self))
3932 line = yield next(gen := cmd.SectionDetector(line))
3933 break
3934 elif line._tclCommand == Link_Design._TCL_COMMAND:
3935 self._commands[Link_Design] = (cmd := Link_Design(self))
3936 line = yield next(gen := cmd.SectionDetector(line))
3937 break
3938 elif line._tclCommand == Opt_Design._TCL_COMMAND:
3939 self._commands[Opt_Design] = (cmd := Opt_Design(self))
3940 line = yield next(gen := cmd.SectionDetector(line))
3941 break
3942 elif line._tclCommand == Place_Design._TCL_COMMAND:
3943 self._commands[Place_Design] = (cmd := Place_Design(self))
3944 line = yield next(gen := cmd.SectionDetector(line))
3945 break
3946 elif line._tclCommand == PhyOpt_Design._TCL_COMMAND:
3947 self._commands[PhyOpt_Design] = (cmd := PhyOpt_Design(self))
3948 line = yield next(gen := cmd.SectionDetector(line))
3949 break
3950 elif line._tclCommand == Route_Design._TCL_COMMAND:
3951 self._commands[Route_Design] = (cmd := Route_Design(self))
3952 line = yield next(gen := cmd.SectionDetector(line))
3953 break
3954 elif line._tclCommand == Write_Bitstream._TCL_COMMAND:
3955 self._commands[Write_Bitstream] = (cmd := Write_Bitstream(self))
3956 line = yield next(gen := cmd.SectionDetector(line))
3957 break
3958 elif line._tclCommand == Report_DRC._TCL_COMMAND:
3959 self._commands[Report_DRC] = (cmd := Report_DRC(self))
3960 line = yield next(gen := cmd.SectionDetector(line))
3961 break
3962 elif line._tclCommand == Report_Methodology._TCL_COMMAND:
3963 self._commands[Report_Methodology] = (cmd := Report_Methodology(self))
3964 line = yield next(gen := cmd.SectionDetector(line))
3965 break
3966 elif line._tclCommand == Report_Power._TCL_COMMAND:
3967 self._commands[Report_Power] = (cmd := Report_Power(self))
3968 line = yield next(gen := cmd.SectionDetector(line))
3969 break
3971 firstWord = line.Partition(" ")[0]
3972 if firstWord in tclProcedures:
3973 line = TclCommand.FromLine(line)
3975 line = yield line
3977 # end = f"{cmd._TCL_COMMAND} completed successfully"
3979 while True:
3980 # if line.StartsWith(end):
3981 # # line._kind |= LineKind.Success
3982 # lastLine = gen.send(line)
3983 # if LineKind.Last in line._kind:
3984 # line._kind ^= LineKind.Last
3985 # line = yield lastLine
3986 # break
3988 try:
3989 line = yield gen.send(line)
3990 except StopIteration as ex:
3991 line = ex.value
3992 break
3995@export
3996class Document(Processor):
3997 """
3998 A Vivado log output processor for a log file.
4000 This processor represents a Vivado log file (e.g. ``*.vds`` or ``*.vdi``). It processees its content line-by-line
4001 while classifying each line as a message. The processing duration is available via :data:`ProcessingDuration`.
4002 """
4003 _logfile: Path #: Path to the processed logfile.
4005 # FIXME: parse=True parameter
4006 def __init__(self, logfile: Path) -> None:
4007 """
4008 Initializes a log file.
4010 :param logfile: Path to the log file.
4011 """
4012 super().__init__()
4014 # FIXME: check if path
4015 self._logfile = logfile
4017 @readonly
4018 def Logfile(self) -> Path:
4019 """
4020 Read-only property to access the document's path.
4022 :returns: Path to the log file.
4023 """
4024 return self._logfile
4026 def Parse(self) -> None:
4027 with Stopwatch() as sw:
4028 timestamp = datetime.fromtimestamp(self._logfile.stat().st_mtime)
4029 with self._logfile.open("r", encoding="utf-8") as file:
4030 for line in self.LineClassification(timestampIterator(file, timestamp)):
4031 pass
4033 self._processingDuration = sw.Duration