Coverage for pyEDAA/OutputFilter/Xilinx/SynthesizeDesign.py: 88%
321 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-26 23:00 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-26 23:00 +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"""A filtering anc classification processor for AMD/Xilinx Vivado Synthesis outputs."""
32from re import compile as re_compile
33from typing import ClassVar, Dict, Generator, Optional as Nullable
35from pyTooling.Decorators import export, readonly
37from pyEDAA.OutputFilter.Xilinx import Section, SubSection, SectionWithChildren
38from pyEDAA.OutputFilter.Xilinx import VHDLAssertionMessage, VivadoLine, LineKind, VivadoInfoMessage, VHDLReportMessage, VivadoMessage
41__all__ = ["TIME_MEMORY_PATTERN"]
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 RTLElaboration(Section):
48 """
49 *RTL Elaboration* section.
51 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
52 """
53 _NAME: ClassVar[str] = "RTL Elaboration"
54 _START: ClassVar[str] = "Starting RTL Elaboration : "
55 _FINISH: ClassVar[str] = "Finished RTL Elaboration : "
56 _DUPLICATES: ClassVar[bool] = False
58 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
59 line = yield from self._SectionStart(line)
61 while True:
62 if line._kind is LineKind.Empty: 62 ↛ 63line 62 didn't jump to line 63 because the condition on line 62 was never true
63 line = yield line
64 continue
65 elif isinstance(line, VivadoInfoMessage):
66 if line._toolID == 8:
67 if line._messageKindID == 63: # VHDL assert statement
68 newLine = VHDLAssertionMessage.Convert(line)
69 if newLine is None: 69 ↛ 70line 69 didn't jump to line 70 because the condition on line 69 was never true
70 pass
71 else:
72 line = newLine
73 elif line._messageKindID == 6031: # VHDL report statement
74 newLine = VHDLReportMessage.Convert(line)
75 if newLine is None: 75 ↛ 76line 75 didn't jump to line 76 because the condition on line 75 was never true
76 pass
77 else:
78 line = newLine
80 if line.StartsWith("----"):
81 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
82 break
83 elif isinstance(line, VivadoMessage):
84 self._AddMessage(line)
85 else:
86 line._kind = LineKind.Verbose
88 line = yield line
90 # line = yield line
91 nextLine = yield from self._SectionFinish(line)
92 return nextLine
95@export
96class HandlingCustomAttributes(Section):
97 """
98 *Handling Custom Attributes* section.
100 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
101 """
102 _NAME: ClassVar[str] = "Handling Custom Attributes"
103 _START: ClassVar[str] = "Start Handling Custom Attributes"
104 _FINISH: ClassVar[str] = "Finished Handling Custom Attributes : "
105 _DUPLICATES: ClassVar[bool] = True
108@export
109class ConstraintValidation(Section):
110 """
111 *Constraint Validation* section.
113 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
114 """
115 _NAME: ClassVar[str] = "Constraint Validation"
116 _START: ClassVar[str] = "Finished RTL Optimization Phase 1"
117 _FINISH: ClassVar[str] = "Finished Constraint Validation : "
118 _DUPLICATES: ClassVar[bool] = False
121@export
122class LoadingPart(Section):
123 """
124 *Loading Part and Timing Information* section.
126 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
127 """
128 _NAME: ClassVar[str] = "Loading Part and Timing Information"
129 _START: ClassVar[str] = "Start Loading Part and Timing Information"
130 _FINISH: ClassVar[str] = "Finished Loading Part and Timing Information : "
131 _DUPLICATES: ClassVar[bool] = False
133 _part: Nullable[str] #: Part name of the device this design was synthesized for.
135 def __init__(self, command: "Command") -> None:
136 """
137 Initializes the section for loading the device information.
139 :param command: Reference to the TCL command.
140 """
141 super().__init__(command)
143 self._part = None
145 @readonly
146 def Part(self) -> Nullable[str]:
147 """
148 Read-only property to access the used device's part name.
150 :returns: Part name of the device this design was synthesized for.
151 """
152 return self._part
154 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
155 line = yield from self._SectionStart(line)
157 while True:
158 if line._kind is LineKind.Empty: 158 ↛ 159line 158 didn't jump to line 159 because the condition on line 158 was never true
159 line = yield line
160 continue
161 elif line.StartsWith("Loading part: "):
162 line._kind = LineKind.Normal
163 self._part = line._message[14:].strip()
164 elif line.StartsWith("----"):
165 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
166 break
167 elif isinstance(line, VivadoMessage): 167 ↛ 170line 167 didn't jump to line 170 because the condition on line 167 was always true
168 self._AddMessage(line)
169 else:
170 line._kind = LineKind.Verbose
172 line = yield line
174 nextLine = yield from self._SectionFinish(line)
175 return nextLine
178@export
179class ApplySetPropertyXDCConstraints(Section):
180 """
181 *Applying 'set_property' XDC Constraints* section.
183 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
184 """
185 _NAME: ClassVar[str] = "Applying 'set_property' XDC Constraints"
186 _START: ClassVar[str] = "Start Applying 'set_property' XDC Constraints"
187 _FINISH: ClassVar[str] = "Finished applying 'set_property' XDC Constraints : "
188 _DUPLICATES: ClassVar[bool] = False
191@export
192class RTLComponentStatistics(Section):
193 """
194 *RTL Component Statistics* section.
196 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
197 """
198 _NAME: ClassVar[str] = "RTL Component Statistics"
199 _START: ClassVar[str] = "Start RTL Component Statistics"
200 _FINISH: ClassVar[str] = "Finished RTL Component Statistics"
201 _DUPLICATES: ClassVar[bool] = False
203 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
204 line = yield from self._SectionStart(line)
206 while True:
207 if line._kind is LineKind.Empty: 207 ↛ 208line 207 didn't jump to line 208 because the condition on line 207 was never true
208 line = yield line
209 continue
210 elif line.StartsWith("----"):
211 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
212 break
213 elif isinstance(line, VivadoMessage): 213 ↛ 214line 213 didn't jump to line 214 because the condition on line 213 was never true
214 self._AddMessage(line)
215 else:
216 line._kind = LineKind.Verbose
218 line = yield line
220 nextLine = yield from self._SectionFinish(line)
221 return nextLine
224@export
225class RTLHierarchicalComponentStatistics(Section):
226 """
227 *RTL Hierarchical Component Statistics* section.
229 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
230 """
231 _NAME: ClassVar[str] = "RTL Hierarchical Component Statistics"
232 _START: ClassVar[str] = "Start RTL Hierarchical Component Statistics"
233 _FINISH: ClassVar[str] = "Finished RTL Hierarchical Component Statistics"
234 _DUPLICATES: ClassVar[bool] = False
237@export
238class PartResourceSummary(Section):
239 """
240 *Part Resource Summary* section.
242 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
243 """
244 _NAME: ClassVar[str] = "Part Resource Summary"
245 _START: ClassVar[str] = "Start Part Resource Summary"
246 _FINISH: ClassVar[str] = "Finished Part Resource Summary"
247 _DUPLICATES: ClassVar[bool] = False
250@export
251class CrossBoundaryAndAreaOptimization(Section):
252 """
253 *Cross Boundary and Area Optimization* section.
255 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
256 """
257 _NAME: ClassVar[str] = "Cross Boundary and Area Optimization"
258 _START: ClassVar[str] = "Start Cross Boundary and Area Optimization"
259 _FINISH: ClassVar[str] = "Finished Cross Boundary and Area Optimization : "
260 _DUPLICATES: ClassVar[bool] = False
263@export
264class ROM_RAM_DSP_SR_Retiming(Section):
265 """
266 *ROM, RAM, DSP, Shift Register and Retiming Reporting* section.
268 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
269 """
270 _NAME: ClassVar[str] = "ROM, RAM, DSP, Shift Register and Retiming Reporting"
271 _START: ClassVar[str] = "Start ROM, RAM, DSP, Shift Register and Retiming Reporting"
272 _FINISH: ClassVar[str] = "Finished ROM, RAM, DSP, Shift Register and Retiming Reporting"
273 _DUPLICATES: ClassVar[bool] = True
276@export
277class ApplyingXDCTimingConstraints(Section):
278 """
279 *Applying XDC Timing Constraints* section.
281 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
282 """
283 _NAME: ClassVar[str] = "Applying XDC Timing Constraints"
284 _START: ClassVar[str] = "Start Applying XDC Timing Constraints"
285 _FINISH: ClassVar[str] = "Finished Applying XDC Timing Constraints : "
286 _DUPLICATES: ClassVar[bool] = False
289@export
290class TimingOptimization(Section):
291 """
292 *Timing Optimization* section.
294 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
295 """
296 _NAME: ClassVar[str] = "Timing Optimization"
297 _START: ClassVar[str] = "Start Timing Optimization"
298 _FINISH: ClassVar[str] = "Finished Timing Optimization : "
299 _DUPLICATES: ClassVar[bool] = False
302@export
303class TechnologyMapping(Section):
304 """
305 *Technology Mapping* section.
307 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
308 """
309 _NAME: ClassVar[str] = "Technology Mapping"
310 _START: ClassVar[str] = "Start Technology Mapping"
311 _FINISH: ClassVar[str] = "Finished Technology Mapping : "
312 _DUPLICATES: ClassVar[bool] = False
315@export
316class FlatteningBeforeIOInsertion(SubSection):
317 """
318 *Flattening Before IO Insertion* subsection.
320 Used by section :class:`IOInsertion`.
321 """
322 _NAME: ClassVar[str] = "Flattening Before IO Insertion"
323 _START: ClassVar[str] = "Start Flattening Before IO Insertion"
324 _FINISH: ClassVar[str] = "Finished Flattening Before IO Insertion"
325 _DUPLICATES: ClassVar[bool] = False
328@export
329class FinalNetlistCleanup(SubSection):
330 """
331 *Final Netlist Cleanup* subsection.
333 Used by section :class:`IOInsertion`.
334 """
335 _NAME: ClassVar[str] = "Final Netlist Cleanup"
336 _START: ClassVar[str] = "Start Final Netlist Cleanup"
337 _FINISH: ClassVar[str] = "Finished Final Netlist Cleanup"
338 _DUPLICATES: ClassVar[bool] = False
341@export
342class IOInsertion(SectionWithChildren):
343 """
344 *IO Insertion* section.
346 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
347 """
348 _NAME: ClassVar[str] = "IO Insertion"
349 _START: ClassVar[str] = "Start IO Insertion"
350 _FINISH: ClassVar[str] = "Finished IO Insertion : "
351 _DUPLICATES: ClassVar[bool] = False
353 # TODO: generalize, use _PARSERS and move to SectionWithChildren
354 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
355 line = yield from self._SectionStart(line)
357 while True:
358 while True:
359 if line._kind is LineKind.Empty: 359 ↛ 360line 359 didn't jump to line 360 because the condition on line 359 was never true
360 line = yield line
361 continue
362 elif line.StartsWith("----"):
363 line._kind = LineKind.SubSectionStart | LineKind.SubSectionDelimiter
364 elif line.StartsWith("Start "):
365 if line == FlatteningBeforeIOInsertion._START:
366 self._subsections[FlatteningBeforeIOInsertion] = (subsection := FlatteningBeforeIOInsertion(self))
367 line = yield next(parser := subsection.Generator(line))
368 break
369 elif line == FinalNetlistCleanup._START: 369 ↛ 380line 369 didn't jump to line 380 because the condition on line 369 was always true
370 self._subsections[FinalNetlistCleanup] = (subsection := FinalNetlistCleanup(self))
371 line = yield next(parser := subsection.Generator(line))
372 break
373 elif isinstance(line, VivadoMessage): 373 ↛ 374line 373 didn't jump to line 374 because the condition on line 373 was never true
374 self._AddMessage(line)
375 elif line.StartsWith("Finished "): 375 ↛ 378line 375 didn't jump to line 378 because the condition on line 375 was always true
376 break
377 else:
378 line._kind |= LineKind.ProcessorError
380 line = yield line
382 if line.StartsWith(self._FINISH):
383 break
385 while True:
386 if line.StartsWith(subsection._FINISH):
387 line = yield parser.send(line)
388 line = yield parser.send(line)
390 subsection = None
391 parser = None
392 break
394 line = parser.send(line)
395 if isinstance(line, VivadoMessage): 395 ↛ 396line 395 didn't jump to line 396 because the condition on line 395 was never true
396 self._AddMessage(line)
398 line = yield line
400 nextLine = yield from self._SectionFinish(line, True)
401 return nextLine
404@export
405class RenamingGeneratedInstances(Section):
406 """
407 *Renaming Generated Instances* section.
409 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
410 """
411 _NAME: ClassVar[str] = "Renaming Generated Instances"
412 _START: ClassVar[str] = "Start Renaming Generated Instances"
413 _FINISH: ClassVar[str] = "Finished Renaming Generated Instances : "
414 _DUPLICATES: ClassVar[bool] = False
417@export
418class RebuildingUserHierarchy(Section):
419 """
420 *Rebuilding User Hierarchy* section.
422 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
423 """
424 _NAME: ClassVar[str] = "Rebuilding User Hierarchy"
425 _START: ClassVar[str] = "Start Rebuilding User Hierarchy"
426 _FINISH: ClassVar[str] = "Finished Rebuilding User Hierarchy : "
427 _DUPLICATES: ClassVar[bool] = False
430@export
431class RenamingGeneratedPorts(Section):
432 """
433 *Renaming Generated Ports* section.
435 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
436 """
437 _NAME: ClassVar[str] = "Renaming Generated Ports"
438 _START: ClassVar[str] = "Start Renaming Generated Ports"
439 _FINISH: ClassVar[str] = "Finished Renaming Generated Ports : "
440 _DUPLICATES: ClassVar[bool] = False
443@export
444class RenamingGeneratedNets(Section):
445 """
446 *Renaming Generated Nets* section.
448 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
449 """
450 _NAME: ClassVar[str] = "Renaming Generated Nets"
451 _START: ClassVar[str] = "Start Renaming Generated Nets"
452 _FINISH: ClassVar[str] = "Finished Renaming Generated Nets : "
453 _DUPLICATES: ClassVar[bool] = False
456@export
457class WritingSynthesisReport(Section):
458 """
459 *Writing Synthesis Report* section.
461 Used by Vivado command :class:`~pyEDAA.OutputFilter.Xilinx.Commands.SynthesizeDesign`.
462 """
463 _NAME: ClassVar[str] = "Writing Synthesis Report"
464 _START: ClassVar[str] = "Start Writing Synthesis Report"
465 _FINISH: ClassVar[str] = "Finished Writing Synthesis Report : "
466 _DUPLICATES: ClassVar[bool] = False
468 _blackboxes: Dict[str, int] #: Blackbox statistics: blackbox name -> count
469 _cells: Dict[str, int] #: Cell statistics: cell name -> count
471 def __init__(self, command: "Command") -> None:
472 super().__init__(command)
474 self._blackboxes = {}
475 self._cells = {}
477 @readonly
478 def Cells(self) -> Dict[str, int]:
479 """
480 Read-only property to access the dictionary of synthesized cell statistics.
482 :returns: The dictionary of used cell statistics.
483 """
484 return self._cells
486 @readonly
487 def Blackboxes(self) -> Dict[str, int]:
488 """
489 Read-only property to access the dictionary of found blackbox statistics.
491 :returns: The dictionary of found blackbox statistics.
492 """
493 return self._blackboxes
495 def _BlackboxesGenerator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
496 """
497 A parser parsing the blackboxes table.
499 :param line: First line to process.
500 :returns: A generator to process multiple lines containing a table of blackboxes.
502 .. rubric:: Example
504 .. code-block::
506 Report BlackBoxes:
507 +------+----------------------------------+----------+
508 | |BlackBox name |Instances |
509 +------+----------------------------------+----------+
510 |1 |name | 1|
511 |[...] |[...] | [...]|
512 +------+----------------------------------+----------+
513 """
514 if line.StartsWith("+-"): 514 ↛ 517line 514 didn't jump to line 517 because the condition on line 514 was always true
515 line._kind = LineKind.TableFrame
516 else:
517 line._kind = LineKind.ProcessorError
519 line = yield line
520 if line.StartsWith("| "): 520 ↛ 523line 520 didn't jump to line 523 because the condition on line 520 was always true
521 line._kind = LineKind.TableHeader
522 else:
523 line._kind = LineKind.ProcessorError
525 line = yield line
526 if line.StartsWith("+-"): 526 ↛ 529line 526 didn't jump to line 529 because the condition on line 526 was always true
527 line._kind = LineKind.TableFrame
528 else:
529 line._kind = LineKind.ProcessorError
531 line = yield line
532 while True:
533 if line.StartsWith("|"):
534 line._kind = LineKind.TableRow
536 columns = line._message.strip("|").split("|")
537 self._blackboxes[columns[1].strip()] = int(columns[2].strip())
538 elif line.StartsWith("+-"): 538 ↛ 542line 538 didn't jump to line 542 because the condition on line 538 was always true
539 line._kind = LineKind.TableFrame
540 break
541 else:
542 line._kind = LineKind.ProcessorError
544 line = yield line
546 nextLine = yield line
547 return nextLine
549 def _CellGenerator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
550 """
551 A parser parsing the cell statistic table.
553 :param line: First line to process.
554 :returns: A generator to process multiple lines containing a table of cell statistics.
556 .. rubric:: Example
558 .. code-block::
560 Report Cell Usage:
561 +------+----------------------------------+------+
562 | |Cell |Count |
563 +------+----------------------------------+------+
564 |1 |name | 1|
565 |[...] |[...] | [...]|
566 +------+----------------------------------+------+
567 """
568 if line.StartsWith("+-"): 568 ↛ 571line 568 didn't jump to line 571 because the condition on line 568 was always true
569 line._kind = LineKind.TableFrame
570 else:
571 line._kind = LineKind.ProcessorError
573 line = yield line
574 if line.StartsWith("| "): 574 ↛ 577line 574 didn't jump to line 577 because the condition on line 574 was always true
575 line._kind = LineKind.TableHeader
576 else:
577 line._kind = LineKind.ProcessorError
579 line = yield line
580 if line.StartsWith("+-"): 580 ↛ 583line 580 didn't jump to line 583 because the condition on line 580 was always true
581 line._kind = LineKind.TableFrame
582 else:
583 line._kind = LineKind.ProcessorError
585 line = yield line
586 while True:
587 if line.StartsWith("|"):
588 line._kind = LineKind.TableRow
590 columns = line._message.strip("|").split("|")
591 self._cells[columns[1].strip()] = int(columns[2].strip())
592 elif line.StartsWith("+-"): 592 ↛ 596line 592 didn't jump to line 596 because the condition on line 592 was always true
593 line._kind = LineKind.TableFrame
594 break
595 else:
596 line._kind = LineKind.ProcessorError
598 line = yield line
600 nextLine = yield line
601 return nextLine
603 def Generator(self, line: VivadoLine) -> Generator[VivadoLine, VivadoLine, VivadoLine]:
604 line = yield from self._SectionStart(line)
606 while True:
607 if line._kind is LineKind.Empty:
608 line = yield line
609 continue
610 elif line.StartsWith("Report BlackBoxes:"):
611 line._kind = LineKind.ParagraphHeadline
612 line = yield line
613 line = yield from self._BlackboxesGenerator(line)
614 elif line.StartsWith("Report Cell Usage:"):
615 line._kind = LineKind.ParagraphHeadline
616 line = yield line
617 line = yield from self._CellGenerator(line)
618 elif line.StartsWith("----"):
619 line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
620 break
621 elif isinstance(line, VivadoMessage): 621 ↛ 622line 621 didn't jump to line 622 because the condition on line 621 was never true
622 self._AddMessage(line)
623 else:
624 line._kind = LineKind.Verbose
625 line = yield line
627 nextLine = yield from self._SectionFinish(line)
628 return nextLine