# ==================================================================================================================== #
# _____ ____ _ _ ___ _ _ _____ _ _ _ #
# _ __ _ _| ____| _ \ / \ / \ / _ \ _ _| |_ _ __ _ _| |_| ___(_) | |_ ___ _ __ #
# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | | __| '_ \| | | | __| |_ | | | __/ _ \ '__| #
# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| | |_| | |_| |_) | |_| | |_| _| | | | || __/ | #
# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/ \__,_|\__| .__/ \__,_|\__|_| |_|_|\__\___|_| #
# |_| |___/ |_| #
# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# #
# License: #
# ==================================================================================================================== #
# Copyright 2025-2026 Electronic Design Automation Abstraction (EDA²) #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# #
# SPDX-License-Identifier: Apache-2.0 #
# ==================================================================================================================== #
#
"""Basic classes for outputs from AMD/Xilinx Vivado."""
from enum import Flag
from pathlib import Path
from re import Pattern, compile as re_compile
from typing import Optional as Nullable, Self, ClassVar, Tuple, Union, Any, Iterator, Generator, Callable
from pyTooling.Decorators import export, readonly
from pyTooling.MetaClasses import ExtendedType
from pyTooling.Common import getFullyQualifiedName
[docs]
@export
class LineKind(Flag):
"""
Classification of a log message line.
"""
Unprocessed = 0
ProcessorError = 2** 0
Empty = 2** 1
Delimiter = 2** 2
Success = 2** 3
Failed = 2** 4
Verbose = 2**10
Normal = 2**11
Info = 2**12
Warning = 2**13
CriticalWarning = 2**14
Error = 2**15
Fatal = 2**16
Start = 2**20
End = 2**21
Header = 2**22
Content = 2**23
Time = 2**24
Footer = 2**25
Last = 2**29
Message = 2**30
InfoMessage = Message | Info
WarningMessage = Message | Warning
CriticalWarningMessage = Message | CriticalWarning
ErrorMessage = Message | Error
Task = 2**31
TaskStart = Task | Start
TaskEnd = Task | End
TaskTime = Task | Time
Phase = 2**32
PhaseDelimiter = Phase | Delimiter
PhaseStart = Phase | Start
PhaseEnd = Phase | End
PhaseTime = Phase | Time
PhaseFinal = Phase | Footer
SubPhase = 2**33
SubPhaseStart = SubPhase | Start
SubPhaseEnd = SubPhase | End
SubPhaseTime = SubPhase | Time
SubSubPhase = 2**34
SubSubPhaseStart = SubSubPhase | Start
SubSubPhaseEnd = SubSubPhase | End
SubSubPhaseTime = SubSubPhase | Time
SubSubSubPhase = 2**35
SubSubSubPhaseStart = SubSubSubPhase | Start
SubSubSubPhaseEnd = SubSubSubPhase | End
SubSubSubPhaseTime = SubSubSubPhase | Time
NestedTask = 2**36
NestedTaskStart = NestedTask | Start
NestedTaskEnd = NestedTask | End
NestedPhase = 2**37
NestedPhaseStart = NestedPhase | Start
NestedPhaseEnd = NestedPhase | End
Section = 2**38
SectionDelimiter = Section | Delimiter
SectionStart = Section | Start
SectionEnd = Section | End
SubSection = 2**39
SubSectionDelimiter = SubSection | Delimiter
SubSectionStart = SubSection | Start
SubSectionEnd = SubSection | End
Paragraph = 2**40
ParagraphHeadline = Paragraph | Header
Hierarchy = 2**41
HierarchyStart = Hierarchy | Start
HierarchyEnd = Hierarchy | End
XDC = 2**42
XDCStart = XDC | Start
XDCEnd = XDC | End
Table = 2**43
TableFrame = Table | Delimiter
TableHeader = Table | Header
TableRow = Table | Content
TableFooter = Table | Footer
TclCommand = 2**44
GenericTclCommand = TclCommand | 2**0
VivadoTclCommand = TclCommand | 2**1
[docs]
@export
class Line(metaclass=ExtendedType, slots=True):
"""
This class represents any line in a log file.
"""
_lineNumber: int
_kind: LineKind
_message: str
_previousLine: Nullable["Line"]
_nextLine: Nullable["Line"]
[docs]
def __init__(self, lineNumber: int, kind: LineKind, message: str, previousLine: Nullable["Line"] = None) -> None:
self._lineNumber = lineNumber
self._kind = kind
self._message = message
self._previousLine = previousLine
self._nextLine = None
if previousLine is not None:
previousLine._nextLine = self
@readonly
def LineNumber(self) -> int:
return self._lineNumber
@readonly
def Kind(self) -> LineKind:
return self._kind
@readonly
def Message(self) -> str:
return self._message
@property
def PreviousLine(self) -> "Line":
return self._previousLine
@PreviousLine.setter
def PreviousLine(self, line: "Line") -> None:
self._previousLine = line
if line is not None:
line._nextLine = self
@readonly
def NextLine(self) -> "Line":
return self._nextLine
def Partition(self, separator: str) -> Tuple[str, str, str]:
return self._message.partition(separator)
def StartsWith(self, prefix: Union[str, Tuple[str, ...]]):
return self._message.startswith(prefix)
[docs]
def __iter__(self) -> Generator["Line", None, None]:
"""
Forward iteration from the successor of this node.
.. hint::
Delegates to :meth:`GetIterator` with all defaults.
:returns: A generator yielding each successive :class:`Line` node.
"""
return self.GetIterator()
[docs]
def GetIterator(
self,
stopPredicate: Nullable[Callable[["Line"], bool]] = None,
*,
reverse: bool = False,
inclusive: bool = True,
maxLines: Nullable[int] = None,
) -> Generator["Line", None, None]:
"""
Iterate consecutive lines starting from next line towards the end of the log.
If the order is reversed, iterate starting at the previous line towards the beginning of the log. The iteration ends
either at the bounds of the log, by specifying a stop predicate or a maximum number of lines to return. When stopped
this line is usually included in the iteration, but can be excluded.
:param stopPredicate: Optional, a callable receiving a :class:`Line` and returning ``True`` when iteration should
stop at that line.
:param reverse: Optional, reverse the iteration from previous line to the beginning of the log.
:param inclusive: Optional, when ``True`` the line where ``stopPredicate`` or ``maxLines`` triggers, is
included in the iteration, otherwise it's excluded.
:param maxLines: Optional, maximum number of lines to yield.
:returns: A generator yielding :class:`Line` in the requested direction, stopping at the log boundary,
the predicate match, or the line limit — whichever comes first.
:raises TypeError: When ``stopPredicate`` is not callable.
:raises ValueError: When ``maxLines`` is not a positive integer.
"""
if stopPredicate is not None and not callable(stopPredicate):
ex = TypeError("Parameter 'stopPredicate' is not a callable.")
ex.add_note(f"Got type '{getFullyQualifiedName(stopPredicate)}'.")
raise ex
if not isinstance(reverse, bool):
ex = TypeError("Parameter 'reverse' is not a boolean.")
ex.add_note(f"Got type '{getFullyQualifiedName(reverse)}'.")
raise ex
if not isinstance(inclusive, bool):
ex = TypeError("Parameter 'inclusive' is not a boolean.")
ex.add_note(f"Got type '{getFullyQualifiedName(inclusive)}'.")
raise ex
if maxLines is not None:
if not isinstance(maxLines, int):
ex = TypeError("Parameter 'maxLines' is not a integer.")
ex.add_note(f"Got type '{getFullyQualifiedName(maxLines)}'.")
raise ex
elif maxLines <= 0:
ex = ValueError("Parameter 'maxLines' must be a positive integer.")
ex.add_note(f"Got {maxLines!r}.")
raise ex
current = self._previousLine if reverse else self._nextLine
if maxLines is None:
if stopPredicate is None:
if reverse:
while current is not None:
yield current
current = current._previousLine
else:
while current is not None:
yield current
current = current._nextLine
else:
if reverse:
while current is not None:
if stopPredicate(current):
if inclusive:
yield current
return
yield current
current = current._previousLine
else:
while current is not None:
if stopPredicate(current):
if inclusive:
yield current
return
yield current
current = current._nextLine
elif stopPredicate is None:
remaining = maxLines
if reverse:
while current is not None and remaining > 0:
yield current
current = current._previousLine
remaining -= 1
else:
while current is not None and remaining > 0:
yield current
current = current._nextLine
remaining -= 1
else:
remaining = maxLines
if reverse:
while current is not None and remaining > 0:
if stopPredicate(current):
if inclusive:
yield current
return
yield current
current = current._previousLine
remaining -= 1
else:
while current is not None and remaining > 0:
if stopPredicate(current):
if inclusive:
yield current
return
yield current
current = current._nextLine
remaining -= 1
def __getitem__(self, item: slice) -> str:
return self._message[item]
[docs]
def __eq__(self, other: Any):
return self._message == other
[docs]
def __ne__(self, other: Any):
return self._message != other
[docs]
def __str__(self) -> str:
return self._message
[docs]
def __repr__(self) -> str:
return f"{self._lineNumber}: {self._message}"
[docs]
@export
class InfoMessage(metaclass=ExtendedType, mixin=True):
pass
[docs]
@export
class WarningMessage(metaclass=ExtendedType, mixin=True):
pass
[docs]
@export
class CriticalWarningMessage(metaclass=ExtendedType, mixin=True):
pass
[docs]
@export
class ErrorMessage(metaclass=ExtendedType, mixin=True):
pass
[docs]
@export
class VivadoMessage(Line):
"""
This class represents an AMD/Xilinx Vivado message.
The usual message format is:
.. code-block:: text
INFO: [Synth 8-7079] Multithreading enabled for synth_design using a maximum of 2 processes.
WARNING: [Synth 8-3332] Sequential element (gen[0].Sync/FF2) is unused and will be removed from module sync_Bits_Xilinx.
The following message severities are defined:
* ``INFO``
* ``WARNING``
* ``CRITICAL WARNING``
* ``ERROR``
.. seealso::
:class:`VivadoInfoMessage`
Representing a Vivado info message.
:class:`VivadoWarningMessage`
Representing a Vivado warning message.
:class:`VivadoCriticalWarningMessage`
Representing a Vivado critical warning message.
:class:`VivadoErrorMessage`
Representing a Vivado error message.
"""
# _MESSAGE_KIND: ClassVar[str]
# _REGEXP: ClassVar[Pattern]
_toolName: Nullable[str]
_toolID: Nullable[int]
_messageKindID: Nullable[int]
[docs]
def __init__(
self,
lineNumber: int,
kind: LineKind,
message: str,
toolName: Nullable[str] = None,
toolID: Nullable[int] = None,
messageKindID: Nullable[int] = None,
previousLine: Nullable[Line] = None
) -> None:
super().__init__(lineNumber, kind, message, previousLine)
self._toolName = toolName
self._toolID = toolID
self._messageKindID = messageKindID
@readonly
def ToolName(self) -> Nullable[str]:
return self._toolName
@readonly
def ToolID(self) -> Nullable[int]:
return self._toolID
@readonly
def MessageKindID(self) -> Nullable[int]:
return self._messageKindID
@classmethod
def Parse(cls, lineNumber: int, kind: LineKind, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
if (match := cls._REGEXP.match(rawMessage)) is not None:
return cls(lineNumber, kind, match[4], match[1], int(match[2]), int(match[3]), previousLine)
return None
[docs]
def __str__(self) -> str:
return f"{self._MESSAGE_KIND}: [{self._toolName} {self._toolID}-{self._messageKindID}] {self._message}"
[docs]
@export
class VivadoInfoMessage(VivadoMessage, InfoMessage):
"""
This class represents an AMD/Xilinx Vivado info message.
.. rubric:: Example
.. code-block::
INFO: [Common 17-83] 66-Releasing license: Synthesis
"""
_MESSAGE_KIND: ClassVar[str] = "INFO"
_REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+) (\d+)-(\d+)\] (.*)""")
@classmethod
def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
return super().Parse(lineNumber, LineKind.InfoMessage, rawMessage, previousLine)
[docs]
@export
class VivadoDRCInfoMessage(VivadoMessage, InfoMessage):
"""
This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) info message.
.. rubric:: Example
.. code-block::
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'.
"""
_MESSAGE_KIND: ClassVar[str] = "INFO"
_REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[DRC (\w+)-(\d+)\] (.*)""")
_drcRuleName: str
[docs]
def __init__(
self,
lineNumber: int,
kind: LineKind,
drcRuleName: str,
message: str,
toolName: Nullable[str] = None,
toolID: Nullable[int] = None,
messageKindID: Nullable[int] = None,
previousLine: Nullable[Line] = None
) -> None:
super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine)
self._drcRuleName = drcRuleName
@readonly
def DRCRuleName(self) -> str:
return self._drcRuleName
@classmethod
def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
if (match := cls._REGEXP.match(rawMessage)) is not None:
return cls(lineNumber, LineKind.WarningMessage, match[1], match[3], toolName="DRC", toolID=None,
messageKindID=int(match[2]), previousLine=previousLine)
return None
[docs]
def __str__(self) -> str:
return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
[docs]
@export
class VivadoIrregularInfoMessage(VivadoMessage, InfoMessage):
"""
This class represents an irregular AMD/Xilinx Vivado info message.
.. rubric:: Example
.. code-block::
INFO: [runtcl-4] Executing : report_io -file system_top_io_placed.rpt
"""
_MESSAGE_KIND: ClassVar[str] = "INFO"
_REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+)-(\d+)\] (.*)""")
@classmethod
def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
if (match := cls._REGEXP.match(rawMessage)) is not None:
return cls(lineNumber, LineKind.InfoMessage, match[3], toolName=match[1], messageKindID=int(match[2]), previousLine=previousLine)
return None
[docs]
def __str__(self) -> str:
return f"{self._MESSAGE_KIND}: [{self._toolName}-{self._messageKindID}] {self._message}"
[docs]
@export
class VivadoStuntedInfoMessage(VivadoMessage, InfoMessage):
"""
This class represents a stunted AMD/Xilinx Vivado info message.
.. rubric:: Example
.. code-block::
INFO: Helper process launched with PID 29056
"""
_MESSAGE_KIND: ClassVar[str] = "INFO"
_REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: ([^\[].*)""")
@classmethod
def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
if (match := cls._REGEXP.match(rawMessage)) is not None:
return cls(lineNumber, LineKind.InfoMessage, match[1], previousLine=previousLine)
return None
[docs]
def __str__(self) -> str:
return f"{self._MESSAGE_KIND}: {self._message}"
[docs]
@export
class VivadoWarningMessage(VivadoMessage, WarningMessage):
"""
This class represents an AMD/Xilinx Vivado warning message.
.. rubric:: Example
.. code-block::
WARNING: [Synth 8-7080] Parallel synthesis criteria is not met
"""
_MESSAGE_KIND: ClassVar[str] = "WARNING"
_REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(\w+(?: \w+)*?) (\d+)-(\d+)\] (.*)""")
@classmethod
def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
return super().Parse(lineNumber, LineKind.WarningMessage, rawMessage, previousLine=previousLine)
[docs]
@export
class VivadoDRCWarningMessage(VivadoMessage, WarningMessage):
"""
This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) warning message.
.. rubric:: Example
.. code-block::
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.
"""
_MESSAGE_KIND: ClassVar[str] = "WARNING"
_REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[DRC (\w+)-(\d+)\] (.*)""")
_drcRuleName: str
[docs]
def __init__(
self,
lineNumber: int,
kind: LineKind,
drcRuleName: str,
message: str,
toolName: Nullable[str] = None,
toolID: Nullable[int] = None,
messageKindID: Nullable[int] = None,
previousLine: Nullable[Line] = None
) -> None:
super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine)
self._drcRuleName = drcRuleName
@readonly
def DRCRuleName(self) -> str:
return self._drcRuleName
@classmethod
def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
if (match := cls._REGEXP.match(rawMessage)) is not None:
return cls(lineNumber, LineKind.WarningMessage, match[1], match[3], toolName="DRC", toolID=None, messageKindID=int(match[2]), previousLine=previousLine)
return None
[docs]
def __str__(self) -> str:
return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
[docs]
@export
class VivadoXPMWarningMessage(VivadoMessage, WarningMessage):
"""
This class represents an AMD/Xilinx Vivado XPM warning message.
.. rubric:: Example
.. code-block::
WARNING: [XPM_CDC_GRAY: TCL-1000] The source and destination clocks are the same.
"""
_MESSAGE_KIND: ClassVar[str] = "WARNING"
_REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(XPM_\w+): (\w+)-(\d+)\] (.*)""")
_xpmName: str
[docs]
def __init__(
self,
lineNumber: int,
kind: LineKind,
xpmName: str,
message: str,
toolName: Nullable[str] = None,
toolID: Nullable[int] = None,
messageKindID: Nullable[int] = None,
previousLine: Nullable[Line] = None
) -> None:
super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine)
self._xpmName = xpmName
@readonly
def XPMName(self) -> str:
return self._xpmName
@classmethod
def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
if (match := cls._REGEXP.match(rawMessage)) is not None:
return cls(lineNumber, LineKind.WarningMessage, match[1], match[4], toolName=match[2], toolID=None, messageKindID=int(match[3]), previousLine=previousLine)
return None
[docs]
def __str__(self) -> str:
return f"{self._MESSAGE_KIND}: [{self._xpmName}: {self._toolName}-{self._messageKindID}] {self._message}"
[docs]
@export
class VivadoStuntedWarningMessage(VivadoMessage, WarningMessage):
"""
This class represents a stunted AMD/Xilinx Vivado warning message.
.. rubric:: Example
.. code-block::
WARNING: set_property ASYNC_REG could not find object (constraint file /path/to/sync_Bits_Xilinx.xdc, line 5).
"""
_MESSAGE_KIND: ClassVar[str] = "WARNING"
_REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: ([^\[].*)""")
@classmethod
def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
if (match := cls._REGEXP.match(rawMessage)) is not None:
return cls(lineNumber, LineKind.WarningMessage, match[1], previousLine=previousLine)
return None
[docs]
def __str__(self) -> str:
return f"{self._MESSAGE_KIND}: {self._message}"
[docs]
@export
class VivadoCriticalWarningMessage(VivadoMessage, CriticalWarningMessage):
"""
This class represents an AMD/Xilinx Vivado critical warning message.
.. rubric:: Example
.. code-block::
CRITICAL WARNING: [Constraints 18-1056] Clock 'RefClkA_SFP_Quad' completely overrides clock 'USRCLKA_SFP[P]'.
"""
_MESSAGE_KIND: ClassVar[str] = "CRITICAL WARNING"
_REGEXP: ClassVar[Pattern] = re_compile(r"""CRITICAL WARNING: \[(\w+) (\d+)-(\d+)\] (.*)""")
@classmethod
def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
return super().Parse(lineNumber, LineKind.CriticalWarningMessage, rawMessage, previousLine)
[docs]
@export
class VivadoErrorMessage(VivadoMessage, ErrorMessage):
"""
This class represents an AMD/Xilinx Vivado error message.
.. rubric:: Example
.. code-block::
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
"""
_MESSAGE_KIND: ClassVar[str] = "ERROR"
_REGEXP: ClassVar[Pattern] = re_compile(r"""ERROR: \[(\w+) (\d+)-(\d+)\] (.*)""")
@classmethod
def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
return super().Parse(lineNumber, LineKind.ErrorMessage, rawMessage, previousLine)
[docs]
@export
class VHDLReportMessage(VivadoInfoMessage):
_REGEXP: ClassVar[Pattern ] = re_compile(r"""RTL report: "(.*)" \[(.*):(\d+)\]""")
_reportMessage: str
_sourceFile: Path
_sourceLineNumber: int
[docs]
def __init__(
self,
lineNumber: int,
rawMessage: str,
toolName: str,
toolID: int,
messageKindID: int,
reportMessage: str,
sourceFile: Path,
sourceLineNumber: int,
previousLine: Nullable[Line] = None
) -> None:
super().__init__(lineNumber, LineKind.InfoMessage, rawMessage, toolName, toolID, messageKindID, previousLine)
self._reportMessage = reportMessage
self._sourceFile = sourceFile
self._sourceLineNumber = sourceLineNumber
@classmethod
def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]:
if (match := cls._REGEXP.match(line._message)) is not None:
return cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, match[1], Path(match[2]), int(match[3]), previousLine=line._previousLine)
return None
[docs]
@export
class VHDLAssertionMessage(VHDLReportMessage):
_REGEXP: ClassVar[Pattern ] = re_compile(r"""RTL assertion: "(.*)" \[(.*):(\d+)\]""")
[docs]
@export
class TclCommand(Line):
"""
Represents a TCL command found in a Vivado log output.
Besides the full log message (:class:`Line`), this class splits the TCL command into the command name and its
arguments.
"""
_command: str
_arguments: Tuple[str, ...]
[docs]
def __init__(
self,
lineNumber: int,
command: str,
arguments: Tuple[str, ...],
rawMessage: str,
previousLine: Nullable[Line] = None
) -> None:
super().__init__(lineNumber, LineKind.GenericTclCommand, rawMessage, previousLine)
self._command = command
self._arguments = arguments
@readonly
def Command(self) -> str:
return self._command
@readonly
def Arguments(self) -> Tuple[str, ...]:
return self._arguments
@classmethod
def FromLine(cls, line: Line) -> Nullable[Self]:
args = line._message.split()
return cls(line._lineNumber, args[0], tuple(args[1:]), line._message, previousLine=line._previousLine)
[docs]
def __str__(self) -> str:
return f"{self._command} {' '.join(self._arguments)}"
[docs]
@export
class VivadoTclCommand(TclCommand):
"""
Represents a Vivado specific TCL command.
"""
_PREFIX: ClassVar[str] = "Command:"
@classmethod
def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]:
command = rawMessage[len(cls._PREFIX) + 1:]
args = command.split()
vivadoCommand = cls(lineNumber, args[0], tuple(args[1:]), rawMessage, previousLine)
vivadoCommand._kind = LineKind.VivadoTclCommand
return vivadoCommand
[docs]
def __str__(self) -> str:
return f"{self._PREFIX} {self._command} {' '.join(self._arguments)}"