Coverage for pyEDAA/OutputFilter/Xilinx/Commands.py: 82%
492 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 pathlib import Path
33from re import compile as re_compile
34from typing import ClassVar, Generator, Union, List, Type, Dict, Any, Tuple
36from pyTooling.Decorators import export, readonly
37from pyTooling.Common import getFullyQualifiedName
38from pyTooling.Warning import WarningCollector
40from pyEDAA.OutputFilter.Xilinx import VivadoTclCommand
41from pyEDAA.OutputFilter.Xilinx.Exception import ProcessorException, NotPresentException
42from pyEDAA.OutputFilter.Xilinx.Common import Line, LineKind, VivadoMessage, VHDLReportMessage
43from pyEDAA.OutputFilter.Xilinx.Common2 import Parser, UnknownSection, UnknownTask, Task
44from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import Section, RTLElaboration, HandlingCustomAttributes
45from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import ConstraintValidation, LoadingPart, ApplySetProperty
46from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import RTLComponentStatistics, RTLHierarchicalComponentStatistics
47from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import PartResourceSummary, CrossBoundaryAndAreaOptimization
48from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import ROM_RAM_DSP_SR_Retiming, ApplyingXDCTimingConstraints
49from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import TimingOptimization, TechnologyMapping, IOInsertion
50from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import FlatteningBeforeIOInsertion, FinalNetlistCleanup
51from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import RenamingGeneratedInstances, RebuildingUserHierarchy
52from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import RenamingGeneratedPorts, RenamingGeneratedNets
53from pyEDAA.OutputFilter.Xilinx.SynthesizeDesign import WritingSynthesisReport
54from pyEDAA.OutputFilter.Xilinx.OptimizeDesign import DRCTask, CacheTimingInformationTask, LogicOptimizationTask
55from pyEDAA.OutputFilter.Xilinx.OptimizeDesign import PowerOptimizationTask, FinalCleanupTask, NetlistObfuscationTask
56from pyEDAA.OutputFilter.Xilinx.PlaceDesign import PlacerTask
57from pyEDAA.OutputFilter.Xilinx.PhysicalOptimizeDesign import PhysicalSynthesisTask, InitialUpdateTimingTask
58from pyEDAA.OutputFilter.Xilinx.RouteDesign import RoutingTask
61@export
62class CommandNotPresentException(NotPresentException):
63 pass
66@export
67class SectionNotPresentException(NotPresentException):
68 pass
71@export
72class HandlingCustomAttributes1(HandlingCustomAttributes):
73 pass
76@export
77class HandlingCustomAttributes2(HandlingCustomAttributes):
78 pass
80# FIXME: remove duplications
81@export
82class ROM_RAM_DSP_SR_Retiming1(ROM_RAM_DSP_SR_Retiming):
83 pass
86@export
87class ROM_RAM_DSP_SR_Retiming2(ROM_RAM_DSP_SR_Retiming):
88 pass
91@export
92class ROM_RAM_DSP_SR_Retiming3(ROM_RAM_DSP_SR_Retiming):
93 pass
96@export
97class Command(Parser):
98 """
99 This parser parses outputs from Vivado TCL commands.
101 Depending on the command's output (and how it's implemented), they use different subcategories.
103 .. rubric:: Command subcategories
105 * :class:`CommandWithSections`
106 * :class:`CommandWithtasks`
108 .. rubric:: Supported commands
110 * :class:`SynthesizeDesign`
111 * :class:`LinkDesign`
112 * :class:`OptimizeDesign`
113 * :class:`PlaceDesign`
114 * :class:`PhysicalOptimizeDesign`
115 * :class:`RouteDesign`
116 * :class:`WriteBitstream`
117 * :class:`ReportDRC`
118 * :class:`ReportMethodology`
119 * :class:`ReportPower`
121 .. rubric:: Example
123 .. code-block::
125 [...]
126 Command: synth_design -top system_top -part xc7z015clg485-2
127 Starting synth_design
128 [...]
129 """
131 # _TCL_COMMAND: ClassVar[str]
133 def _CommandStart(self, line: Line) -> Generator[Line, Line, Line]:
134 """
135 A generator accepting a line containing the expected Vivado TCL command.
137 When the generator exits, the returned line is the successor line to the line containing the Vivado TCL command.
139 :param line: The first line for the generator to process.
140 :returns: A generator processing Vivado output log lines.
141 """
142 if not (isinstance(line, VivadoTclCommand) and line._command == self._TCL_COMMAND): 142 ↛ 143line 142 didn't jump to line 143 because the condition on line 142 was never true
143 raise ProcessorException() # FIXME: add exception message
145 nextLine = yield line
146 return nextLine
148 def _CommandFinish(self, line: Line) -> Generator[Line, Line, Line]:
149 if line.StartsWith(f"{self._TCL_COMMAND} completed successfully"): 149 ↛ 152line 149 didn't jump to line 152 because the condition on line 149 was always true
150 line._kind |= LineKind.Success
151 else:
152 line._kind |= LineKind.Failed
154 line = yield line
156 if self._TIME is not None: # and self._processor._preamble._toolVersion > "2022.2":
157 end = f"{self._TCL_COMMAND}: {self._TIME}"
158 if line.StartsWith(end):
159 line._kind = LineKind.TaskTime
160 line = yield line
162 return line
164 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, None]:
165 line = yield from self._CommandStart(line)
167 end = f"{self._TCL_COMMAND} "
168 while True:
169 if line._kind is LineKind.Empty:
170 line = yield line
171 continue
172 elif isinstance(line, VivadoMessage):
173 self._AddMessage(line)
174 elif line.StartsWith(end):
175 nextLine = yield from self._CommandFinish(line)
176 return nextLine
178 line = yield line
180 def __str__(self) -> str:
181 return f"{self._TCL_COMMAND}"
184@export
185class CommandWithSections(Command):
186 """
187 A Vivado command writing sections into the output log.
189 .. rubric:: Example
191 .. code-block::
193 [...]
194 ---------------------------------------------------------------------------------
195 Starting RTL Elaboration : Time (s): cpu = 00:00:03 ; elapsed = 00:00:03 . Memory (MB): peak = 847.230 ; gain = 176.500
196 ---------------------------------------------------------------------------------
197 INFO: [Synth 8-638] synthesizing module 'system_top' [C:/Users/tgomes/git/2019_1/src/system_top_PE1.vhd:257]
198 [...]
199 [...]
200 [...]
201 ---------------------------------------------------------------------------------
202 Finished RTL Elaboration : Time (s): cpu = 00:00:04 ; elapsed = 00:00:04 . Memory (MB): peak = 917.641 ; gain = 246.910
203 ---------------------------------------------------------------------------------
204 [...]
205 """
206 # _PARSERS: ClassVar[Tuple[Type[Section], ...]]
208 _sections: Dict[Type[Section], Section]
211 def __init__(self, processor: "Processor") -> None:
212 super().__init__(processor)
214 self._sections = {p: p(self) for p in self._PARSERS}
216 @readonly
217 def Sections(self) -> Dict[Type[Section], Section]:
218 """
219 Read-only property to access a dictionary of found sections within the TCL command's output.
221 :returns: A dictionary of found :class:`~pyEDAA.OutputFilter.Xilinx.SynthesizeDesign.Section`s.
222 """
223 return self._sections
225 def __contains__(self, key: Any) -> bool:
226 if not issubclass(key, Section): 226 ↛ 227line 226 didn't jump to line 227 because the condition on line 226 was never true
227 ex = TypeError(f"Parameter 'key' is not a Section.")
228 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
229 raise ex
231 return key in self._sections
233 def __getitem__(self, key: Type[Section]) -> Section:
234 try:
235 return self._sections[key]
236 except KeyError as ex:
237 raise SectionNotPresentException(F"Section '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
240@export
241class CommandWithTasks(Command):
242 """
243 A Vivado command writing tasks into the output log.
245 .. rubric:: Example
247 .. code-block::
249 [...]
250 Starting Cache Timing Information Task
251 INFO: [Timing 38-35] 79-Done setting XDC timing constraints.
252 [...]
253 [...]
254 Ending Cache Timing Information Task | Checksum: 19fe8cb97
255 [...]
256 """
257 # _PARSERS: Tuple[Type[Task], ...]
259 _tasks: Dict[Type[Task], Task]
261 def __init__(self, processor: "Processor") -> None:
262 super().__init__(processor)
264 self._tasks = {p: p(self) for p in self._PARSERS}
266 @readonly
267 def Tasks(self) -> Dict[Type[Task], Task]:
268 """
269 Read-only property to access a dictionary of found tasks within the TCL command's output.
271 :returns: A dictionary of found :class:`~pyEDAA.OutputFilter.Xilinx.Common2.Task`s.
272 """
273 return self._tasks
275 def __contains__(self, key: Any) -> bool:
276 if not issubclass(key, Task): 276 ↛ 277line 276 didn't jump to line 277 because the condition on line 276 was never true
277 ex = TypeError(f"Parameter 'key' is not a Task.")
278 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.")
279 raise ex
281 return key in self._tasks
283 def __getitem__(self, key: Type[Task]) -> Task:
284 try:
285 return self._tasks[key]
286 except KeyError as ex:
287 raise SectionNotPresentException(F"Task '{key._NAME}' not present in '{self._parent.logfile}'.") from ex
290@export
291class SynthesizeDesign(CommandWithSections):
292 """
293 A Vivado command output parser for ``synth_design``.
294 """
295 _TCL_COMMAND: ClassVar[str] = "synth_design"
296 _PARSERS: ClassVar[Tuple[Type[Section], ...]] = (
297 RTLElaboration,
298 HandlingCustomAttributes1,
299 ConstraintValidation,
300 LoadingPart,
301 ApplySetProperty,
302 RTLComponentStatistics,
303 RTLHierarchicalComponentStatistics,
304 PartResourceSummary,
305 CrossBoundaryAndAreaOptimization,
306 ROM_RAM_DSP_SR_Retiming1,
307 ApplyingXDCTimingConstraints,
308 TimingOptimization,
309 ROM_RAM_DSP_SR_Retiming2,
310 TechnologyMapping,
311 IOInsertion,
312 FlatteningBeforeIOInsertion,
313 FinalNetlistCleanup,
314 RenamingGeneratedInstances,
315 RebuildingUserHierarchy,
316 RenamingGeneratedPorts,
317 HandlingCustomAttributes2,
318 RenamingGeneratedNets,
319 ROM_RAM_DSP_SR_Retiming3,
320 WritingSynthesisReport,
321 )
323 @readonly
324 def HasLatches(self) -> bool:
325 """
326 Read-only property returning if synthesis inferred latches into the design.
328 Latch detection is based on:
330 * Vivado message ``synth 8-327``
331 * Cells of lind ``LD`` listed in the *Cell Usage* report.
333 :returns: True, if the design contains latches.
334 """
335 if (8 in self._messagesByID) and (327 in self._messagesByID[8]):
336 return True
338 return "LD" in self._sections[WritingSynthesisReport]._cells
340 @readonly
341 def Latches(self) -> List[VivadoMessage]:
342 """
343 Read-only property to access a list of Vivado output messages for inferred latches.
345 :returns: A list of Vivado messages for interred latches.
347 .. note::
349 This returns ``[Synth 8-327]`` messages.
351 .. code-block::
353 WARNING: [Synth 8-327] inferring latch for variable 'Q_reg'
354 """
355 if 8 in self._messagesByID:
356 if 327 in (synthMessages := self._messagesByID[8]):
357 return [message for message in synthMessages[327]]
359 return []
361 @readonly
362 def HasBlackboxes(self) -> bool:
363 """
364 Read-only property returning if the design contains black-boxes.
366 :returns: True, if the design contains black-boxes.
367 """
368 return len(self._sections[WritingSynthesisReport]._blackboxes) > 0
370 @readonly
371 def Blackboxes(self) -> Dict[str, int]:
372 """
373 Read-only property to access the dictionary of found blackbox statistics.
375 :returns: The dictionary of found blackbox statistics.
376 """
377 return self._sections[WritingSynthesisReport]._blackboxes
379 @readonly
380 def Cells(self) -> Dict[str, int]:
381 """
382 Read-only property to access the dictionary of synthesized cell statistics.
384 :returns: The dictionary of used cell statistics.
385 """
386 return self._sections[WritingSynthesisReport]._cells
388 @readonly
389 def VHDLReportMessages(self) -> List[VHDLReportMessage]:
390 """
391 Read-only property to access a list of Vivado output messages generated by VHDL report statement.
393 :returns: A list of VHDL report statement outputs.
395 .. note::
397 This returns ``[Synth 8-6031]`` messages.
399 .. code-block::
401 INFO: [Synth 8-6031] RTL report: "TimingToCycles(time, freq): period=10.000000 ns -- 0.000000 fs" [C:/[...]/StopWatch/src/Utilities.pkg.vhdl:118]
402 """
403 if 8 in self._messagesByID:
404 if 6031 in (synthMessages := self._messagesByID[8]):
405 return [message for message in synthMessages[6031]]
407 return []
409 @readonly
410 def VHDLAssertMessages(self) -> List[VHDLReportMessage]:
411 """
412 Read-only property to access a list of Vivado output messages generated by VHDL assert statement.
414 :returns: A list of VHDL assert statement outputs.
416 .. note::
418 This returns ``[Synth 8-63]`` messages.
420 .. code-block::
422 INFO: [Synth 8-63] RTL assertion: "CLOCK_FREQ: 100.000000 ns" [C:/[...]/StopWatch/src/Debouncer.vhdl:28]
423 """
424 if 8 in self._messagesByID:
425 if 63 in (synthMessages := self._messagesByID[8]):
426 return [message for message in synthMessages[63]]
428 return []
430 def SectionDetector(self, line: Line) -> Generator[Line, Line, None]:
431 if not (isinstance(line, VivadoTclCommand) and line._command == self._TCL_COMMAND): 431 ↛ 432line 431 didn't jump to line 432 because the condition on line 431 was never true
432 raise ProcessorException() # FIXME: add exception message
434 activeParsers: List[Section] = list(self._sections.values())
436 rtlElaboration = self._sections[RTLElaboration]
437 # constraintValidation = self._sections[ConstraintValidation]
439 line = yield line
440 if line == "Starting synth_design": 440 ↛ 443line 440 didn't jump to line 443 because the condition on line 440 was always true
441 line._kind = LineKind.Verbose
442 else:
443 raise ProcessorException() # FIXME: add exception message
445 line = yield line
446 while True:
447 while True:
448 if line._kind is LineKind.Empty:
449 line = yield line
450 continue
451 elif isinstance(line, VivadoMessage):
452 self._AddMessage(line)
453 elif line.StartsWith("Start "):
454 for parser in activeParsers: # type: Section 454 ↛ 460line 454 didn't jump to line 460 because the loop on line 454 didn't complete
455 if line.StartsWith(parser._START):
456 line = next(section := parser.Generator(line))
457 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter
458 break
459 else:
460 WarningCollector.Raise(UnknownSection(f"Unknown section: '{line!r}'", line))
461 ex = Exception(f"How to recover from here? Unknown section: '{line!r}'")
462 # ex.add_note(f"Current task: start pattern='{self._task}'")
463 ex.add_note(f"Current cmd: {self}")
464 raise ex
465 break
466 elif line.StartsWith("Starting "):
467 if line.StartsWith(rtlElaboration._START): 467 ↛ 487line 467 didn't jump to line 487 because the condition on line 467 was always true
468 parser = rtlElaboration
469 line = next(section := parser.Generator(line))
470 line._previousLine._kind = LineKind.SectionStart | LineKind.SectionDelimiter
471 break
472 elif line.StartsWith(self._TCL_COMMAND):
473 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 473 ↛ 487line 473 didn't jump to line 487 because the condition on line 473 was always true
474 line._kind |= LineKind.Success
476 # FIXME: use similar style like for _TIME
477 line = yield line
478 lastLine = yield line
479 return lastLine
480 elif line.StartsWith("Finished RTL Optimization Phase"):
481 line._kind = LineKind.PhaseEnd
482 line._previousLine._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter
483 elif line.StartsWith("----"):
484 if LineKind.Phase in line._previousLine._kind:
485 line._kind = LineKind.PhaseEnd | LineKind.PhaseDelimiter
487 line = yield line
489 line = yield line
491 while True:
492 if line.StartsWith("Finished"):
493 l = line[9:]
494 if not (l.startswith("Flattening") or l.startswith("Final")):
495 line = yield section.send(line)
496 break
498 if isinstance(line, VivadoMessage):
499 self._AddMessage(line)
501 line = yield section.send(line)
503 line = yield section.send(line)
505 activeParsers.remove(parser)
508@export
509class LinkDesign(Command):
510 """
511 A Vivado command output parser for ``link_design``.
512 """
513 _TCL_COMMAND: ClassVar[str] = "link_design"
514 _TIME: ClassVar[str] = "Time (s):"
516 _ParsingXDCFile_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\]$""")
517 _FinishedParsingXDCFile_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\]$""")
518 _ParsingXDCFileForCell_Pattern = re_compile(r"""^Parsing XDC File \[(.*)\] for cell '(.*)'$""")
519 _FinishedParsingXDCFileForCell_Pattern = re_compile(r"""^Finished Parsing XDC File \[(.*)\] for cell '(.*)'$""")
521 _commonXDCFiles: Dict[Path, List[VivadoMessage]]
522 _perCellXDCFiles: Dict[Path, Dict[str, List[VivadoMessage]]]
524 def __init__(self, processor: "Processor") -> None:
525 super().__init__(processor)
527 self._commonXDCFiles = {}
528 self._perCellXDCFiles = {}
530 @readonly
531 def CommonXDCFiles(self) -> Dict[Path, List[VivadoMessage]]:
532 return self._commonXDCFiles
534 @readonly
535 def PerCellXDCFiles(self) -> Dict[Path, Dict[str, List[VivadoMessage]]]:
536 return self._perCellXDCFiles
538 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, Line]:
539 line = yield from self._CommandStart(line)
541 end = f"{self._TCL_COMMAND} "
542 while True:
543 if line._kind is LineKind.Empty:
544 line = yield line
545 continue
546 elif isinstance(line, VivadoMessage):
547 self._AddMessage(line)
548 elif (match := self._ParsingXDCFile_Pattern.match(line._message)) is not None:
549 line._kind = LineKind.Normal
551 path = Path(match[1])
552 self._commonXDCFiles[path] = (messages := [])
554 line = yield line
555 while True:
556 if line._kind is LineKind.Empty: 556 ↛ 557line 556 didn't jump to line 557 because the condition on line 556 was never true
557 line = yield line
558 continue
559 elif isinstance(line, VivadoMessage):
560 self._AddMessage(line)
561 messages.append(line)
562 elif (match := self._FinishedParsingXDCFile_Pattern.match(line._message)) is not None and path == Path(match[1]):
563 line._kind = LineKind.Normal
564 break
565 elif line.StartsWith("Finished Parsing XDC File"): 565 ↛ 566line 565 didn't jump to line 566 because the condition on line 565 was never true
566 line._kind = LineKind.ProcessorError
567 break
568 elif line.StartsWith(end): 568 ↛ 569line 568 didn't jump to line 569 because the condition on line 568 was never true
569 break
571 line = yield line
572 elif (match := self._ParsingXDCFileForCell_Pattern.match(line._message)) is not None:
573 line._kind = LineKind.Normal
575 path = Path(match[1])
576 cell = match[2]
577 if path in self._perCellXDCFiles:
578 self._perCellXDCFiles[path][cell] = (messages := [])
579 else:
580 self._perCellXDCFiles[path] = {cell: (messages := [])}
582 line = yield line
583 while True:
584 if line._kind is LineKind.Empty: 584 ↛ 585line 584 didn't jump to line 585 because the condition on line 584 was never true
585 line = yield line
586 continue
587 elif isinstance(line, VivadoMessage):
588 self._AddMessage(line)
589 messages.append(line)
590 elif (match := self._FinishedParsingXDCFileForCell_Pattern.match(line._message)) is not None and path == Path(match[1]) and cell == match[2]:
591 line._kind = LineKind.Normal
592 break
593 elif line.StartsWith("Finished Parsing XDC File"): 593 ↛ 594line 593 didn't jump to line 594 because the condition on line 593 was never true
594 line._kind = LineKind.ProcessorError
595 break
596 elif line.StartsWith(end): 596 ↛ 597line 596 didn't jump to line 597 because the condition on line 596 was never true
597 break
599 line = yield line
601 if line.StartsWith(end):
602 nextLine = yield from self._CommandFinish(line)
603 return nextLine
605 line = yield line
608@export
609class OptimizeDesign(CommandWithTasks):
610 """
611 A Vivado command output parser for ``opt_design``.
612 """
613 _TCL_COMMAND: ClassVar[str] = "opt_design"
614 _TIME: ClassVar[str] = None
616 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
617 DRCTask,
618 CacheTimingInformationTask,
619 LogicOptimizationTask,
620 PowerOptimizationTask,
621 FinalCleanupTask,
622 NetlistObfuscationTask
623 )
625 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, Line]:
626 line = yield from self._CommandStart(line)
628 activeParsers: List[Task] = list(self._tasks.values())
630 while True:
631 while True:
632 if line._kind is LineKind.Empty:
633 line = yield line
634 continue
635 elif isinstance(line, VivadoMessage):
636 self._AddMessage(line)
637 elif line.StartsWith("Starting ") and not line.StartsWith("Starting Connectivity Check Task"):
638 for parser in activeParsers: # type: Section 638 ↛ 643line 638 didn't jump to line 643 because the loop on line 638 didn't complete
639 if line.StartsWith(parser._START):
640 line = yield next(task := parser.Generator(line))
641 break
642 else:
643 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
644 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
645 # ex.add_note(f"Current task: start pattern='{self._task}'")
646 ex.add_note(f"Current cmd: {self}")
647 raise ex
648 break
649 elif line.StartsWith(self._TCL_COMMAND):
650 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 650 ↛ 659line 650 didn't jump to line 659 because the condition on line 650 was always true
651 line._kind |= LineKind.Success
653 # FIXME: use similar style like for _TIME
654 line = yield line
655 lastLine = yield line
656 return lastLine
657 # line._kind = LineKind.Unprocessed
659 line = yield line
661 while True:
662 # if line.StartsWith("Ending"):
663 # line = yield task.send(line)
664 # break
666 if isinstance(line, VivadoMessage):
667 self._AddMessage(line)
669 try:
670 line = yield task.send(line)
671 except StopIteration as ex:
672 task = None
673 line = ex.value
675 if isinstance(line, VivadoMessage):
676 line = yield line
678 break
680 if task is not None: 680 ↛ 681line 680 didn't jump to line 681 because the condition on line 680 was never true
681 line = yield task.send(line)
683 activeParsers.remove(parser)
686@export
687class PlaceDesign(CommandWithTasks):
688 """
689 A Vivado command output parser for ``place_design``.
690 """
691 _TCL_COMMAND: ClassVar[str] = "place_design"
692 _TIME: ClassVar[str] = None
694 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
695 PlacerTask,
696 )
698 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, Line]:
699 line = yield from self._CommandStart(line)
701 activeParsers: List[Task] = list(self._tasks.values())
703 while True:
704 while True:
705 if line._kind is LineKind.Empty:
706 line = yield line
707 continue
708 elif isinstance(line, VivadoMessage):
709 self._AddMessage(line)
710 elif line.StartsWith("Starting "):
711 for parser in activeParsers: # type: Section 711 ↛ 716line 711 didn't jump to line 716 because the loop on line 711 didn't complete
712 if line.StartsWith(parser._START): 712 ↛ 711line 712 didn't jump to line 711 because the condition on line 712 was always true
713 line = yield next(task := parser.Generator(line))
714 break
715 else:
716 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
717 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
718 # ex.add_note(f"Current task: start pattern='{self._task}'")
719 ex.add_note(f"Current cmd: {self}")
720 raise ex
721 break
722 elif line.StartsWith(self._TCL_COMMAND):
723 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 723 ↛ 732line 723 didn't jump to line 732 because the condition on line 723 was always true
724 line._kind |= LineKind.Success
726 # FIXME: use similar style like for _TIME
727 line = yield line
728 lastLine = yield line
729 return lastLine
730 # line._kind = LineKind.Unprocessed
732 line = yield line
734 while True:
735 # if line.StartsWith("Ending"):
736 # line = yield task.send(line)
737 # break
739 if isinstance(line, VivadoMessage):
740 self._AddMessage(line)
742 try:
743 line = yield task.send(line)
744 except StopIteration as ex:
745 task = None
746 line = ex.value
748 if isinstance(line, VivadoMessage):
749 line = yield line
751 break
753 if task is not None: 753 ↛ 754line 753 didn't jump to line 754 because the condition on line 753 was never true
754 line = yield task.send(line)
756 activeParsers.remove(parser)
759@export
760class PhysicalOptimizeDesign(CommandWithTasks):
761 """
762 A Vivado command output parser for ``phy_opt_design``.
763 """
764 _TCL_COMMAND: ClassVar[str] = "phys_opt_design"
765 _TIME: ClassVar[str] = None
767 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
768 InitialUpdateTimingTask,
769 PhysicalSynthesisTask
770 )
772 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, Line]:
773 line = yield from self._CommandStart(line)
775 activeParsers: List[Task] = list(self._tasks.values())
777 while True:
778 while True:
779 if line._kind is LineKind.Empty:
780 line = yield line
781 continue
782 elif isinstance(line, VivadoMessage):
783 self._AddMessage(line)
784 elif line.StartsWith("Starting "):
785 for parser in activeParsers: # type: Section 785 ↛ 790line 785 didn't jump to line 790 because the loop on line 785 didn't complete
786 if line.StartsWith(parser._START): 786 ↛ 785line 786 didn't jump to line 785 because the condition on line 786 was always true
787 line = yield next(task := parser.Generator(line))
788 break
789 else:
790 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
791 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
792 # ex.add_note(f"Current task: start pattern='{self._task}'")
793 ex.add_note(f"Current cmd: {self}")
794 raise ex
795 break
796 elif line.StartsWith(self._TCL_COMMAND):
797 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 797 ↛ 806line 797 didn't jump to line 806 because the condition on line 797 was always true
798 line._kind |= LineKind.Success
800 # FIXME: use similar style like for _TIME
801 line = yield line
802 lastLine = yield line
803 return lastLine
804 # line._kind = LineKind.Unprocessed
806 line = yield line
808 while True:
809 # if line.StartsWith("Ending"):
810 # line = yield task.send(line)
811 # break
813 if isinstance(line, VivadoMessage):
814 self._AddMessage(line)
816 try:
817 line = yield task.send(line)
818 except StopIteration as ex:
819 task = None
820 line = ex.value
822 if isinstance(line, VivadoMessage): 822 ↛ 825line 822 didn't jump to line 825 because the condition on line 822 was always true
823 line = yield line
825 break
827 if task is not None: 827 ↛ 828line 827 didn't jump to line 828 because the condition on line 827 was never true
828 line = yield task.send(line)
830 activeParsers.remove(parser)
833@export
834class RouteDesign(CommandWithTasks):
835 """
836 A Vivado command output parser for ``route_design``.
837 """
838 _TCL_COMMAND: ClassVar[str] = "route_design"
839 _TIME: ClassVar[str] = "Time (s):"
841 _PARSERS: ClassVar[Tuple[Type[Task], ...]] = (
842 RoutingTask,
843 )
845 def SectionDetector(self, line: Line) -> Generator[Union[Line, ProcessorException], Line, Line]:
846 line = yield from self._CommandStart(line)
848 activeParsers: List[Task] = list(self._tasks.values())
850 while True:
851 while True:
852 if line._kind is LineKind.Empty:
853 line = yield line
854 continue
855 elif isinstance(line, VivadoMessage):
856 self._AddMessage(line)
857 elif line.StartsWith("Starting "):
858 for parser in activeParsers: # type: Section 858 ↛ 863line 858 didn't jump to line 863 because the loop on line 858 didn't complete
859 if line.StartsWith(parser._START): 859 ↛ 858line 859 didn't jump to line 858 because the condition on line 859 was always true
860 line = yield next(task := parser.Generator(line))
861 break
862 else:
863 WarningCollector.Raise(UnknownTask(f"Unknown task: '{line!r}'", line))
864 ex = Exception(f"How to recover from here? Unknown task: '{line!r}'")
865 # ex.add_note(f"Current task: start pattern='{self._task}'")
866 ex.add_note(f"Current cmd: {self}")
867 raise ex
868 break
869 elif line.StartsWith(self._TCL_COMMAND):
870 if line[len(self._TCL_COMMAND) + 1:].startswith("completed successfully"): 870 ↛ 879line 870 didn't jump to line 879 because the condition on line 870 was always true
871 line._kind |= LineKind.Success
873 # FIXME: use similar style like for _TIME
874 line = yield line
875 lastLine = yield line
876 return lastLine
877 # line._kind = LineKind.Unprocessed
879 line = yield line
881 while True:
882 # if line.StartsWith("Ending"):
883 # line = yield task.send(line)
884 # break
886 if isinstance(line, VivadoMessage):
887 self._AddMessage(line)
889 try:
890 line = yield task.send(line)
891 except StopIteration as ex:
892 task = None
893 line = ex.value
895 if isinstance(line, VivadoMessage): 895 ↛ 896line 895 didn't jump to line 896 because the condition on line 895 was never true
896 line = yield line
898 break
900 if task is not None: 900 ↛ 901line 900 didn't jump to line 901 because the condition on line 900 was never true
901 line = yield task.send(line)
903 activeParsers.remove(parser)
906@export
907class WriteBitstream(Command):
908 """
909 A Vivado command output parser for ``write_bitstream``.
910 """
911 _TCL_COMMAND: ClassVar[str] = "write_bitstream"
912 _TIME: ClassVar[str] = "Time (s):"
915@export
916class ReportDRC(Command):
917 """
918 A Vivado command output parser for ``report_drc``.
919 """
920 _TCL_COMMAND: ClassVar[str] = "report_drc"
921 _TIME: ClassVar[str] = "Time (s):"
924@export
925class ReportMethodology(Command):
926 """
927 A Vivado command output parser for ``report_methodology``.
928 """
929 _TCL_COMMAND: ClassVar[str] = "report_methodology"
930 _TIME: ClassVar[str] = None
933@export
934class ReportPower(Command):
935 """
936 A Vivado command output parser for ``report_power``.
937 """
938 _TCL_COMMAND: ClassVar[str] = "report_power"
939 _TIME: ClassVar[str] = None