pyEDAA.OutputFilter 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,573 @@
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."""
32
+ from datetime import datetime
33
+ from enum import Flag
34
+ from pathlib import Path
35
+ from re import compile as re_compile, Pattern
36
+ from typing import ClassVar, List, Optional as Nullable, Callable, Dict, Type
37
+
38
+ from pyTooling.Decorators import export, readonly
39
+ from pyTooling.MetaClasses import ExtendedType, abstractmethod, mustoverride
40
+ from pyTooling.Common import firstValue
41
+ from pyTooling.Stopwatch import Stopwatch
42
+ from pyTooling.Versioning import YearReleaseVersion
43
+
44
+ from pyEDAA.OutputFilter.Xilinx import VivadoMessage, VivadoInfoMessage, VivadoWarningMessage, VivadoCriticalWarningMessage, VivadoErrorMessage
45
+
46
+
47
+ @export
48
+ class ProcessingState(Flag):
49
+ Processed = 1
50
+ Skipped = 2
51
+ EmptyLine = 4
52
+ CommentLine = 8
53
+ DelimiterLine = 16
54
+ TableLine = 32
55
+ TableHeader = 64
56
+ Reprocess = 512
57
+ Last = 1024
58
+
59
+
60
+ 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+)""")
61
+
62
+ @export
63
+ class Parser(metaclass=ExtendedType, slots=True):
64
+ _processor: "Processor"
65
+
66
+ def __init__(self, processor: "Processor"):
67
+ self._processor = processor
68
+
69
+ @readonly
70
+ def Processor(self) -> "Processor":
71
+ return self._processor
72
+
73
+ @abstractmethod
74
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
75
+ pass
76
+
77
+
78
+ @export
79
+ class Preamble(Parser):
80
+ _toolVersion: Nullable[YearReleaseVersion]
81
+ _startDatetime: Nullable[datetime]
82
+
83
+ _VERSION: ClassVar[Pattern] = re_compile(r"""# Vivado v(\d+\.\d(\.\d)?) \(64-bit\)""")
84
+ _STARTTIME: ClassVar[Pattern] = re_compile(r"""# Start of session at: Thu (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)""")
85
+
86
+ def __init__(self, processor: "Processor"):
87
+ super().__init__(processor)
88
+
89
+ self._toolVersion = None
90
+ self._startDatetime = None
91
+
92
+ @readonly
93
+ def ToolVersion(self) -> YearReleaseVersion:
94
+ return self._toolVersion
95
+
96
+ @readonly
97
+ def StartDatetime(self) -> datetime:
98
+ return self._startDatetime
99
+
100
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
101
+ if self._toolVersion is not None and line.startswith("#----"):
102
+ return ProcessingState.DelimiterLine | ProcessingState.Last
103
+ elif (match := self._VERSION.match(line)) is not None:
104
+ self._toolVersion = YearReleaseVersion.Parse(match[1])
105
+ return ProcessingState.Processed
106
+ elif (match := self._VERSION.match(line)) is not None:
107
+ self._startDatetime = datetime(int(match[6]), int(match[1]), int(match[2]), int(match[3]), int(match[4]), int(match[5]))
108
+ return ProcessingState.Processed
109
+
110
+ return ProcessingState.Skipped
111
+
112
+
113
+ @export
114
+ class Initialize(Parser):
115
+ _command: str
116
+ _license: VivadoMessage
117
+
118
+ def ParseLine(self, lineNumber: int, line: str) -> bool:
119
+ pass
120
+
121
+
122
+ @export
123
+ class Section(Parser):
124
+ # _START: ClassVar[str]
125
+ # _FINISH: ClassVar[str]
126
+
127
+ _duration: float
128
+
129
+ def __init__(self, processor: "Processor"):
130
+ super().__init__(processor)
131
+
132
+ self._duration = 0.0
133
+
134
+ @readonly
135
+ def Duration(self) -> float:
136
+ return self._duration
137
+
138
+ @mustoverride
139
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
140
+ if len(line) == 0:
141
+ return ProcessingState.EmptyLine
142
+ elif line.startswith("----"):
143
+ return ProcessingState.DelimiterLine
144
+ elif line.startswith(self._START):
145
+ return ProcessingState.Skipped
146
+ elif line.startswith(self._FINISH):
147
+ l = line[len(self._FINISH):]
148
+ if (match := TIME_MEMORY_PATTERN.match(l)) is not None:
149
+ # cpuParts = match[1].split(":")
150
+ elapsedParts = match[2].split(":")
151
+ # peakMemory = float(match[3])
152
+ # gainMemory = float(match[4])
153
+ self._duration = int(elapsedParts[0]) * 3600 + int(elapsedParts[1]) * 60 + int(elapsedParts[2])
154
+
155
+ return ProcessingState.Skipped | ProcessingState.Last
156
+ elif line.startswith("Start") or line.startswith("Starting"):
157
+ print(f"ERROR: didn't find finish\n {line}")
158
+ return ProcessingState.Reprocess
159
+
160
+ return ProcessingState.Skipped
161
+
162
+
163
+ @export
164
+ class RTLElaboration(Section):
165
+ _START: ClassVar[str] = "Starting RTL Elaboration : "
166
+ _FINISH: ClassVar[str] = "Finished RTL Elaboration : "
167
+
168
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
169
+ return super().ParseLine(lineNumber, line)
170
+
171
+
172
+ @export
173
+ class HandlingCustomAttributes1(Section):
174
+ _START: ClassVar[str] = "Start Handling Custom Attributes"
175
+ _FINISH: ClassVar[str] = "Finished Handling Custom Attributes : "
176
+
177
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
178
+ return super().ParseLine(lineNumber, line)
179
+
180
+
181
+ @export
182
+ class LoadingPart(Section):
183
+ _START: ClassVar[str] = "Start Loading Part and Timing Information"
184
+ _FINISH: ClassVar[str] = "Finished Loading Part and Timing Information : "
185
+
186
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
187
+ return super().ParseLine(lineNumber, line)
188
+
189
+
190
+ @export
191
+ class ApplySetProperty(Section):
192
+ _START: ClassVar[str] = "Start Applying 'set_property' XDC Constraints"
193
+ _FINISH: ClassVar[str] = "Finished applying 'set_property' XDC Constraints : "
194
+
195
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
196
+ return super().ParseLine(lineNumber, line)
197
+
198
+
199
+ @export
200
+ class RTLComponentStatistics(Section):
201
+ _START: ClassVar[str] = "Start RTL Component Statistics"
202
+ _FINISH: ClassVar[str] = "Finished RTL Component Statistics"
203
+
204
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
205
+ return super().ParseLine(lineNumber, line)
206
+
207
+
208
+ @export
209
+ class PartResourceSummary(Section):
210
+ _START: ClassVar[str] = "Start Part Resource Summary"
211
+ _FINISH: ClassVar[str] = "Finished Part Resource Summary"
212
+
213
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
214
+ return super().ParseLine(lineNumber, line)
215
+
216
+
217
+ @export
218
+ class CrossBoundaryAndAreaOptimization(Section):
219
+ _START: ClassVar[str] = "Start Cross Boundary and Area Optimization"
220
+ _FINISH: ClassVar[str] = "Finished Cross Boundary and Area Optimization : "
221
+
222
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
223
+ return super().ParseLine(lineNumber, line)
224
+
225
+
226
+ @export
227
+ class ApplyingXDCTimingConstraints(Section):
228
+ _START: ClassVar[str] = "Start Applying XDC Timing Constraints"
229
+ _FINISH: ClassVar[str] = "Finished Applying XDC Timing Constraints : "
230
+
231
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
232
+ return super().ParseLine(lineNumber, line)
233
+
234
+
235
+ @export
236
+ class TimingOptimization(Section):
237
+ _START: ClassVar[str] = "Start Timing Optimization"
238
+ _FINISH: ClassVar[str] = "Finished Timing Optimization : "
239
+
240
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
241
+ return super().ParseLine(lineNumber, line)
242
+
243
+
244
+ @export
245
+ class TechnologyMapping(Section):
246
+ _START: ClassVar[str] = "Start Technology Mapping"
247
+ _FINISH: ClassVar[str] = "Finished Technology Mapping : "
248
+
249
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
250
+ return super().ParseLine(lineNumber, line)
251
+
252
+
253
+ @export
254
+ class IOInsertion(Section):
255
+ _START: ClassVar[str] = "Start IO Insertion"
256
+ _FINISH: ClassVar[str] = "Finished IO Insertion : "
257
+
258
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
259
+ return super().ParseLine(lineNumber, line)
260
+
261
+
262
+ @export
263
+ class FlatteningBeforeIOInsertion(Section):
264
+ _START: ClassVar[str] = "Start Flattening Before IO Insertion"
265
+ _FINISH: ClassVar[str] = "Finished Flattening Before IO Insertion"
266
+
267
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
268
+ return super().ParseLine(lineNumber, line)
269
+
270
+
271
+ @export
272
+ class FinalNetlistCleanup(Section):
273
+ _START: ClassVar[str] = "Start Final Netlist Cleanup"
274
+ _FINISH: ClassVar[str] = "Finished Final Netlist Cleanup"
275
+
276
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
277
+ return super().ParseLine(lineNumber, line)
278
+
279
+
280
+ @export
281
+ class RenamingGeneratedInstances(Section):
282
+ _START: ClassVar[str] = "Start Renaming Generated Instances"
283
+ _FINISH: ClassVar[str] = "Finished Renaming Generated Instances : "
284
+
285
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
286
+ return super().ParseLine(lineNumber, line)
287
+
288
+
289
+ @export
290
+ class RebuildingUserHierarchy(Section):
291
+ _START: ClassVar[str] = "Start Rebuilding User Hierarchy"
292
+ _FINISH: ClassVar[str] = "Finished Rebuilding User Hierarchy : "
293
+
294
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
295
+ return super().ParseLine(lineNumber, line)
296
+
297
+
298
+ @export
299
+ class RenamingGeneratedPorts(Section):
300
+ _START: ClassVar[str] = "Start Renaming Generated Ports"
301
+ _FINISH: ClassVar[str] = "Finished Renaming Generated Ports : "
302
+
303
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
304
+ return super().ParseLine(lineNumber, line)
305
+
306
+
307
+ @export
308
+ class HandlingCustomAttributes2(Section):
309
+ _START: ClassVar[str] = "Start Handling Custom Attributes"
310
+ _FINISH: ClassVar[str] = "Finished Handling Custom Attributes : "
311
+
312
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
313
+ return super().ParseLine(lineNumber, line)
314
+
315
+
316
+ @export
317
+ class RenamingGeneratedNets(Section):
318
+ _START: ClassVar[str] = "Start Renaming Generated Nets"
319
+ _FINISH: ClassVar[str] = "Finished Renaming Generated Nets : "
320
+
321
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
322
+ return super().ParseLine(lineNumber, line)
323
+
324
+
325
+ @export
326
+ class WritingSynthesisReport(Section):
327
+ _START: ClassVar[str] = "Start Writing Synthesis Report"
328
+ _FINISH: ClassVar[str] = "Finished Writing Synthesis Report : "
329
+
330
+ _state: int
331
+ _blackboxes: Dict[str, int]
332
+ _cells: Dict[str, int]
333
+
334
+ def __init__(self, processor: "Processor"):
335
+ super().__init__(processor)
336
+
337
+ self._state = 0
338
+ self._blackboxes = {}
339
+ self._cells = {}
340
+
341
+ @readonly
342
+ def Cells(self) -> Dict[str, int]:
343
+ return self._cells
344
+
345
+ @readonly
346
+ def Blackboxes(self) -> Dict[str, int]:
347
+ return self._blackboxes
348
+
349
+ def ParseLine(self, lineNumber: int, line: str) -> ProcessingState:
350
+ if self._state == 0:
351
+ if line.startswith("Report BlackBoxes:"):
352
+ self._state = 10
353
+ return ProcessingState.Processed
354
+ elif line.startswith("Report Cell Usage:"):
355
+ self._state = 20
356
+ return ProcessingState.Processed
357
+ elif 10 <= self._state < 20:
358
+ if self._state == 10 or self._state == 12 and line.startswith("+-"):
359
+ self._state += 1
360
+ return ProcessingState.TableLine
361
+ elif self._state == 11 and line.startswith("| "):
362
+ self._state += 1
363
+ return ProcessingState.TableHeader
364
+ elif self._state == 13:
365
+ if line.startswith("+-"):
366
+ self._state = 0
367
+ return ProcessingState.TableLine
368
+ else:
369
+ columns = line.strip("|").split("|")
370
+ self._blackboxes[columns[1].strip()] = int(columns[2].strip())
371
+ elif 20 <= self._state < 30:
372
+ if self._state == 20 or self._state == 22 and line.startswith("+-"):
373
+ self._state += 1
374
+ return ProcessingState.TableLine
375
+ elif self._state == 21 and line.startswith("| "):
376
+ self._state += 1
377
+ return ProcessingState.TableHeader
378
+ elif self._state == 23:
379
+ if line.startswith("+-"):
380
+ self._state = 0
381
+ return ProcessingState.TableLine
382
+ else:
383
+ columns = line.strip("|").split("|")
384
+ self._cells[columns[1].strip()] = int(columns[2].strip())
385
+
386
+ return super().ParseLine(lineNumber, line)
387
+
388
+ PARSERS = (
389
+ Preamble,
390
+ RTLElaboration,
391
+ HandlingCustomAttributes1,
392
+ LoadingPart,
393
+ ApplySetProperty,
394
+ RTLComponentStatistics,
395
+ PartResourceSummary,
396
+ CrossBoundaryAndAreaOptimization,
397
+ ApplyingXDCTimingConstraints,
398
+ TimingOptimization,
399
+ TechnologyMapping,
400
+ IOInsertion,
401
+ FlatteningBeforeIOInsertion,
402
+ FinalNetlistCleanup,
403
+ RenamingGeneratedInstances,
404
+ RebuildingUserHierarchy,
405
+ RenamingGeneratedPorts,
406
+ HandlingCustomAttributes2,
407
+ RenamingGeneratedNets,
408
+ WritingSynthesisReport,
409
+ )
410
+
411
+ @export
412
+ class Processor(metaclass=ExtendedType, slots=True):
413
+ _logfile: Path
414
+ _parsers: Dict[Type[Parser], Parser]
415
+ _state: Callable[[int, str], bool]
416
+ _duration: float
417
+
418
+ _infoMessages: List[VivadoInfoMessage]
419
+ _warningMessages: List[VivadoWarningMessage]
420
+ _criticalWarningMessages: List[VivadoCriticalWarningMessage]
421
+ _errorMessages: List[VivadoErrorMessage]
422
+ _toolIDs: Dict[int, str]
423
+ _toolNames: Dict[str, int]
424
+ _messagesByID: Dict[int, Dict[int, List[VivadoMessage]]]
425
+
426
+ def __init__(self, synthesisLogfile: Path):
427
+ self._logfile = synthesisLogfile
428
+ self._parsers = {p: p(self) for p in PARSERS}
429
+ self._state = firstValue(self._parsers).ParseLine
430
+ self._duration = 0.0
431
+
432
+ self._infoMessages = []
433
+ self._warningMessages = []
434
+ self._criticalWarningMessages = []
435
+ self._errorMessages = []
436
+ self._toolIDs = {}
437
+ self._toolNames = {}
438
+ self._messagesByID = {}
439
+
440
+ @readonly
441
+ def Duration(self) -> float:
442
+ return self._duration
443
+
444
+ @readonly
445
+ def InfoMessages(self) -> List[VivadoInfoMessage]:
446
+ return self._infoMessages
447
+
448
+ @readonly
449
+ def WarningMessages(self) -> List[VivadoWarningMessage]:
450
+ return self._warningMessages
451
+
452
+ @readonly
453
+ def CriticalWarningMessages(self) -> List[VivadoCriticalWarningMessage]:
454
+ return self._criticalWarningMessages
455
+
456
+ @readonly
457
+ def ErrorMessages(self) -> List[VivadoErrorMessage]:
458
+ return self._errorMessages
459
+
460
+ def __getitem__(self, item: Type[Parser]) -> Parser:
461
+ return self._parsers[item]
462
+
463
+ def Parse(self):
464
+ with Stopwatch() as sw:
465
+ with self._logfile.open("r", encoding="utf-8") as f:
466
+ content = f.read()
467
+
468
+ activeParsers = list(self._parsers.values())
469
+
470
+ lines = content.splitlines()
471
+ for lineNumber, line in enumerate(l.rstrip() for l in lines):
472
+ prefix = line[:4]
473
+ if prefix == "INFO":
474
+ if (message := VivadoInfoMessage.Parse(line, lineNumber)) is None:
475
+ print(f"pattern not detected\n{line}")
476
+ continue
477
+
478
+ self._infoMessages.append(message)
479
+ elif prefix == "WARN":
480
+ if (message := VivadoWarningMessage.Parse(line, lineNumber)) is None:
481
+ print(f"pattern not detected\n{line}")
482
+ continue
483
+
484
+ self._warningMessages.append(message)
485
+ elif prefix == "CRIT":
486
+ if (message := VivadoCriticalWarningMessage.Parse(line, lineNumber)) is None:
487
+ print(f"pattern not detected\n{line}")
488
+ continue
489
+
490
+ self._criticalWarningMessages.append(message)
491
+ elif prefix == "ERRO":
492
+ if (message := VivadoErrorMessage.Parse(line, lineNumber)) is None:
493
+ print(f"pattern not detected\n{line}")
494
+ continue
495
+
496
+ self._errorMessages.append(message)
497
+ else:
498
+ if self._state is not None:
499
+ result = self._state(lineNumber, line)
500
+ if ProcessingState.Last in result:
501
+ obj: Section = self._state.__self__
502
+ activeParsers.remove(obj)
503
+ self._state = None
504
+
505
+ print(f" DONE: {obj.__class__.__name__}")
506
+ else:
507
+ if line.startswith("Start") or line.startswith("Starting"):
508
+ for parser in activeParsers: # type: Section
509
+ if line.startswith(parser._START):
510
+ print(f"BEGIN: {parser.__class__.__name__}")
511
+ self._state = parser.ParseLine
512
+ _ = self._state(lineNumber, line)
513
+ break
514
+ else:
515
+ print(f"Unknown section\n {line}")
516
+
517
+ continue
518
+
519
+ if message._toolID in self._messagesByID:
520
+ sub = self._messagesByID[message._toolID]
521
+ if message._messageKindID in sub:
522
+ sub[message._messageKindID].append(message)
523
+ else:
524
+ sub[message._messageKindID] = [message]
525
+ else:
526
+ self._toolIDs[message._toolID] = message._toolName
527
+ self._toolNames[message._toolName] = message._toolID
528
+ self._messagesByID[message._toolID] = {message._messageKindID: [message]}
529
+
530
+ self._duration = sw.Duration
531
+
532
+
533
+ def main():
534
+ logfile = Path("tests/data/Stopwatch/toplevel.vds")
535
+ logfile = Path("tests/data/ADL-1000/toplevel.vds")
536
+ processor = Processor(logfile)
537
+ processor.Parse()
538
+
539
+ print()
540
+ print(f"Vivado version: {processor._parsers[Preamble]._toolVersion}")
541
+
542
+
543
+ #
544
+ # print("%" * 80)
545
+ # for toolID, messageGroups in processor._messagesByID.items():
546
+ # print(f"{toolID} ({len(messageGroups)}):")
547
+ # for messageID, messages in messageGroups.items():
548
+ # print(f" {messageID} ({len(messages)}):")
549
+ # for message in messages:
550
+ # print(f" {message}")
551
+ #
552
+ # print("%" * 80)
553
+ # for tool, toolID in processor._toolNames.items():
554
+ # messages = list(chain(*processor._messagesByID[toolID].values()))
555
+ # print(f"{tool} ({len(messages)}):")
556
+ # for message in messages:
557
+ # print(f" {message}")
558
+
559
+ # print(f"Cells Statistics")
560
+ # for cellName, cellCount in processor._parsers[WritingSynthesisReport]._cells.items():
561
+ # print(f" {cellName:16}: {cellCount}")
562
+
563
+ if __name__ == '__main__':
564
+ main()
565
+
566
+
567
+ # latches
568
+ # unused
569
+ # used but not set
570
+ # statemachine encodings
571
+ # resources
572
+ # * RTL
573
+ # * Mapped