# ==================================================================================================================== #
# _____ ____ _ _ ___ _ _ _____ _ _ _ #
# _ __ _ _| ____| _ \ / \ / \ / _ \ _ _| |_ _ __ _ _| |_| ___(_) | |_ ___ _ __ #
# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | | __| '_ \| | | | __| |_ | | | __/ _ \ '__| #
# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| | |_| | |_| |_) | |_| | |_| _| | | | || __/ | #
# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/ \__,_|\__| .__/ \__,_|\__|_| |_|_|\__\___|_| #
# |_| |___/ |_| #
# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# #
# License: #
# ==================================================================================================================== #
# Copyright 2025-2025 Electronic Design Automation Abstraction (EDA²) #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# #
# SPDX-License-Identifier: Apache-2.0 #
# ==================================================================================================================== #
#
"""A filtering anc classification processor for AMD/Xilinx Vivado Synthesis outputs."""
from pathlib import Path
from re import compile as re_compile
from typing import ClassVar, List, Callable, Dict, Type, Iterator, Union, Generator
from pyTooling.Decorators import export, readonly
from pyTooling.MetaClasses import mustoverride
from pyTooling.Common import firstValue
from pyEDAA.OutputFilter.Xilinx import VivadoMessage, ProcessingState, Parser, Preamble, BaseProcessor, BaseDocument, \
Line, ProcessorException, LineKind, VivadoInfoMessage, VHDLAssertionMessage, VHDLReportMessage
TIME_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+)""")
[docs]
@export
class Initialize(Parser):
_command: str
_license: VivadoMessage
def ParseLine(self, lineNumber: int, line: str) -> bool:
pass
[docs]
@export
class Section(Parser):
# _START: ClassVar[str]
# _FINISH: ClassVar[str]
_duration: float
[docs]
def __init__(self, processor: "Processor"):
super().__init__(processor)
self._duration = 0.0
@readonly
def Duration(self) -> float:
return self._duration
def _SectionStart(self, line: Line) -> Generator[Line, Line, Line]:
line._kind = LineKind.SectionStart
line = yield line
if line._message.startswith("----"):
line._kind = LineKind.SectionStart | LineKind.SectionDelimiter
else:
line._kind |= LineKind.ProcessorError
nextLine = yield line
return nextLine
def _SectionFinish(self, line: Line) -> Generator[Line, Line, None]:
if line._message.startswith(self._FINISH):
line._kind = LineKind.SectionEnd
else:
line._kind |= LineKind.ProcessorError
line = yield line
if line._message.startswith("----"):
line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter | LineKind.Last
else:
line._kind |= LineKind.ProcessorError
check = yield line
if check is not None:
raise Exception()
# @mustoverride
# def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
# if len(line) == 0:
# return ProcessingState.EmptyLine
# elif line.startswith("----"):
# return ProcessingState.DelimiterLine
# elif line.startswith(self._START):
# return ProcessingState.Skipped
# elif line.startswith(self._FINISH):
# l = line[len(self._FINISH):]
# if (match := TIME_MEMORY_PATTERN.match(l)) is not None:
# # cpuParts = match[1].split(":")
# elapsedParts = match[2].split(":")
# # peakMemory = float(match[3])
# # gainMemory = float(match[4])
# self._duration = int(elapsedParts[0]) * 3600 + int(elapsedParts[1]) * 60 + int(elapsedParts[2])
#
# return ProcessingState.Skipped | ProcessingState.Last
# elif line.startswith("Start") or line.startswith("Starting"):
# print(f"ERROR: didn't find finish\n {line}")
# return ProcessingState.Reprocess
#
# return ProcessingState.Skipped
def Generator(self, line: Line) -> Generator[Line, Line, Line]:
line = yield from self._SectionStart(line)
while line is not None:
rawMessage = line._message
if rawMessage.startswith("----"):
line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
break
else:
line._kind = LineKind.Verbose
line = yield line
line = yield line
nextLine = yield from self._SectionFinish(line)
return nextLine
[docs]
@export
class SubSection(Section):
def _SectionStart(self, line: Line) -> Generator[Line, Line, Line]:
line._kind = LineKind.SubSectionStart
line = yield line
if line._message.startswith("----"):
line._kind = LineKind.SubSectionStart | LineKind.SubSectionDelimiter
else:
line._kind |= LineKind.ProcessorError
nextLine = yield line
return nextLine
def _SectionFinish(self, line: Line) -> Generator[Line, Line, None]:
if line._message.startswith(self._FINISH):
line._kind = LineKind.SubSectionEnd
else:
line._kind |= LineKind.ProcessorError
line = yield line
if line._message.startswith("----"):
line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter | LineKind.Last
else:
line._kind |= LineKind.ProcessorError
nextLine = yield line
return nextLine
def Generator(self, line: Line) -> Generator[Line, Line, Line]:
line = yield from self._SectionStart(line)
while line is not None:
rawMessage = line._message
if rawMessage.startswith("----"):
line._kind = LineKind.SubSectionEnd | LineKind.SubSectionDelimiter
break
else:
line._kind = LineKind.Verbose
line = yield line
line = yield line
nextLine = yield from self._SectionFinish(line)
return nextLine
[docs]
@export
class RTLElaboration(Section):
_START: ClassVar[str] = "Starting RTL Elaboration : "
_FINISH: ClassVar[str] = "Finished RTL Elaboration : "
def Generator(self, line: Line) -> Generator[Line, Line, Line]:
line = yield from self._SectionStart(line)
while line is not None:
rawMessage = line._message
if isinstance(line, VivadoInfoMessage):
if line._toolID == 8:
if line._messageKindID == 63: # VHDL assert statement
newLine = VHDLAssertionMessage.Convert(line)
if newLine is None:
print(f"Convert error at: {line}")
else:
line = newLine
elif line._messageKindID == 6031: # VHDL report statement
newLine = VHDLReportMessage.Convert(line)
if newLine is None:
print(f"Convert error at: {line}")
else:
line = newLine
if rawMessage.startswith("----"):
line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
break
elif not isinstance(line, VivadoMessage):
line._kind = LineKind.Verbose
line = yield line
line = yield line
nextLine = yield from self._SectionFinish(line)
return nextLine
[docs]
@export
class HandlingCustomAttributes1(Section):
_START: ClassVar[str] = "Start Handling Custom Attributes"
_FINISH: ClassVar[str] = "Finished Handling Custom Attributes : "
[docs]
@export
class ConstraintValidation(Section):
_START: ClassVar[str] = "Finished RTL Optimization Phase 1"
_FINISH: ClassVar[str] = "Finished Constraint Validation : "
[docs]
@export
class LoadingPart(Section):
_START: ClassVar[str] = "Start Loading Part and Timing Information"
_FINISH: ClassVar[str] = "Finished Loading Part and Timing Information : "
_part: str
[docs]
def __init__(self, processor: "Processor"):
super().__init__(processor)
self._part = None
@readonly
def Part(self) -> str:
return self._part
def Generator(self, line: Line) -> Generator[Line, Line, Line]:
line = yield from self._SectionStart(line)
while line is not None:
rawMessage = line._message
if line._message.startswith("Loading part: "):
line._kind = LineKind.Normal
self._part = line._message[14:].strip()
if rawMessage.startswith("----"):
line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
break
line = yield line
line = yield line
nextLine = yield from self._SectionFinish(line)
return nextLine
[docs]
@export
class ApplySetProperty(Section):
_START: ClassVar[str] = "Start Applying 'set_property' XDC Constraints"
_FINISH: ClassVar[str] = "Finished applying 'set_property' XDC Constraints : "
[docs]
@export
class RTLComponentStatistics(Section):
_START: ClassVar[str] = "Start RTL Component Statistics"
_FINISH: ClassVar[str] = "Finished RTL Component Statistics"
def Generator(self, line: Line) -> Generator[Line, Line, Line]:
line = yield from self._SectionStart(line)
while line is not None:
rawMessage = line._message
if rawMessage.startswith("----"):
line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
break
else:
line._kind = LineKind.Verbose
line = yield line
line = yield line
nextLine = yield from self._SectionFinish(line)
return nextLine
[docs]
@export
class PartResourceSummary(Section):
_START: ClassVar[str] = "Start Part Resource Summary"
_FINISH: ClassVar[str] = "Finished Part Resource Summary"
[docs]
@export
class CrossBoundaryAndAreaOptimization(Section):
_START: ClassVar[str] = "Start Cross Boundary and Area Optimization"
_FINISH: ClassVar[str] = "Finished Cross Boundary and Area Optimization : "
[docs]
@export
class ApplyingXDCTimingConstraints(Section):
_START: ClassVar[str] = "Start Applying XDC Timing Constraints"
_FINISH: ClassVar[str] = "Finished Applying XDC Timing Constraints : "
[docs]
@export
class TimingOptimization(Section):
_START: ClassVar[str] = "Start Timing Optimization"
_FINISH: ClassVar[str] = "Finished Timing Optimization : "
[docs]
@export
class TechnologyMapping(Section):
_START: ClassVar[str] = "Start Technology Mapping"
_FINISH: ClassVar[str] = "Finished Technology Mapping : "
[docs]
@export
class FlatteningBeforeIOInsertion(SubSection):
_START: ClassVar[str] = "Start Flattening Before IO Insertion"
_FINISH: ClassVar[str] = "Finished Flattening Before IO Insertion"
[docs]
@export
class FinalNetlistCleanup(SubSection):
_START: ClassVar[str] = "Start Final Netlist Cleanup"
_FINISH: ClassVar[str] = "Finished Final Netlist Cleanup"
[docs]
@export
class IOInsertion(Section):
_START: ClassVar[str] = "Start IO Insertion"
_FINISH: ClassVar[str] = "Finished IO Insertion : "
def Generator(self, line: Line) -> Generator[Line, Line, Line]:
flattening = FlatteningBeforeIOInsertion(None)
netlist = FinalNetlistCleanup(None)
line = yield from self._SectionStart(line)
if line._message.startswith("----"):
line._kind = LineKind.SectionStart | LineKind.SectionDelimiter
else:
line._kind |= LineKind.ProcessorError
line = yield line
if line._message.startswith("Start "):
line = yield from flattening.Generator(line)
if line._message.startswith("----"):
line._kind = LineKind.SubSectionStart | LineKind.SectionDelimiter
else:
line._kind |= LineKind.ProcessorError
line = yield line
if line._message.startswith("Start "):
line = yield from netlist.Generator(line)
if line._message.startswith("----"):
line._kind = LineKind.SubSectionEnd | LineKind.SectionDelimiter
else:
line._kind |= LineKind.ProcessorError
line = yield line
nextLine = yield from self._SectionFinish(line)
return nextLine
[docs]
@export
class RenamingGeneratedInstances(Section):
_START: ClassVar[str] = "Start Renaming Generated Instances"
_FINISH: ClassVar[str] = "Finished Renaming Generated Instances : "
[docs]
@export
class RebuildingUserHierarchy(Section):
_START: ClassVar[str] = "Start Rebuilding User Hierarchy"
_FINISH: ClassVar[str] = "Finished Rebuilding User Hierarchy : "
[docs]
@export
class RenamingGeneratedPorts(Section):
_START: ClassVar[str] = "Start Renaming Generated Ports"
_FINISH: ClassVar[str] = "Finished Renaming Generated Ports : "
[docs]
@export
class HandlingCustomAttributes2(Section):
_START: ClassVar[str] = "Start Handling Custom Attributes"
_FINISH: ClassVar[str] = "Finished Handling Custom Attributes : "
[docs]
@export
class RenamingGeneratedNets(Section):
_START: ClassVar[str] = "Start Renaming Generated Nets"
_FINISH: ClassVar[str] = "Finished Renaming Generated Nets : "
[docs]
@export
class WritingSynthesisReport(Section):
_START: ClassVar[str] = "Start Writing Synthesis Report"
_FINISH: ClassVar[str] = "Finished Writing Synthesis Report : "
_blackboxes: Dict[str, int]
_cells: Dict[str, int]
[docs]
def __init__(self, processor: "Processor"):
super().__init__(processor)
self._blackboxes = {}
self._cells = {}
@readonly
def Cells(self) -> Dict[str, int]:
return self._cells
@readonly
def Blackboxes(self) -> Dict[str, int]:
return self._blackboxes
def _BlackboxesGenerator(self, line: Line) -> Generator[Line, Line, Line]:
if line._message.startswith("+-"):
line._kind = LineKind.TableFrame
else:
line._kind = LineKind.ProcessorError
line = yield line
if line._message.startswith("| "):
line._kind = LineKind.TableHeader
else:
line._kind = LineKind.ProcessorError
line = yield line
if line._message.startswith("+-"):
line._kind = LineKind.TableFrame
else:
line._kind = LineKind.ProcessorError
line = yield line
while line is not None:
if line._message.startswith("| "):
line._kind = LineKind.TableRow
columns = line._message.strip("|").split("|")
self._blackboxes[columns[1].strip()] = int(columns[2].strip())
elif line._message.startswith("+-"):
line._kind = LineKind.TableFrame
break
else:
line._kind = LineKind.ProcessorError
line = yield line
nextLine = yield line
return nextLine
def _CellGenerator(self, line: Line) -> Generator[Line, Line, Line]:
if line._message.startswith("+-"):
line._kind = LineKind.TableFrame
else:
line._kind = LineKind.ProcessorError
line = yield line
if line._message.startswith("| "):
line._kind = LineKind.TableHeader
else:
line._kind = LineKind.ProcessorError
line = yield line
if line._message.startswith("+-"):
line._kind = LineKind.TableFrame
else:
line._kind = LineKind.ProcessorError
line = yield line
while line is not None:
if line._message.startswith("|"):
line._kind = LineKind.TableRow
columns = line._message.strip("|").split("|")
self._cells[columns[1].strip()] = int(columns[2].strip())
elif line._message.startswith("+-"):
line._kind = LineKind.TableFrame
break
else:
line._kind = LineKind.ProcessorError
line = yield line
nextLine = yield line
return nextLine
def Generator(self, line: Line) -> Generator[Line, Line, Line]:
line = yield from self._SectionStart(line)
while line is not None:
rawMessage = line._message
if rawMessage.startswith("Report BlackBoxes:"):
line._kind = LineKind.ParagraphHeadline
line = yield line
line = yield from self._BlackboxesGenerator(line)
elif rawMessage.startswith("Report Cell Usage:"):
line._kind = LineKind.ParagraphHeadline
line = yield line
line = yield from self._CellGenerator(line)
elif rawMessage.startswith("----"):
line._kind = LineKind.SectionEnd | LineKind.SectionDelimiter
break
elif not isinstance(line, VivadoMessage):
line._kind = LineKind.Verbose
line = yield line
elif line._kind is LineKind.Empty:
line = yield line
line = yield line
nextLine = yield from self._SectionFinish(line)
return nextLine
PARSERS = (
Preamble,
RTLElaboration,
HandlingCustomAttributes1,
ConstraintValidation,
LoadingPart,
ApplySetProperty,
RTLComponentStatistics,
PartResourceSummary,
CrossBoundaryAndAreaOptimization,
ApplyingXDCTimingConstraints,
TimingOptimization,
TechnologyMapping,
IOInsertion,
FlatteningBeforeIOInsertion,
FinalNetlistCleanup,
RenamingGeneratedInstances,
RebuildingUserHierarchy,
RenamingGeneratedPorts,
HandlingCustomAttributes2,
RenamingGeneratedNets,
WritingSynthesisReport,
)
Parsers = Union[*PARSERS]
[docs]
@export
class Processor(BaseDocument):
_parsers: Dict[Type[Parser], Parsers]
[docs]
def __init__(self, synthesisLogfile: Path):
super().__init__(synthesisLogfile)
self._parsers = {p: p(self) for p in PARSERS}
@readonly
def HasLatches(self) -> bool:
if (8 in self._messagesByID) and (327 in self._messagesByID[8]):
return True
return "LD" in self._parsers[WritingSynthesisReport]._cells
@readonly
def Latches(self) -> Iterator[VivadoMessage]:
try:
yield from iter(self._messagesByID[8][327])
except KeyError:
yield from ()
@readonly
def HasBlackboxes(self) -> bool:
return len(self._parsers[WritingSynthesisReport]._blackboxes) > 0
@readonly
def Cells(self) -> Dict[str, int]:
return self._parsers[WritingSynthesisReport]._cells
def __getitem__(self, item: Type[Parser]) -> Parsers:
return self._parsers[item]
def DocumentSlicer(self) -> Generator[Union[Line, ProcessorException], Line, None]:
parser: Section = firstValue(self._parsers)
activeParsers: List[Parsers] = list(self._parsers.values())
rtlElaboration = self._parsers[RTLElaboration]
constraintValidation = self._parsers[ConstraintValidation]
# get first line and send to preamble filter
line = yield
line = next(filter := parser.Generator(line))
# return first line and get the second line
line = yield line
while line is not None:
if filter is not None:
line = filter.send(line)
if (LineKind.Last in line._kind) and (LineKind.SectionDelimiter in line._kind):
activeParsers.remove(parser)
filter = None
else:
if line._message.startswith("Start "):
for parser in activeParsers: # type: Section
if line._message.startswith(parser._START):
line = next(filter := parser.Generator(line))
break
else:
raise Exception(f"Unknown section: {line}")
elif line._message.startswith("Starting "):
if line._message[9:].startswith("synth_design"):
line._kind = LineKind.Verbose
elif line._message.startswith(rtlElaboration._START):
parser = rtlElaboration
line = next(filter := parser.Generator(line))
elif line._message.startswith("Finished "):
if line._message.startswith(constraintValidation._START):
parser = constraintValidation
line = next(filter := parser.Generator(line))
if line._kind is LineKind.Unprocessed:
line._kind = LineKind.Normal
line = yield line
pass
# unused
# used but not set
# statemachine encodings
# resources
# * RTL
# * Mapped
# Design hierarchy + generics per hierarchy
# read XDC files