Coverage for pyEDAA/OutputFilter/Xilinx/__init__.py: 80%
2354 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-20 22:02 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-20 22:02 +0000
1# ==================================================================================================================== #
2# _____ ____ _ _ ___ _ _ _____ _ _ _ #
3# _ __ _ _| ____| _ \ / \ / \ / _ \ _ _| |_ _ __ _ _| |_| ___(_) | |_ ___ _ __ #
4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | | __| '_ \| | | | __| |_ | | | __/ _ \ '__| #
5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| | |_| | |_| |_) | |_| | |_| _| | | | || __/ | #
6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/ \__,_|\__| .__/ \__,_|\__|_| |_|_|\__\___|_| #
7# |_| |___/ |_| #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2025-2026 Electronic Design Automation Abstraction (EDA²) #
15# #
16# Licensed under the Apache License, Version 2.0 (the "License"); #
17# you may not use this file except in compliance with the License. #
18# You may obtain a copy of the License at #
19# #
20# http://www.apache.org/licenses/LICENSE-2.0 #
21# #
22# Unless required by applicable law or agreed to in writing, software #
23# distributed under the License is distributed on an "AS IS" BASIS, #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25# See the License for the specific language governing permissions and #
26# limitations under the License. #
27# #
28# SPDX-License-Identifier: Apache-2.0 #
29# ==================================================================================================================== #
30#
31"""Basic classes for outputs from AMD/Xilinx Vivado."""
32from datetime import datetime
33from enum import Flag, Enum
34from pathlib import Path
35from re import Pattern, compile as re_compile
36from typing import Optional as Nullable, Self, Type, ClassVar, Tuple, List, Dict, Generator, Union, Any, Iterator, cast
38from pyTooling.Decorators import export, readonly
39from pyTooling.MetaClasses import ExtendedType, abstractmethod
40from pyTooling.Common import getFullyQualifiedName
41from pyTooling.Stopwatch import Stopwatch
42from pyTooling.Versioning import YearReleaseVersion
43from pyTooling.Warning import WarningCollector, Warning, CriticalWarning
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 ProcessorWarnings(Warning):
72 """
73 Base-class for warnings raised by processors parsing log outputs.
74 """
77@export
78class ProcessorCriticalWarning(CriticalWarning):
79 """
80 Base-class for critical warning raised by processors parsing log outputs.
81 """
84@export
85class ClassificationException(ProcessorException):
86 """
87 Raised if a log output line couldn't be classified.
88 """
89 _lineNumber: int #: Line number of the unclassified line.
90 _rawMessage: str #: Raw message of the unclassified line.
92 def __init__(self, errorMessage: str, lineNumber: int, rawMessageLine: str) -> None:
93 """
94 Initializes a classification exception.
96 :param errorMessage: Error message why the line couldn't be classified.
97 :param lineNumber: Line number of the unclassified line.
98 :param rawMessageLine: Raw message of the unclassified line.
99 """
100 super().__init__(errorMessage)
102 self._lineNumber = lineNumber
103 self._rawMessage = rawMessageLine
105 def __str__(self) -> str:
106 return f"{self.message}: {self._rawMessage} (at line {self._lineNumber})"
109@export
110class ParserStateException(ProcessorException):
111 """
112 Raised if a log output parser has a broken state.
113 """
116@export
117class NotPresentException(ProcessorException):
118 pass
121@export
122class CommandNotPresentException(NotPresentException):
123 pass
126@export
127class SectionNotPresentException(NotPresentException):
128 pass
131@export
132class SubSectionNotPresentException(NotPresentException):
133 pass
136@export
137class SubTaskNotPresentException(NotPresentException):
138 pass
141@export
142class PhaseNotPresentException(NotPresentException):
143 pass
146@export
147class NestedTaskNotPresentException(NotPresentException):
148 pass
151@export
152class UndetectedEnd(ProcessorCriticalWarning):
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 UnknownLine(ProcessorWarnings):
167 _line: "VivadoLine"
169 def __init__(self, message: str, line: "VivadoLine") -> None:
170 super().__init__(message)
172 self._line = line
174 @readonly
175 def Line(self) -> "VivadoLine":
176 return self._line
179@export
180class UnknownTask(UnknownLine):
181 pass
184@export
185class UnknownSubTask(UnknownLine):
186 pass
189@export
190class UnknownSection(UnknownLine):
191 pass
194@export
195class UnknownPhase(UnknownLine):
196 pass
199@export
200class UnknownSubPhase(UnknownLine):
201 pass
204@export
205class LineKind(Flag):
206 """
207 Classification of a log message line.
208 """
209 Unprocessed = 0
210 ProcessorError = 2** 0
211 Empty = 2** 1
212 Delimiter = 2** 2
214 Success = 2** 3
215 Failed = 2** 4
217 Verbose = 2**10
218 Normal = 2**11
219 Info = 2**12
220 Warning = 2**13
221 CriticalWarning = 2**14
222 Error = 2**15
223 Fatal = 2**16
225 Start = 2**20
226 End = 2**21
227 Header = 2**22
228 Content = 2**23
229 Time = 2**24
230 Footer = 2**25
232 Last = 2**28
234 DateTimeLine = 2**29
236 Message = 2**30
237 InfoMessage = Message | Info
238 WarningMessage = Message | Warning
239 CriticalWarningMessage = Message | CriticalWarning
240 ErrorMessage = Message | Error
242 Launch = 2**31
243 LaunchStart = Launch | Start
244 LaunchArguments = Launch | Header
245 LaunchFinished = Launch | End
246 LaunchTime = Launch | Time
248 Task = 2**32
249 TaskStart = Task | Start
250 TaskEnd = Task | End
251 TaskTime = Task | Time
253 Phase = 2**33
254 PhaseDelimiter = Phase | Delimiter
255 PhaseStart = Phase | Start
256 PhaseEnd = Phase | End
257 PhaseTime = Phase | Time
258 PhaseFinal = Phase | Footer
260 SubPhase = 2**34
261 SubPhaseStart = SubPhase | Start
262 SubPhaseEnd = SubPhase | End
263 SubPhaseTime = SubPhase | Time
265 SubSubPhase = 2**35
266 SubSubPhaseStart = SubSubPhase | Start
267 SubSubPhaseEnd = SubSubPhase | End
268 SubSubPhaseTime = SubSubPhase | Time
270 SubSubSubPhase = 2**36
271 SubSubSubPhaseStart = SubSubSubPhase | Start
272 SubSubSubPhaseEnd = SubSubSubPhase | End
273 SubSubSubPhaseTime = SubSubSubPhase | Time
275 NestedTask = 2**37
276 NestedTaskStart = NestedTask | Start
277 NestedTaskEnd = NestedTask | End
279 NestedPhase = 2**38
280 NestedPhaseStart = NestedPhase | Start
281 NestedPhaseEnd = NestedPhase | End
283 Section = 2**39
284 SectionDelimiter = Section | Delimiter
285 SectionStart = Section | Start
286 SectionEnd = Section | End
288 SubSection = 2**40
289 SubSectionDelimiter = SubSection | Delimiter
290 SubSectionStart = SubSection | Start
291 SubSectionEnd = SubSection | End
293 Paragraph = 2**41
294 ParagraphHeadline = Paragraph | Header
296 Hierarchy = 2**42
297 HierarchyStart = Hierarchy | Start
298 HierarchyEnd = Hierarchy | End
300 XDC = 2**43
301 XDCStart = XDC | Start
302 XDCEnd = XDC | End
304 Table = 2**44
305 TableFrame = Table | Delimiter
306 TableHeader = Table | Header
307 TableRow = Table | Content
308 TableFooter = Table | Footer
310 TclCommand = 2**45
311 GenericTclCommand = TclCommand | 2**0
312 VivadoTclCommand = TclCommand | 2**1
315@export
316class LineAction(Flag):
317 Default = 0
318 Remove = 1
321@export
322class VivadoLine(Line[LineKind, LineAction]):
323 """
324 This class represents any line in a log file.
326 A line has a line number (:attr:`_lineNumber`), a message (:attr:`__message`) and a message kind (:attr:`__kind`). In
327 addition, all line objects in a log file form a doubly
328 linked list.
329 """
330 _processor: "Processor"
331 _command: "Nullable[Command]"
333 def __init__(
334 self,
335 lineNumber: int,
336 kind: LineKind,
337 action: LineAction,
338 message: str,
339 previousLine: Nullable["VivadoLine"] = None
340 ) -> None:
341 super().__init__(lineNumber, kind, action, message, previousLine)
343 self._processor = None
344 self._command = None
346 @readonly
347 def Processor(self) -> "Processor":
348 return self._processor
350 @readonly
351 def Command(self) -> "Nullable[Command]":
352 return self._command
354 @classmethod
355 def Copy(cls, line: "VivadoLine", previousLine: "VivadoLine") -> "VivadoLine":
356 newLine = cls(line._lineNumber, line._kind, line._action, line._message, previousLine)
357 newLine._timestamp = line._timestamp
358 return newLine
361@export
362class DateTimeLine(VivadoLine):
363 _PREFIX: ClassVar[Pattern] = re_compile(r"\[(?P<datetime>\w+ \w+ ?\d{1,2} \d{1,2}:\d{1,2}:\d{1,2} \d{4})\] (?P<message>.*)")
365 _dateTime: datetime
367 def __init__(
368 self,
369 lineNumber: int,
370 kind: LineKind,
371 action: LineAction,
372 dateTime: datetime,
373 message: str,
374 previousLine: Nullable[VivadoLine] = None
375 ) -> None:
376 super().__init__(lineNumber, kind, action, message, previousLine)
378 self._dateTime = dateTime
380 @readonly
381 def DateTime(self) -> datetime:
382 return self._dateTime
384 @classmethod
385 def Copy(cls, line: "DateTimeLine", previousLine: "VivadoLine") -> "VivadoLine":
386 newLine = cls(line._lineNumber, line._kind, line._action, line._dateTime, line._message, previousLine)
387 newLine._timestamp = line._timestamp
388 return newLine
390 def __str__(self) -> str:
391 return f"[{self._dateTime:%a %b} {self._dateTime.day:2d} {self._dateTime:%H:%M:%S %Y}] {self._message}"
394@export
395class VivadoMessage(VivadoLine):
396 """
397 This class represents an AMD/Xilinx Vivado message.
399 The usual message format is:
401 .. code-block:: text
403 INFO: [Synth 8-7079] Multithreading enabled for synth_design using a maximum of 2 processes.
404 WARNING: [Synth 8-3332] Sequential element (gen[0].Sync/FF2) is unused and will be removed from module sync_Bits_Xilinx.
406 The following message severities are defined:
408 * ``INFO``
409 * ``WARNING``
410 * ``CRITICAL WARNING``
411 * ``ERROR``
413 .. seealso::
415 :class:`VivadoInfoMessage`
416 Representing a Vivado info message.
418 :class:`VivadoWarningMessage`
419 Representing a Vivado warning message.
421 :class:`VivadoCriticalWarningMessage`
422 Representing a Vivado critical warning message.
424 :class:`VivadoErrorMessage`
425 Representing a Vivado error message.
426 """
427 # _MESSAGE_KIND: ClassVar[str]
428 # _REGEXP: ClassVar[Pattern]
430 _toolName: Nullable[str]
431 _toolID: Nullable[int]
432 _messageKindID: Nullable[int]
434 def __init__(
435 self,
436 lineNumber: int,
437 kind: LineKind,
438 action: LineAction,
439 message: str,
440 toolName: Nullable[str] = None,
441 toolID: Nullable[int] = None,
442 messageKindID: Nullable[int] = None,
443 previousLine: Nullable[VivadoLine] = None
444 ) -> None:
445 super().__init__(lineNumber, kind, action, message, previousLine)
446 self._toolName = toolName
447 self._toolID = toolID
448 self._messageKindID = messageKindID
450 @readonly
451 def ToolName(self) -> Nullable[str]:
452 return self._toolName
454 @readonly
455 def ToolID(self) -> Nullable[int]:
456 return self._toolID
458 @readonly
459 def MessageKindID(self) -> Nullable[int]:
460 return self._messageKindID
462 @classmethod
463 def Parse(cls, lineNumber: int, kind: LineKind, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
464 if (match := cls._REGEXP.match(rawMessage)) is not None:
465 return cls(lineNumber, kind, LineAction.Default, match[4], match[1], int(match[2]), int(match[3]), previousLine)
467 return None
469 @classmethod
470 def Copy(cls, line: "VivadoMessage", previousLine: "VivadoLine") -> "VivadoMessage":
471 newLine = cls(line._lineNumber, line._kind, line._action, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
472 newLine._timestamp = line._timestamp
473 return newLine
475 def __str__(self) -> str:
476 return f"{self._MESSAGE_KIND}: [{self._toolName} {self._toolID}-{self._messageKindID}] {self._message}"
479@export
480class VivadoInfoMessage(VivadoMessage, InfoMessage):
481 """
482 This class represents an AMD/Xilinx Vivado info message.
484 .. rubric:: Example
486 .. code-block::
488 INFO: [Common 17-83] 66-Releasing license: Synthesis
489 """
491 _MESSAGE_KIND: ClassVar[str] = "INFO"
492 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+) (\d+)-(\d+)\] (.*)""")
494 @classmethod
495 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
496 return super().Parse(lineNumber, LineKind.InfoMessage, rawMessage, previousLine)
498 @classmethod
499 def FromMessage(cls, line: VivadoMessage) -> Self:
500 message = cls(
501 line._lineNumber,
502 LineKind.InfoMessage,
503 line._message,
504 line._toolName,
505 line._toolID,
506 line._messageKindID,
507 previousLine=line._previousLine)
508 message._nextLine = line._nextLine
509 line._nextLine._previousLine = message
511 return message
514@export
515class VivadoDRCInfoMessage(VivadoMessage, InfoMessage):
516 """
517 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) info message.
519 .. rubric:: Example
521 .. code-block::
523 INFO: [DRC AVAL-4] enum_USE_DPORT_FALSE_enum_DREG_ADREG_0_connects_CED_CEAD_RSTD_GND: i_system/xbip_dsp48_macro_0/U0/i_synth/i_synth_option.i_synth_model/opt_7series.i_uniwrap/i_primitive: DSP48E1 is not using the D port (USE_DPORT = FALSE). For improved power characteristics, set DREG and ADREG to '1', tie CED, CEAD, and RSTD to logic '0'.
524 """
526 _MESSAGE_KIND: ClassVar[str] = "INFO"
527 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[DRC (\w+)-(\d+)\] (.*)""")
529 _drcRuleName: str
531 def __init__(
532 self,
533 lineNumber: int,
534 kind: LineKind,
535 action: LineAction,
536 drcRuleName: str,
537 message: str,
538 toolName: Nullable[str] = None,
539 toolID: Nullable[int] = None,
540 messageKindID: Nullable[int] = None,
541 previousLine: Nullable[VivadoLine] = None
542 ) -> None:
543 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine)
545 self._drcRuleName = drcRuleName
547 @readonly
548 def DRCRuleName(self) -> str:
549 return self._drcRuleName
551 @classmethod
552 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
553 if (match := cls._REGEXP.match(rawMessage)) is not None:
554 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[3], toolName="DRC", toolID=None,
555 messageKindID=int(match[2]), previousLine=previousLine)
557 return None
559 @classmethod
560 def Copy(cls, line: "VivadoDRCInfoMessage", previousLine: "VivadoLine") -> "VivadoDRCInfoMessage":
561 newLine = cls(line._lineNumber, line._kind, line._action, line._drcRuleName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
562 newLine._timestamp = line._timestamp
563 return newLine
565 def __str__(self) -> str:
566 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
569@export
570class VivadoIrregularInfoMessage(VivadoMessage, InfoMessage):
571 """
572 This class represents an irregular AMD/Xilinx Vivado info message.
574 .. rubric:: Example
576 .. code-block::
578 INFO: [runtcl-4] Executing : report_io -file system_top_io_placed.rpt
579 """
581 _MESSAGE_KIND: ClassVar[str] = "INFO"
582 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+)-(\d+)\] (.*)""")
584 @classmethod
585 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
586 if (match := cls._REGEXP.match(rawMessage)) is not None:
587 return cls(lineNumber, LineKind.InfoMessage, LineAction.Default, match[3], toolName=match[1], messageKindID=int(match[2]), previousLine=previousLine)
589 return None
591 def __str__(self) -> str:
592 return f"{self._MESSAGE_KIND}: [{self._toolName}-{self._messageKindID}] {self._message}"
595@export
596class VivadoStuntedInfoMessage(VivadoMessage, InfoMessage):
597 """
598 This class represents a stunted AMD/Xilinx Vivado info message.
600 .. rubric:: Example
602 .. code-block::
604 INFO: Helper process launched with PID 29056
605 """
607 _MESSAGE_KIND: ClassVar[str] = "INFO"
608 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: ([^\[].*)""")
610 @classmethod
611 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
612 if (match := cls._REGEXP.match(rawMessage)) is not None: 612 ↛ 615line 612 didn't jump to line 615 because the condition on line 612 was always true
613 return cls(lineNumber, LineKind.InfoMessage, LineAction.Default, match[1], previousLine=previousLine)
615 return None
617 def __str__(self) -> str:
618 return f"{self._MESSAGE_KIND}: {self._message}"
621@export
622class VivadoWarningMessage(VivadoMessage, WarningMessage):
623 """
624 This class represents an AMD/Xilinx Vivado warning message.
626 .. rubric:: Example
628 .. code-block::
630 WARNING: [Synth 8-7080] Parallel synthesis criteria is not met
631 """
633 _MESSAGE_KIND: ClassVar[str] = "WARNING"
634 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(\w+(?: \w+)*?) (\d+)-(\d+)\] (.*)""")
636 @classmethod
637 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
638 return super().Parse(lineNumber, LineKind.WarningMessage, rawMessage, previousLine=previousLine)
640 @classmethod
641 def FromMessage(cls, line: VivadoMessage) -> Self:
642 message = cls(
643 line._lineNumber,
644 LineKind.WarningMessage,
645 line._message,
646 line._toolName,
647 line._toolID,
648 line._messageKindID,
649 previousLine=line._previousLine)
650 message._nextLine = line._nextLine
651 line._nextLine._previousLine = message
653 return message
656@export
657class VivadoDRCWarningMessage(VivadoMessage, WarningMessage):
658 """
659 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) warning message.
661 .. rubric:: Example
663 .. code-block::
665 WARNING: [DRC PDCN-1569] LUT equation term check: Used physical LUT pin 'A1' of cell ps/path/to/cell (pin ps/path/to/cell/I0) is not included in the LUT equation: 'O6=(A6+~A6)*((A3*A2)+(A3*(~A2)*A5)+((~A3)*A4*A5)+((~A3)*(~A4)*A2)+((~A3)*(~A4)*(~A2)*A5))'. If this cell is a user instantiated LUT in the design, please remove connectivity to the pin or change the equation and/or INIT string of the LUT to prevent this issue. If the cell is inferred or IP created LUT, please regenerate the IP and/or resynthesize the design to attempt to correct the issue.
666 """
668 _MESSAGE_KIND: ClassVar[str] = "WARNING"
669 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[DRC (\w+)-(\d+)\] (.*)""")
671 _drcRuleName: str
673 def __init__(
674 self,
675 lineNumber: int,
676 kind: LineKind,
677 action: LineAction,
678 drcRuleName: str,
679 message: str,
680 toolName: Nullable[str] = None,
681 toolID: Nullable[int] = None,
682 messageKindID: Nullable[int] = None,
683 previousLine: Nullable[VivadoLine] = None
684 ) -> None:
685 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine)
687 self._drcRuleName = drcRuleName
689 @readonly
690 def DRCRuleName(self) -> str:
691 return self._drcRuleName
693 @classmethod
694 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
695 if (match := cls._REGEXP.match(rawMessage)) is not None: 695 ↛ 696line 695 didn't jump to line 696 because the condition on line 695 was never true
696 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[3], toolName="DRC", toolID=None, messageKindID=int(match[2]), previousLine=previousLine)
698 return None
700 @classmethod
701 def Copy(cls, line: "VivadoDRCWarningMessage", previousLine: "VivadoLine") -> "VivadoDRCWarningMessage":
702 newLine = cls(line._lineNumber, line._kind, line._action, line._drcRuleName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
703 newLine._timestamp = line._timestamp
704 return newLine
706 def __str__(self) -> str:
707 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
710@export
711class VivadoXPMWarningMessage(VivadoMessage, WarningMessage):
712 """
713 This class represents an AMD/Xilinx Vivado XPM warning message.
715 .. rubric:: Example
717 .. code-block::
719 WARNING: [XPM_CDC_GRAY: TCL-1000] The source and destination clocks are the same.
720 """
722 _MESSAGE_KIND: ClassVar[str] = "WARNING"
723 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(XPM_\w+): (\w+)-(\d+)\] (.*)""")
725 _xpmName: str
727 def __init__(
728 self,
729 lineNumber: int,
730 kind: LineKind,
731 action: LineAction,
732 xpmName: str,
733 message: str,
734 toolName: Nullable[str] = None,
735 toolID: Nullable[int] = None,
736 messageKindID: Nullable[int] = None,
737 previousLine: Nullable[VivadoLine] = None
738 ) -> None:
739 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine)
741 self._xpmName = xpmName
743 @readonly
744 def XPMName(self) -> str:
745 return self._xpmName
747 @classmethod
748 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
749 if (match := cls._REGEXP.match(rawMessage)) is not None: 749 ↛ 752line 749 didn't jump to line 752 because the condition on line 749 was always true
750 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[4], toolName=match[2], toolID=None, messageKindID=int(match[3]), previousLine=previousLine)
752 return None
754 @classmethod
755 def Copy(cls, line: "VivadoXPMWarningMessage", previousLine: "VivadoLine") -> "VivadoXPMWarningMessage":
756 newLine = cls(line._lineNumber, line._kind, line._action, line._xpmName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
757 newLine._timestamp = line._timestamp
758 return newLine
760 def __str__(self) -> str:
761 return f"{self._MESSAGE_KIND}: [{self._xpmName}: {self._toolName}-{self._messageKindID}] {self._message}"
764@export
765class VivadoStuntedWarningMessage(VivadoMessage, WarningMessage):
766 """
767 This class represents a stunted AMD/Xilinx Vivado warning message.
769 .. rubric:: Example
771 .. code-block::
773 WARNING: set_property ASYNC_REG could not find object (constraint file /path/to/sync_Bits_Xilinx.xdc, line 5).
774 """
776 _MESSAGE_KIND: ClassVar[str] = "WARNING"
777 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: ([^\[].*)""")
779 @classmethod
780 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
781 if (match := cls._REGEXP.match(rawMessage)) is not None: 781 ↛ 784line 781 didn't jump to line 784 because the condition on line 781 was always true
782 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], previousLine=previousLine)
784 return None
786 def __str__(self) -> str:
787 return f"{self._MESSAGE_KIND}: {self._message}"
790@export
791class VivadoCriticalWarningMessage(VivadoMessage, CriticalWarningMessage):
792 """
793 This class represents an AMD/Xilinx Vivado critical warning message.
795 .. rubric:: Example
797 .. code-block::
799 CRITICAL WARNING: [Constraints 18-1056] Clock 'RefClkA_SFP_Quad' completely overrides clock 'USRCLKA_SFP[P]'.
800 """
802 _MESSAGE_KIND: ClassVar[str] = "CRITICAL WARNING"
803 _REGEXP: ClassVar[Pattern] = re_compile(r"""CRITICAL WARNING: \[(\w+) (\d+)-(\d+)\] (.*)""")
805 @classmethod
806 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
807 return super().Parse(lineNumber, LineKind.CriticalWarningMessage, rawMessage, previousLine)
809 @classmethod
810 def FromMessage(cls, line: VivadoMessage) -> Self:
811 message = cls(
812 line._lineNumber,
813 LineKind.CriticalWarningMessage,
814 line._message,
815 line._toolName,
816 line._toolID,
817 line._messageKindID,
818 previousLine=line._previousLine)
819 message._nextLine = line._nextLine
820 line._nextLine._previousLine = message
822 return message
825@export
826class VivadoErrorMessage(VivadoMessage, ErrorMessage):
827 """
828 This class represents an AMD/Xilinx Vivado error message.
830 .. rubric:: Example
832 .. code-block::
834 ERROR: [Memdata 28-96] Could not find a BMM_INFO_DESIGN property in the design. Could not generate the merged BMM file: C:/Users/username/git/design.runs/impl_1/system_top_bd.bmm
835 """
837 _MESSAGE_KIND: ClassVar[str] = "ERROR"
838 _REGEXP: ClassVar[Pattern] = re_compile(r"""ERROR: \[(\w+) (\d+)-(\d+)\] (.*)""")
840 @classmethod
841 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
842 return super().Parse(lineNumber, LineKind.ErrorMessage, rawMessage, previousLine)
844 @classmethod
845 def FromMessage(cls, line: VivadoMessage) -> Self:
846 message = cls(
847 line._lineNumber,
848 LineKind.ErrorMessage,
849 line._message,
850 line._toolName,
851 line._toolID,
852 line._messageKindID,
853 previousLine=line._previousLine)
854 message._nextLine = line._nextLine
855 line._nextLine._previousLine = message
857 return message
860@export
861class VHDLReportMessage(VivadoInfoMessage):
862 _REGEXP2: ClassVar[Pattern ] = re_compile(r"""RTL report: "(.*)" \[(.*):(\d+)\]""") # todo: workaround for ClassVar problem
864 _reportMessage: str
865 _sourceFile: Path
866 _sourceLineNumber: int
868 def __init__(
869 self,
870 lineNumber: int,
871 rawMessage: str,
872 toolName: str,
873 toolID: int,
874 messageKindID: int,
875 reportMessage: str,
876 sourceFile: Path,
877 sourceLineNumber: int,
878 previousLine: Nullable[VivadoLine] = None
879 ) -> None:
880 super().__init__(lineNumber, LineKind.InfoMessage, LineAction.Default, rawMessage, toolName, toolID, messageKindID, previousLine)
882 self._reportMessage = reportMessage
883 self._sourceFile = sourceFile
884 self._sourceLineNumber = sourceLineNumber
886 @classmethod
887 def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]:
888 if (match := cls._REGEXP2.match(line._message)) is not None: 888 ↛ 891line 888 didn't jump to line 891 because the condition on line 888 was always true
889 return cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, match[1], Path(match[2]), int(match[3]), previousLine=line._previousLine)
891 return None
893 @classmethod
894 def Copy(cls, line: "VHDLReportMessage", previousLine: "VivadoLine") -> "VHDLReportMessage":
895 newLine = cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, line._reportMessage, line._sourceFile, line._sourceLineNumber, previousLine)
896 newLine._timestamp = line._timestamp
897 return newLine
899@export
900class VHDLAssertionMessage(VHDLReportMessage):
901 _REGEXP3: ClassVar[Pattern ] = re_compile(r"""RTL assertion: "(.*)" \[(.*):(\d+)\]""") # todo: workaround for ClassVar problem
903 @classmethod
904 def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]:
905 if (match := cls._REGEXP3.match(line._message)) is not None: 905 ↛ 908line 905 didn't jump to line 908 because the condition on line 905 was always true
906 return cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, match[1], Path(match[2]), int(match[3]), previousLine=line._previousLine)
908 return None
911@export
912class TclCommand(VivadoLine):
913 """
914 Represents a TCL command found in a Vivado log output.
916 Besides the full log message (:class:`Line`), this class splits the TCL command into the command name and its
917 arguments.
918 """
919 _tclCommand: str
920 _arguments: Tuple[str, ...]
922 def __init__(
923 self,
924 lineNumber: int,
925 tclCommand: str,
926 arguments: Tuple[str, ...],
927 rawMessage: str,
928 previousLine: Nullable[VivadoLine] = None
929 ) -> None:
930 super().__init__(lineNumber, LineKind.GenericTclCommand, LineAction.Default, rawMessage, previousLine)
932 self._tclCommand = tclCommand
933 self._arguments = arguments
935 @readonly
936 def TCLCommand(self) -> str:
937 return self._tclCommand
939 @readonly
940 def Arguments(self) -> Tuple[str, ...]:
941 return self._arguments
943 @classmethod
944 def FromLine(cls, line: VivadoLine) -> Nullable[Self]:
945 args = line._message.split()
947 return cls(line._lineNumber, args[0], tuple(args[1:]), line._message, previousLine=line._previousLine)
949 @classmethod
950 def Copy(cls, line: "TclCommand", previousLine: VivadoLine) -> "TclCommand":
951 newLine = cls(line._lineNumber, line._tclCommand, line._arguments, line._message, previousLine)
952 newLine._timestamp = line._timestamp
953 return newLine
955 def __str__(self) -> str:
956 return f"{self._tclCommand} {' '.join(self._arguments)}"
959@export
960class VivadoTclCommand(TclCommand):
961 """
962 Represents a Vivado specific TCL command.
963 """
965 _PREFIX: ClassVar[str] = "Command:"
967 @classmethod
968 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
969 tclCommand = rawMessage[len(cls._PREFIX) + 1:]
970 args = tclCommand.split()
972 vivadoCommand = cls(lineNumber, args[0], tuple(args[1:]), rawMessage, previousLine)
973 vivadoCommand._kind = LineKind.VivadoTclCommand
974 return vivadoCommand
976 def __str__(self) -> str:
977 return f"{self._PREFIX} {self._tclCommand} {' '.join(self._arguments)}"
980@export
981class VivadoMessagesMixin(metaclass=ExtendedType, mixin=True):
982 _infoMessages: List[VivadoInfoMessage]
983 _warningMessages: List[VivadoWarningMessage]
984 _criticalWarningMessages: List[VivadoCriticalWarningMessage]
985 _errorMessages: List[VivadoErrorMessage]
986 _toolIDs: Dict[int, str]
987 _toolNames: Dict[str, int]
988 _messagesByID: Dict[int, Dict[int, List[VivadoMessage]]]
990 def __init__(self) -> None:
991 self._infoMessages = []
992 self._warningMessages = []
993 self._criticalWarningMessages = []
994 self._errorMessages = []
995 self._toolIDs = {}
996 self._toolNames = {}
997 self._messagesByID = {}
999 @readonly
1000 def ToolIDs(self) -> Dict[int, str]:
1001 return self._toolIDs
1003 @readonly
1004 def ToolNames(self) -> Dict[str, int]:
1005 return self._toolNames
1007 @readonly
1008 def MessagesByID(self) -> Dict[int, Dict[int, List[VivadoMessage]]]:
1009 return self._messagesByID
1011 @readonly
1012 def InfoMessages(self) -> List[VivadoInfoMessage]:
1013 return self._infoMessages
1015 @readonly
1016 def WarningMessages(self) -> List[VivadoWarningMessage]:
1017 return self._warningMessages
1019 @readonly
1020 def CriticalWarningMessages(self) -> List[VivadoCriticalWarningMessage]:
1021 return self._criticalWarningMessages
1023 @readonly
1024 def ErrorMessages(self) -> List[VivadoErrorMessage]:
1025 return self._errorMessages
1027 def _AddMessage(self, message: VivadoMessage) -> None:
1028 if isinstance(message, InfoMessage):
1029 self._infoMessages.append(message)
1030 elif isinstance(message, WarningMessage):
1031 self._warningMessages.append(message)
1032 elif isinstance(message, CriticalWarningMessage):
1033 self._criticalWarningMessages.append(message)
1034 elif isinstance(message, ErrorMessage): 1034 ↛ 1037line 1034 didn't jump to line 1037 because the condition on line 1034 was always true
1035 self._errorMessages.append(message)
1037 if message._toolID in self._messagesByID:
1038 sub = self._messagesByID[message._toolID]
1039 if message._messageKindID in sub:
1040 sub[message._messageKindID].append(message)
1041 else:
1042 sub[message._messageKindID] = [message]
1043 else:
1044 if message._toolID is not None:
1045 self._toolIDs[message._toolID] = message._toolName
1046 self._toolNames[message._toolName] = message._toolID
1048 self._messagesByID[message._toolID] = {message._messageKindID: [message]}
1051# todo: check usage or merge with parser
1052@export
1053class BaseParser(VivadoMessagesMixin, metaclass=ExtendedType, slots=True):
1054 def __init__(self) -> None:
1055 super().__init__()
1058@export
1059class Parser(BaseParser):
1060 _processor: "Processor"
1062 def __init__(self, processor: "Processor") -> None:
1063 super().__init__()
1065 self._processor = processor
1067 @readonly
1068 def Processor(self) -> "Processor":
1069 return self._processor
1072@export
1073class PreambleFormat(Enum):
1074 """
1075 An enumeration representing the preamble format (``Unknown``, ``Console`` or ``Logfile``).
1076 """
1078 Unknown = 0 #: Vivado was called on console in batch or interactive mode.
1079 Console = 1 #: Vivado was called on console in batch or interactive mode.
1080 Logfile = 2 #: Vivado writes a logfile.
1082 def __str__(self) -> str:
1083 """
1084 Formats the preamble format to ``console`` or ``logfile``.
1086 :returns: Formatted preamble format.
1087 """
1088 return ("unknown", "console", "logfile")[cast(int, self.value)] # TODO: check performance
1091@export
1092class Preamble(Parser, VivadoMessagesMixin):
1093 """
1094 A parser for the preamble emitted by Vivado at session start.
1096 .. rubric:: Extracted information
1098 * Vivado tool version. |br|
1099 See :data:`ToolVersion`
1100 * Session start timestamp (date and time). |br|
1101 See :data:`StartDateTime`
1103 .. rubric:: Examples
1105 .. code-block::
1107 ****** Vivado v2024.2 (64-bit)
1108 **** SW Build 5239630 on Fri Nov 08 22:35:27 MST 2024
1109 **** IP Build 5239520 on Sun Nov 10 16:12:51 MST 2024
1110 **** SharedData Build 5239561 on Fri Nov 08 14:39:27 MST 2024
1111 **** Start of session at: Wed Jul 1 23:50:26 2026
1112 ** Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
1113 ** Copyright 2022-2024 Advanced Micro Devices, Inc. All Rights Reserved.
1116 .. code-block::
1118 INFO: [Common 17-3922] A valid Vivado Design Suite ENTERPRISE license has been detected. Your current license is active and will expire on Permanent.
1119 #-----------------------------------------------------------
1120 # Vivado v2025.1 (64-bit)
1121 # SW Build 6140274 on Thu May 22 00:12:29 MDT 2025
1122 # IP Build 6138677 on Thu May 22 03:10:11 MDT 2025
1123 # SharedData Build 6139179 on Tue May 20 17:58:58 MDT 2025
1124 # Start of session at: Thu Jun 12 18:39:05 2025
1125 # Process ID : 28856
1126 # Current directory : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1
1127 # Command line : vivado.exe -log toplevel.vdi -applog -product Vivado -messageDb vivado.pb -mode batch -source toplevel.tcl -notrace
1128 # Log file : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1/toplevel.vdi
1129 # Journal file : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1\vivado.jou
1130 # Running On : Paebbels
1131 # Platform : Windows Server 2016 or Windows 10
1132 # Operating System : 26100
1133 # Processor Detail : 11th Gen Intel(R) Core(TM) i9-11950H @ 2.60GHz
1134 # CPU Frequency : 2611 MHz
1135 # CPU Physical cores : 8
1136 # CPU Logical cores : 16
1137 # Host memory : 34048 MB
1138 # Swap memory : 28991 MB
1139 # Total Virtual : 63039 MB
1140 # Available Virtual : 29246 MB
1141 #-----------------------------------------------------------
1142 """
1143 _VERSION: ClassVar[Pattern] = re_compile(r"""(?P<prefix>#|\*\*\*\*\*\*) Vivado v(?P<version>\d+\.\d(\.\d)?) \(64-bit\)""")
1144 _STARTTIME: ClassVar[Pattern] = re_compile(r"""(?P<prefix>#| \*\*\*\*) Start of session at: (?P<datetime>\w+ \w+ ?\d{1,2} \d{1,2}:\d{1,2}:\d{1,2} \d{4})""")
1146 _preambleFormat: PreambleFormat #: Format of the preamble
1147 _toolVersion: Nullable[YearReleaseVersion] #: Used Vivado version.
1148 _startDateTime: Nullable[datetime] #: Session start timestamp.
1150 def __init__(self, processor: "BaseProcessor") -> None:
1151 """
1152 Initializes a Vivado preamble parser.
1154 :param processor: Reference to the Vivado log processor.
1155 """
1156 super().__init__(processor)
1157 VivadoMessagesMixin.__init__(self)
1159 self._preambleFormat = PreambleFormat.Unknown
1160 self._toolVersion = None
1161 self._startDateTime = None
1163 @readonly
1164 def PreambleFormat(self) -> PreambleFormat:
1165 """
1166 Read-only property to access the preamble format.
1168 :returns: The detected format of the preamble.
1169 """
1170 return self._preambleFormat
1172 @readonly
1173 def ToolVersion(self) -> YearReleaseVersion:
1174 """
1175 Read-only property to access the extracted Vivado tool version.
1177 :returns: The used Vivado version as reported in the Vivado log messages.
1178 """
1179 if self._toolVersion is None: 1179 ↛ 1180line 1179 didn't jump to line 1180 because the condition on line 1179 was never true
1180 raise ProcessorException("No tool version extracted from preamble.")
1182 return self._toolVersion
1184 @readonly
1185 def StartDateTime(self) -> datetime:
1186 """
1187 Read-only property to access the date and time when the Vivado session was started.
1189 :returns: Datetime when the session was started.
1190 :raises ProcessorException: When start timestamp wasn't extracted from preamble.
1191 """
1192 if self._startDateTime is None: 1192 ↛ 1193line 1192 didn't jump to line 1193 because the condition on line 1192 was never true
1193 raise ProcessorException("No start timestamp extracted from preamble.")
1195 return self._startDateTime
1197 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1198 """
1199 A generator for processing the Vivado session preamble line-by-line.
1201 :param line: First line to process.
1202 :returns: A generator processing log messages.
1203 """
1204 delimiterCount = 0
1205 preambleFormat = PreambleFormat.Unknown
1207 # a normal preamble has up to 23 lines including both delimiter lines.
1208 for _ in range(30): 1208 ↛ 1239line 1208 didn't jump to line 1239 because the loop on line 1208 didn't complete
1209 if (match := self._VERSION.match(line._message)) is not None:
1210 preambleFormat = PreambleFormat.Logfile if match["prefix"] == "#" else PreambleFormat.Console
1211 self._toolVersion = YearReleaseVersion.Parse(match["version"])
1212 line._kind = LineKind.Normal
1213 elif (match := self._STARTTIME.match(line._message)) is not None:
1214 preambleFormat = PreambleFormat.Logfile if match["prefix"] == "#" else PreambleFormat.Console
1215 self._startDateTime = datetime.strptime(match["datetime"], "%a %b %d %H:%M:%S %Y")
1216 line._kind = LineKind.Normal
1217 elif isinstance(line, VivadoMessage):
1218 self._AddMessage(line)
1219 elif line.StartsWith("#-----"):
1220 preambleFormat = PreambleFormat.Logfile
1221 line._kind = LineKind.SectionDelimiter
1222 if (delimiterCount := delimiterCount + 1) == 2:
1223 break
1224 elif line == "":
1225 preambleFormat = PreambleFormat.Console
1226 line._kind = LineKind.SectionDelimiter
1227 if (delimiterCount := delimiterCount + 1) == 2:
1228 break
1229 else:
1230 line._kind = LineKind.Verbose
1232 if self._preambleFormat is PreambleFormat.Unknown:
1233 self._preambleFormat = preambleFormat
1234 elif self._preambleFormat is not preambleFormat: 1234 ↛ 1235line 1234 didn't jump to line 1235 because the condition on line 1234 was never true
1235 raise ProcessorException(f"Preamble format is not consistent.")
1237 line = yield line
1238 else:
1239 raise OutputFilterException(f"Preamble is longer than 30 lines or delimiter was not detected.")
1241 if self._toolVersion is None: 1241 ↛ 1242line 1241 didn't jump to line 1242 because the condition on line 1241 was never true
1242 raise OutputFilterException(f"Tool version not found in preamble.")
1243 elif self._startDateTime is None: 1243 ↛ 1244line 1243 didn't jump to line 1244 because the condition on line 1243 was never true
1244 raise OutputFilterException(f"Session start time and date not found in preamble.")
1246 nextLine = yield line
1247 return nextLine
1249 def __str__(self) -> str:
1250 return f"Vivado {self._toolVersion}: started at {self._startDateTime}"
1253@export
1254class Postamble(Parser, VivadoMessagesMixin): # todo: double mixin?
1255 """
1256 A parser for the postamble emitted by Vivado at session end.
1258 .. rubric:: Extracted information
1260 * Session exit timestamp (date and time). |br|
1261 See :data:`ExitDateTime`
1263 .. rubric:: Example
1265 .. code-block::
1267 INFO: [Common 17-206] Exiting Vivado at Tue Sep 2 08:46:23 2025...
1269 """
1270 _INFO: Tuple[int, int] = (17, 206)
1271 _ENDTIME: ClassVar[Pattern] = re_compile(r"""Exiting Vivado at (?P<datetime>\w+ \w+ ?\d{1,2} \d{1,2}:\d{1,2}:\d{1,2} \d{4})""")
1273 _exitDateTime: Nullable[datetime] #: Session exit timestamp.
1275 def __init__(self, processor: "BaseProcessor") -> None:
1276 """
1277 Initializes a Vivado postamble parser.
1279 :param processor: Reference to the Vivado log processor.
1280 """
1281 super().__init__(processor)
1282 VivadoMessagesMixin.__init__(self)
1284 self._exitDateTime = None
1286 @readonly
1287 def ExitDateTime(self) -> Nullable[datetime]:
1288 """
1289 Read-only property to access the date and time when the Vivado session was exited.
1291 :returns: Datetime when the session was exited.
1292 :raises ProcessorException: When exit timestamp wasn't extracted from postamble.
1293 """
1294 if self._exitDateTime is None: 1294 ↛ 1295line 1294 didn't jump to line 1295 because the condition on line 1294 was never true
1295 raise ProcessorException("No exit timestamp extracted from postamble.")
1297 return self._exitDateTime
1299 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1300 """
1301 A generator for processing the Vivado session preamble line-by-line.
1303 :param line: First line to process.
1304 :returns: A generator processing log messages.
1305 """
1306 if isinstance(line, VivadoMessage): 1306 ↛ 1312line 1306 didn't jump to line 1312 because the condition on line 1306 was always true
1307 self._AddMessage(line)
1309 if not isinstance(line, VivadoInfoMessage): 1309 ↛ 1310line 1309 didn't jump to line 1310 because the condition on line 1309 was never true
1310 raise ProcessorException(f"{self.__class__.__name__}.Generator(): Expected '{self._ENDTIME}' at line {line._lineNumber}.")
1312 if (match := self._ENDTIME.match(line._message)) is not None:
1313 self._exitDateTime = datetime.strptime(match["datetime"], "%a %b %d %H:%M:%S %Y")
1314 else:
1315 pass
1317 line = yield line
1319 # todo: should we receive and expect an ned-token like None?
1320 return line
1322@export
1323class Command(Parser):
1324 """
1325 This parser parses outputs from Vivado TCL commands.
1327 Depending on the command's output (and how it's implemented), they use different subcategories.
1329 .. rubric:: Command subcategories
1331 * :class:`CommandWithSections`
1332 * :class:`CommandWithtasks`
1334 .. rubric:: Supported commands
1336 * :class:`SynthesizeDesign`
1337 * :class:`LinkDesign`
1338 * :class:`OptimizeDesign`
1339 * :class:`PlaceDesign`
1340 * :class:`PhysicalOptimizeDesign`
1341 * :class:`RouteDesign`
1342 * :class:`WriteBitstream`
1343 * :class:`ReportDRC`
1344 * :class:`ReportMethodology`
1345 * :class:`ReportPower`
1347 .. rubric:: Example
1349 .. code-block::
1351 [...]
1352 Command: synth_design -top system_top -part xc7z015clg485-2
1353 Starting synth_design
1354 [...]
1355 """
1357 # _TCL_COMMAND: ClassVar[str]
1359 def _CommandStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1360 """
1361 A generator accepting a line containing the expected Vivado TCL command.
1363 When the generator exits, the returned line is the successor line to the line containing the Vivado TCL command.
1365 :param line: The first line for the generator to process.
1366 :returns: A generator processing Vivado output log lines.
1367 """
1368 if not (isinstance(line, VivadoTclCommand) and line._tclCommand == self._TCL_COMMAND): 1368 ↛ 1369line 1368 didn't jump to line 1369 because the condition on line 1368 was never true
1369 raise ProcessorException() # FIXME: add exception message
1371 nextLine = yield line
1372 return nextLine
1374 def _CommandFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1375 if line.StartsWith(f"{self._TCL_COMMAND} completed successfully"): 1375 ↛ 1378line 1375 didn't jump to line 1378 because the condition on line 1375 was always true
1376 line._kind |= LineKind.Success
1377 else:
1378 line._kind |= LineKind.Failed
1380 line = yield line
1382 if self._TIME is not None: # and self._processor._preamble._toolVersion > "2022.2":
1383 end = f"{self._TCL_COMMAND}: {self._TIME}"
1385 # while True: # TODO: limit search for time to 10 lines
1386 # if line.StartsWith(end):
1387 # line._kind = LineKind.TaskTime
1388 # line = yield line
1389 # break
1390 #
1391 # line = yield line
1393 if line.StartsWith(end):
1394 line._kind = LineKind.TaskTime
1395 line = yield line
1397 return line
1399 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, None]:
1400 line = yield from self._CommandStart(line)
1402 end = f"{self._TCL_COMMAND}"
1403 while True:
1404 if line._kind is LineKind.Empty:
1405 line = yield line
1406 continue
1407 elif isinstance(line, VivadoMessage):
1408 self._AddMessage(line)
1409 elif line.StartsWith(end):
1410 nextLine = yield from self._CommandFinish(line)
1411 return nextLine
1413 line = yield line
1415 def __str__(self) -> str:
1416 return f"{self._TCL_COMMAND}"
1419@export
1420class CommandWithSections(Command):
1421 """
1422 A Vivado command writing sections into the output log.
1424 .. rubric:: Example
1426 .. code-block::
1428 [...]
1429 ---------------------------------------------------------------------------------
1430 Starting RTL Elaboration : Time (s): cpu = 00:00:03 ; elapsed = 00:00:03 . Memory (MB): peak = 847.230 ; gain = 176.500
1431 ---------------------------------------------------------------------------------
1432 INFO: [Synth 8-638] synthesizing module 'system_top' [C:/Users/tgomes/git/2019_1/src/system_top_PE1.vhd:257]
1433 [...]
1434 [...]
1435 [...]
1436 ---------------------------------------------------------------------------------
1437 Finished RTL Elaboration : Time (s): cpu = 00:00:04 ; elapsed = 00:00:04 . Memory (MB): peak = 917.641 ; gain = 246.910
1438 ---------------------------------------------------------------------------------
1439 [...]
1440 """
1441 # _PARSERS: ClassVar[Tuple[Type[Section], ...]]
1443 _sections: List["Section"] # Dict[Type["Section"], "Section"]
1446 def __init__(self, processor: "Processor") -> None:
1447 super().__init__(processor)
1449 self._sections = [] # p: p(self) for p in self._PARSERS}
1451 @readonly
1452 def Sections(self) -> List["Section"]: # Dict[Type["Section"], "Section"]:
1453 """
1454 Read-only property to access a dictionary of found sections within the TCL command's output.
1456 :returns: A dictionary of found :class:`~pyEDAA.OutputFilter.Xilinx.SynthesizeDesign.Section`s.
1457 """
1458 return self._sections
1460 def __contains__(self, key: Any) -> bool:
1461 if not issubclass(key, Section): 1461 ↛ 1462line 1461 didn't jump to line 1462 because the condition on line 1461 was never true
1462 ex = TypeError(f"Parameter 'key' is not a Section.")
1463 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1464 raise ex
1466 for section in self._sections: 1466 ↛ 1470line 1466 didn't jump to line 1470 because the loop on line 1466 didn't complete
1467 if isinstance(section, key): 1467 ↛ 1466line 1467 didn't jump to line 1466 because the condition on line 1467 was always true
1468 return True
1469 else:
1470 return False
1472 def __getitem__(self, key: Type["Section"]) -> "Section":
1473 if not issubclass(key, Section): 1473 ↛ 1474line 1473 didn't jump to line 1474 because the condition on line 1473 was never true
1474 ex = TypeError(f"Parameter 'key' is not a Section.")
1475 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1476 raise ex
1478 for section in self._sections: 1478 ↛ 1482line 1478 didn't jump to line 1482 because the loop on line 1478 didn't complete
1479 if isinstance(section, key):
1480 return section
1481 else:
1482 raise SectionNotPresentException(F"Section '{key._NAME}' not present in '{self._parent.logfile}'.")
1485@export
1486class CommandWithTasks(Command):
1487 """
1488 A Vivado command writing tasks into the output log.
1490 .. rubric:: Example
1492 .. code-block::
1494 [...]
1495 Starting Cache Timing Information Task
1496 INFO: [Timing 38-35] 79-Done setting XDC timing constraints.
1497 [...]
1498 [...]
1499 Ending Cache Timing Information Task | Checksum: 19fe8cb97
1500 [...]
1501 """
1502 # _PARSERS: Tuple[Type[Task], ...]
1504 _tasks: Dict[Type["Task"], "Task"]
1506 def __init__(self, processor: "Processor") -> None:
1507 super().__init__(processor)
1509 self._tasks = {p: p(self) for p in self._PARSERS}
1511 @readonly
1512 def Tasks(self) -> Dict[Type["Task"], "Task"]:
1513 """
1514 Read-only property to access a dictionary of found tasks within the TCL command's output.
1516 :returns: A dictionary of found :class:`~pyEDAA.OutputFilter.Xilinx.Common2.Task`s.
1517 """
1518 return self._tasks
1520 def __contains__(self, key: Any) -> bool:
1521 if not issubclass(key, Task): 1521 ↛ 1522line 1521 didn't jump to line 1522 because the condition on line 1521 was never true
1522 ex = TypeError(f"Parameter 'key' is not a Task.")
1523 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1524 raise ex
1526 return key in self._tasks
1528 def __getitem__(self, key: Type["Task"]) -> "Task":
1529 try:
1530 return self._tasks[key]
1531 except KeyError as ex:
1532 raise SectionNotPresentException(F"Task '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
1535@export
1536class BaseSection(metaclass=ExtendedType, mixin=True):
1537 @abstractmethod
1538 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1539 pass
1541 @abstractmethod
1542 def _SectionFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
1543 pass
1545 @abstractmethod
1546 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1547 pass
1550@export
1551class Section(BaseParser, BaseSection):
1552 """
1553 Base-class for sections within log outputs from *synthesize design*.
1554 """
1555 # _NAME: ClassVar[str]
1556 # _START: ClassVar[str]
1557 # _FINISH: ClassVar[str]
1558 # _DUPLICATES: ClassVar[bool]
1560 _command: "Command" #: Reference to the command (parent).
1561 _next: Nullable["Section"]
1562 _duration: float #: Duration synthesis spent in processing a synthesis step logged in this log output section.
1564 def __init__(self, command: "Command") -> None:
1565 """
1566 Initialized a section.
1568 :param command: Reference to the parent TCL command.
1569 """
1570 super().__init__() #command._processor)
1572 self._command = command
1573 self._next = None
1574 self._duration = 0.0
1576 @readonly
1577 def Next(self) -> Nullable["Section"]:
1578 """
1579 Read-only property to access the next section in case the section appeared multiple times.
1581 :returns: Next section of same type.
1582 """
1583 return self._next
1585 @readonly
1586 def Duration(self) -> float:
1587 """
1588 Read-only property to access the duration synthesis spent in processing a synthesis step logged in this log output
1589 section.
1591 :returns: Synthesis step duration in seconds.
1592 """
1593 return self._duration
1595 def __iter__(self) -> Generator["Section", None, None]:
1596 section = self._next
1597 while section is not None:
1598 yield section
1600 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1601 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter
1602 line._kind = LineKind.SectionStart
1604 line = yield line
1605 if line.StartsWith("----"): 1605 ↛ 1608line 1605 didn't jump to line 1608 because the condition on line 1605 was always true
1606 line._kind = LineKind.SectionStart | LineKind.SectionDelimiter
1607 else:
1608 line._kind |= LineKind.ProcessorError
1610 nextLine = yield line
1611 return nextLine
1613 def _SectionFinish(self, line: VivadoLine, skipDashes: bool = False) -> Generator[VivadoLine, VivadoLine, None]:
1614 if not skipDashes:
1615 if line.StartsWith("----"): 1615 ↛ 1618line 1615 didn't jump to line 1618 because the condition on line 1615 was always true
1616 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
1617 else:
1618 line._kind |= LineKind.ProcessorError
1620 line = yield line
1622 if line.StartsWith(self._FINISH): 1622 ↛ 1625line 1622 didn't jump to line 1625 because the condition on line 1622 was always true
1623 line._kind = LineKind.SectionEnd
1624 else:
1625 line._kind |= LineKind.ProcessorError
1627 line = yield line
1628 if line.StartsWith("----"): 1628 ↛ 1631line 1628 didn't jump to line 1631 because the condition on line 1628 was always true
1629 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
1630 else:
1631 line._kind |= LineKind.ProcessorError
1633 nextLine = yield line
1634 return nextLine
1636 # @mustoverride
1637 # def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
1638 # if len(line) == 0:
1639 # return ProcessingState.EmptyLine
1640 # elif line.startswith("----"):
1641 # return ProcessingState.DelimiterLine
1642 # elif line.startswith(self._START):
1643 # return ProcessingState.Skipped
1644 # elif line.startswith(self._FINISH):
1645 # l = line[len(self._FINISH):]
1646 # if (match := TIME_MEMORY_PATTERN.match(l)) is not None:
1647 # # cpuParts = match[1].split(":")
1648 # elapsedParts = match[2].split(":")
1649 # # peakMemory = float(match[3])
1650 # # gainMemory = float(match[4])
1651 # self._duration = int(elapsedParts[0]) * 3600 + int(elapsedParts[1]) * 60 + int(elapsedParts[2])
1652 #
1653 # return ProcessingState.Skipped | ProcessingState.Last
1654 # elif line.startswith("Start") or line.startswith("Starting"):
1655 # print(f"ERROR: didn't find finish\n {line}")
1656 # return ProcessingState.Reprocess
1657 #
1658 # return ProcessingState.Skipped
1660 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1661 line = yield from self._SectionStart(line)
1663 while True:
1664 if line._kind is LineKind.Empty: 1664 ↛ 1665line 1664 didn't jump to line 1665 because the condition on line 1664 was never true
1665 line = yield line
1666 continue
1667 elif line.StartsWith("----"):
1668 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
1669 break
1670 elif isinstance(line, VivadoMessage):
1671 self._AddMessage(line)
1672 else:
1673 line._kind = LineKind.Verbose
1675 line = yield line
1677 # line = yield line
1678 nextLine = yield from self._SectionFinish(line)
1679 return nextLine
1682@export
1683class SubSection(BaseParser, BaseSection):
1684 """
1685 Base-class for subsections within log outputs from *synthesize design*.
1686 """
1687 # _NAME: ClassVar[str]
1689 _section: Section #: Reference to the section (parent).
1691 def __init__(self, section: Section) -> None:
1692 """
1693 Initialized a subsection.
1695 :param section: Reference to the parent section.
1696 """
1697 super().__init__()
1698 self._section = section
1700 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1701 line._kind = LineKind.SubSectionStart
1703 line = yield line
1704 if line.StartsWith("----"): 1704 ↛ 1707line 1704 didn't jump to line 1707 because the condition on line 1704 was always true
1705 line._kind = LineKind.SubSectionStart | LineKind.SubSectionDelimiter
1706 else:
1707 line._kind |= LineKind.ProcessorError
1709 nextLine = yield line
1710 return nextLine
1712 def _SectionFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
1713 if line.StartsWith("----"): 1713 ↛ 1716line 1713 didn't jump to line 1716 because the condition on line 1713 was always true
1714 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter
1715 else:
1716 line._kind |= LineKind.ProcessorError
1718 line = yield line
1719 if line.StartsWith(self._FINISH): 1719 ↛ 1722line 1719 didn't jump to line 1722 because the condition on line 1719 was always true
1720 line._kind = LineKind.SubSectionEnd
1721 else:
1722 line._kind |= LineKind.ProcessorError
1724 line = yield line
1725 if line.StartsWith("----"): 1725 ↛ 1728line 1725 didn't jump to line 1728 because the condition on line 1725 was always true
1726 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter
1727 else:
1728 line._kind |= LineKind.ProcessorError
1730 nextLine = yield line
1731 return nextLine
1733 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1734 line = yield from self._SectionStart(line)
1736 while True:
1737 if line._kind is LineKind.Empty: 1737 ↛ 1738line 1737 didn't jump to line 1738 because the condition on line 1737 was never true
1738 line = yield line
1739 continue
1740 elif line.StartsWith("----"): 1740 ↛ 1743line 1740 didn't jump to line 1743 because the condition on line 1740 was always true
1741 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter
1742 break
1743 elif isinstance(line, VivadoMessage):
1744 self._AddMessage(line)
1745 else:
1746 line._kind = LineKind.Verbose
1748 line = yield line
1750 nextLine = yield from self._SectionFinish(line)
1751 return nextLine
1754@export
1755class SectionWithChildren(Section):
1756 """
1757 Base-class for sections with subsections.
1758 """
1759 _subsections: Dict[Type[SubSection], SubSection]
1761 def __init__(self, command: "Command") -> None:
1762 super().__init__(command)
1764 self._subsections = {}
1766 def __contains__(self, key: Any) -> bool:
1767 if not issubclass(key, SubSection): 1767 ↛ 1768line 1767 didn't jump to line 1768 because the condition on line 1767 was never true
1768 ex = TypeError(f"Parameter 'item' is not a SubSection.")
1769 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1770 raise ex
1772 return key in self._subsections
1774 def __getitem__(self, item: Type[SubSection]) -> SubSection:
1775 try:
1776 return self._subsections[item]
1777 except KeyError as ex:
1778 raise SubSectionNotPresentException(f"SubSection '{item._NAME}' not present in '{self._parent._parent.logfile}'.") from ex
1781@export
1782class Task(BaseParser, VivadoMessagesMixin):
1783 """
1784 A task's output emitted by a Vivado command.
1786 .. rubric:: Extracted information
1788 * Vivado messages (info, warning, critical warning, error).
1790 .. rubric:: Example
1792 .. code-block::
1794 Starting Cache Timing Information Task
1795 INFO: [Timing 38-35] 79-Done setting XDC timing constraints.
1796 Ending Cache Timing Information Task | Checksum: 19fe8cb97
1798 Time (s): cpu = 00:00:09 ; elapsed = 00:00:09 . Memory (MB): peak = 1370.594 ; gain = 493.266
1800 """
1801 # _NAME: ClassVar[str]
1802 # _START: ClassVar[str]
1803 # _FINISH: ClassVar[str]
1804 _TIME: ClassVar[str] = "Time (s):"
1806 _command: "Command" #: Reference to the command (parent).
1807 _duration: float #: Duration of a task according to reported times by Vivado.
1809 def __init__(self, command: "Command") -> None:
1810 """
1811 Initializes a task (without child elements).
1813 :param command: Reference to the command.
1814 """
1815 super().__init__()
1816 VivadoMessagesMixin.__init__(self)
1818 self._command = command
1820 @readonly
1821 def Command(self) -> "Command":
1822 """
1823 Read-only property to access the command.
1825 :returns: The command this task's output was logged for.
1826 """
1827 return self._command
1829 def _TaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1830 """
1831 A generator for processing a task start (single line).
1833 :param line: First line to process (task start).
1834 :returns: A generator processing log messages.
1835 :raises ProcessorException: If first line doesn't conform to the *task start* pattern.
1836 """
1837 if not line.StartsWith(self._START): 1837 ↛ 1838line 1837 didn't jump to line 1838 because the condition on line 1837 was never true
1838 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.")
1840 line._kind = LineKind.TaskStart
1841 nextLine = yield line
1842 return nextLine
1844 def _TaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1845 """
1846 A generator for processing a task finish line-by-line.
1848 :param line: First line to process (task finish).
1849 :returns: A generator processing log messages.
1850 :raises ProcessorException: If finish line doesn't conform to the *task finish* pattern.
1851 """
1852 if not line.StartsWith(self._FINISH): 1852 ↛ 1853line 1852 didn't jump to line 1853 because the condition on line 1852 was never true
1853 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.")
1855 line._kind = LineKind.TaskEnd
1856 line = yield line
1857 while self._TIME is not None: # TODO: limit search for time pattern to XX lines 1857 ↛ 1864line 1857 didn't jump to line 1864 because the condition on line 1857 was always true
1858 if line.StartsWith(self._TIME):
1859 line._kind = LineKind.TaskTime
1860 break
1862 line = yield line
1864 nextLine = yield line
1865 return nextLine
1867 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1868 """
1869 A generator for processing a task without child elements line-by-line.
1871 .. rubric:: Algorithm
1873 1. Send first line to :meth:`_TaskStart`.
1874 2. Process body lines
1876 * Collect Vivado messages (info, warning, critical warning, error).
1877 * Check for *task finish* pattern.
1878 * Check for *time* pattern.
1880 3. Send last lines to :meth:`_TaskFinish`.
1882 :param line: First line to process.
1883 :returns: A generator processing log messages.
1884 """
1885 line = yield from self._TaskStart(line)
1887 while True:
1888 if line._kind is LineKind.Empty:
1889 line = yield line
1890 continue
1891 elif self._FINISH is not None and line.StartsWith("Ending"):
1892 break
1893 elif isinstance(line, VivadoMessage):
1894 self._AddMessage(line)
1895 elif line.StartsWith(self._TIME):
1896 line._kind = LineKind.TaskTime
1897 nextLine = yield line
1898 return nextLine
1900 line = yield line
1902 nextLine = yield from self._TaskFinish(line)
1903 return nextLine
1905 def __str__(self) -> str:
1906 return f"{self.__class__.__name__}: {self._START}"
1909@export
1910class TaskWithSubTasks(Task):
1911 """
1912 A task's output emitted by a Vivado command.
1914 .. rubric:: Extracted information
1916 * Vivado messages (info, warning, critical warning, error).
1917 * Subtasks
1919 .. rubric:: Example
1921 .. code-block::
1923 Starting Cache Timing Information Task
1924 INFO: [Timing 38-35] 79-Done setting XDC timing constraints.
1925 Ending Cache Timing Information Task | Checksum: 19fe8cb97
1927 Time (s): cpu = 00:00:09 ; elapsed = 00:00:09 . Memory (MB): peak = 1370.594 ; gain = 493.266
1929 """
1930 # _PARSERS: ClassVar[Tuple[Type["SubTask"], ...]]
1932 _subtasks: Dict[Type["SubTask"], "SubTask"]
1934 def __init__(self, command: "Command") -> None:
1935 super().__init__(command)
1937 self._subtasks = {p: p(self) for p in self._PARSERS}
1939 @readonly
1940 def SubTasks(self) -> Dict[Type["SubTask"], "SubTask"]:
1941 return self._subtasks
1943 def __contains__(self, key: Any) -> bool:
1944 if not issubclass(key, SubTask): 1944 ↛ 1945line 1944 didn't jump to line 1945 because the condition on line 1944 was never true
1945 ex = TypeError(f"Parameter 'key' is not a Subtask.")
1946 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1947 raise ex
1949 return key in self._subtasks
1951 def __getitem__(self, key: Type["SubTask"]) -> "SubTask":
1952 try:
1953 return self._subtasks[key]
1954 except KeyError as ex:
1955 raise SubTaskNotPresentException(F"Subtask '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
1957 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1958 line = yield from self._TaskStart(line)
1960 activeParsers: List[Phase] = list(self._subtasks.values())
1962 while True:
1963 while True:
1964 if line._kind is LineKind.Empty:
1965 line = yield line
1966 continue
1967 elif isinstance(line, VivadoMessage):
1968 self._AddMessage(line)
1969 elif line.StartsWith("Starting "):
1970 for parser in activeParsers: # type: SubTask 1970 ↛ 1975line 1970 didn't jump to line 1975 because the loop on line 1970 didn't complete
1971 if line.StartsWith(parser._START): 1971 ↛ 1970line 1971 didn't jump to line 1970 because the condition on line 1971 was always true
1972 line = yield next(subtask := parser.Generator(line))
1973 break
1974 else:
1975 WarningCollector.Raise(UnknownSubTask(f"Unknown subtask: '{line!r}'", line))
1976 ex = Exception(f"How to recover from here? Unknown subtask: '{line!r}'")
1977 ex.add_note(f"Current task: start pattern='{self}'")
1978 ex.add_note(f"Current command: {self._command}")
1979 raise ex
1980 break
1981 elif line.StartsWith("Ending"):
1982 nextLine = yield from self._TaskFinish(line)
1983 return nextLine
1984 elif line.StartsWith(self._TIME): 1984 ↛ 1985line 1984 didn't jump to line 1985 because the condition on line 1984 was never true
1985 line._kind = LineKind.TaskTime
1986 nextLine = yield line
1987 return nextLine
1989 line = yield line
1991 while True:
1992 isFinish = False # line.StartsWith("Ending") # FIXME: detect end, but time might come later
1994 try:
1995 processedLine = subtask.send(line)
1997 if isinstance(processedLine, VivadoMessage):
1998 self._AddMessage(processedLine)
2000 if isFinish: 2000 ↛ 2001line 2000 didn't jump to line 2001 because the condition on line 2000 was never true
2001 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2002 line = yield processedLine
2003 break
2004 except StopIteration as ex:
2005 activeParsers.remove(parser)
2006 line = ex.value
2007 break
2009 line = yield processedLine
2012@export
2013class SubTask(BaseParser, VivadoMessagesMixin):
2014 # _NAME: ClassVar[str]
2015 # _START: ClassVar[str]
2016 # _FINISH: ClassVar[str]
2017 _TIME: ClassVar[str] = "Time (s):"
2019 _task: TaskWithSubTasks
2020 _duration: float
2022 def __init__(self, task: TaskWithSubTasks) -> None:
2023 super().__init__()
2024 VivadoMessagesMixin.__init__(self)
2026 self._task = task
2028 @readonly
2029 def Task(self) -> TaskWithSubTasks:
2030 return self._task
2032 def _TaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2033 if not line.StartsWith(self._START): 2033 ↛ 2034line 2033 didn't jump to line 2034 because the condition on line 2033 was never true
2034 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.")
2036 line._kind = LineKind.TaskStart
2037 nextLine = yield line
2038 return nextLine
2040 def _TaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2041 if not line.StartsWith(self._FINISH): 2041 ↛ 2042line 2041 didn't jump to line 2042 because the condition on line 2041 was never true
2042 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.")
2044 line._kind = LineKind.TaskEnd
2045 line = yield line
2046 while self._TIME is not None: 2046 ↛ 2053line 2046 didn't jump to line 2053 because the condition on line 2046 was always true
2047 if line.StartsWith(self._TIME):
2048 line._kind = LineKind.TaskTime
2049 break
2051 line = yield line
2053 nextLine = yield line
2054 return nextLine
2056 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2057 line = yield from self._TaskStart(line)
2059 while True:
2060 if line._kind is LineKind.Empty: 2060 ↛ 2061line 2060 didn't jump to line 2061 because the condition on line 2060 was never true
2061 line = yield line
2062 continue
2063 elif self._FINISH is not None and line.StartsWith("Ending"):
2064 break
2065 elif isinstance(line, VivadoMessage):
2066 self._AddMessage(line)
2067 elif line.StartsWith(self._TIME): 2067 ↛ 2068line 2067 didn't jump to line 2068 because the condition on line 2067 was never true
2068 line._kind = LineKind.TaskTime
2069 nextLine = yield line
2070 return nextLine
2072 line = yield line
2074 nextLine = yield from self._TaskFinish(line)
2075 return nextLine
2077 def __str__(self) -> str:
2078 return self._NAME
2081@export
2082class TaskWithPhases(Task):
2083 # _PARSERS: ClassVar[Tuple[Type["Phase"], ...]]
2085 _phases: Dict[Type["Phase"], "Phase"]
2087 def __init__(self, command: "Command") -> None:
2088 super().__init__(command)
2090 self._phases = {p: p(self) for p in self._PARSERS}
2092 @readonly
2093 def Phases(self) -> Dict[Type["Phase"], "Phase"]:
2094 return self._phases
2096 def __contains__(self, key: Any) -> bool:
2097 if not issubclass(key, Phase): 2097 ↛ 2098line 2097 didn't jump to line 2098 because the condition on line 2097 was never true
2098 ex = TypeError(f"Parameter 'key' is not a Phase.")
2099 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2100 raise ex
2102 return key in self._phases
2104 def __getitem__(self, key: Type["Phase"]) -> "Phase":
2105 try:
2106 return self._phases[key]
2107 except KeyError as ex:
2108 raise PhaseNotPresentException(F"Phase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2110 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2111 line = yield from self._TaskStart(line)
2113 activeParsers: List[Phase] = list(self._phases.values())
2115 while True:
2116 while True:
2117 if line._kind is LineKind.Empty:
2118 line = yield line
2119 continue
2120 elif isinstance(line, VivadoMessage):
2121 self._AddMessage(line)
2122 elif line.StartsWith("Phase "):
2123 for parser in activeParsers: # type: Phase 2123 ↛ 2128line 2123 didn't jump to line 2128 because the loop on line 2123 didn't complete
2124 if (match := parser._START.match(line._message)) is not None:
2125 line = yield next(phase := parser.Generator(line))
2126 break
2127 else:
2128 WarningCollector.Raise(UnknownPhase(f"Unknown phase: '{line!r}'", line))
2129 ex = Exception(f"How to recover from here? Unknown phase: '{line!r}'")
2130 ex.add_note(f"Current task: start pattern='{self}'")
2131 ex.add_note(f"Current command: {self._command}")
2132 raise ex
2133 break
2134 elif line.StartsWith("Ending"):
2135 nextLine = yield from self._TaskFinish(line)
2136 return nextLine
2137 elif line.StartsWith(self._TIME):
2138 line._kind = LineKind.TaskTime
2139 nextLine = yield line
2140 return nextLine
2142 line = yield line
2144 while True:
2145 isFinish = False #line.StartsWith("Ending")
2147 try:
2148 processedLine = phase.send(line)
2150 if isinstance(processedLine, VivadoMessage):
2151 self._AddMessage(processedLine)
2153 if isFinish: 2153 ↛ 2154line 2153 didn't jump to line 2154 because the condition on line 2153 was never true
2154 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2155 line = yield processedLine
2156 break
2157 except StopIteration as ex:
2158 activeParsers.remove(parser)
2159 line = ex.value
2160 break
2162 line = yield processedLine
2165@export
2166class Phase(BaseParser, VivadoMessagesMixin):
2167 # _NAME: ClassVar[str]
2168 # _START: ClassVar[str]
2169 # _FINISH: ClassVar[str]
2170 # _TIME: ClassVar[str] = "Time (s):"
2171 # _FINAL: ClassVar[Nullable[str]] = None
2173 _task: TaskWithPhases
2174 _phaseIndex: int
2175 _duration: float
2177 def __init__(self, task: TaskWithPhases) -> None:
2178 super().__init__()
2179 VivadoMessagesMixin.__init__(self)
2181 self._task = task
2182 self._phaseIndex = None
2184 @readonly
2185 def Task(self) -> TaskWithPhases:
2186 return self._task
2188 def _PhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2189 if (match := self._START.match(line._message)) is None: 2189 ↛ 2190line 2189 didn't jump to line 2190 because the condition on line 2189 was never true
2190 raise ProcessorException(f"{self.__class__.__name__}._PhaseStart(): Expected '{self._START}' at line {line._lineNumber}.")
2192 self._phaseIndex = int(match["major"])
2194 line._kind = LineKind.PhaseStart
2195 nextLine = yield line
2196 return nextLine
2198 def _PhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2199 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
2200 if not line.StartsWith(FINISH): 2200 ↛ 2201line 2200 didn't jump to line 2201 because the condition on line 2200 was never true
2201 raise ProcessorException(f"{self.__class__.__name__}._PhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2203 line._kind = LineKind.PhaseEnd
2204 line = yield line
2206 if self._TIME is not None: 2206 ↛ 2218line 2206 didn't jump to line 2218 because the condition on line 2206 was always true
2207 while True:
2208 if line.StartsWith(self._TIME):
2209 line._kind = LineKind.PhaseTime
2210 break
2211 elif isinstance(line, VivadoMessage): 2211 ↛ 2212line 2211 didn't jump to line 2212 because the condition on line 2211 was never true
2212 self._AddMessage(line)
2214 line = yield line
2216 line = yield line
2218 if self._FINAL is not None and self._task._command._processor._preamble._toolVersion >= "2023.2":
2219 while True:
2220 if line.StartsWith(self._FINAL): 2220 ↛ 2223line 2220 didn't jump to line 2223 because the condition on line 2220 was always true
2221 line._kind = LineKind.PhaseFinal
2222 break
2223 elif isinstance(line, VivadoMessage):
2224 self._AddMessage(line)
2226 line = yield line
2228 line = yield line
2230 # TODO: optionally collect following INFO messages like 31-389, 31-1021, 31-662
2232 return line
2234 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2235 line = yield from self._PhaseStart(line)
2237 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
2239 while True:
2240 if line._kind is LineKind.Empty:
2241 line = yield line
2242 continue
2243 elif isinstance(line, VivadoMessage):
2244 self._AddMessage(line)
2245 elif line.StartsWith(FINISH):
2246 break
2248 line = yield line
2250 nextLine = yield from self._PhaseFinish(line)
2251 return nextLine
2253 def __str__(self) -> str:
2254 return f"{self.__class__.__name__}: {self._START.pattern}"
2257@export
2258class PhaseWithChildren(Phase):
2259 _SUBPHASE_PREFIX: ClassVar[str] = "Phase {phaseIndex}."
2261 _subPhases: Dict[Type["SubPhase"], "SubPhase"]
2263 def __init__(self, task: TaskWithPhases) -> None:
2264 super().__init__(task)
2266 self._subPhases = {p: p(self) for p in self._PARSERS}
2268 def __contains__(self, key: Any) -> bool:
2269 if not issubclass(key, SubPhase):
2270 ex = TypeError(f"Parameter 'item' is not a SubPhase.")
2271 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2272 raise ex
2274 return key in self._subPhases
2276 def __getitem__(self, key: Type["SubPhase"]) -> "SubPhase":
2277 try:
2278 return self._subPhases[key]
2279 except KeyError as ex:
2280 raise PhaseNotPresentException(F"SubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2282 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2283 line = yield from self._PhaseStart(line)
2285 activeParsers: List[SubPhase] = list(self._subPhases.values())
2287 SUBPHASE_PREFIX = self._SUBPHASE_PREFIX.format(phaseIndex=self._phaseIndex)
2288 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
2290 while True:
2291 while True:
2292 if line._kind is LineKind.Empty:
2293 line = yield line
2294 continue
2295 elif isinstance(line, VivadoMessage):
2296 self._AddMessage(line)
2297 elif line.StartsWith(SUBPHASE_PREFIX):
2298 for parser in activeParsers: # type: Section 2298 ↛ 2303line 2298 didn't jump to line 2303 because the loop on line 2298 didn't complete
2299 if (match := parser._START.match(line._message)) is not None:
2300 line = yield next(phase := parser.Generator(line))
2301 break
2302 else:
2303 WarningCollector.Raise(UnknownSubPhase(f"Unknown subphase: '{line!r}'", line))
2304 ex = Exception(f"How to recover from here? Unknown subphase: '{line!r}'")
2305 ex.add_note(f"Current phase: start pattern='{self}'")
2306 ex.add_note(f"Current task: start pattern='{self._task}'")
2307 ex.add_note(f"Current command: {self._task._command}")
2308 raise ex
2309 break
2310 elif line.StartsWith(FINISH):
2311 nextLine = yield from self._PhaseFinish(line)
2312 return nextLine
2314 line = yield line
2316 while True:
2317 isFinish = False # line.StartsWith(SUBPHASE_PREFIX) # FIXME: detect end, but end (e.g. time) is later then ending text
2319 try:
2320 processedLine = phase.send(line)
2322 if isinstance(processedLine, VivadoMessage):
2323 self._AddMessage(processedLine)
2325 if isFinish: 2325 ↛ 2326line 2325 didn't jump to line 2326 because the condition on line 2325 was never true
2326 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2327 line = yield processedLine
2328 break
2329 except StopIteration as ex:
2330 activeParsers.remove(parser)
2331 line = ex.value
2332 break
2334 line = yield processedLine
2337@export
2338class SubPhase(BaseParser, VivadoMessagesMixin):
2339 # _NAME: ClassVar[str]
2340 # _START: ClassVar[str]
2341 # _FINISH: ClassVar[str]
2343 _phase: Phase
2344 _phaseIndex: int
2345 _subPhaseIndex: int
2346 _duration: float
2348 def __init__(self, phase: Phase) -> None:
2349 super().__init__()
2350 VivadoMessagesMixin.__init__(self)
2352 self._phase = phase
2353 self._phaseIndex = None
2354 self._subPhaseIndex = None
2356 def _SubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2357 if (match := self._START.match(line._message)) is None: 2357 ↛ 2358line 2357 didn't jump to line 2358 because the condition on line 2357 was never true
2358 raise ProcessorException(f"{self.__class__.__name__}._SubPhaseStart(): Expected '{self._START}' at line {line._lineNumber}.")
2360 self._phaseIndex = int(match["major"])
2361 self._subPhaseIndex = int(match["minor"])
2363 line._kind = LineKind.SubPhaseStart
2364 nextLine = yield line
2365 return nextLine
2367 def _SubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2368 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex)
2370 if not line.StartsWith(FINISH): 2370 ↛ 2371line 2370 didn't jump to line 2371 because the condition on line 2370 was never true
2371 raise ProcessorException(f"{self.__class__.__name__}._SubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2373 if self._TIME is None:
2374 line._kind = LineKind.SubPhaseTime
2375 else:
2376 line._kind = LineKind.SubPhaseEnd
2378 line = yield line
2379 while self._TIME is not None: 2379 ↛ 2386line 2379 didn't jump to line 2386 because the condition on line 2379 was always true
2380 if line.StartsWith(self._TIME):
2381 line._kind = LineKind.SubPhaseTime
2382 break
2384 line = yield line
2386 nextLine = yield line
2387 return nextLine
2389 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2390 line = yield from self._SubPhaseStart(line)
2392 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex)
2394 while True:
2395 if line._kind is LineKind.Empty:
2396 line = yield line
2397 continue
2398 elif line.StartsWith(FINISH):
2399 break
2400 elif isinstance(line, VivadoMessage):
2401 self._AddMessage(line)
2403 line = yield line
2405 nextLine = yield from self._SubPhaseFinish(line)
2406 return nextLine
2408 def __str__(self) -> str:
2409 return f"{self.__class__.__name__}: {self._START.pattern}"
2412@export
2413class SubPhaseWithChildren(SubPhase):
2414 _subSubPhases: Dict[Type["SubSubPhase"], "SubSubPhase"]
2416 def __init__(self, phase: Phase) -> None:
2417 super().__init__(phase)
2419 self._subSubPhases = {p: p(self) for p in self._PARSERS}
2421 def __contains__(self, key: Any) -> bool:
2422 if not issubclass(key, SubSubPhase):
2423 ex = TypeError(f"Parameter 'item' is not a SubSubPhase.")
2424 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2425 raise ex
2427 return key in self._subSubPhases
2429 def __getitem__(self, key: Type["SubSubPhase"]) -> "SubSubPhase":
2430 try:
2431 return self._subSubPhases[key]
2432 except KeyError as ex:
2433 raise PhaseNotPresentException(F"SubSubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2435 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2436 line = yield from self._SubPhaseStart(line)
2438 activeParsers: List["SubSubPhase"] = list(self._subSubPhases.values())
2440 START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}."
2441 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex)
2443 while True:
2444 while True:
2445 if line._kind is LineKind.Empty:
2446 line = yield line
2447 continue
2448 elif isinstance(line, VivadoMessage):
2449 self._AddMessage(line)
2450 elif line.StartsWith(START_PREFIX):
2451 for parser in activeParsers: # type: SubSubPhase 2451 ↛ 2456line 2451 didn't jump to line 2456 because the loop on line 2451 didn't complete
2452 if (match := parser._START.match(line._message)) is not None:
2453 line = yield next(phase := parser.Generator(line))
2454 break
2455 else:
2456 WarningCollector.Raise(UnknownSubPhase(f"Unknown subsubphase: '{line!r}'", line))
2457 ex = Exception(f"How to recover from here? Unknown subsubphase: '{line!r}'")
2458 ex.add_note(f"Current subphase: start pattern='{self}'")
2459 ex.add_note(f"Current phase: start pattern='{self._phase}'")
2460 ex.add_note(f"Current task: start pattern='{self._phase._task}'")
2461 ex.add_note(f"Current cmd: {self._phase._task._command}")
2462 raise ex
2463 break
2464 elif line.StartsWith(FINISH): 2464 ↛ 2468line 2464 didn't jump to line 2468 because the condition on line 2464 was always true
2465 nextLine = yield from self._SubPhaseFinish(line)
2466 return nextLine
2468 line = yield line
2470 while True:
2471 isFinish = False # line.StartsWith("Ending")
2473 try:
2474 processedLine = phase.send(line)
2476 if isinstance(processedLine, VivadoMessage):
2477 self._AddMessage(processedLine)
2479 if isFinish: 2479 ↛ 2480line 2479 didn't jump to line 2480 because the condition on line 2479 was never true
2480 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2481 line = yield processedLine
2482 break
2483 except StopIteration as ex:
2484 activeParsers.remove(parser)
2485 line = ex.value
2486 break
2488 line = yield processedLine
2491@export
2492class SubSubPhase(BaseParser, VivadoMessagesMixin):
2493 # _NAME: ClassVar[str]
2494 # _START: ClassVar[str]
2495 # _FINISH: ClassVar[str]
2497 _subphase: SubPhase
2498 _phaseIndex: int
2499 _subPhaseIndex: int
2500 _subSubPhaseIndex: int
2501 _duration: float
2503 def __init__(self, subphase: SubPhase) -> None:
2504 super().__init__()
2505 VivadoMessagesMixin.__init__(self)
2507 self._subphase = subphase
2508 self._phaseIndex = None
2509 self._subPhaseIndex = None
2510 self._subSubPhaseIndex = None
2512 def _SubSubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2513 if (match := self._START.match(line._message)) is None: 2513 ↛ 2514line 2513 didn't jump to line 2514 because the condition on line 2513 was never true
2514 raise ProcessorException()
2516 self._phaseIndex = int(match["major"])
2517 self._subPhaseIndex = int(match["minor"])
2518 self._subSubPhaseIndex = int(match["micro"])
2520 line._kind = LineKind.SubSubPhaseStart
2521 nextLine = yield line
2522 return nextLine
2524 def _SubSubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2525 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex)
2527 if not line.StartsWith(FINISH): 2527 ↛ 2528line 2527 didn't jump to line 2528 because the condition on line 2527 was never true
2528 raise ProcessorException(f"{self.__class__.__name__}._SubSubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2530 line._kind = LineKind.SubSubPhaseEnd
2531 line = yield line
2533 while self._TIME is not None: 2533 ↛ 2540line 2533 didn't jump to line 2540 because the condition on line 2533 was always true
2534 if line.StartsWith(self._TIME):
2535 line._kind = LineKind.SubSubPhaseTime
2536 break
2538 line = yield line
2540 nextLine = yield line
2541 return nextLine
2543 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2544 line = yield from self._SubSubPhaseStart(line)
2546 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex)
2548 while True:
2549 if line._kind is LineKind.Empty:
2550 line = yield line
2551 continue
2552 elif line.StartsWith(FINISH):
2553 break
2554 elif isinstance(line, VivadoMessage):
2555 self._AddMessage(line)
2557 line = yield line
2559 nextLine = yield from self._SubSubPhaseFinish(line)
2560 return nextLine
2562 def __str__(self) -> str:
2563 return f"{self.__class__.__name__}: {self._START.pattern}"
2566@export
2567class SubSubPhaseWithChildren(SubSubPhase):
2568 _subSubSubPhases: Dict[Type["SubSubSubPhase"], "SubSubSubPhase"]
2570 def __init__(self, subphase: SubPhase) -> None:
2571 super().__init__(subphase)
2573 self._subSubSubPhases = {p: p(self) for p in self._PARSERS}
2575 def __contains__(self, key: Any) -> bool:
2576 if not issubclass(key, SubSubSubPhase):
2577 ex = TypeError(f"Parameter 'item' is not a SubSubSubPhase.")
2578 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2579 raise ex
2581 return key in self._subSubSubPhases
2583 def __getitem__(self, key: Type["SubSubSubPhase"]) -> "SubSubSubPhase":
2584 try:
2585 return self._subSubSubPhases[key]
2586 except KeyError as ex:
2587 raise PhaseNotPresentException(F"SubSubSubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2589 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2590 line = yield from self._SubSubPhaseStart(line)
2592 activeParsers: List["SubSubSubPhase"] = list(self._subSubSubPhases.values())
2594 START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}.{self._subSubPhaseIndex}."
2596 while True:
2597 while True:
2598 if line._kind is LineKind.Empty:
2599 line = yield line
2600 continue
2601 elif isinstance(line, VivadoMessage):
2602 self._AddMessage(line)
2603 elif line.StartsWith(START_PREFIX):
2604 for parser in activeParsers: # type: SubSubSubPhase 2604 ↛ 2609line 2604 didn't jump to line 2609 because the loop on line 2604 didn't complete
2605 if (match := parser._START.match(line._message)) is not None: 2605 ↛ 2604line 2605 didn't jump to line 2604 because the condition on line 2605 was always true
2606 line = yield next(phase := parser.Generator(line))
2607 break
2608 else:
2609 WarningCollector.Raise(UnknownSubPhase(f"Unknown subsubsubphase: '{line!r}'", line))
2610 ex = Exception(f"How to recover from here? Unknown subsubsubphase: '{line!r}'")
2611 ex.add_note(f"Current subsubphase: start pattern='{self}'")
2612 ex.add_note(f"Current subphase: start pattern='{self._subphase}'")
2613 ex.add_note(f"Current phase: start pattern='{self._subphase._phase}'")
2614 ex.add_note(f"Current task: start pattern='{self._subphase._phase._task}'")
2615 ex.add_note(f"Current cmd: {self._subphase._phase._task._command}")
2616 raise ex
2617 break
2618 elif line.StartsWith(self._TIME):
2619 line._kind = LineKind.SubSubPhaseTime
2620 nextLine = yield line
2621 return nextLine
2623 line = yield line
2625 while True:
2626 isFinish = False # line.StartsWith("Ending")
2628 try:
2629 processedLine = phase.send(line)
2631 if isinstance(processedLine, VivadoMessage):
2632 self._AddMessage(processedLine)
2634 if isFinish: 2634 ↛ 2635line 2634 didn't jump to line 2635 because the condition on line 2634 was never true
2635 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2636 line = yield processedLine
2637 break
2638 except StopIteration as ex:
2639 activeParsers.remove(parser)
2640 line = ex.value
2641 break
2643 line = yield processedLine
2646@export
2647class SubSubSubPhase(BaseParser, VivadoMessagesMixin):
2648 # _NAME: ClassVar[str]
2649 # _START: ClassVar[str]
2650 # _FINISH: ClassVar[str]
2652 _subsubphase: SubSubPhase
2653 _phaseIndex: int
2654 _subPhaseIndex: int
2655 _subSubPhaseIndex: int
2656 _subSubSubPhaseIndex: int
2657 _duration: float
2659 def __init__(self, subsubphase: SubSubPhase) -> None:
2660 super().__init__()
2661 VivadoMessagesMixin.__init__(self)
2663 self._subsubphase = subsubphase
2664 self._phaseIndex = None
2665 self._subPhaseIndex = None
2666 self._subSubPhaseIndex = None
2667 self._subSubSubPhaseIndex = None
2669 def _SubSubSubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2670 if (match := self._START.match(line._message)) is None: 2670 ↛ 2671line 2670 didn't jump to line 2671 because the condition on line 2670 was never true
2671 raise ProcessorException()
2673 self._phaseIndex = int(match["major"])
2674 self._subPhaseIndex = int(match["minor"])
2675 self._subSubPhaseIndex = int(match["micro"])
2676 self._subSubSubPhaseIndex = int(match["nano"])
2678 line._kind = LineKind.SubSubSubPhaseStart
2679 nextLine = yield line
2680 return nextLine
2682 def _SubSubSubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2683 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex, subSubSubPhaseIndex=self._subSubSubPhaseIndex)
2685 if not line.StartsWith(FINISH): 2685 ↛ 2686line 2685 didn't jump to line 2686 because the condition on line 2685 was never true
2686 raise ProcessorException(f"{self.__class__.__name__}._SubSubSubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2688 line._kind = LineKind.SubSubSubPhaseEnd
2689 line = yield line
2691 while self._TIME is not None: 2691 ↛ 2698line 2691 didn't jump to line 2698 because the condition on line 2691 was always true
2692 if line.StartsWith(self._TIME):
2693 line._kind = LineKind.SubSubSubPhaseTime
2694 break
2696 line = yield line
2698 nextLine = yield line
2699 return nextLine
2701 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2702 line = yield from self._SubSubSubPhaseStart(line)
2704 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex, subSubSubPhaseIndex=self._subSubSubPhaseIndex)
2706 while True:
2707 if line._kind is LineKind.Empty: 2707 ↛ 2708line 2707 didn't jump to line 2708 because the condition on line 2707 was never true
2708 line = yield line
2709 continue
2710 elif line.StartsWith(FINISH):
2711 break
2712 elif isinstance(line, VivadoMessage): 2712 ↛ 2715line 2712 didn't jump to line 2715 because the condition on line 2712 was always true
2713 self._AddMessage(line)
2715 line = yield line
2717 nextLine = yield from self._SubSubSubPhaseFinish(line)
2718 return nextLine
2720 def __str__(self) -> str:
2721 return f"{self.__class__.__name__}: {self._START.pattern}"
2724@export
2725class SubSubSubPhaseWithTasks(SubSubSubPhase):
2726 _nestedTasks: Dict[Type["NestedTask"], "NestedTask"]
2728 def __init__(self, subsubphase: SubSubPhase) -> None:
2729 super().__init__(subsubphase)
2731 self._nestedTasks = {p: p(self) for p in self._PARSERS}
2733 def __contains__(self, key: Any) -> bool:
2734 if not issubclass(key, NestedTask):
2735 ex = TypeError(f"Parameter 'key' is not a NestedTask.")
2736 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2737 raise ex
2739 return key in self._nestedTasks
2741 def __getitem__(self, key: Type["NestedTask"]) -> "NestedTask":
2742 try:
2743 return self._nestedTasks[key]
2744 except KeyError as ex:
2745 raise NestedTaskNotPresentException(F"NestedTask '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2747 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2748 line = yield from self._SubSubSubPhaseStart(line)
2750 activeParsers: List["NestedTask"] = list(self._nestedTasks.values())
2752 # START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}.{self._subSubPhaseIndex}."
2754 while True:
2755 while True:
2756 if line._kind is LineKind.Empty:
2757 line = yield line
2758 continue
2759 elif isinstance(line, VivadoMessage):
2760 self._AddMessage(line)
2761 elif line.StartsWith("Starting "):
2762 for parser in activeParsers: # type: NestedTask 2762 ↛ 2767line 2762 didn't jump to line 2767 because the loop on line 2762 didn't complete
2763 if line.StartsWith(parser._START): 2763 ↛ 2762line 2763 didn't jump to line 2762 because the condition on line 2763 was always true
2764 line = yield next(phase := parser.Generator(line))
2765 break
2766 else:
2767 WarningCollector.Raise(UnknownSubPhase(f"Unknown NestedTask: '{line!r}'", line))
2768 ex = Exception(f"How to recover from here? Unknown NestedTask: '{line!r}'")
2769 ex.add_note(f"Current subsubsubphase: start pattern='{self}'")
2770 ex.add_note(f"Current subsubphase: start pattern='{self._subsubphase}'")
2771 ex.add_note(f"Current subphase: start pattern='{self._subsubphase._subphase}'")
2772 ex.add_note(f"Current phase: start pattern='{self._subsubphase._subphase._phase}'")
2773 ex.add_note(f"Current task: start pattern='{self._subsubphase._subphase._phase._task}'")
2774 ex.add_note(f"Current cmd: {self._subsubphase._subphase._phase._task._command}")
2775 raise ex
2776 break
2777 elif line.StartsWith(self._TIME):
2778 line._kind = LineKind.SubSubSubPhaseTime
2779 nextLine = yield line
2780 return nextLine
2782 line = yield line
2784 while True:
2785 isFinish = False # line.StartsWith("Ending")
2787 try:
2788 processedLine = phase.send(line)
2790 if isinstance(processedLine, VivadoMessage):
2791 self._AddMessage(processedLine)
2793 if isFinish: 2793 ↛ 2794line 2793 didn't jump to line 2794 because the condition on line 2793 was never true
2794 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2795 line = yield processedLine
2796 break
2797 except StopIteration as ex:
2798 activeParsers.remove(parser)
2799 line = ex.value
2800 break
2802 line = yield processedLine
2805@export
2806class NestedTask(BaseParser, VivadoMessagesMixin):
2807 # _NAME: ClassVar[str]
2808 # _START: ClassVar[str]
2809 # _FINISH: ClassVar[str]
2810 # _TIME: ClassVar[str] = "Time (s):"
2812 _subsubsubphase: SubSubSubPhaseWithTasks
2813 _duration: float
2815 def __init__(self, subsubsubphase: SubSubSubPhaseWithTasks) -> None:
2816 super().__init__()
2817 VivadoMessagesMixin.__init__(self)
2819 self._subsubsubphase = subsubsubphase
2821 @readonly
2822 def SubSubSubPhase(self) -> SubSubSubPhaseWithTasks:
2823 return self._subsubsubphase
2825 def _NestedTaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2826 if not line.StartsWith(self._START): 2826 ↛ 2827line 2826 didn't jump to line 2827 because the condition on line 2826 was never true
2827 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.")
2829 line._kind = LineKind.NestedTaskStart
2830 nextLine = yield line
2831 return nextLine
2833 def _NestedTaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2834 if not line.StartsWith(self._FINISH): 2834 ↛ 2835line 2834 didn't jump to line 2835 because the condition on line 2834 was never true
2835 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.")
2837 line._kind = LineKind.NestedTaskEnd
2838 line = yield line
2840 if self._TIME is not None: 2840 ↛ 2849line 2840 didn't jump to line 2849 because the condition on line 2840 was always true
2841 while True:
2842 if line.StartsWith(self._TIME):
2843 line._kind = LineKind.TaskTime
2844 line = yield line
2845 break
2847 line = yield line
2849 return line
2851 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2852 line = yield from self._NestedTaskStart(line)
2854 while True:
2855 if line._kind is LineKind.Empty:
2856 line = yield line
2857 continue
2858 elif self._FINISH is not None and line.StartsWith("Ending"):
2859 break
2860 elif isinstance(line, VivadoMessage):
2861 self._AddMessage(line)
2862 elif line.StartsWith(self._TIME):
2863 line._kind = LineKind.TaskTime
2864 nextLine = yield line
2865 return nextLine
2867 line = yield line
2869 nextLine = yield from self._NestedTaskFinish(line)
2870 return nextLine
2872 def __str__(self) -> str:
2873 return self._NAME
2876@export
2877class NestedTaskWithPhases(NestedTask):
2878 """
2879 A task's output emitted by a Vivado command.
2881 .. rubric:: Extracted information
2883 * Vivado messages (info, warning, critical warning, error).
2884 * Nested phases
2886 .. rubric:: Example
2888 .. code-block::
2890 Phase 4.1.1.1 BUFG Insertion
2892 Starting Physical Synthesis Task
2894 Phase 1 Physical Synthesis Initialization
2895 INFO: [Physopt 32-721] Multithreading enabled for phys_opt_design using a maximum of 2 CPUs
2896 INFO: [Physopt 32-619] Estimated Timing Summary | WNS=-0.733 | TNS=-0.936 |
2897 Phase 1 Physical Synthesis Initialization | Checksum: 1818afcc0
2899 Time (s): cpu = 00:00:00 ; elapsed = 00:00:00.014 . Memory (MB): peak = 1865.645 ; gain = 0.000
2900 INFO: [Place 46-56] BUFG insertion identified 0 candidate nets. Inserted BUFG: 0, Replicated BUFG Driver: 0, Skipped due to Placement/Routing Conflicts: 0, Skipped due to Timing Degradation: 0, Skipped due to netlist editing failed: 0.
2901 Ending Physical Synthesis Task | Checksum: 22839c186
2903 Time (s): cpu = 00:00:00 ; elapsed = 00:00:00.016 . Memory (MB): peak = 1865.645 ; gain = 0.000
2904 Phase 4.1.1.1 BUFG Insertion | Checksum: 1a8cbaaf2
2905 """
2906 # _PARSERS: ClassVar[Tuple[Type["SubTask"], ...]]
2908 _nestedPhases: Dict[Type["NestedPhase"], "NestedPhase"]
2910 def __init__(self, subsubsubPhase: SubSubSubPhaseWithTasks) -> None:
2911 super().__init__(subsubsubPhase)
2913 self._nestedPhases = {p: p(self) for p in self._PARSERS}
2915 @readonly
2916 def NestedPhases(self) -> Dict[Type["NestedPhase"], "NestedPhase"]:
2917 return self._nestedPhases
2919 def __contains__(self, key: Any) -> bool:
2920 if not issubclass(key, NestedPhase):
2921 ex = TypeError(f"Parameter 'key' is not a NestedPhase.")
2922 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2923 raise ex
2925 return key in self._nestedPhases
2927 def __getitem__(self, key: Type["NestedPhase"]) -> "NestedPhase":
2928 try:
2929 return self._nestedPhases[key]
2930 except KeyError as ex:
2931 raise SubTaskNotPresentException(F"NestedPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2933 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2934 line = yield from self._NestedTaskStart(line)
2936 activeParsers: List[Phase] = list(self._nestedPhases.values())
2938 while True:
2939 while True:
2940 if line._kind is LineKind.Empty:
2941 line = yield line
2942 continue
2943 elif isinstance(line, VivadoMessage):
2944 self._AddMessage(line)
2945 elif line.StartsWith("Phase "):
2946 for parser in activeParsers: # type: NestedPhase 2946 ↛ 2951line 2946 didn't jump to line 2951 because the loop on line 2946 didn't complete
2947 if (match := parser._START.match(line._message)) is not None: 2947 ↛ 2946line 2947 didn't jump to line 2946 because the condition on line 2947 was always true
2948 line = yield next(phase := parser.Generator(line))
2949 break
2950 else:
2951 WarningCollector.Raise(UnknownSubPhase(f"Unknown NestedPhase: '{line!r}'", line))
2952 ex = Exception(f"How to recover from here? Unknown NestedPhase: '{line!r}'")
2953 ex.add_note(f"Current nestedtask: start pattern='{self}'")
2954 ex.add_note(f"Current subsubsubphase: start pattern='{self._subsubsubphase}'")
2955 ex.add_note(f"Current subsubphase: start pattern='{self._subsubsubphase._subsubphase}'")
2956 ex.add_note(f"Current subphase: start pattern='{self._subsubsubphase._subsubphase._subphase}'")
2957 ex.add_note(f"Current phase: start pattern='{self._subsubsubphase._subsubphase._subphase._phase}'")
2958 ex.add_note(f"Current task: start pattern='{self._subsubsubphase._subsubphase._subphase._phase._task}'")
2959 ex.add_note(f"Current cmd: {self._subsubsubphase._subsubphase._subphase._phase._task._command}")
2960 raise ex
2961 break
2962 elif line.StartsWith("Ending"): 2962 ↛ 2965line 2962 didn't jump to line 2965 because the condition on line 2962 was always true
2963 nextLine = yield from self._NestedTaskFinish(line)
2964 return nextLine
2965 elif line.StartsWith(self._TIME):
2966 line._kind = LineKind.TaskTime
2967 nextLine = yield line
2968 return nextLine
2970 line = yield line
2972 while True:
2973 isFinish = False # line.StartsWith("Ending") # FIXME: detect end, but time might come later
2975 try:
2976 processedLine = phase.send(line)
2978 if isinstance(processedLine, VivadoMessage):
2979 self._AddMessage(processedLine)
2981 if isFinish: 2981 ↛ 2982line 2981 didn't jump to line 2982 because the condition on line 2981 was never true
2982 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2983 line = yield processedLine
2984 break
2985 except StopIteration as ex:
2986 activeParsers.remove(parser)
2987 line = ex.value
2988 break
2990 line = yield processedLine
2993@export
2994class NestedPhase(BaseParser, VivadoMessagesMixin):
2995 # _NAME: ClassVar[str]
2996 # _START: ClassVar[str]
2997 # _FINISH: ClassVar[str]
2998 # _TIME: ClassVar[str] = "Time (s):"
2999 _FINAL: ClassVar[Nullable[str]] = None
3001 _nestedTask: NestedTaskWithPhases
3002 _phaseIndex: int
3003 _duration: float
3005 def __init__(self, nestedTask: TaskWithPhases) -> None:
3006 super().__init__()
3007 VivadoMessagesMixin.__init__(self)
3009 self._nestedTask = nestedTask
3010 self._phaseIndex = None
3012 @readonly
3013 def NestedTask(self) -> NestedTaskWithPhases:
3014 return self._nestedTask
3016 def _NestedPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
3017 if (match := self._START.match(line._message)) is None: 3017 ↛ 3018line 3017 didn't jump to line 3018 because the condition on line 3017 was never true
3018 raise ProcessorException(f"{self.__class__.__name__}._PhaseStart(): Expected '{self._START}' at line {line._lineNumber}.")
3020 self._phaseIndex = int(match["major"])
3022 line._kind = LineKind.NestedPhaseStart
3023 nextLine = yield line
3024 return nextLine
3026 def _NestedPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
3027 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
3028 if not line.StartsWith(FINISH): 3028 ↛ 3029line 3028 didn't jump to line 3029 because the condition on line 3028 was never true
3029 raise ProcessorException(f"{self.__class__.__name__}._PhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
3031 line._kind = LineKind.NestedPhaseEnd
3032 line = yield line
3034 if self._TIME is not None: 3034 ↛ 3046line 3034 didn't jump to line 3046 because the condition on line 3034 was always true
3035 while True:
3036 if line.StartsWith(self._TIME):
3037 line._kind = LineKind.PhaseTime
3038 break
3039 elif isinstance(line, VivadoMessage): 3039 ↛ 3040line 3039 didn't jump to line 3040 because the condition on line 3039 was never true
3040 self._AddMessage(line)
3042 line = yield line
3044 line = yield line
3046 return line
3048 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
3049 line = yield from self._NestedPhaseStart(line)
3051 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
3053 while True:
3054 if line._kind is LineKind.Empty: 3054 ↛ 3055line 3054 didn't jump to line 3055 because the condition on line 3054 was never true
3055 line = yield line
3056 continue
3057 elif isinstance(line, VivadoMessage):
3058 self._AddMessage(line)
3059 elif line.StartsWith(FINISH): 3059 ↛ 3062line 3059 didn't jump to line 3062 because the condition on line 3059 was always true
3060 break
3062 line = yield line
3064 nextLine = yield from self._NestedPhaseFinish(line)
3065 return nextLine
3067 def __str__(self) -> str:
3068 return f"{self.__class__.__name__}: {self._START.pattern}"
3071@export
3072class Synth_Design(CommandWithSections):
3073 """
3074 A Vivado command output parser for ``synth_design``.
3075 """
3076 from . import SynthesizeDesign as _SynthDesign
3078 _TCL_COMMAND: ClassVar[str] = "synth_design"
3079 _PARSERS: ClassVar[Tuple[Type[Section], ...]] = (
3080 _SynthDesign.RTLElaboration,
3081 _SynthDesign.HandlingCustomAttributes,
3082 _SynthDesign.ConstraintValidation,
3083 _SynthDesign.LoadingPart,
3084 _SynthDesign.ApplySetPropertyXDCConstraints,
3085 _SynthDesign.RTLComponentStatistics,
3086 _SynthDesign.RTLHierarchicalComponentStatistics,
3087 _SynthDesign.PartResourceSummary,
3088 _SynthDesign.CrossBoundaryAndAreaOptimization,
3089 _SynthDesign.ROM_RAM_DSP_SR_Retiming,
3090 _SynthDesign.ApplyingXDCTimingConstraints,
3091 _SynthDesign.TimingOptimization,
3092 _SynthDesign.TechnologyMapping,
3093 _SynthDesign.IOInsertion,
3094 _SynthDesign.FlatteningBeforeIOInsertion,
3095 _SynthDesign.FinalNetlistCleanup,
3096 _SynthDesign.RenamingGeneratedInstances,
3097 _SynthDesign.RebuildingUserHierarchy,
3098 _SynthDesign.RenamingGeneratedPorts,
3099 _SynthDesign.RenamingGeneratedNets,
3100 _SynthDesign.WritingSynthesisReport,
3101 )
3103 @readonly
3104 def HasLatches(self) -> bool:
3105 """
3106 Read-only property returning if synthesis inferred latches into the design.
3108 Latch detection is based on:
3110 * Vivado message ``synth 8-327``
3111 * Cells of lind ``LD`` listed in the *Cell Usage* report.
3113 :returns: True, if the design contains latches.
3114 """
3115 from .SynthesizeDesign import WritingSynthesisReport
3117 if (8 in self._messagesByID) and (327 in self._messagesByID[8]):
3118 return True
3120 return "LD" in self._sections[WritingSynthesisReport]._cells
3122 @readonly
3123 def Latches(self) -> List[VivadoMessage]:
3124 """
3125 Read-only property to access a list of Vivado output messages for inferred latches.
3127 :returns: A list of Vivado messages for interred latches.
3129 .. note::
3131 This returns ``[Synth 8-327]`` messages.
3133 .. code-block::
3135 WARNING: [Synth 8-327] inferring latch for variable 'Q_reg'
3136 """
3137 if 8 in self._messagesByID:
3138 if 327 in (synthMessages := self._messagesByID[8]):
3139 return [message for message in synthMessages[327]]
3141 return []
3143 @readonly
3144 def Statemachines(self) -> Dict[str, List[str]]:
3145 """
3147 :returns: undocumented
3149 .. note::
3151 INFO: [Synth 8-802] inferred FSM for state register 'State_reg' in module 'stream_Padder'
3152 INFO: [Synth 8-802] inferred FSM for state register 'RX_blk.blkRXFSM.State_reg' in module 'eth_XGEMAC_XGMII'
3153 """
3155 @readonly
3156 def DistributedRAMs(self) -> Dict[str, Any]:
3157 """
3159 :returns: undocumented
3160 """
3162 @readonly
3163 def BlockRAMs(self) -> Dict[str, Any]:
3164 """
3166 :returns: undocumented
3167 """
3170 @readonly
3171 def UltraRAMs(self) -> Dict[str, Any]:
3172 """
3174 :returns: undocumented
3175 """
3177 @readonly
3178 def ShiftRegister(self) -> Dict[str, Dict[str, Any]]:
3179 """
3181 :returns: undocumented
3183 .. note::
3185 Static Shift Register Report:
3186 Dynamic Shift Register Report:
3187 """
3189 @readonly
3190 def UndrivenPins(self) -> Dict[str, str]:
3191 """
3193 :returns: undocumented
3195 .. note::
3197 WARNING: [Synth 8-3295] tying undriven pin AXI4FullPipeLine_M2S[AWID]_inferred:in0 to constant 0
3198 """
3200 # ignored instructions
3201 # ignored ram_style WARNING: [Synth 8-5791] The ram_style = "ultra" set on RAM "ocram_sdp__parameterized4:/gInfer.ram_reg" is ignored because clocks on ports do not match.
3203 @readonly
3204 def HasBlackboxes(self) -> bool:
3205 """
3206 Read-only property returning if the design contains black-boxes.
3208 :returns: True, if the design contains black-boxes.
3209 """
3210 from .SynthesizeDesign import WritingSynthesisReport
3212 return len(self._sections[WritingSynthesisReport]._blackboxes) > 0
3214 @readonly
3215 def Blackboxes(self) -> Dict[str, int]:
3216 """
3217 Read-only property to access the dictionary of found blackbox statistics.
3219 :returns: The dictionary of found blackbox statistics.
3220 """
3221 from .SynthesizeDesign import WritingSynthesisReport
3223 return self._sections[WritingSynthesisReport]._blackboxes
3225 @readonly
3226 def Cells(self) -> Dict[str, int]:
3227 """
3228 Read-only property to access the dictionary of synthesized cell statistics.
3230 :returns: The dictionary of used cell statistics.
3231 """
3232 from .SynthesizeDesign import WritingSynthesisReport
3234 return self._sections[WritingSynthesisReport]._cells
3236 @readonly
3237 def VHDLReportMessages(self) -> List[VHDLReportMessage]:
3238 """
3239 Read-only property to access a list of Vivado output messages generated by VHDL report statement.
3241 :returns: A list of VHDL report statement outputs.
3243 .. note::
3245 This returns ``[Synth 8-6031]`` messages.
3247 .. code-block::
3249 INFO: [Synth 8-6031] RTL report: "TimingToCycles(time, freq): period=10.000000 ns -- 0.000000 fs" [C:/[...]/StopWatch/src/Utilities.pkg.vhdl:118]
3250 """
3251 if 8 in self._messagesByID:
3252 if 6031 in (synthMessages := self._messagesByID[8]):
3253 return [message for message in synthMessages[6031]]
3255 return []
3257 @readonly
3258 def VHDLAssertMessages(self) -> List[VHDLReportMessage]:
3259 """
3260 Read-only property to access a list of Vivado output messages generated by VHDL assert statement.
3262 :returns: A list of VHDL assert statement outputs.
3264 .. note::
3266 This returns ``[Synth 8-63]`` messages.
3268 .. code-block::
3270 INFO: [Synth 8-63] RTL assertion: "CLOCK_FREQ: 100.000000 ns" [C:/[...]/StopWatch/src/Debouncer.vhdl:28]
3271 """
3272 if 8 in self._messagesByID:
3273 if 63 in (synthMessages := self._messagesByID[8]):
3274 return [message for message in synthMessages[63]]
3276 return []
3278 def SectionDetector(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
3279 from .SynthesizeDesign import RTLElaboration
3281 if not (isinstance(line, VivadoTclCommand) and line._tclCommand == self._TCL_COMMAND): 3281 ↛ 3282line 3281 didn't jump to line 3282 because the condition on line 3281 was never true
3282 raise ProcessorException() # FIXME: add exception message
3284 activeParsers: List[Section] = [p(self) for p in self._PARSERS]
3285 rtlElaboration: RTLElaboration = next(p for p in activeParsers if isinstance(p, RTLElaboration))
3287 line = yield line
3288 if line == "Starting synth_design": 3288 ↛ 3291line 3288 didn't jump to line 3291 because the condition on line 3288 was always true
3289 line._kind = LineKind.Verbose
3290 else:
3291 raise ProcessorException() # FIXME: add exception message
3293 line = yield line
3294 while True:
3295 while True:
3296 if line._kind is LineKind.Empty:
3297 line = yield line
3298 continue
3299 elif isinstance(line, VivadoMessage):
3300 self._AddMessage(line)
3301 elif line.StartsWith("Start "):
3302 for parser in activeParsers: # type: Section 3302 ↛ 3317line 3302 didn't jump to line 3317 because the loop on line 3302 didn't complete
3303 if line.StartsWith(parser._START):
3304 # Found a suitable section parser.
3305 # Add section parser to list of found sections.
3306 # In case of duplicates, create a chain of psrer instances.
3307 if parser not in self._sections:
3308 self._sections.append(parser)
3309 else:
3310 parser._next = (newParser := parser.__class__(self))
3311 parser = newParser
3313 line = next(section := parser.Generator(line))
3314 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter
3315 break
3316 else:
3317 WarningCollector.Raise(UnknownSection(f"Unknown section: '{line!r}'", line))
3318 ex = Exception(f"How to recover from here? Unknown section: '{line!r}'")
3319 # ex.add_note(f"Current task: start pattern='{self._task}'")
3320 ex.add_note(f"Current cmd: {self}")
3321 raise ex
3322 break
3323 elif line.StartsWith("Starting "):
3324 if line.StartsWith(rtlElaboration._START):
3325 self._sections.append(parser := rtlElaboration)
3326 line = next(section := parser.Generator(line))
3327 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter
3328 break
3329 elif line.StartsWith(self._TCL_COMMAND):
3330 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3330 ↛ 3344line 3330 didn't jump to line 3344 because the condition on line 3330 was always true
3331 line._kind |= LineKind.Success
3333 # FIXME: use similar style like for _TIME
3334 line = yield line
3335 lastLine = yield line
3336 return lastLine
3337 elif line.StartsWith("Finished RTL Optimization Phase"):
3338 line._kind = LineKind.PhaseEnd
3339 line._previousLine._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter
3340 elif line.StartsWith("----"):
3341 if LineKind.Phase in line._previousLine._kind:
3342 line._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter
3344 line = yield line
3346 line = yield line
3348 while True:
3349 if line.StartsWith("Finished"):
3350 l = line[9:]
3351 if not (l.startswith("Flattening") or l.startswith("Final")):
3352 line = yield section.send(line)
3353 break
3355 if isinstance(line, VivadoMessage):
3356 self._AddMessage(line)
3358 line = yield section.send(line)
3360 line = yield section.send(line)
3362 if not parser._DUPLICATES:
3363 activeParsers.remove(parser)
3366@export
3367class Link_Design(Command):
3368 """
3369 A Vivado command output parser for ``link_design``.
3370 """
3371 _TCL_COMMAND: ClassVar[str] = "link_design"
3372 _TIME: ClassVar[str] = "Time (s):"
3374 _ParsingXDCFile_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\]$""")
3375 _FinishedParsingXDCFile_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\]$""")
3376 _ParsingXDCFileForCell_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\] for cell '(.*)'$""")
3377 _FinishedParsingXDCFileForCell_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\] for cell '(.*)'$""")
3379 _commonXDCFiles: Dict[Path, List[VivadoMessage]]
3380 _perCellXDCFiles: Dict[Path, Dict[str, List[VivadoMessage]]]
3382 def __init__(self, processor: "Processor") -> None:
3383 super().__init__(processor)
3385 self._commonXDCFiles = {}
3386 self._perCellXDCFiles = {}
3388 @readonly
3389 def CommonXDCFiles(self) -> Dict[Path, List[VivadoMessage]]:
3390 return self._commonXDCFiles
3392 @readonly
3393 def PerCellXDCFiles(self) -> Dict[Path, Dict[str, List[VivadoMessage]]]:
3394 return self._perCellXDCFiles
3396 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3397 line = yield from self._CommandStart(line)
3399 end = f"{self._TCL_COMMAND} "
3400 while True:
3401 if line._kind is LineKind.Empty:
3402 line = yield line
3403 continue
3404 elif isinstance(line, VivadoMessage):
3405 self._AddMessage(line)
3406 elif (match := self._ParsingXDCFile_Pattern.match(line._message)) is not None:
3407 line._kind = LineKind.Normal
3409 path = Path(match[1])
3410 self._commonXDCFiles[path] = (messages := [])
3412 line = yield line
3413 while True:
3414 if line._kind is LineKind.Empty: 3414 ↛ 3415line 3414 didn't jump to line 3415 because the condition on line 3414 was never true
3415 line = yield line
3416 continue
3417 elif isinstance(line, VivadoMessage):
3418 self._AddMessage(line)
3419 messages.append(line)
3420 elif (match := self._FinishedParsingXDCFile_Pattern.match(line._message)) is not None and path == Path(match[1]):
3421 line._kind = LineKind.Normal
3422 break
3423 elif line.StartsWith("Finished Parsing XDC File"): 3423 ↛ 3424line 3423 didn't jump to line 3424 because the condition on line 3423 was never true
3424 line._kind = LineKind.ProcessorError
3425 break
3426 elif line.StartsWith(end): 3426 ↛ 3427line 3426 didn't jump to line 3427 because the condition on line 3426 was never true
3427 break
3429 line = yield line
3430 elif (match := self._ParsingXDCFileForCell_Pattern.match(line._message)) is not None:
3431 line._kind = LineKind.Normal
3433 path = Path(match[1])
3434 cell = match[2]
3435 if path in self._perCellXDCFiles:
3436 self._perCellXDCFiles[path][cell] = (messages := [])
3437 else:
3438 self._perCellXDCFiles[path] = {cell: (messages := [])}
3440 line = yield line
3441 while True:
3442 if line._kind is LineKind.Empty: 3442 ↛ 3443line 3442 didn't jump to line 3443 because the condition on line 3442 was never true
3443 line = yield line
3444 continue
3445 elif isinstance(line, VivadoMessage):
3446 self._AddMessage(line)
3447 messages.append(line)
3448 elif (match := self._FinishedParsingXDCFileForCell_Pattern.match(line._message)) is not None and path == Path(match[1]) and cell == match[2]:
3449 line._kind = LineKind.Normal
3450 break
3451 elif line.StartsWith("Finished Parsing XDC File"): 3451 ↛ 3452line 3451 didn't jump to line 3452 because the condition on line 3451 was never true
3452 line._kind = LineKind.ProcessorError
3453 break
3454 elif line.StartsWith(end): 3454 ↛ 3455line 3454 didn't jump to line 3455 because the condition on line 3454 was never true
3455 break
3457 line = yield line
3459 if line.StartsWith(end):
3460 nextLine = yield from self._CommandFinish(line)
3461 return nextLine
3463 line = yield line
3466@export
3467class Opt_Design(CommandWithTasks):
3468 """
3469 A Vivado command output parser for ``opt_design``.
3470 """
3471 from . import OptimizeDesign as _OptDesign
3473 _TCL_COMMAND: ClassVar[str] = "opt_design"
3474 _TIME: ClassVar[str] = None
3476 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3477 _OptDesign.DRCTask,
3478 _OptDesign.CacheTimingInformationTask,
3479 _OptDesign.LogicOptimizationTask,
3480 _OptDesign.PowerOptimizationTask,
3481 _OptDesign.FinalCleanupTask,
3482 _OptDesign.NetlistObfuscationTask
3483 )
3485 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3486 line = yield from self._CommandStart(line)
3488 activeParsers: List[Task] = list(self._tasks.values())
3490 while True:
3491 while True:
3492 if line._kind is LineKind.Empty:
3493 line = yield line
3494 continue
3495 elif isinstance(line, VivadoMessage):
3496 self._AddMessage(line)
3497 elif line.StartsWith("Starting ") and not line.StartsWith("Starting Connectivity Check Task"):
3498 for parser in activeParsers: # type: Section 3498 ↛ 3503line 3498 didn't jump to line 3503 because the loop on line 3498 didn't complete
3499 if line.StartsWith(parser._START):
3500 line = yield next(task := parser.Generator(line))
3501 break
3502 else:
3503 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3504 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3505 # ex.add_note(f"Current task: start pattern='{self._task}'")
3506 ex.add_note(f"Current cmd: {self}")
3507 raise ex
3508 break
3509 elif line.StartsWith(self._TCL_COMMAND):
3510 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3510 ↛ 3519line 3510 didn't jump to line 3519 because the condition on line 3510 was always true
3511 line._kind |= LineKind.Success
3513 # FIXME: use similar style like for _TIME
3514 line = yield line
3515 lastLine = yield line
3516 return lastLine
3517 # line._kind = LineKind.Unprocessed
3519 line = yield line
3521 while True:
3522 # if line.StartsWith("Ending"):
3523 # line = yield task.send(line)
3524 # break
3526 if isinstance(line, VivadoMessage):
3527 self._AddMessage(line)
3529 try:
3530 line = yield task.send(line)
3531 except StopIteration as ex:
3532 task = None
3533 line = ex.value
3535 if isinstance(line, VivadoMessage):
3536 line = yield line
3538 break
3540 if task is not None: 3540 ↛ 3541line 3540 didn't jump to line 3541 because the condition on line 3540 was never true
3541 line = yield task.send(line)
3543 activeParsers.remove(parser)
3546@export
3547class Place_Design(CommandWithTasks):
3548 """
3549 A Vivado command output parser for ``place_design``.
3550 """
3551 from . import PlaceDesign as _PlaceDesign
3553 _TCL_COMMAND: ClassVar[str] = "place_design"
3554 _TIME: ClassVar[str] = None
3556 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3557 _PlaceDesign.PlacerTask,
3558 )
3560 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3561 line = yield from self._CommandStart(line)
3563 activeParsers: List[Task] = list(self._tasks.values())
3565 while True:
3566 while True:
3567 if line._kind is LineKind.Empty:
3568 line = yield line
3569 continue
3570 elif isinstance(line, VivadoMessage):
3571 self._AddMessage(line)
3572 elif line.StartsWith("Starting "):
3573 for parser in activeParsers: # type: Section 3573 ↛ 3578line 3573 didn't jump to line 3578 because the loop on line 3573 didn't complete
3574 if line.StartsWith(parser._START): 3574 ↛ 3573line 3574 didn't jump to line 3573 because the condition on line 3574 was always true
3575 line = yield next(task := parser.Generator(line))
3576 break
3577 else:
3578 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3579 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3580 # ex.add_note(f"Current task: start pattern='{self._task}'")
3581 ex.add_note(f"Current cmd: {self}")
3582 raise ex
3583 break
3584 elif line.StartsWith(self._TCL_COMMAND):
3585 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3585 ↛ 3594line 3585 didn't jump to line 3594 because the condition on line 3585 was always true
3586 line._kind |= LineKind.Success
3588 # FIXME: use similar style like for _TIME
3589 line = yield line
3590 lastLine = yield line
3591 return lastLine
3592 # line._kind = LineKind.Unprocessed
3594 line = yield line
3596 while True:
3597 # if line.StartsWith("Ending"):
3598 # line = yield task.send(line)
3599 # break
3601 if isinstance(line, VivadoMessage):
3602 self._AddMessage(line)
3604 try:
3605 line = yield task.send(line)
3606 except StopIteration as ex:
3607 task = None
3608 line = ex.value
3610 if isinstance(line, VivadoMessage):
3611 line = yield line
3613 break
3615 if task is not None: 3615 ↛ 3616line 3615 didn't jump to line 3616 because the condition on line 3615 was never true
3616 line = yield task.send(line)
3618 activeParsers.remove(parser)
3621@export
3622class PhyOpt_Design(CommandWithTasks):
3623 """
3624 A Vivado command output parser for ``phy_opt_design``.
3625 """
3626 from . import PhysicalOptimizeDesign as _PhyOptDesign
3628 _TCL_COMMAND: ClassVar[str] = "phys_opt_design"
3629 _TIME: ClassVar[str] = None
3631 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3632 _PhyOptDesign.InitialUpdateTimingTask,
3633 _PhyOptDesign.PhysicalSynthesisTask
3634 )
3636 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3637 line = yield from self._CommandStart(line)
3639 activeParsers: List[Task] = list(self._tasks.values())
3641 while True:
3642 while True:
3643 if line._kind is LineKind.Empty:
3644 line = yield line
3645 continue
3646 elif isinstance(line, VivadoMessage):
3647 self._AddMessage(line)
3648 elif line.StartsWith("Starting "):
3649 for parser in activeParsers: # type: Section 3649 ↛ 3654line 3649 didn't jump to line 3654 because the loop on line 3649 didn't complete
3650 if line.StartsWith(parser._START): 3650 ↛ 3649line 3650 didn't jump to line 3649 because the condition on line 3650 was always true
3651 line = yield next(task := parser.Generator(line))
3652 break
3653 else:
3654 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3655 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3656 # ex.add_note(f"Current task: start pattern='{self._task}'")
3657 ex.add_note(f"Current cmd: {self}")
3658 raise ex
3659 break
3660 elif line.StartsWith(self._TCL_COMMAND):
3661 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3661 ↛ 3670line 3661 didn't jump to line 3670 because the condition on line 3661 was always true
3662 line._kind |= LineKind.Success
3664 # FIXME: use similar style like for _TIME
3665 line = yield line
3666 lastLine = yield line
3667 return lastLine
3668 # line._kind = LineKind.Unprocessed
3670 line = yield line
3672 while True:
3673 # if line.StartsWith("Ending"):
3674 # line = yield task.send(line)
3675 # break
3677 if isinstance(line, VivadoMessage):
3678 self._AddMessage(line)
3680 try:
3681 line = yield task.send(line)
3682 except StopIteration as ex:
3683 task = None
3684 line = ex.value
3686 if isinstance(line, VivadoMessage): 3686 ↛ 3689line 3686 didn't jump to line 3689 because the condition on line 3686 was always true
3687 line = yield line
3689 break
3691 if task is not None: 3691 ↛ 3692line 3691 didn't jump to line 3692 because the condition on line 3691 was never true
3692 line = yield task.send(line)
3694 activeParsers.remove(parser)
3697@export
3698class Route_Design(CommandWithTasks):
3699 """
3700 A Vivado command output parser for ``route_design``.
3701 """
3702 from . import RouteDesign as _RouteDesign
3704 _TCL_COMMAND: ClassVar[str] = "route_design"
3705 _TIME: ClassVar[str] = "Time (s):"
3707 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3708 _RouteDesign.RoutingTask,
3709 )
3711 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3712 line = yield from self._CommandStart(line)
3714 activeParsers: List[Task] = list(self._tasks.values())
3716 while True:
3717 while True:
3718 if line._kind is LineKind.Empty:
3719 line = yield line
3720 continue
3721 elif isinstance(line, VivadoMessage):
3722 self._AddMessage(line)
3723 elif line.StartsWith("Starting "):
3724 for parser in activeParsers: # type: Section 3724 ↛ 3729line 3724 didn't jump to line 3729 because the loop on line 3724 didn't complete
3725 if line.StartsWith(parser._START): 3725 ↛ 3724line 3725 didn't jump to line 3724 because the condition on line 3725 was always true
3726 line = yield next(task := parser.Generator(line))
3727 break
3728 else:
3729 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3730 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3731 # ex.add_note(f"Current task: start pattern='{self._task}'")
3732 ex.add_note(f"Current cmd: {self}")
3733 raise ex
3734 break
3735 elif line.StartsWith(self._TCL_COMMAND):
3736 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 3736 ↛ 3745line 3736 didn't jump to line 3745 because the condition on line 3736 was always true
3737 line._kind |= LineKind.Success
3739 # FIXME: use similar style like for _TIME
3740 line = yield line
3741 lastLine = yield line
3742 return lastLine
3743 # line._kind = LineKind.Unprocessed
3745 line = yield line
3747 while True:
3748 # if line.StartsWith("Ending"):
3749 # line = yield task.send(line)
3750 # break
3752 if isinstance(line, VivadoMessage):
3753 self._AddMessage(line)
3755 try:
3756 line = yield task.send(line)
3757 except StopIteration as ex:
3758 task = None
3759 line = ex.value
3761 if isinstance(line, VivadoMessage): 3761 ↛ 3762line 3761 didn't jump to line 3762 because the condition on line 3761 was never true
3762 line = yield line
3764 break
3766 if task is not None: 3766 ↛ 3767line 3766 didn't jump to line 3767 because the condition on line 3766 was never true
3767 line = yield task.send(line)
3769 activeParsers.remove(parser)
3772@export
3773class Write_Bitstream(Command):
3774 """
3775 A Vivado command output parser for ``write_bitstream``.
3776 """
3777 _TCL_COMMAND: ClassVar[str] = "write_bitstream"
3778 _TIME: ClassVar[str] = "Time (s):"
3781@export
3782class Report_DRC(Command):
3783 """
3784 A Vivado command output parser for ``report_drc``.
3785 """
3786 _TCL_COMMAND: ClassVar[str] = "report_drc"
3787 _TIME: ClassVar[str] = "Time (s):"
3790@export
3791class Report_Methodology(Command):
3792 """
3793 A Vivado command output parser for ``report_methodology``.
3794 """
3795 _TCL_COMMAND: ClassVar[str] = "report_methodology"
3796 _TIME: ClassVar[str] = None
3799@export
3800class Report_Power(Command):
3801 """
3802 A Vivado command output parser for ``report_power``.
3803 """
3804 _TCL_COMMAND: ClassVar[str] = "report_power"
3805 _TIME: ClassVar[str] = None
3808@export
3809class Open_Checkpoint(Command):
3810 """
3811 A Vivado command output parser for ``open_checkpoint``.
3812 """
3813 _TCL_COMMAND: ClassVar[str] = "open_checkpoint"
3814 _TIME: ClassVar[str] = "Time (s):"
3816 def _CommandFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
3817 end = f"{self._TCL_COMMAND}: {self._TIME}"
3819 if line.StartsWith(end): 3819 ↛ 3823line 3819 didn't jump to line 3823 because the condition on line 3819 was always true
3820 line._kind = LineKind.TaskTime
3821 line = yield line
3822 else:
3823 pass # FIXME: error
3825 return line
3828@export
3829class VivadoProcessor(VivadoMessagesMixin, mixin=True):
3830 """
3831 A processor for Vivado log outputs.
3833 Each output line from Vivado gets processed and converted into a :class:`ProcessedLine` objects. Such lines form a
3834 doubly-linked list.
3835 """
3836 _duration: float #: Duration of the observed process (e.g. start to end of synthesis).
3837 _processingDuration: float #: Duration for the log output processor to parse all log messages.
3839 _lines: List[VivadoLine] #: A list of processed log message lines.
3840 _preamble: Nullable[Preamble] #: Reference to the Vivado preamble written after tool startup.
3841 _postamble: Nullable[Postamble] #: Reference to the Vivado postamble written after tool startup.
3842 _commands: Dict[Type[Command], Command] #: A dictionary of processed Vivado commands.
3844 def __init__(self) -> None:
3845 """
3846 Initializes a Vivado log output processor.
3847 """
3848 super().__init__()
3850 self._duration = 0.0
3851 self._processingDuration = 0.0
3853 self._lines = []
3854 self._preamble = None
3855 self._postamble = None
3856 self._commands = {}
3858 @readonly
3859 def Lines(self) -> List[VivadoLine]:
3860 """
3861 Read-only property to access the list of processed and classified log lines (messages).
3863 :returns: A list of processed lines.
3864 """
3865 return self._lines
3867 @readonly
3868 def Preamble(self) -> Nullable[Preamble]:
3869 """
3870 Read-only property to access the parsed preamble information.
3872 :returns: The log's output preamble.
3873 """
3874 return self._preamble
3876 @readonly
3877 def Postamble(self) -> Nullable[Postamble]:
3878 """
3879 Read-only property to access the parsed postamble information.
3881 :returns: The log's output postamble.
3882 """
3883 return self._postamble
3885 @readonly
3886 def Commands(self) -> Dict[Type[Command], Command]:
3887 """
3888 Read-only property to access the dictionary of processed Vivado commands.
3890 :returns: The dictionary of processed Vivado commands.
3891 """
3892 return self._commands
3894 @readonly
3895 def StartDateTime(self) -> datetime:
3896 if self._preamble is None: 3896 ↛ 3897line 3896 didn't jump to line 3897 because the condition on line 3896 was never true
3897 raise ValueError(f"Preamble was not found when parsing the Vivado log outputs.")
3899 return self._preamble.StartDateTime
3901 @readonly
3902 def ExitDateTime(self) -> datetime:
3903 if self._postamble is None: 3903 ↛ 3904line 3903 didn't jump to line 3904 because the condition on line 3903 was never true
3904 raise ValueError(f"Postamble was not found when parsing the Vivado log outputs.")
3906 return self._postamble.ExitDateTime
3908 @readonly
3909 def Duration(self) -> float:
3910 """
3911 Duration of the observed process (e.g. start to end of synthesis).
3913 :returns: The observed process' execution duration in seconds.
3914 """
3915 return (self.ExitDateTime - self.StartDateTime).total_seconds()
3917 @readonly
3918 def ProcessingDuration(self) -> float:
3919 """
3920 Processing duration for the log output processor to parse all log messages.
3922 :returns: The processing duration in seconds.
3923 """
3924 return self._processingDuration
3926 def __contains__(self, key: Type[Command]) -> bool:
3927 """
3928 Returns True, if log outputs where found for the given command.
3930 :param key: Vivado command (class).
3931 :returns: True, if the Vivado command's outputs were found in log outputs.
3932 """
3933 if not issubclass(key, Command): 3933 ↛ 3934line 3933 didn't jump to line 3934 because the condition on line 3933 was never true
3934 ex = TypeError(f"Parameter 'key' is not a Command.")
3935 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
3936 raise ex
3938 return key in self._commands
3940 def __getitem__(self, key: Type[Command]) -> Command:
3941 """
3942 Access Vivado command specific log outputs and parsed data by the command.
3944 :param key: Vivado command (class) to access.
3945 :returns: A Vivado command instance with parsed log messages and extracted data.
3946 """
3947 if not issubclass(key, Command): 3947 ↛ 3948line 3947 didn't jump to line 3948 because the condition on line 3947 was never true
3948 ex = TypeError(f"Parameter 'key' is not a Command.")
3949 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
3950 raise ex
3952 try:
3953 return self._commands[key]
3954 except KeyError as ex:
3955 raise CommandNotPresentException(F"Command '{key._TCL_COMMAND}' not present in '{self._logfile}'.") from ex
3957 @readonly
3958 def IsIncompleteLog(self) -> bool:
3959 """
3960 Read-only property returning true if the processed Vivado log output is incomplete.
3962 A log can be incomplete, because:
3964 * Vivado disabled messages, because too many messages of the same kind appeared. Usually, a message type is disabled
3965 after 100 messages of that type. This is indicated by message ``[Common 17-14]``.
3967 :returns: True, if messages where silenced by Vivado.
3969 .. note::
3971 .. code-block::
3973 INFO: [Common 17-14] Message 'Synth 8-3321' appears 100 times and further instances of the messages will be
3974 disabled. Use the Tcl command set_msg_config to change the current settings.
3975 """
3976 return 17 in self._messagesByID and 14 in self._messagesByID[17]
3978 def CommandFinder(self, line: Nullable[VivadoLine] = None) -> Generator[VivadoLine, VivadoLine, None]:
3979 tclProcedures = {"source"}
3981 self._preamble = Preamble(self)
3982 self._postamble = Postamble(self)
3984 # wait for first line
3985 if line is None:
3986 line = yield
3988 # process preamble
3989 line = yield from self._preamble.Generator(line)
3991 while True:
3992 while True:
3993 if line._kind is LineKind.Empty:
3994 line = yield line
3995 continue
3996 elif isinstance(line, VivadoInfoMessage):
3997 if line.ToolID == 17 and line.MessageKindID == 206:
3998 lastLine = yield from self._postamble.Generator(line)
3999 return lastLine
4000 elif isinstance(line, VivadoTclCommand):
4001 if line._tclCommand == Synth_Design._TCL_COMMAND:
4002 self._commands[Synth_Design] = (cmd := Synth_Design(self))
4003 line = yield next(gen := cmd.SectionDetector(line))
4004 break
4005 elif line._tclCommand == Link_Design._TCL_COMMAND:
4006 self._commands[Link_Design] = (cmd := Link_Design(self))
4007 line = yield next(gen := cmd.SectionDetector(line))
4008 break
4009 elif line._tclCommand == Opt_Design._TCL_COMMAND:
4010 self._commands[Opt_Design] = (cmd := Opt_Design(self))
4011 line = yield next(gen := cmd.SectionDetector(line))
4012 break
4013 elif line._tclCommand == Place_Design._TCL_COMMAND:
4014 self._commands[Place_Design] = (cmd := Place_Design(self))
4015 line = yield next(gen := cmd.SectionDetector(line))
4016 break
4017 elif line._tclCommand == PhyOpt_Design._TCL_COMMAND:
4018 self._commands[PhyOpt_Design] = (cmd := PhyOpt_Design(self))
4019 line = yield next(gen := cmd.SectionDetector(line))
4020 break
4021 elif line._tclCommand == Route_Design._TCL_COMMAND:
4022 self._commands[Route_Design] = (cmd := Route_Design(self))
4023 line = yield next(gen := cmd.SectionDetector(line))
4024 break
4025 elif line._tclCommand == Write_Bitstream._TCL_COMMAND:
4026 self._commands[Write_Bitstream] = (cmd := Write_Bitstream(self))
4027 line = yield next(gen := cmd.SectionDetector(line))
4028 break
4029 elif line._tclCommand == Open_Checkpoint._TCL_COMMAND:
4030 self._commands[Open_Checkpoint] = (cmd := Open_Checkpoint(self))
4031 line = yield next(gen := cmd.SectionDetector(line))
4032 break
4033 elif line._tclCommand == Report_DRC._TCL_COMMAND:
4034 self._commands[Report_DRC] = (cmd := Report_DRC(self))
4035 line = yield next(gen := cmd.SectionDetector(line))
4036 break
4037 elif line._tclCommand == Report_Methodology._TCL_COMMAND:
4038 self._commands[Report_Methodology] = (cmd := Report_Methodology(self))
4039 line = yield next(gen := cmd.SectionDetector(line))
4040 break
4041 elif line._tclCommand == Report_Power._TCL_COMMAND:
4042 self._commands[Report_Power] = (cmd := Report_Power(self))
4043 line = yield next(gen := cmd.SectionDetector(line))
4044 break
4045 elif isinstance(line, DateTimeLine):
4046 if (match := Launch._LAUNCHED.match(line._message)) is not None: 4046 ↛ 4053line 4046 didn't jump to line 4053 because the condition on line 4046 was always true
4047 launchName = match["launchName"]
4048 self._nestedLaunches.append(launch := Launch(launchName, parent=self))
4050 line = yield next(gen := launch.Parser(line))
4051 break
4052 else:
4053 pass
4055 firstWord = line.Partition(" ")[0]
4056 if firstWord in tclProcedures:
4057 line = TclCommand.FromLine(line)
4059 line = yield line
4061 # end = f"{cmd._TCL_COMMAND} completed successfully"
4063 while True:
4064 # if line.StartsWith(end):
4065 # # line._kind |= LineKind.Success
4066 # lastLine = gen.send(line)
4067 # if LineKind.Last in line._kind:
4068 # line._kind ^= LineKind.Last
4069 # line = yield lastLine
4070 # break
4072 try:
4073 line = yield gen.send(line)
4074 except StopIteration as ex:
4075 line = ex.value
4076 break
4079@export
4080class Processor(VivadoProcessor):
4081 _nestedLaunches: List["Launch"] #: Nested Vivado launches (e.g. ``synth_1``/``impl_1``)
4083 def __init__(self) -> None:
4084 """
4085 Initializes a Vivado log output processor (toplevel processor).
4086 """
4087 super().__init__()
4089 self._nestedLaunches = []
4091 @readonly
4092 def HasNestedLaunches(self) -> bool:
4093 """
4094 Read-only property returnning true, if this processor encountered nested launches.
4096 :returns: True, if nested launches were found.
4098 .. seealso::
4100 :data:`NestedLaunches`
4101 Access the list of nested launches.
4102 """
4103 return len(self._nestedLaunches) > 0
4105 @readonly
4106 def NestedLaunches(self) -> List["Launch"]:
4107 """
4108 Read-only property to access nested launches.
4110 .. hint::
4112 A nested launch gets created when the outer Vivado instance starts another nested Vivado instance and waits on
4113 its completion. Examples are typically ``synth_1`` and ``impl_1``.
4115 :returns: The list of nested launches.
4117 .. seealso::
4119 :data:`HasNestedLaunches`
4120 Check if nested launches have been found.
4121 """
4122 return self._nestedLaunches
4124 def LineClassification(self, inputStream: Iterator[Tuple[datetime, str]]) -> Generator[VivadoLine, None, None]:
4125 # Instantiate and initialize CommandFinder
4126 next(cmdFinder := self.CommandFinder())
4128 lastLine = None
4129 lineNumber = 0
4130 _errorMessage = "Unknown processing error"
4132 try:
4133 while (tup := next(inputStream)) is not None: 4133 ↛ exitline 4133 didn't return from function 'LineClassification' because the condition on line 4133 was always true
4134 timestamp, rawMessageLine = tup
4135 lineNumber += 1
4136 rawMessageLine = rawMessageLine.rstrip()
4137 errorMessage = _errorMessage
4139 if len(rawMessageLine) == 0:
4140 line = VivadoLine(lineNumber, LineKind.Empty, LineAction.Default, rawMessageLine, previousLine=lastLine)
4141 elif rawMessageLine.startswith("INFO"):
4142 if (line := VivadoInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
4143 if (line := VivadoDRCInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
4144 if (line := VivadoIrregularInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
4145 line = VivadoStuntedInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
4147 errorMessage = f"Line starting with 'INFO' was not a VivadoInfoMessage."
4148 elif rawMessageLine.startswith("WARNING"):
4149 if (line := VivadoWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
4150 if (line := VivadoDRCWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 4150 ↛ 4154line 4150 didn't jump to line 4154 because the condition on line 4150 was always true
4151 if (line := VivadoXPMWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None: 4151 ↛ 4152line 4151 didn't jump to line 4152 because the condition on line 4151 was never true
4152 line = VivadoStuntedWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
4154 errorMessage = f"Line starting with 'WARNING' was not a VivadoWarningMessage."
4155 elif rawMessageLine.startswith("CRITICAL WARNING"):
4156 line = VivadoCriticalWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
4158 errorMessage = f"Line starting with 'CRITICAL WARNING' was not a VivadoCriticalWarningMessage."
4159 elif rawMessageLine.startswith("ERROR"):
4160 line = VivadoErrorMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
4162 errorMessage = f"Line starting with 'ERROR' was not a VivadoErrorMessage."
4163 elif rawMessageLine.startswith("Command: "):
4164 line = VivadoTclCommand.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
4166 errorMessage = "Line starting with 'Command:' was not a VivadoTclCommand."
4167 elif (match := DateTimeLine._PREFIX.match(rawMessageLine)) is not None:
4168 dateTime = datetime.strptime(match["datetime"], "%a %b %d %H:%M:%S %Y")
4169 line = DateTimeLine(lineNumber, LineKind.DateTimeLine, LineAction.Default, dateTime, match["message"], previousLine=lastLine)
4170 else:
4171 line = VivadoLine(lineNumber, LineKind.Unprocessed, LineAction.Default, rawMessageLine, previousLine=lastLine)
4173 if line.StartsWith("Resolution:") and isinstance(lastLine, VivadoMessage):
4174 line._kind = LineKind.Verbose
4176 if line is None: 4176 ↛ 4178line 4176 didn't jump to line 4178 because the condition on line 4176 was never true
4177 # TODO: what to do with this line? attache to exception?
4178 line = VivadoLine(lineNumber, LineKind.ProcessorError, LineAction.Default, rawMessageLine, previousLine=lastLine)
4180 raise ClassificationException(errorMessage, lineNumber, rawMessageLine)
4182 if isinstance(line, VivadoMessage):
4183 self._AddMessage(line)
4185 line = cmdFinder.send(line)
4187 if line._kind is LineKind.ProcessorError: 4187 ↛ 4188line 4187 didn't jump to line 4188 because the condition on line 4187 was never true
4188 line = ClassificationException(errorMessage, lineNumber, rawMessageLine)
4190 # TODO: find a better solution/location to assign the timestamp.
4191 line._timestamp = timestamp
4193 self._lines.append(line)
4195 lastLine = line
4196 yield line
4198 except StopIteration:
4199 pass
4202@export
4203class Document(Processor):
4204 """
4205 A Vivado log output processor for a log file.
4207 This processor represents a Vivado log file (e.g. ``*.vds`` or ``*.vdi``). It processees its content line-by-line
4208 while classifying each line as a message. The processing duration is available via :data:`ProcessingDuration`.
4209 """
4210 _logfile: Path #: Path to the processed logfile.
4212 # FIXME: parse=True parameter
4213 def __init__(self, logfile: Path) -> None:
4214 """
4215 Initializes a log file.
4217 :param logfile: Path to the log file.
4218 """
4219 super().__init__()
4221 # FIXME: check if path
4222 self._logfile = logfile
4224 @readonly
4225 def Logfile(self) -> Path:
4226 """
4227 Read-only property to access the document's path.
4229 :returns: Path to the log file.
4230 """
4231 return self._logfile
4233 def Parse(self) -> None:
4234 with Stopwatch() as sw:
4235 timestamp = datetime.fromtimestamp(self._logfile.stat().st_mtime)
4236 with self._logfile.open("r", encoding="utf-8") as file:
4237 for line in self.LineClassification(timestampIterator(file, timestamp)):
4238 pass
4240 self._processingDuration = sw.Duration
4243@export
4244class Launch(Parser, VivadoProcessor):
4245 _LAUNCHED: ClassVar[Pattern] = re_compile(r"^Launched (?P<launchName>.+?)\.\.\.")
4246 _LOGFILE: ClassVar[Pattern] = re_compile(r"^Run output will be captured here: (?P<logfile>.+)")
4247 _WAITING: ClassVar[Pattern] = re_compile(r"^Waiting for (?P<launchName>.+?) to finish(?: \(timeout in (?P<timeout>\d+) minutes\))?\.\.\.")
4248 _RUNNING: ClassVar[Pattern] = re_compile(r"^\*\*\*+\s+Running vivado")
4249 _WITH_ARGS: ClassVar[Pattern] = re_compile(r"^\s+with args (?P<arguments>.+)")
4250 _FINISHED: ClassVar[Pattern] = re_compile(r"^(?P<launchName>.+?) finished")
4251 _TIME: ClassVar[str] = "wait_on_runs: Time (s):"
4253 _name: str #: Name of the launch.
4254 _logfile: Nullable[Path] #: Logfile used by nested Vivado instance.
4255 _vivadoArguments: Nullable[List[str]] #: Launch parameters passed to nested Vivado instance.
4256 _timeout: int #: Timeout in minutes.
4257 _startDateTime: datetime #: Date and time when the nested Vivado instance was started.
4258 _finishDateTime: datetime #: Date and time when the nested Vivado instance finished.
4260 def __init__(self, name: str, parent: VivadoProcessor) -> None:
4261 super().__init__(parent)
4262 VivadoProcessor.__init__(self)
4264 self._name = name
4265 self._logfile = None
4266 self._vivadoArguments = None
4267 self._timeout = 0
4268 self._startDateTime = None
4269 self._finishDateTime = None
4271 @readonly
4272 def Name(self) -> str:
4273 return self._name
4275 @readonly
4276 def Logfile(self) -> Path:
4277 return self._logfile
4279 @readonly
4280 def VivadoArguments(self) -> List[str]:
4281 return self._vivadoArguments
4283 @readonly
4284 def Timeout(self) -> int:
4285 return self._timeout
4287 @readonly
4288 def LaunchDateTime(self) -> datetime:
4289 return self._startDateTime
4291 @readonly
4292 def FinishDateTime(self) -> datetime:
4293 return self._finishDateTime
4295 @readonly
4296 def Duration(self) -> float:
4297 """
4298 Duration of the observed nested Vivado instance.
4300 :returns: The observed nested Vivado instance's execution duration in seconds.
4301 """
4302 return (self.FinishDateTime - self.LaunchDateTime).total_seconds()
4304 def Parser(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
4305 if isinstance(line, DateTimeLine): 4305 ↛ 4309line 4305 didn't jump to line 4309 because the condition on line 4305 was always true
4306 line._kind = LineKind.LaunchStart
4307 self._startDateTime = line._dateTime
4308 else:
4309 raise TypeError(f"Expected type DateTimeLine for first line.")
4311 if self._LAUNCHED.match(line._message) is None: 4311 ↛ 4312line 4311 didn't jump to line 4312 because the condition on line 4311 was never true
4312 raise ValueError(f"Expected a 'Launched xxx...' line, but git '{line._message}'.")
4314 while True:
4315 line = yield line
4317 if isinstance(line, DateTimeLine):
4318 if (match := self._WAITING.match(line._message)) is not None: 4318 ↛ 4328line 4318 didn't jump to line 4328 because the condition on line 4318 was always true
4319 line._kind = LineKind.LaunchStart
4320 if (launchName := match["launchName"]) != self._name: 4320 ↛ 4321line 4320 didn't jump to line 4321 because the condition on line 4320 was never true
4321 WarningCollector.Raise(ProcessorCriticalWarning(f"Detected launch name '{launchName}' doesn't match current's launch's name '{self._name}'."))
4322 return line
4324 if (timeout := match["timeout"]) is not None:
4325 self._timeout = int(timeout)
4327 else:
4328 pass
4329 elif (match := self._LOGFILE.match(line._message)) is not None:
4330 line._kind = LineKind.Normal
4331 self._logfile = Path(match["logfile"])
4332 elif (match := self._RUNNING.match(line._message)) is not None:
4333 line._kind = LineKind.Normal
4334 elif (match := self._WITH_ARGS.match(line._message)) is not None:
4335 line._kind = LineKind.LaunchArguments
4336 self._vivadoArguments = match["arguments"].split(" ")
4337 break
4339 # Consume 3 empty lines
4340 line = yield line
4341 line = yield line
4342 line = yield line
4344 try:
4345 processedLine = next(gen := self.CommandFinder(line)) # create + prime
4346 while True:
4347 line = yield processedLine
4348 if isinstance(line, VivadoMessage):
4349 self._AddMessage(line)
4351 processedLine = gen.send(line)
4352 except StopIteration as ex:
4353 line = ex.value
4355 # Check for 'finished' line
4356 if isinstance(line, DateTimeLine): 4356 ↛ 4365line 4356 didn't jump to line 4365 because the condition on line 4356 was always true
4357 if (match := self._FINISHED.match(line._message)) is not None: 4357 ↛ 4363line 4357 didn't jump to line 4363 because the condition on line 4357 was always true
4358 line._kind = LineKind.LaunchFinished
4359 self._finishDateTime = line._dateTime
4361 # TODO: check launchName
4362 else:
4363 pass # FIXME: raise error
4364 else:
4365 pass # FIXME: raise error
4367 line = yield line
4369 # Check for 'wait_on_runs' line
4370 if line.StartsWith(self._TIME): 4370 ↛ 4373line 4370 didn't jump to line 4373 because the condition on line 4370 was always true
4371 line._kind = LineKind.LaunchTime
4373 line = yield line
4375 if isinstance(line, VivadoMessage):
4376 if not (line._toolID == 17 and line._messageKindID == 206): # Exiting Vivado
4377 self._AddMessage(line)
4379 line = yield line
4381 return line
4383 def __str__(self) -> str:
4384 return f"Launch: {self._name} (timeout: {self._timeout} minutes)"