Coverage for pyEDAA / OutputFilter / Xilinx / __init__.py: 96%
170 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-23 22:13 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-23 22:13 +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 pathlib import Path
33from typing import Optional as Nullable, Dict, List, Generator, Union, Type
35from pyTooling.Decorators import export, readonly
36from pyTooling.MetaClasses import ExtendedType
37from pyTooling.Stopwatch import Stopwatch
39from pyEDAA.OutputFilter.Xilinx.Common import LineKind, Line
40from pyEDAA.OutputFilter.Xilinx.Common import VivadoMessage, TclCommand, VivadoTclCommand
41from pyEDAA.OutputFilter.Xilinx.Common import InfoMessage, VivadoInfoMessage, VivadoIrregularInfoMessage
42from pyEDAA.OutputFilter.Xilinx.Common import WarningMessage, VivadoWarningMessage, VivadoIrregularWarningMessage
43from pyEDAA.OutputFilter.Xilinx.Common import CriticalWarningMessage, VivadoCriticalWarningMessage
44from pyEDAA.OutputFilter.Xilinx.Common import ErrorMessage, VivadoErrorMessage
45from pyEDAA.OutputFilter.Xilinx.Common import VHDLReportMessage
46from pyEDAA.OutputFilter.Xilinx.Commands import Command, SynthesizeDesign, LinkDesign, OptimizeDesign, PlaceDesign, \
47 PhysicalOptimizeDesign, RouteDesign, WriteBitstream, ReportDRC, ReportMethodology, ReportPower
48from pyEDAA.OutputFilter.Xilinx.Common2 import Preamble, VivadoMessagesMixin
49from pyEDAA.OutputFilter.Xilinx.Exception import ProcessorException, ClassificationException
52ProcessedLine = Union[Line, ProcessorException]
55@export
56class Processor(VivadoMessagesMixin, metaclass=ExtendedType, slots=True):
57 _duration: float
59 _lines: List[ProcessedLine]
60 _preamble: Preamble
61 _commands: Dict[Type[Command], Command]
63 def __init__(self) -> None:
64 super().__init__()
66 self._duration = 0.0
68 self._lines = []
69 self._preamble = None
70 self._commands = {}
72 @readonly
73 def Lines(self) -> List[ProcessedLine]:
74 return self._lines
76 @readonly
77 def Preamble(self) -> Preamble:
78 return self._preamble
80 @readonly
81 def Commands(self) -> Dict[Type[Command], Command]:
82 return self._commands
84 @readonly
85 def Duration(self) -> float:
86 return self._duration
88 def __getitem__(self, item: Type[Command]) -> Command:
89 return self._commands[item]
91 def IsIncompleteLog(self) -> bool:
92 """
94 :returns: undocumented
96 .. note::
98 ``INFO: [Common 17-14] Message 'Synth 8-3321' appears 100 times and further instances of the messages will be disabled. Use the Tcl command set_msg_config to change the current settings.``
99 """
100 return 17 in self._messagesByID and 14 in self._messagesByID[17]
102 def LineClassification(self) -> Generator[ProcessedLine, str, None]:
103 # Instantiate and initialize CommandFinder
104 next(cmdFinder := self.CommandFinder())
106 # wait for first line
107 lastLine = None
108 rawMessageLine = yield
109 lineNumber = 0
110 _errorMessage = "Unknown processing error"
112 while rawMessageLine is not None: 112 ↛ exitline 112 didn't return from function 'LineClassification' because the condition on line 112 was always true
113 lineNumber += 1
114 rawMessageLine = rawMessageLine.rstrip()
115 errorMessage = _errorMessage
117 if rawMessageLine.startswith(VivadoInfoMessage._MESSAGE_KIND):
118 if (line := VivadoInfoMessage.Parse(lineNumber, rawMessageLine)) is None:
119 line = VivadoIrregularInfoMessage.Parse(lineNumber, rawMessageLine)
121 errorMessage = f"Line starting with 'INFO' was not a VivadoInfoMessage."
122 elif rawMessageLine.startswith(VivadoWarningMessage._MESSAGE_KIND):
123 if (line := VivadoWarningMessage.Parse(lineNumber, rawMessageLine)) is None:
124 line = VivadoIrregularWarningMessage.Parse(lineNumber, rawMessageLine)
126 errorMessage = f"Line starting with 'WARNING' was not a VivadoWarningMessage."
127 elif rawMessageLine.startswith(VivadoCriticalWarningMessage._MESSAGE_KIND):
128 line = VivadoCriticalWarningMessage.Parse(lineNumber, rawMessageLine)
130 errorMessage = f"Line starting with 'CRITICAL WARNING' was not a VivadoCriticalWarningMessage."
131 elif rawMessageLine.startswith(VivadoErrorMessage._MESSAGE_KIND):
132 line = VivadoErrorMessage.Parse(lineNumber, rawMessageLine)
134 errorMessage = f"Line starting with 'ERROR' was not a VivadoErrorMessage."
135 elif len(rawMessageLine) == 0:
136 line = Line(lineNumber, LineKind.Empty, rawMessageLine)
137 elif rawMessageLine.startswith("Command: "):
138 line = VivadoTclCommand.Parse(lineNumber, rawMessageLine)
139 errorMessage = "Line starting with 'Command:' was not a VivadoTclCommand."
140 else:
141 line = Line(lineNumber, LineKind.Unprocessed, rawMessageLine)
143 if line is None:
144 line = Line(lineNumber, LineKind.ProcessorError, rawMessageLine)
145 else:
146 if line.StartsWith("Resolution:") and isinstance(lastLine, VivadoMessage):
147 line._kind = LineKind.Verbose
149 line.PreviousLine = lastLine
150 lastLine = line
151 line = cmdFinder.send(line)
153 if isinstance(line, VivadoMessage):
154 self._AddMessage(line)
156 if line._kind is LineKind.ProcessorError:
157 line = ClassificationException(errorMessage, lineNumber, rawMessageLine)
159 self._lines.append(line)
161 rawMessageLine = yield line
163 def CommandFinder(self) -> Generator[Line, Line, None]:
164 self._preamble = Preamble(self)
165 cmd = None
167 tclProcedures = {"source"}
169 # wait for first line
170 line = yield
171 # process preable
172 line = yield from self._preamble.Generator(line)
174 while True:
175 while True:
176 if line._kind is LineKind.Empty:
177 line = yield line
178 continue
179 elif isinstance(line, VivadoTclCommand):
180 if line._command == SynthesizeDesign._TCL_COMMAND:
181 self._commands[SynthesizeDesign] = (cmd := SynthesizeDesign(self))
182 line = yield next(gen := cmd.SectionDetector(line))
183 break
184 elif line._command == LinkDesign._TCL_COMMAND:
185 self._commands[LinkDesign] = (cmd := LinkDesign(self))
186 line = yield next(gen := cmd.SectionDetector(line))
187 break
188 elif line._command == OptimizeDesign._TCL_COMMAND:
189 self._commands[OptimizeDesign] = (cmd := OptimizeDesign(self))
190 line = yield next(gen := cmd.SectionDetector(line))
191 break
192 elif line._command == OptimizeDesign._TCL_COMMAND: 192 ↛ 193line 192 didn't jump to line 193 because the condition on line 192 was never true
193 self._commands[OptimizeDesign] = (cmd := OptimizeDesign(self))
194 line = yield next(gen := cmd.SectionDetector(line))
195 break
196 elif line._command == PlaceDesign._TCL_COMMAND:
197 self._commands[PlaceDesign] = (cmd := PlaceDesign(self))
198 line = yield next(gen := cmd.SectionDetector(line))
199 break
200 elif line._command == PhysicalOptimizeDesign._TCL_COMMAND:
201 self._commands[PhysicalOptimizeDesign] = (cmd := PhysicalOptimizeDesign(self))
202 line = yield next(gen := cmd.SectionDetector(line))
203 break
204 elif line._command == RouteDesign._TCL_COMMAND:
205 self._commands[RouteDesign] = (cmd := RouteDesign(self))
206 line = yield next(gen := cmd.SectionDetector(line))
207 break
208 elif line._command == WriteBitstream._TCL_COMMAND:
209 self._commands[WriteBitstream] = (cmd := WriteBitstream(self))
210 line = yield next(gen := cmd.SectionDetector(line))
211 break
212 elif line._command == ReportDRC._TCL_COMMAND:
213 self._commands[ReportDRC] = (cmd := ReportDRC(self))
214 line = yield next(gen := cmd.SectionDetector(line))
215 break
216 elif line._command == ReportMethodology._TCL_COMMAND:
217 self._commands[ReportMethodology] = (cmd := ReportMethodology(self))
218 line = yield next(gen := cmd.SectionDetector(line))
219 break
220 elif line._command == ReportPower._TCL_COMMAND:
221 self._commands[ReportPower] = (cmd := ReportPower(self))
222 line = yield next(gen := cmd.SectionDetector(line))
223 break
225 firstWord = line.Partition(" ")[0]
226 if firstWord in tclProcedures:
227 line = TclCommand.FromLine(line)
229 line = yield line
231 # end = f"{cmd._TCL_COMMAND} completed successfully"
233 while True:
234 # if line.StartsWith(end):
235 # # line._kind |= LineKind.Success
236 # lastLine = gen.send(line)
237 # if LineKind.Last in line._kind:
238 # line._kind ^= LineKind.Last
239 # line = yield lastLine
240 # break
242 try:
243 line = yield gen.send(line)
244 except StopIteration as ex:
245 line = ex.value
246 break
249@export
250class Document(Processor):
251 _logfile: Path
253 def __init__(self, logfile: Path) -> None:
254 super().__init__()
256 self._logfile = logfile
258 @readonly
259 def Logfile(self) -> Path:
260 return self._logfile
262 def Parse(self) -> None:
263 with Stopwatch() as sw:
264 with self._logfile.open("r", encoding="utf-8") as f:
265 content = f.read()
267 next(generator := self.LineClassification())
268 for rawLine in content.splitlines():
269 generator.send(rawLine)
271 self._duration = sw.Duration