siliconcompiler 0.33.0__py3-none-any.whl → 0.33.2__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/_common.py +5 -0
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/sc_install.py +7 -0
- siliconcompiler/apps/sc_remote.py +7 -2
- siliconcompiler/apps/utils/replay.py +5 -5
- siliconcompiler/core.py +38 -12
- siliconcompiler/data/templates/replay/replay.sh.j2 +18 -1
- siliconcompiler/metric.py +78 -0
- siliconcompiler/package/git.py +1 -1
- siliconcompiler/record.py +63 -7
- siliconcompiler/remote/client.py +57 -14
- siliconcompiler/remote/server.py +110 -60
- siliconcompiler/report/dashboard/cli/__init__.py +2 -0
- siliconcompiler/report/dashboard/cli/board.py +34 -31
- siliconcompiler/report/report.py +10 -5
- siliconcompiler/report/utils.py +12 -6
- siliconcompiler/scheduler/__init__.py +146 -976
- siliconcompiler/scheduler/run_node.py +12 -5
- siliconcompiler/scheduler/send_messages.py +9 -3
- siliconcompiler/scheduler/slurm.py +10 -43
- siliconcompiler/scheduler/taskscheduler.py +320 -0
- siliconcompiler/schema/baseschema.py +25 -4
- siliconcompiler/schema/journalingschema.py +4 -0
- siliconcompiler/schema/schema_cfg.py +3 -3
- siliconcompiler/tool.py +201 -32
- siliconcompiler/tools/_common/__init__.py +14 -11
- siliconcompiler/tools/_common/asic.py +5 -5
- siliconcompiler/tools/bluespec/convert.py +2 -1
- siliconcompiler/tools/builtin/_common.py +9 -2
- siliconcompiler/tools/builtin/concatenate.py +6 -2
- siliconcompiler/tools/builtin/minimum.py +7 -2
- siliconcompiler/tools/builtin/mux.py +7 -2
- siliconcompiler/tools/builtin/nop.py +7 -2
- siliconcompiler/tools/builtin/verify.py +7 -3
- siliconcompiler/tools/chisel/convert.py +10 -10
- siliconcompiler/tools/klayout/drc.py +2 -2
- siliconcompiler/tools/klayout/klayout_show.py +6 -6
- siliconcompiler/tools/klayout/klayout_utils.py +12 -12
- siliconcompiler/tools/netgen/count_lvs.py +2 -2
- siliconcompiler/tools/netgen/lvs.py +1 -1
- siliconcompiler/tools/openroad/_apr.py +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +1 -7
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +18 -0
- siliconcompiler/tools/openroad/scripts/common/read_input_files.tcl +1 -7
- siliconcompiler/tools/opensta/scripts/sc_timing.tcl +10 -0
- siliconcompiler/tools/opensta/timing.py +11 -0
- siliconcompiler/tools/slang/__init__.py +6 -5
- siliconcompiler/tools/slang/elaborate.py +6 -6
- siliconcompiler/tools/slang/lint.py +1 -3
- siliconcompiler/tools/vpr/_xml_constraint.py +8 -8
- siliconcompiler/tools/yosys/prepareLib.py +2 -2
- siliconcompiler/tools/yosys/sc_synth_asic.tcl +43 -5
- siliconcompiler/tools/yosys/screenshot.py +1 -1
- siliconcompiler/tools/yosys/syn_asic.py +5 -5
- siliconcompiler/toolscripts/_tools.json +17 -10
- siliconcompiler/toolscripts/rhel8/install-chisel.sh +9 -2
- siliconcompiler/toolscripts/rhel8/install-icarus.sh +10 -3
- siliconcompiler/toolscripts/rhel8/install-klayout.sh +8 -1
- siliconcompiler/toolscripts/rhel8/install-magic.sh +9 -2
- siliconcompiler/toolscripts/rhel8/install-montage.sh +1 -1
- siliconcompiler/toolscripts/rhel8/install-netgen.sh +9 -2
- siliconcompiler/toolscripts/rhel8/install-slang.sh +11 -4
- siliconcompiler/toolscripts/rhel8/install-surelog.sh +9 -2
- siliconcompiler/toolscripts/rhel8/install-sv2v.sh +11 -4
- siliconcompiler/toolscripts/rhel8/install-verible.sh +11 -3
- siliconcompiler/toolscripts/rhel8/install-verilator.sh +10 -3
- siliconcompiler/toolscripts/rhel8/install-xyce.sh +15 -10
- siliconcompiler/toolscripts/rhel9/install-chisel.sh +9 -2
- siliconcompiler/toolscripts/rhel9/install-ghdl.sh +9 -2
- siliconcompiler/toolscripts/rhel9/install-gtkwave.sh +10 -3
- siliconcompiler/toolscripts/rhel9/install-icarus.sh +10 -3
- siliconcompiler/toolscripts/rhel9/install-klayout.sh +8 -1
- siliconcompiler/toolscripts/rhel9/install-magic.sh +9 -2
- siliconcompiler/toolscripts/rhel9/install-montage.sh +1 -1
- siliconcompiler/toolscripts/rhel9/install-netgen.sh +9 -2
- siliconcompiler/toolscripts/rhel9/install-openroad.sh +16 -3
- siliconcompiler/toolscripts/rhel9/install-opensta.sh +17 -5
- siliconcompiler/toolscripts/rhel9/install-slang.sh +11 -4
- siliconcompiler/toolscripts/rhel9/install-surelog.sh +9 -2
- siliconcompiler/toolscripts/rhel9/install-sv2v.sh +11 -4
- siliconcompiler/toolscripts/rhel9/install-verible.sh +11 -3
- siliconcompiler/toolscripts/rhel9/install-verilator.sh +10 -3
- siliconcompiler/toolscripts/rhel9/install-vpr.sh +9 -2
- siliconcompiler/toolscripts/rhel9/install-xdm.sh +10 -2
- siliconcompiler/toolscripts/rhel9/install-xyce.sh +15 -10
- siliconcompiler/toolscripts/rhel9/install-yosys-moosic.sh +9 -2
- siliconcompiler/toolscripts/rhel9/install-yosys-parmys.sh +10 -3
- siliconcompiler/toolscripts/rhel9/install-yosys-slang.sh +10 -2
- siliconcompiler/toolscripts/rhel9/install-yosys.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-bambu.sh +10 -2
- siliconcompiler/toolscripts/ubuntu20/install-bluespec.sh +10 -3
- siliconcompiler/toolscripts/ubuntu20/install-chisel.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-ghdl.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-gtkwave.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-icarus.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-icepack.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-klayout.sh +8 -1
- siliconcompiler/toolscripts/ubuntu20/install-magic.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-montage.sh +1 -1
- siliconcompiler/toolscripts/ubuntu20/install-netgen.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-nextpnr.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-openroad.sh +16 -3
- siliconcompiler/toolscripts/ubuntu20/install-opensta.sh +16 -5
- siliconcompiler/toolscripts/ubuntu20/install-slang.sh +11 -4
- siliconcompiler/toolscripts/ubuntu20/install-slurm.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-surelog.sh +10 -2
- siliconcompiler/toolscripts/ubuntu20/install-sv2v.sh +11 -4
- siliconcompiler/toolscripts/ubuntu20/install-verible.sh +11 -3
- siliconcompiler/toolscripts/ubuntu20/install-verilator.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-xdm.sh +10 -2
- siliconcompiler/toolscripts/ubuntu20/install-xyce.sh +13 -8
- siliconcompiler/toolscripts/ubuntu20/install-yosys-moosic.sh +9 -2
- siliconcompiler/toolscripts/ubuntu20/install-yosys.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-bambu.sh +10 -2
- siliconcompiler/toolscripts/ubuntu22/install-bluespec.sh +10 -3
- siliconcompiler/toolscripts/ubuntu22/install-chisel.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-ghdl.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-gtkwave.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-icarus.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-icepack.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-klayout.sh +12 -1
- siliconcompiler/toolscripts/ubuntu22/install-magic.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-montage.sh +1 -1
- siliconcompiler/toolscripts/ubuntu22/install-netgen.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-nextpnr.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-openroad.sh +16 -3
- siliconcompiler/toolscripts/ubuntu22/install-opensta.sh +17 -5
- siliconcompiler/toolscripts/ubuntu22/install-slang.sh +11 -4
- siliconcompiler/toolscripts/ubuntu22/install-slurm.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-surelog.sh +10 -2
- siliconcompiler/toolscripts/ubuntu22/install-sv2v.sh +11 -4
- siliconcompiler/toolscripts/ubuntu22/install-verible.sh +11 -3
- siliconcompiler/toolscripts/ubuntu22/install-verilator.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-vpr.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-xdm.sh +10 -2
- siliconcompiler/toolscripts/ubuntu22/install-xyce.sh +13 -8
- siliconcompiler/toolscripts/ubuntu22/install-yosys-moosic.sh +9 -2
- siliconcompiler/toolscripts/ubuntu22/install-yosys-parmys.sh +10 -3
- siliconcompiler/toolscripts/ubuntu22/install-yosys-slang.sh +10 -2
- siliconcompiler/toolscripts/ubuntu22/install-yosys.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-bambu.sh +12 -4
- siliconcompiler/toolscripts/ubuntu24/install-bluespec.sh +10 -3
- siliconcompiler/toolscripts/ubuntu24/install-chisel.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-ghdl.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-gtkwave.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-icarus.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-icepack.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +12 -1
- siliconcompiler/toolscripts/ubuntu24/install-magic.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-montage.sh +1 -1
- siliconcompiler/toolscripts/ubuntu24/install-netgen.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-nextpnr.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-openroad.sh +16 -3
- siliconcompiler/toolscripts/ubuntu24/install-opensta.sh +17 -5
- siliconcompiler/toolscripts/ubuntu24/install-slang.sh +11 -4
- siliconcompiler/toolscripts/ubuntu24/install-slurm.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-surelog.sh +10 -2
- siliconcompiler/toolscripts/ubuntu24/install-sv2v.sh +11 -4
- siliconcompiler/toolscripts/ubuntu24/install-verible.sh +11 -3
- siliconcompiler/toolscripts/ubuntu24/install-verilator.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-vpr.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-xdm.sh +10 -2
- siliconcompiler/toolscripts/ubuntu24/install-xyce.sh +13 -8
- siliconcompiler/toolscripts/ubuntu24/install-yosys-moosic.sh +9 -2
- siliconcompiler/toolscripts/ubuntu24/install-yosys-parmys.sh +10 -3
- siliconcompiler/toolscripts/ubuntu24/install-yosys-slang.sh +10 -2
- siliconcompiler/toolscripts/ubuntu24/install-yosys.sh +9 -2
- siliconcompiler/utils/__init__.py +11 -0
- siliconcompiler/utils/flowgraph.py +6 -101
- siliconcompiler/utils/issue.py +15 -23
- siliconcompiler/utils/logging.py +2 -2
- {siliconcompiler-0.33.0.dist-info → siliconcompiler-0.33.2.dist-info}/METADATA +6 -5
- {siliconcompiler-0.33.0.dist-info → siliconcompiler-0.33.2.dist-info}/RECORD +177 -176
- {siliconcompiler-0.33.0.dist-info → siliconcompiler-0.33.2.dist-info}/WHEEL +1 -1
- {siliconcompiler-0.33.0.dist-info → siliconcompiler-0.33.2.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.33.0.dist-info → siliconcompiler-0.33.2.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.33.0.dist-info → siliconcompiler-0.33.2.dist-info}/top_level.txt +0 -0
|
@@ -4,6 +4,8 @@ import argparse
|
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
6
|
import tarfile
|
|
7
|
+
import os.path
|
|
8
|
+
|
|
7
9
|
from siliconcompiler import Chip, Schema
|
|
8
10
|
from siliconcompiler.package import path as sc_path
|
|
9
11
|
from siliconcompiler.scheduler import _runtask, _executenode
|
|
@@ -38,7 +40,6 @@ def main():
|
|
|
38
40
|
field='shorthelp'))
|
|
39
41
|
parser.add_argument('-cachedir',
|
|
40
42
|
metavar='<directory>',
|
|
41
|
-
required=True,
|
|
42
43
|
help=schema.get('option', 'cachedir',
|
|
43
44
|
field='shorthelp'))
|
|
44
45
|
parser.add_argument('-cachemap',
|
|
@@ -72,12 +73,15 @@ def main():
|
|
|
72
73
|
parser.add_argument('-unset_scheduler',
|
|
73
74
|
action='store_true',
|
|
74
75
|
help='Unset scheduler to ensure local run')
|
|
76
|
+
parser.add_argument('-replay',
|
|
77
|
+
action='store_true',
|
|
78
|
+
help='Running as replay')
|
|
75
79
|
args = parser.parse_args()
|
|
76
80
|
|
|
77
81
|
# Change to working directory to allow rel path to be build dir
|
|
78
82
|
# this avoids needing to deal with the job hash on the client
|
|
79
83
|
# side
|
|
80
|
-
os.chdir(args.cwd)
|
|
84
|
+
os.chdir(os.path.abspath(args.cwd))
|
|
81
85
|
|
|
82
86
|
# Create the Chip object.
|
|
83
87
|
chip = Chip('<design>')
|
|
@@ -86,8 +90,10 @@ def main():
|
|
|
86
90
|
# setup work directory
|
|
87
91
|
chip.set('arg', 'step', args.step)
|
|
88
92
|
chip.set('arg', 'index', args.index)
|
|
89
|
-
chip.set('option', 'builddir', args.builddir)
|
|
90
|
-
|
|
93
|
+
chip.set('option', 'builddir', os.path.abspath(args.builddir))
|
|
94
|
+
|
|
95
|
+
if args.cachedir:
|
|
96
|
+
chip.set('option', 'cachedir', os.path.abspath(args.cachedir))
|
|
91
97
|
|
|
92
98
|
if args.remoteid:
|
|
93
99
|
chip.set('record', 'remoteid', args.remoteid)
|
|
@@ -118,7 +124,8 @@ def main():
|
|
|
118
124
|
chip.get('option', 'flow'),
|
|
119
125
|
chip.get('arg', 'step'),
|
|
120
126
|
chip.get('arg', 'index'),
|
|
121
|
-
_executenode
|
|
127
|
+
_executenode,
|
|
128
|
+
replay=args.replay)
|
|
122
129
|
error = False
|
|
123
130
|
|
|
124
131
|
finally:
|
|
@@ -10,8 +10,8 @@ from siliconcompiler import Schema
|
|
|
10
10
|
from siliconcompiler.report import utils as report_utils
|
|
11
11
|
import fastjsonschema
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from siliconcompiler.utils.flowgraph import nodes_to_execute
|
|
14
13
|
import uuid
|
|
14
|
+
from siliconcompiler.flowgraph import RuntimeFlowgraph
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
# Compile validation code for API request bodies.
|
|
@@ -25,7 +25,7 @@ with open(api_dir / 'email_credentials.json') as schema:
|
|
|
25
25
|
def __load_config(chip):
|
|
26
26
|
path = default_email_credentials_file()
|
|
27
27
|
if not os.path.exists(path):
|
|
28
|
-
chip.logger.
|
|
28
|
+
chip.logger.warning(f'Email credentials are not available: {path}')
|
|
29
29
|
return {}
|
|
30
30
|
|
|
31
31
|
with open(path) as f:
|
|
@@ -91,9 +91,15 @@ def send(chip, msg_type, step, index):
|
|
|
91
91
|
filename=os.path.basename(layout_img))
|
|
92
92
|
msg.attach(img_attach)
|
|
93
93
|
|
|
94
|
+
runtime = RuntimeFlowgraph(
|
|
95
|
+
chip.schema.get("flowgraph", flow, field='schema'),
|
|
96
|
+
from_steps=chip.get('option', 'from'),
|
|
97
|
+
to_steps=chip.get('option', 'to'),
|
|
98
|
+
prune_nodes=chip.get('option', 'prune'))
|
|
99
|
+
|
|
94
100
|
nodes, errors, metrics, metrics_unit, metrics_to_show, _ = \
|
|
95
101
|
report_utils._collect_data(chip, flow=flow,
|
|
96
|
-
flowgraph_nodes=
|
|
102
|
+
flowgraph_nodes=runtime.get_nodes())
|
|
97
103
|
|
|
98
104
|
text_msg = get_file_template('email/summary.j2').render(
|
|
99
105
|
design=chip.design,
|
|
@@ -2,13 +2,12 @@ import os
|
|
|
2
2
|
import shlex
|
|
3
3
|
import subprocess
|
|
4
4
|
import stat
|
|
5
|
-
import time
|
|
6
5
|
import uuid
|
|
7
6
|
import json
|
|
8
7
|
import shutil
|
|
9
8
|
from siliconcompiler import utils, SiliconCompilerError
|
|
10
|
-
from siliconcompiler.utils.flowgraph import nodes_to_execute
|
|
11
9
|
from siliconcompiler.package import get_cache_path
|
|
10
|
+
from siliconcompiler.flowgraph import RuntimeFlowgraph
|
|
12
11
|
|
|
13
12
|
# Full list of Slurm states, split into 'active' and 'inactive' categories.
|
|
14
13
|
# Many of these do not apply to a minimal configuration, but we'll track them all.
|
|
@@ -60,7 +59,14 @@ def init(chip):
|
|
|
60
59
|
collect = False
|
|
61
60
|
flow = chip.get('option', 'flow')
|
|
62
61
|
entry_nodes = chip.schema.get("flowgraph", flow, field="schema").get_entry_nodes()
|
|
63
|
-
|
|
62
|
+
|
|
63
|
+
runtime = RuntimeFlowgraph(
|
|
64
|
+
chip.schema.get("flowgraph", flow, field='schema'),
|
|
65
|
+
from_steps=chip.get('option', 'from'),
|
|
66
|
+
to_steps=chip.get('option', 'to'),
|
|
67
|
+
prune_nodes=chip.get('option', 'prune'))
|
|
68
|
+
|
|
69
|
+
for (step, index) in runtime.get_nodes():
|
|
64
70
|
if (step, index) in entry_nodes:
|
|
65
71
|
collect = True
|
|
66
72
|
|
|
@@ -125,7 +131,7 @@ def _defernode(chip, step, index, replay):
|
|
|
125
131
|
os.chmod(script_file,
|
|
126
132
|
os.stat(script_file).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
127
133
|
|
|
128
|
-
schedule_cmd = ['
|
|
134
|
+
schedule_cmd = ['srun',
|
|
129
135
|
'--exclusive',
|
|
130
136
|
'--partition', partition,
|
|
131
137
|
'--chdir', chip.cwd,
|
|
@@ -149,45 +155,6 @@ def _defernode(chip, step, index, replay):
|
|
|
149
155
|
# as it has closed its output stream. But if we don't call '.wait()',
|
|
150
156
|
# the '.returncode' value will not be set correctly.
|
|
151
157
|
step_result.wait()
|
|
152
|
-
result_msg = step_result.stdout.read().decode()
|
|
153
|
-
sbatch_id = result_msg.split(' ')[-1].strip()
|
|
154
|
-
retcode = 0
|
|
155
|
-
|
|
156
|
-
while True:
|
|
157
|
-
# Return early with an error if the batch ID is not an integer.
|
|
158
|
-
if not sbatch_id.isdigit():
|
|
159
|
-
retcode = 1
|
|
160
|
-
break
|
|
161
|
-
|
|
162
|
-
# Rate-limit the status checks to once every few seconds.
|
|
163
|
-
time.sleep(3.0)
|
|
164
|
-
|
|
165
|
-
# Check whether the job is still running.
|
|
166
|
-
jobcheck = subprocess.run(['scontrol', 'show', 'job', sbatch_id],
|
|
167
|
-
stdout=subprocess.PIPE,
|
|
168
|
-
stderr=subprocess.STDOUT)
|
|
169
|
-
jobout = jobcheck.stdout.decode()
|
|
170
|
-
|
|
171
|
-
# Jobs have a number of potential states that they can be in if they
|
|
172
|
-
# are still active in the Slurm scheduler.
|
|
173
|
-
if [state for state in SLURM_ACTIVE_STATES if state in jobout]:
|
|
174
|
-
pass
|
|
175
|
-
# 'COMPLETED' is a special case indicating successful job termination.
|
|
176
|
-
elif 'COMPLETED' in jobout:
|
|
177
|
-
break
|
|
178
|
-
elif 'Invalid job id specified' in jobout:
|
|
179
|
-
# May have already completed and been purged from active list.
|
|
180
|
-
break
|
|
181
|
-
# Jobs have a number of potential states that they can be in if they
|
|
182
|
-
# did not terminate successfully.
|
|
183
|
-
elif [state for state in SLURM_INACTIVE_STATES if state in jobout]:
|
|
184
|
-
# FAILED, TIMEOUT, etc.
|
|
185
|
-
retcode = 1
|
|
186
|
-
break
|
|
187
|
-
|
|
188
|
-
if retcode > 0:
|
|
189
|
-
chip.logger.error(f'srun command for {step} failed.')
|
|
190
|
-
chip.logger.error(f'srun output for {step}: {jobout}')
|
|
191
158
|
|
|
192
159
|
|
|
193
160
|
def _get_slurm_partition():
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import multiprocessing
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import os.path
|
|
6
|
+
|
|
7
|
+
from logging.handlers import QueueListener
|
|
8
|
+
|
|
9
|
+
from siliconcompiler import NodeStatus
|
|
10
|
+
from siliconcompiler import SiliconCompilerError
|
|
11
|
+
from siliconcompiler import utils
|
|
12
|
+
from siliconcompiler.flowgraph import RuntimeFlowgraph
|
|
13
|
+
|
|
14
|
+
from siliconcompiler.schema import JournalingSchema
|
|
15
|
+
|
|
16
|
+
from siliconcompiler.scheduler import slurm
|
|
17
|
+
from siliconcompiler.scheduler import docker_runner
|
|
18
|
+
from siliconcompiler.tools._common import get_tool_task
|
|
19
|
+
from siliconcompiler.utils.logging import SCBlankLoggerFormatter
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TaskScheduler:
|
|
23
|
+
__callbacks = {
|
|
24
|
+
"pre_run": lambda chip: None,
|
|
25
|
+
"pre_node": lambda chip, step, index: None,
|
|
26
|
+
"post_node": lambda chip, step, index: None,
|
|
27
|
+
"post_run": lambda chip: None,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def register_callback(hook, func):
|
|
32
|
+
if hook not in TaskScheduler.__callbacks:
|
|
33
|
+
raise ValueError(f"{hook} is not a valid callback")
|
|
34
|
+
TaskScheduler.__callbacks[hook] = func
|
|
35
|
+
|
|
36
|
+
def __init__(self, chip):
|
|
37
|
+
self.__chip = chip
|
|
38
|
+
self.__logger = self.__chip.logger
|
|
39
|
+
self.__schema = self.__chip.schema
|
|
40
|
+
self.__flow = self.__schema.get("flowgraph", self.__chip.get('option', 'flow'),
|
|
41
|
+
field="schema")
|
|
42
|
+
self.__record = self.__schema.get("record", field="schema")
|
|
43
|
+
self.__dashboard = chip._dash
|
|
44
|
+
|
|
45
|
+
self.__max_cores = utils.get_cores(chip)
|
|
46
|
+
self.__max_threads = utils.get_cores(chip)
|
|
47
|
+
self.__max_parallel_run = self.__chip.get('option', 'scheduler', 'maxnodes')
|
|
48
|
+
if not self.__max_parallel_run:
|
|
49
|
+
self.__max_parallel_run = utils.get_cores(chip)
|
|
50
|
+
# clip max parallel jobs to 1 <= jobs <= max_cores
|
|
51
|
+
self.__max_parallel_run = max(1, min(self.__max_parallel_run, self.__max_cores))
|
|
52
|
+
|
|
53
|
+
self.__log_queue = multiprocessing.Queue(-1)
|
|
54
|
+
|
|
55
|
+
self.__nodes = {}
|
|
56
|
+
self.__startTimes = {}
|
|
57
|
+
self.__dwellTime = 0.1
|
|
58
|
+
|
|
59
|
+
self.__create_nodes()
|
|
60
|
+
|
|
61
|
+
def __create_nodes(self):
|
|
62
|
+
from siliconcompiler.scheduler import _executenode, _runtask
|
|
63
|
+
|
|
64
|
+
runtime = RuntimeFlowgraph(
|
|
65
|
+
self.__flow,
|
|
66
|
+
from_steps=set([step for step, _ in self.__flow.get_entry_nodes()]),
|
|
67
|
+
prune_nodes=self.__chip.get('option', 'prune'))
|
|
68
|
+
|
|
69
|
+
init_funcs = set()
|
|
70
|
+
|
|
71
|
+
runtime_flow = RuntimeFlowgraph(
|
|
72
|
+
self.__flow,
|
|
73
|
+
from_steps=self.__chip.get('option', 'from'),
|
|
74
|
+
to_steps=self.__chip.get('option', 'to'),
|
|
75
|
+
prune_nodes=self.__chip.get('option', 'prune'))
|
|
76
|
+
|
|
77
|
+
for step, index in runtime_flow.get_nodes():
|
|
78
|
+
if self.__record.get('status', step=step, index=index) != NodeStatus.PENDING:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
tool_name, task_name = get_tool_task(self.__chip, step, index)
|
|
82
|
+
threads = self.__chip.get('tool', tool_name, 'task', task_name, 'threads',
|
|
83
|
+
step=step, index=index)
|
|
84
|
+
if not threads:
|
|
85
|
+
threads = self.__max_threads
|
|
86
|
+
threads = max(1, min(threads, self.__max_threads))
|
|
87
|
+
|
|
88
|
+
task = {
|
|
89
|
+
"name": f"{step}{index}",
|
|
90
|
+
"inputs": runtime.get_node_inputs(step, index, record=self.__record),
|
|
91
|
+
"proc": None,
|
|
92
|
+
"child_pipe": None,
|
|
93
|
+
"parent_pipe": None,
|
|
94
|
+
"local": False,
|
|
95
|
+
"tool": tool_name,
|
|
96
|
+
"task": task_name,
|
|
97
|
+
"threads": threads,
|
|
98
|
+
"running": False,
|
|
99
|
+
"manifest": os.path.join(self.__chip.getworkdir(step=step, index=index),
|
|
100
|
+
'outputs',
|
|
101
|
+
f'{self.__chip.design}.pkg.json')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
exec_func = _executenode
|
|
105
|
+
|
|
106
|
+
node_scheduler = self.__chip.get('option', 'scheduler', 'name', step=step, index=index)
|
|
107
|
+
if node_scheduler == 'slurm':
|
|
108
|
+
# Defer job to compute node
|
|
109
|
+
# If the job is configured to run on a cluster, collect the schema
|
|
110
|
+
# and send it to a compute node for deferred execution.
|
|
111
|
+
init_funcs.add(slurm.init)
|
|
112
|
+
exec_func = slurm._defernode
|
|
113
|
+
elif node_scheduler == 'docker':
|
|
114
|
+
# Run job in docker
|
|
115
|
+
init_funcs.add(docker_runner.init)
|
|
116
|
+
exec_func = docker_runner.run
|
|
117
|
+
task["local"] = True
|
|
118
|
+
else:
|
|
119
|
+
task["local"] = True
|
|
120
|
+
|
|
121
|
+
task["parent_pipe"], task["child_pipe"] = multiprocessing.Pipe()
|
|
122
|
+
task["proc"] = multiprocessing.Process(
|
|
123
|
+
target=_runtask,
|
|
124
|
+
args=(self.__chip, self.__flow.name(), step, index, exec_func),
|
|
125
|
+
kwargs={"pipe": task["child_pipe"],
|
|
126
|
+
"queue": self.__log_queue})
|
|
127
|
+
|
|
128
|
+
self.__nodes[(step, index)] = task
|
|
129
|
+
|
|
130
|
+
# Call preprocessing for schedulers
|
|
131
|
+
for init_func in init_funcs:
|
|
132
|
+
init_func(self.__chip)
|
|
133
|
+
|
|
134
|
+
def run(self):
|
|
135
|
+
# Call this in case this was invoked without __main__
|
|
136
|
+
multiprocessing.freeze_support()
|
|
137
|
+
|
|
138
|
+
# Handle logs across threads
|
|
139
|
+
log_listener = QueueListener(self.__log_queue, self.__logger._console)
|
|
140
|
+
console_format = self.__logger._console.formatter
|
|
141
|
+
self.__logger._console.setFormatter(SCBlankLoggerFormatter())
|
|
142
|
+
log_listener.start()
|
|
143
|
+
|
|
144
|
+
# Update dashboard before run begins
|
|
145
|
+
if self.__dashboard:
|
|
146
|
+
self.__dashboard.update_manifest()
|
|
147
|
+
|
|
148
|
+
TaskScheduler.__callbacks["pre_run"](self.__chip)
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
self.__run_loop()
|
|
152
|
+
except KeyboardInterrupt:
|
|
153
|
+
# exit immediately
|
|
154
|
+
log_listener.stop()
|
|
155
|
+
sys.exit(0)
|
|
156
|
+
|
|
157
|
+
TaskScheduler.__callbacks["post_run"](self.__chip)
|
|
158
|
+
|
|
159
|
+
# Cleanup logger
|
|
160
|
+
log_listener.stop()
|
|
161
|
+
self.__logger._console.setFormatter(console_format)
|
|
162
|
+
|
|
163
|
+
def __run_loop(self):
|
|
164
|
+
self.__startTimes = {None: time.time()}
|
|
165
|
+
|
|
166
|
+
while len(self.get_nodes_waiting_to_run()) > 0 or len(self.get_running_nodes()) > 0:
|
|
167
|
+
changed = self.__process_completed_nodes()
|
|
168
|
+
changed |= self.__lanuch_nodes()
|
|
169
|
+
|
|
170
|
+
if changed and self.__dashboard:
|
|
171
|
+
# Update dashboard if the manifest changed
|
|
172
|
+
self.__dashboard.update_manifest(payload={"starttimes": self.__startTimes})
|
|
173
|
+
|
|
174
|
+
running_nodes = self.get_running_nodes()
|
|
175
|
+
|
|
176
|
+
# Check for situation where we have stuff left to run but don't
|
|
177
|
+
# have any nodes running. This shouldn't happen, but we will get
|
|
178
|
+
# stuck in an infinite loop if it does, so we want to break out
|
|
179
|
+
# with an explicit error.
|
|
180
|
+
if len(self.get_nodes_waiting_to_run()) > 0 and len(running_nodes) == 0:
|
|
181
|
+
raise SiliconCompilerError(
|
|
182
|
+
'Nodes left to run, but no running nodes. From/to may be invalid.',
|
|
183
|
+
chip=self.__chip)
|
|
184
|
+
|
|
185
|
+
if len(running_nodes) == 1:
|
|
186
|
+
# if there is only one node running, just join the thread
|
|
187
|
+
self.__nodes[running_nodes[0]]["proc"].join()
|
|
188
|
+
elif len(running_nodes) > 1:
|
|
189
|
+
# if there are more than 1, join the first with a timeout
|
|
190
|
+
self.__nodes[running_nodes[0]]["proc"].join(timeout=self.__dwellTime)
|
|
191
|
+
|
|
192
|
+
def get_nodes(self):
|
|
193
|
+
return sorted(self.__nodes.keys())
|
|
194
|
+
|
|
195
|
+
def get_running_nodes(self):
|
|
196
|
+
nodes = []
|
|
197
|
+
for node, info in self.__nodes.items():
|
|
198
|
+
if info["running"]:
|
|
199
|
+
nodes.append(node)
|
|
200
|
+
return sorted(nodes)
|
|
201
|
+
|
|
202
|
+
def get_nodes_waiting_to_run(self):
|
|
203
|
+
nodes = []
|
|
204
|
+
for node, info in self.__nodes.items():
|
|
205
|
+
if not info["running"] and info["proc"]:
|
|
206
|
+
nodes.append(node)
|
|
207
|
+
return sorted(nodes)
|
|
208
|
+
|
|
209
|
+
def __process_completed_nodes(self):
|
|
210
|
+
changed = False
|
|
211
|
+
for node in self.get_running_nodes():
|
|
212
|
+
info = self.__nodes[node]
|
|
213
|
+
|
|
214
|
+
if not info["proc"].is_alive():
|
|
215
|
+
manifest = info["manifest"]
|
|
216
|
+
|
|
217
|
+
self.__logger.debug(f'{info["name"]} is complete merging: {manifest}')
|
|
218
|
+
|
|
219
|
+
if os.path.exists(manifest):
|
|
220
|
+
JournalingSchema(self.__schema).read_journal(manifest)
|
|
221
|
+
|
|
222
|
+
if info["parent_pipe"] and info["parent_pipe"].poll(1):
|
|
223
|
+
try:
|
|
224
|
+
packages = info["parent_pipe"].recv()
|
|
225
|
+
if isinstance(packages, dict):
|
|
226
|
+
self.__chip._packages.update(packages)
|
|
227
|
+
except: # noqa E722
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
step, index = node
|
|
231
|
+
if info["proc"].exitcode > 0:
|
|
232
|
+
status = NodeStatus.ERROR
|
|
233
|
+
else:
|
|
234
|
+
status = self.__record.get('status', step=step, index=index)
|
|
235
|
+
if not status or status == NodeStatus.PENDING:
|
|
236
|
+
status = NodeStatus.ERROR
|
|
237
|
+
|
|
238
|
+
self.__record.set('status', status, step=step, index=index)
|
|
239
|
+
|
|
240
|
+
info["running"] = False
|
|
241
|
+
info["proc"] = None
|
|
242
|
+
|
|
243
|
+
changed = True
|
|
244
|
+
|
|
245
|
+
TaskScheduler.__callbacks['post_node'](self.__chip, step, index)
|
|
246
|
+
|
|
247
|
+
return changed
|
|
248
|
+
|
|
249
|
+
def __allow_start(self, node):
|
|
250
|
+
info = self.__nodes[node]
|
|
251
|
+
|
|
252
|
+
if not info["local"]:
|
|
253
|
+
# using a different scheduler, so allow
|
|
254
|
+
return True
|
|
255
|
+
|
|
256
|
+
running_nodes = self.get_running_nodes()
|
|
257
|
+
|
|
258
|
+
if len(running_nodes) >= self.__max_parallel_run:
|
|
259
|
+
# exceeding machine resources
|
|
260
|
+
return False
|
|
261
|
+
|
|
262
|
+
current_threads = sum([self.__nodes[run_node]["threads"] for run_node in running_nodes])
|
|
263
|
+
|
|
264
|
+
if info["threads"] + current_threads > self.__max_cores:
|
|
265
|
+
# delay until there are enough core available
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
# allow
|
|
269
|
+
return True
|
|
270
|
+
|
|
271
|
+
def __lanuch_nodes(self):
|
|
272
|
+
changed = False
|
|
273
|
+
for node in self.get_nodes_waiting_to_run():
|
|
274
|
+
# TODO: breakpoint logic:
|
|
275
|
+
# if node is breakpoint, then don't launch while len(running_nodes) > 0
|
|
276
|
+
|
|
277
|
+
info = self.__nodes[node]
|
|
278
|
+
step, index = node
|
|
279
|
+
|
|
280
|
+
ready = True
|
|
281
|
+
inputs = []
|
|
282
|
+
for in_step, in_index in info["inputs"]:
|
|
283
|
+
in_status = self.__record.get('status', step=in_step, index=in_index)
|
|
284
|
+
inputs.append(in_status)
|
|
285
|
+
|
|
286
|
+
if not NodeStatus.is_done(in_status):
|
|
287
|
+
ready = False
|
|
288
|
+
break
|
|
289
|
+
if NodeStatus.is_error(in_status) and info["tool"] != "builtin":
|
|
290
|
+
# Fail if any dependency failed for non-builtin task
|
|
291
|
+
self.__record.set("status", NodeStatus.ERROR, step=step, index=index)
|
|
292
|
+
|
|
293
|
+
# Fail if no dependency successfully finished for builtin task
|
|
294
|
+
if inputs:
|
|
295
|
+
any_success = any([status == NodeStatus.SUCCESS for status in inputs])
|
|
296
|
+
else:
|
|
297
|
+
any_success = True
|
|
298
|
+
if ready and info["tool"] == "builtin" and not any_success:
|
|
299
|
+
self.__record.set("status", NodeStatus.ERROR, step=step, index=index)
|
|
300
|
+
|
|
301
|
+
if self.__record.get('status', step=step, index=index) == NodeStatus.ERROR:
|
|
302
|
+
info["proc"] = None
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
# If there are no dependencies left, launch this node and
|
|
306
|
+
# remove from nodes_to_run.
|
|
307
|
+
if ready and self.__allow_start(node):
|
|
308
|
+
self.__logger.debug(f'Launching {info["name"]}')
|
|
309
|
+
|
|
310
|
+
TaskScheduler.__callbacks['pre_node'](self.__chip, step, index)
|
|
311
|
+
|
|
312
|
+
self.__record.set('status', NodeStatus.RUNNING, step=step, index=index)
|
|
313
|
+
self.__startTimes[node] = time.time()
|
|
314
|
+
changed = True
|
|
315
|
+
|
|
316
|
+
# Start the process
|
|
317
|
+
info["running"] = True
|
|
318
|
+
info["proc"].start()
|
|
319
|
+
|
|
320
|
+
return changed
|
|
@@ -225,7 +225,13 @@ class BaseSchema:
|
|
|
225
225
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
226
226
|
if field is None:
|
|
227
227
|
return param
|
|
228
|
-
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
return param.get(field, step=step, index=index)
|
|
231
|
+
except Exception as e:
|
|
232
|
+
new_msg = f"error while accessing [{','.join(keypath)}]: {e.args[0]}"
|
|
233
|
+
e.args = (new_msg, *e.args[1:])
|
|
234
|
+
raise e
|
|
229
235
|
|
|
230
236
|
def set(self, *args, field='value', clobber=True, step=None, index=None):
|
|
231
237
|
'''
|
|
@@ -259,7 +265,12 @@ class BaseSchema:
|
|
|
259
265
|
except KeyError:
|
|
260
266
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
261
267
|
|
|
262
|
-
|
|
268
|
+
try:
|
|
269
|
+
return param.set(value, field=field, clobber=clobber, step=step, index=index)
|
|
270
|
+
except Exception as e:
|
|
271
|
+
new_msg = f"error while setting [{','.join(keypath)}]: {e.args[0]}"
|
|
272
|
+
e.args = (new_msg, *e.args[1:])
|
|
273
|
+
raise e
|
|
263
274
|
|
|
264
275
|
def add(self, *args, field='value', step=None, index=None):
|
|
265
276
|
'''
|
|
@@ -292,7 +303,12 @@ class BaseSchema:
|
|
|
292
303
|
except KeyError:
|
|
293
304
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
294
305
|
|
|
295
|
-
|
|
306
|
+
try:
|
|
307
|
+
return param.add(value, field=field, step=step, index=index)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
new_msg = f"error while adding to [{','.join(keypath)}]: {e.args[0]}"
|
|
310
|
+
e.args = (new_msg, *e.args[1:])
|
|
311
|
+
raise e
|
|
296
312
|
|
|
297
313
|
def unset(self, *keypath, step=None, index=None):
|
|
298
314
|
'''
|
|
@@ -325,7 +341,12 @@ class BaseSchema:
|
|
|
325
341
|
except KeyError:
|
|
326
342
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
327
343
|
|
|
328
|
-
|
|
344
|
+
try:
|
|
345
|
+
param.unset(step=step, index=index)
|
|
346
|
+
except Exception as e:
|
|
347
|
+
new_msg = f"error while unsetting [{','.join(keypath)}]: {e.args[0]}"
|
|
348
|
+
e.args = (new_msg, *e.args[1:])
|
|
349
|
+
raise e
|
|
329
350
|
|
|
330
351
|
def remove(self, *keypath):
|
|
331
352
|
'''
|
|
@@ -45,6 +45,10 @@ class JournalingSchema(BaseSchema):
|
|
|
45
45
|
|
|
46
46
|
self.__parent = self
|
|
47
47
|
|
|
48
|
+
@classmethod
|
|
49
|
+
def from_manifest(cls, filepath=None, cfg=None):
|
|
50
|
+
raise RuntimeError("Journal cannot be generated from manifest")
|
|
51
|
+
|
|
48
52
|
def get(self, *keypath, field='value', step=None, index=None):
|
|
49
53
|
get_ret = super().get(*keypath, field=field, step=step, index=index)
|
|
50
54
|
self.__record_journal("get", keypath, field=field, step=step, index=index)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from . import EditableSchema, Parameter, Scope, PerNode
|
|
4
4
|
from .utils import trim
|
|
5
5
|
|
|
6
|
-
SCHEMA_VERSION = '0.51.
|
|
6
|
+
SCHEMA_VERSION = '0.51.4'
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
#############################################################################
|
|
@@ -1024,8 +1024,8 @@ def schema_flowgraph(cfg):
|
|
|
1024
1024
|
# Tool Setup
|
|
1025
1025
|
###########################################################################
|
|
1026
1026
|
def schema_tool(cfg):
|
|
1027
|
-
from siliconcompiler.tool import
|
|
1028
|
-
cfg.insert("tool", "default",
|
|
1027
|
+
from siliconcompiler.tool import ToolSchemaTmp
|
|
1028
|
+
cfg.insert("tool", "default", ToolSchemaTmp())
|
|
1029
1029
|
return cfg
|
|
1030
1030
|
|
|
1031
1031
|
|