Coverage for pyEDAA/OutputFilter/Xilinx/Synthesis.py: 82%
392 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-27 22:12 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-27 22:12 +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"""A filtering anc classification processor for AMD/Xilinx Vivado Synthesis outputs."""
32from pathlib import Path
33from re import compile as re_compile
34from typing import ClassVar, List, Callable, Dict, Type, Iterator, Union, Generator
36from pyTooling.Decorators import export, readonly
37from pyTooling.MetaClasses import mustoverride
38from pyTooling.Common import firstValue
40from pyEDAA.OutputFilter.Xilinx import VivadoMessage, ProcessingState, Parser, Preamble, BaseProcessor, BaseDocument, \
41 Line, ProcessorException, LineKind, VivadoInfoMessage, VHDLAssertionMessage, VHDLReportMessage
43TIME_MEMORY_PATTERN = re_compile(r"""Time \(s\): cpu = (\d{2}:\d{2}:\d{2}) ; elapsed = (\d{2}:\d{2}:\d{2}) . Memory \(MB\): peak = (\d+\.\d+) ; gain = (\d+\.\d+)""")
46@export
47class Initialize(Parser):
48 _command: str
49 _license: VivadoMessage
51 def ParseLine(self, lineNumber: int, line: str) -> bool:
52 pass
55@export
56class Section(Parser):
57 # _START: ClassVar[str]
58 # _FINISH: ClassVar[str]
60 _duration: float
62 def __init__(self, processor: "Processor"):
63 super().__init__(processor)
65 self._duration = 0.0
67 @readonly
68 def Duration(self) -> float:
69 return self._duration
71 def _SectionStart(self, line: Line) -> Generator[Line, Line, Line]:
72 line._kind = LineKind.SectionStart
74 line = yield line
75 if line._message.startswith("----"): 75 ↛ 78line 75 didn't jump to line 78 because the condition on line 75 was always true
76 line._kind = LineKind.SectionStart | LineKind.SectionDelimiter
77 else:
78 line._kind |= LineKind.ProcessorError
80 nextLine = yield line
81 return nextLine
83 def _SectionFinish(self, line: Line) -> Generator[Line, Line, None]:
84 if line._message.startswith(self._FINISH): 84 ↛ 87line 84 didn't jump to line 87 because the condition on line 84 was always true
85 line._kind = LineKind.SectionEnd
86 else:
87 line._kind |= LineKind.ProcessorError
89 line = yield line
90 if line._message.startswith("----"): 90 ↛ 93line 90 didn't jump to line 93 because the condition on line 90 was always true
91 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter | LineKind.Last
92 else:
93 line._kind |= LineKind.ProcessorError
95 check = yield line
96 if check is not None:
97 raise Exception()
99 # @mustoverride
100 # def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
101 # if len(line) == 0:
102 # return ProcessingState.EmptyLine
103 # elif line.startswith("----"):
104 # return ProcessingState.DelimiterLine
105 # elif line.startswith(self._START):
106 # return ProcessingState.Skipped
107 # elif line.startswith(self._FINISH):
108 # l = line[len(self._FINISH):]
109 # if (match := TIME_MEMORY_PATTERN.match(l)) is not None:
110 # # cpuParts = match[1].split(":")
111 # elapsedParts = match[2].split(":")
112 # # peakMemory = float(match[3])
113 # # gainMemory = float(match[4])
114 # self._duration = int(elapsedParts[0]) * 3600 + int(elapsedParts[1]) * 60 + int(elapsedParts[2])
115 #
116 # return ProcessingState.Skipped | ProcessingState.Last
117 # elif line.startswith("Start") or line.startswith("Starting"):
118 # print(f"ERROR: didn't find finish\n {line}")
119 # return ProcessingState.Reprocess
120 #
121 # return ProcessingState.Skipped
123 def Generator(self, line: Line) -> Generator[Line, Line, Line]:
124 line = yield from self._SectionStart(line)
126 while line is not None: 126 ↛ 137line 126 didn't jump to line 137 because the condition on line 126 was always true
127 rawMessage = line._message
129 if rawMessage.startswith("----"):
130 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
131 break
132 else:
133 line._kind = LineKind.Verbose
135 line = yield line
137 line = yield line
138 nextLine = yield from self._SectionFinish(line)
139 return nextLine
142@export
143class SubSection(Section):
144 def _SectionStart(self, line: Line) -> Generator[Line, Line, Line]:
145 line._kind = LineKind.SubSectionStart
147 line = yield line
148 if line._message.startswith("----"): 148 ↛ 151line 148 didn't jump to line 151 because the condition on line 148 was always true
149 line._kind = LineKind.SubSectionStart | LineKind.SubSectionDelimiter
150 else:
151 line._kind |= LineKind.ProcessorError
153 nextLine = yield line
154 return nextLine
156 def _SectionFinish(self, line: Line) -> Generator[Line, Line, None]:
157 if line._message.startswith(self._FINISH): 157 ↛ 160line 157 didn't jump to line 160 because the condition on line 157 was always true
158 line._kind = LineKind.SubSectionEnd
159 else:
160 line._kind |= LineKind.ProcessorError
162 line = yield line
163 if line._message.startswith("----"): 163 ↛ 166line 163 didn't jump to line 166 because the condition on line 163 was always true
164 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter | LineKind.Last
165 else:
166 line._kind |= LineKind.ProcessorError
168 nextLine = yield line
169 return nextLine
171 def Generator(self, line: Line) -> Generator[Line, Line, Line]:
172 line = yield from self._SectionStart(line)
174 while line is not None: 174 ↛ 185line 174 didn't jump to line 185 because the condition on line 174 was always true
175 rawMessage = line._message
177 if rawMessage.startswith("----"): 177 ↛ 181line 177 didn't jump to line 181 because the condition on line 177 was always true
178 line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter
179 break
180 else:
181 line._kind = LineKind.Verbose
183 line = yield line
185 line = yield line
186 nextLine = yield from self._SectionFinish(line)
188 return nextLine
191@export
192class RTLElaboration(Section):
193 _START: ClassVar[str] = "Starting RTL Elaboration : "
194 _FINISH: ClassVar[str] = "Finished RTL Elaboration : "
196 def Generator(self, line: Line) -> Generator[Line, Line, Line]:
197 line = yield from self._SectionStart(line)
199 while line is not None: 199 ↛ 225line 199 didn't jump to line 225 because the condition on line 199 was always true
200 rawMessage = line._message
202 if isinstance(line, VivadoInfoMessage):
203 if line._toolID == 8: 203 ↛ 217line 203 didn't jump to line 217 because the condition on line 203 was always true
204 if line._messageKindID == 63: # VHDL assert statement
205 newLine = VHDLAssertionMessage.Convert(line)
206 if newLine is None: 206 ↛ 207line 206 didn't jump to line 207 because the condition on line 206 was never true
207 print(f"Convert error at: {line}")
208 else:
209 line = newLine
210 elif line._messageKindID == 6031: # VHDL report statement
211 newLine = VHDLReportMessage.Convert(line)
212 if newLine is None: 212 ↛ 213line 212 didn't jump to line 213 because the condition on line 212 was never true
213 print(f"Convert error at: {line}")
214 else:
215 line = newLine
217 if rawMessage.startswith("----"):
218 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
219 break
220 elif not isinstance(line, VivadoMessage):
221 line._kind = LineKind.Verbose
223 line = yield line
225 line = yield line
226 nextLine = yield from self._SectionFinish(line)
227 return nextLine
230@export
231class HandlingCustomAttributes1(Section):
232 _START: ClassVar[str] = "Start Handling Custom Attributes"
233 _FINISH: ClassVar[str] = "Finished Handling Custom Attributes : "
236@export
237class ConstraintValidation(Section):
238 _START: ClassVar[str] = "Finished RTL Optimization Phase 1"
239 _FINISH: ClassVar[str] = "Finished Constraint Validation : "
242@export
243class LoadingPart(Section):
244 _START: ClassVar[str] = "Start Loading Part and Timing Information"
245 _FINISH: ClassVar[str] = "Finished Loading Part and Timing Information : "
247 _part: str
249 def __init__(self, processor: "Processor"):
250 super().__init__(processor)
252 self._part = None
254 @readonly
255 def Part(self) -> str:
256 return self._part
258 def Generator(self, line: Line) -> Generator[Line, Line, Line]:
259 line = yield from self._SectionStart(line)
261 while line is not None: 261 ↛ 274line 261 didn't jump to line 274 because the condition on line 261 was always true
262 rawMessage = line._message
264 if line._message.startswith("Loading part: "):
265 line._kind = LineKind.Normal
266 self._part = line._message[14:].strip()
268 if rawMessage.startswith("----"):
269 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
270 break
272 line = yield line
274 line = yield line
275 nextLine = yield from self._SectionFinish(line)
276 return nextLine
278@export
279class ApplySetProperty(Section):
280 _START: ClassVar[str] = "Start Applying 'set_property' XDC Constraints"
281 _FINISH: ClassVar[str] = "Finished applying 'set_property' XDC Constraints : "
284@export
285class RTLComponentStatistics(Section):
286 _START: ClassVar[str] = "Start RTL Component Statistics"
287 _FINISH: ClassVar[str] = "Finished RTL Component Statistics"
289 def Generator(self, line: Line) -> Generator[Line, Line, Line]:
290 line = yield from self._SectionStart(line)
292 while line is not None: 292 ↛ 303line 292 didn't jump to line 303 because the condition on line 292 was always true
293 rawMessage = line._message
295 if rawMessage.startswith("----"):
296 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
297 break
298 else:
299 line._kind = LineKind.Verbose
301 line = yield line
303 line = yield line
304 nextLine = yield from self._SectionFinish(line)
305 return nextLine
308@export
309class PartResourceSummary(Section):
310 _START: ClassVar[str] = "Start Part Resource Summary"
311 _FINISH: ClassVar[str] = "Finished Part Resource Summary"
314@export
315class CrossBoundaryAndAreaOptimization(Section):
316 _START: ClassVar[str] = "Start Cross Boundary and Area Optimization"
317 _FINISH: ClassVar[str] = "Finished Cross Boundary and Area Optimization : "
320@export
321class ApplyingXDCTimingConstraints(Section):
322 _START: ClassVar[str] = "Start Applying XDC Timing Constraints"
323 _FINISH: ClassVar[str] = "Finished Applying XDC Timing Constraints : "
326@export
327class TimingOptimization(Section):
328 _START: ClassVar[str] = "Start Timing Optimization"
329 _FINISH: ClassVar[str] = "Finished Timing Optimization : "
332@export
333class TechnologyMapping(Section):
334 _START: ClassVar[str] = "Start Technology Mapping"
335 _FINISH: ClassVar[str] = "Finished Technology Mapping : "
338@export
339class FlatteningBeforeIOInsertion(SubSection):
340 _START: ClassVar[str] = "Start Flattening Before IO Insertion"
341 _FINISH: ClassVar[str] = "Finished Flattening Before IO Insertion"
344@export
345class FinalNetlistCleanup(SubSection):
346 _START: ClassVar[str] = "Start Final Netlist Cleanup"
347 _FINISH: ClassVar[str] = "Finished Final Netlist Cleanup"
350@export
351class IOInsertion(Section):
352 _START: ClassVar[str] = "Start IO Insertion"
353 _FINISH: ClassVar[str] = "Finished IO Insertion : "
355 def Generator(self, line: Line) -> Generator[Line, Line, Line]:
356 flattening = FlatteningBeforeIOInsertion(None)
357 netlist = FinalNetlistCleanup(None)
359 line = yield from self._SectionStart(line)
360 if line._message.startswith("----"): 360 ↛ 363line 360 didn't jump to line 363 because the condition on line 360 was always true
361 line._kind = LineKind.SectionStart | LineKind.SectionDelimiter
362 else:
363 line._kind |= LineKind.ProcessorError
365 line = yield line
366 if line._message.startswith("Start "): 366 ↛ 369line 366 didn't jump to line 369 because the condition on line 366 was always true
367 line = yield from flattening.Generator(line)
369 if line._message.startswith("----"): 369 ↛ 372line 369 didn't jump to line 372 because the condition on line 369 was always true
370 line._kind = LineKind.SubSectionStart | LineKind.SectionDelimiter
371 else:
372 line._kind |= LineKind.ProcessorError
374 line = yield line
375 if line._message.startswith("Start "): 375 ↛ 378line 375 didn't jump to line 378 because the condition on line 375 was always true
376 line = yield from netlist.Generator(line)
378 if line._message.startswith("----"): 378 ↛ 381line 378 didn't jump to line 381 because the condition on line 378 was always true
379 line._kind = LineKind.SubSectionEnd | LineKind.SectionDelimiter
380 else:
381 line._kind |= LineKind.ProcessorError
383 line = yield line
384 nextLine = yield from self._SectionFinish(line)
385 return nextLine
387@export
388class RenamingGeneratedInstances(Section):
389 _START: ClassVar[str] = "Start Renaming Generated Instances"
390 _FINISH: ClassVar[str] = "Finished Renaming Generated Instances : "
393@export
394class RebuildingUserHierarchy(Section):
395 _START: ClassVar[str] = "Start Rebuilding User Hierarchy"
396 _FINISH: ClassVar[str] = "Finished Rebuilding User Hierarchy : "
399@export
400class RenamingGeneratedPorts(Section):
401 _START: ClassVar[str] = "Start Renaming Generated Ports"
402 _FINISH: ClassVar[str] = "Finished Renaming Generated Ports : "
405@export
406class HandlingCustomAttributes2(Section):
407 _START: ClassVar[str] = "Start Handling Custom Attributes"
408 _FINISH: ClassVar[str] = "Finished Handling Custom Attributes : "
411@export
412class RenamingGeneratedNets(Section):
413 _START: ClassVar[str] = "Start Renaming Generated Nets"
414 _FINISH: ClassVar[str] = "Finished Renaming Generated Nets : "
417@export
418class WritingSynthesisReport(Section):
419 _START: ClassVar[str] = "Start Writing Synthesis Report"
420 _FINISH: ClassVar[str] = "Finished Writing Synthesis Report : "
422 _blackboxes: Dict[str, int]
423 _cells: Dict[str, int]
425 def __init__(self, processor: "Processor"):
426 super().__init__(processor)
428 self._blackboxes = {}
429 self._cells = {}
431 @readonly
432 def Cells(self) -> Dict[str, int]:
433 return self._cells
435 @readonly
436 def Blackboxes(self) -> Dict[str, int]:
437 return self._blackboxes
439 def _BlackboxesGenerator(self, line: Line) -> Generator[Line, Line, Line]:
440 if line._message.startswith("+-"): 440 ↛ 443line 440 didn't jump to line 443 because the condition on line 440 was always true
441 line._kind = LineKind.TableFrame
442 else:
443 line._kind = LineKind.ProcessorError
445 line = yield line
446 if line._message.startswith("| "): 446 ↛ 449line 446 didn't jump to line 449 because the condition on line 446 was always true
447 line._kind = LineKind.TableHeader
448 else:
449 line._kind = LineKind.ProcessorError
451 line = yield line
452 if line._message.startswith("+-"): 452 ↛ 455line 452 didn't jump to line 455 because the condition on line 452 was always true
453 line._kind = LineKind.TableFrame
454 else:
455 line._kind = LineKind.ProcessorError
457 line = yield line
458 while line is not None: 458 ↛ 472line 458 didn't jump to line 472 because the condition on line 458 was always true
459 if line._message.startswith("| "): 459 ↛ 460line 459 didn't jump to line 460 because the condition on line 459 was never true
460 line._kind = LineKind.TableRow
462 columns = line._message.strip("|").split("|")
463 self._blackboxes[columns[1].strip()] = int(columns[2].strip())
464 elif line._message.startswith("+-"): 464 ↛ 468line 464 didn't jump to line 468 because the condition on line 464 was always true
465 line._kind = LineKind.TableFrame
466 break
467 else:
468 line._kind = LineKind.ProcessorError
470 line = yield line
472 nextLine = yield line
473 return nextLine
475 def _CellGenerator(self, line: Line) -> Generator[Line, Line, Line]:
476 if line._message.startswith("+-"): 476 ↛ 479line 476 didn't jump to line 479 because the condition on line 476 was always true
477 line._kind = LineKind.TableFrame
478 else:
479 line._kind = LineKind.ProcessorError
481 line = yield line
482 if line._message.startswith("| "): 482 ↛ 485line 482 didn't jump to line 485 because the condition on line 482 was always true
483 line._kind = LineKind.TableHeader
484 else:
485 line._kind = LineKind.ProcessorError
487 line = yield line
488 if line._message.startswith("+-"): 488 ↛ 491line 488 didn't jump to line 491 because the condition on line 488 was always true
489 line._kind = LineKind.TableFrame
490 else:
491 line._kind = LineKind.ProcessorError
493 line = yield line
494 while line is not None: 494 ↛ 508line 494 didn't jump to line 508 because the condition on line 494 was always true
495 if line._message.startswith("|"):
496 line._kind = LineKind.TableRow
498 columns = line._message.strip("|").split("|")
499 self._cells[columns[1].strip()] = int(columns[2].strip())
500 elif line._message.startswith("+-"): 500 ↛ 504line 500 didn't jump to line 504 because the condition on line 500 was always true
501 line._kind = LineKind.TableFrame
502 break
503 else:
504 line._kind = LineKind.ProcessorError
506 line = yield line
508 nextLine = yield line
509 return nextLine
511 def Generator(self, line: Line) -> Generator[Line, Line, Line]:
512 line = yield from self._SectionStart(line)
514 while line is not None: 514 ↛ 534line 514 didn't jump to line 534 because the condition on line 514 was always true
515 rawMessage = line._message
517 if rawMessage.startswith("Report BlackBoxes:"):
518 line._kind = LineKind.ParagraphHeadline
519 line = yield line
520 line = yield from self._BlackboxesGenerator(line)
521 elif rawMessage.startswith("Report Cell Usage:"):
522 line._kind = LineKind.ParagraphHeadline
523 line = yield line
524 line = yield from self._CellGenerator(line)
525 elif rawMessage.startswith("----"):
526 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
527 break
528 elif not isinstance(line, VivadoMessage): 528 ↛ 531line 528 didn't jump to line 531 because the condition on line 528 was always true
529 line._kind = LineKind.Verbose
530 line = yield line
531 elif line._kind is LineKind.Empty:
532 line = yield line
534 line = yield line
535 nextLine = yield from self._SectionFinish(line)
536 return nextLine
539PARSERS = (
540 Preamble,
541 RTLElaboration,
542 HandlingCustomAttributes1,
543 ConstraintValidation,
544 LoadingPart,
545 ApplySetProperty,
546 RTLComponentStatistics,
547 PartResourceSummary,
548 CrossBoundaryAndAreaOptimization,
549 ApplyingXDCTimingConstraints,
550 TimingOptimization,
551 TechnologyMapping,
552 IOInsertion,
553 FlatteningBeforeIOInsertion,
554 FinalNetlistCleanup,
555 RenamingGeneratedInstances,
556 RebuildingUserHierarchy,
557 RenamingGeneratedPorts,
558 HandlingCustomAttributes2,
559 RenamingGeneratedNets,
560 WritingSynthesisReport,
561)
564Parsers = Union[*PARSERS]
567@export
568class Processor(BaseDocument):
569 _parsers: Dict[Type[Parser], Parsers]
571 def __init__(self, synthesisLogfile: Path):
572 super().__init__(synthesisLogfile)
574 self._parsers = {p: p(self) for p in PARSERS}
576 @readonly
577 def HasLatches(self) -> bool:
578 if (8 in self._messagesByID) and (327 in self._messagesByID[8]):
579 return True
581 return "LD" in self._parsers[WritingSynthesisReport]._cells
583 @readonly
584 def Latches(self) -> Iterator[VivadoMessage]:
585 try:
586 yield from iter(self._messagesByID[8][327])
587 except KeyError:
588 yield from ()
590 @readonly
591 def HasBlackboxes(self) -> bool:
592 return len(self._parsers[WritingSynthesisReport]._blackboxes) > 0
594 @readonly
595 def Cells(self) -> Dict[str, int]:
596 return self._parsers[WritingSynthesisReport]._cells
598 def __getitem__(self, item: Type[Parser]) -> Parsers:
599 return self._parsers[item]
601 def DocumentSlicer(self) -> Generator[Union[Line, ProcessorException], Line, None]:
602 parser: Section = firstValue(self._parsers)
603 activeParsers: List[Parsers] = list(self._parsers.values())
605 rtlElaboration = self._parsers[RTLElaboration]
606 constraintValidation = self._parsers[ConstraintValidation]
608 # get first line and send to preamble filter
609 line = yield
610 line = next(filter := parser.Generator(line))
612 # return first line and get the second line
613 line = yield line
615 while line is not None: 615 ↛ 646line 615 didn't jump to line 646 because the condition on line 615 was always true
616 if filter is not None:
617 line = filter.send(line)
619 if (LineKind.Last in line._kind) and (LineKind.SectionDelimiter in line._kind):
620 activeParsers.remove(parser)
621 filter = None
622 else:
623 if line._message.startswith("Start "):
624 for parser in activeParsers: # type: Section 624 ↛ 629line 624 didn't jump to line 629 because the loop on line 624 didn't complete
625 if line._message.startswith(parser._START):
626 line = next(filter := parser.Generator(line))
627 break
628 else:
629 raise Exception(f"Unknown section: {line}")
630 elif line._message.startswith("Starting "):
631 if line._message[9:].startswith("synth_design"):
632 line._kind = LineKind.Verbose
633 elif line._message.startswith(rtlElaboration._START): 633 ↛ 641line 633 didn't jump to line 641 because the condition on line 633 was always true
634 parser = rtlElaboration
635 line = next(filter := parser.Generator(line))
636 elif line._message.startswith("Finished "):
637 if line._message.startswith(constraintValidation._START):
638 parser = constraintValidation
639 line = next(filter := parser.Generator(line))
641 if line._kind is LineKind.Unprocessed:
642 line._kind = LineKind.Normal
644 line = yield line
646 pass
648# unused
649# used but not set
650# statemachine encodings
651# resources
652# * RTL
653# * Mapped
655# Design hierarchy + generics per hierarchy
656# read XDC files