Coverage for pyEDAA/OutputFilter/Xilinx/Common.py: 67%
417 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-05 22:59 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-05 22:59 +0000
1# ==================================================================================================================== #
2# _____ ____ _ _ ___ _ _ _____ _ _ _ #
3# _ __ _ _| ____| _ \ / \ / \ / _ \ _ _| |_ _ __ _ _| |_| ___(_) | |_ ___ _ __ #
4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | | __| '_ \| | | | __| |_ | | | __/ _ \ '__| #
5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| | |_| | |_| |_) | |_| | |_| _| | | | || __/ | #
6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/ \__,_|\__| .__/ \__,_|\__|_| |_|_|\__\___|_| #
7# |_| |___/ |_| #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2025-2026 Electronic Design Automation Abstraction (EDA²) #
15# #
16# Licensed under the Apache License, Version 2.0 (the "License"); #
17# you may not use this file except in compliance with the License. #
18# You may obtain a copy of the License at #
19# #
20# http://www.apache.org/licenses/LICENSE-2.0 #
21# #
22# Unless required by applicable law or agreed to in writing, software #
23# distributed under the License is distributed on an "AS IS" BASIS, #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25# See the License for the specific language governing permissions and #
26# limitations under the License. #
27# #
28# SPDX-License-Identifier: Apache-2.0 #
29# ==================================================================================================================== #
30#
31"""Basic classes for outputs from AMD/Xilinx Vivado."""
32from enum import Flag
33from pathlib import Path
34from re import Pattern, compile as re_compile
35from typing import Optional as Nullable, Self, ClassVar, Tuple, Union, Any, Iterator, Generator, Callable
37from pyTooling.Decorators import export, readonly
38from pyTooling.MetaClasses import ExtendedType
39from pyTooling.Common import getFullyQualifiedName
42@export
43class LineKind(Flag):
44 """
45 Classification of a log message line.
46 """
47 Unprocessed = 0
48 ProcessorError = 2** 0
49 Empty = 2** 1
50 Delimiter = 2** 2
52 Success = 2** 3
53 Failed = 2** 4
55 Verbose = 2**10
56 Normal = 2**11
57 Info = 2**12
58 Warning = 2**13
59 CriticalWarning = 2**14
60 Error = 2**15
61 Fatal = 2**16
63 Start = 2**20
64 End = 2**21
65 Header = 2**22
66 Content = 2**23
67 Time = 2**24
68 Footer = 2**25
70 Last = 2**29
72 Message = 2**30
73 InfoMessage = Message | Info
74 WarningMessage = Message | Warning
75 CriticalWarningMessage = Message | CriticalWarning
76 ErrorMessage = Message | Error
78 Task = 2**31
79 TaskStart = Task | Start
80 TaskEnd = Task | End
81 TaskTime = Task | Time
83 Phase = 2**32
84 PhaseDelimiter = Phase | Delimiter
85 PhaseStart = Phase | Start
86 PhaseEnd = Phase | End
87 PhaseTime = Phase | Time
88 PhaseFinal = Phase | Footer
90 SubPhase = 2**33
91 SubPhaseStart = SubPhase | Start
92 SubPhaseEnd = SubPhase | End
93 SubPhaseTime = SubPhase | Time
95 SubSubPhase = 2**34
96 SubSubPhaseStart = SubSubPhase | Start
97 SubSubPhaseEnd = SubSubPhase | End
98 SubSubPhaseTime = SubSubPhase | Time
100 SubSubSubPhase = 2**35
101 SubSubSubPhaseStart = SubSubSubPhase | Start
102 SubSubSubPhaseEnd = SubSubSubPhase | End
103 SubSubSubPhaseTime = SubSubSubPhase | Time
105 NestedTask = 2**36
106 NestedTaskStart = NestedTask | Start
107 NestedTaskEnd = NestedTask | End
109 NestedPhase = 2**37
110 NestedPhaseStart = NestedPhase | Start
111 NestedPhaseEnd = NestedPhase | End
113 Section = 2**38
114 SectionDelimiter = Section | Delimiter
115 SectionStart = Section | Start
116 SectionEnd = Section | End
118 SubSection = 2**39
119 SubSectionDelimiter = SubSection | Delimiter
120 SubSectionStart = SubSection | Start
121 SubSectionEnd = SubSection | End
123 Paragraph = 2**40
124 ParagraphHeadline = Paragraph | Header
126 Hierarchy = 2**41
127 HierarchyStart = Hierarchy | Start
128 HierarchyEnd = Hierarchy | End
130 XDC = 2**42
131 XDCStart = XDC | Start
132 XDCEnd = XDC | End
134 Table = 2**43
135 TableFrame = Table | Delimiter
136 TableHeader = Table | Header
137 TableRow = Table | Content
138 TableFooter = Table | Footer
140 TclCommand = 2**44
141 GenericTclCommand = TclCommand | 2**0
142 VivadoTclCommand = TclCommand | 2**1
145@export
146class Line(metaclass=ExtendedType, slots=True):
147 """
148 This class represents any line in a log file.
149 """
150 _lineNumber: int
151 _kind: LineKind
152 _message: str
153 _previousLine: Nullable["Line"]
154 _nextLine: Nullable["Line"]
156 def __init__(self, lineNumber: int, kind: LineKind, message: str, previousLine: Nullable["Line"] = None) -> None:
157 self._lineNumber = lineNumber
158 self._kind = kind
159 self._message = message
160 self._previousLine = previousLine
161 self._nextLine = None
163 if previousLine is not None:
164 previousLine._nextLine = self
166 @readonly
167 def LineNumber(self) -> int:
168 return self._lineNumber
170 @readonly
171 def Kind(self) -> LineKind:
172 return self._kind
174 @readonly
175 def Message(self) -> str:
176 return self._message
178 @property
179 def PreviousLine(self) -> "Line":
180 return self._previousLine
182 @PreviousLine.setter
183 def PreviousLine(self, line: "Line") -> None:
184 self._previousLine = line
185 if line is not None:
186 line._nextLine = self
188 @readonly
189 def NextLine(self) -> "Line":
190 return self._nextLine
192 def Partition(self, separator: str) -> Tuple[str, str, str]:
193 return self._message.partition(separator)
195 def StartsWith(self, prefix: Union[str, Tuple[str, ...]]):
196 return self._message.startswith(prefix)
198 def __iter__(self) -> Generator["Line", None, None]:
199 """
200 Forward iteration from the successor of this node.
202 .. hint::
204 Delegates to :meth:`GetIterator` with all defaults.
206 :returns: A generator yielding each successive :class:`Line` node.
207 """
208 return self.GetIterator()
210 def GetIterator(
211 self,
212 stopPredicate: Nullable[Callable[["Line"], bool]] = None,
213 *,
214 reverse: bool = False,
215 inclusive: bool = True,
216 maxLines: Nullable[int] = None,
217 ) -> Generator["Line", None, None]:
218 """
219 Iterate consecutive lines starting from next line towards the end of the log.
221 If the order is reversed, iterate starting at the previous line towards the beginning of the log. The iteration ends
222 either at the bounds of the log, by specifying a stop predicate or a maximum number of lines to return. When stopped
223 this line is usually included in the iteration, but can be excluded.
225 :param stopPredicate: Optional, a callable receiving a :class:`Line` and returning ``True`` when iteration should
226 stop at that line.
227 :param reverse: Optional, reverse the iteration from previous line to the beginning of the log.
228 :param inclusive: Optional, when ``True`` the line where ``stopPredicate`` or ``maxLines`` triggers, is
229 included in the iteration, otherwise it's excluded.
230 :param maxLines: Optional, maximum number of lines to yield.
231 :returns: A generator yielding :class:`Line` in the requested direction, stopping at the log boundary,
232 the predicate match, or the line limit — whichever comes first.
233 :raises TypeError: When ``stopPredicate`` is not callable.
234 :raises ValueError: When ``maxLines`` is not a positive integer.
235 """
236 if stopPredicate is not None and not callable(stopPredicate):
237 ex = TypeError("Parameter 'stopPredicate' is not a callable.")
238 ex.add_note(f"Got type '{getFullyQualifiedName(stopPredicate)}'.")
239 raise ex
240 if not isinstance(reverse, bool):
241 ex = TypeError("Parameter 'reverse' is not a boolean.")
242 ex.add_note(f"Got type '{getFullyQualifiedName(reverse)}'.")
243 raise ex
244 if not isinstance(inclusive, bool):
245 ex = TypeError("Parameter 'inclusive' is not a boolean.")
246 ex.add_note(f"Got type '{getFullyQualifiedName(inclusive)}'.")
247 raise ex
248 if maxLines is not None:
249 if not isinstance(maxLines, int):
250 ex = TypeError("Parameter 'maxLines' is not a integer.")
251 ex.add_note(f"Got type '{getFullyQualifiedName(maxLines)}'.")
252 raise ex
253 elif maxLines <= 0:
254 ex = ValueError("Parameter 'maxLines' must be a positive integer.")
255 ex.add_note(f"Got {maxLines!r}.")
256 raise ex
258 current = self._previousLine if reverse else self._nextLine
260 if maxLines is None:
261 if stopPredicate is None:
262 if reverse:
263 while current is not None:
264 yield current
265 current = current._previousLine
266 else:
267 while current is not None:
268 yield current
269 current = current._nextLine
270 else:
271 if reverse:
272 while current is not None:
273 if stopPredicate(current):
274 if inclusive:
275 yield current
276 return
277 yield current
278 current = current._previousLine
279 else:
280 while current is not None:
281 if stopPredicate(current):
282 if inclusive:
283 yield current
284 return
285 yield current
286 current = current._nextLine
288 elif stopPredicate is None:
289 remaining = maxLines
290 if reverse:
291 while current is not None and remaining > 0:
292 yield current
293 current = current._previousLine
294 remaining -= 1
295 else:
296 while current is not None and remaining > 0:
297 yield current
298 current = current._nextLine
299 remaining -= 1
301 else:
302 remaining = maxLines
303 if reverse:
304 while current is not None and remaining > 0:
305 if stopPredicate(current):
306 if inclusive:
307 yield current
308 return
309 yield current
310 current = current._previousLine
311 remaining -= 1
312 else:
313 while current is not None and remaining > 0:
314 if stopPredicate(current):
315 if inclusive:
316 yield current
317 return
318 yield current
319 current = current._nextLine
320 remaining -= 1
322 def __getitem__(self, item: slice) -> str:
323 return self._message[item]
325 def __eq__(self, other: Any):
326 return self._message == other
328 def __ne__(self, other: Any):
329 return self._message != other
331 def __str__(self) -> str:
332 return self._message
334 def __repr__(self) -> str:
335 return f"{self._lineNumber}: {self._message}"
338@export
339class InfoMessage(metaclass=ExtendedType, mixin=True):
340 pass
343@export
344class WarningMessage(metaclass=ExtendedType, mixin=True):
345 pass
348@export
349class CriticalWarningMessage(metaclass=ExtendedType, mixin=True):
350 pass
353@export
354class ErrorMessage(metaclass=ExtendedType, mixin=True):
355 pass
358@export
359class VivadoMessage(Line):
360 """
361 This class represents an AMD/Xilinx Vivado message.
363 The usual message format is:
365 .. code-block:: text
367 INFO: [Synth 8-7079] Multithreading enabled for synth_design using a maximum of 2 processes.
368 WARNING: [Synth 8-3332] Sequential element (gen[0].Sync/FF2) is unused and will be removed from module sync_Bits_Xilinx.
370 The following message severities are defined:
372 * ``INFO``
373 * ``WARNING``
374 * ``CRITICAL WARNING``
375 * ``ERROR``
377 .. seealso::
379 :class:`VivadoInfoMessage`
380 Representing a Vivado info message.
382 :class:`VivadoWarningMessage`
383 Representing a Vivado warning message.
385 :class:`VivadoCriticalWarningMessage`
386 Representing a Vivado critical warning message.
388 :class:`VivadoErrorMessage`
389 Representing a Vivado error message.
390 """
391 # _MESSAGE_KIND: ClassVar[str]
392 # _REGEXP: ClassVar[Pattern]
394 _toolName: Nullable[str]
395 _toolID: Nullable[int]
396 _messageKindID: Nullable[int]
398 def __init__(
399 self,
400 lineNumber: int,
401 kind: LineKind,
402 message: str,
403 toolName: Nullable[str] = None,
404 toolID: Nullable[int] = None,
405 messageKindID: Nullable[int] = None,
406 previousLine: Nullable[Line] = None
407 ) -> None:
408 super().__init__(lineNumber, kind, message, previousLine)
409 self._toolName = toolName
410 self._toolID = toolID
411 self._messageKindID = messageKindID
413 @readonly
414 def ToolName(self) -> Nullable[str]:
415 return self._toolName
417 @readonly
418 def ToolID(self) -> Nullable[int]:
419 return self._toolID
421 @readonly
422 def MessageKindID(self) -> Nullable[int]:
423 return self._messageKindID
425 @classmethod
426 def Parse(cls, lineNumber: int, kind: LineKind, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
427 if (match := cls._REGEXP.match(rawMessage)) is not None:
428 return cls(lineNumber, kind, match[4], match[1], int(match[2]), int(match[3]), previousLine)
430 return None
432 def __str__(self) -> str:
433 return f"{self._MESSAGE_KIND}: [{self._toolName} {self._toolID}-{self._messageKindID}] {self._message}"
436@export
437class VivadoInfoMessage(VivadoMessage, InfoMessage):
438 """
439 This class represents an AMD/Xilinx Vivado info message.
441 .. rubric:: Example
443 .. code-block::
445 INFO: [Common 17-83] 66-Releasing license: Synthesis
446 """
448 _MESSAGE_KIND: ClassVar[str] = "INFO"
449 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+) (\d+)-(\d+)\] (.*)""")
451 @classmethod
452 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
453 return super().Parse(lineNumber, LineKind.InfoMessage, rawMessage, previousLine)
456@export
457class VivadoDRCInfoMessage(VivadoMessage, InfoMessage):
458 """
459 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) info message.
461 .. rubric:: Example
463 .. code-block::
465 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'.
466 """
468 _MESSAGE_KIND: ClassVar[str] = "INFO"
469 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[DRC (\w+)-(\d+)\] (.*)""")
471 _drcRuleName: str
473 def __init__(
474 self,
475 lineNumber: int,
476 kind: LineKind,
477 drcRuleName: str,
478 message: str,
479 toolName: Nullable[str] = None,
480 toolID: Nullable[int] = None,
481 messageKindID: Nullable[int] = None,
482 previousLine: Nullable[Line] = None
483 ) -> None:
484 super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine)
486 self._drcRuleName = drcRuleName
488 @readonly
489 def DRCRuleName(self) -> str:
490 return self._drcRuleName
492 @classmethod
493 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
494 if (match := cls._REGEXP.match(rawMessage)) is not None:
495 return cls(lineNumber, LineKind.WarningMessage, match[1], match[3], toolName="DRC", toolID=None,
496 messageKindID=int(match[2]), previousLine=previousLine)
498 return None
500 def __str__(self) -> str:
501 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
504@export
505class VivadoIrregularInfoMessage(VivadoMessage, InfoMessage):
506 """
507 This class represents an irregular AMD/Xilinx Vivado info message.
509 .. rubric:: Example
511 .. code-block::
513 INFO: [runtcl-4] Executing : report_io -file system_top_io_placed.rpt
514 """
516 _MESSAGE_KIND: ClassVar[str] = "INFO"
517 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+)-(\d+)\] (.*)""")
519 @classmethod
520 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
521 if (match := cls._REGEXP.match(rawMessage)) is not None:
522 return cls(lineNumber, LineKind.InfoMessage, match[3], toolName=match[1], messageKindID=int(match[2]), previousLine=previousLine)
524 return None
526 def __str__(self) -> str:
527 return f"{self._MESSAGE_KIND}: [{self._toolName}-{self._messageKindID}] {self._message}"
530@export
531class VivadoStuntedInfoMessage(VivadoMessage, InfoMessage):
532 """
533 This class represents a stunted AMD/Xilinx Vivado info message.
535 .. rubric:: Example
537 .. code-block::
539 INFO: Helper process launched with PID 29056
540 """
542 _MESSAGE_KIND: ClassVar[str] = "INFO"
543 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: ([^\[].*)""")
545 @classmethod
546 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
547 if (match := cls._REGEXP.match(rawMessage)) is not None: 547 ↛ 550line 547 didn't jump to line 550 because the condition on line 547 was always true
548 return cls(lineNumber, LineKind.InfoMessage, match[1], previousLine=previousLine)
550 return None
552 def __str__(self) -> str:
553 return f"{self._MESSAGE_KIND}: {self._message}"
556@export
557class VivadoWarningMessage(VivadoMessage, WarningMessage):
558 """
559 This class represents an AMD/Xilinx Vivado warning message.
561 .. rubric:: Example
563 .. code-block::
565 WARNING: [Synth 8-7080] Parallel synthesis criteria is not met
566 """
568 _MESSAGE_KIND: ClassVar[str] = "WARNING"
569 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(\w+(?: \w+)*?) (\d+)-(\d+)\] (.*)""")
571 @classmethod
572 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
573 return super().Parse(lineNumber, LineKind.WarningMessage, rawMessage, previousLine=previousLine)
576@export
577class VivadoDRCWarningMessage(VivadoMessage, WarningMessage):
578 """
579 This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) warning message.
581 .. rubric:: Example
583 .. code-block::
585 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.
586 """
588 _MESSAGE_KIND: ClassVar[str] = "WARNING"
589 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[DRC (\w+)-(\d+)\] (.*)""")
591 _drcRuleName: str
593 def __init__(
594 self,
595 lineNumber: int,
596 kind: LineKind,
597 drcRuleName: str,
598 message: str,
599 toolName: Nullable[str] = None,
600 toolID: Nullable[int] = None,
601 messageKindID: Nullable[int] = None,
602 previousLine: Nullable[Line] = None
603 ) -> None:
604 super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine)
606 self._drcRuleName = drcRuleName
608 @readonly
609 def DRCRuleName(self) -> str:
610 return self._drcRuleName
612 @classmethod
613 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
614 if (match := cls._REGEXP.match(rawMessage)) is not None: 614 ↛ 615line 614 didn't jump to line 615 because the condition on line 614 was never true
615 return cls(lineNumber, LineKind.WarningMessage, match[1], match[3], toolName="DRC", toolID=None, messageKindID=int(match[2]), previousLine=previousLine)
617 return None
619 def __str__(self) -> str:
620 return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
623@export
624class VivadoXPMWarningMessage(VivadoMessage, WarningMessage):
625 """
626 This class represents an AMD/Xilinx Vivado XPM warning message.
628 .. rubric:: Example
630 .. code-block::
632 WARNING: [XPM_CDC_GRAY: TCL-1000] The source and destination clocks are the same.
633 """
635 _MESSAGE_KIND: ClassVar[str] = "WARNING"
636 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(XPM_\w+): (\w+)-(\d+)\] (.*)""")
638 _xpmName: str
640 def __init__(
641 self,
642 lineNumber: int,
643 kind: LineKind,
644 xpmName: str,
645 message: str,
646 toolName: Nullable[str] = None,
647 toolID: Nullable[int] = None,
648 messageKindID: Nullable[int] = None,
649 previousLine: Nullable[Line] = None
650 ) -> None:
651 super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine)
653 self._xpmName = xpmName
655 @readonly
656 def XPMName(self) -> str:
657 return self._xpmName
659 @classmethod
660 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
661 if (match := cls._REGEXP.match(rawMessage)) is not None: 661 ↛ 664line 661 didn't jump to line 664 because the condition on line 661 was always true
662 return cls(lineNumber, LineKind.WarningMessage, match[1], match[4], toolName=match[2], toolID=None, messageKindID=int(match[3]), previousLine=previousLine)
664 return None
666 def __str__(self) -> str:
667 return f"{self._MESSAGE_KIND}: [{self._xpmName}: {self._toolName}-{self._messageKindID}] {self._message}"
670@export
671class VivadoStuntedWarningMessage(VivadoMessage, WarningMessage):
672 """
673 This class represents a stunted AMD/Xilinx Vivado warning message.
675 .. rubric:: Example
677 .. code-block::
679 WARNING: set_property ASYNC_REG could not find object (constraint file /path/to/sync_Bits_Xilinx.xdc, line 5).
680 """
682 _MESSAGE_KIND: ClassVar[str] = "WARNING"
683 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: ([^\[].*)""")
685 @classmethod
686 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
687 if (match := cls._REGEXP.match(rawMessage)) is not None: 687 ↛ 690line 687 didn't jump to line 690 because the condition on line 687 was always true
688 return cls(lineNumber, LineKind.WarningMessage, match[1], previousLine=previousLine)
690 return None
692 def __str__(self) -> str:
693 return f"{self._MESSAGE_KIND}: {self._message}"
696@export
697class VivadoCriticalWarningMessage(VivadoMessage, CriticalWarningMessage):
698 """
699 This class represents an AMD/Xilinx Vivado critical warning message.
701 .. rubric:: Example
703 .. code-block::
705 CRITICAL WARNING: [Constraints 18-1056] Clock 'RefClkA_SFP_Quad' completely overrides clock 'USRCLKA_SFP[P]'.
706 """
708 _MESSAGE_KIND: ClassVar[str] = "CRITICAL WARNING"
709 _REGEXP: ClassVar[Pattern] = re_compile(r"""CRITICAL WARNING: \[(\w+) (\d+)-(\d+)\] (.*)""")
711 @classmethod
712 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
713 return super().Parse(lineNumber, LineKind.CriticalWarningMessage, rawMessage, previousLine)
716@export
717class VivadoErrorMessage(VivadoMessage, ErrorMessage):
718 """
719 This class represents an AMD/Xilinx Vivado error message.
721 .. rubric:: Example
723 .. code-block::
725 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
726 """
728 _MESSAGE_KIND: ClassVar[str] = "ERROR"
729 _REGEXP: ClassVar[Pattern] = re_compile(r"""ERROR: \[(\w+) (\d+)-(\d+)\] (.*)""")
731 @classmethod
732 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
733 return super().Parse(lineNumber, LineKind.ErrorMessage, rawMessage, previousLine)
736@export
737class VHDLReportMessage(VivadoInfoMessage):
738 _REGEXP: ClassVar[Pattern ] = re_compile(r"""RTL report: "(.*)" \[(.*):(\d+)\]""")
740 _reportMessage: str
741 _sourceFile: Path
742 _sourceLineNumber: int
744 def __init__(
745 self,
746 lineNumber: int,
747 rawMessage: str,
748 toolName: str,
749 toolID: int,
750 messageKindID: int,
751 reportMessage: str,
752 sourceFile: Path,
753 sourceLineNumber: int,
754 previousLine: Nullable[Line] = None
755 ) -> None:
756 super().__init__(lineNumber, LineKind.InfoMessage, rawMessage, toolName, toolID, messageKindID, previousLine)
758 self._reportMessage = reportMessage
759 self._sourceFile = sourceFile
760 self._sourceLineNumber = sourceLineNumber
762 @classmethod
763 def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]:
764 if (match := cls._REGEXP.match(line._message)) is not None: 764 ↛ 767line 764 didn't jump to line 767 because the condition on line 764 was always true
765 return cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, match[1], Path(match[2]), int(match[3]), previousLine=line._previousLine)
767 return None
770@export
771class VHDLAssertionMessage(VHDLReportMessage):
772 _REGEXP: ClassVar[Pattern ] = re_compile(r"""RTL assertion: "(.*)" \[(.*):(\d+)\]""")
775@export
776class TclCommand(Line):
777 """
778 Represents a TCL command found in a Vivado log output.
780 Besides the full log message (:class:`Line`), this class splits the TCL command into the command name and its
781 arguments.
782 """
783 _command: str
784 _arguments: Tuple[str, ...]
786 def __init__(
787 self,
788 lineNumber: int,
789 command: str,
790 arguments: Tuple[str, ...],
791 rawMessage: str,
792 previousLine: Nullable[Line] = None
793 ) -> None:
794 super().__init__(lineNumber, LineKind.GenericTclCommand, rawMessage, previousLine)
796 self._command = command
797 self._arguments = arguments
799 @readonly
800 def Command(self) -> str:
801 return self._command
803 @readonly
804 def Arguments(self) -> Tuple[str, ...]:
805 return self._arguments
807 @classmethod
808 def FromLine(cls, line: Line) -> Nullable[Self]:
809 args = line._message.split()
811 return cls(line._lineNumber, args[0], tuple(args[1:]), line._message, previousLine=line._previousLine)
813 def __str__(self) -> str:
814 return f"{self._command} {' '.join(self._arguments)}"
817@export
818class VivadoTclCommand(TclCommand):
819 """
820 Represents a Vivado specific TCL command.
821 """
823 _PREFIX: ClassVar[str] = "Command:"
825 @classmethod
826 def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
827 command = rawMessage[len(cls._PREFIX) + 1:]
828 args = command.split()
830 vivadoCommand = cls(lineNumber, args[0], tuple(args[1:]), rawMessage, previousLine)
831 vivadoCommand._kind = LineKind.VivadoTclCommand
832 return vivadoCommand
834 def __str__(self) -> str:
835 return f"{self._PREFIX} {self._command} {' '.join(self._arguments)}"