pyEDAA.OutputFilter 0.7.1__tar.gz → 0.9.0__tar.gz
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.
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/PKG-INFO +23 -25
- pyedaa_outputfilter-0.9.0/pyEDAA/OutputFilter/CLI/Configuration.py +617 -0
- pyedaa_outputfilter-0.7.1/pyEDAA/OutputFilter/Xilinx/Exception.py → pyedaa_outputfilter-0.9.0/pyEDAA/OutputFilter/CLI/Filter.py +43 -33
- pyedaa_outputfilter-0.9.0/pyEDAA/OutputFilter/CLI/Vivado.py +544 -0
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA/OutputFilter/CLI/__init__.py +9 -6
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA/OutputFilter/Xilinx/OptimizeDesign.py +3 -3
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA/OutputFilter/Xilinx/PhysicalOptimizeDesign.py +11 -3
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA/OutputFilter/Xilinx/PlaceDesign.py +4 -4
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA/OutputFilter/Xilinx/RouteDesign.py +24 -4
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA/OutputFilter/Xilinx/SynthesizeDesign.py +92 -303
- pyedaa_outputfilter-0.9.0/pyEDAA/OutputFilter/Xilinx/__init__.py +4033 -0
- pyedaa_outputfilter-0.9.0/pyEDAA/OutputFilter/__init__.py +278 -0
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA.OutputFilter.egg-info/PKG-INFO +23 -25
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA.OutputFilter.egg-info/SOURCES.txt +2 -4
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA.OutputFilter.egg-info/requires.txt +22 -24
- pyedaa_outputfilter-0.7.1/pyEDAA/OutputFilter/CLI/Vivado.py +0 -352
- pyedaa_outputfilter-0.7.1/pyEDAA/OutputFilter/Xilinx/Commands.py +0 -939
- pyedaa_outputfilter-0.7.1/pyEDAA/OutputFilter/Xilinx/Common.py +0 -710
- pyedaa_outputfilter-0.7.1/pyEDAA/OutputFilter/Xilinx/Common2.py +0 -1675
- pyedaa_outputfilter-0.7.1/pyEDAA/OutputFilter/Xilinx/__init__.py +0 -387
- pyedaa_outputfilter-0.7.1/pyEDAA/OutputFilter/__init__.py +0 -52
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/LICENSE.md +0 -0
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/README.md +0 -0
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA.OutputFilter.egg-info/dependency_links.txt +0 -0
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA.OutputFilter.egg-info/entry_points.txt +0 -0
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyEDAA.OutputFilter.egg-info/top_level.txt +0 -0
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/pyproject.toml +0 -0
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/setup.cfg +0 -0
- {pyedaa_outputfilter-0.7.1 → pyedaa_outputfilter-0.9.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyEDAA.OutputFilter
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Post-processing of EDA Tool outputs (log files).
|
|
5
5
|
Home-page: https://GitHub.com/edaa-org/pyEDAA.OutputFilter
|
|
6
6
|
Author: Patrick Lehmann
|
|
@@ -28,45 +28,43 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
License-File: LICENSE.md
|
|
29
29
|
Requires-Dist: pyTooling[terminal]~=8.14
|
|
30
30
|
Provides-Extra: doc
|
|
31
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: docutils_stubs~=0.0.22; extra == "doc"
|
|
32
32
|
Requires-Dist: sphinxcontrib-autoprogram~=0.1.0; extra == "doc"
|
|
33
|
-
Requires-Dist: sphinx_rtd_theme~=3.1; extra == "doc"
|
|
34
33
|
Requires-Dist: sphinx_autodoc_typehints~=3.10; extra == "doc"
|
|
35
|
-
Requires-Dist: docutils~=0.22.0; extra == "doc"
|
|
36
34
|
Requires-Dist: pyTooling[terminal]~=8.14; extra == "doc"
|
|
37
|
-
Requires-Dist:
|
|
35
|
+
Requires-Dist: sphinx_reports~=0.11.0; extra == "doc"
|
|
36
|
+
Requires-Dist: sphinx~=9.1; extra == "doc"
|
|
38
37
|
Requires-Dist: sphinx_design~=0.7.0; extra == "doc"
|
|
38
|
+
Requires-Dist: sphinx_rtd_theme~=3.1; extra == "doc"
|
|
39
|
+
Requires-Dist: sphinxcontrib-mermaid~=2.0; extra == "doc"
|
|
40
|
+
Requires-Dist: autoapi>=2.0.1; extra == "doc"
|
|
39
41
|
Requires-Dist: sphinx-copybutton>=0.5.2; extra == "doc"
|
|
40
|
-
Requires-Dist: sphinx_reports~=0.10.0; extra == "doc"
|
|
41
|
-
Requires-Dist: docutils_stubs~=0.0.22; extra == "doc"
|
|
42
|
-
Requires-Dist: sphinx~=9.1; extra == "doc"
|
|
43
42
|
Provides-Extra: test
|
|
44
|
-
Requires-Dist: typing_extensions~=4.15; extra == "test"
|
|
45
|
-
Requires-Dist: pytest~=9.0; extra == "test"
|
|
46
|
-
Requires-Dist: pytest-cov~=7.1; extra == "test"
|
|
47
|
-
Requires-Dist: mypy[reports]~=1.19; extra == "test"
|
|
48
43
|
Requires-Dist: lxml<7.0,>=5.4; extra == "test"
|
|
49
44
|
Requires-Dist: pyTooling[terminal]~=8.14; extra == "test"
|
|
50
|
-
Requires-Dist:
|
|
45
|
+
Requires-Dist: pytest~=9.0; extra == "test"
|
|
46
|
+
Requires-Dist: mypy[reports]~=1.19; extra == "test"
|
|
47
|
+
Requires-Dist: pytest-cov~=7.1; extra == "test"
|
|
48
|
+
Requires-Dist: Coverage~=7.14; extra == "test"
|
|
49
|
+
Requires-Dist: typing_extensions~=4.15; extra == "test"
|
|
51
50
|
Provides-Extra: all
|
|
52
|
-
Requires-Dist:
|
|
53
|
-
Requires-Dist: typing_extensions~=4.15; extra == "all"
|
|
54
|
-
Requires-Dist: pytest~=9.0; extra == "all"
|
|
55
|
-
Requires-Dist: mypy[reports]~=1.19; extra == "all"
|
|
51
|
+
Requires-Dist: docutils_stubs~=0.0.22; extra == "all"
|
|
56
52
|
Requires-Dist: sphinxcontrib-autoprogram~=0.1.0; extra == "all"
|
|
57
|
-
Requires-Dist:
|
|
53
|
+
Requires-Dist: lxml<7.0,>=5.4; extra == "all"
|
|
58
54
|
Requires-Dist: sphinx_autodoc_typehints~=3.10; extra == "all"
|
|
59
|
-
Requires-Dist:
|
|
60
|
-
Requires-Dist:
|
|
55
|
+
Requires-Dist: pyTooling[terminal]~=8.14; extra == "all"
|
|
56
|
+
Requires-Dist: pytest~=9.0; extra == "all"
|
|
57
|
+
Requires-Dist: sphinx~=9.1; extra == "all"
|
|
58
|
+
Requires-Dist: mypy[reports]~=1.19; extra == "all"
|
|
61
59
|
Requires-Dist: autoapi>=2.0.1; extra == "all"
|
|
62
60
|
Requires-Dist: pytest-cov~=7.1; extra == "all"
|
|
63
61
|
Requires-Dist: sphinx_design~=0.7.0; extra == "all"
|
|
62
|
+
Requires-Dist: Coverage~=7.14; extra == "all"
|
|
63
|
+
Requires-Dist: typing_extensions~=4.15; extra == "all"
|
|
64
|
+
Requires-Dist: sphinx_reports~=0.11.0; extra == "all"
|
|
65
|
+
Requires-Dist: sphinxcontrib-mermaid~=2.0; extra == "all"
|
|
66
|
+
Requires-Dist: sphinx_rtd_theme~=3.1; extra == "all"
|
|
64
67
|
Requires-Dist: sphinx-copybutton>=0.5.2; extra == "all"
|
|
65
|
-
Requires-Dist: sphinx_reports~=0.10.0; extra == "all"
|
|
66
|
-
Requires-Dist: lxml<7.0,>=5.4; extra == "all"
|
|
67
|
-
Requires-Dist: pyTooling[terminal]~=8.14; extra == "all"
|
|
68
|
-
Requires-Dist: Coverage~=7.13; extra == "all"
|
|
69
|
-
Requires-Dist: sphinx~=9.1; extra == "all"
|
|
70
68
|
Dynamic: author
|
|
71
69
|
Dynamic: author-email
|
|
72
70
|
Dynamic: classifier
|
|
@@ -0,0 +1,617 @@
|
|
|
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
|
+
from enum import StrEnum
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from re import compile as re_compile
|
|
34
|
+
from typing import Optional as Nullable, Dict, List, ClassVar, Set, Self, Any, TextIO, Callable
|
|
35
|
+
|
|
36
|
+
from pyTooling.Decorators import export
|
|
37
|
+
from pyTooling.MetaClasses import ExtendedType, abstractmethod
|
|
38
|
+
from pyTooling.Stopwatch import Stopwatch
|
|
39
|
+
from pyTooling.TerminalUI import TerminalBaseApplication
|
|
40
|
+
from pyTooling.Warning import WarningCollector, Warning
|
|
41
|
+
from ruamel.yaml import YAML, CommentedMap, CommentedSeq
|
|
42
|
+
|
|
43
|
+
from pyEDAA.OutputFilter import OutputFilterException
|
|
44
|
+
from pyEDAA.OutputFilter.Xilinx import VivadoInfoMessage, VivadoWarningMessage, VivadoCriticalWarningMessage, VivadoErrorMessage, LineAction
|
|
45
|
+
from pyEDAA.OutputFilter.Xilinx import Synth_Design, Opt_Design, Link_Design, Place_Design, PhyOpt_Design, Route_Design, Write_Bitstream
|
|
46
|
+
from pyEDAA.OutputFilter.Xilinx import Command, LineKind, VivadoLine
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@export
|
|
50
|
+
class ConfigurationException(OutputFilterException):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@export
|
|
55
|
+
class ConfigurationWarning(Warning):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@export
|
|
60
|
+
class OutputFormat(StrEnum):
|
|
61
|
+
Plain = "plain"
|
|
62
|
+
JSON = "json"
|
|
63
|
+
JSONLine = "jsonl"
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def parse(cls, value: str) -> Self:
|
|
67
|
+
if value not in cls._value2member_map_:
|
|
68
|
+
ex = ValueError(f"'{value}' is not a valid OutputFormat.")
|
|
69
|
+
ex.add_note(f"Allowed values: {', '.join([item.value for item in cls])}")
|
|
70
|
+
raise ex
|
|
71
|
+
|
|
72
|
+
return cls(value)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@export
|
|
76
|
+
class TimestampFormat(StrEnum):
|
|
77
|
+
Undefined = "undefined"
|
|
78
|
+
DateTime = "datetime"
|
|
79
|
+
TimeOnly = "timeonly"
|
|
80
|
+
Runtime = "deltatime"
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def parse(cls, value: str) -> Self:
|
|
84
|
+
if value not in cls._value2member_map_:
|
|
85
|
+
ex = ValueError(f"'{value}' is not a valid TimestampFormat.")
|
|
86
|
+
ex.add_note(f"Allowed values: {', '.join([item.value for item in cls])}")
|
|
87
|
+
raise ex
|
|
88
|
+
|
|
89
|
+
return cls(value)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@export
|
|
93
|
+
class Action(StrEnum):
|
|
94
|
+
Default = "default"
|
|
95
|
+
Remove = "remove"
|
|
96
|
+
Keep = "keep"
|
|
97
|
+
Error = "error"
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def parse(cls, value: str) -> Self:
|
|
101
|
+
if value not in cls._value2member_map_:
|
|
102
|
+
ex = ValueError(f"'{value}' is not a valid Action.")
|
|
103
|
+
ex.add_note(f"Allowed values: {', '.join([item.value for item in cls])}")
|
|
104
|
+
raise ex
|
|
105
|
+
|
|
106
|
+
return cls(value)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@export
|
|
110
|
+
class Level(StrEnum):
|
|
111
|
+
Unknown = ""
|
|
112
|
+
Info = "info"
|
|
113
|
+
Warning = "warning"
|
|
114
|
+
CriticalWarning = "critical warning"
|
|
115
|
+
Error = "error"
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def parse(cls, value: str) -> Self:
|
|
119
|
+
if value not in cls._value2member_map_:
|
|
120
|
+
ex = ValueError(f"'{value}' is not a valid Level.")
|
|
121
|
+
ex.add_note(f"Allowed values: {', '.join([item.value for item in cls])}")
|
|
122
|
+
raise ex
|
|
123
|
+
|
|
124
|
+
return cls(value)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@export
|
|
128
|
+
class Rule(metaclass=ExtendedType, slots=True):
|
|
129
|
+
_optional: bool
|
|
130
|
+
_action: Nullable[Action]
|
|
131
|
+
_level: Nullable[Level]
|
|
132
|
+
|
|
133
|
+
# todo: justification (whitelist/ blacklist / message)
|
|
134
|
+
|
|
135
|
+
def __init__(self, action: Nullable[Action] = None, level: Nullable[Level] = None) -> None:
|
|
136
|
+
self._action = action
|
|
137
|
+
self._level = level
|
|
138
|
+
|
|
139
|
+
@abstractmethod
|
|
140
|
+
def Match(self, line: VivadoLine) -> bool:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
def Process(self, line: VivadoLine) -> None:
|
|
144
|
+
if self._action is Action.Remove:
|
|
145
|
+
line._action = LineAction.Remove
|
|
146
|
+
elif self._action is Action.Default or self._action is Action.Keep:
|
|
147
|
+
line._action = LineAction.Default
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@export
|
|
151
|
+
class AllRule(Rule):
|
|
152
|
+
def __init__(self, action: Nullable[Action] = None) -> None:
|
|
153
|
+
super().__init__(action)
|
|
154
|
+
|
|
155
|
+
def Match(self, line: VivadoLine) -> bool:
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
def __str__(self) -> str:
|
|
159
|
+
return "Rule match all lines"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@export
|
|
163
|
+
class ClassificationRule(Rule):
|
|
164
|
+
_lineKind: LineKind
|
|
165
|
+
|
|
166
|
+
def __init__(self, lineKind: LineKind, action: Nullable[Action] = None, level: Nullable[Level] = None) -> None:
|
|
167
|
+
super().__init__(action, level)
|
|
168
|
+
|
|
169
|
+
self._lineKind = lineKind
|
|
170
|
+
|
|
171
|
+
def Match(self, line: VivadoLine) -> bool:
|
|
172
|
+
return self._lineKind == line._kind
|
|
173
|
+
|
|
174
|
+
def __str__(self) -> str:
|
|
175
|
+
if self._lineKind is LineKind.InfoMessage:
|
|
176
|
+
kind = "info"
|
|
177
|
+
elif self._lineKind is LineKind.Warning:
|
|
178
|
+
kind = "warning"
|
|
179
|
+
elif self._lineKind is LineKind.CriticalWarning:
|
|
180
|
+
kind = "critical warning"
|
|
181
|
+
elif self._lineKind is LineKind.Error:
|
|
182
|
+
kind = "error"
|
|
183
|
+
|
|
184
|
+
return f"Rule for all '{kind}' messages: Level={self._level}; Action={self._action}"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@export
|
|
188
|
+
class VivadoMessageRule(Rule):
|
|
189
|
+
_toolID: int
|
|
190
|
+
_messageKindID: int
|
|
191
|
+
|
|
192
|
+
def __init__(self, toolID: int, messageKindID: int, action: Nullable[Action] = None, level: Nullable[Level] = None) -> None:
|
|
193
|
+
super().__init__(action, level)
|
|
194
|
+
|
|
195
|
+
self._toolID = toolID
|
|
196
|
+
self._messageKindID = messageKindID
|
|
197
|
+
|
|
198
|
+
def Match(self, line: VivadoLine) -> bool:
|
|
199
|
+
if isinstance(line, (VivadoInfoMessage, VivadoWarningMessage, VivadoCriticalWarningMessage, VivadoErrorMessage)):
|
|
200
|
+
return line._toolID == self._toolID and line._messageKindID == self._messageKindID
|
|
201
|
+
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
def __str__(self) -> str:
|
|
205
|
+
return f"Rule for '{self._toolID}-{self._messageKindID}': Level={self._level}; Action={self._action}"
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@export
|
|
209
|
+
class Output(metaclass=ExtendedType, slots=True):
|
|
210
|
+
_parent: "ProcessingPipeline"
|
|
211
|
+
_file: TextIO
|
|
212
|
+
_format: OutputFormat
|
|
213
|
+
_commands: Nullable[List[Command]]
|
|
214
|
+
_rules: Nullable[List[Rule]]
|
|
215
|
+
|
|
216
|
+
def __init__(
|
|
217
|
+
self,
|
|
218
|
+
parent: "ProcessingPipeline",
|
|
219
|
+
format: OutputFormat,
|
|
220
|
+
commands: Nullable[List[Command]],
|
|
221
|
+
rules: Nullable[List[Rule]]
|
|
222
|
+
) -> None:
|
|
223
|
+
self._parent = parent
|
|
224
|
+
self._format = format
|
|
225
|
+
self._commands = commands
|
|
226
|
+
self._rules = rules
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@export
|
|
230
|
+
class StdOutOutput(Output):
|
|
231
|
+
_coloring: bool
|
|
232
|
+
_colors: Dict[str, str]
|
|
233
|
+
_lineNumbers: bool
|
|
234
|
+
_timestampFormat: TimestampFormat
|
|
235
|
+
|
|
236
|
+
def __init__(
|
|
237
|
+
self,
|
|
238
|
+
parent: "ProcessingPipeline",
|
|
239
|
+
coloring: bool,
|
|
240
|
+
format: OutputFormat,
|
|
241
|
+
commands: Nullable[List[Command]],
|
|
242
|
+
rules: Nullable[List[Rule]]
|
|
243
|
+
) -> None:
|
|
244
|
+
super().__init__(parent, format, commands, rules)
|
|
245
|
+
|
|
246
|
+
self._coloring = coloring
|
|
247
|
+
self._colors = parent._parent._colors
|
|
248
|
+
self._lineNumbers = False
|
|
249
|
+
self._timestampFormat = TimestampFormat.Undefined
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@export
|
|
253
|
+
class FileOutput(Output):
|
|
254
|
+
_path: Path
|
|
255
|
+
|
|
256
|
+
def __init__(
|
|
257
|
+
self,
|
|
258
|
+
parent: "ProcessingPipeline",
|
|
259
|
+
file: Path,
|
|
260
|
+
format: OutputFormat,
|
|
261
|
+
commands: List[Command],
|
|
262
|
+
rules: List[Rule]
|
|
263
|
+
) -> None:
|
|
264
|
+
super().__init__(parent, format, commands, rules)
|
|
265
|
+
|
|
266
|
+
self._path = file
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@export
|
|
270
|
+
class ProcessingPipeline(metaclass=ExtendedType, slots=True):
|
|
271
|
+
_ALLOWED_BOOL_VALUES: ClassVar[Dict[str, bool]] = {
|
|
272
|
+
"false": False,
|
|
273
|
+
"true": True,
|
|
274
|
+
"off": False,
|
|
275
|
+
"on": True,
|
|
276
|
+
"no": False,
|
|
277
|
+
"yes": True
|
|
278
|
+
}
|
|
279
|
+
_ALLOWED_COMMANDS: ClassVar[Dict[str, Command]] = {
|
|
280
|
+
"synth_design": Synth_Design,
|
|
281
|
+
"link_design": Link_Design,
|
|
282
|
+
"opt_design": Opt_Design,
|
|
283
|
+
"place_design": Place_Design,
|
|
284
|
+
"phyopt_design": PhyOpt_Design,
|
|
285
|
+
"route_design": Route_Design,
|
|
286
|
+
"write_bitstream": Write_Bitstream
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
_parent: "Vivado"
|
|
290
|
+
_preprocessing: Nullable[List[Rule]]
|
|
291
|
+
_outputs: Dict[str, Output]
|
|
292
|
+
|
|
293
|
+
def __init__(self, parent: "Vivado") -> None:
|
|
294
|
+
self._parent = parent
|
|
295
|
+
self._preprocessing = None
|
|
296
|
+
self._outputs = {
|
|
297
|
+
"stdout": StdOutOutput(self, True, OutputFormat.Plain, None, [])
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
def Parse(self, outputsConfig: CommentedMap) -> None:
|
|
301
|
+
if "preprocessing" in outputsConfig and (preprocessing := outputsConfig["preprocessing"]) is not None:
|
|
302
|
+
try:
|
|
303
|
+
self._preprocessing = list(self._parent._ruleSets[preprocessing].values())
|
|
304
|
+
except KeyError:
|
|
305
|
+
warn = ConfigurationWarning(f"tools.vivado.outputs.preprocessing: Unknown rule set '{preprocessing}'.")
|
|
306
|
+
warn.add_note(f"Rule set '{preprocessing}' not found in tools.vivado.rule-sets.")
|
|
307
|
+
WarningCollector.Raise(warn)
|
|
308
|
+
|
|
309
|
+
for outputName, outputConfig in outputsConfig.items():
|
|
310
|
+
if outputName == "preprocessing":
|
|
311
|
+
continue
|
|
312
|
+
elif outputName == "stdout":
|
|
313
|
+
self._ParseStdOutOutput(outputConfig)
|
|
314
|
+
elif outputName == "stderr":
|
|
315
|
+
pass
|
|
316
|
+
# self._ParseStdErrOutput(outputConfig)
|
|
317
|
+
else:
|
|
318
|
+
self._ParseOutput(outputName, outputConfig)
|
|
319
|
+
|
|
320
|
+
def _ParseStdOutOutput(self, outputConfig: CommentedMap) -> None:
|
|
321
|
+
stdout: StdOutOutput = self._outputs["stdout"]
|
|
322
|
+
|
|
323
|
+
stdout._coloring = self._ParseBoolean(outputConfig, "coloring", False, "tools.vivado.outputs.stdout.coloring")
|
|
324
|
+
stdout._lineNumbers = self._ParseBoolean(outputConfig, "lineNumbers", False, "tools.vivado.outputs.stdout.lineNumbers")
|
|
325
|
+
stdout._timestampFormat = self._ParseTimestampFormat(outputConfig, "timestamps", "tools.vivado.outputs.stdout.timestamps")
|
|
326
|
+
stdout._commands = self._ParseCommands(outputConfig, "commands", "tools.vivado.outputs.stdout.commands")
|
|
327
|
+
stdout._rules = self._ParseRuleSets(outputConfig, "rule-sets", "tools.vivado.outputs.stdout.rule-sets")
|
|
328
|
+
|
|
329
|
+
def _ParseOutput(self, outputName: str, outputConfig: CommentedMap) -> None:
|
|
330
|
+
try:
|
|
331
|
+
path = self._ParsePath(outputConfig, "path", f"tools.vivado.outputs.{outputName}.path")
|
|
332
|
+
except ConfigurationException as ex:
|
|
333
|
+
WarningCollector.Raise(ConfigurationWarning(str(ex)))
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
self._outputs[outputName] = FileOutput(
|
|
337
|
+
self,
|
|
338
|
+
path,
|
|
339
|
+
self._ParseOutputFormat(outputConfig, "format", f"tools.vivado.outputs.{outputName}.format"),
|
|
340
|
+
self._ParseCommands(outputConfig,"commands", f"tools.vivado.outputs.{outputName}.commands"),
|
|
341
|
+
self._ParseRuleSets(outputConfig,"rule-sets", f"tools.vivado.outputs.{outputName}.rule-sets")
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def _ParseBoolean(self, config: CommentedMap, key: str, default: Nullable[bool], configPath: str) -> Nullable[bool]:
|
|
345
|
+
if key not in config:
|
|
346
|
+
return default
|
|
347
|
+
elif isinstance((value := config[key]), bool):
|
|
348
|
+
return value
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
return self._ALLOWED_BOOL_VALUES[value]
|
|
352
|
+
except KeyError:
|
|
353
|
+
WarningCollector.Raise(ConfigurationWarning(f"{configPath}: Unknown value '{value}'."))
|
|
354
|
+
|
|
355
|
+
def _ParsePath(self, config: CommentedMap, key: str, configPath: str) -> Path:
|
|
356
|
+
if key not in config:
|
|
357
|
+
raise ConfigurationException(f"{configPath}: Doesn't exist. A filename is required.")
|
|
358
|
+
elif (pathConfig := config[key]) is None:
|
|
359
|
+
raise ConfigurationException(f"{configPath}: Is empty. A filename is required.")
|
|
360
|
+
elif not isinstance(pathConfig, str):
|
|
361
|
+
raise ConfigurationException(f"{configPath}: Unknown value '{pathConfig}'.")
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
return Path(pathConfig)
|
|
365
|
+
except ValueError as ex:
|
|
366
|
+
raise ConfigurationException(f"{configPath}: Value '{pathConfig}' is not a path.") from ex
|
|
367
|
+
|
|
368
|
+
def _ParseOutputFormat(self, config: CommentedMap, key: str, configPath: str) -> OutputFormat:
|
|
369
|
+
if key not in config:
|
|
370
|
+
return OutputFormat.Plain
|
|
371
|
+
elif (formatConfig := config[key]) is None:
|
|
372
|
+
return OutputFormat.Plain
|
|
373
|
+
elif not isinstance(formatConfig, str):
|
|
374
|
+
WarningCollector.Raise(ConfigurationWarning(f"{configPath}: Unknown settings."))
|
|
375
|
+
return OutputFormat.Plain
|
|
376
|
+
|
|
377
|
+
return OutputFormat.parse(formatConfig)
|
|
378
|
+
|
|
379
|
+
def _ParseTimestampFormat(self, config: CommentedMap, key: str, configPath: str) -> TimestampFormat:
|
|
380
|
+
if key not in config:
|
|
381
|
+
return TimestampFormat.Undefined
|
|
382
|
+
elif (formatConfig := config[key]) is None:
|
|
383
|
+
return TimestampFormat.Undefined
|
|
384
|
+
elif not isinstance(formatConfig, str):
|
|
385
|
+
WarningCollector.Raise(ConfigurationWarning(f"{configPath}: Unknown settings."))
|
|
386
|
+
return TimestampFormat.Undefined
|
|
387
|
+
|
|
388
|
+
return TimestampFormat.parse(formatConfig)
|
|
389
|
+
|
|
390
|
+
def _ParseCommands(self, config: CommentedMap, key: str, configPath: str) -> Nullable[List[Command]]:
|
|
391
|
+
if key not in config:
|
|
392
|
+
return None
|
|
393
|
+
elif (commandsConfig := config[key]) is None:
|
|
394
|
+
return None
|
|
395
|
+
elif not isinstance(commandsConfig, CommentedMap):
|
|
396
|
+
WarningCollector.Raise(ConfigurationWarning(f"{configPath}: Unknown settings."))
|
|
397
|
+
return None
|
|
398
|
+
|
|
399
|
+
commands: List[Command] = []
|
|
400
|
+
for commandName in commandsConfig:
|
|
401
|
+
try:
|
|
402
|
+
commands.append(self._ALLOWED_COMMANDS[commandName])
|
|
403
|
+
except KeyError:
|
|
404
|
+
WarningCollector.Raise(ConfigurationWarning(f"{configPath}: Unknown Vivado TCL command '{commandName}'."))
|
|
405
|
+
|
|
406
|
+
if len(commands) > 0:
|
|
407
|
+
return commands
|
|
408
|
+
|
|
409
|
+
return None
|
|
410
|
+
|
|
411
|
+
def _ParseRuleSets(self, config: CommentedMap, key: str, configPath: str) -> Nullable[List[Rule]]:
|
|
412
|
+
if key not in config:
|
|
413
|
+
return None
|
|
414
|
+
elif (ruleSetsConfig := config[key]) is None:
|
|
415
|
+
return None
|
|
416
|
+
elif not isinstance(ruleSetsConfig, CommentedSeq):
|
|
417
|
+
WarningCollector.Raise(ConfigurationWarning(f"{configPath}: Unknown settings."))
|
|
418
|
+
return None
|
|
419
|
+
|
|
420
|
+
rules: List[Rule] = []
|
|
421
|
+
for ruleSetName in ruleSetsConfig:
|
|
422
|
+
try:
|
|
423
|
+
rules.extend(self._parent._ruleSets[ruleSetName].values())
|
|
424
|
+
except KeyError:
|
|
425
|
+
warn = ConfigurationWarning(f"{configPath}: Unknown rule set '{ruleSetName}'.")
|
|
426
|
+
warn.add_note(f"Rule set '{ruleSetName}' not found in tools.vivado.rule-sets.")
|
|
427
|
+
WarningCollector.Raise(warn)
|
|
428
|
+
|
|
429
|
+
if len(rules) > 0:
|
|
430
|
+
return rules
|
|
431
|
+
|
|
432
|
+
return None
|
|
433
|
+
|
|
434
|
+
def __len__(self) -> int:
|
|
435
|
+
return len(self._outputs)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@export
|
|
439
|
+
class Tool(metaclass=ExtendedType, slots=True):
|
|
440
|
+
pass
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@export
|
|
444
|
+
class Vivado(Tool):
|
|
445
|
+
_ALLOWED_COLORS: ClassVar[Set[str]] = set(TerminalBaseApplication.Foreground.keys())
|
|
446
|
+
|
|
447
|
+
_parent: "Configuration"
|
|
448
|
+
_colors: Dict[str, str]
|
|
449
|
+
_ruleSets: Dict[str, Dict[str, Rule]]
|
|
450
|
+
_processingPipeline: ProcessingPipeline
|
|
451
|
+
_hasLatches: Action
|
|
452
|
+
|
|
453
|
+
def __init__(self, parent: "Configuration") -> None:
|
|
454
|
+
self._parent = parent
|
|
455
|
+
|
|
456
|
+
self._colors = self._InitializeColors()
|
|
457
|
+
self._ruleSets = {}
|
|
458
|
+
self._processingPipeline = ProcessingPipeline(self)
|
|
459
|
+
self._hasLatches = Action.Default
|
|
460
|
+
|
|
461
|
+
def _InitializeColors(self) -> Dict[str, str]:
|
|
462
|
+
return {
|
|
463
|
+
"normal": "WHITE",
|
|
464
|
+
"info": "GRAY", # "DARK_BLUE",
|
|
465
|
+
"warning": "YELLOW",
|
|
466
|
+
"critical": "MAGENTA",
|
|
467
|
+
"error": "RED",
|
|
468
|
+
"tcl": "CYAN",
|
|
469
|
+
"success": "GREEN",
|
|
470
|
+
"failed": "RED",
|
|
471
|
+
"verbose": "GRAY",
|
|
472
|
+
"unprocessed": "DARK_GRAY",
|
|
473
|
+
"empty": "NOCOLOR",
|
|
474
|
+
"sectionDelimiter": "DARK_GRAY",
|
|
475
|
+
"sectionStart": "DARK_CYAN",
|
|
476
|
+
"sectionEnd": "DARK_CYAN",
|
|
477
|
+
"sectionTime": "DARK_GREEN",
|
|
478
|
+
"subsectionStart": "DARK_CYAN",
|
|
479
|
+
"subsectionEnd": "DARK_CYAN",
|
|
480
|
+
"subsectionTime": "DARK_GREEN",
|
|
481
|
+
"taskStart": "YELLOW",
|
|
482
|
+
"taskEnd": "YELLOW",
|
|
483
|
+
"taskTime": "DARK_GREEN",
|
|
484
|
+
"phaseStart": "BLUE",
|
|
485
|
+
"phaseEnd": "BLUE",
|
|
486
|
+
"phaseTime": "DARK_GREEN",
|
|
487
|
+
"subphaseStart": "DARK_CYAN",
|
|
488
|
+
"subphaseEnd": "DARK_CYAN",
|
|
489
|
+
"subphaseTime": "DARK_GREEN",
|
|
490
|
+
"subsubphaseStart": "DARK_CYAN",
|
|
491
|
+
"subsubphaseEnd": "DARK_CYAN",
|
|
492
|
+
"subsubphaseTime": "DARK_GREEN",
|
|
493
|
+
"subsubsubphaseStart": "DARK_CYAN",
|
|
494
|
+
"subsubsubphaseEnd": "DARK_CYAN",
|
|
495
|
+
"subsubsubphaseTime": "DARK_GREEN",
|
|
496
|
+
"nestedTaskStart": "DARK_CYAN",
|
|
497
|
+
"nestedTaskEnd": "DARK_CYAN",
|
|
498
|
+
"nestedTaskTime": "DARK_GREEN",
|
|
499
|
+
"nestedPhaseStart": "DARK_CYAN",
|
|
500
|
+
"nestedPhaseEnd": "DARK_CYAN",
|
|
501
|
+
"nestedPhaseTime": "DARK_GREEN",
|
|
502
|
+
"paragraphHeadline": "DARK_YELLOW",
|
|
503
|
+
"hierarchyStart": "DARK_CYAN",
|
|
504
|
+
"hierarchyEnd": "DARK_GRAY",
|
|
505
|
+
"xdcStart": "DARK_CYAN",
|
|
506
|
+
"xdcEnd": "DARK_GRAY",
|
|
507
|
+
"table": "GRAY",
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
def Parse(self, toolConfig: CommentedMap) -> None:
|
|
511
|
+
if "colors" in toolConfig:
|
|
512
|
+
self._ParseColors(toolConfig["colors"])
|
|
513
|
+
|
|
514
|
+
if "rule-sets" in toolConfig:
|
|
515
|
+
for ruleSetName, ruleSetConfig in toolConfig["rule-sets"].items():
|
|
516
|
+
if len(ruleSetConfig) > 0:
|
|
517
|
+
self._ParseRuleSet(ruleSetName, ruleSetConfig)
|
|
518
|
+
|
|
519
|
+
if "outputs" in toolConfig:
|
|
520
|
+
self._processingPipeline.Parse(toolConfig["outputs"])
|
|
521
|
+
|
|
522
|
+
if "exports" in toolConfig:
|
|
523
|
+
if "cellUsage" in (exportConfig := toolConfig["exports"]):
|
|
524
|
+
pass
|
|
525
|
+
# self._ParseCellUsage(exportConfig["cellUsage"])
|
|
526
|
+
|
|
527
|
+
if "policies" in toolConfig:
|
|
528
|
+
self._ParsePolicies(toolConfig["policies"])
|
|
529
|
+
|
|
530
|
+
def _ParseColors(self, colors: CommentedMap) -> None:
|
|
531
|
+
for lineKind, color in colors.items():
|
|
532
|
+
if lineKind not in self._colors:
|
|
533
|
+
WarningCollector.Raise(ConfigurationWarning(f"tools.vivado.colors: Item '{lineKind}' not supported for coloring."))
|
|
534
|
+
continue
|
|
535
|
+
elif (col := color.upper()) not in self._ALLOWED_COLORS:
|
|
536
|
+
WarningCollector.Raise(ConfigurationWarning(f"tools.vivado.colors.{lineKind}: Color '{color}' is not supported."))
|
|
537
|
+
continue
|
|
538
|
+
|
|
539
|
+
self._colors[lineKind] = col
|
|
540
|
+
|
|
541
|
+
def _ParseRuleSet(self, ruleSetName: str, ruleSetConfig: CommentedMap) -> None:
|
|
542
|
+
self._ruleSets[ruleSetName] = (ruleSet := {})
|
|
543
|
+
|
|
544
|
+
pattern = re_compile(r"^(?P<toolID>\d+)-(?P<messageID>\d+)$")
|
|
545
|
+
|
|
546
|
+
for ruleName, ruleConfig in ruleSetConfig.items():
|
|
547
|
+
if ruleName == "all":
|
|
548
|
+
ruleSet[ruleName] = AllRule(self._ParseRuleSetAction(ruleConfig["action"]))
|
|
549
|
+
elif ruleName == "info":
|
|
550
|
+
ruleSet[ruleName] = ClassificationRule(LineKind.InfoMessage, self._ParseRuleSetAction(ruleConfig["action"]))
|
|
551
|
+
elif ruleName == "warning":
|
|
552
|
+
ruleSet[ruleName] = ClassificationRule(LineKind.WarningMessage, self._ParseRuleSetAction(ruleConfig["action"]))
|
|
553
|
+
elif ruleName == "criticalWarning":
|
|
554
|
+
ruleSet[ruleName] = ClassificationRule(LineKind.CriticalWarningMessage, self._ParseRuleSetAction(ruleConfig["action"]))
|
|
555
|
+
elif ruleName == "error":
|
|
556
|
+
ruleSet[ruleName] = ClassificationRule(LineKind.ErrorMessage, self._ParseRuleSetAction(ruleConfig["action"]))
|
|
557
|
+
elif (match := pattern.match(ruleName)) is not None:
|
|
558
|
+
toolID = int(match.group("toolID"))
|
|
559
|
+
messageID = int(match.group("messageID"))
|
|
560
|
+
ruleSet[ruleName] = VivadoMessageRule(toolID, messageID, self._ParseRuleSetAction(ruleConfig["action"]))
|
|
561
|
+
else:
|
|
562
|
+
WarningCollector.Raise(ConfigurationWarning(f"tools.vivado.rule-sets.{ruleSetName}: Unknown rule '{ruleName}'."))
|
|
563
|
+
|
|
564
|
+
def _ParsePolicies(self, policies: CommentedMap) -> None:
|
|
565
|
+
if "hasLatches" in policies:
|
|
566
|
+
|
|
567
|
+
if policies["hasLatches"] == "error":
|
|
568
|
+
self._hasLatches = Action.Error
|
|
569
|
+
else:
|
|
570
|
+
WarningCollector.Raise(
|
|
571
|
+
ConfigurationWarning(f"tools.vivado.policies.hasLatches: Unknown value '{policies["hasLatches"]}'."))
|
|
572
|
+
|
|
573
|
+
def _ParseRuleSetAction(self, actionConfig: Any) -> LineAction:
|
|
574
|
+
if not isinstance(actionConfig, str):
|
|
575
|
+
WarningCollector.Raise(ConfigurationWarning(f"tools.vivado.rule-sets.<ruleset>.<rule>.action: Unsupported value '{actionConfig}'."))
|
|
576
|
+
try:
|
|
577
|
+
return Action(actionConfig)
|
|
578
|
+
except ValueError:
|
|
579
|
+
WarningCollector.Raise(ConfigurationWarning(f"tools.vivado.rule-sets.<ruleset>.<rule>.action: Unknown value '{actionConfig}'."))
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
@export
|
|
583
|
+
class Configuration(metaclass=ExtendedType, slots=True):
|
|
584
|
+
_file: Path
|
|
585
|
+
_yamlDocument: YAML
|
|
586
|
+
_yamlLoadTime: float
|
|
587
|
+
|
|
588
|
+
_tools: Dict[str, Tool]
|
|
589
|
+
|
|
590
|
+
def __init__(self, file: Path) -> None:
|
|
591
|
+
self._file = file
|
|
592
|
+
self._tools = {}
|
|
593
|
+
|
|
594
|
+
with Stopwatch() as sw:
|
|
595
|
+
try:
|
|
596
|
+
yamlReader = YAML()
|
|
597
|
+
self._yamlDocument = yamlReader.load(self._file)
|
|
598
|
+
except Exception as ex:
|
|
599
|
+
raise ConfigurationException(f"Couldn't open '{self._file}'.") from ex
|
|
600
|
+
|
|
601
|
+
self._yamlLoadTime = sw.Duration
|
|
602
|
+
|
|
603
|
+
self.Parse()
|
|
604
|
+
|
|
605
|
+
def Parse(self) ->None:
|
|
606
|
+
if (version := self._yamlDocument["version"]) != "0.1":
|
|
607
|
+
ex = ConfigurationException(f"Configuration file version {version} is not supported.")
|
|
608
|
+
ex.add_note("Supported versions: 0.1")
|
|
609
|
+
raise ex
|
|
610
|
+
|
|
611
|
+
self._Parse_v0_1()
|
|
612
|
+
|
|
613
|
+
def _Parse_v0_1(self) -> None:
|
|
614
|
+
for toolName, toolConfig in self._yamlDocument["tools"].items():
|
|
615
|
+
if toolName == "vivado":
|
|
616
|
+
self._tools["vivado"] = (vivado := Vivado(self))
|
|
617
|
+
vivado.Parse(toolConfig)
|