Coverage for pyEDAA/OutputFilter/Xilinx/__init__.py: 90%
167 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 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):
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 :return:
96 .. info::
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: 118 ↛ 119line 118 didn't jump to line 119 because the condition on line 118 was never true
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: 123 ↛ 124line 123 didn't jump to line 124 because the condition on line 123 was never true
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): 131 ↛ 132line 131 didn't jump to line 132 because the condition on line 131 was never true
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: 143 ↛ 144line 143 didn't jump to line 144 because the condition on line 143 was never true
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: 156 ↛ 157line 156 didn't jump to line 157 because the condition on line 156 was never true
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: 176 ↛ 177line 176 didn't jump to line 177 because the condition on line 176 was never true
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: 220 ↛ 225line 220 didn't jump to line 225 because the condition on line 220 was always true
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 def Parse(self) -> None:
259 with Stopwatch() as sw:
260 with self._logfile.open("r", encoding="utf-8") as f:
261 content = f.read()
263 next(generator := self.LineClassification())
264 for rawLine in content.splitlines():
265 generator.send(rawLine)
267 self._duration = sw.Duration