siliconcompiler 0.35.3__py3-none-any.whl → 0.35.4__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/asic_component.py +49 -11
- siliconcompiler/constraints/asic_floorplan.py +23 -21
- siliconcompiler/constraints/asic_pins.py +55 -17
- siliconcompiler/constraints/asic_timing.py +53 -22
- siliconcompiler/constraints/fpga_timing.py +5 -6
- siliconcompiler/data/templates/replay/replay.sh.j2 +27 -14
- siliconcompiler/package/__init__.py +17 -6
- siliconcompiler/project.py +9 -1
- siliconcompiler/scheduler/docker.py +24 -25
- siliconcompiler/scheduler/scheduler.py +82 -68
- siliconcompiler/scheduler/schedulernode.py +133 -20
- siliconcompiler/scheduler/slurm.py +113 -29
- siliconcompiler/scheduler/taskscheduler.py +0 -7
- siliconcompiler/schema/editableschema.py +29 -0
- siliconcompiler/schema/parametervalue.py +14 -2
- siliconcompiler/schema_support/option.py +82 -1
- siliconcompiler/schema_support/pathschema.py +7 -13
- siliconcompiler/tool.py +47 -25
- siliconcompiler/tools/klayout/__init__.py +3 -0
- siliconcompiler/tools/klayout/scripts/klayout_convert_drc_db.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_export.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_operations.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_show.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_utils.py +3 -4
- siliconcompiler/tools/openroad/__init__.py +27 -1
- siliconcompiler/tools/openroad/_apr.py +81 -4
- siliconcompiler/tools/openroad/clock_tree_synthesis.py +1 -0
- siliconcompiler/tools/openroad/global_placement.py +1 -0
- siliconcompiler/tools/openroad/init_floorplan.py +116 -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 +42 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_irdrop.tcl +146 -0
- siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +4 -6
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/common/reports.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/rcx/sc_rcx_bench.tcl +2 -4
- siliconcompiler/tools/opensta/__init__.py +1 -1
- siliconcompiler/tools/opensta/scripts/sc_timing.tcl +17 -12
- 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 +6 -6
- siliconcompiler/utils/__init__.py +243 -51
- siliconcompiler/utils/curation.py +89 -56
- siliconcompiler/utils/issue.py +6 -1
- siliconcompiler/utils/multiprocessing.py +35 -2
- siliconcompiler/utils/paths.py +21 -0
- siliconcompiler/utils/settings.py +141 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.dist-info}/METADATA +4 -3
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.dist-info}/RECORD +68 -65
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.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,9 +208,6 @@ 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
212
|
partition = self.project.get('option', 'scheduler', 'queue',
|
|
169
213
|
step=self.step, index=self.index)
|
|
@@ -191,7 +235,7 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
191
235
|
with open(script_file, 'w') as sf:
|
|
192
236
|
sf.write(utils.get_file_template('slurm/run.sh').render(
|
|
193
237
|
cfg_file=shlex.quote(cfg_file),
|
|
194
|
-
build_dir=shlex.quote(self.project.
|
|
238
|
+
build_dir=shlex.quote(self.project.option.get_builddir()),
|
|
195
239
|
step=shlex.quote(self.step),
|
|
196
240
|
index=shlex.quote(self.index),
|
|
197
241
|
cachedir=shlex.quote(str(RemoteResolver.determine_cache_dir(self.project)))
|
|
@@ -217,9 +261,12 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
217
261
|
|
|
218
262
|
schedule_cmd.append(script_file)
|
|
219
263
|
|
|
264
|
+
self.logger.debug(f"Executing slurm command: {shlex.join(schedule_cmd)}")
|
|
265
|
+
|
|
220
266
|
# Run the 'srun' command, and track its output.
|
|
221
267
|
# TODO: output should be fed to log, and stdout if quiet = False
|
|
222
268
|
step_result = subprocess.Popen(schedule_cmd,
|
|
269
|
+
stdin=subprocess.DEVNULL,
|
|
223
270
|
stdout=subprocess.PIPE,
|
|
224
271
|
stderr=subprocess.STDOUT)
|
|
225
272
|
|
|
@@ -227,3 +274,40 @@ class SlurmSchedulerNode(SchedulerNode):
|
|
|
227
274
|
# as it has closed its output stream. But if we don't call '.wait()',
|
|
228
275
|
# the '.returncode' value will not be set correctly.
|
|
229
276
|
step_result.wait()
|
|
277
|
+
|
|
278
|
+
# Attempt to list dir to trigger network FS to update
|
|
279
|
+
try:
|
|
280
|
+
os.listdir(os.path.dirname(log_file))
|
|
281
|
+
except: # noqa E722
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
# Print the log to logger
|
|
285
|
+
if os.path.exists(log_file):
|
|
286
|
+
org_formatter = self.project._logger_console.formatter
|
|
287
|
+
try:
|
|
288
|
+
self.project._logger_console.setFormatter(SCBlankLoggerFormatter())
|
|
289
|
+
with sc_open(log_file) as log:
|
|
290
|
+
for line in log.readlines():
|
|
291
|
+
self.logger.info(line.rstrip())
|
|
292
|
+
finally:
|
|
293
|
+
self.project._logger_console.setFormatter(org_formatter)
|
|
294
|
+
|
|
295
|
+
if step_result.returncode != 0:
|
|
296
|
+
self.logger.error(f"Slurm exited with a non-zero code ({step_result.returncode}).")
|
|
297
|
+
if os.path.exists(log_file):
|
|
298
|
+
self.logger.error(f"Node log file: {log_file}")
|
|
299
|
+
self.halt()
|
|
300
|
+
|
|
301
|
+
# Wait for manifest to propagate through network filesystem
|
|
302
|
+
start = time.time()
|
|
303
|
+
elapsed = 0
|
|
304
|
+
manifest_path = self.get_manifest()
|
|
305
|
+
while not os.path.exists(manifest_path) and elapsed <= SlurmSchedulerNode._MAX_FS_DELAY:
|
|
306
|
+
os.listdir(os.path.dirname(manifest_path))
|
|
307
|
+
elapsed = time.time() - start
|
|
308
|
+
time.sleep(SlurmSchedulerNode._FS_DWELL)
|
|
309
|
+
if not os.path.exists(manifest_path):
|
|
310
|
+
self.logger.error(f"Manifest was not created on time: {manifest_path}")
|
|
311
|
+
|
|
312
|
+
def check_required_paths(self) -> bool:
|
|
313
|
+
return True
|
|
@@ -117,8 +117,6 @@ class TaskScheduler:
|
|
|
117
117
|
from_steps=set([step for step, _ in self.__flow.get_entry_nodes()]),
|
|
118
118
|
prune_nodes=self.__project.option.get_prune())
|
|
119
119
|
|
|
120
|
-
init_funcs = set()
|
|
121
|
-
|
|
122
120
|
for step, index in self.__runtime_flow.get_nodes():
|
|
123
121
|
if self.__record.get('status', step=step, index=index) != NodeStatus.PENDING:
|
|
124
122
|
continue
|
|
@@ -145,7 +143,6 @@ class TaskScheduler:
|
|
|
145
143
|
task["node"].set_queue(pipe, self.__log_queue)
|
|
146
144
|
|
|
147
145
|
task["proc"] = multiprocessing.Process(target=task["node"].run)
|
|
148
|
-
init_funcs.add(task["node"].init)
|
|
149
146
|
self.__nodes[(step, index)] = task
|
|
150
147
|
|
|
151
148
|
# Create ordered list of nodes
|
|
@@ -155,10 +152,6 @@ class TaskScheduler:
|
|
|
155
152
|
if node in self.__nodes:
|
|
156
153
|
self.__ordered_nodes.append(node)
|
|
157
154
|
|
|
158
|
-
# Call preprocessing for schedulers
|
|
159
|
-
for init_func in init_funcs:
|
|
160
|
-
init_func(self.__project)
|
|
161
|
-
|
|
162
155
|
def run(self, job_log_handler: logging.Handler) -> None:
|
|
163
156
|
"""
|
|
164
157
|
The main entry point for the task scheduling loop.
|
|
@@ -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
|
|
@@ -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
|
"""
|
|
@@ -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)
|
siliconcompiler/tool.py
CHANGED
|
@@ -130,7 +130,8 @@ class Task(NamedSchema, PathSchema, DocsSchema):
|
|
|
130
130
|
r"^\s*" + __parse_version_check_str + r"\s*$",
|
|
131
131
|
re.VERBOSE | re.IGNORECASE)
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
__IO_POLL_INTERVAL: float = 0.1
|
|
134
|
+
__MEM_POLL_INTERVAL: float = 0.5
|
|
134
135
|
__MEMORY_WARN_LIMIT: int = 90
|
|
135
136
|
|
|
136
137
|
def __init__(self):
|
|
@@ -415,7 +416,7 @@ class Task(NamedSchema, PathSchema, DocsSchema):
|
|
|
415
416
|
cmdlist.extend(veropt)
|
|
416
417
|
|
|
417
418
|
self.logger.debug(f'Running {self.tool()}/{self.task()} version check: '
|
|
418
|
-
f'{
|
|
419
|
+
f'{shlex.join(cmdlist)}')
|
|
419
420
|
|
|
420
421
|
proc = subprocess.run(cmdlist,
|
|
421
422
|
stdin=subprocess.DEVNULL,
|
|
@@ -900,6 +901,37 @@ class Task(NamedSchema, PathSchema, DocsSchema):
|
|
|
900
901
|
f'{timeout} seconds. Terminating...')
|
|
901
902
|
terminate_process(proc.pid, timeout=timeout)
|
|
902
903
|
|
|
904
|
+
def __collect_memory(self, pid) -> Optional[int]:
|
|
905
|
+
try:
|
|
906
|
+
pproc = psutil.Process(pid)
|
|
907
|
+
proc_mem_bytes = pproc.memory_full_info().uss
|
|
908
|
+
for child in pproc.children(recursive=True):
|
|
909
|
+
proc_mem_bytes += child.memory_full_info().uss
|
|
910
|
+
return proc_mem_bytes
|
|
911
|
+
except psutil.Error:
|
|
912
|
+
# Process may have already terminated or been killed.
|
|
913
|
+
# Retain existing memory usage statistics in this case.
|
|
914
|
+
pass
|
|
915
|
+
except PermissionError:
|
|
916
|
+
# OS is preventing access to this information so it cannot
|
|
917
|
+
# be collected
|
|
918
|
+
pass
|
|
919
|
+
return None
|
|
920
|
+
|
|
921
|
+
def __check_memory_limit(self, warn_limit: int) -> int:
|
|
922
|
+
try:
|
|
923
|
+
memory_usage = psutil.virtual_memory()
|
|
924
|
+
if memory_usage.percent > warn_limit:
|
|
925
|
+
self.logger.warning(
|
|
926
|
+
'Current system memory usage is '
|
|
927
|
+
f'{memory_usage.percent:.1f}%')
|
|
928
|
+
return int(memory_usage.percent + 1)
|
|
929
|
+
except psutil.Error:
|
|
930
|
+
pass
|
|
931
|
+
except PermissionError:
|
|
932
|
+
pass
|
|
933
|
+
return warn_limit
|
|
934
|
+
|
|
903
935
|
def run_task(self,
|
|
904
936
|
workdir: str,
|
|
905
937
|
quiet: bool,
|
|
@@ -1034,39 +1066,29 @@ class Task(NamedSchema, PathSchema, DocsSchema):
|
|
|
1034
1066
|
raise TaskError(f"Unable to start {exe}: {str(e)}")
|
|
1035
1067
|
|
|
1036
1068
|
memory_warn_limit = Task.__MEMORY_WARN_LIMIT
|
|
1069
|
+
next_collection = None
|
|
1037
1070
|
try:
|
|
1038
1071
|
while proc.poll() is None:
|
|
1072
|
+
curr_time = time.time()
|
|
1073
|
+
|
|
1039
1074
|
# Monitor subprocess memory usage
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
proc_mem_bytes =
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
if memory_usage.percent > memory_warn_limit:
|
|
1049
|
-
self.logger.warning(
|
|
1050
|
-
'Current system memory usage is '
|
|
1051
|
-
f'{memory_usage.percent:.1f}%')
|
|
1052
|
-
memory_warn_limit = int(memory_usage.percent + 1)
|
|
1053
|
-
except psutil.Error:
|
|
1054
|
-
# Process may have already terminated or been killed.
|
|
1055
|
-
# Retain existing memory usage statistics in this case.
|
|
1056
|
-
pass
|
|
1057
|
-
except PermissionError:
|
|
1058
|
-
# OS is preventing access to this information so it cannot
|
|
1059
|
-
# be collected
|
|
1060
|
-
pass
|
|
1075
|
+
if next_collection is None or \
|
|
1076
|
+
next_collection <= curr_time:
|
|
1077
|
+
proc_mem_bytes = self.__collect_memory(proc.pid)
|
|
1078
|
+
if proc_mem_bytes is not None:
|
|
1079
|
+
max_mem_bytes = max(max_mem_bytes, proc_mem_bytes)
|
|
1080
|
+
next_collection = curr_time + Task.__MEM_POLL_INTERVAL
|
|
1081
|
+
|
|
1082
|
+
memory_warn_limit = self.__check_memory_limit(memory_warn_limit)
|
|
1061
1083
|
|
|
1062
1084
|
read_stdio(stdout_reader, stderr_reader)
|
|
1063
1085
|
|
|
1064
1086
|
# Check for timeout
|
|
1065
|
-
duration =
|
|
1087
|
+
duration = curr_time - cpu_start
|
|
1066
1088
|
if timeout is not None and duration > timeout:
|
|
1067
1089
|
raise TaskTimeout(timeout=duration)
|
|
1068
1090
|
|
|
1069
|
-
time.sleep(Task.
|
|
1091
|
+
time.sleep(Task.__IO_POLL_INTERVAL)
|
|
1070
1092
|
except KeyboardInterrupt:
|
|
1071
1093
|
self.logger.info("Received ctrl-c.")
|
|
1072
1094
|
self.__terminate_exe(proc)
|
|
@@ -172,6 +172,7 @@ class KLayoutTask(ASICTask):
|
|
|
172
172
|
with self.active_dataroot("refdir"):
|
|
173
173
|
self.set_refdir("scripts")
|
|
174
174
|
|
|
175
|
+
self.set_environmentalvariable('PYTHONUNBUFFERED', '1')
|
|
175
176
|
if self.project.get('option', 'nodisplay'):
|
|
176
177
|
# Tells QT to use the offscreen platform if nodisplay is used
|
|
177
178
|
self.set_environmentalvariable('QT_QPA_PLATFORM', 'offscreen')
|
|
@@ -185,6 +186,8 @@ class KLayoutTask(ASICTask):
|
|
|
185
186
|
options = super().runtime_options()
|
|
186
187
|
options.extend(['-rd', f'SC_KLAYOUT_ROOT={self.find_files("refdir")[0]}'])
|
|
187
188
|
options.extend(['-rd', f'SC_TOOLS_ROOT={os.path.dirname(os.path.dirname(__file__))}'])
|
|
189
|
+
options.extend(['-rd',
|
|
190
|
+
f'SC_ROOT={os.path.dirname(os.path.dirname(os.path.dirname(__file__)))}'])
|
|
188
191
|
return options
|
|
189
192
|
|
|
190
193
|
def post_process(self):
|
|
@@ -193,6 +193,7 @@ def main():
|
|
|
193
193
|
# SC_ROOT provided by CLI, and is only accessible when this is main module
|
|
194
194
|
sys.path.append(SC_KLAYOUT_ROOT) # noqa: F821
|
|
195
195
|
sys.path.append(SC_TOOLS_ROOT) # noqa: F821
|
|
196
|
+
sys.path.append(SC_ROOT) # noqa: F821
|
|
196
197
|
|
|
197
198
|
from klayout_utils import (
|
|
198
199
|
technology,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import pya
|
|
2
2
|
import json
|
|
3
3
|
import shutil
|
|
4
|
-
import sys
|
|
5
4
|
import os.path
|
|
6
5
|
|
|
7
6
|
|
|
@@ -129,8 +128,9 @@ def technology(design, schema):
|
|
|
129
128
|
map_file = os.path.abspath(os.path.join(os.path.dirname(tech_file),
|
|
130
129
|
map_file))
|
|
131
130
|
for s in get_streams(schema):
|
|
132
|
-
if schema.valid('library', sc_pdk, 'layermapfileset', 'klayout', 'def', s):
|
|
133
|
-
for fileset in schema.get('library', sc_pdk, '
|
|
131
|
+
if schema.valid('library', sc_pdk, 'pdk', 'layermapfileset', 'klayout', 'def', s):
|
|
132
|
+
for fileset in schema.get('library', sc_pdk, 'pdk', 'layermapfileset', 'klayout',
|
|
133
|
+
'def', s):
|
|
134
134
|
if schema.valid('library', sc_pdk, "fileset", fileset, "file", "layermap"):
|
|
135
135
|
map_file = schema.get('library', sc_pdk, "fileset", fileset, "file", "layermap")
|
|
136
136
|
if map_file:
|
|
@@ -170,7 +170,6 @@ def get_write_options(filename, timestamps):
|
|
|
170
170
|
|
|
171
171
|
|
|
172
172
|
def get_schema(manifest):
|
|
173
|
-
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
|
|
174
173
|
from schema.safeschema import SafeSchema
|
|
175
174
|
return SafeSchema.from_manifest(filepath=manifest)
|
|
176
175
|
|