siliconcompiler 0.35.3__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/apps/sc_issue.py +18 -2
- siliconcompiler/checklist.py +2 -1
- siliconcompiler/constraints/__init__.py +4 -1
- siliconcompiler/constraints/asic_component.py +49 -11
- siliconcompiler/constraints/asic_floorplan.py +23 -21
- siliconcompiler/constraints/asic_pins.py +55 -17
- siliconcompiler/constraints/asic_timing.py +280 -57
- siliconcompiler/constraints/fpga_timing.py +212 -18
- siliconcompiler/constraints/timing_mode.py +82 -0
- siliconcompiler/data/templates/replay/replay.sh.j2 +27 -14
- 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 +56 -51
- siliconcompiler/project.py +13 -2
- siliconcompiler/scheduler/docker.py +24 -25
- siliconcompiler/scheduler/scheduler.py +143 -100
- siliconcompiler/scheduler/schedulernode.py +138 -22
- siliconcompiler/scheduler/slurm.py +120 -35
- siliconcompiler/scheduler/taskscheduler.py +19 -23
- siliconcompiler/schema/_metadata.py +1 -1
- siliconcompiler/schema/editableschema.py +29 -0
- siliconcompiler/schema/namedschema.py +2 -4
- siliconcompiler/schema/parametervalue.py +14 -2
- siliconcompiler/schema_support/cmdlineschema.py +0 -3
- siliconcompiler/schema_support/dependencyschema.py +0 -6
- siliconcompiler/schema_support/option.py +82 -1
- siliconcompiler/schema_support/pathschema.py +7 -13
- siliconcompiler/schema_support/record.py +4 -3
- siliconcompiler/tool.py +105 -52
- 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/__init__.py +3 -0
- siliconcompiler/tools/klayout/screenshot.py +66 -1
- siliconcompiler/tools/klayout/scripts/klayout_convert_drc_db.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_export.py +11 -40
- siliconcompiler/tools/klayout/scripts/klayout_operations.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_show.py +5 -4
- siliconcompiler/tools/klayout/scripts/klayout_utils.py +16 -5
- siliconcompiler/tools/montage/tile.py +26 -12
- siliconcompiler/tools/openroad/__init__.py +27 -1
- siliconcompiler/tools/openroad/_apr.py +107 -14
- siliconcompiler/tools/openroad/clock_tree_synthesis.py +1 -0
- siliconcompiler/tools/openroad/global_placement.py +1 -0
- siliconcompiler/tools/openroad/init_floorplan.py +119 -7
- siliconcompiler/tools/openroad/power_grid_analysis.py +174 -0
- siliconcompiler/tools/openroad/repair_design.py +1 -0
- siliconcompiler/tools/openroad/repair_timing.py +1 -0
- siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +91 -18
- siliconcompiler/tools/openroad/scripts/apr/sc_irdrop.tcl +148 -0
- siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +8 -10
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +15 -6
- siliconcompiler/tools/openroad/scripts/common/read_liberty.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/common/reports.tcl +7 -4
- 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/rcx/sc_rcx_bench.tcl +2 -4
- siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -1
- siliconcompiler/tools/openroad/write_data.py +2 -2
- siliconcompiler/tools/opensta/__init__.py +1 -1
- 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 +13 -10
- siliconcompiler/tools/opensta/timing.py +6 -2
- siliconcompiler/tools/vivado/scripts/sc_bitstream.tcl +11 -0
- siliconcompiler/tools/vivado/scripts/sc_place.tcl +11 -0
- siliconcompiler/tools/vivado/scripts/sc_route.tcl +11 -0
- siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +10 -0
- siliconcompiler/tools/vpr/__init__.py +28 -0
- siliconcompiler/tools/yosys/scripts/sc_screenshot.tcl +1 -1
- siliconcompiler/tools/yosys/scripts/sc_synth_asic.tcl +40 -4
- siliconcompiler/tools/yosys/scripts/sc_synth_fpga.tcl +15 -5
- siliconcompiler/tools/yosys/syn_asic.py +42 -0
- siliconcompiler/tools/yosys/syn_fpga.py +8 -0
- siliconcompiler/toolscripts/_tools.json +12 -7
- siliconcompiler/toolscripts/ubuntu22/install-keplerformal.sh +72 -0
- siliconcompiler/toolscripts/ubuntu24/install-keplerformal.sh +72 -0
- siliconcompiler/utils/__init__.py +243 -51
- siliconcompiler/utils/curation.py +89 -56
- siliconcompiler/utils/issue.py +6 -1
- siliconcompiler/utils/multiprocessing.py +46 -2
- siliconcompiler/utils/paths.py +21 -0
- siliconcompiler/utils/settings.py +162 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/METADATA +5 -4
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/RECORD +96 -87
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/top_level.txt +0 -0
|
@@ -5,15 +5,18 @@ import shutil
|
|
|
5
5
|
import stat
|
|
6
6
|
import subprocess
|
|
7
7
|
import uuid
|
|
8
|
+
import time
|
|
8
9
|
|
|
9
10
|
import os.path
|
|
10
11
|
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
from siliconcompiler
|
|
12
|
+
from typing import List, Union, Final
|
|
13
|
+
|
|
14
|
+
from siliconcompiler import utils, sc_open
|
|
15
|
+
from siliconcompiler.utils.paths import jobdir
|
|
14
16
|
from siliconcompiler.package import RemoteResolver
|
|
15
|
-
from siliconcompiler.flowgraph import RuntimeFlowgraph
|
|
16
17
|
from siliconcompiler.scheduler import SchedulerNode
|
|
18
|
+
from siliconcompiler.utils.logging import SCBlankLoggerFormatter
|
|
19
|
+
from siliconcompiler.utils.multiprocessing import MPManager
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
class SlurmSchedulerNode(SchedulerNode):
|
|
@@ -24,6 +27,10 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
24
27
|
It prepares a run script, a manifest, and uses the 'srun' command
|
|
25
28
|
to execute the step on a compute node.
|
|
26
29
|
"""
|
|
30
|
+
__OPTIONS: Final[str] = "scheduler-slurm"
|
|
31
|
+
|
|
32
|
+
_MAX_FS_DELAY = 2
|
|
33
|
+
_FS_DWELL = 0.1
|
|
27
34
|
|
|
28
35
|
def __init__(self, project, step, index, replay=False):
|
|
29
36
|
"""Initializes a SlurmSchedulerNode.
|
|
@@ -52,34 +59,31 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
52
59
|
"""
|
|
53
60
|
A static pre-processing hook for the Slurm scheduler.
|
|
54
61
|
|
|
55
|
-
This method
|
|
56
|
-
|
|
57
|
-
into a central location before any remote jobs are submitted. This
|
|
58
|
-
ensures that compute nodes have access to all required source files.
|
|
62
|
+
This method ensures that the Slurm environment is available and loads
|
|
63
|
+
any existing user configuration for the scheduler.
|
|
59
64
|
|
|
60
65
|
Args:
|
|
61
66
|
project (Project): The project object to perform pre-processing on.
|
|
62
67
|
"""
|
|
63
|
-
|
|
64
|
-
# nothing to do
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
do_collect = False
|
|
68
|
-
flow = project.get('option', 'flow')
|
|
69
|
-
entry_nodes = project.get("flowgraph", flow, field="schema").get_entry_nodes()
|
|
68
|
+
SlurmSchedulerNode.assert_slurm()
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
prune_nodes=project.get('option', 'prune'))
|
|
70
|
+
@staticmethod
|
|
71
|
+
def _set_user_config(tag: str, value: Union[List[str], str]) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Sets a specific value in the user configuration map.
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
Args:
|
|
76
|
+
tag (str): The configuration key to update.
|
|
77
|
+
value (Union[List[str], str]): The value to assign to the key.
|
|
78
|
+
"""
|
|
79
|
+
MPManager.get_settings().set(SlurmSchedulerNode.__OPTIONS, tag, value)
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _write_user_config() -> None:
|
|
83
|
+
"""
|
|
84
|
+
Writes the current system configuration to the user configuration file.
|
|
85
|
+
"""
|
|
86
|
+
MPManager.get_settings().save()
|
|
83
87
|
|
|
84
88
|
@property
|
|
85
89
|
def is_local(self):
|
|
@@ -150,6 +154,49 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
150
154
|
# Return the first listed partition
|
|
151
155
|
return sinfo['nodes'][0]['partitions'][0]
|
|
152
156
|
|
|
157
|
+
@staticmethod
|
|
158
|
+
def assert_slurm() -> None:
|
|
159
|
+
"""
|
|
160
|
+
Check if slurm is installed and throw error when not installed.
|
|
161
|
+
"""
|
|
162
|
+
if shutil.which('sinfo') is None:
|
|
163
|
+
raise RuntimeError('slurm is not available or installed on this machine')
|
|
164
|
+
|
|
165
|
+
def mark_copy(self) -> bool:
|
|
166
|
+
sharedprefix: List[str] = MPManager.get_settings().get(
|
|
167
|
+
SlurmSchedulerNode.__OPTIONS, "sharedpaths", default=[])
|
|
168
|
+
|
|
169
|
+
if "/" in sharedprefix:
|
|
170
|
+
# Entire filesystem is shared so no need to check
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
do_collect = False
|
|
174
|
+
for key in self.get_required_path_keys():
|
|
175
|
+
mark_copy = True
|
|
176
|
+
if sharedprefix:
|
|
177
|
+
mark_copy = False
|
|
178
|
+
|
|
179
|
+
check_step, check_index = self.step, self.index
|
|
180
|
+
if self.project.get(*key, field='pernode').is_never():
|
|
181
|
+
check_step, check_index = None, None
|
|
182
|
+
|
|
183
|
+
paths = self.project.find_files(*key, missing_ok=True,
|
|
184
|
+
step=check_step, index=check_index)
|
|
185
|
+
if not isinstance(paths, list):
|
|
186
|
+
paths = [paths]
|
|
187
|
+
paths = [str(path) for path in paths if path]
|
|
188
|
+
|
|
189
|
+
for path in paths:
|
|
190
|
+
if not any([path.startswith(shared) for shared in sharedprefix]):
|
|
191
|
+
# File exists outside shared paths and needs to be copied
|
|
192
|
+
mark_copy = True
|
|
193
|
+
break
|
|
194
|
+
|
|
195
|
+
if mark_copy:
|
|
196
|
+
self.project.set(*key, True, field='copy')
|
|
197
|
+
do_collect = True
|
|
198
|
+
return do_collect
|
|
199
|
+
|
|
153
200
|
def run(self):
|
|
154
201
|
"""
|
|
155
202
|
Runs the node's task as a job on a Slurm cluster.
|
|
@@ -161,12 +208,8 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
161
208
|
|
|
162
209
|
self._init_run_logger()
|
|
163
210
|
|
|
164
|
-
if shutil.which('sinfo') is None:
|
|
165
|
-
raise RuntimeError('slurm is not available or installed on this machine')
|
|
166
|
-
|
|
167
211
|
# Determine which cluster parititon to use.
|
|
168
|
-
partition = self.project.
|
|
169
|
-
step=self.step, index=self.index)
|
|
212
|
+
partition = self.project.option.scheduler.get_queue(step=self.step, index=self.index)
|
|
170
213
|
if not partition:
|
|
171
214
|
partition = SlurmSchedulerNode.get_slurm_partition()
|
|
172
215
|
|
|
@@ -191,7 +234,7 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
191
234
|
with open(script_file, 'w') as sf:
|
|
192
235
|
sf.write(utils.get_file_template('slurm/run.sh').render(
|
|
193
236
|
cfg_file=shlex.quote(cfg_file),
|
|
194
|
-
build_dir=shlex.quote(self.project.
|
|
237
|
+
build_dir=shlex.quote(self.project.option.get_builddir()),
|
|
195
238
|
step=shlex.quote(self.step),
|
|
196
239
|
index=shlex.quote(self.index),
|
|
197
240
|
cachedir=shlex.quote(str(RemoteResolver.determine_cache_dir(self.project)))
|
|
@@ -202,7 +245,6 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
202
245
|
os.stat(script_file).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
203
246
|
|
|
204
247
|
schedule_cmd = ['srun',
|
|
205
|
-
'--exclusive',
|
|
206
248
|
'--partition', partition,
|
|
207
249
|
'--chdir', self.project_cwd,
|
|
208
250
|
'--job-name', SlurmSchedulerNode.get_job_name(self.__job_hash,
|
|
@@ -210,16 +252,22 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
210
252
|
'--output', log_file]
|
|
211
253
|
|
|
212
254
|
# Only delay the starting time if the 'defer' Schema option is specified.
|
|
213
|
-
defer_time = self.project.
|
|
214
|
-
step=self.step, index=self.index)
|
|
255
|
+
defer_time = self.project.option.scheduler.get_defer(step=self.step, index=self.index)
|
|
215
256
|
if defer_time:
|
|
216
257
|
schedule_cmd.extend(['--begin', defer_time])
|
|
217
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
|
+
|
|
218
264
|
schedule_cmd.append(script_file)
|
|
219
265
|
|
|
266
|
+
self.logger.debug(f"Executing slurm command: {shlex.join(schedule_cmd)}")
|
|
267
|
+
|
|
220
268
|
# Run the 'srun' command, and track its output.
|
|
221
|
-
# TODO: output should be fed to log, and stdout if quiet = False
|
|
222
269
|
step_result = subprocess.Popen(schedule_cmd,
|
|
270
|
+
stdin=subprocess.DEVNULL,
|
|
223
271
|
stdout=subprocess.PIPE,
|
|
224
272
|
stderr=subprocess.STDOUT)
|
|
225
273
|
|
|
@@ -227,3 +275,40 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
227
275
|
# as it has closed its output stream. But if we don't call '.wait()',
|
|
228
276
|
# the '.returncode' value will not be set correctly.
|
|
229
277
|
step_result.wait()
|
|
278
|
+
|
|
279
|
+
# Attempt to list dir to trigger network FS to update
|
|
280
|
+
try:
|
|
281
|
+
os.listdir(os.path.dirname(log_file))
|
|
282
|
+
except: # noqa E722
|
|
283
|
+
pass
|
|
284
|
+
|
|
285
|
+
# Print the log to logger
|
|
286
|
+
if os.path.exists(log_file):
|
|
287
|
+
org_formatter = self.project._logger_console.formatter
|
|
288
|
+
try:
|
|
289
|
+
self.project._logger_console.setFormatter(SCBlankLoggerFormatter())
|
|
290
|
+
with sc_open(log_file) as log:
|
|
291
|
+
for line in log.readlines():
|
|
292
|
+
self.logger.info(line.rstrip())
|
|
293
|
+
finally:
|
|
294
|
+
self.project._logger_console.setFormatter(org_formatter)
|
|
295
|
+
|
|
296
|
+
if step_result.returncode != 0:
|
|
297
|
+
self.logger.error(f"Slurm exited with a non-zero code ({step_result.returncode}).")
|
|
298
|
+
if os.path.exists(log_file):
|
|
299
|
+
self.logger.error(f"Node log file: {log_file}")
|
|
300
|
+
self.halt()
|
|
301
|
+
|
|
302
|
+
# Wait for manifest to propagate through network filesystem
|
|
303
|
+
start = time.time()
|
|
304
|
+
elapsed = 0
|
|
305
|
+
manifest_path = self.get_manifest()
|
|
306
|
+
while not os.path.exists(manifest_path) and elapsed <= SlurmSchedulerNode._MAX_FS_DELAY:
|
|
307
|
+
os.listdir(os.path.dirname(manifest_path))
|
|
308
|
+
elapsed = time.time() - start
|
|
309
|
+
time.sleep(SlurmSchedulerNode._FS_DWELL)
|
|
310
|
+
if not os.path.exists(manifest_path):
|
|
311
|
+
self.logger.error(f"Manifest was not created on time: {manifest_path}")
|
|
312
|
+
|
|
313
|
+
def check_required_paths(self) -> bool:
|
|
314
|
+
return True
|
|
@@ -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.
|
|
@@ -117,8 +111,6 @@ class TaskScheduler:
|
|
|
117
111
|
from_steps=set([step for step, _ in self.__flow.get_entry_nodes()]),
|
|
118
112
|
prune_nodes=self.__project.option.get_prune())
|
|
119
113
|
|
|
120
|
-
init_funcs = set()
|
|
121
|
-
|
|
122
114
|
for step, index in self.__runtime_flow.get_nodes():
|
|
123
115
|
if self.__record.get('status', step=step, index=index) != NodeStatus.PENDING:
|
|
124
116
|
continue
|
|
@@ -141,11 +133,7 @@ class TaskScheduler:
|
|
|
141
133
|
threads = self.__max_threads
|
|
142
134
|
task["threads"] = max(1, min(threads, self.__max_threads))
|
|
143
135
|
|
|
144
|
-
task["parent_pipe"], pipe = multiprocessing.Pipe()
|
|
145
|
-
task["node"].set_queue(pipe, self.__log_queue)
|
|
146
|
-
|
|
147
136
|
task["proc"] = multiprocessing.Process(target=task["node"].run)
|
|
148
|
-
init_funcs.add(task["node"].init)
|
|
149
137
|
self.__nodes[(step, index)] = task
|
|
150
138
|
|
|
151
139
|
# Create ordered list of nodes
|
|
@@ -155,10 +143,6 @@ class TaskScheduler:
|
|
|
155
143
|
if node in self.__nodes:
|
|
156
144
|
self.__ordered_nodes.append(node)
|
|
157
145
|
|
|
158
|
-
# Call preprocessing for schedulers
|
|
159
|
-
for init_func in init_funcs:
|
|
160
|
-
init_func(self.__project)
|
|
161
|
-
|
|
162
146
|
def run(self, job_log_handler: logging.Handler) -> None:
|
|
163
147
|
"""
|
|
164
148
|
The main entry point for the task scheduling loop.
|
|
@@ -188,11 +172,13 @@ class TaskScheduler:
|
|
|
188
172
|
if self.__dashboard:
|
|
189
173
|
self.__dashboard.update_manifest()
|
|
190
174
|
|
|
191
|
-
|
|
175
|
+
MPManager.get_transient_settings().get(
|
|
176
|
+
'TaskScheduler', 'pre_run', lambda project: None)(self.__project)
|
|
192
177
|
|
|
193
178
|
try:
|
|
194
179
|
self.__run_loop()
|
|
195
|
-
|
|
180
|
+
MPManager.get_transient_settings().get(
|
|
181
|
+
'TaskScheduler', 'post_run', lambda project: None)(self.__project)
|
|
196
182
|
except KeyboardInterrupt:
|
|
197
183
|
# exit immediately
|
|
198
184
|
log_listener.stop()
|
|
@@ -311,6 +297,10 @@ class TaskScheduler:
|
|
|
311
297
|
except: # noqa E722
|
|
312
298
|
pass
|
|
313
299
|
|
|
300
|
+
# Remove pipe
|
|
301
|
+
info["parent_pipe"] = None
|
|
302
|
+
info["node"].set_queue(None, None)
|
|
303
|
+
|
|
314
304
|
step, index = node
|
|
315
305
|
if info["proc"].exitcode > 0:
|
|
316
306
|
status = NodeStatus.ERROR
|
|
@@ -326,7 +316,9 @@ class TaskScheduler:
|
|
|
326
316
|
|
|
327
317
|
changed = True
|
|
328
318
|
|
|
329
|
-
|
|
319
|
+
MPManager.get_transient_settings().get(
|
|
320
|
+
'TaskScheduler', 'post_node',
|
|
321
|
+
lambda project, step, index: None)(self.__project, step, index)
|
|
330
322
|
|
|
331
323
|
return changed
|
|
332
324
|
|
|
@@ -413,7 +405,9 @@ class TaskScheduler:
|
|
|
413
405
|
if ready and self.__allow_start(node):
|
|
414
406
|
self.__logger.debug(f'Launching {info["name"]}')
|
|
415
407
|
|
|
416
|
-
|
|
408
|
+
MPManager.get_transient_settings().get(
|
|
409
|
+
'TaskScheduler', 'pre_node',
|
|
410
|
+
lambda project, step, index: None)(self.__project, step, index)
|
|
417
411
|
|
|
418
412
|
self.__record.set('status', NodeStatus.RUNNING, step=step, index=index)
|
|
419
413
|
self.__startTimes[node] = time.time()
|
|
@@ -421,6 +415,8 @@ class TaskScheduler:
|
|
|
421
415
|
|
|
422
416
|
# Start the process
|
|
423
417
|
info["running"] = True
|
|
418
|
+
info["parent_pipe"], pipe = multiprocessing.Pipe()
|
|
419
|
+
info["node"].set_queue(pipe, self.__log_queue)
|
|
424
420
|
info["proc"].start()
|
|
425
421
|
|
|
426
422
|
return changed
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# Version number following semver standard.
|
|
2
|
-
version = '0.
|
|
2
|
+
version = '0.53.1'
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
from .parameter import Parameter
|
|
8
8
|
from .baseschema import BaseSchema
|
|
9
|
+
from .namedschema import NamedSchema
|
|
9
10
|
|
|
10
11
|
from typing import Union, Tuple
|
|
11
12
|
|
|
@@ -38,6 +39,11 @@ class EditableSchema:
|
|
|
38
39
|
if isinstance(value, BaseSchema):
|
|
39
40
|
value._BaseSchema__parent = self.__schema
|
|
40
41
|
value._BaseSchema__key = key
|
|
42
|
+
if isinstance(value, NamedSchema):
|
|
43
|
+
if key == "default":
|
|
44
|
+
value._NamedSchema__name = None
|
|
45
|
+
else:
|
|
46
|
+
value._NamedSchema__name = key
|
|
41
47
|
|
|
42
48
|
if key == "default":
|
|
43
49
|
self.__schema._BaseSchema__default = value
|
|
@@ -146,3 +152,26 @@ class EditableSchema:
|
|
|
146
152
|
raise ValueError("Keypath must only be strings")
|
|
147
153
|
|
|
148
154
|
return self.__schema._BaseSchema__search(*keypath, require_leaf=False)
|
|
155
|
+
|
|
156
|
+
def copy(self) -> BaseSchema:
|
|
157
|
+
'''
|
|
158
|
+
Creates a copy of the schema object, disconnected from any parent schema
|
|
159
|
+
'''
|
|
160
|
+
|
|
161
|
+
new_schema = self.__schema.copy()
|
|
162
|
+
if new_schema._parent() is not new_schema:
|
|
163
|
+
new_schema._BaseSchema__parent = None
|
|
164
|
+
return new_schema
|
|
165
|
+
|
|
166
|
+
def rename(self, name: str):
|
|
167
|
+
'''
|
|
168
|
+
Renames a named schema
|
|
169
|
+
'''
|
|
170
|
+
|
|
171
|
+
if not isinstance(self.__schema, NamedSchema):
|
|
172
|
+
raise TypeError("schema must be a named schema")
|
|
173
|
+
|
|
174
|
+
if self.__schema._parent() is not self.__schema:
|
|
175
|
+
raise ValueError("object is already in a schema")
|
|
176
|
+
|
|
177
|
+
self.__schema._NamedSchema__name = name
|
|
@@ -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:
|
|
@@ -185,7 +185,13 @@ class NodeListValue:
|
|
|
185
185
|
'''
|
|
186
186
|
Returns a copy of the values stored in the list
|
|
187
187
|
'''
|
|
188
|
-
|
|
188
|
+
if self.__values:
|
|
189
|
+
return self.__values.copy()
|
|
190
|
+
else:
|
|
191
|
+
if self.__base.has_value:
|
|
192
|
+
return [self.__base]
|
|
193
|
+
else:
|
|
194
|
+
return []
|
|
189
195
|
|
|
190
196
|
def copy(self) -> "NodeListValue":
|
|
191
197
|
"""
|
|
@@ -385,7 +391,13 @@ class NodeSetValue:
|
|
|
385
391
|
'''
|
|
386
392
|
Returns a copy of the values stored in the list
|
|
387
393
|
'''
|
|
388
|
-
|
|
394
|
+
if self.__values:
|
|
395
|
+
return self.__values.copy()
|
|
396
|
+
else:
|
|
397
|
+
if self.__base.has_value:
|
|
398
|
+
return [self.__base]
|
|
399
|
+
else:
|
|
400
|
+
return []
|
|
389
401
|
|
|
390
402
|
def copy(self) -> "NodeSetValue":
|
|
391
403
|
"""
|
|
@@ -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]
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from typing import Union, List, Tuple, Callable, Dict, Optional
|
|
1
|
+
from typing import Union, List, Tuple, Callable, Dict, Optional, Final
|
|
2
2
|
|
|
3
3
|
from siliconcompiler.schema import BaseSchema, EditableSchema, Parameter, Scope, PerNode
|
|
4
4
|
from siliconcompiler.schema.utils import trim
|
|
5
|
+
from siliconcompiler.utils.multiprocessing import MPManager
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class SchedulerSchema(BaseSchema):
|
|
@@ -427,6 +428,8 @@ class OptionSchema(BaseSchema):
|
|
|
427
428
|
compiler's behavior, such as flow control, logging, build settings, and
|
|
428
429
|
remote execution. It provides getter and setter methods for each parameter.
|
|
429
430
|
"""
|
|
431
|
+
__OPTIONS: Final[str] = "schema-options"
|
|
432
|
+
|
|
430
433
|
def __init__(self):
|
|
431
434
|
"""Initializes the options schema and defines all its parameters."""
|
|
432
435
|
super().__init__()
|
|
@@ -844,6 +847,84 @@ class OptionSchema(BaseSchema):
|
|
|
844
847
|
|
|
845
848
|
schema.insert('scheduler', SchedulerSchema())
|
|
846
849
|
|
|
850
|
+
self.__load_defaults()
|
|
851
|
+
|
|
852
|
+
def __load_defaults(self) -> None:
|
|
853
|
+
"""Loads and applies settings from the default options file.
|
|
854
|
+
|
|
855
|
+
This method reads the configuration file specified by the settings
|
|
856
|
+
manager. It iterates through the list of option
|
|
857
|
+
objects in the file.
|
|
858
|
+
|
|
859
|
+
For each object, it checks for a "key" and a "value". If the key
|
|
860
|
+
is recognized (exists in `self.allkeys()`), it attempts to apply
|
|
861
|
+
the value using `self.set()`.
|
|
862
|
+
|
|
863
|
+
Errors during value setting (`ValueError`) are silently ignored.
|
|
864
|
+
"""
|
|
865
|
+
options = MPManager.get_settings().get_category(OptionSchema.__OPTIONS)
|
|
866
|
+
|
|
867
|
+
if not options:
|
|
868
|
+
return
|
|
869
|
+
|
|
870
|
+
allkeys = self.allkeys()
|
|
871
|
+
for key, value in options.items():
|
|
872
|
+
if key is None:
|
|
873
|
+
continue
|
|
874
|
+
|
|
875
|
+
key = tuple(key.split(","))
|
|
876
|
+
if key not in allkeys:
|
|
877
|
+
continue
|
|
878
|
+
|
|
879
|
+
try:
|
|
880
|
+
self.set(*key, value)
|
|
881
|
+
except ValueError:
|
|
882
|
+
pass
|
|
883
|
+
|
|
884
|
+
def write_defaults(self) -> None:
|
|
885
|
+
"""Saves all non-default settings to the configuration file.
|
|
886
|
+
|
|
887
|
+
This method iterates through all parameters known to the system
|
|
888
|
+
(via `self.allkeys()`). It compares the current value of each
|
|
889
|
+
parameter against its default value.
|
|
890
|
+
|
|
891
|
+
Any parameter whose current value differs from its default is
|
|
892
|
+
collected. This list of non-default settings is then
|
|
893
|
+
serialized as a JSON array to the file specified by
|
|
894
|
+
`default_options_file()`.
|
|
895
|
+
|
|
896
|
+
If all parameters are set to their default values, the list
|
|
897
|
+
will be empty, and no file will be written.
|
|
898
|
+
"""
|
|
899
|
+
transientkeys = {
|
|
900
|
+
# Flow information
|
|
901
|
+
("flow",),
|
|
902
|
+
("from",),
|
|
903
|
+
("to",),
|
|
904
|
+
("prune",),
|
|
905
|
+
|
|
906
|
+
# Design information
|
|
907
|
+
("design",),
|
|
908
|
+
("alias",),
|
|
909
|
+
("fileset",),
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
settings = MPManager.get_settings()
|
|
913
|
+
settings.delete(OptionSchema.__OPTIONS)
|
|
914
|
+
|
|
915
|
+
for key in self.allkeys():
|
|
916
|
+
if key in transientkeys:
|
|
917
|
+
continue
|
|
918
|
+
|
|
919
|
+
param: Parameter = self.get(*key, field=None)
|
|
920
|
+
|
|
921
|
+
value = param.get()
|
|
922
|
+
if value != param.default.get():
|
|
923
|
+
settings.set(OptionSchema.__OPTIONS, ",".join(key), value)
|
|
924
|
+
|
|
925
|
+
if settings.get_category(OptionSchema.__OPTIONS):
|
|
926
|
+
settings.save()
|
|
927
|
+
|
|
847
928
|
# Getters and Setters
|
|
848
929
|
def get_remote(self) -> bool:
|
|
849
930
|
"""Gets the remote processing flag.
|
|
@@ -11,7 +11,7 @@ from siliconcompiler.schema.parameter import Parameter, Scope
|
|
|
11
11
|
from siliconcompiler.schema.utils import trim
|
|
12
12
|
|
|
13
13
|
from siliconcompiler.package import Resolver
|
|
14
|
-
from siliconcompiler.utils.paths import collectiondir
|
|
14
|
+
from siliconcompiler.utils.paths import collectiondir, cwdirsafe
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class PathSchemaBase(BaseSchema):
|
|
@@ -51,14 +51,12 @@ class PathSchemaBase(BaseSchema):
|
|
|
51
51
|
the schema.
|
|
52
52
|
"""
|
|
53
53
|
schema_root = self._parent(root=True)
|
|
54
|
-
cwd = getattr(schema_root, "_Project__cwd", os.getcwd())
|
|
55
|
-
collection_dir = collectiondir(schema_root)
|
|
56
54
|
|
|
57
55
|
return super()._find_files(*keypath,
|
|
58
56
|
missing_ok=missing_ok,
|
|
59
57
|
step=step, index=index,
|
|
60
|
-
collection_dir=
|
|
61
|
-
cwd=
|
|
58
|
+
collection_dir=collectiondir(schema_root),
|
|
59
|
+
cwd=cwdirsafe(schema_root))
|
|
62
60
|
|
|
63
61
|
def check_filepaths(self, ignore_keys: Optional[List[Tuple[str, ...]]] = None) -> bool:
|
|
64
62
|
'''
|
|
@@ -71,17 +69,15 @@ class PathSchemaBase(BaseSchema):
|
|
|
71
69
|
True if all file paths are valid, otherwise False.
|
|
72
70
|
'''
|
|
73
71
|
schema_root = self._parent(root=True)
|
|
74
|
-
cwd = getattr(schema_root, "_Project__cwd", os.getcwd())
|
|
75
72
|
logger = getattr(schema_root,
|
|
76
73
|
"logger",
|
|
77
74
|
logging.getLogger("siliconcompiler.check_filepaths"))
|
|
78
|
-
collection_dir = collectiondir(schema_root)
|
|
79
75
|
|
|
80
76
|
return super()._check_filepaths(
|
|
81
77
|
ignore_keys=ignore_keys,
|
|
82
78
|
logger=logger,
|
|
83
|
-
collection_dir=
|
|
84
|
-
cwd=
|
|
79
|
+
collection_dir=collectiondir(schema_root),
|
|
80
|
+
cwd=cwdirsafe(schema_root))
|
|
85
81
|
|
|
86
82
|
def hash_files(self, *keypath: str,
|
|
87
83
|
update: bool = True,
|
|
@@ -126,11 +122,9 @@ class PathSchemaBase(BaseSchema):
|
|
|
126
122
|
Computes, stores, and returns hashes of files in :keypath:`input, rtl, verilog`.
|
|
127
123
|
'''
|
|
128
124
|
schema_root = self._parent(root=True)
|
|
129
|
-
cwd = getattr(schema_root, "_Project__cwd", os.getcwd())
|
|
130
125
|
logger = getattr(schema_root,
|
|
131
126
|
"logger",
|
|
132
127
|
logging.getLogger("siliconcompiler.hash_files"))
|
|
133
|
-
collection_dir = collectiondir(schema_root)
|
|
134
128
|
|
|
135
129
|
if verbose:
|
|
136
130
|
logger.info(f"Computing hash value for [{','.join([*self._keypath, *keypath])}]")
|
|
@@ -138,8 +132,8 @@ class PathSchemaBase(BaseSchema):
|
|
|
138
132
|
hashes = super()._hash_files(*keypath,
|
|
139
133
|
missing_ok=missing_ok,
|
|
140
134
|
step=step, index=index,
|
|
141
|
-
collection_dir=
|
|
142
|
-
cwd=
|
|
135
|
+
collection_dir=collectiondir(schema_root),
|
|
136
|
+
cwd=cwdirsafe(schema_root))
|
|
143
137
|
|
|
144
138
|
if check:
|
|
145
139
|
check_hashes = self.get(*keypath, field="filehash", step=step, index=index)
|
|
@@ -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]]:
|