siliconcompiler 0.35.4__py3-none-any.whl → 0.36.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/constraints/__init__.py +4 -1
- siliconcompiler/constraints/asic_timing.py +230 -38
- siliconcompiler/constraints/fpga_timing.py +209 -14
- siliconcompiler/constraints/timing_mode.py +82 -0
- siliconcompiler/data/templates/tcl/manifest.tcl.j2 +0 -6
- siliconcompiler/flowgraph.py +95 -42
- siliconcompiler/flows/generate_openroad_rcx.py +2 -2
- siliconcompiler/flows/highresscreenshotflow.py +37 -0
- siliconcompiler/library.py +2 -1
- siliconcompiler/package/__init__.py +39 -45
- siliconcompiler/project.py +4 -1
- siliconcompiler/scheduler/scheduler.py +64 -35
- siliconcompiler/scheduler/schedulernode.py +5 -2
- siliconcompiler/scheduler/slurm.py +7 -6
- siliconcompiler/scheduler/taskscheduler.py +19 -16
- siliconcompiler/schema/_metadata.py +1 -1
- siliconcompiler/schema/namedschema.py +2 -4
- siliconcompiler/schema_support/cmdlineschema.py +0 -3
- siliconcompiler/schema_support/dependencyschema.py +0 -6
- siliconcompiler/schema_support/record.py +4 -3
- siliconcompiler/tool.py +58 -27
- siliconcompiler/tools/_common/tcl/sc_schema_access.tcl +0 -6
- siliconcompiler/tools/keplerformal/__init__.py +7 -0
- siliconcompiler/tools/keplerformal/lec.py +112 -0
- siliconcompiler/tools/klayout/screenshot.py +66 -1
- siliconcompiler/tools/klayout/scripts/klayout_export.py +10 -40
- siliconcompiler/tools/klayout/scripts/klayout_show.py +4 -4
- siliconcompiler/tools/klayout/scripts/klayout_utils.py +13 -1
- siliconcompiler/tools/montage/tile.py +26 -12
- siliconcompiler/tools/openroad/_apr.py +26 -10
- siliconcompiler/tools/openroad/init_floorplan.py +5 -2
- siliconcompiler/tools/openroad/power_grid_analysis.py +1 -1
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +49 -14
- siliconcompiler/tools/openroad/scripts/apr/sc_irdrop.tcl +6 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +4 -4
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +14 -5
- siliconcompiler/tools/openroad/scripts/common/read_liberty.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/common/reports.tcl +6 -3
- siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/common/write_data_physical.tcl +8 -0
- siliconcompiler/tools/openroad/scripts/common/write_images.tcl +16 -12
- siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -1
- siliconcompiler/tools/openroad/write_data.py +2 -2
- siliconcompiler/tools/opensta/scripts/sc_check_library.tcl +2 -2
- siliconcompiler/tools/opensta/scripts/sc_report_libraries.tcl +2 -2
- siliconcompiler/tools/opensta/scripts/sc_timing.tcl +12 -14
- siliconcompiler/tools/opensta/timing.py +6 -2
- siliconcompiler/toolscripts/_tools.json +9 -4
- siliconcompiler/toolscripts/ubuntu22/install-keplerformal.sh +72 -0
- siliconcompiler/toolscripts/ubuntu24/install-keplerformal.sh +72 -0
- siliconcompiler/utils/multiprocessing.py +11 -0
- siliconcompiler/utils/settings.py +70 -49
- {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/METADATA +3 -3
- {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/RECORD +59 -53
- {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/top_level.txt +0 -0
|
@@ -14,7 +14,7 @@ from datetime import datetime
|
|
|
14
14
|
|
|
15
15
|
from typing import Union, Dict, Optional, Tuple, List, Set, TYPE_CHECKING
|
|
16
16
|
|
|
17
|
-
from siliconcompiler import NodeStatus
|
|
17
|
+
from siliconcompiler import NodeStatus, Task
|
|
18
18
|
from siliconcompiler.schema import Journal
|
|
19
19
|
from siliconcompiler.flowgraph import RuntimeFlowgraph
|
|
20
20
|
from siliconcompiler.scheduler import SchedulerNode
|
|
@@ -292,6 +292,10 @@ class Scheduler:
|
|
|
292
292
|
self.__run_setup()
|
|
293
293
|
self.configure_nodes()
|
|
294
294
|
|
|
295
|
+
# Verify task classes
|
|
296
|
+
if not self.__check_task_classes():
|
|
297
|
+
raise SCRuntimeError("Task classes are missing")
|
|
298
|
+
|
|
295
299
|
# Verify tool setups
|
|
296
300
|
if not self.__check_tool_versions():
|
|
297
301
|
raise SCRuntimeError("Tools did not meet version requirements")
|
|
@@ -353,6 +357,33 @@ class Scheduler:
|
|
|
353
357
|
|
|
354
358
|
return not error
|
|
355
359
|
|
|
360
|
+
def __check_task_classes(self) -> bool:
|
|
361
|
+
"""
|
|
362
|
+
Verifies that all runtime nodes have loaded their specific Task implementation classes.
|
|
363
|
+
|
|
364
|
+
Iterates through all nodes in the execution flow and checks if the associated
|
|
365
|
+
task object is a generic instance of the base `Task` class. If so, it indicates
|
|
366
|
+
that the specific module for that tool/task was not loaded correctly.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
bool: `True` if all nodes are using specialized Task subclasses, `False` if any
|
|
370
|
+
node is using the base `Task` class.
|
|
371
|
+
"""
|
|
372
|
+
nodes = self.__flow_runtime.get_nodes()
|
|
373
|
+
error = False
|
|
374
|
+
|
|
375
|
+
for (step, index) in nodes:
|
|
376
|
+
tool = self.__flow.get(step, index, "tool")
|
|
377
|
+
task = self.__flow.get(step, index, "task")
|
|
378
|
+
|
|
379
|
+
task_cls = self.project.get("tool", tool, "task", task, field="schema")
|
|
380
|
+
if type(task_cls) is Task:
|
|
381
|
+
self.__logger.error(f"Invalid task: {step}/{index} did not load "
|
|
382
|
+
"the correct class module")
|
|
383
|
+
error = True
|
|
384
|
+
|
|
385
|
+
return not error
|
|
386
|
+
|
|
356
387
|
def __check_flowgraph_io(self) -> bool:
|
|
357
388
|
"""
|
|
358
389
|
Validate that every runtime node will receive its required input files and that no
|
|
@@ -409,6 +440,8 @@ class Scheduler:
|
|
|
409
440
|
node_inp = task.compute_input_file_node_name(inp, in_step, in_index)
|
|
410
441
|
if node_inp in requirements:
|
|
411
442
|
inp = node_inp
|
|
443
|
+
if inp not in requirements:
|
|
444
|
+
continue
|
|
412
445
|
if inp in all_inputs:
|
|
413
446
|
self.__logger.error(f'Invalid flow: {step}/{index} '
|
|
414
447
|
f'receives {inp} from multiple input tasks')
|
|
@@ -908,43 +941,39 @@ class Scheduler:
|
|
|
908
941
|
|
|
909
942
|
error = False
|
|
910
943
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
versions: Dict[str, Optional[str]] = {}
|
|
944
|
+
with tempfile.TemporaryDirectory(
|
|
945
|
+
prefix=f"sc_tool_check_{self.project.option.get_jobname()}_") as d:
|
|
946
|
+
versions: Dict[str, Optional[str]] = {}
|
|
915
947
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
continue
|
|
948
|
+
self.__logger.debug(f"Executing tool checks in: {d}")
|
|
949
|
+
for (step, index) in self.__flow_runtime.get_nodes():
|
|
950
|
+
if self.__project.option.scheduler.get_name(step=step, index=index) is not None:
|
|
951
|
+
continue
|
|
921
952
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
953
|
+
node = self.__tasks[(step, index)]
|
|
954
|
+
with node.runtime():
|
|
955
|
+
try:
|
|
956
|
+
exe = node.get_exe_path()
|
|
957
|
+
except TaskExecutableNotReceived:
|
|
958
|
+
continue
|
|
959
|
+
except TaskExecutableNotFound:
|
|
960
|
+
exe = node.task.get("exe")
|
|
961
|
+
self.__logger.error(f"Executable for {step}/{index} could not "
|
|
962
|
+
f"be found: {exe}")
|
|
963
|
+
error = True
|
|
964
|
+
continue
|
|
934
965
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
finally:
|
|
947
|
-
os.chdir(cwd)
|
|
966
|
+
try:
|
|
967
|
+
if exe:
|
|
968
|
+
version: Optional[str] = versions.get(exe, None)
|
|
969
|
+
version, check = node.check_version(version, workdir=d)
|
|
970
|
+
versions[exe] = version
|
|
971
|
+
if not check:
|
|
972
|
+
self.__logger.error(f"Executable for {step}/{index} did not "
|
|
973
|
+
"meet version checks")
|
|
974
|
+
error = True
|
|
975
|
+
except NotImplementedError:
|
|
976
|
+
self.__logger.error(f"Unable to process version for {step}/{index}")
|
|
948
977
|
|
|
949
978
|
return not error
|
|
950
979
|
|
|
@@ -895,7 +895,8 @@ class SchedulerNode:
|
|
|
895
895
|
with self.__set_env():
|
|
896
896
|
return self.__task.get_exe()
|
|
897
897
|
|
|
898
|
-
def check_version(self, version: Optional[str] = None
|
|
898
|
+
def check_version(self, version: Optional[str] = None,
|
|
899
|
+
workdir: Optional[str] = None) -> Tuple[Optional[str], bool]:
|
|
899
900
|
"""Checks the version of the tool for this task.
|
|
900
901
|
|
|
901
902
|
Compares a version string against the tool's requirements. This check
|
|
@@ -908,6 +909,8 @@ class SchedulerNode:
|
|
|
908
909
|
Args:
|
|
909
910
|
version: The version string to check. If None, the task's
|
|
910
911
|
configured version is fetched and used.
|
|
912
|
+
workdir: The working directory to use for the version check. If None,
|
|
913
|
+
the current working directory is used.
|
|
911
914
|
|
|
912
915
|
Returns:
|
|
913
916
|
A tuple (version_str, check_passed):
|
|
@@ -921,7 +924,7 @@ class SchedulerNode:
|
|
|
921
924
|
|
|
922
925
|
with self.__set_env():
|
|
923
926
|
if version is None:
|
|
924
|
-
version = self.__task.get_exe_version()
|
|
927
|
+
version = self.__task.get_exe_version(workdir=workdir)
|
|
925
928
|
|
|
926
929
|
check = self.__task.check_exe_version(version)
|
|
927
930
|
|
|
@@ -209,8 +209,7 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
209
209
|
self._init_run_logger()
|
|
210
210
|
|
|
211
211
|
# Determine which cluster parititon to use.
|
|
212
|
-
partition = self.project.
|
|
213
|
-
step=self.step, index=self.index)
|
|
212
|
+
partition = self.project.option.scheduler.get_queue(step=self.step, index=self.index)
|
|
214
213
|
if not partition:
|
|
215
214
|
partition = SlurmSchedulerNode.get_slurm_partition()
|
|
216
215
|
|
|
@@ -246,7 +245,6 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
246
245
|
os.stat(script_file).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
247
246
|
|
|
248
247
|
schedule_cmd = ['srun',
|
|
249
|
-
'--exclusive',
|
|
250
248
|
'--partition', partition,
|
|
251
249
|
'--chdir', self.project_cwd,
|
|
252
250
|
'--job-name', SlurmSchedulerNode.get_job_name(self.__job_hash,
|
|
@@ -254,17 +252,20 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
254
252
|
'--output', log_file]
|
|
255
253
|
|
|
256
254
|
# Only delay the starting time if the 'defer' Schema option is specified.
|
|
257
|
-
defer_time = self.project.
|
|
258
|
-
step=self.step, index=self.index)
|
|
255
|
+
defer_time = self.project.option.scheduler.get_defer(step=self.step, index=self.index)
|
|
259
256
|
if defer_time:
|
|
260
257
|
schedule_cmd.extend(['--begin', defer_time])
|
|
261
258
|
|
|
259
|
+
# Forward additional user options
|
|
260
|
+
options = self.project.option.scheduler.get_options(step=self.step, index=self.index)
|
|
261
|
+
if options:
|
|
262
|
+
schedule_cmd.extend(options)
|
|
263
|
+
|
|
262
264
|
schedule_cmd.append(script_file)
|
|
263
265
|
|
|
264
266
|
self.logger.debug(f"Executing slurm command: {shlex.join(schedule_cmd)}")
|
|
265
267
|
|
|
266
268
|
# Run the 'srun' command, and track its output.
|
|
267
|
-
# TODO: output should be fed to log, and stdout if quiet = False
|
|
268
269
|
step_result = subprocess.Popen(schedule_cmd,
|
|
269
270
|
stdin=subprocess.DEVNULL,
|
|
270
271
|
stdout=subprocess.PIPE,
|
|
@@ -5,7 +5,7 @@ import time
|
|
|
5
5
|
|
|
6
6
|
import os.path
|
|
7
7
|
|
|
8
|
-
from typing import List, Dict, Tuple, Optional, Callable,
|
|
8
|
+
from typing import List, Dict, Tuple, Optional, Callable, Any, Literal, TYPE_CHECKING
|
|
9
9
|
|
|
10
10
|
from logging.handlers import QueueListener
|
|
11
11
|
|
|
@@ -35,12 +35,6 @@ class TaskScheduler:
|
|
|
35
35
|
dependency checking. It operates on a set of pending tasks defined by the
|
|
36
36
|
main Scheduler and executes them in a loop until the flow is complete.
|
|
37
37
|
"""
|
|
38
|
-
__callbacks: ClassVar[Dict[str, Callable[..., None]]] = {
|
|
39
|
-
"pre_run": lambda project: None,
|
|
40
|
-
"pre_node": lambda project, step, index: None,
|
|
41
|
-
"post_node": lambda project, step, index: None,
|
|
42
|
-
"post_run": lambda project: None,
|
|
43
|
-
}
|
|
44
38
|
|
|
45
39
|
@staticmethod
|
|
46
40
|
def register_callback(hook: Literal["pre_run", "pre_node", "post_node", "post_run"],
|
|
@@ -57,9 +51,9 @@ class TaskScheduler:
|
|
|
57
51
|
Raises:
|
|
58
52
|
ValueError: If the specified hook is not valid.
|
|
59
53
|
"""
|
|
60
|
-
if hook not in
|
|
54
|
+
if hook not in ('pre_run', 'pre_node', 'post_node', 'post_run'):
|
|
61
55
|
raise ValueError(f"{hook} is not a valid callback")
|
|
62
|
-
TaskScheduler
|
|
56
|
+
MPManager.get_transient_settings().set('TaskScheduler', hook, func)
|
|
63
57
|
|
|
64
58
|
def __init__(self, project: "Project", tasks: Dict[Tuple[str, str], "SchedulerNode"]):
|
|
65
59
|
"""Initializes the TaskScheduler.
|
|
@@ -139,9 +133,6 @@ class TaskScheduler:
|
|
|
139
133
|
threads = self.__max_threads
|
|
140
134
|
task["threads"] = max(1, min(threads, self.__max_threads))
|
|
141
135
|
|
|
142
|
-
task["parent_pipe"], pipe = multiprocessing.Pipe()
|
|
143
|
-
task["node"].set_queue(pipe, self.__log_queue)
|
|
144
|
-
|
|
145
136
|
task["proc"] = multiprocessing.Process(target=task["node"].run)
|
|
146
137
|
self.__nodes[(step, index)] = task
|
|
147
138
|
|
|
@@ -181,11 +172,13 @@ class TaskScheduler:
|
|
|
181
172
|
if self.__dashboard:
|
|
182
173
|
self.__dashboard.update_manifest()
|
|
183
174
|
|
|
184
|
-
|
|
175
|
+
MPManager.get_transient_settings().get(
|
|
176
|
+
'TaskScheduler', 'pre_run', lambda project: None)(self.__project)
|
|
185
177
|
|
|
186
178
|
try:
|
|
187
179
|
self.__run_loop()
|
|
188
|
-
|
|
180
|
+
MPManager.get_transient_settings().get(
|
|
181
|
+
'TaskScheduler', 'post_run', lambda project: None)(self.__project)
|
|
189
182
|
except KeyboardInterrupt:
|
|
190
183
|
# exit immediately
|
|
191
184
|
log_listener.stop()
|
|
@@ -304,6 +297,10 @@ class TaskScheduler:
|
|
|
304
297
|
except: # noqa E722
|
|
305
298
|
pass
|
|
306
299
|
|
|
300
|
+
# Remove pipe
|
|
301
|
+
info["parent_pipe"] = None
|
|
302
|
+
info["node"].set_queue(None, None)
|
|
303
|
+
|
|
307
304
|
step, index = node
|
|
308
305
|
if info["proc"].exitcode > 0:
|
|
309
306
|
status = NodeStatus.ERROR
|
|
@@ -319,7 +316,9 @@ class TaskScheduler:
|
|
|
319
316
|
|
|
320
317
|
changed = True
|
|
321
318
|
|
|
322
|
-
|
|
319
|
+
MPManager.get_transient_settings().get(
|
|
320
|
+
'TaskScheduler', 'post_node',
|
|
321
|
+
lambda project, step, index: None)(self.__project, step, index)
|
|
323
322
|
|
|
324
323
|
return changed
|
|
325
324
|
|
|
@@ -406,7 +405,9 @@ class TaskScheduler:
|
|
|
406
405
|
if ready and self.__allow_start(node):
|
|
407
406
|
self.__logger.debug(f'Launching {info["name"]}')
|
|
408
407
|
|
|
409
|
-
|
|
408
|
+
MPManager.get_transient_settings().get(
|
|
409
|
+
'TaskScheduler', 'pre_node',
|
|
410
|
+
lambda project, step, index: None)(self.__project, step, index)
|
|
410
411
|
|
|
411
412
|
self.__record.set('status', NodeStatus.RUNNING, step=step, index=index)
|
|
412
413
|
self.__startTimes[node] = time.time()
|
|
@@ -414,6 +415,8 @@ class TaskScheduler:
|
|
|
414
415
|
|
|
415
416
|
# Start the process
|
|
416
417
|
info["running"] = True
|
|
418
|
+
info["parent_pipe"], pipe = multiprocessing.Pipe()
|
|
419
|
+
info["node"].set_queue(pipe, self.__log_queue)
|
|
417
420
|
info["proc"].start()
|
|
418
421
|
|
|
419
422
|
return changed
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# Version number following semver standard.
|
|
2
|
-
version = '0.
|
|
2
|
+
version = '0.53.1'
|
|
@@ -44,12 +44,10 @@ class NamedSchema(BaseSchema):
|
|
|
44
44
|
"""
|
|
45
45
|
|
|
46
46
|
try:
|
|
47
|
-
if self.
|
|
48
|
-
raise RuntimeError("Cannot call set_name
|
|
47
|
+
if self._parent() is not self:
|
|
48
|
+
raise RuntimeError("Cannot call set_name after it has been inserted into schema.")
|
|
49
49
|
except AttributeError:
|
|
50
50
|
pass
|
|
51
|
-
if name is not None and "." in name:
|
|
52
|
-
raise ValueError("Named schema object cannot contains: .")
|
|
53
51
|
self.__name = name
|
|
54
52
|
|
|
55
53
|
def type(self) -> str:
|
|
@@ -172,9 +172,6 @@ class CommandLineSchema(BaseSchema):
|
|
|
172
172
|
|
|
173
173
|
# Iterate over all keys from an empty schema to add parser arguments
|
|
174
174
|
for keypath in sorted(keyschema.allkeys()):
|
|
175
|
-
if keypath == ("option", "cfg"): # TODO: remove this when cfg is removed from schema
|
|
176
|
-
continue
|
|
177
|
-
|
|
178
175
|
param: Parameter = keyschema.get(*keypath, field=None)
|
|
179
176
|
|
|
180
177
|
dest, switches = param.add_commandline_arguments(
|
|
@@ -210,12 +210,6 @@ class DependencySchema(BaseSchema):
|
|
|
210
210
|
|
|
211
211
|
if name:
|
|
212
212
|
if not self.has_dep(name):
|
|
213
|
-
if "." in name:
|
|
214
|
-
name0, *name1 = name.split(".")
|
|
215
|
-
subdep = self.get_dep(name0)
|
|
216
|
-
if isinstance(subdep, DependencySchema):
|
|
217
|
-
return subdep.get_dep(".".join(name1))
|
|
218
|
-
raise KeyError(f"{name} does not contain dependency information")
|
|
219
213
|
raise KeyError(f"{name} is not an imported module")
|
|
220
214
|
|
|
221
215
|
return self.__deps[name]
|
|
@@ -15,6 +15,7 @@ from enum import Enum
|
|
|
15
15
|
from siliconcompiler.schema import BaseSchema, LazyLoad
|
|
16
16
|
from siliconcompiler.schema import EditableSchema, Parameter, PerNode, Scope
|
|
17
17
|
from siliconcompiler.schema.utils import trim
|
|
18
|
+
from siliconcompiler.utils.multiprocessing import MPManager
|
|
18
19
|
|
|
19
20
|
from siliconcompiler import _metadata
|
|
20
21
|
|
|
@@ -138,9 +139,9 @@ class RecordSchema(BaseSchema):
|
|
|
138
139
|
"region": str
|
|
139
140
|
}
|
|
140
141
|
'''
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return {"region":
|
|
142
|
+
|
|
143
|
+
region = MPManager().get_settings().get("record", "region", default="local")
|
|
144
|
+
return {"region": region}
|
|
144
145
|
|
|
145
146
|
@staticmethod
|
|
146
147
|
def get_ip_information() -> Dict[str, Optional[str]]:
|
siliconcompiler/tool.py
CHANGED
|
@@ -11,7 +11,6 @@ import shlex
|
|
|
11
11
|
import shutil
|
|
12
12
|
import subprocess
|
|
13
13
|
import sys
|
|
14
|
-
import threading
|
|
15
14
|
import time
|
|
16
15
|
import yaml
|
|
17
16
|
|
|
@@ -35,7 +34,7 @@ from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
|
|
35
34
|
from typing import List, Dict, Tuple, Union, Optional, Set, TextIO, Type, TypeVar, TYPE_CHECKING
|
|
36
35
|
from pathlib import Path
|
|
37
36
|
|
|
38
|
-
from siliconcompiler.schema import BaseSchema, NamedSchema,
|
|
37
|
+
from siliconcompiler.schema import BaseSchema, NamedSchema, DocsSchema, LazyLoad
|
|
39
38
|
from siliconcompiler.schema import EditableSchema, Parameter, PerNode, Scope
|
|
40
39
|
from siliconcompiler.schema.parametertype import NodeType
|
|
41
40
|
from siliconcompiler.schema.utils import trim
|
|
@@ -43,6 +42,7 @@ from siliconcompiler.schema.utils import trim
|
|
|
43
42
|
from siliconcompiler import utils, NodeStatus, Flowgraph
|
|
44
43
|
from siliconcompiler import sc_open
|
|
45
44
|
from siliconcompiler.utils import paths
|
|
45
|
+
from siliconcompiler.utils.multiprocessing import MPManager
|
|
46
46
|
|
|
47
47
|
from siliconcompiler.schema_support.pathschema import PathSchema
|
|
48
48
|
from siliconcompiler.schema_support.record import RecordTool, RecordSchema
|
|
@@ -390,7 +390,7 @@ class Task(NamedSchema, PathSchema, DocsSchema):
|
|
|
390
390
|
if self.tool() in tools:
|
|
391
391
|
self.logger.info(f"Missing tool can be installed via: \"sc-install {self.tool()}\"")
|
|
392
392
|
|
|
393
|
-
def get_exe_version(self) -> Optional[str]:
|
|
393
|
+
def get_exe_version(self, workdir: Optional[str] = None) -> Optional[str]:
|
|
394
394
|
"""
|
|
395
395
|
Gets the version of the task's executable by running it with a version switch.
|
|
396
396
|
|
|
@@ -398,6 +398,10 @@ class Task(NamedSchema, PathSchema, DocsSchema):
|
|
|
398
398
|
TaskExecutableNotFound: If the executable is not found.
|
|
399
399
|
NotImplementedError: If the `parse_version` method is not implemented.
|
|
400
400
|
|
|
401
|
+
Args:
|
|
402
|
+
workdir (str): The working directory to use for the version check. If None,
|
|
403
|
+
the current working directory is used.
|
|
404
|
+
|
|
401
405
|
Returns:
|
|
402
406
|
str: The parsed version string.
|
|
403
407
|
"""
|
|
@@ -422,7 +426,8 @@ class Task(NamedSchema, PathSchema, DocsSchema):
|
|
|
422
426
|
stdin=subprocess.DEVNULL,
|
|
423
427
|
stdout=subprocess.PIPE,
|
|
424
428
|
stderr=subprocess.STDOUT,
|
|
425
|
-
universal_newlines=True
|
|
429
|
+
universal_newlines=True,
|
|
430
|
+
cwd=workdir)
|
|
426
431
|
|
|
427
432
|
if proc.returncode != 0:
|
|
428
433
|
self.logger.warning(f"Version check on '{exe_base}' ended with "
|
|
@@ -736,9 +741,7 @@ class Task(NamedSchema, PathSchema, DocsSchema):
|
|
|
736
741
|
fout.write(template.render(manifest_dict='\n'.join(tcl_set_cmds),
|
|
737
742
|
scroot=os.path.abspath(
|
|
738
743
|
os.path.join(os.path.dirname(__file__))),
|
|
739
|
-
toolvars=self.get_tcl_variables(manifest)
|
|
740
|
-
record_access="get" in Journal.access(self).get_types(),
|
|
741
|
-
record_access_id="TODO"))
|
|
744
|
+
toolvars=self.get_tcl_variables(manifest)))
|
|
742
745
|
else:
|
|
743
746
|
for cmd in tcl_set_cmds:
|
|
744
747
|
fout.write(cmd + '\n')
|
|
@@ -2084,9 +2087,6 @@ class ShowTask(Task):
|
|
|
2084
2087
|
Subclasses should implement `get_supported_show_extentions` to declare
|
|
2085
2088
|
which file types they can handle.
|
|
2086
2089
|
"""
|
|
2087
|
-
__TASKS_LOCK = threading.Lock()
|
|
2088
|
-
__TASKS = {}
|
|
2089
|
-
|
|
2090
2090
|
def __init__(self):
|
|
2091
2091
|
"""Initializes a ShowTask, adding specific parameters for show tasks."""
|
|
2092
2092
|
super().__init__()
|
|
@@ -2133,8 +2133,10 @@ class ShowTask(Task):
|
|
|
2133
2133
|
if not cls.__check_task(task):
|
|
2134
2134
|
raise TypeError(f"task must be a subclass of {cls.__name__}")
|
|
2135
2135
|
|
|
2136
|
-
|
|
2137
|
-
cls.
|
|
2136
|
+
MPManager.get_transient_settings().set(
|
|
2137
|
+
cls.__name__,
|
|
2138
|
+
f"{task.__module__}/{task.__name__}",
|
|
2139
|
+
task)
|
|
2138
2140
|
|
|
2139
2141
|
@classmethod
|
|
2140
2142
|
def __populate_tasks(cls) -> None:
|
|
@@ -2146,6 +2148,9 @@ class ShowTask(Task):
|
|
|
2146
2148
|
"""
|
|
2147
2149
|
cls.__check_task(None)
|
|
2148
2150
|
|
|
2151
|
+
if MPManager.get_transient_settings().get_category(cls.__name__):
|
|
2152
|
+
return # Already populated
|
|
2153
|
+
|
|
2149
2154
|
def recurse(searchcls: Type["ShowTask"]):
|
|
2150
2155
|
subclss = set()
|
|
2151
2156
|
if not cls.__check_task(searchcls):
|
|
@@ -2160,20 +2165,25 @@ class ShowTask(Task):
|
|
|
2160
2165
|
classes = recurse(cls)
|
|
2161
2166
|
|
|
2162
2167
|
# Support non-SC defined tasks from plugins
|
|
2163
|
-
for plugin in utils.get_plugins('showtask'):
|
|
2168
|
+
for plugin in utils.get_plugins('showtask'):
|
|
2164
2169
|
plugin()
|
|
2165
2170
|
|
|
2166
2171
|
if not classes:
|
|
2167
2172
|
return
|
|
2168
2173
|
|
|
2169
|
-
|
|
2170
|
-
|
|
2174
|
+
for c in classes:
|
|
2175
|
+
cls.register_task(c)
|
|
2171
2176
|
|
|
2172
2177
|
@classmethod
|
|
2173
2178
|
def get_task(cls, ext: Optional[str]) -> Union[Optional["ShowTask"], Set[Type["ShowTask"]]]:
|
|
2174
2179
|
"""
|
|
2175
2180
|
Retrieves a suitable show task instance for a given file extension.
|
|
2176
2181
|
|
|
2182
|
+
The method first checks the user's settings file (~/.sc/settings.json)
|
|
2183
|
+
under the 'showtask' category for a preferred tool. If no preference
|
|
2184
|
+
is found or the preferred tool is not available, it falls back to
|
|
2185
|
+
automatic discovery.
|
|
2186
|
+
|
|
2177
2187
|
Args:
|
|
2178
2188
|
ext (str): The file extension to find a viewer for.
|
|
2179
2189
|
|
|
@@ -2182,24 +2192,45 @@ class ShowTask(Task):
|
|
|
2182
2192
|
no suitable task is found.
|
|
2183
2193
|
"""
|
|
2184
2194
|
cls.__check_task(None)
|
|
2195
|
+
cls.__populate_tasks()
|
|
2185
2196
|
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
if cls not in ShowTask.__TASKS:
|
|
2191
|
-
return None
|
|
2192
|
-
tasks = ShowTask.__TASKS[cls].copy()
|
|
2193
|
-
|
|
2194
|
-
# TODO: add user preference lookup (ext -> task)
|
|
2197
|
+
settings = MPManager.get_transient_settings()
|
|
2198
|
+
tasks = set(settings.get_category(cls.__name__).values())
|
|
2199
|
+
if not tasks:
|
|
2200
|
+
return None
|
|
2195
2201
|
|
|
2196
2202
|
if ext is None:
|
|
2197
2203
|
return tasks
|
|
2198
2204
|
|
|
2199
|
-
|
|
2205
|
+
# 1. Check User Settings for Preference
|
|
2206
|
+
preference = MPManager.get_settings().get("showtask", ext)
|
|
2207
|
+
|
|
2208
|
+
if preference:
|
|
2209
|
+
# Preference format: "tool" or "tool/task"
|
|
2210
|
+
pref_parts = preference.split('/')
|
|
2211
|
+
pref_tool = pref_parts[0]
|
|
2212
|
+
pref_task = pref_parts[1] if len(pref_parts) > 1 else None
|
|
2213
|
+
|
|
2214
|
+
for task_cls in tasks:
|
|
2215
|
+
try:
|
|
2216
|
+
task_inst = task_cls()
|
|
2217
|
+
# Check if this task matches the preference
|
|
2218
|
+
if task_inst.tool() == pref_tool:
|
|
2219
|
+
if pref_task and task_inst.task() != pref_task:
|
|
2220
|
+
continue
|
|
2221
|
+
|
|
2222
|
+
# Verify the preferred tool actually supports the extension
|
|
2223
|
+
if ext in task_inst.get_supported_show_extentions():
|
|
2224
|
+
return task_inst
|
|
2225
|
+
except NotImplementedError:
|
|
2226
|
+
continue
|
|
2227
|
+
|
|
2228
|
+
# 2. Fallback to Automatic Discovery
|
|
2229
|
+
for task_cls in tasks:
|
|
2200
2230
|
try:
|
|
2201
|
-
|
|
2202
|
-
|
|
2231
|
+
task_inst = task_cls()
|
|
2232
|
+
if ext in task_inst.get_supported_show_extentions():
|
|
2233
|
+
return task_inst
|
|
2203
2234
|
except NotImplementedError:
|
|
2204
2235
|
pass
|
|
2205
2236
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Kepler-Formal is a logic equivalence checking (LEC) tool that operates on verilog
|
|
3
|
+
and the naja interchange format (https://github.com/najaeda/naja-if) and focuses
|
|
4
|
+
today on combinational equivalence checking only.
|
|
5
|
+
|
|
6
|
+
Sources: https://github.com/keplertech/kepler-formal
|
|
7
|
+
'''
|