siliconcompiler 0.32.2__py3-none-any.whl → 0.33.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/__init__.py +19 -2
- siliconcompiler/_metadata.py +3 -2
- siliconcompiler/apps/sc.py +2 -2
- siliconcompiler/apps/sc_install.py +3 -3
- siliconcompiler/apps/sc_issue.py +1 -1
- siliconcompiler/apps/sc_remote.py +4 -4
- siliconcompiler/apps/sc_show.py +2 -2
- siliconcompiler/apps/utils/replay.py +5 -3
- siliconcompiler/asic.py +120 -0
- siliconcompiler/checklist.py +150 -0
- siliconcompiler/core.py +319 -345
- siliconcompiler/{templates → data/templates}/replay/replay.sh.j2 +2 -2
- siliconcompiler/flowgraph.py +803 -515
- siliconcompiler/fpga.py +84 -0
- siliconcompiler/metric.py +420 -0
- siliconcompiler/optimizer/vizier.py +2 -3
- siliconcompiler/package/__init__.py +29 -6
- siliconcompiler/pdk.py +415 -0
- siliconcompiler/record.py +449 -0
- siliconcompiler/remote/client.py +61 -19
- siliconcompiler/remote/schema.py +116 -112
- siliconcompiler/remote/server.py +3 -5
- siliconcompiler/report/__init__.py +3 -2
- siliconcompiler/report/dashboard/__init__.py +61 -170
- siliconcompiler/report/dashboard/cli/__init__.py +79 -0
- siliconcompiler/report/dashboard/cli/board.py +895 -0
- siliconcompiler/report/dashboard/web/__init__.py +196 -0
- siliconcompiler/report/dashboard/{components → web/components}/__init__.py +9 -8
- siliconcompiler/report/dashboard/{components → web/components}/flowgraph.py +3 -3
- siliconcompiler/report/dashboard/{components → web/components}/graph.py +7 -4
- siliconcompiler/report/dashboard/{layouts → web/layouts}/__init__.py +3 -3
- siliconcompiler/report/dashboard/{layouts → web/layouts}/_common.py +1 -1
- siliconcompiler/report/dashboard/{layouts → web/layouts}/vertical_flowgraph.py +5 -5
- siliconcompiler/report/dashboard/{layouts → web/layouts}/vertical_flowgraph_node_tab.py +6 -6
- siliconcompiler/report/dashboard/{layouts → web/layouts}/vertical_flowgraph_sac_tabs.py +6 -6
- siliconcompiler/report/dashboard/{state.py → web/state.py} +1 -1
- siliconcompiler/report/dashboard/{utils → web/utils}/__init__.py +4 -3
- siliconcompiler/report/dashboard/{viewer.py → web/viewer.py} +4 -4
- siliconcompiler/report/html_report.py +2 -3
- siliconcompiler/report/report.py +13 -7
- siliconcompiler/report/summary_image.py +1 -1
- siliconcompiler/report/summary_table.py +3 -3
- siliconcompiler/report/utils.py +11 -10
- siliconcompiler/scheduler/__init__.py +153 -286
- siliconcompiler/scheduler/run_node.py +2 -1
- siliconcompiler/scheduler/send_messages.py +4 -4
- siliconcompiler/scheduler/slurm.py +2 -2
- siliconcompiler/schema/__init__.py +19 -2
- siliconcompiler/schema/baseschema.py +493 -0
- siliconcompiler/schema/cmdlineschema.py +250 -0
- siliconcompiler/{sphinx_ext → schema/docs}/__init__.py +3 -1
- siliconcompiler/{sphinx_ext → schema/docs}/dynamicgen.py +63 -81
- siliconcompiler/{sphinx_ext → schema/docs}/schemagen.py +73 -85
- siliconcompiler/{sphinx_ext → schema/docs}/utils.py +12 -13
- siliconcompiler/schema/editableschema.py +136 -0
- siliconcompiler/schema/journalingschema.py +238 -0
- siliconcompiler/schema/namedschema.py +41 -0
- siliconcompiler/schema/packageschema.py +101 -0
- siliconcompiler/schema/parameter.py +791 -0
- siliconcompiler/schema/parametertype.py +323 -0
- siliconcompiler/schema/parametervalue.py +736 -0
- siliconcompiler/schema/safeschema.py +37 -0
- siliconcompiler/schema/schema_cfg.py +109 -1789
- siliconcompiler/schema/utils.py +5 -68
- siliconcompiler/schema_obj.py +119 -0
- siliconcompiler/tool.py +1308 -0
- siliconcompiler/tools/_common/__init__.py +8 -10
- siliconcompiler/tools/_common/sdc/sc_constraints.sdc +1 -1
- siliconcompiler/tools/bluespec/convert.py +7 -7
- siliconcompiler/tools/builtin/_common.py +1 -1
- siliconcompiler/tools/builtin/concatenate.py +2 -2
- siliconcompiler/tools/builtin/minimum.py +1 -1
- siliconcompiler/tools/builtin/mux.py +2 -1
- siliconcompiler/tools/builtin/nop.py +1 -1
- siliconcompiler/tools/builtin/verify.py +6 -4
- siliconcompiler/tools/chisel/convert.py +4 -4
- siliconcompiler/tools/genfasm/bitstream.py +3 -3
- siliconcompiler/tools/ghdl/convert.py +1 -1
- siliconcompiler/tools/icarus/compile.py +4 -4
- siliconcompiler/tools/icepack/bitstream.py +6 -1
- siliconcompiler/tools/klayout/convert_drc_db.py +5 -0
- siliconcompiler/tools/klayout/klayout_export.py +0 -1
- siliconcompiler/tools/klayout/klayout_utils.py +3 -10
- siliconcompiler/tools/nextpnr/apr.py +6 -1
- siliconcompiler/tools/nextpnr/nextpnr.py +4 -4
- siliconcompiler/tools/openroad/_apr.py +17 -0
- siliconcompiler/tools/openroad/fillmetal_insertion.py +14 -14
- siliconcompiler/tools/openroad/rdlroute.py +3 -3
- siliconcompiler/tools/openroad/scripts/apr/postamble.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +5 -5
- siliconcompiler/tools/openroad/scripts/apr/sc_antenna_repair.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_clock_tree_synthesis.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_detailed_placement.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_endcap_tapcell_insertion.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_fillercell_insertion.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_fillmetal_insertion.tcl +4 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_global_placement.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_macro_placement.tcl +4 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_metrics.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_pin_placement.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_power_grid.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_repair_timing.tcl +4 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +58 -2
- siliconcompiler/tools/openroad/scripts/common/reports.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/common/write_images.tcl +28 -3
- siliconcompiler/tools/openroad/scripts/sc_rcx.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +4 -4
- siliconcompiler/tools/openroad/scripts/sc_show.tcl +7 -7
- siliconcompiler/tools/opensta/__init__.py +1 -1
- siliconcompiler/tools/opensta/scripts/sc_check_library.tcl +1 -1
- siliconcompiler/tools/opensta/scripts/sc_procs.tcl +16 -0
- siliconcompiler/tools/opensta/scripts/sc_report_libraries.tcl +1 -1
- siliconcompiler/tools/opensta/scripts/sc_timing.tcl +35 -7
- siliconcompiler/tools/opensta/timing.py +6 -2
- siliconcompiler/tools/slang/__init__.py +10 -10
- siliconcompiler/tools/surelog/parse.py +4 -4
- siliconcompiler/tools/sv2v/convert.py +20 -3
- siliconcompiler/tools/verilator/compile.py +2 -2
- siliconcompiler/tools/verilator/verilator.py +3 -3
- siliconcompiler/tools/vpr/place.py +1 -1
- siliconcompiler/tools/vpr/route.py +4 -4
- siliconcompiler/tools/vpr/screenshot.py +1 -1
- siliconcompiler/tools/vpr/show.py +5 -5
- siliconcompiler/tools/vpr/vpr.py +24 -24
- siliconcompiler/tools/xdm/convert.py +2 -2
- siliconcompiler/tools/xyce/simulate.py +1 -1
- siliconcompiler/tools/yosys/sc_synth_asic.tcl +104 -90
- siliconcompiler/tools/yosys/syn_asic.py +13 -4
- siliconcompiler/toolscripts/_tools.json +12 -7
- siliconcompiler/toolscripts/rhel8/install-chisel.sh +2 -0
- siliconcompiler/toolscripts/rhel8/install-icarus.sh +1 -0
- siliconcompiler/toolscripts/rhel8/install-klayout.sh +2 -0
- siliconcompiler/toolscripts/rhel8/install-magic.sh +1 -2
- siliconcompiler/toolscripts/rhel8/install-netgen.sh +1 -1
- siliconcompiler/toolscripts/rhel8/install-slang.sh +2 -0
- siliconcompiler/toolscripts/rhel8/install-surelog.sh +3 -1
- siliconcompiler/toolscripts/rhel8/install-sv2v.sh +1 -0
- siliconcompiler/toolscripts/rhel8/install-verible.sh +2 -0
- siliconcompiler/toolscripts/rhel8/install-verilator.sh +1 -0
- siliconcompiler/toolscripts/rhel8/install-xyce.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-chisel.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-ghdl.sh +1 -0
- siliconcompiler/toolscripts/rhel9/install-gtkwave.sh +1 -0
- siliconcompiler/toolscripts/rhel9/install-icarus.sh +1 -0
- siliconcompiler/toolscripts/rhel9/install-klayout.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-magic.sh +1 -2
- siliconcompiler/toolscripts/rhel9/install-netgen.sh +1 -1
- siliconcompiler/toolscripts/rhel9/install-openroad.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-opensta.sh +76 -0
- siliconcompiler/toolscripts/rhel9/install-slang.sh +3 -1
- siliconcompiler/toolscripts/rhel9/install-surelog.sh +2 -1
- siliconcompiler/toolscripts/rhel9/install-sv2v.sh +1 -0
- siliconcompiler/toolscripts/rhel9/install-verible.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-verilator.sh +1 -0
- siliconcompiler/toolscripts/rhel9/install-vpr.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-xdm.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-xyce.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-yosys-moosic.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-yosys-parmys.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-yosys-slang.sh +3 -1
- siliconcompiler/toolscripts/rhel9/install-yosys.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-bambu.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-bluespec.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-chisel.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-ghdl.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-gtkwave.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-icarus.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-icepack.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-klayout.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-magic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-netgen.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-nextpnr.sh +1 -3
- siliconcompiler/toolscripts/ubuntu20/install-openroad.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-opensta.sh +72 -0
- siliconcompiler/toolscripts/ubuntu20/install-slang.sh +3 -1
- siliconcompiler/toolscripts/ubuntu20/install-slurm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-surelog.sh +3 -1
- siliconcompiler/toolscripts/ubuntu20/install-sv2v.sh +1 -1
- siliconcompiler/toolscripts/ubuntu20/install-verible.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-verilator.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-xdm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-xyce.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-yosys-moosic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-yosys.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-bambu.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-bluespec.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-chisel.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-ghdl.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-gtkwave.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-icarus.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-icepack.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-klayout.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-magic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-netgen.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-nextpnr.sh +1 -2
- siliconcompiler/toolscripts/ubuntu22/install-openroad.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-opensta.sh +72 -0
- siliconcompiler/toolscripts/ubuntu22/install-slang.sh +3 -1
- siliconcompiler/toolscripts/ubuntu22/install-slurm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-surelog.sh +3 -1
- siliconcompiler/toolscripts/ubuntu22/install-sv2v.sh +1 -1
- siliconcompiler/toolscripts/ubuntu22/install-verible.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-verilator.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-vpr.sh +2 -2
- siliconcompiler/toolscripts/ubuntu22/install-xdm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-xyce.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-yosys-moosic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-yosys-parmys.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-yosys-slang.sh +3 -1
- siliconcompiler/toolscripts/ubuntu22/install-yosys.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-bambu.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-bluespec.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-chisel.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-ghdl.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-gtkwave.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-icarus.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-icepack.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-magic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-netgen.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-nextpnr.sh +1 -3
- siliconcompiler/toolscripts/ubuntu24/install-openroad.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-opensta.sh +72 -0
- siliconcompiler/toolscripts/ubuntu24/install-slang.sh +3 -1
- siliconcompiler/toolscripts/ubuntu24/install-slurm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-surelog.sh +3 -1
- siliconcompiler/toolscripts/ubuntu24/install-sv2v.sh +1 -1
- siliconcompiler/toolscripts/ubuntu24/install-verible.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-verilator.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-vpr.sh +2 -2
- siliconcompiler/toolscripts/ubuntu24/install-xdm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-xyce.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-yosys-moosic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-yosys-parmys.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-yosys-slang.sh +3 -1
- siliconcompiler/toolscripts/ubuntu24/install-yosys.sh +2 -0
- siliconcompiler/utils/__init__.py +8 -112
- siliconcompiler/utils/flowgraph.py +339 -0
- siliconcompiler/{issue.py → utils/issue.py} +7 -4
- siliconcompiler/utils/logging.py +86 -33
- {siliconcompiler-0.32.2.dist-info → siliconcompiler-0.33.0.dist-info}/METADATA +10 -8
- siliconcompiler-0.33.0.dist-info/RECORD +487 -0
- {siliconcompiler-0.32.2.dist-info → siliconcompiler-0.33.0.dist-info}/WHEEL +1 -1
- {siliconcompiler-0.32.2.dist-info → siliconcompiler-0.33.0.dist-info}/entry_points.txt +8 -8
- siliconcompiler/schema/schema_obj.py +0 -1936
- siliconcompiler/toolscripts/ubuntu20/install-vpr.sh +0 -27
- siliconcompiler/toolscripts/ubuntu20/install-yosys-parmys.sh +0 -59
- siliconcompiler-0.32.2.dist-info/RECORD +0 -464
- /siliconcompiler/{templates → data/templates}/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/email/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/email/general.j2 +0 -0
- /siliconcompiler/{templates → data/templates}/email/summary.j2 +0 -0
- /siliconcompiler/{templates → data/templates}/issue/README.txt +0 -0
- /siliconcompiler/{templates → data/templates}/issue/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/issue/run.sh +0 -0
- /siliconcompiler/{templates → data/templates}/replay/replay.py.j2 +0 -0
- /siliconcompiler/{templates → data/templates}/replay/requirements.txt +0 -0
- /siliconcompiler/{templates → data/templates}/replay/setup.sh +0 -0
- /siliconcompiler/{templates → data/templates}/report/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/report/bootstrap.min.css +0 -0
- /siliconcompiler/{templates → data/templates}/report/bootstrap.min.js +0 -0
- /siliconcompiler/{templates → data/templates}/report/bootstrap_LICENSE.md +0 -0
- /siliconcompiler/{templates → data/templates}/report/sc_report.j2 +0 -0
- /siliconcompiler/{templates → data/templates}/slurm/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/slurm/run.sh +0 -0
- /siliconcompiler/{templates → data/templates}/tcl/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/tcl/manifest.tcl.j2 +0 -0
- /siliconcompiler/report/dashboard/{utils → web/utils}/file_utils.py +0 -0
- /siliconcompiler/{units.py → utils/units.py} +0 -0
- {siliconcompiler-0.32.2.dist-info → siliconcompiler-0.33.0.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.32.2.dist-info → siliconcompiler-0.33.0.dist-info}/top_level.txt +0 -0
siliconcompiler/tool.py
ADDED
|
@@ -0,0 +1,1308 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import os
|
|
3
|
+
import psutil
|
|
4
|
+
import re
|
|
5
|
+
import shlex
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import resource
|
|
13
|
+
except ModuleNotFoundError:
|
|
14
|
+
resource = None
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
# Note: this import throws exception on Windows
|
|
18
|
+
import pty
|
|
19
|
+
except ModuleNotFoundError:
|
|
20
|
+
pty = None
|
|
21
|
+
|
|
22
|
+
import os.path
|
|
23
|
+
|
|
24
|
+
from packaging.version import Version, InvalidVersion
|
|
25
|
+
from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
|
26
|
+
|
|
27
|
+
from siliconcompiler.schema import NamedSchema
|
|
28
|
+
from siliconcompiler.schema import EditableSchema, Parameter, PerNode, Scope
|
|
29
|
+
from siliconcompiler.schema.utils import trim
|
|
30
|
+
|
|
31
|
+
from siliconcompiler import utils
|
|
32
|
+
from siliconcompiler import sc_open
|
|
33
|
+
|
|
34
|
+
from siliconcompiler.record import RecordTool
|
|
35
|
+
|
|
36
|
+
from siliconcompiler.scheduler import print_traceback
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TaskError(Exception):
|
|
40
|
+
'''
|
|
41
|
+
Error indicates execution cannot continue and should be terminated
|
|
42
|
+
'''
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TaskTimeout(TaskError):
|
|
46
|
+
'''
|
|
47
|
+
Error indicates a timeout has occurred
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
timeout (float): execution time at timeout
|
|
51
|
+
'''
|
|
52
|
+
def __init__(self, *args, timeout=None, **kwargs):
|
|
53
|
+
super().__init__(*args, **kwargs)
|
|
54
|
+
self.timeout = timeout
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TaskExecutableNotFound(TaskError):
|
|
58
|
+
'''
|
|
59
|
+
Executable not found.
|
|
60
|
+
'''
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TaskSchema(NamedSchema):
|
|
64
|
+
def __init__(self, name=None):
|
|
65
|
+
super().__init__(name=name)
|
|
66
|
+
|
|
67
|
+
schema_task(self)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ToolSchema(NamedSchema):
|
|
71
|
+
__parse_version_check_str = r"""
|
|
72
|
+
(?P<operator>(==|!=|<=|>=|<|>|~=))
|
|
73
|
+
\s*
|
|
74
|
+
(?P<version>
|
|
75
|
+
[^,;\s)]* # Since this is a "legacy" specifier, and the version
|
|
76
|
+
# string can be just about anything, we match everything
|
|
77
|
+
# except for whitespace, a semi-colon for marker support,
|
|
78
|
+
# a closing paren since versions can be enclosed in
|
|
79
|
+
# them, and a comma since it's a version separator.
|
|
80
|
+
)
|
|
81
|
+
"""
|
|
82
|
+
__parse_version_check = re.compile(
|
|
83
|
+
r"^\s*" + __parse_version_check_str + r"\s*$",
|
|
84
|
+
re.VERBOSE | re.IGNORECASE)
|
|
85
|
+
|
|
86
|
+
def __init__(self, name=None):
|
|
87
|
+
super().__init__(name=name)
|
|
88
|
+
|
|
89
|
+
schema_tool(self)
|
|
90
|
+
|
|
91
|
+
schema = EditableSchema(self)
|
|
92
|
+
schema.insert("task", "default", TaskSchema())
|
|
93
|
+
|
|
94
|
+
self.set_runtime(None)
|
|
95
|
+
|
|
96
|
+
def set_runtime(self, chip):
|
|
97
|
+
'''
|
|
98
|
+
Sets the runtime information needed to properly execute a task.
|
|
99
|
+
Note: unstable API
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
chip (:class:`Chip`): root schema for the runtime information
|
|
103
|
+
'''
|
|
104
|
+
self.__chip = None
|
|
105
|
+
self.__schema_full = None
|
|
106
|
+
self.__logger = None
|
|
107
|
+
if chip:
|
|
108
|
+
self.__chip = chip
|
|
109
|
+
self.__schema_full = chip.schema
|
|
110
|
+
self.__logger = chip.logger
|
|
111
|
+
|
|
112
|
+
self.__step = None
|
|
113
|
+
self.__index = None
|
|
114
|
+
self.__tool = None
|
|
115
|
+
self.__task = None
|
|
116
|
+
|
|
117
|
+
self.__schema_record = None
|
|
118
|
+
self.__schema_metric = None
|
|
119
|
+
if self.__schema_full:
|
|
120
|
+
self.__schema_record = self.__schema_full.get("record", field="schema")
|
|
121
|
+
self.__schema_metric = self.__schema_full.get("metric", field="schema")
|
|
122
|
+
|
|
123
|
+
self.__step = self.__schema_full.get('arg', 'step')
|
|
124
|
+
self.__index = self.__schema_full.get('arg', 'index')
|
|
125
|
+
|
|
126
|
+
if not self.__step or not self.__index:
|
|
127
|
+
raise RuntimeError("step or index not specified")
|
|
128
|
+
|
|
129
|
+
flow = self.__schema_full.get('option', 'flow')
|
|
130
|
+
if not flow:
|
|
131
|
+
raise RuntimeError("flow not specified")
|
|
132
|
+
self.__tool = self.__schema_full.get(
|
|
133
|
+
'flowgraph', flow, self.__step, self.__index, 'tool')
|
|
134
|
+
self.__task = self.__schema_full.get(
|
|
135
|
+
'flowgraph', flow, self.__step, self.__index, 'task')
|
|
136
|
+
|
|
137
|
+
def node(self):
|
|
138
|
+
'''
|
|
139
|
+
Returns:
|
|
140
|
+
step and index for the current runtime
|
|
141
|
+
'''
|
|
142
|
+
|
|
143
|
+
return self.__step, self.__index
|
|
144
|
+
|
|
145
|
+
def task(self):
|
|
146
|
+
'''
|
|
147
|
+
Returns:
|
|
148
|
+
task name
|
|
149
|
+
'''
|
|
150
|
+
|
|
151
|
+
return self.__task
|
|
152
|
+
|
|
153
|
+
def logger(self):
|
|
154
|
+
'''
|
|
155
|
+
Returns:
|
|
156
|
+
logger
|
|
157
|
+
'''
|
|
158
|
+
return self.__logger
|
|
159
|
+
|
|
160
|
+
def schema(self, type=None):
|
|
161
|
+
'''
|
|
162
|
+
Get useful section of the schema.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
type (str): schema section to find, if None returns the root schema.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
schema section.
|
|
169
|
+
'''
|
|
170
|
+
if type is None:
|
|
171
|
+
return self.__schema_full
|
|
172
|
+
elif type == "record":
|
|
173
|
+
return self.__schema_record
|
|
174
|
+
elif type == "metric":
|
|
175
|
+
return self.__schema_metric
|
|
176
|
+
else:
|
|
177
|
+
raise ValueError(f"{type} is not a schema section")
|
|
178
|
+
|
|
179
|
+
def get_exe(self):
|
|
180
|
+
'''
|
|
181
|
+
Determines the absolute path for the specified executable.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
:class:`TaskExecutableNotFound`: if executable not found.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
path to executable, or None if not specified
|
|
188
|
+
'''
|
|
189
|
+
|
|
190
|
+
exe = self.get('exe')
|
|
191
|
+
|
|
192
|
+
if exe is None:
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
# Collect path
|
|
196
|
+
env = self.get_runtime_environmental_variables(include_path=True)
|
|
197
|
+
|
|
198
|
+
fullexe = shutil.which(exe, path=env["PATH"])
|
|
199
|
+
|
|
200
|
+
if not fullexe:
|
|
201
|
+
raise TaskExecutableNotFound(f"{exe} could not be found")
|
|
202
|
+
|
|
203
|
+
return fullexe
|
|
204
|
+
|
|
205
|
+
def get_exe_version(self):
|
|
206
|
+
'''
|
|
207
|
+
Gets the version of the specified executable.
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
:class:`TaskExecutableNotFound`: if executable not found.
|
|
211
|
+
:class:`NotImplementedError`: if :meth:`.parse_version` has not be implemented.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
version determined by :meth:`.parse_version`.
|
|
215
|
+
'''
|
|
216
|
+
|
|
217
|
+
veropt = self.get('vswitch')
|
|
218
|
+
if not veropt:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
exe = self.get_exe()
|
|
222
|
+
if not exe:
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
exe_path, exe_base = os.path.split(exe)
|
|
226
|
+
|
|
227
|
+
cmdlist = [exe]
|
|
228
|
+
cmdlist.extend(veropt)
|
|
229
|
+
|
|
230
|
+
self.__logger.debug(f'Running {self.name()} version check: {" ".join(cmdlist)}')
|
|
231
|
+
|
|
232
|
+
proc = subprocess.run(cmdlist,
|
|
233
|
+
stdin=subprocess.DEVNULL,
|
|
234
|
+
stdout=subprocess.PIPE,
|
|
235
|
+
stderr=subprocess.STDOUT,
|
|
236
|
+
universal_newlines=True)
|
|
237
|
+
|
|
238
|
+
if proc.returncode != 0:
|
|
239
|
+
self.__logger.warning(f"Version check on '{exe_base}' ended with "
|
|
240
|
+
f"code {proc.returncode}")
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
version = self.parse_version(proc.stdout)
|
|
244
|
+
except NotImplementedError:
|
|
245
|
+
raise NotImplementedError(f'{self.name()} does not implement parse_version()')
|
|
246
|
+
except Exception as e:
|
|
247
|
+
self.__logger.error(f'{self.name()} failed to parse version string: {proc.stdout}')
|
|
248
|
+
raise e from None
|
|
249
|
+
|
|
250
|
+
self.__logger.info(f"Tool '{exe_base}' found with version '{version}' "
|
|
251
|
+
f"in directory '{exe_path}'")
|
|
252
|
+
|
|
253
|
+
return version
|
|
254
|
+
|
|
255
|
+
def check_exe_version(self, reported_version):
|
|
256
|
+
'''
|
|
257
|
+
Check if the reported version matches the versions specified in
|
|
258
|
+
:keypath:`tool,<tool>,version`.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
reported_version (str): version to check
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
True if the version matched, false otherwise
|
|
265
|
+
|
|
266
|
+
'''
|
|
267
|
+
|
|
268
|
+
spec_sets = self.get('version', step=self.__step, index=self.__index)
|
|
269
|
+
if not spec_sets:
|
|
270
|
+
# No requirement so always true
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
for spec_set in spec_sets:
|
|
274
|
+
split_specs = [s.strip() for s in spec_set.split(",") if s.strip()]
|
|
275
|
+
specs_list = []
|
|
276
|
+
for spec in split_specs:
|
|
277
|
+
match = re.match(ToolSchema.__parse_version_check, spec)
|
|
278
|
+
if match is None:
|
|
279
|
+
self.__logger.warning(f'Invalid version specifier {spec}. '
|
|
280
|
+
f'Defaulting to =={spec}.')
|
|
281
|
+
operator = '=='
|
|
282
|
+
spec_version = spec
|
|
283
|
+
else:
|
|
284
|
+
operator = match.group('operator')
|
|
285
|
+
spec_version = match.group('version')
|
|
286
|
+
specs_list.append((operator, spec_version))
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
normalized_version = self.normalize_version(reported_version)
|
|
290
|
+
except Exception as e:
|
|
291
|
+
self.__logger.error(f'Unable to normalize version for {self.name()}: '
|
|
292
|
+
f'{reported_version}')
|
|
293
|
+
raise e from None
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
version = Version(normalized_version)
|
|
297
|
+
except InvalidVersion:
|
|
298
|
+
self.__logger.error(f'Version {normalized_version} reported by {self.name()} does '
|
|
299
|
+
'not match standard.')
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
normalized_spec_list = [
|
|
304
|
+
f'{op}{self.normalize_version(ver)}' for op, ver in specs_list]
|
|
305
|
+
normalized_specs = ','.join(normalized_spec_list)
|
|
306
|
+
except Exception as e:
|
|
307
|
+
self.__logger.error(f'Unable to normalize versions for {self.name()}: '
|
|
308
|
+
f'{",".join([f"{op}{ver}" for op, ver in specs_list])}')
|
|
309
|
+
raise e from None
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
spec_set = SpecifierSet(normalized_specs)
|
|
313
|
+
except InvalidSpecifier:
|
|
314
|
+
self.__logger.error(f'Version specifier set {normalized_specs} '
|
|
315
|
+
'does not match standard.')
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
if version in spec_set:
|
|
319
|
+
return True
|
|
320
|
+
|
|
321
|
+
allowedstr = '; '.join(spec_sets)
|
|
322
|
+
self.__logger.error(f"Version check failed for {self.name()}. Check installation.")
|
|
323
|
+
self.__logger.error(f"Found version {reported_version}, "
|
|
324
|
+
f"did not satisfy any version specifier set {allowedstr}.")
|
|
325
|
+
return False
|
|
326
|
+
|
|
327
|
+
def get_runtime_environmental_variables(self, include_path=True):
|
|
328
|
+
'''
|
|
329
|
+
Determine the environmental variables needed for the task
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
include_path (bool): if True, includes PATH variable
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
dict of str: dictionary of environmental variable to value mapping
|
|
336
|
+
'''
|
|
337
|
+
|
|
338
|
+
# Add global environmental vars
|
|
339
|
+
envvars = {}
|
|
340
|
+
for env in self.__schema_full.getkeys('option', 'env'):
|
|
341
|
+
envvars[env] = self.__schema_full.get('option', 'env', env)
|
|
342
|
+
|
|
343
|
+
# Add tool specific vars
|
|
344
|
+
for lic_env in self.getkeys('licenseserver'):
|
|
345
|
+
license_file = self.get('licenseserver', lic_env, step=self.__step, index=self.__index)
|
|
346
|
+
if license_file:
|
|
347
|
+
envvars[lic_env] = ':'.join(license_file)
|
|
348
|
+
|
|
349
|
+
if include_path:
|
|
350
|
+
path_param = self.get('path', field=None, step=self.__step, index=self.__index)
|
|
351
|
+
if path_param.get(field='package'):
|
|
352
|
+
raise NotImplementedError
|
|
353
|
+
|
|
354
|
+
envvars["PATH"] = os.getenv("PATH", os.defpath)
|
|
355
|
+
|
|
356
|
+
path = path_param.get(field=None).resolve_path() # TODO: needs package search
|
|
357
|
+
if path:
|
|
358
|
+
envvars["PATH"] = path + os.pathsep + envvars["PATH"]
|
|
359
|
+
|
|
360
|
+
# Forward additional variables
|
|
361
|
+
for var in ('LD_LIBRARY_PATH',):
|
|
362
|
+
val = os.getenv(var, None)
|
|
363
|
+
if val:
|
|
364
|
+
envvars[var] = val
|
|
365
|
+
|
|
366
|
+
# Add task specific vars
|
|
367
|
+
for env in self.getkeys('task', self.__task, 'env'):
|
|
368
|
+
envvars[env] = self.get('task', self.__task, 'env', env,
|
|
369
|
+
step=self.__step, index=self.__index)
|
|
370
|
+
|
|
371
|
+
return envvars
|
|
372
|
+
|
|
373
|
+
def get_runtime_arguments(self):
|
|
374
|
+
'''
|
|
375
|
+
Constructs the arguments needed to run the task.
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
command (list)
|
|
379
|
+
'''
|
|
380
|
+
|
|
381
|
+
cmdargs = []
|
|
382
|
+
cmdargs.extend(self.get('task', self.__task, 'option',
|
|
383
|
+
step=self.__step, index=self.__index))
|
|
384
|
+
|
|
385
|
+
# Add scripts files / TODO:
|
|
386
|
+
scripts = self.__chip.find_files('tool', self.__tool, 'task', self.__task, 'script',
|
|
387
|
+
step=self.__step, index=self.__index)
|
|
388
|
+
|
|
389
|
+
cmdargs.extend(scripts)
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
cmdargs.extend(self.runtime_options())
|
|
393
|
+
except Exception as e:
|
|
394
|
+
self.__logger.error(f'Failed to get runtime options for {self.name()}/{self.__task}')
|
|
395
|
+
raise e from None
|
|
396
|
+
|
|
397
|
+
# Cleanup args
|
|
398
|
+
cmdargs = [str(arg).strip() for arg in cmdargs]
|
|
399
|
+
|
|
400
|
+
return cmdargs
|
|
401
|
+
|
|
402
|
+
def generate_replay_script(self, filepath, workdir, include_path=True):
|
|
403
|
+
'''
|
|
404
|
+
Generate a replay script for the task.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
filepath (path): path to the file to write
|
|
408
|
+
workdir (path): path to the run work directory
|
|
409
|
+
include_path (bool): include path information in environmental variables
|
|
410
|
+
'''
|
|
411
|
+
replay_opts = {}
|
|
412
|
+
replay_opts["work_dir"] = workdir
|
|
413
|
+
replay_opts["exports"] = self.get_runtime_environmental_variables(include_path=include_path)
|
|
414
|
+
|
|
415
|
+
replay_opts["executable"] = self.get('exe')
|
|
416
|
+
|
|
417
|
+
vswitch = self.get('vswitch')
|
|
418
|
+
if vswitch:
|
|
419
|
+
replay_opts["version_flag"] = shlex.join(vswitch)
|
|
420
|
+
|
|
421
|
+
# detect arguments
|
|
422
|
+
arg_test = re.compile(r'^[-+]')
|
|
423
|
+
|
|
424
|
+
# detect file paths
|
|
425
|
+
file_test = re.compile(r'^[/\.]')
|
|
426
|
+
|
|
427
|
+
format_cmd = [replay_opts["executable"]]
|
|
428
|
+
|
|
429
|
+
for cmdarg in self.get_runtime_arguments():
|
|
430
|
+
add_new_line = len(format_cmd) == 1
|
|
431
|
+
|
|
432
|
+
if arg_test.match(cmdarg) or file_test.match(cmdarg):
|
|
433
|
+
add_new_line = True
|
|
434
|
+
else:
|
|
435
|
+
if not arg_test.match(format_cmd[-1]):
|
|
436
|
+
add_new_line = True
|
|
437
|
+
|
|
438
|
+
cmdarg = shlex.quote(cmdarg)
|
|
439
|
+
if add_new_line:
|
|
440
|
+
format_cmd.append(cmdarg)
|
|
441
|
+
else:
|
|
442
|
+
format_cmd[-1] += f' {cmdarg}'
|
|
443
|
+
|
|
444
|
+
replay_opts["cmds"] = format_cmd
|
|
445
|
+
|
|
446
|
+
# create replay file
|
|
447
|
+
with open(filepath, 'w') as f:
|
|
448
|
+
f.write(utils.get_file_template("replay/replay.sh.j2").render(replay_opts))
|
|
449
|
+
f.write("\n")
|
|
450
|
+
|
|
451
|
+
os.chmod(filepath, 0o755)
|
|
452
|
+
|
|
453
|
+
def setup_work_directory(self, workdir, remove_exist=True):
|
|
454
|
+
'''
|
|
455
|
+
Create the runtime directories needed to execute a task.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
workdir (path): path to the run work directory
|
|
459
|
+
remove_exist (bool): if True, removes the existing directory
|
|
460
|
+
'''
|
|
461
|
+
|
|
462
|
+
# Delete existing directory
|
|
463
|
+
if os.path.isdir(workdir) and remove_exist:
|
|
464
|
+
shutil.rmtree(workdir)
|
|
465
|
+
|
|
466
|
+
# Create directories
|
|
467
|
+
os.makedirs(workdir, exist_ok=True)
|
|
468
|
+
os.makedirs(os.path.join(workdir, 'inputs'), exist_ok=True)
|
|
469
|
+
os.makedirs(os.path.join(workdir, 'outputs'), exist_ok=True)
|
|
470
|
+
os.makedirs(os.path.join(workdir, 'reports'), exist_ok=True)
|
|
471
|
+
|
|
472
|
+
def write_task_manifest(self, directory, backup=True):
|
|
473
|
+
'''
|
|
474
|
+
Write the manifest needed for the task
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
directory (path): directory to write the manifest into.
|
|
478
|
+
backup (bool): if True and an existing manifest is found a backup is kept.
|
|
479
|
+
'''
|
|
480
|
+
|
|
481
|
+
suffix = self.get('format')
|
|
482
|
+
if not suffix:
|
|
483
|
+
return
|
|
484
|
+
|
|
485
|
+
manifest_path = os.path.join(directory, f"sc_manifest.{suffix}")
|
|
486
|
+
|
|
487
|
+
if backup and os.path.exists(manifest_path):
|
|
488
|
+
shutil.copyfile(manifest_path, f'{manifest_path}.bak')
|
|
489
|
+
|
|
490
|
+
# TODO: pull in TCL/yaml here
|
|
491
|
+
self.__chip.write_manifest(manifest_path, abspath=True)
|
|
492
|
+
|
|
493
|
+
def __get_io_file(self, io_type):
|
|
494
|
+
'''
|
|
495
|
+
Get the runtime destination for the io type.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
io_type (str): name of io type
|
|
499
|
+
'''
|
|
500
|
+
suffix = self.get('task', self.__task, io_type, 'suffix',
|
|
501
|
+
step=self.__step, index=self.__index)
|
|
502
|
+
destination = self.get('task', self.__task, io_type, 'destination',
|
|
503
|
+
step=self.__step, index=self.__index)
|
|
504
|
+
|
|
505
|
+
io_file = None
|
|
506
|
+
io_log = False
|
|
507
|
+
if destination == 'log':
|
|
508
|
+
io_file = f"{self.__step}.{suffix}"
|
|
509
|
+
io_log = True
|
|
510
|
+
elif destination == 'output':
|
|
511
|
+
io_file = os.path.join('outputs', f"{self.__chip.top()}.{suffix}")
|
|
512
|
+
elif destination == 'none':
|
|
513
|
+
io_file = os.devnull
|
|
514
|
+
|
|
515
|
+
return io_file, io_log
|
|
516
|
+
|
|
517
|
+
def __terminate_exe(self, proc):
|
|
518
|
+
'''
|
|
519
|
+
Terminates a subprocess
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
proc (subprocess.Process): process to terminate
|
|
523
|
+
'''
|
|
524
|
+
|
|
525
|
+
def terminate_process(pid, timeout=3):
|
|
526
|
+
'''Terminates a process and all its (grand+)children.
|
|
527
|
+
|
|
528
|
+
Based on https://psutil.readthedocs.io/en/latest/#psutil.wait_procs and
|
|
529
|
+
https://psutil.readthedocs.io/en/latest/#kill-process-tree.
|
|
530
|
+
'''
|
|
531
|
+
parent = psutil.Process(pid)
|
|
532
|
+
children = parent.children(recursive=True)
|
|
533
|
+
children.append(parent)
|
|
534
|
+
for p in children:
|
|
535
|
+
try:
|
|
536
|
+
p.terminate()
|
|
537
|
+
except psutil.NoSuchProcess:
|
|
538
|
+
# Process may have terminated on its own in the meantime
|
|
539
|
+
pass
|
|
540
|
+
|
|
541
|
+
_, alive = psutil.wait_procs(children, timeout=timeout)
|
|
542
|
+
for p in alive:
|
|
543
|
+
# If processes are still alive after timeout seconds, send more
|
|
544
|
+
# aggressive signal.
|
|
545
|
+
p.kill()
|
|
546
|
+
|
|
547
|
+
TERMINATE_TIMEOUT = 5
|
|
548
|
+
|
|
549
|
+
terminate_process(proc.pid, timeout=TERMINATE_TIMEOUT)
|
|
550
|
+
self.__logger.info(f'Waiting for {self.name()} to exit...')
|
|
551
|
+
try:
|
|
552
|
+
proc.wait(timeout=TERMINATE_TIMEOUT)
|
|
553
|
+
except subprocess.TimeoutExpired:
|
|
554
|
+
if proc.poll() is None:
|
|
555
|
+
self.__logger.warning(f'{self.name()} did not exit within {TERMINATE_TIMEOUT} '
|
|
556
|
+
'seconds. Terminating...')
|
|
557
|
+
terminate_process(proc.pid, timeout=TERMINATE_TIMEOUT)
|
|
558
|
+
|
|
559
|
+
def run_task(self, workdir, quiet, loglevel, breakpoint, nice, timeout):
|
|
560
|
+
'''
|
|
561
|
+
Run the task.
|
|
562
|
+
|
|
563
|
+
Raises:
|
|
564
|
+
:class:`TaskError`: raised if the task failed to complete and
|
|
565
|
+
should not be considered complete.
|
|
566
|
+
:class:`TaskTimeout`: raised if the task reaches a timeout
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
workdir (path): path to the run work directory
|
|
570
|
+
quiet (bool): if True, execution output is suppressed
|
|
571
|
+
loglevel (str): logging level
|
|
572
|
+
breakpoint (bool): if True, will attempt to execute with a breakpoint
|
|
573
|
+
nice (int): POSIX nice level to use in execution
|
|
574
|
+
timeout (int): timeout to use for execution
|
|
575
|
+
|
|
576
|
+
Returns:
|
|
577
|
+
return code from the execution
|
|
578
|
+
'''
|
|
579
|
+
|
|
580
|
+
# TODO: Currently no memory usage tracking in breakpoints, builtins, or unexpected errors.
|
|
581
|
+
max_mem_bytes = 0
|
|
582
|
+
cpu_start = time.time()
|
|
583
|
+
|
|
584
|
+
# Ensure directories are setup
|
|
585
|
+
self.setup_work_directory(workdir, remove_exist=False)
|
|
586
|
+
|
|
587
|
+
# Write task manifest
|
|
588
|
+
self.write_task_manifest(workdir)
|
|
589
|
+
|
|
590
|
+
# Get file IO
|
|
591
|
+
stdout_file, is_stdout_log = self.__get_io_file("stdout")
|
|
592
|
+
stderr_file, is_stderr_log = self.__get_io_file("stderr")
|
|
593
|
+
|
|
594
|
+
stdout_print = self.__logger.info
|
|
595
|
+
stderr_print = self.__logger.error
|
|
596
|
+
if loglevel == "quiet":
|
|
597
|
+
stdout_print = self.__logger.error
|
|
598
|
+
stderr_print = self.__logger.error
|
|
599
|
+
|
|
600
|
+
def read_stdio(stdout_reader, stderr_reader):
|
|
601
|
+
if quiet:
|
|
602
|
+
return
|
|
603
|
+
|
|
604
|
+
if is_stdout_log and stdout_reader:
|
|
605
|
+
for line in stdout_reader.readlines():
|
|
606
|
+
stdout_print(line.rstrip())
|
|
607
|
+
if is_stderr_log and stderr_reader:
|
|
608
|
+
for line in stderr_reader.readlines():
|
|
609
|
+
stderr_print(line.rstrip())
|
|
610
|
+
|
|
611
|
+
exe = self.get_exe()
|
|
612
|
+
|
|
613
|
+
retcode = 0
|
|
614
|
+
if not exe:
|
|
615
|
+
# No executable, so must call run()
|
|
616
|
+
try:
|
|
617
|
+
with open(stdout_file, 'w') as stdout_writer, \
|
|
618
|
+
open(stderr_file, 'w') as stderr_writer:
|
|
619
|
+
if stderr_file == stdout_file:
|
|
620
|
+
stderr_writer.close()
|
|
621
|
+
stderr_writer = sys.stdout
|
|
622
|
+
|
|
623
|
+
with contextlib.redirect_stderr(stderr_writer), \
|
|
624
|
+
contextlib.redirect_stdout(stdout_writer):
|
|
625
|
+
retcode = self.run()
|
|
626
|
+
except Exception as e:
|
|
627
|
+
self.__logger.error(f'Failed in run() for {self.name()}/{self.__task}: {e}')
|
|
628
|
+
retcode = 1 # default to non-zero
|
|
629
|
+
print_traceback(self.__chip, e)
|
|
630
|
+
raise e
|
|
631
|
+
finally:
|
|
632
|
+
with sc_open(stdout_file) as stdout_reader, \
|
|
633
|
+
sc_open(stderr_file) as stderr_reader:
|
|
634
|
+
read_stdio(stdout_reader, stderr_reader)
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
if resource:
|
|
638
|
+
# Since memory collection is not possible, collect the current process
|
|
639
|
+
# peak memory
|
|
640
|
+
max_mem_bytes = max(
|
|
641
|
+
max_mem_bytes,
|
|
642
|
+
1024 * resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)
|
|
643
|
+
except (OSError, ValueError, PermissionError):
|
|
644
|
+
pass
|
|
645
|
+
else:
|
|
646
|
+
cmdlist = self.get_runtime_arguments()
|
|
647
|
+
|
|
648
|
+
# Make record of tool options
|
|
649
|
+
self.schema("record").record_tool(
|
|
650
|
+
self.__step, self.__index,
|
|
651
|
+
cmdlist, RecordTool.ARGS)
|
|
652
|
+
|
|
653
|
+
self.__logger.info(shlex.join([os.path.basename(exe), *cmdlist]))
|
|
654
|
+
|
|
655
|
+
if not pty and breakpoint:
|
|
656
|
+
# pty not available
|
|
657
|
+
breakpoint = False
|
|
658
|
+
|
|
659
|
+
if breakpoint and sys.platform in ('darwin', 'linux'):
|
|
660
|
+
# When we break on a step, the tool often drops into a shell.
|
|
661
|
+
# However, our usual subprocess scheme seems to break terminal
|
|
662
|
+
# echo for some tools. On POSIX-compatible systems, we can use
|
|
663
|
+
# pty to connect the tool to our terminal instead. This code
|
|
664
|
+
# doesn't handle quiet/timeout logic, since we don't want either
|
|
665
|
+
# of these features for an interactive session. Logic for
|
|
666
|
+
# forwarding to file based on
|
|
667
|
+
# https://docs.python.org/3/library/pty.html#example.
|
|
668
|
+
with open(f"{self.__step}.log", 'wb') as log_writer:
|
|
669
|
+
def read(fd):
|
|
670
|
+
data = os.read(fd, 1024)
|
|
671
|
+
log_writer.write(data)
|
|
672
|
+
return data
|
|
673
|
+
retcode = pty.spawn(cmdlist, read)
|
|
674
|
+
else:
|
|
675
|
+
with open(stdout_file, 'w') as stdout_writer, \
|
|
676
|
+
open(stdout_file, 'r', errors='replace_with_warning') as stdout_reader, \
|
|
677
|
+
open(stderr_file, 'w') as stderr_writer, \
|
|
678
|
+
open(stderr_file, 'r', errors='replace_with_warning') as stderr_reader:
|
|
679
|
+
# if STDOUT and STDERR are to be redirected to the same file,
|
|
680
|
+
# use a single writer
|
|
681
|
+
if stderr_file == stdout_file:
|
|
682
|
+
stderr_writer.close()
|
|
683
|
+
stderr_reader.close()
|
|
684
|
+
stderr_reader = None
|
|
685
|
+
stderr_writer = subprocess.STDOUT
|
|
686
|
+
|
|
687
|
+
preexec_fn = None
|
|
688
|
+
if nice is not None and hasattr(os, 'nice'):
|
|
689
|
+
def set_task_nice():
|
|
690
|
+
os.nice(nice)
|
|
691
|
+
preexec_fn = set_task_nice
|
|
692
|
+
|
|
693
|
+
try:
|
|
694
|
+
proc = subprocess.Popen([exe, *cmdlist],
|
|
695
|
+
stdin=subprocess.DEVNULL,
|
|
696
|
+
stdout=stdout_writer,
|
|
697
|
+
stderr=stderr_writer,
|
|
698
|
+
preexec_fn=preexec_fn)
|
|
699
|
+
except Exception as e:
|
|
700
|
+
raise TaskError(f"Unable to start {exe}: {str(e)}")
|
|
701
|
+
|
|
702
|
+
# How long to wait for proc to quit on ctrl-c before force
|
|
703
|
+
# terminating.
|
|
704
|
+
POLL_INTERVAL = 0.1
|
|
705
|
+
MEMORY_WARN_LIMIT = 90
|
|
706
|
+
try:
|
|
707
|
+
while proc.poll() is None:
|
|
708
|
+
# Gather subprocess memory usage.
|
|
709
|
+
try:
|
|
710
|
+
pproc = psutil.Process(proc.pid)
|
|
711
|
+
proc_mem_bytes = pproc.memory_full_info().uss
|
|
712
|
+
for child in pproc.children(recursive=True):
|
|
713
|
+
proc_mem_bytes += child.memory_full_info().uss
|
|
714
|
+
max_mem_bytes = max(max_mem_bytes, proc_mem_bytes)
|
|
715
|
+
|
|
716
|
+
memory_usage = psutil.virtual_memory()
|
|
717
|
+
if memory_usage.percent > MEMORY_WARN_LIMIT:
|
|
718
|
+
self.__logger.warn(
|
|
719
|
+
'Current system memory usage is '
|
|
720
|
+
f'{memory_usage.percent:.1f}%')
|
|
721
|
+
|
|
722
|
+
# increase limit warning
|
|
723
|
+
MEMORY_WARN_LIMIT = int(memory_usage.percent + 1)
|
|
724
|
+
except psutil.Error:
|
|
725
|
+
# Process may have already terminated or been killed.
|
|
726
|
+
# Retain existing memory usage statistics in this case.
|
|
727
|
+
pass
|
|
728
|
+
except PermissionError:
|
|
729
|
+
# OS is preventing access to this information so it cannot
|
|
730
|
+
# be collected
|
|
731
|
+
pass
|
|
732
|
+
|
|
733
|
+
# Loop until process terminates
|
|
734
|
+
read_stdio(stdout_reader, stderr_reader)
|
|
735
|
+
|
|
736
|
+
duration = time.time() - cpu_start
|
|
737
|
+
if timeout is not None and duration > timeout:
|
|
738
|
+
raise TaskTimeout(timeout=duration)
|
|
739
|
+
|
|
740
|
+
time.sleep(POLL_INTERVAL)
|
|
741
|
+
except KeyboardInterrupt:
|
|
742
|
+
self.__logger.info("Received ctrl-c.")
|
|
743
|
+
self.__terminate_exe(proc)
|
|
744
|
+
raise TaskError
|
|
745
|
+
except TaskTimeout as e:
|
|
746
|
+
self.__logger.error(f'Task timed out after {e.timeout:.1f} seconds')
|
|
747
|
+
self.__terminate_exe(proc)
|
|
748
|
+
raise e from None
|
|
749
|
+
|
|
750
|
+
# Read the remaining io
|
|
751
|
+
read_stdio(stdout_reader, stderr_reader)
|
|
752
|
+
|
|
753
|
+
retcode = proc.returncode
|
|
754
|
+
|
|
755
|
+
# Record record information
|
|
756
|
+
self.schema("record").record_tool(
|
|
757
|
+
self.__step, self.__index,
|
|
758
|
+
retcode, RecordTool.EXITCODE)
|
|
759
|
+
|
|
760
|
+
# Capture runtime metrics
|
|
761
|
+
self.schema("metric").record(
|
|
762
|
+
self.__step, self.__index,
|
|
763
|
+
'exetime', time.time() - cpu_start, unit='s')
|
|
764
|
+
self.schema("metric").record(
|
|
765
|
+
self.__step, self.__index,
|
|
766
|
+
'memory', max_mem_bytes, unit='B')
|
|
767
|
+
|
|
768
|
+
return retcode
|
|
769
|
+
|
|
770
|
+
def __getstate__(self):
|
|
771
|
+
state = self.__dict__.copy()
|
|
772
|
+
|
|
773
|
+
# Remove runtime information
|
|
774
|
+
for key in list(state.keys()):
|
|
775
|
+
if key.startswith("_ToolSchema__"):
|
|
776
|
+
del state[key]
|
|
777
|
+
return state
|
|
778
|
+
|
|
779
|
+
def __setstate__(self, state):
|
|
780
|
+
self.__dict__ = state
|
|
781
|
+
|
|
782
|
+
# Reinit runtime information
|
|
783
|
+
self.set_runtime(None)
|
|
784
|
+
|
|
785
|
+
###############################################################
|
|
786
|
+
def parse_version(self, stdout):
|
|
787
|
+
raise NotImplementedError("must be implemented by the implementation class")
|
|
788
|
+
|
|
789
|
+
def normalize_version(self, version):
|
|
790
|
+
return version
|
|
791
|
+
|
|
792
|
+
def setup(self):
|
|
793
|
+
pass
|
|
794
|
+
|
|
795
|
+
def pre_process(self):
|
|
796
|
+
pass
|
|
797
|
+
|
|
798
|
+
def runtime_options(self):
|
|
799
|
+
return []
|
|
800
|
+
|
|
801
|
+
def run(self):
|
|
802
|
+
raise NotImplementedError("must be implemented by the implementation class")
|
|
803
|
+
|
|
804
|
+
def post_process(self):
|
|
805
|
+
pass
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
###########################################################################
|
|
809
|
+
# Tool Setup
|
|
810
|
+
###########################################################################
|
|
811
|
+
def schema_tool(schema):
|
|
812
|
+
schema = EditableSchema(schema)
|
|
813
|
+
|
|
814
|
+
schema.insert(
|
|
815
|
+
'exe',
|
|
816
|
+
Parameter(
|
|
817
|
+
'str',
|
|
818
|
+
scope=Scope.GLOBAL,
|
|
819
|
+
shorthelp="Tool: executable name",
|
|
820
|
+
switch="-tool_exe 'tool <str>'",
|
|
821
|
+
example=["cli: -tool_exe 'openroad openroad'",
|
|
822
|
+
"api: chip.set('tool', 'openroad', 'exe', 'openroad')"],
|
|
823
|
+
help=trim("""Tool executable name.""")))
|
|
824
|
+
|
|
825
|
+
schema.insert(
|
|
826
|
+
'sbom', 'default',
|
|
827
|
+
Parameter(
|
|
828
|
+
'[file]',
|
|
829
|
+
scope=Scope.GLOBAL,
|
|
830
|
+
pernode=PerNode.OPTIONAL,
|
|
831
|
+
shorthelp="Tool: software BOM",
|
|
832
|
+
switch="-tool_sbom 'tool version <file>'",
|
|
833
|
+
example=[
|
|
834
|
+
"cli: -tool_sbom 'yosys 1.0.1 ys_sbom.json'",
|
|
835
|
+
"api: chip.set('tool', 'yosys', 'sbom', '1.0', 'ys_sbom.json')"],
|
|
836
|
+
help=trim("""
|
|
837
|
+
Paths to software bill of material (SBOM) document file of the tool
|
|
838
|
+
specified on a per version basis. The SBOM includes critical
|
|
839
|
+
package information about the tool including the list of included
|
|
840
|
+
components, licenses, and copyright. The SBOM file is generally
|
|
841
|
+
provided as in a a standardized open data format such as SPDX.""")))
|
|
842
|
+
|
|
843
|
+
schema.insert(
|
|
844
|
+
'path',
|
|
845
|
+
Parameter(
|
|
846
|
+
'dir',
|
|
847
|
+
scope=Scope.GLOBAL,
|
|
848
|
+
pernode=PerNode.OPTIONAL,
|
|
849
|
+
shorthelp="Tool: executable path",
|
|
850
|
+
switch="-tool_path 'tool <dir>'",
|
|
851
|
+
example=[
|
|
852
|
+
"cli: -tool_path 'openroad /usr/local/bin'",
|
|
853
|
+
"api: chip.set('tool', 'openroad', 'path', '/usr/local/bin')"],
|
|
854
|
+
help=trim("""
|
|
855
|
+
File system path to tool executable. The path is prepended to the
|
|
856
|
+
system PATH environment variable for batch and interactive runs. The
|
|
857
|
+
path parameter can be left blank if the :keypath:`tool,<tool>,exe` is already in the
|
|
858
|
+
environment search path.""")))
|
|
859
|
+
|
|
860
|
+
schema.insert(
|
|
861
|
+
'vswitch',
|
|
862
|
+
Parameter(
|
|
863
|
+
'[str]',
|
|
864
|
+
scope=Scope.GLOBAL,
|
|
865
|
+
shorthelp="Tool: executable version switch",
|
|
866
|
+
switch="-tool_vswitch 'tool <str>'",
|
|
867
|
+
example=["cli: -tool_vswitch 'openroad -version'",
|
|
868
|
+
"api: chip.set('tool', 'openroad', 'vswitch', '-version')"],
|
|
869
|
+
help=trim("""
|
|
870
|
+
Command line switch to use with executable used to print out
|
|
871
|
+
the version number. Common switches include ``-v``, ``-version``,
|
|
872
|
+
``--version``. Some tools may require extra flags to run in batch mode.""")))
|
|
873
|
+
|
|
874
|
+
schema.insert(
|
|
875
|
+
'vendor',
|
|
876
|
+
Parameter(
|
|
877
|
+
'str',
|
|
878
|
+
scope=Scope.GLOBAL,
|
|
879
|
+
shorthelp="Tool: vendor",
|
|
880
|
+
switch="-tool_vendor 'tool <str>'",
|
|
881
|
+
example=["cli: -tool_vendor 'yosys yosys'",
|
|
882
|
+
"api: chip.set('tool', 'yosys', 'vendor', 'yosys')"],
|
|
883
|
+
help=trim("""
|
|
884
|
+
Name of the tool vendor. Parameter can be used to set vendor
|
|
885
|
+
specific technology variables in the PDK and libraries. For
|
|
886
|
+
open source projects, the project name should be used in
|
|
887
|
+
place of vendor.""")))
|
|
888
|
+
|
|
889
|
+
schema.insert(
|
|
890
|
+
'version',
|
|
891
|
+
Parameter(
|
|
892
|
+
'[str]',
|
|
893
|
+
scope=Scope.GLOBAL,
|
|
894
|
+
pernode=PerNode.OPTIONAL,
|
|
895
|
+
shorthelp="Tool: version",
|
|
896
|
+
switch="-tool_version 'tool <str>'",
|
|
897
|
+
example=["cli: -tool_version 'openroad >=v2.0'",
|
|
898
|
+
"api: chip.set('tool', 'openroad', 'version', '>=v2.0')"],
|
|
899
|
+
help=trim("""
|
|
900
|
+
List of acceptable versions of the tool executable to be used. Each
|
|
901
|
+
entry in this list must be a version specifier as described by Python
|
|
902
|
+
`PEP-440 <https://peps.python.org/pep-0440/#version-specifiers>`_.
|
|
903
|
+
During task execution, the tool is called with the 'vswitch' to
|
|
904
|
+
check the runtime executable version. If the version of the system
|
|
905
|
+
executable is not allowed by any of the specifiers in 'version',
|
|
906
|
+
then the job is halted pre-execution. For backwards compatibility,
|
|
907
|
+
entries that do not conform to the standard will be interpreted as a
|
|
908
|
+
version with an '==' specifier. This check can be disabled by
|
|
909
|
+
setting :keypath:`option,novercheck` to True.""")))
|
|
910
|
+
|
|
911
|
+
schema.insert(
|
|
912
|
+
'format',
|
|
913
|
+
Parameter(
|
|
914
|
+
'<json,tcl,yaml>',
|
|
915
|
+
scope=Scope.GLOBAL,
|
|
916
|
+
shorthelp="Tool: file format",
|
|
917
|
+
switch="-tool_format 'tool <str>'",
|
|
918
|
+
example=["cli: -tool_format 'yosys tcl'",
|
|
919
|
+
"api: chip.set('tool', 'yosys', 'format', 'tcl')"],
|
|
920
|
+
help=trim("""
|
|
921
|
+
File format for tool manifest handoff.""")))
|
|
922
|
+
|
|
923
|
+
schema.insert(
|
|
924
|
+
'licenseserver', 'default',
|
|
925
|
+
Parameter(
|
|
926
|
+
'[str]',
|
|
927
|
+
scope=Scope.GLOBAL,
|
|
928
|
+
pernode=PerNode.OPTIONAL,
|
|
929
|
+
shorthelp="Tool: license servers",
|
|
930
|
+
switch="-tool_licenseserver 'name key <str>'",
|
|
931
|
+
example=[
|
|
932
|
+
"cli: -tool_licenseserver 'atask ACME_LICENSE 1700@server'",
|
|
933
|
+
"api: chip.set('tool', 'acme', 'licenseserver', 'ACME_LICENSE', '1700@server')"],
|
|
934
|
+
help=trim("""
|
|
935
|
+
Defines a set of tool specific environment variables used by the executable
|
|
936
|
+
that depend on license key servers to control access. For multiple servers,
|
|
937
|
+
separate each server by a 'colon'. The named license variable are read at
|
|
938
|
+
runtime (:meth:`.run()`) and the environment variables are set.
|
|
939
|
+
""")))
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
def schema_task(schema):
|
|
943
|
+
schema = EditableSchema(schema)
|
|
944
|
+
|
|
945
|
+
schema.insert(
|
|
946
|
+
'warningoff',
|
|
947
|
+
Parameter(
|
|
948
|
+
'[str]',
|
|
949
|
+
scope=Scope.JOB,
|
|
950
|
+
pernode=PerNode.OPTIONAL,
|
|
951
|
+
shorthelp="Task: warning filter",
|
|
952
|
+
switch="-tool_task_warningoff 'tool task <str>'",
|
|
953
|
+
example=[
|
|
954
|
+
"cli: -tool_task_warningoff 'verilator lint COMBDLY'",
|
|
955
|
+
"api: chip.set('tool', 'verilator', 'task', 'lint', 'warningoff', 'COMBDLY')"],
|
|
956
|
+
help=trim("""
|
|
957
|
+
A list of tool warnings for which printing should be suppressed.
|
|
958
|
+
Generally this is done on a per design basis after review has
|
|
959
|
+
determined that warning can be safely ignored The code for turning
|
|
960
|
+
off warnings can be found in the specific task reference manual.
|
|
961
|
+
""")))
|
|
962
|
+
|
|
963
|
+
schema.insert(
|
|
964
|
+
'regex', 'default',
|
|
965
|
+
Parameter(
|
|
966
|
+
'[str]',
|
|
967
|
+
scope=Scope.JOB,
|
|
968
|
+
pernode=PerNode.OPTIONAL,
|
|
969
|
+
shorthelp="Task: regex filter",
|
|
970
|
+
switch="-tool_task_regex 'tool task suffix <str>'",
|
|
971
|
+
example=[
|
|
972
|
+
"cli: -tool_task_regex 'openroad place errors \"'-v ERROR'\"'",
|
|
973
|
+
"api: chip.set('tool', 'openroad', 'task', 'place', 'regex', 'errors', "
|
|
974
|
+
"'-v ERROR')"],
|
|
975
|
+
help=trim("""
|
|
976
|
+
A list of piped together grep commands. Each entry represents a set
|
|
977
|
+
of command line arguments for grep including the regex pattern to
|
|
978
|
+
match. Starting with the first list entry, each grep output is piped
|
|
979
|
+
into the following grep command in the list. Supported grep options
|
|
980
|
+
include ``-v`` and ``-e``. Patterns starting with "-" should be
|
|
981
|
+
directly preceded by the ``-e`` option. The following example
|
|
982
|
+
illustrates the concept.
|
|
983
|
+
|
|
984
|
+
UNIX grep:
|
|
985
|
+
|
|
986
|
+
.. code-block:: bash
|
|
987
|
+
|
|
988
|
+
$ grep WARNING place.log | grep -v "bbox" > place.warnings
|
|
989
|
+
|
|
990
|
+
SiliconCompiler::
|
|
991
|
+
|
|
992
|
+
chip.set('task', 'openroad', 'regex', 'place', '0', 'warnings',
|
|
993
|
+
["WARNING", "-v bbox"])
|
|
994
|
+
|
|
995
|
+
The "errors" and "warnings" suffixes are special cases. When set,
|
|
996
|
+
the number of matches found for these regexes will be added to the
|
|
997
|
+
errors and warnings metrics for the task, respectively. This will
|
|
998
|
+
also cause the logfile to be added to the :keypath:`tool, <tool>,
|
|
999
|
+
task, <task>, report` parameter for those metrics, if not already present.""")))
|
|
1000
|
+
|
|
1001
|
+
# Configuration: cli-option, tcl var, env var, file
|
|
1002
|
+
schema.insert(
|
|
1003
|
+
'option',
|
|
1004
|
+
Parameter(
|
|
1005
|
+
'[str]',
|
|
1006
|
+
scope=Scope.JOB,
|
|
1007
|
+
pernode=PerNode.OPTIONAL,
|
|
1008
|
+
shorthelp="Task: executable options",
|
|
1009
|
+
switch="-tool_task_option 'tool task <str>'",
|
|
1010
|
+
example=[
|
|
1011
|
+
"cli: -tool_task_option 'openroad cts -no_init'",
|
|
1012
|
+
"api: chip.set('tool', 'openroad', 'task', 'cts', 'option', '-no_init')"],
|
|
1013
|
+
help=trim("""
|
|
1014
|
+
List of command line options for the task executable, specified on
|
|
1015
|
+
a per task and per step basis. Options must not include spaces.
|
|
1016
|
+
For multiple argument options, each option is a separate list element.
|
|
1017
|
+
""")))
|
|
1018
|
+
|
|
1019
|
+
schema.insert(
|
|
1020
|
+
'var', 'default',
|
|
1021
|
+
Parameter(
|
|
1022
|
+
'[str]',
|
|
1023
|
+
scope=Scope.JOB,
|
|
1024
|
+
pernode=PerNode.OPTIONAL,
|
|
1025
|
+
shorthelp="Task: script variables",
|
|
1026
|
+
switch="-tool_task_var 'tool task key <str>'",
|
|
1027
|
+
example=[
|
|
1028
|
+
"cli: -tool_task_var 'openroad cts myvar 42'",
|
|
1029
|
+
"api: chip.set('tool', 'openroad', 'task', 'cts', 'var', 'myvar', '42')"],
|
|
1030
|
+
help=trim("""
|
|
1031
|
+
Task script variables specified as key value pairs. Variable
|
|
1032
|
+
names and value types must match the name and type of task and reference
|
|
1033
|
+
script consuming the variable.""")))
|
|
1034
|
+
|
|
1035
|
+
schema.insert(
|
|
1036
|
+
'env', 'default',
|
|
1037
|
+
Parameter(
|
|
1038
|
+
'str',
|
|
1039
|
+
scope=Scope.JOB,
|
|
1040
|
+
pernode=PerNode.OPTIONAL,
|
|
1041
|
+
shorthelp="Task: environment variables",
|
|
1042
|
+
switch="-tool_task_env 'tool task env <str>'",
|
|
1043
|
+
example=[
|
|
1044
|
+
"cli: -tool_task_env 'openroad cts MYVAR 42'",
|
|
1045
|
+
"api: chip.set('tool', 'openroad', 'task', 'cts', 'env', 'MYVAR', '42')"],
|
|
1046
|
+
help=trim("""
|
|
1047
|
+
Environment variables to set for individual tasks. Keys and values
|
|
1048
|
+
should be set in accordance with the task's documentation. Most
|
|
1049
|
+
tasks do not require extra environment variables to function.""")))
|
|
1050
|
+
|
|
1051
|
+
schema.insert(
|
|
1052
|
+
'file', 'default', Parameter(
|
|
1053
|
+
'[file]',
|
|
1054
|
+
scope=Scope.JOB,
|
|
1055
|
+
pernode=PerNode.OPTIONAL,
|
|
1056
|
+
copy=True,
|
|
1057
|
+
shorthelp="Task: custom setup files",
|
|
1058
|
+
switch="-tool_task_file 'tool task key <file>'",
|
|
1059
|
+
example=[
|
|
1060
|
+
"cli: -tool_task_file 'openroad floorplan macroplace macroplace.tcl'",
|
|
1061
|
+
"api: chip.set('tool', 'openroad', 'task', 'floorplan', 'file', 'macroplace', "
|
|
1062
|
+
"'macroplace.tcl')"],
|
|
1063
|
+
help=trim("""
|
|
1064
|
+
Paths to user supplied files mapped to keys. Keys and filetypes must
|
|
1065
|
+
match what's expected by the task/reference script consuming the
|
|
1066
|
+
file.
|
|
1067
|
+
""")))
|
|
1068
|
+
|
|
1069
|
+
schema.insert(
|
|
1070
|
+
'dir', 'default',
|
|
1071
|
+
Parameter(
|
|
1072
|
+
'[dir]',
|
|
1073
|
+
scope=Scope.JOB,
|
|
1074
|
+
pernode=PerNode.OPTIONAL,
|
|
1075
|
+
copy=True,
|
|
1076
|
+
shorthelp="Task: custom setup directories",
|
|
1077
|
+
switch="-tool_task_dir 'tool task key <dir>'",
|
|
1078
|
+
example=[
|
|
1079
|
+
"cli: -tool_task_dir 'verilator compile cincludes include'",
|
|
1080
|
+
"api: chip.set('tool', 'verilator', 'task', 'compile', 'dir', 'cincludes', "
|
|
1081
|
+
"'include')"],
|
|
1082
|
+
help=trim("""
|
|
1083
|
+
Paths to user supplied directories mapped to keys. Keys must match
|
|
1084
|
+
what's expected by the task/reference script consuming the
|
|
1085
|
+
directory.
|
|
1086
|
+
""")))
|
|
1087
|
+
|
|
1088
|
+
# Definitions of inputs, outputs, requirements
|
|
1089
|
+
schema.insert(
|
|
1090
|
+
'input',
|
|
1091
|
+
Parameter(
|
|
1092
|
+
'[file]',
|
|
1093
|
+
scope=Scope.JOB,
|
|
1094
|
+
pernode=PerNode.REQUIRED,
|
|
1095
|
+
shorthelp="Task: input files",
|
|
1096
|
+
switch="-tool_task_input 'tool task <file>'",
|
|
1097
|
+
example=[
|
|
1098
|
+
"cli: -tool_task_input 'openroad place \"place 0 oh_add.def\"'",
|
|
1099
|
+
"api: chip.set('tool', 'openroad', 'task', 'place', 'input', 'oh_add.def', "
|
|
1100
|
+
"step='place', index='0')"],
|
|
1101
|
+
help=trim("""
|
|
1102
|
+
List of data files to be copied from previous flowgraph steps 'output'
|
|
1103
|
+
directory. The list of steps to copy files from is defined by the
|
|
1104
|
+
list defined by the dictionary key :keypath:`flowgraph,<flow>,<step>,<index>,input`.
|
|
1105
|
+
All files must be available for flow to continue. If a file
|
|
1106
|
+
is missing, the program exists on an error.""")))
|
|
1107
|
+
|
|
1108
|
+
schema.insert(
|
|
1109
|
+
'output',
|
|
1110
|
+
Parameter(
|
|
1111
|
+
'[file]',
|
|
1112
|
+
scope=Scope.JOB,
|
|
1113
|
+
pernode=PerNode.REQUIRED,
|
|
1114
|
+
shorthelp="Task: output files",
|
|
1115
|
+
switch="-tool_task_output 'tool task <file>'",
|
|
1116
|
+
example=[
|
|
1117
|
+
"cli: -tool_task_output 'openroad place \"place 0 oh_add.def\"'",
|
|
1118
|
+
"api: chip.set('tool', 'openroad', 'task', 'place', 'output', 'oh_add.def', "
|
|
1119
|
+
"step='place', index='0')"],
|
|
1120
|
+
help=trim("""
|
|
1121
|
+
List of data files written to the 'output' directory of the
|
|
1122
|
+
tool/task/step/index used in the keypath. All files must be available
|
|
1123
|
+
for flow to continue. If a file is missing, the program exists on an error.""")))
|
|
1124
|
+
|
|
1125
|
+
dest_enum = ['log', 'output', 'none']
|
|
1126
|
+
schema.insert(
|
|
1127
|
+
'stdout', 'destination',
|
|
1128
|
+
Parameter(
|
|
1129
|
+
f'<{",".join(dest_enum)}>',
|
|
1130
|
+
defvalue='log',
|
|
1131
|
+
scope=Scope.JOB,
|
|
1132
|
+
pernode=PerNode.OPTIONAL,
|
|
1133
|
+
shorthelp="Task: destination for stdout",
|
|
1134
|
+
switch="-tool_task_stdout_destination 'tool task <str>'",
|
|
1135
|
+
example=["cli: -tool_task_stdout_destination 'ghdl import log'",
|
|
1136
|
+
"api: chip.set('tool', 'ghdl', 'task', 'import', 'stdout', 'destination', "
|
|
1137
|
+
"'log')"],
|
|
1138
|
+
help=trim("""
|
|
1139
|
+
Defines where to direct the output generated over stdout.
|
|
1140
|
+
Supported options are:
|
|
1141
|
+
none: the stream generated to STDOUT is ignored.
|
|
1142
|
+
log: the generated stream is stored in <step>.<suffix>; if not in quiet mode,
|
|
1143
|
+
it is additionally dumped to the display.
|
|
1144
|
+
output: the generated stream is stored in outputs/<design>.<suffix>.""")))
|
|
1145
|
+
|
|
1146
|
+
schema.insert(
|
|
1147
|
+
'stdout', 'suffix',
|
|
1148
|
+
Parameter(
|
|
1149
|
+
'str',
|
|
1150
|
+
defvalue='log',
|
|
1151
|
+
scope=Scope.JOB,
|
|
1152
|
+
pernode=PerNode.OPTIONAL,
|
|
1153
|
+
shorthelp="Task: file suffix for redirected stdout",
|
|
1154
|
+
switch="-tool_task_stdout_suffix 'tool task <str>'",
|
|
1155
|
+
example=["cli: -tool_task_stdout_suffix 'ghdl import log'",
|
|
1156
|
+
"api: chip.set('tool', ghdl', 'task', 'import', 'stdout', 'suffix', 'log')"],
|
|
1157
|
+
help=trim("""
|
|
1158
|
+
Specifies the file extension for the content redirected from stdout.""")))
|
|
1159
|
+
|
|
1160
|
+
schema.insert(
|
|
1161
|
+
'stderr', 'destination',
|
|
1162
|
+
Parameter(
|
|
1163
|
+
f'<{",".join(dest_enum)}>',
|
|
1164
|
+
defvalue='log',
|
|
1165
|
+
scope=Scope.JOB,
|
|
1166
|
+
pernode=PerNode.OPTIONAL,
|
|
1167
|
+
shorthelp="Task: destination for stderr",
|
|
1168
|
+
switch="-tool_task_stderr_destination 'tool task <str>'",
|
|
1169
|
+
example=["cli: -tool_task_stderr_destination 'ghdl import log'",
|
|
1170
|
+
"api: chip.set('tool', ghdl', 'task', 'import', 'stderr', 'destination', "
|
|
1171
|
+
"'log')"],
|
|
1172
|
+
help=trim("""
|
|
1173
|
+
Defines where to direct the output generated over stderr.
|
|
1174
|
+
Supported options are:
|
|
1175
|
+
none: the stream generated to STDERR is ignored
|
|
1176
|
+
log: the generated stream is stored in <step>.<suffix>; if not in quiet mode,
|
|
1177
|
+
it is additionally dumped to the display.
|
|
1178
|
+
output: the generated stream is stored in outputs/<design>.<suffix>""")))
|
|
1179
|
+
|
|
1180
|
+
schema.insert(
|
|
1181
|
+
'stderr', 'suffix',
|
|
1182
|
+
Parameter(
|
|
1183
|
+
'str',
|
|
1184
|
+
defvalue='log',
|
|
1185
|
+
scope=Scope.JOB,
|
|
1186
|
+
pernode=PerNode.OPTIONAL,
|
|
1187
|
+
shorthelp="Task: file suffix for redirected stderr",
|
|
1188
|
+
switch="-tool_task_stderr_suffix 'tool task <str>'",
|
|
1189
|
+
example=["cli: -tool_task_stderr_suffix 'ghdl import log'",
|
|
1190
|
+
"api: chip.set('tool', 'ghdl', 'task', 'import', 'stderr', 'suffix', 'log')"],
|
|
1191
|
+
help=trim("""
|
|
1192
|
+
Specifies the file extension for the content redirected from stderr.""")))
|
|
1193
|
+
|
|
1194
|
+
schema.insert(
|
|
1195
|
+
'require',
|
|
1196
|
+
Parameter(
|
|
1197
|
+
'[str]',
|
|
1198
|
+
scope=Scope.JOB,
|
|
1199
|
+
pernode=PerNode.OPTIONAL,
|
|
1200
|
+
shorthelp="Task: parameter requirements",
|
|
1201
|
+
switch="-tool_task_require 'tool task <str>'",
|
|
1202
|
+
example=[
|
|
1203
|
+
"cli: -tool_task_require 'openroad cts design'",
|
|
1204
|
+
"api: chip.set('tool', 'openroad', 'task', 'cts', 'require', 'design')"],
|
|
1205
|
+
help=trim("""
|
|
1206
|
+
List of keypaths to required task parameters. The list is used
|
|
1207
|
+
by :meth:`.check_manifest()` to verify that all parameters have been set up before
|
|
1208
|
+
step execution begins.""")))
|
|
1209
|
+
|
|
1210
|
+
schema.insert(
|
|
1211
|
+
'report', 'default',
|
|
1212
|
+
Parameter(
|
|
1213
|
+
'[file]',
|
|
1214
|
+
scope=Scope.JOB,
|
|
1215
|
+
pernode=PerNode.REQUIRED,
|
|
1216
|
+
shorthelp="Task: metric report files",
|
|
1217
|
+
switch="-tool_task_report 'tool task metric <file>'",
|
|
1218
|
+
example=[
|
|
1219
|
+
"cli: -tool_task_report 'openroad place holdtns \"place 0 place.log\"'",
|
|
1220
|
+
"api: chip.set('tool', 'openroad', 'task', 'place', 'report', 'holdtns', "
|
|
1221
|
+
"'place.log', step='place', index='0')"],
|
|
1222
|
+
help=trim("""
|
|
1223
|
+
List of report files associated with a specific 'metric'. The file path
|
|
1224
|
+
specified is relative to the run directory of the current task.""")))
|
|
1225
|
+
|
|
1226
|
+
schema.insert(
|
|
1227
|
+
'refdir',
|
|
1228
|
+
Parameter(
|
|
1229
|
+
'[dir]',
|
|
1230
|
+
scope=Scope.JOB,
|
|
1231
|
+
pernode=PerNode.OPTIONAL,
|
|
1232
|
+
shorthelp="Task: script directory",
|
|
1233
|
+
switch="-tool_task_refdir 'tool task <dir>'",
|
|
1234
|
+
example=[
|
|
1235
|
+
"cli: -tool_task_refdir 'yosys syn ./myref'",
|
|
1236
|
+
"api: chip.set('tool', 'yosys', 'task', 'syn_asic', 'refdir', './myref')"],
|
|
1237
|
+
help=trim("""
|
|
1238
|
+
Path to directories containing reference flow scripts, specified
|
|
1239
|
+
on a per step and index basis.""")))
|
|
1240
|
+
|
|
1241
|
+
schema.insert(
|
|
1242
|
+
'script',
|
|
1243
|
+
Parameter(
|
|
1244
|
+
'[file]',
|
|
1245
|
+
scope=Scope.JOB,
|
|
1246
|
+
pernode=PerNode.OPTIONAL,
|
|
1247
|
+
shorthelp="Task: entry script",
|
|
1248
|
+
switch="-tool_task_script 'tool task <file>'",
|
|
1249
|
+
example=[
|
|
1250
|
+
"cli: -tool_task_script 'yosys syn syn.tcl'",
|
|
1251
|
+
"api: chip.set('tool', 'yosys', 'task', 'syn_asic', 'script', 'syn.tcl')"],
|
|
1252
|
+
help=trim("""
|
|
1253
|
+
Path to the entry script called by the executable specified
|
|
1254
|
+
on a per task and per step basis.""")))
|
|
1255
|
+
|
|
1256
|
+
schema.insert(
|
|
1257
|
+
'prescript',
|
|
1258
|
+
Parameter(
|
|
1259
|
+
'[file]',
|
|
1260
|
+
scope=Scope.JOB,
|
|
1261
|
+
pernode=PerNode.OPTIONAL,
|
|
1262
|
+
copy=True,
|
|
1263
|
+
shorthelp="Task: pre-step script",
|
|
1264
|
+
switch="-tool_task_prescript 'tool task <file>'",
|
|
1265
|
+
example=[
|
|
1266
|
+
"cli: -tool_task_prescript 'yosys syn syn_pre.tcl'",
|
|
1267
|
+
"api: chip.set('tool', 'yosys', 'task', 'syn_asic', 'prescript', 'syn_pre.tcl')"],
|
|
1268
|
+
help=trim("""
|
|
1269
|
+
Path to a user supplied script to execute after reading in the design
|
|
1270
|
+
but before the main execution stage of the step. Exact entry point
|
|
1271
|
+
depends on the step and main script being executed. An example
|
|
1272
|
+
of a prescript entry point would be immediately before global
|
|
1273
|
+
placement.""")))
|
|
1274
|
+
|
|
1275
|
+
schema.insert(
|
|
1276
|
+
'postscript',
|
|
1277
|
+
Parameter(
|
|
1278
|
+
'[file]',
|
|
1279
|
+
scope=Scope.JOB,
|
|
1280
|
+
pernode=PerNode.OPTIONAL,
|
|
1281
|
+
copy=True,
|
|
1282
|
+
shorthelp="Task: post-step script",
|
|
1283
|
+
switch="-tool_task_postscript 'tool task <file>'",
|
|
1284
|
+
example=[
|
|
1285
|
+
"cli: -tool_task_postscript 'yosys syn syn_post.tcl'",
|
|
1286
|
+
"api: chip.set('tool', 'yosys', 'task', 'syn_asic', 'postscript', 'syn_post.tcl')"],
|
|
1287
|
+
help=trim("""
|
|
1288
|
+
Path to a user supplied script to execute after the main execution
|
|
1289
|
+
stage of the step but before the design is saved.
|
|
1290
|
+
Exact entry point depends on the step and main script being
|
|
1291
|
+
executed. An example of a postscript entry point would be immediately
|
|
1292
|
+
after global placement.""")))
|
|
1293
|
+
|
|
1294
|
+
schema.insert(
|
|
1295
|
+
'threads',
|
|
1296
|
+
Parameter(
|
|
1297
|
+
'int',
|
|
1298
|
+
scope=Scope.JOB,
|
|
1299
|
+
pernode=PerNode.OPTIONAL,
|
|
1300
|
+
shorthelp="Task: thread parallelism",
|
|
1301
|
+
switch="-tool_task_threads 'tool task <int>'",
|
|
1302
|
+
example=["cli: -tool_task_threads 'magic drc 64'",
|
|
1303
|
+
"api: chip.set('tool', 'magic', 'task', 'drc', 'threads', '64')"],
|
|
1304
|
+
help=trim("""
|
|
1305
|
+
Thread parallelism to use for execution specified on a per task and per
|
|
1306
|
+
step basis. If not specified, SC queries the operating system and sets
|
|
1307
|
+
the threads based on the maximum thread count supported by the
|
|
1308
|
+
hardware.""")))
|