__init__.py
Summary
- Lines:
2031 of 2444 covered (83.1%)
- Branches:
497 of 674 taken (73.7%), 137 lines partially
- Coverage:
81.1%
- Directory:
- Missed lines:
158-160, 164, 172-174, 178, 350, 354, 358-360, 384, 388-390, 393, 473-475, 502-513, 563-565, 617, 644-655, 698, 704-706, 747, 754, 758-760, 763, 786, 813-824, 848-859, 893, 897-899, 910, 939, 943, 953-955, 958, 979, 1003, 1007, 1071, 1090, 1172, 1182, 1195, 1237, 1241, 1244, 1246, 1252, 1297, 1312, 1371, 1380, 1418, 1460, 1464-1466, 1472, 1476-1478, 1484, 1520, 1524-1526, 1533-1534, 1541, 1545, 1549, 1595, 1598-1600, 1610, 1620, 1627, 1633, 1636, 1667-1668, 1681, 1709, 1718, 1724, 1730, 1733, 1740-1741, 1745-1750, 1753, 1770-1772, 1779-1780, 1829, 1840, 1855, 1908, 1943, 1947-1949, 1956-1957, 1977-1981, 1987-1989, 2003-2005, 2032, 2036, 2044, 2063-2064, 2070-2072, 2080, 2096, 2100-2102, 2109-2110, 2130-2134, 2156-2158, 2188, 2192, 2203, 2214, 2225-2228, 2256, 2271-2276, 2279-2282, 2305-2310, 2328-2330, 2360, 2373, 2411, 2424-2429, 2432-2435, 2458-2464, 2482-2484, 2516, 2530, 2565, 2578-2583, 2586-2589, 2611-2618, 2637-2639, 2673, 2688, 2710-2711, 2723, 2736-2741, 2744-2747, 2769-2777, 2796-2798, 2825, 2829, 2837, 2854-2872, 2875, 2919, 2922-2927, 2930-2933, 2953-2962, 2967-2970, 2984-2986, 3016, 3020, 3031, 3042, 3057-3058, 3070, 3117-3122, 3139-3143, 3212-3214, 3223-3225, 3234-3236, 3253-3257, 3274-3278, 3284, 3293, 3319-3323, 3392, 3396, 3417-3418, 3426-3427, 3429, 3445-3446, 3454-3455, 3457, 3505-3509, 3543, 3580-3584, 3618, 3656-3660, 3694, 3731-3735, 3764, 3769, 3825, 3894, 3899, 3906, 3946-3948, 3960-3962, 3966-3967, 3988, 4065, 4177, 4205-4207, 4215, 4260, 4311, 4315, 4319, 4336, 4343, 4346, 4355-4356, 4362, 4397-4399, 4418
Source
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 __future__ import annotations
33
34from datetime import datetime
35from enum import Flag, Enum
36from pathlib import Path
37from re import Pattern, compile as re_compile
38from typing import Optional as Nullable, Self, Type, ClassVar, Tuple, List, Dict, Generator, Union, Any, Iterator, cast
39
40from pyTooling.Decorators import export, readonly
41from pyTooling.MetaClasses import ExtendedType, abstractmethod
42from pyTooling.Common import getFullyQualifiedName
43from pyTooling.Stopwatch import Stopwatch, Duration as _Duration
44from pyTooling.Versioning import YearReleaseVersion
45from pyTooling.Warning import WarningCollector, Warning, CriticalWarning
46
47from pyEDAA.OutputFilter import Line, OutputFilterException
48from pyEDAA.OutputFilter import InfoMessage, WarningMessage, CriticalWarningMessage, ErrorMessage
49
50
51__all__ = ["MAJOR", "MAJOR_MINOR", "MAJOR_MINOR_MICRO", "MAJOR_MINOR_MICRO_NANO"]
52
53MAJOR = r"(?P<major>\d+)"
54MAJOR_MINOR = r"(?P<major>\d+)\.(?P<minor>\d+)"
55MAJOR_MINOR_MICRO = r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<micro>\d+)"
56MAJOR_MINOR_MICRO_NANO = r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<micro>\d+)\.(?P<nano>\d+)"
57
58
59@export
60def timestampIterator(iterator: Iterator[str], timestamp: datetime) -> Iterator[Tuple[datetime, str]]:
61 for line in iterator:
62 yield timestamp, line
63
64
65@export
66class ProcessorException(OutputFilterException):
67 """
68 Base-class for exceptions raised by processors parsing log outputs.
69 """
70
71
72@export
73class ProcessorWarnings(Warning):
74 """
75 Base-class for warnings raised by processors parsing log outputs.
76 """
77
78
79@export
80class ProcessorCriticalWarning(CriticalWarning):
81 """
82 Base-class for critical warning raised by processors parsing log outputs.
83 """
84
85
86@export
87class ClassificationException(ProcessorException):
88 """
89 Raised if a log output line couldn't be classified.
90 """
91 _lineNumber: int #: Line number of the unclassified line.
92 _rawMessage: str #: Raw message of the unclassified line.
93
94 def __init__(self, errorMessage: str, lineNumber: int, rawMessageLine: str) -> None:
95 """
96 Initializes a classification exception.
97
98 :param errorMessage: Error message why the line couldn't be classified.
99 :param lineNumber: Line number of the unclassified line.
100 :param rawMessageLine: Raw message of the unclassified line.
101 """
102 super().__init__(errorMessage)
103
104 self._lineNumber = lineNumber
105 self._rawMessage = rawMessageLine
106
107 def __str__(self) -> str:
108 return f"{super().__str__()}: {self._rawMessage} (at line {self._lineNumber})"
109
110
111@export
112class ParserStateException(ProcessorException):
113 """
114 Raised if a log output parser has a broken state.
115 """
116
117
118@export
119class NotPresentException(ProcessorException):
120 pass
121
122
123@export
124class CommandNotPresentException(NotPresentException):
125 pass
126
127
128@export
129class SectionNotPresentException(NotPresentException):
130 pass
131
132
133@export
134class SubSectionNotPresentException(NotPresentException):
135 pass
136
137
138@export
139class SubTaskNotPresentException(NotPresentException):
140 pass
141
142
143@export
144class PhaseNotPresentException(NotPresentException):
145 pass
146
147
148@export
149class NestedTaskNotPresentException(NotPresentException):
150 pass
151
152
153@export
154class UndetectedEnd(ProcessorCriticalWarning):
155 _line: VivadoLine
156
157 def __init__(self, message: str, line: VivadoLine) -> None:
158 super().__init__(message)
159
160 self._line = line
161
162 @readonly
163 def Line(self) -> VivadoLine:
164 return self._line
165
166
167@export
168class UnknownLine(ProcessorWarnings):
169 _line: VivadoLine
170
171 def __init__(self, message: str, line: VivadoLine) -> None:
172 super().__init__(message)
173
174 self._line = line
175
176 @readonly
177 def Line(self) -> VivadoLine:
178 return self._line
179
180
181@export
182class UnknownTask(UnknownLine):
183 pass
184
185
186@export
187class UnknownSubTask(UnknownLine):
188 pass
189
190
191@export
192class UnknownSection(UnknownLine):
193 pass
194
195
196@export
197class UnknownPhase(UnknownLine):
198 pass
199
200
201@export
202class UnknownSubPhase(UnknownLine):
203 pass
204
205
206@export
207class LineKind(Flag):
208 """
209 Classification of a log message line.
210 """
211 Unprocessed = 0
212 ProcessorError = 2** 0
213 Empty = 2** 1
214 Delimiter = 2** 2
215
216 Success = 2** 3
217 Failed = 2** 4
218
219 Verbose = 2**10
220 Normal = 2**11
221 Info = 2**12
222 Warning = 2**13
223 CriticalWarning = 2**14
224 Error = 2**15
225 Fatal = 2**16
226
227 Start = 2**20
228 End = 2**21
229 Header = 2**22
230 Content = 2**23
231 Time = 2**24
232 Footer = 2**25
233
234 Last = 2**28
235
236 DateTimeLine = 2**29
237
238 Message = 2**30
239 InfoMessage = Message | Info
240 WarningMessage = Message | Warning
241 CriticalWarningMessage = Message | CriticalWarning
242 ErrorMessage = Message | Error
243
244 Launch = 2**31
245 LaunchStart = Launch | Start
246 LaunchArguments = Launch | Header
247 LaunchFinished = Launch | End
248 LaunchTime = Launch | Time
249
250 Task = 2**32
251 TaskStart = Task | Start
252 TaskEnd = Task | End
253 TaskTime = Task | Time
254
255 Phase = 2**33
256 PhaseDelimiter = Phase | Delimiter
257 PhaseStart = Phase | Start
258 PhaseEnd = Phase | End
259 PhaseTime = Phase | Time
260 PhaseFinal = Phase | Footer
261
262 SubPhase = 2**34
263 SubPhaseStart = SubPhase | Start
264 SubPhaseEnd = SubPhase | End
265 SubPhaseTime = SubPhase | Time
266
267 SubSubPhase = 2**35
268 SubSubPhaseStart = SubSubPhase | Start
269 SubSubPhaseEnd = SubSubPhase | End
270 SubSubPhaseTime = SubSubPhase | Time
271
272 SubSubSubPhase = 2**36
273 SubSubSubPhaseStart = SubSubSubPhase | Start
274 SubSubSubPhaseEnd = SubSubSubPhase | End
275 SubSubSubPhaseTime = SubSubSubPhase | Time
276
277 NestedTask = 2**37
278 NestedTaskStart = NestedTask | Start
279 NestedTaskEnd = NestedTask | End
280
281 NestedPhase = 2**38
282 NestedPhaseStart = NestedPhase | Start
283 NestedPhaseEnd = NestedPhase | End
284
285 Section = 2**39
286 SectionDelimiter = Section | Delimiter
287 SectionStart = Section | Start
288 SectionEnd = Section | End
289
290 SubSection = 2**40
291 SubSectionDelimiter = SubSection | Delimiter
292 SubSectionStart = SubSection | Start
293 SubSectionEnd = SubSection | End
294
295 Paragraph = 2**41
296 ParagraphHeadline = Paragraph | Header
297
298 Hierarchy = 2**42
299 HierarchyStart = Hierarchy | Start
300 HierarchyEnd = Hierarchy | End
301
302 XDC = 2**43
303 XDCStart = XDC | Start
304 XDCEnd = XDC | End
305
306 Table = 2**44
307 TableFrame = Table | Delimiter
308 TableHeader = Table | Header
309 TableRow = Table | Content
310 TableFooter = Table | Footer
311
312 TclCommand = 2**45
313 GenericTclCommand = TclCommand | 2**0
314 VivadoTclCommand = TclCommand | 2**1
315
316
317@export
318class LineAction(Flag):
319 Default = 0
320 Remove = 1
321
322
323@export
324class VivadoLine(Line[LineKind, LineAction]):
325 """
326 This class represents any line in a log file.
327
328 A line has a line number (:attr:`_lineNumber`), a message (:attr:`__message`) and a message kind (:attr:`__kind`). In
329 addition, all line objects in a log file form a doubly
330 linked list.
331 """
332 _processor: Processor
333 _command: Nullable[Command]
334
335 def __init__(
336 self,
337 lineNumber: int,
338 kind: LineKind,
339 action: LineAction,
340 message: str,
341 previousLine: Nullable[VivadoLine] = None
342 ) -> None:
343 super().__init__(lineNumber, kind, action, message, previousLine)
344
345 self._processor = None
346 self._command = None
347
348 @readonly
349 def Processor(self) -> Processor:
350 return self._processor
351
352 @readonly
353 def Command(self) -> Nullable[Command]:
354 return self._command
355
356 @classmethod
357 def Copy(cls, line: VivadoLine, previousLine: VivadoLine) -> VivadoLine:
358 newLine = cls(line._lineNumber, line._kind, line._action, line._message, previousLine)
359 newLine._timestamp = line._timestamp
360 return newLine
361
362
363@export
364class DateTimeLine(VivadoLine):
365 _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>.*)")
366
367 _dateTime: datetime
368
369 def __init__(
370 self,
371 lineNumber: int,
372 kind: LineKind,
373 action: LineAction,
374 dateTime: datetime,
375 message: str,
376 previousLine: Nullable[VivadoLine] = None
377 ) -> None:
378 super().__init__(lineNumber, kind, action, message, previousLine)
379
380 self._dateTime = dateTime
381
382 @readonly
383 def DateTime(self) -> datetime:
384 return self._dateTime
385
386 @classmethod
387 def Copy(cls, line: DateTimeLine, previousLine: VivadoLine) -> VivadoLine:
388 newLine = cls(line._lineNumber, line._kind, line._action, line._dateTime, line._message, previousLine)
389 newLine._timestamp = line._timestamp
390 return newLine
391
392 def __str__(self) -> str:
393 return f"[{self._dateTime:%a %b} {self._dateTime.day:2d} {self._dateTime:%H:%M:%S %Y}] {self._message}"
394
395
396@export
397class VivadoMessage(VivadoLine):
398 """
399 This class represents an AMD/Xilinx Vivado message.
400
401 The usual message format is:
402
403 .. code-block:: text
404
405 INFO: [Synth 8-7079] Multithreading enabled for synth_design using a maximum of 2 processes.
406 WARNING: [Synth 8-3332] Sequential element (gen[0].Sync/FF2) is unused and will be removed from module sync_Bits_Xilinx.
407
408 The following message severities are defined:
409
410 * ``INFO``
411 * ``WARNING``
412 * ``CRITICAL WARNING``
413 * ``ERROR``
414
415 .. seealso::
416
417 :class:`VivadoInfoMessage`
418 Representing a Vivado info message.
419
420 :class:`VivadoWarningMessage`
421 Representing a Vivado warning message.
422
423 :class:`VivadoCriticalWarningMessage`
424 Representing a Vivado critical warning message.
425
426 :class:`VivadoErrorMessage`
427 Representing a Vivado error message.
428 """
429 # _MESSAGE_KIND: ClassVar[str]
430 # _REGEXP: ClassVar[Pattern]
431
432 _toolName: Nullable[str]
433 _toolID: Nullable[int]
434 _messageKindID: Nullable[int]
435
436 def __init__(
437 self,
438 lineNumber: int,
439 kind: LineKind,
440 action: LineAction,
441 message: str,
442 toolName: Nullable[str] = None,
443 toolID: Nullable[int] = None,
444 messageKindID: Nullable[int] = None,
445 previousLine: Nullable[VivadoLine] = None
446 ) -> None:
447 super().__init__(lineNumber, kind, action, message, previousLine)
448 self._toolName = toolName
449 self._toolID = toolID
450 self._messageKindID = messageKindID
451
452 @readonly
453 def ToolName(self) -> Nullable[str]:
454 return self._toolName
455
456 @readonly
457 def ToolID(self) -> Nullable[int]:
458 return self._toolID
459
460 @readonly
461 def MessageKindID(self) -> Nullable[int]:
462 return self._messageKindID
463
464 @classmethod
465 def Parse(cls, lineNumber: int, kind: LineKind, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
466 if (match := cls._REGEXP.match(rawMessage)) is not None:
467 return cls(lineNumber, kind, LineAction.Default, match[4], match[1], int(match[2]), int(match[3]), previousLine)
468
469 return None
470
471 @classmethod
472 def Copy(cls, line: VivadoMessage, previousLine: VivadoLine) -> VivadoMessage:
473 newLine = cls(line._lineNumber, line._kind, line._action, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
474 newLine._timestamp = line._timestamp
475 return newLine
476
477 def __str__(self) -> str:
478 return f"{self._MESSAGE_KIND}: [{self._toolName} {self._toolID}-{self._messageKindID}] {self._message}"
479
480
481@export
482class VivadoInfoMessage(VivadoMessage, InfoMessage):
483 """
484 This class represents an AMD/Xilinx Vivado info message.
485
486 .. rubric:: Example
487
488 .. code-block::
489
490 INFO: [Common 17-83] 66-Releasing license: Synthesis
491 """
492
493 _MESSAGE_KIND: ClassVar[str] = "INFO"
494 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+) (\d+)-(\d+)\] (.*)""")
495
496 @classmethod
497 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
498 return super().Parse(lineNumber, LineKind.InfoMessage, rawMessage, previousLine)
499
500 @classmethod
501 def FromMessage(cls, line: VivadoMessage) -> Self:
502 message = cls(
503 line._lineNumber,
504 LineKind.InfoMessage,
505 line._message,
506 line._toolName,
507 line._toolID,
508 line._messageKindID,
509 previousLine=line._previousLine)
510 message._nextLine = line._nextLine
511 line._nextLine._previousLine = message
512
513 return message
514
515
516@export
517class VivadoDRCInfoMessage(VivadoMessage, InfoMessage):
518 """
519 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) info message.
520
521 .. rubric:: Example
522
523 .. code-block::
524
525 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'.
526 """
527
528 _MESSAGE_KIND: ClassVar[str] = "INFO"
529 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[DRC (\w+)-(\d+)\] (.*)""")
530
531 _drcRuleName: str
532
533 def __init__(
534 self,
535 lineNumber: int,
536 kind: LineKind,
537 action: LineAction,
538 drcRuleName: str,
539 message: str,
540 toolName: Nullable[str] = None,
541 toolID: Nullable[int] = None,
542 messageKindID: Nullable[int] = None,
543 previousLine: Nullable[VivadoLine] = None
544 ) -> None:
545 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine)
546
547 self._drcRuleName = drcRuleName
548
549 @readonly
550 def DRCRuleName(self) -> str:
551 return self._drcRuleName
552
553 @classmethod
554 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
555 if (match := cls._REGEXP.match(rawMessage)) is not None:
556 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[3], toolName="DRC", toolID=None,
557 messageKindID=int(match[2]), previousLine=previousLine)
558
559 return None
560
561 @classmethod
562 def Copy(cls, line: VivadoDRCInfoMessage, previousLine: VivadoLine) -> VivadoDRCInfoMessage:
563 newLine = cls(line._lineNumber, line._kind, line._action, line._drcRuleName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
564 newLine._timestamp = line._timestamp
565 return newLine
566
567 def __str__(self) -> str:
568 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
569
570
571@export
572class VivadoIrregularInfoMessage(VivadoMessage, InfoMessage):
573 """
574 This class represents an irregular AMD/Xilinx Vivado info message.
575
576 .. rubric:: Example
577
578 .. code-block::
579
580 INFO: [runtcl-4] Executing : report_io -file system_top_io_placed.rpt
581 """
582
583 _MESSAGE_KIND: ClassVar[str] = "INFO"
584 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+)-(\d+)\] (.*)""")
585
586 @classmethod
587 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
588 if (match := cls._REGEXP.match(rawMessage)) is not None:
589 return cls(lineNumber, LineKind.InfoMessage, LineAction.Default, match[3], toolName=match[1], messageKindID=int(match[2]), previousLine=previousLine)
590
591 return None
592
593 def __str__(self) -> str:
594 return f"{self._MESSAGE_KIND}: [{self._toolName}-{self._messageKindID}] {self._message}"
595
596
597@export
598class VivadoStuntedInfoMessage(VivadoMessage, InfoMessage):
599 """
600 This class represents a stunted AMD/Xilinx Vivado info message.
601
602 .. rubric:: Example
603
604 .. code-block::
605
606 INFO: Helper process launched with PID 29056
607 """
608
609 _MESSAGE_KIND: ClassVar[str] = "INFO"
610 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: ([^\[].*)""")
611
612 @classmethod
613 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
614 if (match := cls._REGEXP.match(rawMessage)) is not None:
615 return cls(lineNumber, LineKind.InfoMessage, LineAction.Default, match[1], previousLine=previousLine)
616
617 return None
618
619 def __str__(self) -> str:
620 return f"{self._MESSAGE_KIND}: {self._message}"
621
622
623@export
624class VivadoWarningMessage(VivadoMessage, WarningMessage):
625 """
626 This class represents an AMD/Xilinx Vivado warning message.
627
628 .. rubric:: Example
629
630 .. code-block::
631
632 WARNING: [Synth 8-7080] Parallel synthesis criteria is not met
633 """
634
635 _MESSAGE_KIND: ClassVar[str] = "WARNING"
636 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(\w+(?: \w+)*?) (\d+)-(\d+)\] (.*)""")
637
638 @classmethod
639 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
640 return super().Parse(lineNumber, LineKind.WarningMessage, rawMessage, previousLine=previousLine)
641
642 @classmethod
643 def FromMessage(cls, line: VivadoMessage) -> Self:
644 message = cls(
645 line._lineNumber,
646 LineKind.WarningMessage,
647 line._message,
648 line._toolName,
649 line._toolID,
650 line._messageKindID,
651 previousLine=line._previousLine)
652 message._nextLine = line._nextLine
653 line._nextLine._previousLine = message
654
655 return message
656
657
658@export
659class VivadoDRCWarningMessage(VivadoMessage, WarningMessage):
660 """
661 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) warning message.
662
663 .. rubric:: Example
664
665 .. code-block::
666
667 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.
668 """
669
670 _MESSAGE_KIND: ClassVar[str] = "WARNING"
671 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[DRC (\w+)-(\d+)\] (.*)""")
672
673 _drcRuleName: str
674
675 def __init__(
676 self,
677 lineNumber: int,
678 kind: LineKind,
679 action: LineAction,
680 drcRuleName: str,
681 message: str,
682 toolName: Nullable[str] = None,
683 toolID: Nullable[int] = None,
684 messageKindID: Nullable[int] = None,
685 previousLine: Nullable[VivadoLine] = None
686 ) -> None:
687 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine)
688
689 self._drcRuleName = drcRuleName
690
691 @readonly
692 def DRCRuleName(self) -> str:
693 return self._drcRuleName
694
695 @classmethod
696 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
697 if (match := cls._REGEXP.match(rawMessage)) is not None:
698 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[3], toolName="DRC", toolID=None, messageKindID=int(match[2]), previousLine=previousLine)
699
700 return None
701
702 @classmethod
703 def Copy(cls, line: VivadoDRCWarningMessage, previousLine: VivadoLine) -> VivadoDRCWarningMessage:
704 newLine = cls(line._lineNumber, line._kind, line._action, line._drcRuleName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
705 newLine._timestamp = line._timestamp
706 return newLine
707
708 def __str__(self) -> str:
709 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
710
711
712@export
713class VivadoXPMWarningMessage(VivadoMessage, WarningMessage):
714 """
715 This class represents an AMD/Xilinx Vivado XPM warning message.
716
717 .. rubric:: Example
718
719 .. code-block::
720
721 WARNING: [XPM_CDC_GRAY: TCL-1000] The source and destination clocks are the same.
722 """
723
724 _MESSAGE_KIND: ClassVar[str] = "WARNING"
725 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(XPM_\w+): (\w+)-(\d+)\] (.*)""")
726
727 _xpmName: str
728
729 def __init__(
730 self,
731 lineNumber: int,
732 kind: LineKind,
733 action: LineAction,
734 xpmName: str,
735 message: str,
736 toolName: Nullable[str] = None,
737 toolID: Nullable[int] = None,
738 messageKindID: Nullable[int] = None,
739 previousLine: Nullable[VivadoLine] = None
740 ) -> None:
741 super().__init__(lineNumber, kind, action, message, toolName, toolID, messageKindID, previousLine)
742
743 self._xpmName = xpmName
744
745 @readonly
746 def XPMName(self) -> str:
747 return self._xpmName
748
749 @classmethod
750 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
751 if (match := cls._REGEXP.match(rawMessage)) is not None:
752 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], match[4], toolName=match[2], toolID=None, messageKindID=int(match[3]), previousLine=previousLine)
753
754 return None
755
756 @classmethod
757 def Copy(cls, line: VivadoXPMWarningMessage, previousLine: VivadoLine) -> VivadoXPMWarningMessage:
758 newLine = cls(line._lineNumber, line._kind, line._action, line._xpmName, line._message, line._toolName, line._toolID, line._messageKindID, previousLine)
759 newLine._timestamp = line._timestamp
760 return newLine
761
762 def __str__(self) -> str:
763 return f"{self._MESSAGE_KIND}: [{self._xpmName}: {self._toolName}-{self._messageKindID}] {self._message}"
764
765
766@export
767class VivadoStuntedWarningMessage(VivadoMessage, WarningMessage):
768 """
769 This class represents a stunted AMD/Xilinx Vivado warning message.
770
771 .. rubric:: Example
772
773 .. code-block::
774
775 WARNING: set_property ASYNC_REG could not find object (constraint file /path/to/sync_Bits_Xilinx.xdc, line 5).
776 """
777
778 _MESSAGE_KIND: ClassVar[str] = "WARNING"
779 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: ([^\[].*)""")
780
781 @classmethod
782 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
783 if (match := cls._REGEXP.match(rawMessage)) is not None:
784 return cls(lineNumber, LineKind.WarningMessage, LineAction.Default, match[1], previousLine=previousLine)
785
786 return None
787
788 def __str__(self) -> str:
789 return f"{self._MESSAGE_KIND}: {self._message}"
790
791
792@export
793class VivadoCriticalWarningMessage(VivadoMessage, CriticalWarningMessage):
794 """
795 This class represents an AMD/Xilinx Vivado critical warning message.
796
797 .. rubric:: Example
798
799 .. code-block::
800
801 CRITICAL WARNING: [Constraints 18-1056] Clock 'RefClkA_SFP_Quad' completely overrides clock 'USRCLKA_SFP[P]'.
802 """
803
804 _MESSAGE_KIND: ClassVar[str] = "CRITICAL WARNING"
805 _REGEXP: ClassVar[Pattern] = re_compile(r"""CRITICAL WARNING: \[(\w+) (\d+)-(\d+)\] (.*)""")
806
807 @classmethod
808 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
809 return super().Parse(lineNumber, LineKind.CriticalWarningMessage, rawMessage, previousLine)
810
811 @classmethod
812 def FromMessage(cls, line: VivadoMessage) -> Self:
813 message = cls(
814 line._lineNumber,
815 LineKind.CriticalWarningMessage,
816 line._message,
817 line._toolName,
818 line._toolID,
819 line._messageKindID,
820 previousLine=line._previousLine)
821 message._nextLine = line._nextLine
822 line._nextLine._previousLine = message
823
824 return message
825
826
827@export
828class VivadoErrorMessage(VivadoMessage, ErrorMessage):
829 """
830 This class represents an AMD/Xilinx Vivado error message.
831
832 .. rubric:: Example
833
834 .. code-block::
835
836 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
837 """
838
839 _MESSAGE_KIND: ClassVar[str] = "ERROR"
840 _REGEXP: ClassVar[Pattern] = re_compile(r"""ERROR: \[(\w+) (\d+)-(\d+)\] (.*)""")
841
842 @classmethod
843 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
844 return super().Parse(lineNumber, LineKind.ErrorMessage, rawMessage, previousLine)
845
846 @classmethod
847 def FromMessage(cls, line: VivadoMessage) -> Self:
848 message = cls(
849 line._lineNumber,
850 LineKind.ErrorMessage,
851 line._message,
852 line._toolName,
853 line._toolID,
854 line._messageKindID,
855 previousLine=line._previousLine)
856 message._nextLine = line._nextLine
857 line._nextLine._previousLine = message
858
859 return message
860
861
862@export
863class VHDLReportMessage(VivadoInfoMessage):
864 _REGEXP2: ClassVar[Pattern ] = re_compile(r"""RTL report: "(.*)" \[(.*):(\d+)\]""") # todo: workaround for ClassVar problem
865
866 _reportMessage: str
867 _sourceFile: Path
868 _sourceLineNumber: int
869
870 def __init__(
871 self,
872 lineNumber: int,
873 rawMessage: str,
874 toolName: str,
875 toolID: int,
876 messageKindID: int,
877 reportMessage: str,
878 sourceFile: Path,
879 sourceLineNumber: int,
880 previousLine: Nullable[VivadoLine] = None
881 ) -> None:
882 super().__init__(lineNumber, LineKind.InfoMessage, LineAction.Default, rawMessage, toolName, toolID, messageKindID, previousLine)
883
884 self._reportMessage = reportMessage
885 self._sourceFile = sourceFile
886 self._sourceLineNumber = sourceLineNumber
887
888 @classmethod
889 def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]:
890 if (match := cls._REGEXP2.match(line._message)) is not None:
891 return cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, match[1], Path(match[2]), int(match[3]), previousLine=line._previousLine)
892
893 return None
894
895 @classmethod
896 def Copy(cls, line: VHDLReportMessage, previousLine: VivadoLine) -> VHDLReportMessage:
897 newLine = cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, line._reportMessage, line._sourceFile, line._sourceLineNumber, previousLine)
898 newLine._timestamp = line._timestamp
899 return newLine
900
901@export
902class VHDLAssertionMessage(VHDLReportMessage):
903 _REGEXP3: ClassVar[Pattern ] = re_compile(r"""RTL assertion: "(.*)" \[(.*):(\d+)\]""") # todo: workaround for ClassVar problem
904
905 @classmethod
906 def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]:
907 if (match := cls._REGEXP3.match(line._message)) is not None:
908 return cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, match[1], Path(match[2]), int(match[3]), previousLine=line._previousLine)
909
910 return None
911
912
913@export
914class TclCommand(VivadoLine):
915 """
916 Represents a TCL command found in a Vivado log output.
917
918 Besides the full log message (:class:`Line`), this class splits the TCL command into the command name and its
919 arguments.
920 """
921 _tclCommand: str
922 _arguments: Tuple[str, ...]
923
924 def __init__(
925 self,
926 lineNumber: int,
927 tclCommand: str,
928 arguments: Tuple[str, ...],
929 rawMessage: str,
930 previousLine: Nullable[VivadoLine] = None
931 ) -> None:
932 super().__init__(lineNumber, LineKind.GenericTclCommand, LineAction.Default, rawMessage, previousLine)
933
934 self._tclCommand = tclCommand
935 self._arguments = arguments
936
937 @readonly
938 def TCLCommand(self) -> str:
939 return self._tclCommand
940
941 @readonly
942 def Arguments(self) -> Tuple[str, ...]:
943 return self._arguments
944
945 @classmethod
946 def FromLine(cls, line: VivadoLine) -> Nullable[Self]:
947 args = line._message.split()
948
949 return cls(line._lineNumber, args[0], tuple(args[1:]), line._message, previousLine=line._previousLine)
950
951 @classmethod
952 def Copy(cls, line: TclCommand, previousLine: VivadoLine) -> TclCommand:
953 newLine = cls(line._lineNumber, line._tclCommand, line._arguments, line._message, previousLine)
954 newLine._timestamp = line._timestamp
955 return newLine
956
957 def __str__(self) -> str:
958 return f"{self._tclCommand} {' '.join(self._arguments)}"
959
960
961@export
962class VivadoTclCommand(TclCommand):
963 """
964 Represents a Vivado specific TCL command.
965 """
966
967 _PREFIX: ClassVar[str] = "Command:"
968
969 @classmethod
970 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[VivadoLine] = None) -> Nullable[Self]:
971 tclCommand = rawMessage[len(cls._PREFIX) + 1:]
972 args = tclCommand.split()
973
974 vivadoCommand = cls(lineNumber, args[0], tuple(args[1:]), rawMessage, previousLine)
975 vivadoCommand._kind = LineKind.VivadoTclCommand
976 return vivadoCommand
977
978 def __str__(self) -> str:
979 return f"{self._PREFIX} {self._tclCommand} {' '.join(self._arguments)}"
980
981
982@export
983class VivadoMessagesMixin(metaclass=ExtendedType, mixin=True):
984 _infoMessages: List[VivadoInfoMessage]
985 _warningMessages: List[VivadoWarningMessage]
986 _criticalWarningMessages: List[VivadoCriticalWarningMessage]
987 _errorMessages: List[VivadoErrorMessage]
988 _toolIDs: Dict[int, str]
989 _toolNames: Dict[str, int]
990 _messagesByID: Dict[int, Dict[int, List[VivadoMessage]]]
991
992 def __init__(self) -> None:
993 self._infoMessages = []
994 self._warningMessages = []
995 self._criticalWarningMessages = []
996 self._errorMessages = []
997 self._toolIDs = {}
998 self._toolNames = {}
999 self._messagesByID = {}
1000
1001 @readonly
1002 def ToolIDs(self) -> Dict[int, str]:
1003 return self._toolIDs
1004
1005 @readonly
1006 def ToolNames(self) -> Dict[str, int]:
1007 return self._toolNames
1008
1009 @readonly
1010 def MessagesByID(self) -> Dict[int, Dict[int, List[VivadoMessage]]]:
1011 return self._messagesByID
1012
1013 @readonly
1014 def InfoMessages(self) -> List[VivadoInfoMessage]:
1015 return self._infoMessages
1016
1017 @readonly
1018 def WarningMessages(self) -> List[VivadoWarningMessage]:
1019 return self._warningMessages
1020
1021 @readonly
1022 def CriticalWarningMessages(self) -> List[VivadoCriticalWarningMessage]:
1023 return self._criticalWarningMessages
1024
1025 @readonly
1026 def ErrorMessages(self) -> List[VivadoErrorMessage]:
1027 return self._errorMessages
1028
1029 def _AddMessage(self, message: VivadoMessage) -> None:
1030 if isinstance(message, InfoMessage):
1031 self._infoMessages.append(message)
1032 elif isinstance(message, WarningMessage):
1033 self._warningMessages.append(message)
1034 elif isinstance(message, CriticalWarningMessage):
1035 self._criticalWarningMessages.append(message)
1036 elif isinstance(message, ErrorMessage):
1037 self._errorMessages.append(message)
1038
1039 if message._toolID in self._messagesByID:
1040 sub = self._messagesByID[message._toolID]
1041 if message._messageKindID in sub:
1042 sub[message._messageKindID].append(message)
1043 else:
1044 sub[message._messageKindID] = [message]
1045 else:
1046 if message._toolID is not None:
1047 self._toolIDs[message._toolID] = message._toolName
1048 self._toolNames[message._toolName] = message._toolID
1049
1050 self._messagesByID[message._toolID] = {message._messageKindID: [message]}
1051
1052
1053# todo: check usage or merge with parser
1054@export
1055class BaseParser(VivadoMessagesMixin, metaclass=ExtendedType, slots=True):
1056 def __init__(self) -> None:
1057 super().__init__()
1058
1059
1060@export
1061class Parser(BaseParser):
1062 _processor: Processor
1063
1064 def __init__(self, processor: Processor) -> None:
1065 super().__init__()
1066
1067 self._processor = processor
1068
1069 @readonly
1070 def Processor(self) -> Processor:
1071 return self._processor
1072
1073
1074@export
1075class PreambleFormat(Enum):
1076 """
1077 An enumeration representing the preamble format (``Unknown``, ``Console`` or ``Logfile``).
1078 """
1079
1080 Unknown = 0 #: Vivado was called on console in batch or interactive mode.
1081 Console = 1 #: Vivado was called on console in batch or interactive mode.
1082 Logfile = 2 #: Vivado writes a logfile.
1083
1084 def __str__(self) -> str:
1085 """
1086 Formats the preamble format to ``console`` or ``logfile``.
1087
1088 :returns: Formatted preamble format.
1089 """
1090 return ("unknown", "console", "logfile")[cast(int, self.value)] # TODO: check performance
1091
1092
1093@export
1094class Preamble(Parser, VivadoMessagesMixin):
1095 """
1096 A parser for the preamble emitted by Vivado at session start.
1097
1098 .. rubric:: Extracted information
1099
1100 * Vivado tool version. |br|
1101 See :data:`ToolVersion`
1102 * Session start timestamp (date and time). |br|
1103 See :data:`StartDateTime`
1104
1105 .. rubric:: Examples
1106
1107 .. code-block::
1108
1109 ****** Vivado v2024.2 (64-bit)
1110 **** SW Build 5239630 on Fri Nov 08 22:35:27 MST 2024
1111 **** IP Build 5239520 on Sun Nov 10 16:12:51 MST 2024
1112 **** SharedData Build 5239561 on Fri Nov 08 14:39:27 MST 2024
1113 **** Start of session at: Wed Jul 1 23:50:26 2026
1114 ** Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
1115 ** Copyright 2022-2024 Advanced Micro Devices, Inc. All Rights Reserved.
1116
1117
1118 .. code-block::
1119
1120 INFO: [Common 17-3922] A valid Vivado Design Suite ENTERPRISE license has been detected. Your current license is active and will expire on Permanent.
1121 #-----------------------------------------------------------
1122 # Vivado v2025.1 (64-bit)
1123 # SW Build 6140274 on Thu May 22 00:12:29 MDT 2025
1124 # IP Build 6138677 on Thu May 22 03:10:11 MDT 2025
1125 # SharedData Build 6139179 on Tue May 20 17:58:58 MDT 2025
1126 # Start of session at: Thu Jun 12 18:39:05 2025
1127 # Process ID : 28856
1128 # Current directory : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1
1129 # Command line : vivado.exe -log toplevel.vdi -applog -product Vivado -messageDb vivado.pb -mode batch -source toplevel.tcl -notrace
1130 # Log file : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1/toplevel.vdi
1131 # Journal file : C:/Git/.../StopWatch/project/4_WithTiming.runs/impl_1\vivado.jou
1132 # Running On : Paebbels
1133 # Platform : Windows Server 2016 or Windows 10
1134 # Operating System : 26100
1135 # Processor Detail : 11th Gen Intel(R) Core(TM) i9-11950H @ 2.60GHz
1136 # CPU Frequency : 2611 MHz
1137 # CPU Physical cores : 8
1138 # CPU Logical cores : 16
1139 # Host memory : 34048 MB
1140 # Swap memory : 28991 MB
1141 # Total Virtual : 63039 MB
1142 # Available Virtual : 29246 MB
1143 #-----------------------------------------------------------
1144 """
1145 _VERSION: ClassVar[Pattern] = re_compile(r"""(?P<prefix>#|\*\*\*\*\*\*) Vivado v(?P<version>\d+\.\d(\.\d)?) \(64-bit\)""")
1146 _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})""")
1147
1148 _preambleFormat: PreambleFormat #: Format of the preamble
1149 _toolVersion: Nullable[YearReleaseVersion] #: Used Vivado version.
1150 _startDateTime: Nullable[datetime] #: Session start timestamp.
1151
1152 def __init__(self, processor: BaseProcessor) -> None:
1153 """
1154 Initializes a Vivado preamble parser.
1155
1156 :param processor: Reference to the Vivado log processor.
1157 """
1158 super().__init__(processor)
1159 VivadoMessagesMixin.__init__(self)
1160
1161 self._preambleFormat = PreambleFormat.Unknown
1162 self._toolVersion = None
1163 self._startDateTime = None
1164
1165 @readonly
1166 def PreambleFormat(self) -> PreambleFormat:
1167 """
1168 Read-only property to access the preamble format.
1169
1170 :returns: The detected format of the preamble.
1171 """
1172 return self._preambleFormat
1173
1174 @readonly
1175 def ToolVersion(self) -> YearReleaseVersion:
1176 """
1177 Read-only property to access the extracted Vivado tool version.
1178
1179 :returns: The used Vivado version as reported in the Vivado log messages.
1180 """
1181 if self._toolVersion is None:
1182 raise ProcessorException("No tool version extracted from preamble.")
1183
1184 return self._toolVersion
1185
1186 @readonly
1187 def StartDateTime(self) -> datetime:
1188 """
1189 Read-only property to access the date and time when the Vivado session was started.
1190
1191 :returns: Datetime when the session was started.
1192 :raises ProcessorException: When start timestamp wasn't extracted from preamble.
1193 """
1194 if self._startDateTime is None:
1195 raise ProcessorException("No start timestamp extracted from preamble.")
1196
1197 return self._startDateTime
1198
1199 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1200 """
1201 A generator for processing the Vivado session preamble line-by-line.
1202
1203 :param line: First line to process.
1204 :returns: A generator processing log messages.
1205 """
1206 delimiterCount = 0
1207 preambleFormat = PreambleFormat.Unknown
1208
1209 # a normal preamble has up to 23 lines including both delimiter lines.
1210 for _ in range(30):
1211 if (match := self._VERSION.match(line._message)) is not None:
1212 preambleFormat = PreambleFormat.Logfile if match["prefix"] == "#" else PreambleFormat.Console
1213 self._toolVersion = YearReleaseVersion.Parse(match["version"])
1214 line._kind = LineKind.Normal
1215 elif (match := self._STARTTIME.match(line._message)) is not None:
1216 preambleFormat = PreambleFormat.Logfile if match["prefix"] == "#" else PreambleFormat.Console
1217 self._startDateTime = datetime.strptime(match["datetime"], "%a %b %d %H:%M:%S %Y")
1218 line._kind = LineKind.Normal
1219 elif isinstance(line, VivadoMessage):
1220 self._AddMessage(line)
1221 elif line.StartsWith("#-----"):
1222 preambleFormat = PreambleFormat.Logfile
1223 line._kind = LineKind.SectionDelimiter
1224 if (delimiterCount := delimiterCount + 1) == 2:
1225 break
1226 elif line == "":
1227 preambleFormat = PreambleFormat.Console
1228 line._kind = LineKind.SectionDelimiter
1229 if (delimiterCount := delimiterCount + 1) == 2:
1230 break
1231 else:
1232 line._kind = LineKind.Verbose
1233
1234 if self._preambleFormat is PreambleFormat.Unknown:
1235 self._preambleFormat = preambleFormat
1236 elif self._preambleFormat is not preambleFormat:
1237 raise ProcessorException("Preamble format is not consistent.")
1238
1239 line = yield line
1240 else:
1241 raise OutputFilterException("Preamble is longer than 30 lines or delimiter was not detected.")
1242
1243 if self._toolVersion is None:
1244 raise OutputFilterException("Tool version not found in preamble.")
1245 elif self._startDateTime is None:
1246 raise OutputFilterException("Session start time and date not found in preamble.")
1247
1248 nextLine = yield line
1249 return nextLine
1250
1251 def __str__(self) -> str:
1252 return f"Vivado {self._toolVersion}: started at {self._startDateTime}"
1253
1254
1255@export
1256class Postamble(Parser, VivadoMessagesMixin): # todo: double mixin?
1257 """
1258 A parser for the postamble emitted by Vivado at session end.
1259
1260 .. rubric:: Extracted information
1261
1262 * Session exit timestamp (date and time). |br|
1263 See :data:`ExitDateTime`
1264
1265 .. rubric:: Example
1266
1267 .. code-block::
1268
1269 INFO: [Common 17-206] Exiting Vivado at Tue Sep 2 08:46:23 2025...
1270
1271 """
1272 _INFO: Tuple[int, int] = (17, 206)
1273 _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})""")
1274
1275 _exitDateTime: Nullable[datetime] #: Session exit timestamp.
1276
1277 def __init__(self, processor: BaseProcessor) -> None:
1278 """
1279 Initializes a Vivado postamble parser.
1280
1281 :param processor: Reference to the Vivado log processor.
1282 """
1283 super().__init__(processor)
1284 VivadoMessagesMixin.__init__(self)
1285
1286 self._exitDateTime = None
1287
1288 @readonly
1289 def ExitDateTime(self) -> Nullable[datetime]:
1290 """
1291 Read-only property to access the date and time when the Vivado session was exited.
1292
1293 :returns: Datetime when the session was exited.
1294 :raises ProcessorException: When exit timestamp wasn't extracted from postamble.
1295 """
1296 if self._exitDateTime is None:
1297 raise ProcessorException("No exit timestamp extracted from postamble.")
1298
1299 return self._exitDateTime
1300
1301 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1302 """
1303 A generator for processing the Vivado session preamble line-by-line.
1304
1305 :param line: First line to process.
1306 :returns: A generator processing log messages.
1307 """
1308 if isinstance(line, VivadoMessage):
1309 self._AddMessage(line)
1310
1311 if not isinstance(line, VivadoInfoMessage):
1312 raise ProcessorException(f"{self.__class__.__name__}.Generator(): Expected '{self._ENDTIME}' at line {line._lineNumber}.")
1313
1314 if (match := self._ENDTIME.match(line._message)) is not None:
1315 self._exitDateTime = datetime.strptime(match["datetime"], "%a %b %d %H:%M:%S %Y")
1316 else:
1317 pass
1318
1319 line = yield line
1320
1321 # todo: should we receive and expect an ned-token like None?
1322 return line
1323
1324@export
1325class Command(Parser):
1326 """
1327 This parser parses outputs from Vivado TCL commands.
1328
1329 Depending on the command's output (and how it's implemented), they use different subcategories.
1330
1331 .. rubric:: Command subcategories
1332
1333 * :class:`CommandWithSections`
1334 * :class:`CommandWithtasks`
1335
1336 .. rubric:: Supported commands
1337
1338 * :class:`SynthesizeDesign`
1339 * :class:`LinkDesign`
1340 * :class:`OptimizeDesign`
1341 * :class:`PlaceDesign`
1342 * :class:`PhysicalOptimizeDesign`
1343 * :class:`RouteDesign`
1344 * :class:`WriteBitstream`
1345 * :class:`ReportDRC`
1346 * :class:`ReportMethodology`
1347 * :class:`ReportPower`
1348
1349 .. rubric:: Example
1350
1351 .. code-block::
1352
1353 [...]
1354 Command: synth_design -top system_top -part xc7z015clg485-2
1355 Starting synth_design
1356 [...]
1357 """
1358
1359 # _TCL_COMMAND: ClassVar[str]
1360
1361 def _CommandStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1362 """
1363 A generator accepting a line containing the expected Vivado TCL command.
1364
1365 When the generator exits, the returned line is the successor line to the line containing the Vivado TCL command.
1366
1367 :param line: The first line for the generator to process.
1368 :returns: A generator processing Vivado output log lines.
1369 """
1370 if not (isinstance(line, VivadoTclCommand) and line._tclCommand == self._TCL_COMMAND):
1371 raise ProcessorException() # FIXME: add exception message
1372
1373 nextLine = yield line
1374 return nextLine
1375
1376 def _CommandFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1377 if line.StartsWith(f"{self._TCL_COMMAND} completed successfully"):
1378 line._kind |= LineKind.Success
1379 else:
1380 line._kind |= LineKind.Failed
1381
1382 line = yield line
1383
1384 if self._TIME is not None: # and self._processor._preamble._toolVersion > "2022.2":
1385 end = f"{self._TCL_COMMAND}: {self._TIME}"
1386
1387 # while True: # TODO: limit search for time to 10 lines
1388 # if line.StartsWith(end):
1389 # line._kind = LineKind.TaskTime
1390 # line = yield line
1391 # break
1392 #
1393 # line = yield line
1394
1395 if line.StartsWith(end):
1396 line._kind = LineKind.TaskTime
1397 line = yield line
1398
1399 return line
1400
1401 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, None]:
1402 line = yield from self._CommandStart(line)
1403
1404 end = f"{self._TCL_COMMAND}"
1405 while True:
1406 if line._kind is LineKind.Empty:
1407 line = yield line
1408 continue
1409 elif isinstance(line, VivadoMessage):
1410 self._AddMessage(line)
1411 elif line.StartsWith(end):
1412 nextLine = yield from self._CommandFinish(line)
1413 return nextLine
1414
1415 line = yield line
1416
1417 def __str__(self) -> str:
1418 return f"{self._TCL_COMMAND}"
1419
1420
1421@export
1422class CommandWithSections(Command):
1423 """
1424 A Vivado command writing sections into the output log.
1425
1426 .. rubric:: Example
1427
1428 .. code-block::
1429
1430 [...]
1431 ---------------------------------------------------------------------------------
1432 Starting RTL Elaboration : Time (s): cpu = 00:00:03 ; elapsed = 00:00:03 . Memory (MB): peak = 847.230 ; gain = 176.500
1433 ---------------------------------------------------------------------------------
1434 INFO: [Synth 8-638] synthesizing module 'system_top' [C:/Users/tgomes/git/2019_1/src/system_top_PE1.vhd:257]
1435 [...]
1436 [...]
1437 [...]
1438 ---------------------------------------------------------------------------------
1439 Finished RTL Elaboration : Time (s): cpu = 00:00:04 ; elapsed = 00:00:04 . Memory (MB): peak = 917.641 ; gain = 246.910
1440 ---------------------------------------------------------------------------------
1441 [...]
1442 """
1443 # _PARSERS: ClassVar[Tuple[Type[Section], ...]]
1444
1445 _sections: List[Section] # Dict[Type["Section"], "Section"]
1446
1447
1448 def __init__(self, processor: Processor) -> None:
1449 super().__init__(processor)
1450
1451 self._sections = [] # p: p(self) for p in self._PARSERS}
1452
1453 @readonly
1454 def Sections(self) -> List[Section]: # Dict[Type["Section"], "Section"]:
1455 """
1456 Read-only property to access a dictionary of found sections within the TCL command's output.
1457
1458 :returns: A dictionary of found :class:`~pyEDAA.OutputFilter.Xilinx.SynthesizeDesign.Section`s.
1459 """
1460 return self._sections
1461
1462 def __contains__(self, key: Any) -> bool:
1463 if not issubclass(key, Section):
1464 ex = TypeError("Parameter 'key' is not a Section.")
1465 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1466 raise ex
1467
1468 for section in self._sections:
1469 if isinstance(section, key):
1470 return True
1471 else:
1472 return False
1473
1474 def __getitem__(self, key: Type[Section]) -> Section:
1475 if not issubclass(key, Section):
1476 ex = TypeError("Parameter 'key' is not a Section.")
1477 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1478 raise ex
1479
1480 for section in self._sections:
1481 if isinstance(section, key):
1482 return section
1483 else:
1484 raise SectionNotPresentException(F"Section '{key._NAME}' not present in '{self._parent.logfile}'.")
1485
1486
1487@export
1488class CommandWithTasks(Command):
1489 """
1490 A Vivado command writing tasks into the output log.
1491
1492 .. rubric:: Example
1493
1494 .. code-block::
1495
1496 [...]
1497 Starting Cache Timing Information Task
1498 INFO: [Timing 38-35] 79-Done setting XDC timing constraints.
1499 [...]
1500 [...]
1501 Ending Cache Timing Information Task | Checksum: 19fe8cb97
1502 [...]
1503 """
1504 # _PARSERS: Tuple[Type[Task], ...]
1505
1506 _tasks: Dict[Type[Task], Task]
1507
1508 def __init__(self, processor: Processor) -> None:
1509 super().__init__(processor)
1510
1511 self._tasks = {p: p(self) for p in self._PARSERS}
1512
1513 @readonly
1514 def Tasks(self) -> Dict[Type[Task], Task]:
1515 """
1516 Read-only property to access a dictionary of found tasks within the TCL command's output.
1517
1518 :returns: A dictionary of found :class:`~pyEDAA.OutputFilter.Xilinx.Common2.Task`s.
1519 """
1520 return self._tasks
1521
1522 def __contains__(self, key: Any) -> bool:
1523 if not issubclass(key, Task):
1524 ex = TypeError("Parameter 'key' is not a Task.")
1525 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1526 raise ex
1527
1528 return key in self._tasks
1529
1530 def __getitem__(self, key: Type[Task]) -> Task:
1531 try:
1532 return self._tasks[key]
1533 except KeyError as ex:
1534 raise SectionNotPresentException(F"Task '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
1535
1536
1537@export
1538class BaseSection(metaclass=ExtendedType, mixin=True):
1539 @abstractmethod
1540 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1541 pass
1542
1543 @abstractmethod
1544 def _SectionFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
1545 pass
1546
1547 @abstractmethod
1548 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1549 pass
1550
1551
1552@export
1553class Section(BaseParser, BaseSection):
1554 """
1555 Base-class for sections within log outputs from *synthesize design*.
1556 """
1557 # _NAME: ClassVar[str]
1558 # _START: ClassVar[str]
1559 # _FINISH: ClassVar[str]
1560 # _DUPLICATES: ClassVar[bool]
1561
1562 _command: Command #: Reference to the command (parent).
1563 _next: Nullable[Section]
1564 _duration: float #: Duration synthesis spent in processing a synthesis step logged in this log output section.
1565
1566 def __init__(self, command: Command) -> None:
1567 """
1568 Initialized a section.
1569
1570 :param command: Reference to the parent TCL command.
1571 """
1572 super().__init__() #command._processor)
1573
1574 self._command = command
1575 self._next = None
1576 self._duration = 0.0
1577
1578 @readonly
1579 def Next(self) -> Nullable[Section]:
1580 """
1581 Read-only property to access the next section in case the section appeared multiple times.
1582
1583 :returns: Next section of same type.
1584 """
1585 return self._next
1586
1587 @readonly
1588 def Duration(self) -> float:
1589 """
1590 Read-only property to access the duration synthesis spent in processing a synthesis step logged in this log output
1591 section.
1592
1593 :returns: Synthesis step duration in seconds.
1594 """
1595 return self._duration
1596
1597 def __iter__(self) -> Generator[Section, None, None]:
1598 section = self._next
1599 while section is not None:
1600 yield section
1601
1602 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1603 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter
1604 line._kind = LineKind.SectionStart
1605
1606 line = yield line
1607 if line.StartsWith("----"):
1608 line._kind = LineKind.SectionStart | LineKind.SectionDelimiter
1609 else:
1610 line._kind |= LineKind.ProcessorError
1611
1612 nextLine = yield line
1613 return nextLine
1614
1615 def _SectionFinish(self, line: VivadoLine, skipDashes: bool = False) -> Generator[VivadoLine, VivadoLine, None]:
1616 if not skipDashes:
1617 if line.StartsWith("----"):
1618 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
1619 else:
1620 line._kind |= LineKind.ProcessorError
1621
1622 line = yield line
1623
1624 if line.StartsWith(self._FINISH):
1625 line._kind = LineKind.SectionEnd
1626 else:
1627 line._kind |= LineKind.ProcessorError
1628
1629 line = yield line
1630 if line.StartsWith("----"):
1631 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
1632 else:
1633 line._kind |= LineKind.ProcessorError
1634
1635 nextLine = yield line
1636 return nextLine
1637
1638 # @mustoverride
1639 # def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
1640 # if len(line) == 0:
1641 # return ProcessingState.EmptyLine
1642 # elif line.startswith("----"):
1643 # return ProcessingState.DelimiterLine
1644 # elif line.startswith(self._START):
1645 # return ProcessingState.Skipped
1646 # elif line.startswith(self._FINISH):
1647 # l = line[len(self._FINISH):]
1648 # if (match := TIME_MEMORY_PATTERN.match(l)) is not None:
1649 # # cpuParts = match[1].split(":")
1650 # elapsedParts = match[2].split(":")
1651 # # peakMemory = float(match[3])
1652 # # gainMemory = float(match[4])
1653 # self._duration = int(elapsedParts[0]) * 3600 + int(elapsedParts[1]) * 60 + int(elapsedParts[2])
1654 #
1655 # return ProcessingState.Skipped | ProcessingState.Last
1656 # elif line.startswith("Start") or line.startswith("Starting"):
1657 # print(f"ERROR: didn't find finish\n {line}")
1658 # return ProcessingState.Reprocess
1659 #
1660 # return ProcessingState.Skipped
1661
1662 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1663 line = yield from self._SectionStart(line)
1664
1665 while True:
1666 if line._kind is LineKind.Empty:
1667 line = yield line
1668 continue
1669 elif line.StartsWith("----"):
1670 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
1671 break
1672 elif isinstance(line, VivadoMessage):
1673 self._AddMessage(line)
1674 else:
1675 line._kind = LineKind.Verbose
1676
1677 line = yield line
1678
1679 # line = yield line
1680 nextLine = yield from self._SectionFinish(line)
1681 return nextLine
1682
1683
1684@export
1685class SubSection(BaseParser, BaseSection):
1686 """
1687 Base-class for subsections within log outputs from *synthesize design*.
1688 """
1689 # _NAME: ClassVar[str]
1690
1691 _section: Section #: Reference to the section (parent).
1692
1693 def __init__(self, section: Section) -> None:
1694 """
1695 Initialized a subsection.
1696
1697 :param section: Reference to the parent section.
1698 """
1699 super().__init__()
1700 self._section = section
1701
1702 def _SectionStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1703 line._kind = LineKind.SubSectionStart
1704
1705 line = yield line
1706 if line.StartsWith("----"):
1707 line._kind = LineKind.SubSectionStart | LineKind.SubSectionDelimiter
1708 else:
1709 line._kind |= LineKind.ProcessorError
1710
1711 nextLine = yield line
1712 return nextLine
1713
1714 def _SectionFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
1715 if line.StartsWith("----"):
1716 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter
1717 else:
1718 line._kind |= LineKind.ProcessorError
1719
1720 line = yield line
1721 if line.StartsWith(self._FINISH):
1722 line._kind = LineKind.SubSectionEnd
1723 else:
1724 line._kind |= LineKind.ProcessorError
1725
1726 line = yield line
1727 if line.StartsWith("----"):
1728 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter
1729 else:
1730 line._kind |= LineKind.ProcessorError
1731
1732 nextLine = yield line
1733 return nextLine
1734
1735 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1736 line = yield from self._SectionStart(line)
1737
1738 while True:
1739 if line._kind is LineKind.Empty:
1740 line = yield line
1741 continue
1742 elif line.StartsWith("----"):
1743 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter
1744 break
1745 elif isinstance(line, VivadoMessage):
1746 self._AddMessage(line)
1747 else:
1748 line._kind = LineKind.Verbose
1749
1750 line = yield line
1751
1752 nextLine = yield from self._SectionFinish(line)
1753 return nextLine
1754
1755
1756@export
1757class SectionWithChildren(Section):
1758 """
1759 Base-class for sections with subsections.
1760 """
1761 _subsections: Dict[Type[SubSection], SubSection]
1762
1763 def __init__(self, command: Command) -> None:
1764 super().__init__(command)
1765
1766 self._subsections = {}
1767
1768 def __contains__(self, key: Any) -> bool:
1769 if not issubclass(key, SubSection):
1770 ex = TypeError("Parameter 'item' is not a SubSection.")
1771 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1772 raise ex
1773
1774 return key in self._subsections
1775
1776 def __getitem__(self, item: Type[SubSection]) -> SubSection:
1777 try:
1778 return self._subsections[item]
1779 except KeyError as ex:
1780 raise SubSectionNotPresentException(f"SubSection '{item._NAME}' not present in '{self._parent._parent.logfile}'.") from ex
1781
1782
1783@export
1784class Task(BaseParser, VivadoMessagesMixin):
1785 """
1786 A task's output emitted by a Vivado command.
1787
1788 .. rubric:: Extracted information
1789
1790 * Vivado messages (info, warning, critical warning, error).
1791
1792 .. rubric:: Example
1793
1794 .. code-block::
1795
1796 Starting Cache Timing Information Task
1797 INFO: [Timing 38-35] 79-Done setting XDC timing constraints.
1798 Ending Cache Timing Information Task | Checksum: 19fe8cb97
1799
1800 Time (s): cpu = 00:00:09 ; elapsed = 00:00:09 . Memory (MB): peak = 1370.594 ; gain = 493.266
1801
1802 """
1803 # _NAME: ClassVar[str]
1804 # _START: ClassVar[str]
1805 # _FINISH: ClassVar[str]
1806 _TIME: ClassVar[str] = "Time (s):"
1807
1808 _command: Command #: Reference to the command (parent).
1809 _duration: float #: Duration of a task according to reported times by Vivado.
1810
1811 def __init__(self, command: Command) -> None:
1812 """
1813 Initializes a task (without child elements).
1814
1815 :param command: Reference to the command.
1816 """
1817 super().__init__()
1818 VivadoMessagesMixin.__init__(self)
1819
1820 self._command = command
1821
1822 @readonly
1823 def Command(self) -> Command:
1824 """
1825 Read-only property to access the command.
1826
1827 :returns: The command this task's output was logged for.
1828 """
1829 return self._command
1830
1831 def _TaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1832 """
1833 A generator for processing a task start (single line).
1834
1835 :param line: First line to process (task start).
1836 :returns: A generator processing log messages.
1837 :raises ProcessorException: If first line doesn't conform to the *task start* pattern.
1838 """
1839 if not line.StartsWith(self._START):
1840 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.")
1841
1842 line._kind = LineKind.TaskStart
1843 nextLine = yield line
1844 return nextLine
1845
1846 def _TaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1847 """
1848 A generator for processing a task finish line-by-line.
1849
1850 :param line: First line to process (task finish).
1851 :returns: A generator processing log messages.
1852 :raises ProcessorException: If finish line doesn't conform to the *task finish* pattern.
1853 """
1854 if not line.StartsWith(self._FINISH):
1855 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.")
1856
1857 line._kind = LineKind.TaskEnd
1858 line = yield line
1859 while self._TIME is not None: # TODO: limit search for time pattern to XX lines
1860 if line.StartsWith(self._TIME):
1861 line._kind = LineKind.TaskTime
1862 break
1863
1864 line = yield line
1865
1866 nextLine = yield line
1867 return nextLine
1868
1869 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1870 """
1871 A generator for processing a task without child elements line-by-line.
1872
1873 .. rubric:: Algorithm
1874
1875 1. Send first line to :meth:`_TaskStart`.
1876 2. Process body lines
1877
1878 * Collect Vivado messages (info, warning, critical warning, error).
1879 * Check for *task finish* pattern.
1880 * Check for *time* pattern.
1881
1882 3. Send last lines to :meth:`_TaskFinish`.
1883
1884 :param line: First line to process.
1885 :returns: A generator processing log messages.
1886 """
1887 line = yield from self._TaskStart(line)
1888
1889 while True:
1890 if line._kind is LineKind.Empty:
1891 line = yield line
1892 continue
1893 elif self._FINISH is not None and line.StartsWith("Ending"):
1894 break
1895 elif isinstance(line, VivadoMessage):
1896 self._AddMessage(line)
1897 elif line.StartsWith(self._TIME):
1898 line._kind = LineKind.TaskTime
1899 nextLine = yield line
1900 return nextLine
1901
1902 line = yield line
1903
1904 nextLine = yield from self._TaskFinish(line)
1905 return nextLine
1906
1907 def __str__(self) -> str:
1908 return f"{self.__class__.__name__}: {self._START}"
1909
1910
1911@export
1912class TaskWithSubTasks(Task):
1913 """
1914 A task's output emitted by a Vivado command.
1915
1916 .. rubric:: Extracted information
1917
1918 * Vivado messages (info, warning, critical warning, error).
1919 * Subtasks
1920
1921 .. rubric:: Example
1922
1923 .. code-block::
1924
1925 Starting Cache Timing Information Task
1926 INFO: [Timing 38-35] 79-Done setting XDC timing constraints.
1927 Ending Cache Timing Information Task | Checksum: 19fe8cb97
1928
1929 Time (s): cpu = 00:00:09 ; elapsed = 00:00:09 . Memory (MB): peak = 1370.594 ; gain = 493.266
1930
1931 """
1932 # _PARSERS: ClassVar[Tuple[Type["SubTask"], ...]]
1933
1934 _subtasks: Dict[Type[SubTask], SubTask]
1935
1936 def __init__(self, command: Command) -> None:
1937 super().__init__(command)
1938
1939 self._subtasks = {p: p(self) for p in self._PARSERS}
1940
1941 @readonly
1942 def SubTasks(self) -> Dict[Type[SubTask], SubTask]:
1943 return self._subtasks
1944
1945 def __contains__(self, key: Any) -> bool:
1946 if not issubclass(key, SubTask):
1947 ex = TypeError("Parameter 'key' is not a Subtask.")
1948 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
1949 raise ex
1950
1951 return key in self._subtasks
1952
1953 def __getitem__(self, key: Type[SubTask]) -> SubTask:
1954 try:
1955 return self._subtasks[key]
1956 except KeyError as ex:
1957 raise SubTaskNotPresentException(F"Subtask '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
1958
1959 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
1960 line = yield from self._TaskStart(line)
1961
1962 activeParsers: List[Phase] = list(self._subtasks.values())
1963
1964 while True:
1965 while True:
1966 if line._kind is LineKind.Empty:
1967 line = yield line
1968 continue
1969 elif isinstance(line, VivadoMessage):
1970 self._AddMessage(line)
1971 elif line.StartsWith("Starting "):
1972 for parser in activeParsers: # type: SubTask
1973 if line.StartsWith(parser._START):
1974 line = yield next(subtask := parser.Generator(line))
1975 break
1976 else:
1977 WarningCollector.Raise(UnknownSubTask(f"Unknown subtask: '{line!r}'", line))
1978 ex = Exception(f"How to recover from here? Unknown subtask: '{line!r}'")
1979 ex.add_note(f"Current task: start pattern='{self}'")
1980 ex.add_note(f"Current command: {self._command}")
1981 raise ex
1982 break
1983 elif line.StartsWith("Ending"):
1984 nextLine = yield from self._TaskFinish(line)
1985 return nextLine
1986 elif line.StartsWith(self._TIME):
1987 line._kind = LineKind.TaskTime
1988 nextLine = yield line
1989 return nextLine
1990
1991 line = yield line
1992
1993 while True:
1994 isFinish = False # line.StartsWith("Ending") # FIXME: detect end, but time might come later
1995
1996 try:
1997 processedLine = subtask.send(line)
1998
1999 if isinstance(processedLine, VivadoMessage):
2000 self._AddMessage(processedLine)
2001
2002 if isFinish:
2003 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2004 line = yield processedLine
2005 break
2006 except StopIteration as ex:
2007 activeParsers.remove(parser)
2008 line = ex.value
2009 break
2010
2011 line = yield processedLine
2012
2013
2014@export
2015class SubTask(BaseParser, VivadoMessagesMixin):
2016 # _NAME: ClassVar[str]
2017 # _START: ClassVar[str]
2018 # _FINISH: ClassVar[str]
2019 _TIME: ClassVar[str] = "Time (s):"
2020
2021 _task: TaskWithSubTasks
2022 _duration: float
2023
2024 def __init__(self, task: TaskWithSubTasks) -> None:
2025 super().__init__()
2026 VivadoMessagesMixin.__init__(self)
2027
2028 self._task = task
2029
2030 @readonly
2031 def Task(self) -> TaskWithSubTasks:
2032 return self._task
2033
2034 def _TaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2035 if not line.StartsWith(self._START):
2036 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.")
2037
2038 line._kind = LineKind.TaskStart
2039 nextLine = yield line
2040 return nextLine
2041
2042 def _TaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2043 if not line.StartsWith(self._FINISH):
2044 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.")
2045
2046 line._kind = LineKind.TaskEnd
2047 line = yield line
2048 while self._TIME is not None:
2049 if line.StartsWith(self._TIME):
2050 line._kind = LineKind.TaskTime
2051 break
2052
2053 line = yield line
2054
2055 nextLine = yield line
2056 return nextLine
2057
2058 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2059 line = yield from self._TaskStart(line)
2060
2061 while True:
2062 if line._kind is LineKind.Empty:
2063 line = yield line
2064 continue
2065 elif self._FINISH is not None and line.StartsWith("Ending"):
2066 break
2067 elif isinstance(line, VivadoMessage):
2068 self._AddMessage(line)
2069 elif line.StartsWith(self._TIME):
2070 line._kind = LineKind.TaskTime
2071 nextLine = yield line
2072 return nextLine
2073
2074 line = yield line
2075
2076 nextLine = yield from self._TaskFinish(line)
2077 return nextLine
2078
2079 def __str__(self) -> str:
2080 return self._NAME
2081
2082
2083@export
2084class TaskWithPhases(Task):
2085 # _PARSERS: ClassVar[Tuple[Type["Phase"], ...]]
2086
2087 _phases: Dict[Type[Phase], Phase]
2088
2089 def __init__(self, command: Command) -> None:
2090 super().__init__(command)
2091
2092 self._phases = {p: p(self) for p in self._PARSERS}
2093
2094 @readonly
2095 def Phases(self) -> Dict[Type[Phase], Phase]:
2096 return self._phases
2097
2098 def __contains__(self, key: Any) -> bool:
2099 if not issubclass(key, Phase):
2100 ex = TypeError("Parameter 'key' is not a Phase.")
2101 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2102 raise ex
2103
2104 return key in self._phases
2105
2106 def __getitem__(self, key: Type[Phase]) -> Phase:
2107 try:
2108 return self._phases[key]
2109 except KeyError as ex:
2110 raise PhaseNotPresentException(F"Phase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2111
2112 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2113 line = yield from self._TaskStart(line)
2114
2115 activeParsers: List[Phase] = list(self._phases.values())
2116
2117 while True:
2118 while True:
2119 if line._kind is LineKind.Empty:
2120 line = yield line
2121 continue
2122 elif isinstance(line, VivadoMessage):
2123 self._AddMessage(line)
2124 elif line.StartsWith("Phase "):
2125 for parser in activeParsers: # type: Phase
2126 if (match := parser._START.match(line._message)) is not None:
2127 line = yield next(phase := parser.Generator(line))
2128 break
2129 else:
2130 WarningCollector.Raise(UnknownPhase(f"Unknown phase: '{line!r}'", line))
2131 ex = Exception(f"How to recover from here? Unknown phase: '{line!r}'")
2132 ex.add_note(f"Current task: start pattern='{self}'")
2133 ex.add_note(f"Current command: {self._command}")
2134 raise ex
2135 break
2136 elif line.StartsWith("Ending"):
2137 nextLine = yield from self._TaskFinish(line)
2138 return nextLine
2139 elif line.StartsWith(self._TIME):
2140 line._kind = LineKind.TaskTime
2141 nextLine = yield line
2142 return nextLine
2143
2144 line = yield line
2145
2146 while True:
2147 isFinish = False #line.StartsWith("Ending")
2148
2149 try:
2150 processedLine = phase.send(line)
2151
2152 if isinstance(processedLine, VivadoMessage):
2153 self._AddMessage(processedLine)
2154
2155 if isFinish:
2156 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2157 line = yield processedLine
2158 break
2159 except StopIteration as ex:
2160 activeParsers.remove(parser)
2161 line = ex.value
2162 break
2163
2164 line = yield processedLine
2165
2166
2167@export
2168class Phase(BaseParser, VivadoMessagesMixin):
2169 # _NAME: ClassVar[str]
2170 # _START: ClassVar[str]
2171 # _FINISH: ClassVar[str]
2172 # _TIME: ClassVar[str] = "Time (s):"
2173 # _FINAL: ClassVar[Nullable[str]] = None
2174
2175 _task: TaskWithPhases
2176 _phaseIndex: int
2177 _duration: float
2178
2179 def __init__(self, task: TaskWithPhases) -> None:
2180 super().__init__()
2181 VivadoMessagesMixin.__init__(self)
2182
2183 self._task = task
2184 self._phaseIndex = None
2185
2186 @readonly
2187 def Task(self) -> TaskWithPhases:
2188 return self._task
2189
2190 def _PhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2191 if (match := self._START.match(line._message)) is None:
2192 raise ProcessorException(f"{self.__class__.__name__}._PhaseStart(): Expected '{self._START}' at line {line._lineNumber}.")
2193
2194 self._phaseIndex = int(match["major"])
2195
2196 line._kind = LineKind.PhaseStart
2197 nextLine = yield line
2198 return nextLine
2199
2200 def _PhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2201 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
2202 if not line.StartsWith(FINISH):
2203 raise ProcessorException(f"{self.__class__.__name__}._PhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2204
2205 line._kind = LineKind.PhaseEnd
2206 line = yield line
2207
2208 if self._TIME is not None:
2209 while True:
2210 if line.StartsWith(self._TIME):
2211 line._kind = LineKind.PhaseTime
2212 break
2213 elif isinstance(line, VivadoMessage):
2214 self._AddMessage(line)
2215
2216 line = yield line
2217
2218 line = yield line
2219
2220 if self._FINAL is not None and self._task._command._processor._preamble._toolVersion >= "2023.2":
2221 while True:
2222 if line.StartsWith(self._FINAL):
2223 line._kind = LineKind.PhaseFinal
2224 break
2225 elif isinstance(line, VivadoMessage):
2226 self._AddMessage(line)
2227
2228 line = yield line
2229
2230 line = yield line
2231
2232 # TODO: optionally collect following INFO messages like 31-389, 31-1021, 31-662
2233
2234 return line
2235
2236 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2237 line = yield from self._PhaseStart(line)
2238
2239 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
2240
2241 while True:
2242 if line._kind is LineKind.Empty:
2243 line = yield line
2244 continue
2245 elif isinstance(line, VivadoMessage):
2246 self._AddMessage(line)
2247 elif line.StartsWith(FINISH):
2248 break
2249
2250 line = yield line
2251
2252 nextLine = yield from self._PhaseFinish(line)
2253 return nextLine
2254
2255 def __str__(self) -> str:
2256 return f"{self.__class__.__name__}: {self._START.pattern}"
2257
2258
2259@export
2260class PhaseWithChildren(Phase):
2261 _SUBPHASE_PREFIX: ClassVar[str] = "Phase {phaseIndex}."
2262
2263 _subPhases: Dict[Type[SubPhase], SubPhase]
2264
2265 def __init__(self, task: TaskWithPhases) -> None:
2266 super().__init__(task)
2267
2268 self._subPhases = {p: p(self) for p in self._PARSERS}
2269
2270 def __contains__(self, key: Any) -> bool:
2271 if not issubclass(key, SubPhase):
2272 ex = TypeError("Parameter 'item' is not a SubPhase.")
2273 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2274 raise ex
2275
2276 return key in self._subPhases
2277
2278 def __getitem__(self, key: Type[SubPhase]) -> SubPhase:
2279 try:
2280 return self._subPhases[key]
2281 except KeyError as ex:
2282 raise PhaseNotPresentException(F"SubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2283
2284 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2285 line = yield from self._PhaseStart(line)
2286
2287 activeParsers: List[SubPhase] = list(self._subPhases.values())
2288
2289 SUBPHASE_PREFIX = self._SUBPHASE_PREFIX.format(phaseIndex=self._phaseIndex)
2290 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
2291
2292 while True:
2293 while True:
2294 if line._kind is LineKind.Empty:
2295 line = yield line
2296 continue
2297 elif isinstance(line, VivadoMessage):
2298 self._AddMessage(line)
2299 elif line.StartsWith(SUBPHASE_PREFIX):
2300 for parser in activeParsers: # type: Section
2301 if (match := parser._START.match(line._message)) is not None:
2302 line = yield next(phase := parser.Generator(line))
2303 break
2304 else:
2305 WarningCollector.Raise(UnknownSubPhase(f"Unknown subphase: '{line!r}'", line))
2306 ex = Exception(f"How to recover from here? Unknown subphase: '{line!r}'")
2307 ex.add_note(f"Current phase: start pattern='{self}'")
2308 ex.add_note(f"Current task: start pattern='{self._task}'")
2309 ex.add_note(f"Current command: {self._task._command}")
2310 raise ex
2311 break
2312 elif line.StartsWith(FINISH):
2313 nextLine = yield from self._PhaseFinish(line)
2314 return nextLine
2315
2316 line = yield line
2317
2318 while True:
2319 isFinish = False # line.StartsWith(SUBPHASE_PREFIX) # FIXME: detect end, but end (e.g. time) is later then ending text
2320
2321 try:
2322 processedLine = phase.send(line)
2323
2324 if isinstance(processedLine, VivadoMessage):
2325 self._AddMessage(processedLine)
2326
2327 if isFinish:
2328 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2329 line = yield processedLine
2330 break
2331 except StopIteration as ex:
2332 activeParsers.remove(parser)
2333 line = ex.value
2334 break
2335
2336 line = yield processedLine
2337
2338
2339@export
2340class SubPhase(BaseParser, VivadoMessagesMixin):
2341 # _NAME: ClassVar[str]
2342 # _START: ClassVar[str]
2343 # _FINISH: ClassVar[str]
2344
2345 _phase: Phase
2346 _phaseIndex: int
2347 _subPhaseIndex: int
2348 _duration: float
2349
2350 def __init__(self, phase: Phase) -> None:
2351 super().__init__()
2352 VivadoMessagesMixin.__init__(self)
2353
2354 self._phase = phase
2355 self._phaseIndex = None
2356 self._subPhaseIndex = None
2357
2358 def _SubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2359 if (match := self._START.match(line._message)) is None:
2360 raise ProcessorException(f"{self.__class__.__name__}._SubPhaseStart(): Expected '{self._START}' at line {line._lineNumber}.")
2361
2362 self._phaseIndex = int(match["major"])
2363 self._subPhaseIndex = int(match["minor"])
2364
2365 line._kind = LineKind.SubPhaseStart
2366 nextLine = yield line
2367 return nextLine
2368
2369 def _SubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2370 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex)
2371
2372 if not line.StartsWith(FINISH):
2373 raise ProcessorException(f"{self.__class__.__name__}._SubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2374
2375 if self._TIME is None:
2376 line._kind = LineKind.SubPhaseTime
2377 else:
2378 line._kind = LineKind.SubPhaseEnd
2379
2380 line = yield line
2381 while self._TIME is not None:
2382 if line.StartsWith(self._TIME):
2383 line._kind = LineKind.SubPhaseTime
2384 break
2385
2386 line = yield line
2387
2388 nextLine = yield line
2389 return nextLine
2390
2391 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2392 line = yield from self._SubPhaseStart(line)
2393
2394 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex)
2395
2396 while True:
2397 if line._kind is LineKind.Empty:
2398 line = yield line
2399 continue
2400 elif line.StartsWith(FINISH):
2401 break
2402 elif isinstance(line, VivadoMessage):
2403 self._AddMessage(line)
2404
2405 line = yield line
2406
2407 nextLine = yield from self._SubPhaseFinish(line)
2408 return nextLine
2409
2410 def __str__(self) -> str:
2411 return f"{self.__class__.__name__}: {self._START.pattern}"
2412
2413
2414@export
2415class SubPhaseWithChildren(SubPhase):
2416 _subSubPhases: Dict[Type[SubSubPhase], SubSubPhase]
2417
2418 def __init__(self, phase: Phase) -> None:
2419 super().__init__(phase)
2420
2421 self._subSubPhases = {p: p(self) for p in self._PARSERS}
2422
2423 def __contains__(self, key: Any) -> bool:
2424 if not issubclass(key, SubSubPhase):
2425 ex = TypeError("Parameter 'item' is not a SubSubPhase.")
2426 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2427 raise ex
2428
2429 return key in self._subSubPhases
2430
2431 def __getitem__(self, key: Type[SubSubPhase]) -> SubSubPhase:
2432 try:
2433 return self._subSubPhases[key]
2434 except KeyError as ex:
2435 raise PhaseNotPresentException(F"SubSubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2436
2437 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2438 line = yield from self._SubPhaseStart(line)
2439
2440 activeParsers: List[SubSubPhase] = list(self._subSubPhases.values())
2441
2442 START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}."
2443 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex)
2444
2445 while True:
2446 while True:
2447 if line._kind is LineKind.Empty:
2448 line = yield line
2449 continue
2450 elif isinstance(line, VivadoMessage):
2451 self._AddMessage(line)
2452 elif line.StartsWith(START_PREFIX):
2453 for parser in activeParsers: # type: SubSubPhase
2454 if (match := parser._START.match(line._message)) is not None:
2455 line = yield next(phase := parser.Generator(line))
2456 break
2457 else:
2458 WarningCollector.Raise(UnknownSubPhase(f"Unknown subsubphase: '{line!r}'", line))
2459 ex = Exception(f"How to recover from here? Unknown subsubphase: '{line!r}'")
2460 ex.add_note(f"Current subphase: start pattern='{self}'")
2461 ex.add_note(f"Current phase: start pattern='{self._phase}'")
2462 ex.add_note(f"Current task: start pattern='{self._phase._task}'")
2463 ex.add_note(f"Current cmd: {self._phase._task._command}")
2464 raise ex
2465 break
2466 elif line.StartsWith(FINISH):
2467 nextLine = yield from self._SubPhaseFinish(line)
2468 return nextLine
2469
2470 line = yield line
2471
2472 while True:
2473 isFinish = False # line.StartsWith("Ending")
2474
2475 try:
2476 processedLine = phase.send(line)
2477
2478 if isinstance(processedLine, VivadoMessage):
2479 self._AddMessage(processedLine)
2480
2481 if isFinish:
2482 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2483 line = yield processedLine
2484 break
2485 except StopIteration as ex:
2486 activeParsers.remove(parser)
2487 line = ex.value
2488 break
2489
2490 line = yield processedLine
2491
2492
2493@export
2494class SubSubPhase(BaseParser, VivadoMessagesMixin):
2495 # _NAME: ClassVar[str]
2496 # _START: ClassVar[str]
2497 # _FINISH: ClassVar[str]
2498
2499 _subphase: SubPhase
2500 _phaseIndex: int
2501 _subPhaseIndex: int
2502 _subSubPhaseIndex: int
2503 _duration: float
2504
2505 def __init__(self, subphase: SubPhase) -> None:
2506 super().__init__()
2507 VivadoMessagesMixin.__init__(self)
2508
2509 self._subphase = subphase
2510 self._phaseIndex = None
2511 self._subPhaseIndex = None
2512 self._subSubPhaseIndex = None
2513
2514 def _SubSubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2515 if (match := self._START.match(line._message)) is None:
2516 raise ProcessorException()
2517
2518 self._phaseIndex = int(match["major"])
2519 self._subPhaseIndex = int(match["minor"])
2520 self._subSubPhaseIndex = int(match["micro"])
2521
2522 line._kind = LineKind.SubSubPhaseStart
2523 nextLine = yield line
2524 return nextLine
2525
2526 def _SubSubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2527 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex)
2528
2529 if not line.StartsWith(FINISH):
2530 raise ProcessorException(f"{self.__class__.__name__}._SubSubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2531
2532 line._kind = LineKind.SubSubPhaseEnd
2533 line = yield line
2534
2535 while self._TIME is not None:
2536 if line.StartsWith(self._TIME):
2537 line._kind = LineKind.SubSubPhaseTime
2538 break
2539
2540 line = yield line
2541
2542 nextLine = yield line
2543 return nextLine
2544
2545 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2546 line = yield from self._SubSubPhaseStart(line)
2547
2548 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex)
2549
2550 while True:
2551 if line._kind is LineKind.Empty:
2552 line = yield line
2553 continue
2554 elif line.StartsWith(FINISH):
2555 break
2556 elif isinstance(line, VivadoMessage):
2557 self._AddMessage(line)
2558
2559 line = yield line
2560
2561 nextLine = yield from self._SubSubPhaseFinish(line)
2562 return nextLine
2563
2564 def __str__(self) -> str:
2565 return f"{self.__class__.__name__}: {self._START.pattern}"
2566
2567
2568@export
2569class SubSubPhaseWithChildren(SubSubPhase):
2570 _subSubSubPhases: Dict[Type[SubSubSubPhase], SubSubSubPhase]
2571
2572 def __init__(self, subphase: SubPhase) -> None:
2573 super().__init__(subphase)
2574
2575 self._subSubSubPhases = {p: p(self) for p in self._PARSERS}
2576
2577 def __contains__(self, key: Any) -> bool:
2578 if not issubclass(key, SubSubSubPhase):
2579 ex = TypeError("Parameter 'item' is not a SubSubSubPhase.")
2580 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2581 raise ex
2582
2583 return key in self._subSubSubPhases
2584
2585 def __getitem__(self, key: Type[SubSubSubPhase]) -> SubSubSubPhase:
2586 try:
2587 return self._subSubSubPhases[key]
2588 except KeyError as ex:
2589 raise PhaseNotPresentException(F"SubSubSubPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2590
2591 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2592 line = yield from self._SubSubPhaseStart(line)
2593
2594 activeParsers: List[SubSubSubPhase] = list(self._subSubSubPhases.values())
2595
2596 START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}.{self._subSubPhaseIndex}."
2597
2598 while True:
2599 while True:
2600 if line._kind is LineKind.Empty:
2601 line = yield line
2602 continue
2603 elif isinstance(line, VivadoMessage):
2604 self._AddMessage(line)
2605 elif line.StartsWith(START_PREFIX):
2606 for parser in activeParsers: # type: SubSubSubPhase
2607 if (match := parser._START.match(line._message)) is not None:
2608 line = yield next(phase := parser.Generator(line))
2609 break
2610 else:
2611 WarningCollector.Raise(UnknownSubPhase(f"Unknown subsubsubphase: '{line!r}'", line))
2612 ex = Exception(f"How to recover from here? Unknown subsubsubphase: '{line!r}'")
2613 ex.add_note(f"Current subsubphase: start pattern='{self}'")
2614 ex.add_note(f"Current subphase: start pattern='{self._subphase}'")
2615 ex.add_note(f"Current phase: start pattern='{self._subphase._phase}'")
2616 ex.add_note(f"Current task: start pattern='{self._subphase._phase._task}'")
2617 ex.add_note(f"Current cmd: {self._subphase._phase._task._command}")
2618 raise ex
2619 break
2620 elif line.StartsWith(self._TIME):
2621 line._kind = LineKind.SubSubPhaseTime
2622 nextLine = yield line
2623 return nextLine
2624
2625 line = yield line
2626
2627 while True:
2628 isFinish = False # line.StartsWith("Ending")
2629
2630 try:
2631 processedLine = phase.send(line)
2632
2633 if isinstance(processedLine, VivadoMessage):
2634 self._AddMessage(processedLine)
2635
2636 if isFinish:
2637 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2638 line = yield processedLine
2639 break
2640 except StopIteration as ex:
2641 activeParsers.remove(parser)
2642 line = ex.value
2643 break
2644
2645 line = yield processedLine
2646
2647
2648@export
2649class SubSubSubPhase(BaseParser, VivadoMessagesMixin):
2650 # _NAME: ClassVar[str]
2651 # _START: ClassVar[str]
2652 # _FINISH: ClassVar[str]
2653
2654 _subsubphase: SubSubPhase
2655 _phaseIndex: int
2656 _subPhaseIndex: int
2657 _subSubPhaseIndex: int
2658 _subSubSubPhaseIndex: int
2659 _duration: float
2660
2661 def __init__(self, subsubphase: SubSubPhase) -> None:
2662 super().__init__()
2663 VivadoMessagesMixin.__init__(self)
2664
2665 self._subsubphase = subsubphase
2666 self._phaseIndex = None
2667 self._subPhaseIndex = None
2668 self._subSubPhaseIndex = None
2669 self._subSubSubPhaseIndex = None
2670
2671 def _SubSubSubPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2672 if (match := self._START.match(line._message)) is None:
2673 raise ProcessorException()
2674
2675 self._phaseIndex = int(match["major"])
2676 self._subPhaseIndex = int(match["minor"])
2677 self._subSubPhaseIndex = int(match["micro"])
2678 self._subSubSubPhaseIndex = int(match["nano"])
2679
2680 line._kind = LineKind.SubSubSubPhaseStart
2681 nextLine = yield line
2682 return nextLine
2683
2684 def _SubSubSubPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
2685 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex, subSubSubPhaseIndex=self._subSubSubPhaseIndex)
2686
2687 if not line.StartsWith(FINISH):
2688 raise ProcessorException(f"{self.__class__.__name__}._SubSubSubPhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
2689
2690 line._kind = LineKind.SubSubSubPhaseEnd
2691 line = yield line
2692
2693 while self._TIME is not None:
2694 if line.StartsWith(self._TIME):
2695 line._kind = LineKind.SubSubSubPhaseTime
2696 break
2697
2698 line = yield line
2699
2700 nextLine = yield line
2701 return nextLine
2702
2703 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2704 line = yield from self._SubSubSubPhaseStart(line)
2705
2706 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex, subPhaseIndex=self._subPhaseIndex, subSubPhaseIndex=self._subSubPhaseIndex, subSubSubPhaseIndex=self._subSubSubPhaseIndex)
2707
2708 while True:
2709 if line._kind is LineKind.Empty:
2710 line = yield line
2711 continue
2712 elif line.StartsWith(FINISH):
2713 break
2714 elif isinstance(line, VivadoMessage):
2715 self._AddMessage(line)
2716
2717 line = yield line
2718
2719 nextLine = yield from self._SubSubSubPhaseFinish(line)
2720 return nextLine
2721
2722 def __str__(self) -> str:
2723 return f"{self.__class__.__name__}: {self._START.pattern}"
2724
2725
2726@export
2727class SubSubSubPhaseWithTasks(SubSubSubPhase):
2728 _nestedTasks: Dict[Type[NestedTask], NestedTask]
2729
2730 def __init__(self, subsubphase: SubSubPhase) -> None:
2731 super().__init__(subsubphase)
2732
2733 self._nestedTasks = {p: p(self) for p in self._PARSERS}
2734
2735 def __contains__(self, key: Any) -> bool:
2736 if not issubclass(key, NestedTask):
2737 ex = TypeError("Parameter 'key' is not a NestedTask.")
2738 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2739 raise ex
2740
2741 return key in self._nestedTasks
2742
2743 def __getitem__(self, key: Type[NestedTask]) -> NestedTask:
2744 try:
2745 return self._nestedTasks[key]
2746 except KeyError as ex:
2747 raise NestedTaskNotPresentException(F"NestedTask '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2748
2749 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2750 line = yield from self._SubSubSubPhaseStart(line)
2751
2752 activeParsers: List[NestedTask] = list(self._nestedTasks.values())
2753
2754 # START_PREFIX = f"Phase {self._phaseIndex}.{self._subPhaseIndex}.{self._subSubPhaseIndex}."
2755
2756 while True:
2757 while True:
2758 if line._kind is LineKind.Empty:
2759 line = yield line
2760 continue
2761 elif isinstance(line, VivadoMessage):
2762 self._AddMessage(line)
2763 elif line.StartsWith("Starting "):
2764 for parser in activeParsers: # type: NestedTask
2765 if line.StartsWith(parser._START):
2766 line = yield next(phase := parser.Generator(line))
2767 break
2768 else:
2769 WarningCollector.Raise(UnknownSubPhase(f"Unknown NestedTask: '{line!r}'", line))
2770 ex = Exception(f"How to recover from here? Unknown NestedTask: '{line!r}'")
2771 ex.add_note(f"Current subsubsubphase: start pattern='{self}'")
2772 ex.add_note(f"Current subsubphase: start pattern='{self._subsubphase}'")
2773 ex.add_note(f"Current subphase: start pattern='{self._subsubphase._subphase}'")
2774 ex.add_note(f"Current phase: start pattern='{self._subsubphase._subphase._phase}'")
2775 ex.add_note(f"Current task: start pattern='{self._subsubphase._subphase._phase._task}'")
2776 ex.add_note(f"Current cmd: {self._subsubphase._subphase._phase._task._command}")
2777 raise ex
2778 break
2779 elif line.StartsWith(self._TIME):
2780 line._kind = LineKind.SubSubSubPhaseTime
2781 nextLine = yield line
2782 return nextLine
2783
2784 line = yield line
2785
2786 while True:
2787 isFinish = False # line.StartsWith("Ending")
2788
2789 try:
2790 processedLine = phase.send(line)
2791
2792 if isinstance(processedLine, VivadoMessage):
2793 self._AddMessage(processedLine)
2794
2795 if isFinish:
2796 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2797 line = yield processedLine
2798 break
2799 except StopIteration as ex:
2800 activeParsers.remove(parser)
2801 line = ex.value
2802 break
2803
2804 line = yield processedLine
2805
2806
2807@export
2808class NestedTask(BaseParser, VivadoMessagesMixin):
2809 # _NAME: ClassVar[str]
2810 # _START: ClassVar[str]
2811 # _FINISH: ClassVar[str]
2812 # _TIME: ClassVar[str] = "Time (s):"
2813
2814 _subsubsubphase: SubSubSubPhaseWithTasks
2815 _duration: float
2816
2817 def __init__(self, subsubsubphase: SubSubSubPhaseWithTasks) -> None:
2818 super().__init__()
2819 VivadoMessagesMixin.__init__(self)
2820
2821 self._subsubsubphase = subsubsubphase
2822
2823 @readonly
2824 def SubSubSubPhase(self) -> SubSubSubPhaseWithTasks:
2825 return self._subsubsubphase
2826
2827 def _NestedTaskStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2828 if not line.StartsWith(self._START):
2829 raise ProcessorException(f"{self.__class__.__name__}._TaskStart(): Expected '{self._START}' at line {line._lineNumber}.")
2830
2831 line._kind = LineKind.NestedTaskStart
2832 nextLine = yield line
2833 return nextLine
2834
2835 def _NestedTaskFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2836 if not line.StartsWith(self._FINISH):
2837 raise ProcessorException(f"{self.__class__.__name__}._TaskFinish(): Expected '{self._FINISH}' at line {line._lineNumber}.")
2838
2839 line._kind = LineKind.NestedTaskEnd
2840 line = yield line
2841
2842 if self._TIME is not None:
2843 while True:
2844 if line.StartsWith(self._TIME):
2845 line._kind = LineKind.TaskTime
2846 line = yield line
2847 break
2848
2849 line = yield line
2850
2851 return line
2852
2853 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2854 line = yield from self._NestedTaskStart(line)
2855
2856 while True:
2857 if line._kind is LineKind.Empty:
2858 line = yield line
2859 continue
2860 elif self._FINISH is not None and line.StartsWith("Ending"):
2861 break
2862 elif isinstance(line, VivadoMessage):
2863 self._AddMessage(line)
2864 elif line.StartsWith(self._TIME):
2865 line._kind = LineKind.TaskTime
2866 nextLine = yield line
2867 return nextLine
2868
2869 line = yield line
2870
2871 nextLine = yield from self._NestedTaskFinish(line)
2872 return nextLine
2873
2874 def __str__(self) -> str:
2875 return self._NAME
2876
2877
2878@export
2879class NestedTaskWithPhases(NestedTask):
2880 """
2881 A task's output emitted by a Vivado command.
2882
2883 .. rubric:: Extracted information
2884
2885 * Vivado messages (info, warning, critical warning, error).
2886 * Nested phases
2887
2888 .. rubric:: Example
2889
2890 .. code-block::
2891
2892 Phase 4.1.1.1 BUFG Insertion
2893
2894 Starting Physical Synthesis Task
2895
2896 Phase 1 Physical Synthesis Initialization
2897 INFO: [Physopt 32-721] Multithreading enabled for phys_opt_design using a maximum of 2 CPUs
2898 INFO: [Physopt 32-619] Estimated Timing Summary | WNS=-0.733 | TNS=-0.936 |
2899 Phase 1 Physical Synthesis Initialization | Checksum: 1818afcc0
2900
2901 Time (s): cpu = 00:00:00 ; elapsed = 00:00:00.014 . Memory (MB): peak = 1865.645 ; gain = 0.000
2902 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.
2903 Ending Physical Synthesis Task | Checksum: 22839c186
2904
2905 Time (s): cpu = 00:00:00 ; elapsed = 00:00:00.016 . Memory (MB): peak = 1865.645 ; gain = 0.000
2906 Phase 4.1.1.1 BUFG Insertion | Checksum: 1a8cbaaf2
2907 """
2908 # _PARSERS: ClassVar[Tuple[Type["SubTask"], ...]]
2909
2910 _nestedPhases: Dict[Type[NestedPhase], NestedPhase]
2911
2912 def __init__(self, subsubsubPhase: SubSubSubPhaseWithTasks) -> None:
2913 super().__init__(subsubsubPhase)
2914
2915 self._nestedPhases = {p: p(self) for p in self._PARSERS}
2916
2917 @readonly
2918 def NestedPhases(self) -> Dict[Type[NestedPhase], NestedPhase]:
2919 return self._nestedPhases
2920
2921 def __contains__(self, key: Any) -> bool:
2922 if not issubclass(key, NestedPhase):
2923 ex = TypeError("Parameter 'key' is not a NestedPhase.")
2924 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
2925 raise ex
2926
2927 return key in self._nestedPhases
2928
2929 def __getitem__(self, key: Type[NestedPhase]) -> NestedPhase:
2930 try:
2931 return self._nestedPhases[key]
2932 except KeyError as ex:
2933 raise SubTaskNotPresentException(F"NestedPhase '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
2934
2935 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
2936 line = yield from self._NestedTaskStart(line)
2937
2938 activeParsers: List[Phase] = list(self._nestedPhases.values())
2939
2940 while True:
2941 while True:
2942 if line._kind is LineKind.Empty:
2943 line = yield line
2944 continue
2945 elif isinstance(line, VivadoMessage):
2946 self._AddMessage(line)
2947 elif line.StartsWith("Phase "):
2948 for parser in activeParsers: # type: NestedPhase
2949 if (match := parser._START.match(line._message)) is not None:
2950 line = yield next(phase := parser.Generator(line))
2951 break
2952 else:
2953 WarningCollector.Raise(UnknownSubPhase(f"Unknown NestedPhase: '{line!r}'", line))
2954 ex = Exception(f"How to recover from here? Unknown NestedPhase: '{line!r}'")
2955 ex.add_note(f"Current nestedtask: start pattern='{self}'")
2956 ex.add_note(f"Current subsubsubphase: start pattern='{self._subsubsubphase}'")
2957 ex.add_note(f"Current subsubphase: start pattern='{self._subsubsubphase._subsubphase}'")
2958 ex.add_note(f"Current subphase: start pattern='{self._subsubsubphase._subsubphase._subphase}'")
2959 ex.add_note(f"Current phase: start pattern='{self._subsubsubphase._subsubphase._subphase._phase}'")
2960 ex.add_note(f"Current task: start pattern='{self._subsubsubphase._subsubphase._subphase._phase._task}'")
2961 ex.add_note(f"Current cmd: {self._subsubsubphase._subsubphase._subphase._phase._task._command}")
2962 raise ex
2963 break
2964 elif line.StartsWith("Ending"):
2965 nextLine = yield from self._NestedTaskFinish(line)
2966 return nextLine
2967 elif line.StartsWith(self._TIME):
2968 line._kind = LineKind.TaskTime
2969 nextLine = yield line
2970 return nextLine
2971
2972 line = yield line
2973
2974 while True:
2975 isFinish = False # line.StartsWith("Ending") # FIXME: detect end, but time might come later
2976
2977 try:
2978 processedLine = phase.send(line)
2979
2980 if isinstance(processedLine, VivadoMessage):
2981 self._AddMessage(processedLine)
2982
2983 if isFinish:
2984 WarningCollector.Raise(UndetectedEnd(f"Didn't detect finish: '{processedLine!r}'", processedLine))
2985 line = yield processedLine
2986 break
2987 except StopIteration as ex:
2988 activeParsers.remove(parser)
2989 line = ex.value
2990 break
2991
2992 line = yield processedLine
2993
2994
2995@export
2996class NestedPhase(BaseParser, VivadoMessagesMixin):
2997 # _NAME: ClassVar[str]
2998 # _START: ClassVar[str]
2999 # _FINISH: ClassVar[str]
3000 # _TIME: ClassVar[str] = "Time (s):"
3001 _FINAL: ClassVar[Nullable[str]] = None
3002
3003 _nestedTask: NestedTaskWithPhases
3004 _phaseIndex: int
3005 _duration: float
3006
3007 def __init__(self, nestedTask: TaskWithPhases) -> None:
3008 super().__init__()
3009 VivadoMessagesMixin.__init__(self)
3010
3011 self._nestedTask = nestedTask
3012 self._phaseIndex = None
3013
3014 @readonly
3015 def NestedTask(self) -> NestedTaskWithPhases:
3016 return self._nestedTask
3017
3018 def _NestedPhaseStart(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
3019 if (match := self._START.match(line._message)) is None:
3020 raise ProcessorException(f"{self.__class__.__name__}._PhaseStart(): Expected '{self._START}' at line {line._lineNumber}.")
3021
3022 self._phaseIndex = int(match["major"])
3023
3024 line._kind = LineKind.NestedPhaseStart
3025 nextLine = yield line
3026 return nextLine
3027
3028 def _NestedPhaseFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
3029 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
3030 if not line.StartsWith(FINISH):
3031 raise ProcessorException(f"{self.__class__.__name__}._PhaseFinish(): Expected '{FINISH}' at line {line._lineNumber}.")
3032
3033 line._kind = LineKind.NestedPhaseEnd
3034 line = yield line
3035
3036 if self._TIME is not None:
3037 while True:
3038 if line.StartsWith(self._TIME):
3039 line._kind = LineKind.PhaseTime
3040 break
3041 elif isinstance(line, VivadoMessage):
3042 self._AddMessage(line)
3043
3044 line = yield line
3045
3046 line = yield line
3047
3048 return line
3049
3050 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
3051 line = yield from self._NestedPhaseStart(line)
3052
3053 FINISH = self._FINISH.format(phaseIndex=self._phaseIndex)
3054
3055 while True:
3056 if line._kind is LineKind.Empty:
3057 line = yield line
3058 continue
3059 elif isinstance(line, VivadoMessage):
3060 self._AddMessage(line)
3061 elif line.StartsWith(FINISH):
3062 break
3063
3064 line = yield line
3065
3066 nextLine = yield from self._NestedPhaseFinish(line)
3067 return nextLine
3068
3069 def __str__(self) -> str:
3070 return f"{self.__class__.__name__}: {self._START.pattern}"
3071
3072
3073@export
3074class Synth_Design(CommandWithSections):
3075 """
3076 A Vivado command output parser for ``synth_design``.
3077 """
3078 from . import SynthesizeDesign as _SynthDesign
3079
3080 _TCL_COMMAND: ClassVar[str] = "synth_design"
3081 _PARSERS: ClassVar[Tuple[Type[Section], ...]] = (
3082 _SynthDesign.RTLElaboration,
3083 _SynthDesign.HandlingCustomAttributes,
3084 _SynthDesign.ConstraintValidation,
3085 _SynthDesign.LoadingPart,
3086 _SynthDesign.ApplySetPropertyXDCConstraints,
3087 _SynthDesign.RTLComponentStatistics,
3088 _SynthDesign.RTLHierarchicalComponentStatistics,
3089 _SynthDesign.PartResourceSummary,
3090 _SynthDesign.CrossBoundaryAndAreaOptimization,
3091 _SynthDesign.ROM_RAM_DSP_SR_Retiming,
3092 _SynthDesign.ApplyingXDCTimingConstraints,
3093 _SynthDesign.TimingOptimization,
3094 _SynthDesign.TechnologyMapping,
3095 _SynthDesign.IOInsertion,
3096 _SynthDesign.FlatteningBeforeIOInsertion,
3097 _SynthDesign.FinalNetlistCleanup,
3098 _SynthDesign.RenamingGeneratedInstances,
3099 _SynthDesign.RebuildingUserHierarchy,
3100 _SynthDesign.RenamingGeneratedPorts,
3101 _SynthDesign.RenamingGeneratedNets,
3102 _SynthDesign.WritingSynthesisReport,
3103 )
3104
3105 @readonly
3106 def HasLatches(self) -> bool:
3107 """
3108 Read-only property returning if synthesis inferred latches into the design.
3109
3110 Latch detection is based on:
3111
3112 * Vivado message ``synth 8-327``
3113 * Cells of lind ``LD`` listed in the *Cell Usage* report.
3114
3115 :returns: True, if the design contains latches.
3116 """
3117 from .SynthesizeDesign import WritingSynthesisReport
3118
3119 if (8 in self._messagesByID) and (327 in self._messagesByID[8]):
3120 return True
3121
3122 return "LD" in self._sections[WritingSynthesisReport]._cells
3123
3124 @readonly
3125 def Latches(self) -> List[VivadoMessage]:
3126 """
3127 Read-only property to access a list of Vivado output messages for inferred latches.
3128
3129 :returns: A list of Vivado messages for interred latches.
3130
3131 .. note::
3132
3133 This returns ``[Synth 8-327]`` messages.
3134
3135 .. code-block::
3136
3137 WARNING: [Synth 8-327] inferring latch for variable 'Q_reg'
3138 """
3139 if 8 in self._messagesByID:
3140 if 327 in (synthMessages := self._messagesByID[8]):
3141 return list(synthMessages[327])
3142
3143 return []
3144
3145 @readonly
3146 def Statemachines(self) -> Dict[str, List[str]]:
3147 """
3148
3149 :returns: undocumented
3150
3151 .. note::
3152
3153 INFO: [Synth 8-802] inferred FSM for state register 'State_reg' in module 'stream_Padder'
3154 INFO: [Synth 8-802] inferred FSM for state register 'RX_blk.blkRXFSM.State_reg' in module 'eth_XGEMAC_XGMII'
3155 """
3156
3157 @readonly
3158 def DistributedRAMs(self) -> Dict[str, Any]:
3159 """
3160
3161 :returns: undocumented
3162 """
3163
3164 @readonly
3165 def BlockRAMs(self) -> Dict[str, Any]:
3166 """
3167
3168 :returns: undocumented
3169 """
3170
3171
3172 @readonly
3173 def UltraRAMs(self) -> Dict[str, Any]:
3174 """
3175
3176 :returns: undocumented
3177 """
3178
3179 @readonly
3180 def ShiftRegister(self) -> Dict[str, Dict[str, Any]]:
3181 """
3182
3183 :returns: undocumented
3184
3185 .. note::
3186
3187 Static Shift Register Report:
3188 Dynamic Shift Register Report:
3189 """
3190
3191 @readonly
3192 def UndrivenPins(self) -> Dict[str, str]:
3193 """
3194
3195 :returns: undocumented
3196
3197 .. note::
3198
3199 WARNING: [Synth 8-3295] tying undriven pin AXI4FullPipeLine_M2S[AWID]_inferred:in0 to constant 0
3200 """
3201
3202 # ignored instructions
3203 # 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.
3204
3205 @readonly
3206 def HasBlackboxes(self) -> bool:
3207 """
3208 Read-only property returning if the design contains black-boxes.
3209
3210 :returns: True, if the design contains black-boxes.
3211 """
3212 from .SynthesizeDesign import WritingSynthesisReport
3213
3214 return len(self._sections[WritingSynthesisReport]._blackboxes) > 0
3215
3216 @readonly
3217 def Blackboxes(self) -> Dict[str, int]:
3218 """
3219 Read-only property to access the dictionary of found blackbox statistics.
3220
3221 :returns: The dictionary of found blackbox statistics.
3222 """
3223 from .SynthesizeDesign import WritingSynthesisReport
3224
3225 return self._sections[WritingSynthesisReport]._blackboxes
3226
3227 @readonly
3228 def Cells(self) -> Dict[str, int]:
3229 """
3230 Read-only property to access the dictionary of synthesized cell statistics.
3231
3232 :returns: The dictionary of used cell statistics.
3233 """
3234 from .SynthesizeDesign import WritingSynthesisReport
3235
3236 return self._sections[WritingSynthesisReport]._cells
3237
3238 @readonly
3239 def VHDLReportMessages(self) -> List[VHDLReportMessage]:
3240 """
3241 Read-only property to access a list of Vivado output messages generated by VHDL report statement.
3242
3243 :returns: A list of VHDL report statement outputs.
3244
3245 .. note::
3246
3247 This returns ``[Synth 8-6031]`` messages.
3248
3249 .. code-block::
3250
3251 INFO: [Synth 8-6031] RTL report: "TimingToCycles(time, freq): period=10.000000 ns -- 0.000000 fs" [C:/[...]/StopWatch/src/Utilities.pkg.vhdl:118]
3252 """
3253 if 8 in self._messagesByID:
3254 if 6031 in (synthMessages := self._messagesByID[8]):
3255 return list(synthMessages[6031])
3256
3257 return []
3258
3259 @readonly
3260 def VHDLAssertMessages(self) -> List[VHDLReportMessage]:
3261 """
3262 Read-only property to access a list of Vivado output messages generated by VHDL assert statement.
3263
3264 :returns: A list of VHDL assert statement outputs.
3265
3266 .. note::
3267
3268 This returns ``[Synth 8-63]`` messages.
3269
3270 .. code-block::
3271
3272 INFO: [Synth 8-63] RTL assertion: "CLOCK_FREQ: 100.000000 ns" [C:/[...]/StopWatch/src/Debouncer.vhdl:28]
3273 """
3274 if 8 in self._messagesByID:
3275 if 63 in (synthMessages := self._messagesByID[8]):
3276 return list(synthMessages[63])
3277
3278 return []
3279
3280 def SectionDetector(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, None]:
3281 from .SynthesizeDesign import RTLElaboration
3282
3283 if not (isinstance(line, VivadoTclCommand) and line._tclCommand == self._TCL_COMMAND):
3284 raise ProcessorException() # FIXME: add exception message
3285
3286 activeParsers: List[Section] = [p(self) for p in self._PARSERS]
3287 rtlElaboration: RTLElaboration = next(p for p in activeParsers if isinstance(p, RTLElaboration))
3288
3289 line = yield line
3290 if line == "Starting synth_design":
3291 line._kind = LineKind.Verbose
3292 else:
3293 raise ProcessorException() # FIXME: add exception message
3294
3295 line = yield line
3296 while True:
3297 while True:
3298 if line._kind is LineKind.Empty:
3299 line = yield line
3300 continue
3301 elif isinstance(line, VivadoMessage):
3302 self._AddMessage(line)
3303 elif line.StartsWith("Start "):
3304 for parser in activeParsers: # type: Section
3305 if line.StartsWith(parser._START):
3306 # Found a suitable section parser.
3307 # Add section parser to list of found sections.
3308 # In case of duplicates, create a chain of psrer instances.
3309 if parser not in self._sections:
3310 self._sections.append(parser)
3311 else:
3312 parser._next = (newParser := parser.__class__(self))
3313 parser = newParser
3314
3315 line = next(section := parser.Generator(line))
3316 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter
3317 break
3318 else:
3319 WarningCollector.Raise(UnknownSection(f"Unknown section: '{line!r}'", line))
3320 ex = Exception(f"How to recover from here? Unknown section: '{line!r}'")
3321 # ex.add_note(f"Current task: start pattern='{self._task}'")
3322 ex.add_note(f"Current cmd: {self}")
3323 raise ex
3324 break
3325 elif line.StartsWith("Starting "):
3326 if line.StartsWith(rtlElaboration._START):
3327 self._sections.append(parser := rtlElaboration)
3328 line = next(section := parser.Generator(line))
3329 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter
3330 break
3331 elif line.StartsWith(self._TCL_COMMAND):
3332 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"):
3333 line._kind |= LineKind.Success
3334
3335 # FIXME: use similar style like for _TIME
3336 line = yield line
3337 lastLine = yield line
3338 return lastLine
3339 elif line.StartsWith("Finished RTL Optimization Phase"):
3340 line._kind = LineKind.PhaseEnd
3341 line._previousLine._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter
3342 elif line.StartsWith("----"):
3343 if LineKind.Phase in line._previousLine._kind:
3344 line._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter
3345
3346 line = yield line
3347
3348 line = yield line
3349
3350 while True:
3351 if line.StartsWith("Finished"):
3352 l = line[9:]
3353 if not (l.startswith("Flattening") or l.startswith("Final")):
3354 line = yield section.send(line)
3355 break
3356
3357 if isinstance(line, VivadoMessage):
3358 self._AddMessage(line)
3359
3360 line = yield section.send(line)
3361
3362 line = yield section.send(line)
3363
3364 if not parser._DUPLICATES:
3365 activeParsers.remove(parser)
3366
3367
3368@export
3369class Link_Design(Command):
3370 """
3371 A Vivado command output parser for ``link_design``.
3372 """
3373 _TCL_COMMAND: ClassVar[str] = "link_design"
3374 _TIME: ClassVar[str] = "Time (s):"
3375
3376 _ParsingXDCFile_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\]$""")
3377 _FinishedParsingXDCFile_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\]$""")
3378 _ParsingXDCFileForCell_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\] for cell '(.*)'$""")
3379 _FinishedParsingXDCFileForCell_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\] for cell '(.*)'$""")
3380
3381 _commonXDCFiles: Dict[Path, List[VivadoMessage]]
3382 _perCellXDCFiles: Dict[Path, Dict[str, List[VivadoMessage]]]
3383
3384 def __init__(self, processor: Processor) -> None:
3385 super().__init__(processor)
3386
3387 self._commonXDCFiles = {}
3388 self._perCellXDCFiles = {}
3389
3390 @readonly
3391 def CommonXDCFiles(self) -> Dict[Path, List[VivadoMessage]]:
3392 return self._commonXDCFiles
3393
3394 @readonly
3395 def PerCellXDCFiles(self) -> Dict[Path, Dict[str, List[VivadoMessage]]]:
3396 return self._perCellXDCFiles
3397
3398 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3399 line = yield from self._CommandStart(line)
3400
3401 end = f"{self._TCL_COMMAND} "
3402 while True:
3403 if line._kind is LineKind.Empty:
3404 line = yield line
3405 continue
3406 elif isinstance(line, VivadoMessage):
3407 self._AddMessage(line)
3408 elif (match := self._ParsingXDCFile_Pattern.match(line._message)) is not None:
3409 line._kind = LineKind.Normal
3410
3411 path = Path(match[1])
3412 self._commonXDCFiles[path] = (messages := [])
3413
3414 line = yield line
3415 while True:
3416 if line._kind is LineKind.Empty:
3417 line = yield line
3418 continue
3419 elif isinstance(line, VivadoMessage):
3420 self._AddMessage(line)
3421 messages.append(line)
3422 elif (match := self._FinishedParsingXDCFile_Pattern.match(line._message)) is not None and path == Path(match[1]):
3423 line._kind = LineKind.Normal
3424 break
3425 elif line.StartsWith("Finished Parsing XDC File"):
3426 line._kind = LineKind.ProcessorError
3427 break
3428 elif line.StartsWith(end):
3429 break
3430
3431 line = yield line
3432 elif (match := self._ParsingXDCFileForCell_Pattern.match(line._message)) is not None:
3433 line._kind = LineKind.Normal
3434
3435 path = Path(match[1])
3436 cell = match[2]
3437 if path in self._perCellXDCFiles:
3438 self._perCellXDCFiles[path][cell] = (messages := [])
3439 else:
3440 self._perCellXDCFiles[path] = {cell: (messages := [])}
3441
3442 line = yield line
3443 while True:
3444 if line._kind is LineKind.Empty:
3445 line = yield line
3446 continue
3447 elif isinstance(line, VivadoMessage):
3448 self._AddMessage(line)
3449 messages.append(line)
3450 elif (match := self._FinishedParsingXDCFileForCell_Pattern.match(line._message)) is not None and path == Path(match[1]) and cell == match[2]:
3451 line._kind = LineKind.Normal
3452 break
3453 elif line.StartsWith("Finished Parsing XDC File"):
3454 line._kind = LineKind.ProcessorError
3455 break
3456 elif line.StartsWith(end):
3457 break
3458
3459 line = yield line
3460
3461 if line.StartsWith(end):
3462 nextLine = yield from self._CommandFinish(line)
3463 return nextLine
3464
3465 line = yield line
3466
3467
3468@export
3469class Opt_Design(CommandWithTasks):
3470 """
3471 A Vivado command output parser for ``opt_design``.
3472 """
3473 from . import OptimizeDesign as _OptDesign
3474
3475 _TCL_COMMAND: ClassVar[str] = "opt_design"
3476 _TIME: ClassVar[str] = None
3477
3478 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3479 _OptDesign.DRCTask,
3480 _OptDesign.CacheTimingInformationTask,
3481 _OptDesign.LogicOptimizationTask,
3482 _OptDesign.PowerOptimizationTask,
3483 _OptDesign.FinalCleanupTask,
3484 _OptDesign.NetlistObfuscationTask
3485 )
3486
3487 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3488 line = yield from self._CommandStart(line)
3489
3490 activeParsers: List[Task] = list(self._tasks.values())
3491
3492 while True:
3493 while True:
3494 if line._kind is LineKind.Empty:
3495 line = yield line
3496 continue
3497 elif isinstance(line, VivadoMessage):
3498 self._AddMessage(line)
3499 elif line.StartsWith("Starting ") and not line.StartsWith("Starting Connectivity Check Task"):
3500 for parser in activeParsers: # type: Section
3501 if line.StartsWith(parser._START):
3502 line = yield next(task := parser.Generator(line))
3503 break
3504 else:
3505 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3506 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3507 # ex.add_note(f"Current task: start pattern='{self._task}'")
3508 ex.add_note(f"Current cmd: {self}")
3509 raise ex
3510 break
3511 elif line.StartsWith(self._TCL_COMMAND):
3512 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"):
3513 line._kind |= LineKind.Success
3514
3515 # FIXME: use similar style like for _TIME
3516 line = yield line
3517 lastLine = yield line
3518 return lastLine
3519 # line._kind = LineKind.Unprocessed
3520
3521 line = yield line
3522
3523 while True:
3524 # if line.StartsWith("Ending"):
3525 # line = yield task.send(line)
3526 # break
3527
3528 if isinstance(line, VivadoMessage):
3529 self._AddMessage(line)
3530
3531 try:
3532 line = yield task.send(line)
3533 except StopIteration as ex:
3534 task = None
3535 line = ex.value
3536
3537 if isinstance(line, VivadoMessage):
3538 line = yield line
3539
3540 break
3541
3542 if task is not None:
3543 line = yield task.send(line)
3544
3545 activeParsers.remove(parser)
3546
3547
3548@export
3549class Place_Design(CommandWithTasks):
3550 """
3551 A Vivado command output parser for ``place_design``.
3552 """
3553 from . import PlaceDesign as _PlaceDesign
3554
3555 _TCL_COMMAND: ClassVar[str] = "place_design"
3556 _TIME: ClassVar[str] = None
3557
3558 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3559 _PlaceDesign.PlacerTask,
3560 )
3561
3562 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3563 line = yield from self._CommandStart(line)
3564
3565 activeParsers: List[Task] = list(self._tasks.values())
3566
3567 while True:
3568 while True:
3569 if line._kind is LineKind.Empty:
3570 line = yield line
3571 continue
3572 elif isinstance(line, VivadoMessage):
3573 self._AddMessage(line)
3574 elif line.StartsWith("Starting "):
3575 for parser in activeParsers: # type: Section
3576 if line.StartsWith(parser._START):
3577 line = yield next(task := parser.Generator(line))
3578 break
3579 else:
3580 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3581 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3582 # ex.add_note(f"Current task: start pattern='{self._task}'")
3583 ex.add_note(f"Current cmd: {self}")
3584 raise ex
3585 break
3586 elif line.StartsWith(self._TCL_COMMAND):
3587 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"):
3588 line._kind |= LineKind.Success
3589
3590 # FIXME: use similar style like for _TIME
3591 line = yield line
3592 lastLine = yield line
3593 return lastLine
3594 # line._kind = LineKind.Unprocessed
3595
3596 line = yield line
3597
3598 while True:
3599 # if line.StartsWith("Ending"):
3600 # line = yield task.send(line)
3601 # break
3602
3603 if isinstance(line, VivadoMessage):
3604 self._AddMessage(line)
3605
3606 try:
3607 line = yield task.send(line)
3608 except StopIteration as ex:
3609 task = None
3610 line = ex.value
3611
3612 if isinstance(line, VivadoMessage):
3613 line = yield line
3614
3615 break
3616
3617 if task is not None:
3618 line = yield task.send(line)
3619
3620 activeParsers.remove(parser)
3621
3622
3623@export
3624class PhyOpt_Design(CommandWithTasks):
3625 """
3626 A Vivado command output parser for ``phy_opt_design``.
3627 """
3628 from . import PhysicalOptimizeDesign as _PhyOptDesign
3629
3630 _TCL_COMMAND: ClassVar[str] = "phys_opt_design"
3631 _TIME: ClassVar[str] = None
3632
3633 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3634 _PhyOptDesign.InitialUpdateTimingTask,
3635 _PhyOptDesign.PhysicalSynthesisTask
3636 )
3637
3638 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3639 line = yield from self._CommandStart(line)
3640
3641 activeParsers: List[Task] = list(self._tasks.values())
3642
3643 while True:
3644 while True:
3645 if line._kind is LineKind.Empty:
3646 line = yield line
3647 continue
3648 elif isinstance(line, VivadoMessage):
3649 self._AddMessage(line)
3650 elif line.StartsWith("Starting "):
3651 for parser in activeParsers: # type: Section
3652 if line.StartsWith(parser._START):
3653 line = yield next(task := parser.Generator(line))
3654 break
3655 else:
3656 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3657 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3658 # ex.add_note(f"Current task: start pattern='{self._task}'")
3659 ex.add_note(f"Current cmd: {self}")
3660 raise ex
3661 break
3662 elif line.StartsWith(self._TCL_COMMAND):
3663 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"):
3664 line._kind |= LineKind.Success
3665
3666 # FIXME: use similar style like for _TIME
3667 line = yield line
3668 lastLine = yield line
3669 return lastLine
3670 # line._kind = LineKind.Unprocessed
3671
3672 line = yield line
3673
3674 while True:
3675 # if line.StartsWith("Ending"):
3676 # line = yield task.send(line)
3677 # break
3678
3679 if isinstance(line, VivadoMessage):
3680 self._AddMessage(line)
3681
3682 try:
3683 line = yield task.send(line)
3684 except StopIteration as ex:
3685 task = None
3686 line = ex.value
3687
3688 if isinstance(line, VivadoMessage):
3689 line = yield line
3690
3691 break
3692
3693 if task is not None:
3694 line = yield task.send(line)
3695
3696 activeParsers.remove(parser)
3697
3698
3699@export
3700class Route_Design(CommandWithTasks):
3701 """
3702 A Vivado command output parser for ``route_design``.
3703 """
3704 from . import RouteDesign as _RouteDesign
3705
3706 _TCL_COMMAND: ClassVar[str] = "route_design"
3707 _TIME: ClassVar[str] = "Time (s):"
3708
3709 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
3710 _RouteDesign.RoutingTask,
3711 )
3712
3713 def SectionDetector(self, line: VivadoLine) -> Generator[Union[VivadoLine, ProcessorException], VivadoLine, VivadoLine]:
3714 line = yield from self._CommandStart(line)
3715
3716 activeParsers: List[Task] = list(self._tasks.values())
3717
3718 while True:
3719 while True:
3720 if line._kind is LineKind.Empty:
3721 line = yield line
3722 continue
3723 elif isinstance(line, VivadoMessage):
3724 self._AddMessage(line)
3725 elif line.StartsWith("Starting "):
3726 for parser in activeParsers: # type: Section
3727 if line.StartsWith(parser._START):
3728 line = yield next(task := parser.Generator(line))
3729 break
3730 else:
3731 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
3732 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
3733 # ex.add_note(f"Current task: start pattern='{self._task}'")
3734 ex.add_note(f"Current cmd: {self}")
3735 raise ex
3736 break
3737 elif line.StartsWith(self._TCL_COMMAND):
3738 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"):
3739 line._kind |= LineKind.Success
3740
3741 # FIXME: use similar style like for _TIME
3742 line = yield line
3743 lastLine = yield line
3744 return lastLine
3745 # line._kind = LineKind.Unprocessed
3746
3747 line = yield line
3748
3749 while True:
3750 # if line.StartsWith("Ending"):
3751 # line = yield task.send(line)
3752 # break
3753
3754 if isinstance(line, VivadoMessage):
3755 self._AddMessage(line)
3756
3757 try:
3758 line = yield task.send(line)
3759 except StopIteration as ex:
3760 task = None
3761 line = ex.value
3762
3763 if isinstance(line, VivadoMessage):
3764 line = yield line
3765
3766 break
3767
3768 if task is not None:
3769 line = yield task.send(line)
3770
3771 activeParsers.remove(parser)
3772
3773
3774@export
3775class Write_Bitstream(Command):
3776 """
3777 A Vivado command output parser for ``write_bitstream``.
3778 """
3779 _TCL_COMMAND: ClassVar[str] = "write_bitstream"
3780 _TIME: ClassVar[str] = "Time (s):"
3781
3782
3783@export
3784class Report_DRC(Command):
3785 """
3786 A Vivado command output parser for ``report_drc``.
3787 """
3788 _TCL_COMMAND: ClassVar[str] = "report_drc"
3789 _TIME: ClassVar[str] = "Time (s):"
3790
3791
3792@export
3793class Report_Methodology(Command):
3794 """
3795 A Vivado command output parser for ``report_methodology``.
3796 """
3797 _TCL_COMMAND: ClassVar[str] = "report_methodology"
3798 _TIME: ClassVar[str] = None
3799
3800
3801@export
3802class Report_Power(Command):
3803 """
3804 A Vivado command output parser for ``report_power``.
3805 """
3806 _TCL_COMMAND: ClassVar[str] = "report_power"
3807 _TIME: ClassVar[str] = None
3808
3809
3810@export
3811class Open_Checkpoint(Command):
3812 """
3813 A Vivado command output parser for ``open_checkpoint``.
3814 """
3815 _TCL_COMMAND: ClassVar[str] = "open_checkpoint"
3816 _TIME: ClassVar[str] = "Time (s):"
3817
3818 def _CommandFinish(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
3819 end = f"{self._TCL_COMMAND}: {self._TIME}"
3820
3821 if line.StartsWith(end):
3822 line._kind = LineKind.TaskTime
3823 line = yield line
3824 else:
3825 pass # FIXME: error
3826
3827 return line
3828
3829
3830@export
3831class VivadoProcessor(VivadoMessagesMixin, mixin=True):
3832 """
3833 A processor for Vivado log outputs.
3834
3835 Each output line from Vivado gets processed and converted into a :class:`ProcessedLine` objects. Such lines form a
3836 doubly-linked list.
3837 """
3838 _duration: Nullable[_Duration] #: Duration of the observed process (e.g. start to end of synthesis).
3839 _processingDuration: Nullable[_Duration] #: Duration for the log output processor to classify all log lines.
3840
3841 _lines: List[VivadoLine] #: A list of processed log message lines.
3842 _preamble: Nullable[Preamble] #: Reference to the Vivado preamble written after tool startup.
3843 _postamble: Nullable[Postamble] #: Reference to the Vivado postamble written after tool startup.
3844 _commands: Dict[Type[Command], Command] #: A dictionary of processed Vivado commands.
3845
3846 def __init__(self) -> None:
3847 """
3848 Initializes a Vivado log output processor.
3849 """
3850 super().__init__()
3851
3852 self._duration = None
3853 self._processingDuration = None
3854
3855 self._lines = []
3856 self._preamble = None
3857 self._postamble = None
3858 self._commands = {}
3859
3860 @readonly
3861 def Lines(self) -> List[VivadoLine]:
3862 """
3863 Read-only property to access the list of processed and classified log lines (messages).
3864
3865 :returns: A list of processed lines.
3866 """
3867 return self._lines
3868
3869 @readonly
3870 def Preamble(self) -> Nullable[Preamble]:
3871 """
3872 Read-only property to access the parsed preamble information.
3873
3874 :returns: The log's output preamble.
3875 """
3876 return self._preamble
3877
3878 @readonly
3879 def Postamble(self) -> Nullable[Postamble]:
3880 """
3881 Read-only property to access the parsed postamble information.
3882
3883 :returns: The log's output postamble.
3884 """
3885 return self._postamble
3886
3887 @readonly
3888 def Commands(self) -> Dict[Type[Command], Command]:
3889 """
3890 Read-only property to access the dictionary of processed Vivado commands.
3891
3892 :returns: The dictionary of processed Vivado commands.
3893 """
3894 return self._commands
3895
3896 @readonly
3897 def StartDateTime(self) -> datetime:
3898 if self._preamble is None:
3899 raise ValueError("Preamble was not found when parsing the Vivado log outputs.")
3900
3901 return self._preamble.StartDateTime
3902
3903 @readonly
3904 def ExitDateTime(self) -> datetime:
3905 if self._postamble is None:
3906 raise ValueError("Postamble was not found when parsing the Vivado log outputs.")
3907
3908 return self._postamble.ExitDateTime
3909
3910 @readonly
3911 def Duration(self) -> _Duration:
3912 """
3913 Read-only property to return the duration of the observed process.
3914
3915 The observed process is e.g. a synthesis, from its start (preamble) to its end (postamble).
3916
3917 :returns: The observed process' execution duration.
3918 """
3919 return _Duration.FromTimedelta(self.ExitDateTime - self.StartDateTime)
3920
3921 @readonly
3922 def ProcessingDuration(self) -> Nullable[_Duration]:
3923 """
3924 Read-only property to access the duration the log output processor spent on its input (:attr:`_processingDuration`).
3925
3926 The duration has two meanings, depending on the input:
3927
3928 * Lines received as a stream, e.g. from STDIN in a pipe (:meth:`Processor.LineClassification`): the duration from
3929 the request of the first line to the last line received. Its speed is set by the program writing the lines, e.g.
3930 minutes to hours for a Vivado run.
3931 * Lines read from a file (:meth:`Document.Parse`): the duration to read the file and to classify all its lines. Its
3932 speed is set by this package.
3933
3934 :returns: The processing duration, or ``None`` if processing hasn't finished.
3935 """
3936 return self._processingDuration
3937
3938 def __contains__(self, key: Type[Command]) -> bool:
3939 """
3940 Returns True, if log outputs where found for the given command.
3941
3942 :param key: Vivado command (class).
3943 :returns: True, if the Vivado command's outputs were found in log outputs.
3944 """
3945 if not issubclass(key, Command):
3946 ex = TypeError("Parameter 'key' is not a Command.")
3947 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
3948 raise ex
3949
3950 return key in self._commands
3951
3952 def __getitem__(self, key: Type[Command]) -> Command:
3953 """
3954 Access Vivado command specific log outputs and parsed data by the command.
3955
3956 :param key: Vivado command (class) to access.
3957 :returns: A Vivado command instance with parsed log messages and extracted data.
3958 """
3959 if not issubclass(key, Command):
3960 ex = TypeError("Parameter 'key' is not a Command.")
3961 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
3962 raise ex
3963
3964 try:
3965 return self._commands[key]
3966 except KeyError as ex:
3967 raise CommandNotPresentException(F"Command '{key._TCL_COMMAND}' not present in '{self._logfile}'.") from ex
3968
3969 @readonly
3970 def IsIncompleteLog(self) -> bool:
3971 """
3972 Read-only property returning true if the processed Vivado log output is incomplete.
3973
3974 A log can be incomplete, because:
3975
3976 * Vivado disabled messages, because too many messages of the same kind appeared. Usually, a message type is disabled
3977 after 100 messages of that type. This is indicated by message ``[Common 17-14]``.
3978
3979 :returns: True, if messages where silenced by Vivado.
3980
3981 .. note::
3982
3983 .. code-block::
3984
3985 INFO: [Common 17-14] Message 'Synth 8-3321' appears 100 times and further instances of the messages will be
3986 disabled. Use the Tcl command set_msg_config to change the current settings.
3987 """
3988 return 17 in self._messagesByID and 14 in self._messagesByID[17]
3989
3990 def CommandFinder(self, line: Nullable[VivadoLine] = None) -> Generator[VivadoLine, VivadoLine, None]:
3991 tclProcedures = {"source"}
3992
3993 self._preamble = Preamble(self)
3994 self._postamble = Postamble(self)
3995
3996 # wait for first line
3997 if line is None:
3998 line = yield
3999
4000 # process preamble
4001 line = yield from self._preamble.Generator(line)
4002
4003 while True:
4004 while True:
4005 if line._kind is LineKind.Empty:
4006 line = yield line
4007 continue
4008 elif isinstance(line, VivadoInfoMessage):
4009 if line.ToolID == 17 and line.MessageKindID == 206:
4010 lastLine = yield from self._postamble.Generator(line)
4011 return lastLine
4012 elif isinstance(line, VivadoTclCommand):
4013 if line._tclCommand == Synth_Design._TCL_COMMAND:
4014 self._commands[Synth_Design] = (cmd := Synth_Design(self))
4015 line = yield next(gen := cmd.SectionDetector(line))
4016 break
4017 elif line._tclCommand == Link_Design._TCL_COMMAND:
4018 self._commands[Link_Design] = (cmd := Link_Design(self))
4019 line = yield next(gen := cmd.SectionDetector(line))
4020 break
4021 elif line._tclCommand == Opt_Design._TCL_COMMAND:
4022 self._commands[Opt_Design] = (cmd := Opt_Design(self))
4023 line = yield next(gen := cmd.SectionDetector(line))
4024 break
4025 elif line._tclCommand == Place_Design._TCL_COMMAND:
4026 self._commands[Place_Design] = (cmd := Place_Design(self))
4027 line = yield next(gen := cmd.SectionDetector(line))
4028 break
4029 elif line._tclCommand == PhyOpt_Design._TCL_COMMAND:
4030 self._commands[PhyOpt_Design] = (cmd := PhyOpt_Design(self))
4031 line = yield next(gen := cmd.SectionDetector(line))
4032 break
4033 elif line._tclCommand == Route_Design._TCL_COMMAND:
4034 self._commands[Route_Design] = (cmd := Route_Design(self))
4035 line = yield next(gen := cmd.SectionDetector(line))
4036 break
4037 elif line._tclCommand == Write_Bitstream._TCL_COMMAND:
4038 self._commands[Write_Bitstream] = (cmd := Write_Bitstream(self))
4039 line = yield next(gen := cmd.SectionDetector(line))
4040 break
4041 elif line._tclCommand == Open_Checkpoint._TCL_COMMAND:
4042 self._commands[Open_Checkpoint] = (cmd := Open_Checkpoint(self))
4043 line = yield next(gen := cmd.SectionDetector(line))
4044 break
4045 elif line._tclCommand == Report_DRC._TCL_COMMAND:
4046 self._commands[Report_DRC] = (cmd := Report_DRC(self))
4047 line = yield next(gen := cmd.SectionDetector(line))
4048 break
4049 elif line._tclCommand == Report_Methodology._TCL_COMMAND:
4050 self._commands[Report_Methodology] = (cmd := Report_Methodology(self))
4051 line = yield next(gen := cmd.SectionDetector(line))
4052 break
4053 elif line._tclCommand == Report_Power._TCL_COMMAND:
4054 self._commands[Report_Power] = (cmd := Report_Power(self))
4055 line = yield next(gen := cmd.SectionDetector(line))
4056 break
4057 elif isinstance(line, DateTimeLine):
4058 if (match := Launch._LAUNCHED.match(line._message)) is not None:
4059 launchName = match["launchName"]
4060 self._nestedLaunches.append(launch := Launch(launchName, parent=self))
4061
4062 line = yield next(gen := launch.Parser(line))
4063 break
4064 else:
4065 pass
4066
4067 firstWord = line.Partition(" ")[0]
4068 if firstWord in tclProcedures:
4069 line = TclCommand.FromLine(line)
4070
4071 line = yield line
4072
4073 # end = f"{cmd._TCL_COMMAND} completed successfully"
4074
4075 while True:
4076 # if line.StartsWith(end):
4077 # # line._kind |= LineKind.Success
4078 # lastLine = gen.send(line)
4079 # if LineKind.Last in line._kind:
4080 # line._kind ^= LineKind.Last
4081 # line = yield lastLine
4082 # break
4083
4084 try:
4085 line = yield gen.send(line)
4086 except StopIteration as ex:
4087 line = ex.value
4088 break
4089
4090
4091@export
4092class Processor(VivadoProcessor):
4093 _nestedLaunches: List[Launch] #: Nested Vivado launches (e.g. ``synth_1``/``impl_1``)
4094
4095 def __init__(self) -> None:
4096 """
4097 Initializes a Vivado log output processor (toplevel processor).
4098 """
4099 super().__init__()
4100
4101 self._nestedLaunches = []
4102
4103 @readonly
4104 def HasNestedLaunches(self) -> bool:
4105 """
4106 Read-only property returnning true, if this processor encountered nested launches.
4107
4108 :returns: True, if nested launches were found.
4109
4110 .. seealso::
4111
4112 :data:`NestedLaunches`
4113 Access the list of nested launches.
4114 """
4115 return len(self._nestedLaunches) > 0
4116
4117 @readonly
4118 def NestedLaunches(self) -> List[Launch]:
4119 """
4120 Read-only property to access nested launches.
4121
4122 .. hint::
4123
4124 A nested launch gets created when the outer Vivado instance starts another nested Vivado instance and waits on
4125 its completion. Examples are typically ``synth_1`` and ``impl_1``.
4126
4127 :returns: The list of nested launches.
4128
4129 .. seealso::
4130
4131 :data:`HasNestedLaunches`
4132 Check if nested launches have been found.
4133 """
4134 return self._nestedLaunches
4135
4136 def LineClassification(self, inputStream: Iterator[Tuple[datetime, str]]) -> Generator[VivadoLine, None, None]:
4137 """
4138 Classify the lines of a Vivado log output, one line per iteration.
4139
4140 The processor collects the classified lines, messages and commands. It measures the duration from the request of the
4141 first line to the end of the input (:data:`ProcessingDuration`), stored when the input ends.
4142
4143 :param inputStream: Iterator of ``(timestamp, line)`` tuples, e.g. read from a log file or STDIN.
4144 :returns: A generator of the classified lines.
4145 :raises ClassificationException: If a line starting like a message can't be classified as that message.
4146 """
4147 # Instantiate and initialize CommandFinder
4148 next(cmdFinder := self.CommandFinder())
4149
4150 lastLine = None
4151 lineNumber = 0
4152 _errorMessage = "Unknown processing error"
4153
4154 with Stopwatch() as stopwatch:
4155 try:
4156 while (tup := next(inputStream)) is not None:
4157 timestamp, rawMessageLine = tup
4158 lineNumber += 1
4159 rawMessageLine = rawMessageLine.rstrip()
4160 errorMessage = _errorMessage
4161
4162 if len(rawMessageLine) == 0:
4163 line = VivadoLine(lineNumber, LineKind.Empty, LineAction.Default, rawMessageLine, previousLine=lastLine)
4164 elif rawMessageLine.startswith("INFO"):
4165 if (line := VivadoInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
4166 if (line := VivadoDRCInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
4167 if (line := VivadoIrregularInfoMessage.Parse(
4168 lineNumber, rawMessageLine, previousLine=lastLine
4169 )) is None:
4170 line = VivadoStuntedInfoMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
4171
4172 errorMessage = "Line starting with 'INFO' was not a VivadoInfoMessage."
4173 elif rawMessageLine.startswith("WARNING"):
4174 if (line := VivadoWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
4175 if (line := VivadoDRCWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
4176 if (line := VivadoXPMWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)) is None:
4177 line = VivadoStuntedWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
4178
4179 errorMessage = "Line starting with 'WARNING' was not a VivadoWarningMessage."
4180 elif rawMessageLine.startswith("CRITICAL WARNING"):
4181 line = VivadoCriticalWarningMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
4182
4183 errorMessage = "Line starting with 'CRITICAL WARNING' was not a VivadoCriticalWarningMessage."
4184 elif rawMessageLine.startswith("ERROR"):
4185 line = VivadoErrorMessage.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
4186
4187 errorMessage = "Line starting with 'ERROR' was not a VivadoErrorMessage."
4188 elif rawMessageLine.startswith("Command: "):
4189 line = VivadoTclCommand.Parse(lineNumber, rawMessageLine, previousLine=lastLine)
4190
4191 errorMessage = "Line starting with 'Command:' was not a VivadoTclCommand."
4192 elif (match := DateTimeLine._PREFIX.match(rawMessageLine)) is not None:
4193 dateTime = datetime.strptime(match["datetime"], "%a %b %d %H:%M:%S %Y")
4194 line = DateTimeLine(lineNumber, LineKind.DateTimeLine, LineAction.Default, dateTime, match["message"], previousLine=lastLine)
4195 else:
4196 line = VivadoLine(
4197 lineNumber, LineKind.Unprocessed, LineAction.Default, rawMessageLine, previousLine=lastLine
4198 )
4199
4200 if line.StartsWith("Resolution:") and isinstance(lastLine, VivadoMessage):
4201 line._kind = LineKind.Verbose
4202
4203 if line is None:
4204 # TODO: what to do with this line? attache to exception?
4205 line = VivadoLine(lineNumber, LineKind.ProcessorError, LineAction.Default, rawMessageLine, previousLine=lastLine)
4206
4207 raise ClassificationException(errorMessage, lineNumber, rawMessageLine)
4208
4209 if isinstance(line, VivadoMessage):
4210 self._AddMessage(line)
4211
4212 line = cmdFinder.send(line)
4213
4214 if line._kind is LineKind.ProcessorError:
4215 line = ClassificationException(errorMessage, lineNumber, rawMessageLine)
4216
4217 # TODO: find a better solution/location to assign the timestamp.
4218 line._timestamp = timestamp
4219
4220 self._lines.append(line)
4221
4222 lastLine = line
4223 yield line
4224
4225 except StopIteration:
4226 pass
4227
4228 self._processingDuration = stopwatch.Duration
4229
4230
4231@export
4232class Document(Processor):
4233 """
4234 A Vivado log output processor for a log file.
4235
4236 This processor represents a Vivado log file (e.g. ``*.vds`` or ``*.vdi``). It processees its content line-by-line
4237 while classifying each line as a message. The processing duration is available via :data:`ProcessingDuration`.
4238 """
4239 _logfile: Path #: Path to the processed logfile.
4240
4241 # FIXME: parse=True parameter
4242 def __init__(self, logfile: Path) -> None:
4243 """
4244 Initializes a log file.
4245
4246 :param logfile: Path to the log file.
4247 """
4248 super().__init__()
4249
4250 # FIXME: check if path
4251 self._logfile = logfile
4252
4253 @readonly
4254 def Logfile(self) -> Path:
4255 """
4256 Read-only property to access the document's path.
4257
4258 :returns: Path to the log file.
4259 """
4260 return self._logfile
4261
4262 def Parse(self) -> None:
4263 """
4264 Read the log file and classify all its lines.
4265
4266 The processing duration (:data:`ProcessingDuration`) is the duration to read the file and to classify its lines.
4267 """
4268 with Stopwatch() as stopwatch:
4269 timestamp = datetime.fromtimestamp(self._logfile.stat().st_mtime)
4270 with self._logfile.open("r", encoding="utf-8") as file:
4271 for line in self.LineClassification(timestampIterator(file, timestamp)):
4272 pass
4273
4274 self._processingDuration = stopwatch.Duration
4275
4276
4277@export
4278class Launch(Parser, VivadoProcessor):
4279 _LAUNCHED: ClassVar[Pattern] = re_compile(r"^Launched (?P<launchName>.+?)\.\.\.")
4280 _LOGFILE: ClassVar[Pattern] = re_compile(r"^Run output will be captured here: (?P<logfile>.+)")
4281 _WAITING: ClassVar[Pattern] = re_compile(r"^Waiting for (?P<launchName>.+?) to finish(?: \(timeout in (?P<timeout>\d+) minutes\))?\.\.\.")
4282 _RUNNING: ClassVar[Pattern] = re_compile(r"^\*\*\*+\s+Running vivado")
4283 _WITH_ARGS: ClassVar[Pattern] = re_compile(r"^\s+with args (?P<arguments>.+)")
4284 _FINISHED: ClassVar[Pattern] = re_compile(r"^(?P<launchName>.+?) finished")
4285 _TIME: ClassVar[str] = "wait_on_runs: Time (s):"
4286
4287 _name: str #: Name of the launch.
4288 _logfile: Nullable[Path] #: Logfile used by nested Vivado instance.
4289 _vivadoArguments: Nullable[List[str]] #: Launch parameters passed to nested Vivado instance.
4290 _timeout: int #: Timeout in minutes.
4291 _startDateTime: datetime #: Date and time when the nested Vivado instance was started.
4292 _finishDateTime: datetime #: Date and time when the nested Vivado instance finished.
4293
4294 def __init__(self, name: str, parent: VivadoProcessor) -> None:
4295 super().__init__(parent)
4296 VivadoProcessor.__init__(self)
4297
4298 self._name = name
4299 self._logfile = None
4300 self._vivadoArguments = None
4301 self._timeout = 0
4302 self._startDateTime = None
4303 self._finishDateTime = None
4304
4305 @readonly
4306 def Name(self) -> str:
4307 return self._name
4308
4309 @readonly
4310 def Logfile(self) -> Path:
4311 return self._logfile
4312
4313 @readonly
4314 def VivadoArguments(self) -> List[str]:
4315 return self._vivadoArguments
4316
4317 @readonly
4318 def Timeout(self) -> int:
4319 return self._timeout
4320
4321 @readonly
4322 def LaunchDateTime(self) -> datetime:
4323 return self._startDateTime
4324
4325 @readonly
4326 def FinishDateTime(self) -> datetime:
4327 return self._finishDateTime
4328
4329 @readonly
4330 def Duration(self) -> _Duration:
4331 """
4332 Read-only property to return the duration of the observed nested Vivado instance.
4333
4334 :returns: The observed nested Vivado instance's execution duration.
4335 """
4336 return _Duration.FromTimedelta(self.FinishDateTime - self.LaunchDateTime)
4337
4338 def Parser(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
4339 if isinstance(line, DateTimeLine):
4340 line._kind = LineKind.LaunchStart
4341 self._startDateTime = line._dateTime
4342 else:
4343 raise TypeError("Expected type DateTimeLine for first line.")
4344
4345 if self._LAUNCHED.match(line._message) is None:
4346 raise ValueError(f"Expected a 'Launched xxx...' line, but git '{line._message}'.")
4347
4348 while True:
4349 line = yield line
4350
4351 if isinstance(line, DateTimeLine):
4352 if (match := self._WAITING.match(line._message)) is not None:
4353 line._kind = LineKind.LaunchStart
4354 if (launchName := match["launchName"]) != self._name:
4355 WarningCollector.Raise(ProcessorCriticalWarning(f"Detected launch name '{launchName}' doesn't match current's launch's name '{self._name}'."))
4356 return line
4357
4358 if (timeout := match["timeout"]) is not None:
4359 self._timeout = int(timeout)
4360
4361 else:
4362 pass
4363 elif (match := self._LOGFILE.match(line._message)) is not None:
4364 line._kind = LineKind.Normal
4365 self._logfile = Path(match["logfile"])
4366 elif (match := self._RUNNING.match(line._message)) is not None:
4367 line._kind = LineKind.Normal
4368 elif (match := self._WITH_ARGS.match(line._message)) is not None:
4369 line._kind = LineKind.LaunchArguments
4370 self._vivadoArguments = match["arguments"].split(" ")
4371 break
4372
4373 # Consume 3 empty lines
4374 line = yield line
4375 line = yield line
4376 line = yield line
4377
4378 try:
4379 processedLine = next(gen := self.CommandFinder(line)) # create + prime
4380 while True:
4381 line = yield processedLine
4382 if isinstance(line, VivadoMessage):
4383 self._AddMessage(line)
4384
4385 processedLine = gen.send(line)
4386 except StopIteration as ex:
4387 line = ex.value
4388
4389 # Check for 'finished' line
4390 if isinstance(line, DateTimeLine):
4391 if (match := self._FINISHED.match(line._message)) is not None:
4392 line._kind = LineKind.LaunchFinished
4393 self._finishDateTime = line._dateTime
4394
4395 # TODO: check launchName
4396 else:
4397 pass # FIXME: raise error
4398 else:
4399 pass # FIXME: raise error
4400
4401 line = yield line
4402
4403 # Check for 'wait_on_runs' line
4404 if line.StartsWith(self._TIME):
4405 line._kind = LineKind.LaunchTime
4406
4407 line = yield line
4408
4409 if isinstance(line, VivadoMessage):
4410 if not (line._toolID == 17 and line._messageKindID == 206): # Exiting Vivado
4411 self._AddMessage(line)
4412
4413 line = yield line
4414
4415 return line
4416
4417 def __str__(self) -> str:
4418 return f"Launch: {self._name} (timeout: {self._timeout} minutes)"