Coverage for pyEDAA/OutputFilter/Xilinx/Common.py: 91%
265 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-16 06:19 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-16 06:19 +0000
1# ==================================================================================================================== #
2# _____ ____ _ _ ___ _ _ _____ _ _ _ #
3# _ __ _ _| ____| _ \ / \ / \ / _ \ _ _| |_ _ __ _ _| |_| ___(_) | |_ ___ _ __ #
4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | | __| '_ \| | | | __| |_ | | | __/ _ \ '__| #
5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| | |_| | |_| |_) | |_| | |_| _| | | | || __/ | #
6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/ \__,_|\__| .__/ \__,_|\__|_| |_|_|\__\___|_| #
7# |_| |___/ |_| #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2025-2025 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
37from pyTooling.Decorators import export, readonly
38from pyTooling.MetaClasses import ExtendedType
41@export
42class LineKind(Flag):
43 Unprocessed = 0
44 ProcessorError = 2** 0
45 Empty = 2** 1
46 Delimiter = 2** 2
48 Success = 2** 3
49 Failed = 2** 4
51 Verbose = 2**10
52 Normal = 2**11
53 Info = 2**12
54 Warning = 2**13
55 CriticalWarning = 2**14
56 Error = 2**15
57 Fatal = 2**16
59 Start = 2**20
60 End = 2**21
61 Header = 2**22
62 Content = 2**23
63 Time = 2**24
64 Footer = 2**25
66 Last = 2**29
68 Message = 2**30
69 InfoMessage = Message | Info
70 WarningMessage = Message | Warning
71 CriticalWarningMessage = Message | CriticalWarning
72 ErrorMessage = Message | Error
74 Task = 2**31
75 TaskStart = Task | Start
76 TaskEnd = Task | End
77 TaskTime = Task | Time
79 Phase = 2**32
80 PhaseDelimiter = Phase | Delimiter
81 PhaseStart = Phase | Start
82 PhaseEnd = Phase | End
83 PhaseTime = Phase | Time
84 PhaseFinal = Phase | Footer
86 SubPhase = 2**33
87 SubPhaseStart = SubPhase | Start
88 SubPhaseEnd = SubPhase | End
89 SubPhaseTime = SubPhase | Time
91 SubSubPhase = 2**34
92 SubSubPhaseStart = SubSubPhase | Start
93 SubSubPhaseEnd = SubSubPhase | End
94 SubSubPhaseTime = SubSubPhase | Time
96 SubSubSubPhase = 2**35
97 SubSubSubPhaseStart = SubSubSubPhase | Start
98 SubSubSubPhaseEnd = SubSubSubPhase | End
99 SubSubSubPhaseTime = SubSubSubPhase | Time
101 Section = 2**36
102 SectionDelimiter = Section | Delimiter
103 SectionStart = Section | Start
104 SectionEnd = Section | End
106 SubSection = 2**37
107 SubSectionDelimiter = SubSection | Delimiter
108 SubSectionStart = SubSection | Start
109 SubSectionEnd = SubSection | End
111 Paragraph = 2**38
112 ParagraphHeadline = Paragraph | Header
114 Hierarchy = 2**39
115 HierarchyStart = Hierarchy | Start
116 HierarchyEnd = Hierarchy | End
118 XDC = 2**40
119 XDCStart = XDC | Start
120 XDCEnd = XDC | End
122 Table = 2**41
123 TableFrame = Table | Delimiter
124 TableHeader = Table | Header
125 TableRow = Table | Content
126 TableFooter = Table | Footer
128 TclCommand = 2**42
129 GenericTclCommand = TclCommand | 2**0
130 VivadoTclCommand = TclCommand | 2**1
133@export
134class Line(metaclass=ExtendedType, slots=True):
135 """
136 This class represents any line in a log file.
137 """
138 _lineNumber: int
139 _kind: LineKind
140 _message: str
141 _previousLine: "Line"
142 _nextLine: "Line"
144 def __init__(self, lineNumber: int, kind: LineKind, message: str) -> None:
145 self._lineNumber = lineNumber
146 self._kind = kind
147 self._message = message
148 self._previousLine = None
149 self._nextLine = None
151 @readonly
152 def LineNumber(self) -> int:
153 return self._lineNumber
155 @readonly
156 def Kind(self) -> LineKind:
157 return self._kind
159 @readonly
160 def Message(self) -> str:
161 return self._message
163 @property
164 def PreviousLine(self) -> "Line":
165 return self._previousLine
167 @PreviousLine.setter
168 def PreviousLine(self, line: "Line") -> None:
169 self._previousLine = line
170 if line is not None:
171 line._nextLine = self
173 @readonly
174 def NextLine(self) -> "Line":
175 return self._nextLine
177 def Partition(self, separator: str) -> Tuple[str, str, str]:
178 return self._message.partition(separator)
180 def StartsWith(self, prefix: Union[str, Tuple[str, ...]]):
181 return self._message.startswith(prefix)
183 def __getitem__(self, item: slice) -> str:
184 return self._message[item]
186 def __eq__(self, other: Any):
187 return self._message == other
189 def __ne__(self, other: Any):
190 return self._message != other
192 def __str__(self) -> str:
193 return self._message
195 def __repr__(self) -> str:
196 return f"{self._lineNumber}: {self._message}"
199@export
200class InfoMessage(metaclass=ExtendedType, mixin=True):
201 pass
204@export
205class WarningMessage(metaclass=ExtendedType, mixin=True):
206 pass
209@export
210class CriticalWarningMessage(metaclass=ExtendedType, mixin=True):
211 pass
214@export
215class ErrorMessage(metaclass=ExtendedType, mixin=True):
216 pass
219@export
220class VivadoMessage(Line):
221 """
222 This class represents an AMD/Xilinx Vivado message.
224 The usual message format is:
226 .. code-block:: text
228 INFO: [Synth 8-7079] Multithreading enabled for synth_design using a maximum of 2 processes.
229 WARNING: [Synth 8-3332] Sequential element (gen[0].Sync/FF2) is unused and will be removed from module sync_Bits_Xilinx.
231 The following message severities are defined:
233 * ``INFO``
234 * ``WARNING``
235 * ``CRITICAL WARNING``
236 * ``ERROR``
238 .. seealso::
240 :class:`VivadoInfoMessage`
241 Representing a Vivado info message.
243 :class:`VivadoWarningMessage`
244 Representing a Vivado warning message.
246 :class:`VivadoCriticalWarningMessage`
247 Representing a Vivado critical warning message.
249 :class:`VivadoErrorMessage`
250 Representing a Vivado error message.
251 """
252 # _MESSAGE_KIND: ClassVar[str]
253 # _REGEXP: ClassVar[Pattern]
255 _toolID: int
256 _toolName: str
257 _messageKindID: int
259 def __init__(self, lineNumber: int, kind: LineKind, tool: str, toolID: int, messageKindID: int, message: str) -> None:
260 super().__init__(lineNumber, kind, message)
261 self._toolID = toolID
262 self._toolName = tool
263 self._messageKindID = messageKindID
265 @readonly
266 def ToolName(self) -> str:
267 return self._toolName
269 @readonly
270 def ToolID(self) -> int:
271 return self._toolID
273 @readonly
274 def MessageKindID(self) -> int:
275 return self._messageKindID
277 @classmethod
278 def Parse(cls, lineNumber: int, kind: LineKind, rawMessage: str) -> Nullable[Self]:
279 if (match := cls._REGEXP.match(rawMessage)) is not None:
280 return cls(lineNumber, kind, match[1], int(match[2]), int(match[3]), match[4])
282 return None
284 def __str__(self) -> str:
285 return f"{self._MESSAGE_KIND}: [{self._toolName} {self._toolID}-{self._messageKindID}] {self._message}"
288@export
289class VivadoInfoMessage(VivadoMessage, InfoMessage):
290 """
291 This class represents an AMD/Xilinx Vivado info message.
292 """
294 _MESSAGE_KIND: ClassVar[str] = "INFO"
295 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+) (\d+)-(\d+)\] (.*)""")
297 @classmethod
298 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]:
299 return super().Parse(lineNumber, LineKind.InfoMessage, rawMessage)
302@export
303class VivadoIrregularInfoMessage(VivadoMessage, InfoMessage):
304 """
305 This class represents an irregular AMD/Xilinx Vivado info message.
306 """
308 _MESSAGE_KIND: ClassVar[str] = "INFO"
309 _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+)-(\d+)\] (.*)""")
311 @classmethod
312 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]:
313 if (match := cls._REGEXP.match(rawMessage)) is not None:
314 return cls(lineNumber, LineKind.InfoMessage, match[1], None, int(match[2]), match[3])
316 return None
318 def __str__(self) -> str:
319 return f"{self._MESSAGE_KIND}: [{self._toolName}-{self._messageKindID}] {self._message}"
322@export
323class VivadoWarningMessage(VivadoMessage, WarningMessage):
324 """
325 This class represents an AMD/Xilinx Vivado warning message.
326 """
328 _MESSAGE_KIND: ClassVar[str] = "WARNING"
329 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(\w+) (\d+)-(\d+)\] (.*)""")
331 @classmethod
332 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]:
333 return super().Parse(lineNumber, LineKind.WarningMessage, rawMessage)
336@export
337class VivadoIrregularWarningMessage(VivadoMessage, WarningMessage):
338 """
339 This class represents an AMD/Xilinx Vivado warning message.
340 """
342 _MESSAGE_KIND: ClassVar[str] = "WARNING"
343 _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: (.*)""")
345 @classmethod
346 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]:
347 if (match := cls._REGEXP.match(rawMessage)) is not None:
348 return cls(lineNumber, LineKind.WarningMessage, None, None, None, match[1])
350 return None
352 def __str__(self) -> str:
353 return f"{self._MESSAGE_KIND}: {self._message}"
356@export
357class VivadoCriticalWarningMessage(VivadoMessage, CriticalWarningMessage):
358 """
359 This class represents an AMD/Xilinx Vivado critical warning message.
360 """
362 _MESSAGE_KIND: ClassVar[str] = "CRITICAL WARNING"
363 _REGEXP: ClassVar[Pattern] = re_compile(r"""CRITICAL WARNING: \[(\w+) (\d+)-(\d+)\] (.*)""")
365 @classmethod
366 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]:
367 return super().Parse(lineNumber, LineKind.CriticalWarningMessage, rawMessage)
370@export
371class VivadoErrorMessage(VivadoMessage, ErrorMessage):
372 """
373 This class represents an AMD/Xilinx Vivado error message.
374 """
376 _MESSAGE_KIND: ClassVar[str] = "ERROR"
377 _REGEXP: ClassVar[Pattern] = re_compile(r"""ERROR: \[(\w+) (\d+)-(\d+)\] (.*)""")
379 @classmethod
380 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]:
381 return super().Parse(lineNumber, LineKind.ErrorMessage, rawMessage)
384@export
385class VHDLReportMessage(VivadoInfoMessage):
386 _REGEXP: ClassVar[Pattern ] = re_compile(r"""RTL report: "(.*)" \[(.*):(\d+)\]""")
388 _reportMessage: str
389 _sourceFile: Path
390 _sourceLineNumber: int
392 def __init__(self, lineNumber: int, tool: str, toolID: int, messageKindID: int, rawMessage: str, reportMessage: str, sourceFile: Path, sourceLineNumber: int):
393 super().__init__(lineNumber, LineKind.InfoMessage, tool, toolID, messageKindID, rawMessage)
395 self._reportMessage = reportMessage
396 self._sourceFile = sourceFile
397 self._sourceLineNumber = sourceLineNumber
399 @classmethod
400 def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]:
401 if (match := cls._REGEXP.match(line._message)) is not None: 401 ↛ 404line 401 didn't jump to line 404 because the condition on line 401 was always true
402 return cls(line._lineNumber, line._toolName, line._toolID, line._messageKindID, line._message, match[1], Path(match[2]), int(match[3]))
404 return None
407@export
408class VHDLAssertionMessage(VHDLReportMessage):
409 _REGEXP: ClassVar[Pattern ] = re_compile(r"""RTL assertion: "(.*)" \[(.*):(\d+)\]""")
412@export
413class TclCommand(Line):
414 _command: str
415 _args: Tuple[str, ...]
417 def __init__(self, lineNumber: int, command: str, arguments: Tuple[str, ...], tclCommand: str) -> None:
418 super().__init__(lineNumber, LineKind.GenericTclCommand, tclCommand)
420 self._command = command
421 self._args = arguments
423 @readonly
424 def Command(self) -> str:
425 return self._command
427 @readonly
428 def Arguments(self) -> Tuple[str]:
429 return self._args
431 @classmethod
432 def FromLine(cls, line: Line) -> Nullable[Self]:
433 args = line._message.split()
435 return cls(line._lineNumber, args[0], tuple(args[1:]), line._message)
437 def __str__(self) -> str:
438 return f"{self._command} {' '.join(self._args)}"
441@export
442class VivadoTclCommand(TclCommand):
443 _PREFIX: ClassVar[str] = "Command:"
445 @classmethod
446 def Parse(cls, lineNumber: int, rawMessage: str) -> Nullable[Self]:
447 command = rawMessage[len(cls._PREFIX) + 1:]
448 args = command.split()
450 command = cls(lineNumber, args[0], tuple(args[1:]), command)
451 command._kind = LineKind.VivadoTclCommand
452 return command
454 def __str__(self) -> str:
455 return f"{self._PREFIX} {self._command} {' '.join(self._args)}"