siliconcompiler 0.34.2__py3-none-any.whl → 0.34.3__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.
- siliconcompiler/__init__.py +12 -5
- siliconcompiler/__main__.py +1 -7
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/_common.py +104 -23
- siliconcompiler/apps/sc.py +4 -8
- siliconcompiler/apps/sc_dashboard.py +6 -4
- siliconcompiler/apps/sc_install.py +10 -6
- siliconcompiler/apps/sc_issue.py +7 -5
- siliconcompiler/apps/sc_remote.py +1 -1
- siliconcompiler/apps/sc_server.py +9 -14
- siliconcompiler/apps/sc_show.py +6 -5
- siliconcompiler/apps/smake.py +130 -94
- siliconcompiler/apps/utils/replay.py +4 -7
- siliconcompiler/apps/utils/summarize.py +3 -5
- siliconcompiler/asic.py +420 -0
- siliconcompiler/checklist.py +25 -2
- siliconcompiler/cmdlineschema.py +534 -0
- siliconcompiler/constraints/asic_component.py +2 -2
- siliconcompiler/constraints/asic_pins.py +2 -2
- siliconcompiler/constraints/asic_timing.py +3 -3
- siliconcompiler/core.py +7 -32
- siliconcompiler/data/templates/tcl/manifest.tcl.j2 +8 -0
- siliconcompiler/dependencyschema.py +89 -31
- siliconcompiler/design.py +176 -207
- siliconcompiler/filesetschema.py +250 -0
- siliconcompiler/flowgraph.py +274 -95
- siliconcompiler/fpga.py +124 -1
- siliconcompiler/library.py +218 -20
- siliconcompiler/metric.py +233 -20
- siliconcompiler/package/__init__.py +271 -50
- siliconcompiler/package/git.py +92 -16
- siliconcompiler/package/github.py +108 -12
- siliconcompiler/package/https.py +79 -16
- siliconcompiler/packageschema.py +88 -7
- siliconcompiler/pathschema.py +31 -2
- siliconcompiler/pdk.py +566 -1
- siliconcompiler/project.py +1095 -94
- siliconcompiler/record.py +38 -1
- siliconcompiler/remote/__init__.py +5 -2
- siliconcompiler/remote/client.py +11 -6
- siliconcompiler/remote/schema.py +5 -23
- siliconcompiler/remote/server.py +41 -54
- siliconcompiler/report/__init__.py +3 -3
- siliconcompiler/report/dashboard/__init__.py +48 -14
- siliconcompiler/report/dashboard/cli/__init__.py +99 -21
- siliconcompiler/report/dashboard/cli/board.py +364 -179
- siliconcompiler/report/dashboard/web/__init__.py +90 -12
- siliconcompiler/report/dashboard/web/components/__init__.py +219 -240
- siliconcompiler/report/dashboard/web/components/flowgraph.py +49 -26
- siliconcompiler/report/dashboard/web/components/graph.py +139 -100
- siliconcompiler/report/dashboard/web/layouts/__init__.py +29 -1
- siliconcompiler/report/dashboard/web/layouts/_common.py +38 -2
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph.py +39 -26
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_node_tab.py +50 -50
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_sac_tabs.py +49 -46
- siliconcompiler/report/dashboard/web/state.py +141 -14
- siliconcompiler/report/dashboard/web/utils/__init__.py +79 -16
- siliconcompiler/report/dashboard/web/utils/file_utils.py +74 -11
- siliconcompiler/report/dashboard/web/viewer.py +25 -1
- siliconcompiler/report/report.py +5 -2
- siliconcompiler/report/summary_image.py +29 -11
- siliconcompiler/scheduler/__init__.py +9 -1
- siliconcompiler/scheduler/docker.py +79 -1
- siliconcompiler/scheduler/run_node.py +35 -19
- siliconcompiler/scheduler/scheduler.py +208 -24
- siliconcompiler/scheduler/schedulernode.py +372 -46
- siliconcompiler/scheduler/send_messages.py +77 -29
- siliconcompiler/scheduler/slurm.py +76 -12
- siliconcompiler/scheduler/taskscheduler.py +140 -20
- siliconcompiler/schema/__init__.py +0 -2
- siliconcompiler/schema/baseschema.py +194 -38
- siliconcompiler/schema/journal.py +7 -4
- siliconcompiler/schema/namedschema.py +16 -10
- siliconcompiler/schema/parameter.py +55 -9
- siliconcompiler/schema/parametervalue.py +60 -0
- siliconcompiler/schema/safeschema.py +25 -2
- siliconcompiler/schema/schema_cfg.py +5 -5
- siliconcompiler/schema/utils.py +2 -2
- siliconcompiler/schema_obj.py +20 -3
- siliconcompiler/tool.py +979 -302
- siliconcompiler/tools/bambu/__init__.py +41 -0
- siliconcompiler/tools/builtin/concatenate.py +2 -2
- siliconcompiler/tools/builtin/minimum.py +2 -1
- siliconcompiler/tools/builtin/mux.py +2 -1
- siliconcompiler/tools/builtin/nop.py +2 -1
- siliconcompiler/tools/builtin/verify.py +2 -1
- siliconcompiler/tools/klayout/__init__.py +95 -0
- siliconcompiler/tools/openroad/__init__.py +289 -0
- siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +3 -0
- siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +7 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +8 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +9 -5
- siliconcompiler/tools/openroad/scripts/common/write_images.tcl +5 -1
- siliconcompiler/tools/slang/__init__.py +1 -1
- siliconcompiler/tools/slang/elaborate.py +2 -1
- siliconcompiler/tools/vivado/scripts/sc_run.tcl +1 -1
- siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +8 -1
- siliconcompiler/tools/vivado/syn_fpga.py +6 -0
- siliconcompiler/tools/vivado/vivado.py +35 -2
- siliconcompiler/tools/vpr/__init__.py +150 -0
- siliconcompiler/tools/yosys/__init__.py +369 -1
- siliconcompiler/tools/yosys/scripts/procs.tcl +0 -1
- siliconcompiler/toolscripts/_tools.json +5 -10
- siliconcompiler/utils/__init__.py +66 -0
- siliconcompiler/utils/flowgraph.py +2 -2
- siliconcompiler/utils/issue.py +2 -1
- siliconcompiler/utils/logging.py +14 -0
- siliconcompiler/utils/multiprocessing.py +256 -0
- siliconcompiler/utils/showtools.py +10 -0
- {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/METADATA +5 -5
- {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/RECORD +115 -118
- {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/entry_points.txt +3 -0
- siliconcompiler/schema/cmdlineschema.py +0 -250
- siliconcompiler/toolscripts/rhel8/install-slang.sh +0 -40
- siliconcompiler/toolscripts/rhel9/install-slang.sh +0 -40
- siliconcompiler/toolscripts/ubuntu20/install-slang.sh +0 -47
- siliconcompiler/toolscripts/ubuntu22/install-slang.sh +0 -37
- siliconcompiler/toolscripts/ubuntu24/install-slang.sh +0 -37
- {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/top_level.txt +0 -0
siliconcompiler/project.py
CHANGED
|
@@ -1,31 +1,47 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import inspect
|
|
1
3
|
import logging
|
|
2
4
|
import os
|
|
5
|
+
import shutil
|
|
3
6
|
import sys
|
|
4
7
|
import uuid
|
|
5
8
|
|
|
6
9
|
import os.path
|
|
7
10
|
|
|
8
|
-
from
|
|
11
|
+
from inspect import getfullargspec
|
|
12
|
+
from typing import Set, Union, List, Tuple, Type, Callable, TextIO
|
|
9
13
|
|
|
10
|
-
from siliconcompiler.schema import BaseSchema, NamedSchema, EditableSchema, Parameter
|
|
14
|
+
from siliconcompiler.schema import BaseSchema, NamedSchema, EditableSchema, Parameter, Scope
|
|
15
|
+
from siliconcompiler.schema.parametervalue import NodeListValue, NodeSetValue
|
|
16
|
+
from siliconcompiler.schema.utils import trim
|
|
11
17
|
|
|
12
|
-
from siliconcompiler import DesignSchema
|
|
18
|
+
from siliconcompiler import DesignSchema, LibrarySchema
|
|
13
19
|
from siliconcompiler import FlowgraphSchema
|
|
14
20
|
from siliconcompiler import RecordSchema
|
|
15
21
|
from siliconcompiler import MetricSchema
|
|
16
22
|
from siliconcompiler import ChecklistSchema
|
|
17
23
|
from siliconcompiler import ToolSchema, TaskSchema
|
|
24
|
+
from siliconcompiler import ShowTaskSchema, ScreenshotTaskSchema
|
|
18
25
|
|
|
26
|
+
from siliconcompiler.cmdlineschema import CommandLineSchema
|
|
27
|
+
from siliconcompiler.dependencyschema import DependencySchema
|
|
19
28
|
from siliconcompiler.pathschema import PathSchemaBase
|
|
20
29
|
|
|
21
30
|
from siliconcompiler.schema.schema_cfg import schema_option_runtime, schema_arg, schema_version
|
|
22
31
|
|
|
23
|
-
from siliconcompiler.
|
|
32
|
+
from siliconcompiler.report.dashboard.cli import CliDashboard
|
|
33
|
+
from siliconcompiler.scheduler import Scheduler
|
|
24
34
|
from siliconcompiler.utils.logging import SCColorLoggerFormatter, SCLoggerFormatter
|
|
35
|
+
from siliconcompiler.utils import FilterDirectories, get_file_ext
|
|
36
|
+
from siliconcompiler.utils.multiprocessing import MPManager
|
|
25
37
|
|
|
26
38
|
|
|
27
|
-
class Project(PathSchemaBase, BaseSchema):
|
|
39
|
+
class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
28
40
|
"""
|
|
41
|
+
The Project class is the core object in SiliconCompiler, representing a
|
|
42
|
+
complete hardware design project. It manages design parameters, libraries,
|
|
43
|
+
flowgraphs, metrics, and provides methods for compilation, data collection,
|
|
44
|
+
and reporting.
|
|
29
45
|
"""
|
|
30
46
|
|
|
31
47
|
def __init__(self, design: Union[DesignSchema, str] = None):
|
|
@@ -45,11 +61,60 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
45
61
|
|
|
46
62
|
# Add options
|
|
47
63
|
schema_option_runtime(schema)
|
|
48
|
-
schema.insert(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
schema.insert(
|
|
65
|
+
"option", "env", "default",
|
|
66
|
+
Parameter(
|
|
67
|
+
"str",
|
|
68
|
+
scope=Scope.GLOBAL,
|
|
69
|
+
shorthelp="Option: environment variables",
|
|
70
|
+
example=["api: project.set('option', 'env', 'PDK_HOME', '/disk/mypdk')"],
|
|
71
|
+
help=trim("""
|
|
72
|
+
Certain tools and reference flows require global environment
|
|
73
|
+
variables to be set. These variables can be managed externally or
|
|
74
|
+
specified through the env variable.""")))
|
|
75
|
+
|
|
76
|
+
schema.insert(
|
|
77
|
+
"option", "design",
|
|
78
|
+
Parameter(
|
|
79
|
+
"str",
|
|
80
|
+
scope=Scope.GLOBAL,
|
|
81
|
+
shorthelp="Option: Design library name",
|
|
82
|
+
example=["cli: -design hello_world",
|
|
83
|
+
"api: project.set('option', 'design', 'hello_world')"],
|
|
84
|
+
switch=["-design <str>"],
|
|
85
|
+
help="Name of the top level library"))
|
|
86
|
+
schema.insert(
|
|
87
|
+
"option", "alias",
|
|
88
|
+
Parameter(
|
|
89
|
+
"[(str,str,str,str)]",
|
|
90
|
+
scope=Scope.GLOBAL,
|
|
91
|
+
shorthelp="Option: Fileset alias mapping",
|
|
92
|
+
example=["api: project.set('option', 'alias', ('design', 'rtl', 'lambda', 'rtl')"],
|
|
93
|
+
help=trim("""List of filesets to alias during a run. When an alias is specific
|
|
94
|
+
it will be used instead of the source fileset. It is useful when you
|
|
95
|
+
want to substitute a fileset from one library with a fileset from another,
|
|
96
|
+
without changing the original design's code.
|
|
97
|
+
For example, you might use it to swap in a different version of an IP
|
|
98
|
+
block or a specific test environment.""")))
|
|
99
|
+
schema.insert(
|
|
100
|
+
"option", "fileset",
|
|
101
|
+
Parameter(
|
|
102
|
+
"[str]",
|
|
103
|
+
scope=Scope.GLOBAL,
|
|
104
|
+
shorthelp="Option: Selected design filesets",
|
|
105
|
+
example=["api: project.set('option', 'fileset', 'rtl')"],
|
|
106
|
+
help=trim("""List of filesets to use from the selected design library""")))
|
|
107
|
+
|
|
108
|
+
schema.insert(
|
|
109
|
+
"option", "nodashboard",
|
|
110
|
+
Parameter(
|
|
111
|
+
"bool",
|
|
112
|
+
defvalue=False,
|
|
113
|
+
scope=Scope.GLOBAL,
|
|
114
|
+
switch=["-nodashboard <bool>"],
|
|
115
|
+
shorthelp="Option: Disables the dashboard",
|
|
116
|
+
example=["api: project.set('option', 'nodashboard', True)"],
|
|
117
|
+
help=trim("""Disables the dashboard during execution""")))
|
|
53
118
|
|
|
54
119
|
# Add history
|
|
55
120
|
schema.insert("history", BaseSchema())
|
|
@@ -66,11 +131,13 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
66
131
|
else:
|
|
67
132
|
self.set_design(design)
|
|
68
133
|
|
|
134
|
+
self.__init_dashboard()
|
|
135
|
+
|
|
69
136
|
def __init_logger(self):
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
self.__logger
|
|
137
|
+
"""
|
|
138
|
+
Initializes the project-specific logger.
|
|
139
|
+
"""
|
|
140
|
+
self.__logger = MPManager.logger().getChild(f"project_{uuid.uuid4().hex}")
|
|
74
141
|
self.__logger.setLevel(logging.INFO)
|
|
75
142
|
|
|
76
143
|
self._logger_console = logging.StreamHandler(stream=sys.stdout)
|
|
@@ -81,6 +148,33 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
81
148
|
|
|
82
149
|
self.__logger.addHandler(self._logger_console)
|
|
83
150
|
|
|
151
|
+
def __init_dashboard(self):
|
|
152
|
+
"""
|
|
153
|
+
Initializes or disables the CLI dashboard for the project.
|
|
154
|
+
|
|
155
|
+
If the 'nodashboard' option is set to True, any existing dashboard
|
|
156
|
+
instance is stopped and set to None. Otherwise, a new `CliDashboard`
|
|
157
|
+
instance is created and assigned to the project.
|
|
158
|
+
"""
|
|
159
|
+
if self.get("option", "nodashboard"):
|
|
160
|
+
try:
|
|
161
|
+
if self.__dashboard:
|
|
162
|
+
self.__dashboard.stop()
|
|
163
|
+
except AttributeError:
|
|
164
|
+
pass
|
|
165
|
+
self.__dashboard = None
|
|
166
|
+
else:
|
|
167
|
+
self.__dashboard = CliDashboard(self)
|
|
168
|
+
|
|
169
|
+
def set(self, *args, field='value', clobber=True, step=None, index=None):
|
|
170
|
+
ret = super().set(*args, field=field, clobber=clobber, step=step, index=index)
|
|
171
|
+
|
|
172
|
+
# Special handling keys
|
|
173
|
+
if args[0:2] == ("option", "nodashboard"):
|
|
174
|
+
self.__init_dashboard()
|
|
175
|
+
|
|
176
|
+
return ret
|
|
177
|
+
|
|
84
178
|
@property
|
|
85
179
|
def logger(self) -> logging.Logger:
|
|
86
180
|
"""
|
|
@@ -115,48 +209,328 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
115
209
|
"""
|
|
116
210
|
return self.__cwd
|
|
117
211
|
|
|
212
|
+
@classmethod
|
|
213
|
+
def convert(cls, obj: "Project") -> "Project":
|
|
214
|
+
"""
|
|
215
|
+
Convert a project from one type to another
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
obj: Source object to convert from
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
new object of the new class type
|
|
222
|
+
"""
|
|
223
|
+
if not isinstance(obj, Project):
|
|
224
|
+
raise TypeError("source object must be a Project")
|
|
225
|
+
|
|
226
|
+
new_obj = cls()
|
|
227
|
+
|
|
228
|
+
root_keys = new_obj.getkeys()
|
|
229
|
+
import_keys = set(root_keys).intersection(obj.getkeys())
|
|
230
|
+
|
|
231
|
+
if not issubclass(cls, obj.__class__):
|
|
232
|
+
for rm in ("checklist", "flowgraph", "metric", "record", "tool", "schemaversion"):
|
|
233
|
+
try:
|
|
234
|
+
import_keys.remove(rm)
|
|
235
|
+
except KeyError:
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
manifest = obj.getdict()
|
|
239
|
+
for key in list(manifest.keys()):
|
|
240
|
+
if key not in import_keys:
|
|
241
|
+
del manifest[key]
|
|
242
|
+
|
|
243
|
+
new_obj._from_dict(manifest, [])
|
|
244
|
+
|
|
245
|
+
return new_obj
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def _getdict_type(cls) -> str:
|
|
249
|
+
"""
|
|
250
|
+
Returns the meta data for getdict
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
return Project.__name__
|
|
254
|
+
|
|
255
|
+
def __populate_deps(self, obj: DependencySchema = None):
|
|
256
|
+
"""
|
|
257
|
+
Ensures that all loaded dependencies (like libraries) within the project
|
|
258
|
+
contain correct internal pointers back to the project's libraries.
|
|
259
|
+
This is crucial for maintaining a consistent and navigable schema graph.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
obj (DependencySchema, optional): An optional dependency object to
|
|
263
|
+
reset and populate. If None, all existing library dependencies
|
|
264
|
+
in the project are processed. Defaults to None.
|
|
265
|
+
"""
|
|
266
|
+
if obj:
|
|
267
|
+
obj._reset_deps()
|
|
268
|
+
dep_map = {name: self.get("library", name, field="schema")
|
|
269
|
+
for name in self.getkeys("library")}
|
|
270
|
+
for obj in dep_map.values():
|
|
271
|
+
if isinstance(obj, DependencySchema):
|
|
272
|
+
obj._populate_deps(dep_map)
|
|
273
|
+
|
|
274
|
+
def _from_dict(self, manifest, keypath, version=None):
|
|
275
|
+
"""
|
|
276
|
+
Populates the project's schema from a dictionary representation.
|
|
277
|
+
|
|
278
|
+
This method is typically used during deserialization or when loading
|
|
279
|
+
a project state from a manifest. After loading the data, it ensures
|
|
280
|
+
that internal dependencies are correctly re-established.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
manifest (dict): The dictionary containing the schema data.
|
|
284
|
+
keypath (list): The current keypath being processed (used internally
|
|
285
|
+
for recursive loading).
|
|
286
|
+
version (str, optional): The schema version of the manifest. Defaults to None.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Any: The result of the superclass's `_from_dict` method.
|
|
290
|
+
"""
|
|
291
|
+
ret = super()._from_dict(manifest, keypath, version)
|
|
292
|
+
|
|
293
|
+
# Restore dependencies
|
|
294
|
+
self.__populate_deps()
|
|
295
|
+
|
|
296
|
+
return ret
|
|
297
|
+
|
|
298
|
+
def load_target(self, target: Union[str, Callable[["Project"], None]], **kwargs):
|
|
299
|
+
"""
|
|
300
|
+
Loads and executes a target function or method within the project context.
|
|
301
|
+
|
|
302
|
+
This method allows dynamically loading a Python function (e.g., a target
|
|
303
|
+
defined in a separate module) and executing it. It performs type checking
|
|
304
|
+
to ensure the target function accepts a Project object as its first
|
|
305
|
+
required argument and that the current project instance is compatible
|
|
306
|
+
with the target's expected Project type.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
target (Union[str, Callable[["Project"], None]]):
|
|
310
|
+
The target to load. This can be:
|
|
311
|
+
- A string in the format "module.submodule.function_name"
|
|
312
|
+
- A callable Python function that accepts a Project object as its
|
|
313
|
+
first argument.
|
|
314
|
+
**kwargs: Arbitrary keyword arguments to pass to the target function.
|
|
315
|
+
|
|
316
|
+
Raises:
|
|
317
|
+
ValueError: If the target string path is incomplete, if the target
|
|
318
|
+
signature does not meet the requirements (e.g., no
|
|
319
|
+
required arguments, or more than one required argument).
|
|
320
|
+
TypeError: If the target does not take a Project object as its
|
|
321
|
+
first argument, or if the current project instance is
|
|
322
|
+
not compatible with the target's required Project type.
|
|
323
|
+
"""
|
|
324
|
+
if isinstance(target, str):
|
|
325
|
+
if "." not in target:
|
|
326
|
+
raise ValueError("unable to process incomplete function path")
|
|
327
|
+
|
|
328
|
+
*module, func = target.split(".")
|
|
329
|
+
module = ".".join(module)
|
|
330
|
+
|
|
331
|
+
mod = importlib.import_module(module)
|
|
332
|
+
target = getattr(mod, func)
|
|
333
|
+
|
|
334
|
+
func_spec = getfullargspec(target)
|
|
335
|
+
|
|
336
|
+
args_len = len(func_spec.args or []) - len(func_spec.defaults or [])
|
|
337
|
+
|
|
338
|
+
if args_len == 0 and not func_spec.args:
|
|
339
|
+
raise ValueError('target signature cannot must take at least one argument')
|
|
340
|
+
if args_len > 1:
|
|
341
|
+
raise ValueError('target signature cannot have more than one required argument')
|
|
342
|
+
|
|
343
|
+
proj_arg = func_spec.args[0]
|
|
344
|
+
required_type = func_spec.annotations.get(proj_arg, Project)
|
|
345
|
+
|
|
346
|
+
if not issubclass(required_type, Project):
|
|
347
|
+
raise TypeError("target must take in a Project object")
|
|
348
|
+
|
|
349
|
+
if not isinstance(self, required_type):
|
|
350
|
+
raise TypeError(f"target requires a {required_type.__name__} project")
|
|
351
|
+
|
|
352
|
+
target(self, **kwargs)
|
|
353
|
+
|
|
118
354
|
def add_dep(self, obj):
|
|
355
|
+
"""
|
|
356
|
+
Adds a dependency object (e.g., a DesignSchema, FlowgraphSchema, LibrarySchema,
|
|
357
|
+
or ChecklistSchema) to the project.
|
|
358
|
+
|
|
359
|
+
This method intelligently adds various types of schema objects to the
|
|
360
|
+
project's internal structure. It also handles recursive addition of
|
|
361
|
+
dependencies if the added object itself is a `DependencySchema`.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
obj (Union[DesignSchema, FlowgraphSchema, LibrarySchema, ChecklistSchema,
|
|
365
|
+
List, Set, Tuple]):
|
|
366
|
+
The dependency object(s) to add. Can be a single schema object
|
|
367
|
+
or a collection (list, set, tuple) of schema objects.
|
|
368
|
+
|
|
369
|
+
Raises:
|
|
370
|
+
NotImplementedError: If the type of the object is not supported.
|
|
371
|
+
"""
|
|
372
|
+
if isinstance(obj, (list, set, tuple)):
|
|
373
|
+
for iobj in obj:
|
|
374
|
+
self.add_dep(iobj)
|
|
375
|
+
return
|
|
376
|
+
|
|
119
377
|
if isinstance(obj, DesignSchema):
|
|
120
|
-
self.
|
|
378
|
+
if not self.has_library(obj.name):
|
|
379
|
+
EditableSchema(self).insert("library", obj.name, obj)
|
|
121
380
|
elif isinstance(obj, FlowgraphSchema):
|
|
122
381
|
self.__import_flow(obj)
|
|
382
|
+
elif isinstance(obj, LibrarySchema):
|
|
383
|
+
if not self.has_library(obj.name):
|
|
384
|
+
EditableSchema(self).insert("library", obj.name, obj)
|
|
385
|
+
elif isinstance(obj, ChecklistSchema):
|
|
386
|
+
if obj.name not in self.getkeys("checklist"):
|
|
387
|
+
EditableSchema(self).insert("checklist", obj.name, obj)
|
|
123
388
|
else:
|
|
124
389
|
raise NotImplementedError
|
|
125
390
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
edit_schema.insert("library", design.name(), design, clobber=True)
|
|
391
|
+
# Copy dependencies into project
|
|
392
|
+
self._import_dep(obj)
|
|
129
393
|
|
|
394
|
+
def _import_dep(self, obj: DependencySchema):
|
|
130
395
|
# Copy dependencies into project
|
|
131
|
-
|
|
132
|
-
|
|
396
|
+
if isinstance(obj, DependencySchema):
|
|
397
|
+
for dep in obj.get_dep():
|
|
398
|
+
self.add_dep(dep)
|
|
399
|
+
|
|
400
|
+
# Rebuild dependencies to ensure instances are correct
|
|
401
|
+
self.__populate_deps(obj)
|
|
133
402
|
|
|
134
403
|
def __import_flow(self, flow: FlowgraphSchema):
|
|
404
|
+
"""
|
|
405
|
+
Imports a FlowgraphSchema into the project.
|
|
406
|
+
|
|
407
|
+
If the flowgraph with the given name is not already present, it is
|
|
408
|
+
added to the project's flowgraph schema. This method also instantiates
|
|
409
|
+
and registers all tasks defined within the imported flowgraph, ensuring
|
|
410
|
+
that the necessary tool and task schemas are available.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
flow (FlowgraphSchema): The flowgraph schema object to import.
|
|
414
|
+
"""
|
|
415
|
+
if flow.name in self.getkeys("flowgraph"):
|
|
416
|
+
return
|
|
417
|
+
|
|
135
418
|
edit_schema = EditableSchema(self)
|
|
136
|
-
edit_schema.insert("flowgraph", flow.name
|
|
419
|
+
edit_schema.insert("flowgraph", flow.name, flow)
|
|
137
420
|
|
|
138
421
|
# Instantiate tasks
|
|
139
422
|
for task_cls in flow.get_all_tasks():
|
|
140
423
|
task = task_cls()
|
|
141
424
|
# TODO: this is not needed once tool moves
|
|
142
|
-
|
|
143
|
-
|
|
425
|
+
if not self.valid("tool", task.tool()):
|
|
426
|
+
edit_schema.insert("tool", task.tool(), ToolSchema())
|
|
427
|
+
if not self.valid("tool", task.tool(), "task", task.task()):
|
|
428
|
+
edit_schema.insert("tool", task.tool(), "task", task.task(), task)
|
|
429
|
+
|
|
430
|
+
def check_manifest(self) -> bool:
|
|
431
|
+
"""
|
|
432
|
+
Performs a comprehensive check of the project's manifest (configuration)
|
|
433
|
+
for consistency and validity.
|
|
434
|
+
|
|
435
|
+
This method verifies that essential options like 'design', 'fileset',
|
|
436
|
+
and 'flow' are properly set. It also checks if the specified design
|
|
437
|
+
and flowgraph are loaded, and if filesets within the selected design
|
|
438
|
+
are valid and have a top module defined. Additionally, it validates
|
|
439
|
+
any defined fileset aliases, ensuring that source and destination
|
|
440
|
+
libraries and filesets exist. Error messages are logged for any
|
|
441
|
+
detected inconsistencies.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
bool: True if the manifest is valid and all checks pass, False otherwise.
|
|
445
|
+
"""
|
|
446
|
+
error = False
|
|
144
447
|
|
|
145
|
-
def check_manifest(self):
|
|
146
448
|
# Assert design is set
|
|
449
|
+
design = self.get("option", "design")
|
|
450
|
+
if not design:
|
|
451
|
+
self.logger.error("[option,design] has not been set")
|
|
452
|
+
error = True
|
|
453
|
+
else:
|
|
454
|
+
# Assert design is a library
|
|
455
|
+
if not self.has_library(design):
|
|
456
|
+
self.logger.error(f"{design} has not been loaded")
|
|
457
|
+
error = True
|
|
458
|
+
|
|
147
459
|
# Assert fileset is set
|
|
148
|
-
|
|
460
|
+
filesets = self.get("option", "fileset")
|
|
461
|
+
if not filesets:
|
|
462
|
+
self.logger.error("[option,fileset] has not been set")
|
|
463
|
+
error = True
|
|
464
|
+
elif design: # Only check fileset in design if design is valid
|
|
465
|
+
# Assert fileset is in design
|
|
466
|
+
design_obj = self.design # This is a mock object
|
|
467
|
+
for fileset in filesets:
|
|
468
|
+
if not design_obj.has_fileset(fileset):
|
|
469
|
+
self.logger.error(f"{fileset} is not a valid fileset in {design}")
|
|
470
|
+
error = True
|
|
471
|
+
|
|
472
|
+
# Assert design has topmodule
|
|
473
|
+
# This check only happens if filesets are provided and design is valid
|
|
474
|
+
if filesets and design_obj.has_fileset(filesets[0]):
|
|
475
|
+
if not design_obj.get_topmodule(filesets[0]):
|
|
476
|
+
self.logger.error(f"topmodule has not been set in {design}/{filesets[0]}")
|
|
477
|
+
error = True
|
|
149
478
|
|
|
150
|
-
# Assert
|
|
151
|
-
|
|
152
|
-
|
|
479
|
+
# Assert flow is set
|
|
480
|
+
flow = self.get("option", "flow")
|
|
481
|
+
if not flow:
|
|
482
|
+
self.logger.error("[option,flow] has not been set")
|
|
483
|
+
error = True
|
|
484
|
+
else:
|
|
485
|
+
if flow not in self.getkeys("flowgraph"):
|
|
486
|
+
self.logger.error(f"{flow} has not been loaded")
|
|
487
|
+
error = True
|
|
153
488
|
|
|
154
489
|
# Check that alias libraries exist
|
|
490
|
+
# Default to an empty list if 'alias' is not set, to avoid TypeError
|
|
491
|
+
aliases = self.get("option", "alias") or []
|
|
492
|
+
for src_lib, src_fileset, dst_lib, dst_fileset in aliases:
|
|
493
|
+
if not src_lib:
|
|
494
|
+
self.logger.error("source library in [option,alias] must be set")
|
|
495
|
+
error = True
|
|
496
|
+
continue
|
|
497
|
+
|
|
498
|
+
# If src_lib is not in getkeys("library"), skip further checks for this alias
|
|
499
|
+
# as the error would have been caught earlier if it was a 'design' check.
|
|
500
|
+
# This path is for aliases where src_lib itself might not be a primary design.
|
|
501
|
+
if not self.has_library(src_lib):
|
|
502
|
+
continue
|
|
503
|
+
|
|
504
|
+
if not self.get("library", src_lib, field="schema").has_fileset(src_fileset):
|
|
505
|
+
self.logger.error(f"{src_fileset} is not a valid fileset in {src_lib}")
|
|
506
|
+
error = True
|
|
507
|
+
continue
|
|
508
|
+
|
|
509
|
+
if not dst_lib:
|
|
510
|
+
continue
|
|
511
|
+
|
|
512
|
+
if not self.has_library(dst_lib):
|
|
513
|
+
self.logger.error(f"{dst_lib} has not been loaded")
|
|
514
|
+
error = True
|
|
515
|
+
continue
|
|
516
|
+
|
|
517
|
+
if dst_fileset and \
|
|
518
|
+
not self.get("library", dst_lib, field="schema").has_fileset(dst_fileset):
|
|
519
|
+
self.logger.error(f"{dst_fileset} is not a valid fileset in {dst_lib}")
|
|
520
|
+
error = True
|
|
521
|
+
continue
|
|
155
522
|
|
|
156
523
|
# Check flowgraph
|
|
157
524
|
# Check tasks have classes, cannot check post setup that is a runtime check
|
|
158
525
|
|
|
159
|
-
return
|
|
526
|
+
return not error
|
|
527
|
+
|
|
528
|
+
def _init_run(self):
|
|
529
|
+
"""
|
|
530
|
+
Method called before calling :meth:`.check_manifest` to provide a mechanism to
|
|
531
|
+
setup the project correctly.
|
|
532
|
+
"""
|
|
533
|
+
pass
|
|
160
534
|
|
|
161
535
|
def run(self, raise_exception=False):
|
|
162
536
|
'''
|
|
@@ -190,7 +564,14 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
190
564
|
>>> run()
|
|
191
565
|
Runs the execution flow defined by the flowgraph dictionary.
|
|
192
566
|
'''
|
|
193
|
-
from siliconcompiler.remote
|
|
567
|
+
from siliconcompiler.remote import ClientScheduler
|
|
568
|
+
|
|
569
|
+
# Start dashboard
|
|
570
|
+
if self.__dashboard:
|
|
571
|
+
if not self.__dashboard.is_running():
|
|
572
|
+
self.__dashboard.open_dashboard()
|
|
573
|
+
# Attach logger
|
|
574
|
+
self.__dashboard.set_logger(self.logger)
|
|
194
575
|
|
|
195
576
|
try:
|
|
196
577
|
if self.get('option', 'remote'):
|
|
@@ -204,17 +585,22 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
204
585
|
self.logger.error(str(e))
|
|
205
586
|
return False
|
|
206
587
|
finally:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
# self._dash.end_of_run()
|
|
588
|
+
if self.__dashboard:
|
|
589
|
+
# Update dashboard
|
|
590
|
+
self.__dashboard.update_manifest()
|
|
591
|
+
self.__dashboard.end_of_run()
|
|
212
592
|
|
|
213
593
|
return True
|
|
214
594
|
|
|
215
|
-
def
|
|
595
|
+
def _getbuilddir(self) -> str:
|
|
216
596
|
"""
|
|
217
|
-
Returns the path to the build directory
|
|
597
|
+
Returns the absolute path to the project's build directory.
|
|
598
|
+
|
|
599
|
+
This directory is where all intermediate and final compilation
|
|
600
|
+
artifacts are stored.
|
|
601
|
+
|
|
602
|
+
Returns:
|
|
603
|
+
str: The absolute path to the build directory.
|
|
218
604
|
"""
|
|
219
605
|
builddir = self.get('option', 'builddir')
|
|
220
606
|
if os.path.isabs(builddir):
|
|
@@ -224,17 +610,31 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
224
610
|
|
|
225
611
|
def getworkdir(self, step: str = None, index: Union[int, str] = None) -> str:
|
|
226
612
|
"""
|
|
227
|
-
Returns absolute path to the
|
|
228
|
-
|
|
613
|
+
Returns the absolute path to the working directory for a given
|
|
614
|
+
step and index within the project's job structure.
|
|
615
|
+
|
|
616
|
+
The directory structure is typically:
|
|
617
|
+
`<build_dir>/<design_name>/<job_name>/<step>/<index>/`
|
|
618
|
+
|
|
619
|
+
If `step` and `index` are not provided, the job directory is returned.
|
|
620
|
+
If `step` is provided but `index` is not, index '0' is assumed.
|
|
229
621
|
|
|
230
622
|
Args:
|
|
231
|
-
step (str):
|
|
232
|
-
|
|
623
|
+
step (str, optional): The name of the flowgraph step (e.g., 'syn', 'place').
|
|
624
|
+
Defaults to None.
|
|
625
|
+
index (Union[int, str], optional): The index of the task within the step.
|
|
626
|
+
Defaults to None (implies '0' if step is set).
|
|
627
|
+
|
|
628
|
+
Returns:
|
|
629
|
+
str: The absolute path to the specified working directory.
|
|
630
|
+
|
|
631
|
+
Raises:
|
|
632
|
+
ValueError: If the design name is not set in the project.
|
|
233
633
|
"""
|
|
234
634
|
if not self.name:
|
|
235
635
|
raise ValueError("name has not been set")
|
|
236
636
|
|
|
237
|
-
dirlist = [self.
|
|
637
|
+
dirlist = [self._getbuilddir(),
|
|
238
638
|
self.name,
|
|
239
639
|
self.get('option', 'jobname')]
|
|
240
640
|
|
|
@@ -251,12 +651,158 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
251
651
|
|
|
252
652
|
def getcollectiondir(self):
|
|
253
653
|
"""
|
|
254
|
-
Returns absolute path to collected files
|
|
654
|
+
Returns the absolute path to the directory where collected files are stored.
|
|
655
|
+
|
|
656
|
+
This directory is typically located within the project's working directory
|
|
657
|
+
and is used to consolidate files marked for collection.
|
|
658
|
+
|
|
659
|
+
Returns:
|
|
660
|
+
str: The absolute path to the collected files directory.
|
|
255
661
|
"""
|
|
256
662
|
return os.path.join(self.getworkdir(), "sc_collected_files")
|
|
257
663
|
|
|
258
|
-
def collect(self,
|
|
259
|
-
|
|
664
|
+
def collect(self,
|
|
665
|
+
directory: str = None,
|
|
666
|
+
verbose: bool = True,
|
|
667
|
+
whitelist: List[str] = None):
|
|
668
|
+
'''
|
|
669
|
+
Collects files found in the configuration dictionary and places
|
|
670
|
+
them in :meth:`.getcollectiondir`. The function only copies in files that have the 'copy'
|
|
671
|
+
field set as true.
|
|
672
|
+
|
|
673
|
+
Args:
|
|
674
|
+
directory (filepath): Output filepath
|
|
675
|
+
verbose (bool): Flag to indicate if logging should be used
|
|
676
|
+
whitelist (list[path]): List of directories that are allowed to be
|
|
677
|
+
collected. If a directory is is found that is not on this list
|
|
678
|
+
a RuntimeError will be raised.
|
|
679
|
+
'''
|
|
680
|
+
|
|
681
|
+
if not directory:
|
|
682
|
+
directory = self.getcollectiondir()
|
|
683
|
+
directory = os.path.abspath(directory)
|
|
684
|
+
|
|
685
|
+
# Remove existing directory
|
|
686
|
+
if os.path.exists(directory):
|
|
687
|
+
shutil.rmtree(directory)
|
|
688
|
+
os.makedirs(directory)
|
|
689
|
+
|
|
690
|
+
if verbose:
|
|
691
|
+
self.logger.info(f'Collecting files to: {directory}')
|
|
692
|
+
|
|
693
|
+
dirs = {}
|
|
694
|
+
files = {}
|
|
695
|
+
|
|
696
|
+
for key in self.allkeys():
|
|
697
|
+
if key[0] == 'history':
|
|
698
|
+
# skip history
|
|
699
|
+
continue
|
|
700
|
+
|
|
701
|
+
# Skip runtime directories
|
|
702
|
+
if key == ('option', 'builddir'):
|
|
703
|
+
# skip builddir
|
|
704
|
+
continue
|
|
705
|
+
if key == ('option', 'cachedir'):
|
|
706
|
+
# skip cache
|
|
707
|
+
continue
|
|
708
|
+
|
|
709
|
+
if key[0] == 'tool' and key[2] == 'task' and key[4] in ('input',
|
|
710
|
+
'report',
|
|
711
|
+
'output'):
|
|
712
|
+
# skip flow files files from builds
|
|
713
|
+
continue
|
|
714
|
+
|
|
715
|
+
leaftype = self.get(*key, field='type')
|
|
716
|
+
is_dir = "dir" in leaftype
|
|
717
|
+
is_file = "file" in leaftype
|
|
718
|
+
|
|
719
|
+
if not is_dir and not is_file:
|
|
720
|
+
continue
|
|
721
|
+
|
|
722
|
+
if not self.get(*key, field='copy'):
|
|
723
|
+
continue
|
|
724
|
+
|
|
725
|
+
for values, step, index in self.get(*key, field=None).getvalues(return_values=False):
|
|
726
|
+
if not values.has_value:
|
|
727
|
+
continue
|
|
728
|
+
|
|
729
|
+
if isinstance(values, (NodeSetValue, NodeListValue)):
|
|
730
|
+
values = values.values
|
|
731
|
+
else:
|
|
732
|
+
values = [values]
|
|
733
|
+
|
|
734
|
+
if is_dir:
|
|
735
|
+
dirs[(key, step, index)] = values
|
|
736
|
+
else:
|
|
737
|
+
files[(key, step, index)] = values
|
|
738
|
+
|
|
739
|
+
path_filter = FilterDirectories(self)
|
|
740
|
+
for key, step, index in sorted(dirs.keys()):
|
|
741
|
+
abs_paths = self.find_files(*key, step=step, index=index)
|
|
742
|
+
|
|
743
|
+
new_paths = set()
|
|
744
|
+
|
|
745
|
+
if not isinstance(abs_paths, (list, tuple, set)):
|
|
746
|
+
abs_paths = [abs_paths]
|
|
747
|
+
|
|
748
|
+
abs_paths = zip(abs_paths, dirs[(key, step, index)])
|
|
749
|
+
abs_paths = sorted(abs_paths, key=lambda p: p[0])
|
|
750
|
+
|
|
751
|
+
for abs_path, value in abs_paths:
|
|
752
|
+
if not abs_path:
|
|
753
|
+
raise FileNotFoundError(f"{value.get()} could not be copied")
|
|
754
|
+
|
|
755
|
+
if abs_path.startswith(directory):
|
|
756
|
+
# File already imported in directory
|
|
757
|
+
continue
|
|
758
|
+
|
|
759
|
+
imported = False
|
|
760
|
+
for new_path in new_paths:
|
|
761
|
+
if abs_path.startwith(new_path):
|
|
762
|
+
imported = True
|
|
763
|
+
break
|
|
764
|
+
if imported:
|
|
765
|
+
continue
|
|
766
|
+
|
|
767
|
+
new_paths.add(abs_path)
|
|
768
|
+
|
|
769
|
+
import_path = os.path.join(directory, value.get_hashed_filename())
|
|
770
|
+
if os.path.exists(import_path):
|
|
771
|
+
continue
|
|
772
|
+
|
|
773
|
+
if whitelist is not None and abs_path not in whitelist:
|
|
774
|
+
raise RuntimeError(f'{abs_path} is not on the approved collection list.')
|
|
775
|
+
|
|
776
|
+
if verbose:
|
|
777
|
+
self.logger.info(f" Collecting directory: {abs_path}")
|
|
778
|
+
path_filter.abspath = abs_path
|
|
779
|
+
shutil.copytree(abs_path, import_path, ignore=path_filter.filter)
|
|
780
|
+
path_filter.abspath = None
|
|
781
|
+
|
|
782
|
+
for key, step, index in sorted(files.keys()):
|
|
783
|
+
abs_paths = self.find_files(*key, step=step, index=index)
|
|
784
|
+
|
|
785
|
+
if not isinstance(abs_paths, (list, tuple, set)):
|
|
786
|
+
abs_paths = [abs_paths]
|
|
787
|
+
|
|
788
|
+
abs_paths = zip(abs_paths, files[(key, step, index)])
|
|
789
|
+
abs_paths = sorted(abs_paths, key=lambda p: p[0])
|
|
790
|
+
|
|
791
|
+
for abs_path, value in abs_paths:
|
|
792
|
+
if not abs_path:
|
|
793
|
+
raise FileNotFoundError(f"{value.get()} could not be copied")
|
|
794
|
+
|
|
795
|
+
if abs_path.startswith(directory):
|
|
796
|
+
# File already imported in directory
|
|
797
|
+
continue
|
|
798
|
+
|
|
799
|
+
import_path = os.path.join(directory, value.get_hashed_filename())
|
|
800
|
+
if os.path.exists(import_path):
|
|
801
|
+
continue
|
|
802
|
+
|
|
803
|
+
if verbose:
|
|
804
|
+
self.logger.info(f" Collecting file: {abs_path}")
|
|
805
|
+
shutil.copy2(abs_path, import_path)
|
|
260
806
|
|
|
261
807
|
def history(self, job: str) -> "Project":
|
|
262
808
|
'''
|
|
@@ -298,6 +844,9 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
298
844
|
del state["_Project__logger"]
|
|
299
845
|
del state["_logger_console"]
|
|
300
846
|
|
|
847
|
+
# Remove dashboard
|
|
848
|
+
del state["_Project__dashboard"]
|
|
849
|
+
|
|
301
850
|
return state
|
|
302
851
|
|
|
303
852
|
def __setstate__(self, state):
|
|
@@ -306,6 +855,9 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
306
855
|
# Reinitialize logger on restore
|
|
307
856
|
self.__init_logger()
|
|
308
857
|
|
|
858
|
+
# Restore dashboard
|
|
859
|
+
self.__init_dashboard()
|
|
860
|
+
|
|
309
861
|
def get_filesets(self) -> List[Tuple[NamedSchema, str]]:
|
|
310
862
|
"""
|
|
311
863
|
Returns the filesets selected for this project
|
|
@@ -314,7 +866,7 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
314
866
|
alias = {}
|
|
315
867
|
for src_lib, src_fileset, dst_lib, dst_fileset in self.get("option", "alias"):
|
|
316
868
|
if dst_lib:
|
|
317
|
-
if not self.
|
|
869
|
+
if not self.has_library(dst_lib):
|
|
318
870
|
raise KeyError(f"{dst_lib} is not a loaded library")
|
|
319
871
|
dst_obj = self.get("library", dst_lib, field="schema")
|
|
320
872
|
else:
|
|
@@ -326,28 +878,75 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
326
878
|
return self.design.get_fileset(self.get("option", "fileset"), alias=alias)
|
|
327
879
|
|
|
328
880
|
def get_task(self,
|
|
329
|
-
tool: str,
|
|
330
|
-
task: str,
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
881
|
+
tool: str = None,
|
|
882
|
+
task: str = None,
|
|
883
|
+
filter: Union[Type[TaskSchema], Callable[[TaskSchema], bool]] = None) -> \
|
|
884
|
+
Union[Set[TaskSchema], TaskSchema]:
|
|
885
|
+
"""Retrieves tasks based on specified criteria.
|
|
886
|
+
|
|
887
|
+
This method allows you to fetch tasks by tool name, task name, or by applying a custom
|
|
888
|
+
filter. If a single task matches the criteria, that task object is returned directly.
|
|
889
|
+
If multiple tasks match, a set of :class:`TaskSchema` objects is returned.
|
|
890
|
+
If no criteria are provided, all available tasks are returned.
|
|
891
|
+
|
|
892
|
+
Args:
|
|
893
|
+
tool (str, optional): The name of the tool to filter tasks by. Defaults to None.
|
|
894
|
+
task (str, optional): The name of the task to filter by. Defaults to None.
|
|
895
|
+
filter (Union[Type[TaskSchema], Callable[[TaskSchema], bool]], optional):
|
|
896
|
+
A filter to apply to the tasks. This can be:
|
|
897
|
+
- A `Type[TaskSchema]`: Only tasks that are instances of this type will be returned.
|
|
898
|
+
- A `Callable[[TaskSchema], bool]`: A function that takes a `TaskSchema` object
|
|
899
|
+
and returns `True` if the task should be included, `False` otherwise.
|
|
900
|
+
Defaults to None.
|
|
901
|
+
|
|
902
|
+
Returns:
|
|
903
|
+
Union[Set[TaskSchema], TaskSchema]:
|
|
904
|
+
- If exactly one task matches the criteria, returns that single `TaskSchema` object.
|
|
905
|
+
- If multiple tasks match or no specific tool/task is provided (and thus all tasks
|
|
906
|
+
are considered), returns a `Set[TaskSchema]` containing the matching tasks.
|
|
907
|
+
"""
|
|
908
|
+
all_tasks: Set[TaskSchema] = set()
|
|
909
|
+
for tool_name in self.getkeys("tool"):
|
|
910
|
+
for task_name in self.getkeys("tool", tool_name, "task"):
|
|
911
|
+
all_tasks.add(self.get("tool", tool_name, "task", task_name, field="schema"))
|
|
912
|
+
|
|
913
|
+
tasks = set()
|
|
914
|
+
for task_obj in all_tasks:
|
|
915
|
+
if tool and task_obj.tool() != tool:
|
|
916
|
+
continue
|
|
917
|
+
if task and task_obj.task() != task:
|
|
918
|
+
continue
|
|
919
|
+
if filter:
|
|
920
|
+
if inspect.isclass(filter):
|
|
921
|
+
if not isinstance(task_obj, filter):
|
|
922
|
+
continue
|
|
923
|
+
elif callable(filter):
|
|
924
|
+
if not filter(task_obj):
|
|
925
|
+
continue
|
|
926
|
+
tasks.add(task_obj)
|
|
927
|
+
|
|
928
|
+
if len(tasks) == 1:
|
|
929
|
+
return list(tasks)[0]
|
|
930
|
+
return tasks
|
|
340
931
|
|
|
341
932
|
def set_design(self, design: Union[DesignSchema, str]):
|
|
342
933
|
"""
|
|
343
|
-
|
|
934
|
+
Sets the active design for this project.
|
|
935
|
+
|
|
936
|
+
This method allows you to specify the primary design that the project
|
|
937
|
+
will operate on. If a `DesignSchema` object is provided, it is first
|
|
938
|
+
added as a dependency.
|
|
344
939
|
|
|
345
940
|
Args:
|
|
346
|
-
design (
|
|
941
|
+
design (Union[DesignSchema, str]): The design object or its name (string)
|
|
942
|
+
to be set as the current design.
|
|
943
|
+
|
|
944
|
+
Raises:
|
|
945
|
+
TypeError: If the provided `design` is not a string or a `DesignSchema` object.
|
|
347
946
|
"""
|
|
348
947
|
if isinstance(design, DesignSchema):
|
|
349
948
|
self.add_dep(design)
|
|
350
|
-
design = design.name
|
|
949
|
+
design = design.name
|
|
351
950
|
elif not isinstance(design, str):
|
|
352
951
|
raise TypeError("design must be string or Design object")
|
|
353
952
|
|
|
@@ -355,14 +954,22 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
355
954
|
|
|
356
955
|
def set_flow(self, flow: Union[FlowgraphSchema, str]):
|
|
357
956
|
"""
|
|
358
|
-
|
|
957
|
+
Sets the active flowgraph for this project.
|
|
958
|
+
|
|
959
|
+
This method allows you to specify the sequence of steps and tasks
|
|
960
|
+
(the flow) that the project will execute. If a `FlowgraphSchema` object
|
|
961
|
+
is provided, it is first added as a dependency.
|
|
359
962
|
|
|
360
963
|
Args:
|
|
361
|
-
|
|
964
|
+
flow (Union[FlowgraphSchema, str]): The flowgraph object or its name (string)
|
|
965
|
+
to be set as the current flow.
|
|
966
|
+
|
|
967
|
+
Raises:
|
|
968
|
+
TypeError: If the provided `flow` is not a string or a `FlowgraphSchema` object.
|
|
362
969
|
"""
|
|
363
970
|
if isinstance(flow, FlowgraphSchema):
|
|
364
971
|
self.add_dep(flow)
|
|
365
|
-
flow = flow.name
|
|
972
|
+
flow = flow.name
|
|
366
973
|
elif not isinstance(flow, str):
|
|
367
974
|
raise TypeError("flow must be string or Flowgraph object")
|
|
368
975
|
|
|
@@ -370,25 +977,37 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
370
977
|
|
|
371
978
|
def add_fileset(self, fileset: Union[List[str], str], clobber: bool = False):
|
|
372
979
|
"""
|
|
373
|
-
|
|
980
|
+
Adds one or more filesets to be used in this project.
|
|
981
|
+
|
|
982
|
+
Filesets are collections of related files within a design. This method
|
|
983
|
+
allows you to specify which filesets from the selected design library
|
|
984
|
+
should be included in the current project context.
|
|
374
985
|
|
|
375
986
|
Args:
|
|
376
|
-
fileset (
|
|
377
|
-
|
|
987
|
+
fileset (Union[List[str], str]): The name(s) of the fileset(s) to add.
|
|
988
|
+
Can be a single string or a list of strings.
|
|
989
|
+
clobber (bool): If True, existing filesets will be replaced by the new ones.
|
|
990
|
+
If False, new filesets will be added to the existing list.
|
|
991
|
+
Defaults to False.
|
|
992
|
+
|
|
993
|
+
Raises:
|
|
994
|
+
TypeError: If `fileset` is not a string or a list/tuple/set of strings.
|
|
995
|
+
ValueError: If any of the specified filesets are not found in the currently
|
|
996
|
+
selected design.
|
|
378
997
|
"""
|
|
379
998
|
if not isinstance(fileset, str):
|
|
380
999
|
if isinstance(fileset, (list, tuple, set)):
|
|
381
1000
|
if not all([isinstance(v, str) for v in fileset]):
|
|
382
|
-
raise TypeError("fileset must be a string")
|
|
1001
|
+
raise TypeError("fileset must be a string or a list/tuple/set of strings")
|
|
383
1002
|
else:
|
|
384
|
-
raise TypeError("fileset must be a string")
|
|
1003
|
+
raise TypeError("fileset must be a string or a list/tuple/set of strings")
|
|
385
1004
|
|
|
386
1005
|
if isinstance(fileset, str):
|
|
387
1006
|
fileset = [fileset]
|
|
388
1007
|
|
|
389
1008
|
for fs in fileset:
|
|
390
|
-
if
|
|
391
|
-
raise ValueError(f"{fs} is not a valid fileset in {self.design.name
|
|
1009
|
+
if not self.design.has_fileset(fs):
|
|
1010
|
+
raise ValueError(f"{fs} is not a valid fileset in {self.design.name}")
|
|
392
1011
|
|
|
393
1012
|
if clobber:
|
|
394
1013
|
return self.set("option", "fileset", fileset)
|
|
@@ -402,54 +1021,78 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
402
1021
|
alias_fileset: str,
|
|
403
1022
|
clobber: bool = False):
|
|
404
1023
|
"""
|
|
405
|
-
|
|
1024
|
+
Adds an aliased fileset mapping to the project.
|
|
1025
|
+
|
|
1026
|
+
This method allows you to redirect a fileset reference from a source
|
|
1027
|
+
library/fileset to a different destination library/fileset. This is
|
|
1028
|
+
useful for substituting design components or test environments without
|
|
1029
|
+
modifying the original design.
|
|
406
1030
|
|
|
407
1031
|
Args:
|
|
408
|
-
src_dep (
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
1032
|
+
src_dep (Union[DesignSchema, str]): The source design library (object or name)
|
|
1033
|
+
from which the fileset is being aliased.
|
|
1034
|
+
src_fileset (str): The name of the source fileset to alias.
|
|
1035
|
+
alias_dep (Union[DesignSchema, str]): The destination design library (object or name)
|
|
1036
|
+
to which the fileset is being redirected.
|
|
1037
|
+
Can be None or an empty string to indicate
|
|
1038
|
+
deletion.
|
|
1039
|
+
alias_fileset (str): The name of the destination fileset. Can be None or an empty string
|
|
1040
|
+
to indicate deletion of the fileset reference.
|
|
1041
|
+
clobber (bool): If True, any existing alias for `(src_dep, src_fileset)` will be
|
|
1042
|
+
overwritten. If False, the alias will be added (or updated if it's
|
|
1043
|
+
the same source). Defaults to False.
|
|
1044
|
+
|
|
1045
|
+
Raises:
|
|
1046
|
+
TypeError: If `src_dep` or `alias_dep` are not valid types (string or DesignSchema).
|
|
1047
|
+
KeyError: If `alias_dep` is a string but the corresponding library is not loaded.
|
|
1048
|
+
ValueError: If `src_fileset` is not found in `src_dep`, or if `alias_fileset` is
|
|
1049
|
+
not found in `alias_dep` (when `alias_fileset` is not None).
|
|
413
1050
|
"""
|
|
414
1051
|
|
|
415
1052
|
if isinstance(src_dep, str):
|
|
416
|
-
if
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
1053
|
+
if self.has_library(src_dep):
|
|
1054
|
+
src_dep = self.get("library", src_dep, field="schema")
|
|
1055
|
+
else:
|
|
1056
|
+
src_dep_name = src_dep
|
|
1057
|
+
src_dep = None
|
|
1058
|
+
|
|
1059
|
+
if src_dep is not None:
|
|
1060
|
+
if isinstance(src_dep, DesignSchema):
|
|
1061
|
+
src_dep_name = src_dep.name
|
|
1062
|
+
if not self.has_library(src_dep_name):
|
|
1063
|
+
self.add_dep(src_dep)
|
|
1064
|
+
else:
|
|
1065
|
+
raise TypeError("source dep is not a valid type")
|
|
426
1066
|
|
|
427
|
-
|
|
428
|
-
|
|
1067
|
+
if not src_dep.has_fileset(src_fileset):
|
|
1068
|
+
raise ValueError(f"{src_dep_name} does not have {src_fileset} as a fileset")
|
|
429
1069
|
|
|
430
1070
|
if alias_dep is None:
|
|
431
1071
|
alias_dep = ""
|
|
432
1072
|
|
|
1073
|
+
if alias_fileset == "":
|
|
1074
|
+
alias_fileset = None
|
|
1075
|
+
|
|
433
1076
|
if isinstance(alias_dep, str):
|
|
434
1077
|
if alias_dep == "":
|
|
435
1078
|
alias_dep = None
|
|
436
|
-
alias_dep_name =
|
|
437
|
-
alias_fileset =
|
|
1079
|
+
alias_dep_name = None
|
|
1080
|
+
alias_fileset = None
|
|
438
1081
|
else:
|
|
439
|
-
if
|
|
1082
|
+
if not self.has_library(alias_dep):
|
|
440
1083
|
raise KeyError(f"{alias_dep} has not been loaded")
|
|
441
1084
|
|
|
442
1085
|
alias_dep = self.get("library", alias_dep, field="schema")
|
|
443
1086
|
|
|
444
1087
|
if alias_dep is not None:
|
|
445
1088
|
if isinstance(alias_dep, DesignSchema):
|
|
446
|
-
alias_dep_name = alias_dep.name
|
|
447
|
-
if
|
|
1089
|
+
alias_dep_name = alias_dep.name
|
|
1090
|
+
if not self.has_library(alias_dep_name):
|
|
448
1091
|
self.add_dep(alias_dep)
|
|
449
1092
|
else:
|
|
450
1093
|
raise TypeError("alias dep is not a valid type")
|
|
451
1094
|
|
|
452
|
-
if alias_fileset
|
|
1095
|
+
if alias_fileset is not None and not alias_dep.has_fileset(alias_fileset):
|
|
453
1096
|
raise ValueError(f"{alias_dep_name} does not have {alias_fileset} as a fileset")
|
|
454
1097
|
|
|
455
1098
|
alias = (src_dep_name, src_fileset, alias_dep_name, alias_fileset)
|
|
@@ -457,3 +1100,361 @@ class Project(PathSchemaBase, BaseSchema):
|
|
|
457
1100
|
return self.set("option", "alias", alias)
|
|
458
1101
|
else:
|
|
459
1102
|
return self.add("option", "alias", alias)
|
|
1103
|
+
|
|
1104
|
+
def has_library(self, library: str) -> bool:
|
|
1105
|
+
"""
|
|
1106
|
+
Checks if a library with the given name exists and is loaded in the project.
|
|
1107
|
+
|
|
1108
|
+
Args:
|
|
1109
|
+
library (Union[str, NamedSchema]): The name of the library (string)
|
|
1110
|
+
or a `NamedSchema` object representing the library.
|
|
1111
|
+
|
|
1112
|
+
Returns:
|
|
1113
|
+
bool: True if the library exists, False otherwise.
|
|
1114
|
+
"""
|
|
1115
|
+
|
|
1116
|
+
if isinstance(library, NamedSchema):
|
|
1117
|
+
library = library.name
|
|
1118
|
+
|
|
1119
|
+
return library in self.getkeys("library")
|
|
1120
|
+
|
|
1121
|
+
def _summary_headers(self) -> List[Tuple[str, str]]:
|
|
1122
|
+
"""
|
|
1123
|
+
Generates a list of key-value pairs representing project-specific headers
|
|
1124
|
+
to be included in the summary report.
|
|
1125
|
+
|
|
1126
|
+
This method provides information about the selected design, filesets,
|
|
1127
|
+
any active aliases, and the job directory. Projects can extend this
|
|
1128
|
+
method to add custom information to their summaries.
|
|
1129
|
+
|
|
1130
|
+
Returns:
|
|
1131
|
+
List[Tuple[str, str]]: A list of tuples, where each tuple contains
|
|
1132
|
+
a header name (str) and its corresponding value (str).
|
|
1133
|
+
"""
|
|
1134
|
+
|
|
1135
|
+
alias = []
|
|
1136
|
+
for src, src_fs, dst, dst_fs in self.get("option", "alias"):
|
|
1137
|
+
if not self.has_library(src):
|
|
1138
|
+
continue
|
|
1139
|
+
if dst and not self.has_library(dst):
|
|
1140
|
+
continue
|
|
1141
|
+
|
|
1142
|
+
aliased = f"{src} ({src_fs}) -> "
|
|
1143
|
+
if not dst:
|
|
1144
|
+
aliased += "deleted"
|
|
1145
|
+
elif not dst_fs:
|
|
1146
|
+
aliased += "deleted"
|
|
1147
|
+
else:
|
|
1148
|
+
aliased += f"{dst} ({dst_fs})"
|
|
1149
|
+
alias.append(aliased)
|
|
1150
|
+
|
|
1151
|
+
filesets = self.get("option", "fileset")
|
|
1152
|
+
|
|
1153
|
+
headers = [
|
|
1154
|
+
("design", self.get("option", "design"))
|
|
1155
|
+
]
|
|
1156
|
+
if filesets:
|
|
1157
|
+
headers.append(("filesets", ", ".join(filesets)))
|
|
1158
|
+
if alias:
|
|
1159
|
+
headers.append(("alias", ", ".join(alias)))
|
|
1160
|
+
headers.append(("jobdir", self.getworkdir()))
|
|
1161
|
+
|
|
1162
|
+
return headers
|
|
1163
|
+
|
|
1164
|
+
def _snapshot_info(self) -> List[Tuple[str, str]]:
|
|
1165
|
+
"""
|
|
1166
|
+
Generates a list of key-value pairs representing project-specific
|
|
1167
|
+
information to be included in snapshots.
|
|
1168
|
+
|
|
1169
|
+
This method provides basic information about the design used in the
|
|
1170
|
+
snapshot. Projects can extend this method to add custom information.
|
|
1171
|
+
|
|
1172
|
+
Returns:
|
|
1173
|
+
List[Tuple[str, str]]: A list of tuples, where each tuple contains
|
|
1174
|
+
an information label (str) and its corresponding value (str).
|
|
1175
|
+
"""
|
|
1176
|
+
|
|
1177
|
+
info = [
|
|
1178
|
+
("Design", self.get("option", "design"))
|
|
1179
|
+
]
|
|
1180
|
+
|
|
1181
|
+
return info
|
|
1182
|
+
|
|
1183
|
+
def summary(self, jobname: str = None, fd: TextIO = None) -> None:
|
|
1184
|
+
'''
|
|
1185
|
+
Prints a summary of the compilation manifest.
|
|
1186
|
+
|
|
1187
|
+
Metrics from the flowgraph nodes, or from/to parameter if
|
|
1188
|
+
defined, are printed out on a per step basis.
|
|
1189
|
+
|
|
1190
|
+
Args:
|
|
1191
|
+
jobname (str): If provided prints uses this job to print summary,
|
|
1192
|
+
otherwise the value in :keypath:`option,jobname` will be used.
|
|
1193
|
+
fd (TextIO): If provided prints to this file descriptor instead of stdout.
|
|
1194
|
+
|
|
1195
|
+
Examples:
|
|
1196
|
+
>>> chip.summary()
|
|
1197
|
+
Prints out a summary of the run to stdout.
|
|
1198
|
+
'''
|
|
1199
|
+
histories = self.getkeys("history")
|
|
1200
|
+
|
|
1201
|
+
if not histories:
|
|
1202
|
+
raise ValueError("no history to summarize")
|
|
1203
|
+
|
|
1204
|
+
if jobname is None:
|
|
1205
|
+
jobname = self.get("option", "jobname")
|
|
1206
|
+
if jobname not in histories:
|
|
1207
|
+
org_job = jobname
|
|
1208
|
+
jobname = histories[0]
|
|
1209
|
+
self.logger.warning(f"{org_job} not found in history, picking {jobname}")
|
|
1210
|
+
|
|
1211
|
+
history = self.history(jobname)
|
|
1212
|
+
history.get("metric", field='schema').summary(
|
|
1213
|
+
headers=history._summary_headers(),
|
|
1214
|
+
fd=fd)
|
|
1215
|
+
|
|
1216
|
+
def find_result(self,
|
|
1217
|
+
filetype: str = None, step: str = None,
|
|
1218
|
+
index: str = "0", directory: str = "outputs",
|
|
1219
|
+
filename: str = None) -> str:
|
|
1220
|
+
"""
|
|
1221
|
+
Returns the absolute path of a compilation result file.
|
|
1222
|
+
|
|
1223
|
+
This utility function constructs and returns the absolute path to a
|
|
1224
|
+
result file based on the provided arguments. The typical result
|
|
1225
|
+
directory structure is:
|
|
1226
|
+
`<build_dir>/<design_name>/<job_name>/<step>/<index>/<directory>/<design>.<filetype>`
|
|
1227
|
+
|
|
1228
|
+
Args:
|
|
1229
|
+
filetype (str, optional): The file extension (e.g., 'v', 'def', 'gds').
|
|
1230
|
+
Required if `filename` is not provided.
|
|
1231
|
+
step (str, optional): The name of the task step (e.g., 'syn', 'place').
|
|
1232
|
+
Required.
|
|
1233
|
+
index (str, optional): The task index within the step. Defaults to "0".
|
|
1234
|
+
directory (str, optional): The node directory within the step to search
|
|
1235
|
+
(e.g., 'outputs', 'reports'). Defaults to "outputs".
|
|
1236
|
+
filename (str, optional): The exact filename to search for. If provided,
|
|
1237
|
+
`filetype` is ignored for constructing the path.
|
|
1238
|
+
Defaults to None.
|
|
1239
|
+
|
|
1240
|
+
Returns:
|
|
1241
|
+
str: The absolute path to the found file, or None if the file is not found.
|
|
1242
|
+
|
|
1243
|
+
Raises:
|
|
1244
|
+
ValueError: If `step` is not provided, or if `[option,fileset]` is not set
|
|
1245
|
+
when `filename` is not provided.
|
|
1246
|
+
|
|
1247
|
+
Examples:
|
|
1248
|
+
>>> vg_filepath = chip.find_result('vg', 'syn')
|
|
1249
|
+
Returns the absolute path to the gate level verilog.
|
|
1250
|
+
"""
|
|
1251
|
+
|
|
1252
|
+
if filename and step is None:
|
|
1253
|
+
step = filetype
|
|
1254
|
+
|
|
1255
|
+
if step is None:
|
|
1256
|
+
raise ValueError("step is required")
|
|
1257
|
+
|
|
1258
|
+
workdir = self.getworkdir(step, index)
|
|
1259
|
+
|
|
1260
|
+
if not filename:
|
|
1261
|
+
fileset = self.get("option", "fileset")
|
|
1262
|
+
if not fileset:
|
|
1263
|
+
raise ValueError("[option,fileset] is not set")
|
|
1264
|
+
design_name = self.design.get_topmodule(fileset[0])
|
|
1265
|
+
|
|
1266
|
+
checkfiles = [
|
|
1267
|
+
os.path.join(workdir, directory, f'{design_name}.{filetype}'),
|
|
1268
|
+
os.path.join(workdir, directory, f'{design_name}.{filetype}.gz')
|
|
1269
|
+
]
|
|
1270
|
+
else:
|
|
1271
|
+
checkfiles = [
|
|
1272
|
+
os.path.join(workdir, directory, filename)
|
|
1273
|
+
]
|
|
1274
|
+
|
|
1275
|
+
for filename in checkfiles:
|
|
1276
|
+
self.logger.debug(f"Finding node file: {filename}")
|
|
1277
|
+
if os.path.exists(filename):
|
|
1278
|
+
return os.path.abspath(filename)
|
|
1279
|
+
|
|
1280
|
+
return None
|
|
1281
|
+
|
|
1282
|
+
def snapshot(self, path: str = None, display: bool = True) -> None:
|
|
1283
|
+
'''
|
|
1284
|
+
Creates a snapshot image summarizing the job's progress and key information.
|
|
1285
|
+
|
|
1286
|
+
This function generates a PNG image that provides a visual overview
|
|
1287
|
+
of the compilation job. The image can be saved to a specified path
|
|
1288
|
+
and optionally displayed after generation.
|
|
1289
|
+
|
|
1290
|
+
Args:
|
|
1291
|
+
path (str, optional): The file path where the snapshot image should be saved.
|
|
1292
|
+
If not provided, it defaults to
|
|
1293
|
+
`<job_directory>/<design_name>.png`.
|
|
1294
|
+
display (bool, optional): If True, the generated image will be opened for viewing
|
|
1295
|
+
if the system supports it and `option,nodisplay` is False.
|
|
1296
|
+
Defaults to True.
|
|
1297
|
+
|
|
1298
|
+
Examples:
|
|
1299
|
+
>>> chip.snapshot()
|
|
1300
|
+
Creates a snapshot image in the default location.
|
|
1301
|
+
'''
|
|
1302
|
+
from siliconcompiler.report import generate_summary_image, _open_summary_image
|
|
1303
|
+
|
|
1304
|
+
if not path:
|
|
1305
|
+
path = os.path.join(self.getworkdir(), f'{self.design.name}.png')
|
|
1306
|
+
|
|
1307
|
+
if os.path.exists(path):
|
|
1308
|
+
os.remove(path)
|
|
1309
|
+
|
|
1310
|
+
generate_summary_image(self, path, self._snapshot_info())
|
|
1311
|
+
|
|
1312
|
+
if os.path.isfile(path) and not self.get('option', 'nodisplay') and display:
|
|
1313
|
+
_open_summary_image(path)
|
|
1314
|
+
|
|
1315
|
+
def show(self, filename=None, screenshot=False, extension=None) -> str:
|
|
1316
|
+
'''
|
|
1317
|
+
Opens a graphical viewer for a specified file or the last generated layout.
|
|
1318
|
+
|
|
1319
|
+
The `show` function identifies an appropriate viewer tool based on the
|
|
1320
|
+
file's extension and the registered showtools. Display settings and
|
|
1321
|
+
technology-specific viewing configurations are read from the project's
|
|
1322
|
+
in-memory schema. All temporary rendering and display files are stored
|
|
1323
|
+
in a dedicated `_show_<jobname>` directory within the build directory.
|
|
1324
|
+
|
|
1325
|
+
If no `filename` is provided, the method attempts to automatically find
|
|
1326
|
+
the last generated layout file in the build directory based on supported
|
|
1327
|
+
extensions from registered showtools.
|
|
1328
|
+
|
|
1329
|
+
Args:
|
|
1330
|
+
filename (path, optional): The path to the file to display. If None,
|
|
1331
|
+
the system attempts to find the most recent
|
|
1332
|
+
layout file. Defaults to None.
|
|
1333
|
+
screenshot (bool, optional): If True, the operation is treated as a
|
|
1334
|
+
screenshot request, using `ScreenshotTaskSchema`
|
|
1335
|
+
instead of `ShowTaskSchema`. Defaults to False.
|
|
1336
|
+
extension (str, optional): The specific file extension to search for when
|
|
1337
|
+
automatically finding a file (e.g., 'gds', 'lef').
|
|
1338
|
+
Used only if `filename` is None. Defaults to None.
|
|
1339
|
+
|
|
1340
|
+
Returns:
|
|
1341
|
+
str: The path to the generated screenshot file if `screenshot` is True,
|
|
1342
|
+
otherwise None.
|
|
1343
|
+
|
|
1344
|
+
Examples:
|
|
1345
|
+
>>> show('build/oh_add/job0/write.gds/0/outputs/oh_add.gds')
|
|
1346
|
+
Displays a GDS file using a viewer assigned by the showtool.
|
|
1347
|
+
'''
|
|
1348
|
+
|
|
1349
|
+
tool_cls = ScreenshotTaskSchema if screenshot else ShowTaskSchema
|
|
1350
|
+
|
|
1351
|
+
sc_jobname = self.get("option", "jobname")
|
|
1352
|
+
sc_step = None
|
|
1353
|
+
sc_index = None
|
|
1354
|
+
|
|
1355
|
+
has_filename = filename is not None
|
|
1356
|
+
# Finding last layout if no argument specified
|
|
1357
|
+
if filename is None:
|
|
1358
|
+
try:
|
|
1359
|
+
search_obj = self.history(sc_jobname)
|
|
1360
|
+
except KeyError:
|
|
1361
|
+
search_obj = self
|
|
1362
|
+
|
|
1363
|
+
self.logger.info('Searching build directory for layout to show.')
|
|
1364
|
+
|
|
1365
|
+
search_nodes = []
|
|
1366
|
+
flow = search_obj.get("option", "flow")
|
|
1367
|
+
if flow:
|
|
1368
|
+
flow_obj = search_obj.get("flowgraph", flow, field="schema")
|
|
1369
|
+
for nodes in flow_obj.get_execution_order(reverse=True):
|
|
1370
|
+
search_nodes.extend(nodes)
|
|
1371
|
+
|
|
1372
|
+
exts = set()
|
|
1373
|
+
for cls in tool_cls.get_task(None):
|
|
1374
|
+
try:
|
|
1375
|
+
exts.update(cls().get_supported_show_extentions())
|
|
1376
|
+
except NotImplementedError:
|
|
1377
|
+
pass
|
|
1378
|
+
|
|
1379
|
+
for ext in exts:
|
|
1380
|
+
if extension and extension != ext:
|
|
1381
|
+
continue
|
|
1382
|
+
|
|
1383
|
+
for step, index in search_nodes:
|
|
1384
|
+
filename = search_obj.find_result(ext,
|
|
1385
|
+
step=step,
|
|
1386
|
+
index=index)
|
|
1387
|
+
if filename:
|
|
1388
|
+
sc_step = step
|
|
1389
|
+
sc_index = index
|
|
1390
|
+
break
|
|
1391
|
+
if filename:
|
|
1392
|
+
break
|
|
1393
|
+
|
|
1394
|
+
if filename is None:
|
|
1395
|
+
self.logger.error('Unable to automatically find layout in build directory.')
|
|
1396
|
+
self.logger.error('Try passing in a full path to show() instead.')
|
|
1397
|
+
return
|
|
1398
|
+
|
|
1399
|
+
filepath = os.path.abspath(filename)
|
|
1400
|
+
|
|
1401
|
+
if not has_filename:
|
|
1402
|
+
self.logger.info(f'Showing file {filename}')
|
|
1403
|
+
|
|
1404
|
+
# Check that file exists
|
|
1405
|
+
if not os.path.exists(filepath):
|
|
1406
|
+
self.logger.error(f"Invalid filepath {filepath}.")
|
|
1407
|
+
return
|
|
1408
|
+
|
|
1409
|
+
filetype = get_file_ext(filepath)
|
|
1410
|
+
|
|
1411
|
+
task = tool_cls.get_task(filetype)
|
|
1412
|
+
if task is None:
|
|
1413
|
+
self.logger.error(f"Filetype '{filetype}' not available in the registered showtools.")
|
|
1414
|
+
return False
|
|
1415
|
+
|
|
1416
|
+
# Create copy of project to avoid changing user project
|
|
1417
|
+
proj = self.copy()
|
|
1418
|
+
|
|
1419
|
+
nodename = "screenshot" if screenshot else "show"
|
|
1420
|
+
|
|
1421
|
+
class ShowFlow(FlowgraphSchema):
|
|
1422
|
+
"""
|
|
1423
|
+
Small auto created flow to build a single node show/screenshot flow
|
|
1424
|
+
"""
|
|
1425
|
+
def __init__(self, nodename, task):
|
|
1426
|
+
super().__init__()
|
|
1427
|
+
self.set_name("showflow")
|
|
1428
|
+
|
|
1429
|
+
self.node(nodename, task)
|
|
1430
|
+
|
|
1431
|
+
proj.set_flow(ShowFlow(nodename, task))
|
|
1432
|
+
|
|
1433
|
+
# Setup options:
|
|
1434
|
+
for option, value in [
|
|
1435
|
+
("track", False),
|
|
1436
|
+
("hash", False),
|
|
1437
|
+
("nodisplay", False),
|
|
1438
|
+
("continue", True),
|
|
1439
|
+
("quiet", False),
|
|
1440
|
+
("clean", True)]:
|
|
1441
|
+
proj.set("option", option, value)
|
|
1442
|
+
proj.unset("arg", "step")
|
|
1443
|
+
proj.unset("arg", "index")
|
|
1444
|
+
proj.unset("option", "to")
|
|
1445
|
+
proj.unset("option", "prune")
|
|
1446
|
+
proj.unset("option", "from")
|
|
1447
|
+
|
|
1448
|
+
jobname = f"_{nodename}_{sc_jobname}_{sc_step}_{sc_index}_{task.tool()}"
|
|
1449
|
+
proj.set("option", "jobname", jobname)
|
|
1450
|
+
|
|
1451
|
+
# Setup in task variables
|
|
1452
|
+
task: ShowTaskSchema = proj.get_task(filter=task.__class__)
|
|
1453
|
+
task.set_showfilepath(filename)
|
|
1454
|
+
task.set_showfiletype(filetype)
|
|
1455
|
+
task.set_shownode(jobname=sc_jobname, step=sc_step, index=sc_index)
|
|
1456
|
+
|
|
1457
|
+
# run show flow
|
|
1458
|
+
proj.run(raise_exception=True)
|
|
1459
|
+
if screenshot:
|
|
1460
|
+
return proj.find_result('png', step=nodename)
|