rocket-engine 0.0.2b1__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.
- rocket_engine/__init__.py +8 -0
- rocket_engine/__main__.py +12 -0
- rocket_engine/adapters/README.md +54 -0
- rocket_engine/adapters/__init__.py +7 -0
- rocket_engine/adapters/api.py +39 -0
- rocket_engine/adapters/base.py +46 -0
- rocket_engine/adapters/cace/__init__.py +163 -0
- rocket_engine/adapters/circulax/README.md +10 -0
- rocket_engine/adapters/circulax/__init__.py +112 -0
- rocket_engine/adapters/circulax/_driver.py +190 -0
- rocket_engine/adapters/circulax/adapter.py +538 -0
- rocket_engine/adapters/circulax/results.py +291 -0
- rocket_engine/adapters/cli/README.md +34 -0
- rocket_engine/adapters/cli/__init__.py +305 -0
- rocket_engine/adapters/connectivity/__init__.py +196 -0
- rocket_engine/adapters/connectivity/results.py +214 -0
- rocket_engine/adapters/cosim/README.md +9 -0
- rocket_engine/adapters/cosim/__init__.py +1390 -0
- rocket_engine/adapters/cosim/results.py +214 -0
- rocket_engine/adapters/devsim/__init__.py +171 -0
- rocket_engine/adapters/digital_flow/README.md +9 -0
- rocket_engine/adapters/digital_flow/__init__.py +1121 -0
- rocket_engine/adapters/digital_flow/results.py +227 -0
- rocket_engine/adapters/digital_flow/state.py +462 -0
- rocket_engine/adapters/drc/README.klayout.md +7 -0
- rocket_engine/adapters/drc/README.md +7 -0
- rocket_engine/adapters/drc/__init__.py +1 -0
- rocket_engine/adapters/drc/klayout.py +952 -0
- rocket_engine/adapters/drc/magic.py +287 -0
- rocket_engine/adapters/drc/results.py +184 -0
- rocket_engine/adapters/drc/results_kl.py +188 -0
- rocket_engine/adapters/elmer/__init__.py +163 -0
- rocket_engine/adapters/envelope.py +120 -0
- rocket_engine/adapters/equivalence/__init__.py +156 -0
- rocket_engine/adapters/errors.py +181 -0
- rocket_engine/adapters/extract_magic/README.md +15 -0
- rocket_engine/adapters/extract_magic/__init__.py +425 -0
- rocket_engine/adapters/extract_magic/results.py +209 -0
- rocket_engine/adapters/formal_sby/README.md +7 -0
- rocket_engine/adapters/formal_sby/__init__.py +773 -0
- rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
- rocket_engine/adapters/formal_sby/results.py +185 -0
- rocket_engine/adapters/gdsfactory/__init__.py +194 -0
- rocket_engine/adapters/gdsfactory/_driver.py +459 -0
- rocket_engine/adapters/gdsfactory/adapter.py +609 -0
- rocket_engine/adapters/gdsfactory/results.py +181 -0
- rocket_engine/adapters/hdl_sim/README.md +7 -0
- rocket_engine/adapters/hdl_sim/__init__.py +369 -0
- rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
- rocket_engine/adapters/hdl_sim/results.py +178 -0
- rocket_engine/adapters/hdl_sim/verilator.py +167 -0
- rocket_engine/adapters/http/README.md +15 -0
- rocket_engine/adapters/http/__init__.py +458 -0
- rocket_engine/adapters/http/events.py +63 -0
- rocket_engine/adapters/ipc/README.md +9 -0
- rocket_engine/adapters/ipc/__init__.py +111 -0
- rocket_engine/adapters/lvs_netgen/README.md +20 -0
- rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
- rocket_engine/adapters/lvs_netgen/results.py +168 -0
- rocket_engine/adapters/mcp/README.md +19 -0
- rocket_engine/adapters/mcp/__init__.py +85 -0
- rocket_engine/adapters/mcp/api-schema.json +6107 -0
- rocket_engine/adapters/mcp/pull.py +246 -0
- rocket_engine/adapters/mcp/server.py +794 -0
- rocket_engine/adapters/meep/__init__.py +167 -0
- rocket_engine/adapters/meow/README.md +7 -0
- rocket_engine/adapters/meow/__init__.py +190 -0
- rocket_engine/adapters/meow/_driver.py +227 -0
- rocket_engine/adapters/meow/_envelope.py +108 -0
- rocket_engine/adapters/meow/adapter.py +491 -0
- rocket_engine/adapters/meow/results.py +218 -0
- rocket_engine/adapters/mutation/__init__.py +192 -0
- rocket_engine/adapters/ngspice/README.md +28 -0
- rocket_engine/adapters/ngspice/__init__.py +627 -0
- rocket_engine/adapters/ngspice/results.py +215 -0
- rocket_engine/adapters/postlayout/README.md +28 -0
- rocket_engine/adapters/postlayout/__init__.py +17 -0
- rocket_engine/adapters/postlayout/adapter.py +1062 -0
- rocket_engine/adapters/postlayout/results.py +330 -0
- rocket_engine/adapters/postlayout/translate.py +71 -0
- rocket_engine/adapters/profile.py +220 -0
- rocket_engine/adapters/sax/README.md +23 -0
- rocket_engine/adapters/sax/__init__.py +516 -0
- rocket_engine/adapters/sax/_driver.py +148 -0
- rocket_engine/adapters/sax/results.py +291 -0
- rocket_engine/adapters/stdio_console.py +1151 -0
- rocket_engine/adapters/syntax/README.md +5 -0
- rocket_engine/adapters/syntax/__init__.py +366 -0
- rocket_engine/adapters/syntax/results.py +181 -0
- rocket_engine/adapters/synth_yosys/README.md +7 -0
- rocket_engine/adapters/synth_yosys/__init__.py +655 -0
- rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
- rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
- rocket_engine/adapters/synth_yosys/results.py +173 -0
- rocket_engine/adapters/timing_sta/README.md +9 -0
- rocket_engine/adapters/timing_sta/__init__.py +726 -0
- rocket_engine/adapters/view_sync/__init__.py +185 -0
- rocket_engine/adapters/view_sync/results.py +189 -0
- rocket_engine/adapters/vlsir/__init__.py +142 -0
- rocket_engine/adapters/xschem/__init__.py +440 -0
- rocket_engine/adapters/xschem/deck.py +266 -0
- rocket_engine/clients/__init__.py +5 -0
- rocket_engine/clients/runtime.py +255 -0
- rocket_engine/composition.py +2279 -0
- rocket_engine/execution/README.md +30 -0
- rocket_engine/execution/__init__.py +150 -0
- rocket_engine/execution/apig/__init__.py +1 -0
- rocket_engine/execution/apig/pool.py +463 -0
- rocket_engine/execution/apig/submit.py +124 -0
- rocket_engine/execution/block_runs_scope.py +479 -0
- rocket_engine/execution/coalesce.py +414 -0
- rocket_engine/execution/console/__init__.py +1 -0
- rocket_engine/execution/console/apply_session.py +318 -0
- rocket_engine/execution/console/lifecycle.py +265 -0
- rocket_engine/execution/dispatch.py +205 -0
- rocket_engine/execution/errors.py +31 -0
- rocket_engine/execution/ports.py +797 -0
- rocket_engine/execution/pyexec/__init__.py +1 -0
- rocket_engine/execution/pyexec/apply_context.py +547 -0
- rocket_engine/execution/pyexec/approvals.py +173 -0
- rocket_engine/execution/pyexec/runner.py +404 -0
- rocket_engine/execution/pyexec/support.py +464 -0
- rocket_engine/execution/python_exec.py +897 -0
- rocket_engine/execution/queue.py +85 -0
- rocket_engine/execution/replay_safe.py +601 -0
- rocket_engine/execution/routing/__init__.py +1 -0
- rocket_engine/execution/routing/calls.py +364 -0
- rocket_engine/execution/routing/namespace.py +785 -0
- rocket_engine/execution/routing/ports.py +102 -0
- rocket_engine/execution/routing/tools.py +346 -0
- rocket_engine/execution/runner.py +311 -0
- rocket_engine/execution/runtime.py +1149 -0
- rocket_engine/execution/service.py +279 -0
- rocket_engine/execution/worker.py +464 -0
- rocket_engine/interfaces/README.md +26 -0
- rocket_engine/interfaces/__init__.py +58 -0
- rocket_engine/interfaces/apig_assets.py +407 -0
- rocket_engine/interfaces/celllink.py +461 -0
- rocket_engine/interfaces/documents.py +92 -0
- rocket_engine/interfaces/envelopes.py +131 -0
- rocket_engine/interfaces/errors.py +16 -0
- rocket_engine/interfaces/layout.py +410 -0
- rocket_engine/interfaces/layout_schema.yaml +278 -0
- rocket_engine/interfaces/paths.py +223 -0
- rocket_engine/interfaces/pdks.py +550 -0
- rocket_engine/interfaces/ports.py +72 -0
- rocket_engine/interfaces/references.py +115 -0
- rocket_engine/interfaces/refs.py +85 -0
- rocket_engine/interfaces/runparams.py +304 -0
- rocket_engine/interfaces/setup.py +552 -0
- rocket_engine/interfaces/setup_catalogue.yaml +243 -0
- rocket_engine/interfaces/setup_probes.py +66 -0
- rocket_engine/interfaces/vocabulary.py +58 -0
- rocket_engine/interfaces/yaml_dump.py +31 -0
- rocket_engine/library/README.md +22 -0
- rocket_engine/library/__init__.py +13 -0
- rocket_engine/library/apig_catalog.py +371 -0
- rocket_engine/library/catalog.py +185 -0
- rocket_engine/library/errors.py +16 -0
- rocket_engine/mutate/__init__.py +776 -0
- rocket_engine/mutate/operators.py +700 -0
- rocket_engine/ops/__init__.py +1 -0
- rocket_engine/ops/deferred.py +279 -0
- rocket_engine/ops/editors.py +404 -0
- rocket_engine/ops/evidence.py +194 -0
- rocket_engine/ops/gates.py +262 -0
- rocket_engine/ops/ports.py +76 -0
- rocket_engine/ops/queries.py +417 -0
- rocket_engine/ops/runs.py +553 -0
- rocket_engine/ops/submit.py +614 -0
- rocket_engine/project/README.md +27 -0
- rocket_engine/project/__init__.py +6 -0
- rocket_engine/project/canvas/__init__.py +55 -0
- rocket_engine/project/canvas/compile.py +109 -0
- rocket_engine/project/canvas/integrate.py +48 -0
- rocket_engine/project/canvas/schema.py +241 -0
- rocket_engine/project/canvas/templates.py +46 -0
- rocket_engine/project/canvas/validate.py +603 -0
- rocket_engine/project/edit/__init__.py +5 -0
- rocket_engine/project/edit/cas.py +87 -0
- rocket_engine/project/edit/transactions.py +282 -0
- rocket_engine/project/errors.py +16 -0
- rocket_engine/project/frontend13.py +685 -0
- rocket_engine/project/geometry/__init__.py +23 -0
- rocket_engine/project/geometry/ops.py +451 -0
- rocket_engine/project/geometry/snap.py +103 -0
- rocket_engine/project/ip/__init__.py +1 -0
- rocket_engine/project/ip/blocks.py +195 -0
- rocket_engine/project/ip/entry.schema.json +78 -0
- rocket_engine/project/ip/lifecycle.py +402 -0
- rocket_engine/project/ip/verbs.py +123 -0
- rocket_engine/project/layers/__init__.py +6 -0
- rocket_engine/project/layers/bindings.py +115 -0
- rocket_engine/project/layers/catalog.py +228 -0
- rocket_engine/project/layers/presets.py +43 -0
- rocket_engine/project/ledger/__init__.py +1 -0
- rocket_engine/project/ledger/docs.py +167 -0
- rocket_engine/project/ledger/entities.py +176 -0
- rocket_engine/project/ledger/events.py +29 -0
- rocket_engine/project/ledger/runs.py +257 -0
- rocket_engine/project/ledger/shared.py +174 -0
- rocket_engine/project/ledger/snapshots.py +385 -0
- rocket_engine/project/ledger/wires.py +552 -0
- rocket_engine/project/process/__init__.py +14 -0
- rocket_engine/project/process/model.py +193 -0
- rocket_engine/project/process/recipes.py +121 -0
- rocket_engine/project/schematic/__init__.py +1 -0
- rocket_engine/project/schematic/hierarchy.py +312 -0
- rocket_engine/project/schematic/instances.py +393 -0
- rocket_engine/project/schematic/sheets.py +198 -0
- rocket_engine/project/service.py +2941 -0
- rocket_engine/project/symbols/__init__.py +1 -0
- rocket_engine/project/symbols/cells.py +324 -0
- rocket_engine/project/symbols/linkage.py +356 -0
- rocket_engine/project/symbols/symbols.py +352 -0
- rocket_engine/project/templates/__init__.py +303 -0
- rocket_engine/project/templates/errors.py +25 -0
- rocket_engine/project/testbench/__init__.py +49 -0
- rocket_engine/project/testbench/blocks.py +619 -0
- rocket_engine/project/testbench/templates.py +134 -0
- rocket_engine/stores/README.md +12 -0
- rocket_engine/stores/__init__.py +6 -0
- rocket_engine/stores/ecs.py +1508 -0
- rocket_engine/stores/layers.py +1023 -0
- rocket_engine/stores/settings.py +1188 -0
- rocket_engine/stores/wireplace.py +1324 -0
- rocket_engine/workflows/README.md +18 -0
- rocket_engine/workflows/__init__.py +53 -0
- rocket_engine/workflows/capability_plan.py +102 -0
- rocket_engine/workflows/completion.py +228 -0
- rocket_engine/workflows/connectgraph.py +734 -0
- rocket_engine/workflows/connectgraph_emit.py +336 -0
- rocket_engine/workflows/gates.py +144 -0
- rocket_engine/workflows/photonic_wire.py +76 -0
- rocket_engine/workflows/stages.py +324 -0
- rocket_engine/workflows/viewsync.py +399 -0
- rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
- rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
- rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
- rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
- rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
"""Yosys synth solver adapter: run_synth behind the ToolAdapter port.
|
|
2
|
+
|
|
3
|
+
Protocol, verified against Yosys 0.70+219 (oss-cad-suite): write a
|
|
4
|
+
synth script (`read_verilog` the sources, `hierarchy -check -top`,
|
|
5
|
+
`synth -top`, then `stat` plus `write_verilog`), run it under a
|
|
6
|
+
timeout, and parse the log plus the stat report. With a `liberty`
|
|
7
|
+
input the flow maps to standard cells instead (`dfflibmap` +
|
|
8
|
+
`abc -liberty`, `stat -liberty`): evidence is the mapped netlist
|
|
9
|
+
artifact, per-family cell counts, and the chip area. `hierarchy
|
|
10
|
+
-check` fails loudly (nonzero exit plus `ERROR:`) on a missing top,
|
|
11
|
+
and broken RTL fails the same way; warnings (`Warning:`, including
|
|
12
|
+
`Latch inferred` and `has no driver`) parse as findings that never
|
|
13
|
+
fail the verdict on their own. ABC's own `ABC: Warning:` chatter is
|
|
14
|
+
tool noise, never a design finding.
|
|
15
|
+
|
|
16
|
+
Tool resolution: an explicit job `tool_dir` wins, else PATH, and the
|
|
17
|
+
resolved binary must answer `yosys -V` with a parseable `Yosys
|
|
18
|
+
major.minor` banner — ambient installs are kept and
|
|
19
|
+
version-asserted, never reinstalled. Where the tools live on disk is
|
|
20
|
+
B10 integration scope.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import re
|
|
25
|
+
import shutil
|
|
26
|
+
import subprocess
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
from ...execution.errors import ExecutionError
|
|
30
|
+
from ...execution.runner import tool_roots
|
|
31
|
+
from ...interfaces import ToolAdapter
|
|
32
|
+
from ..errors import AdapterError
|
|
33
|
+
|
|
34
|
+
OPERATION = "run_synth"
|
|
35
|
+
_TOOL = "yosys"
|
|
36
|
+
_TIMEOUT_S = 120
|
|
37
|
+
_VERSION_TIMEOUT_S = 30
|
|
38
|
+
|
|
39
|
+
_SCRIPT_NAME = "synth.ys"
|
|
40
|
+
_LOG_NAME = "synth.log"
|
|
41
|
+
_STAT_NAME = "synth_stat.txt"
|
|
42
|
+
_NETLIST_NAME = "synth_netlist.v"
|
|
43
|
+
_NETLIST_JSON_NAME = "synth_netlist.json"
|
|
44
|
+
|
|
45
|
+
_TOP_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
|
46
|
+
# Verilog `define names, with an optional =value (no whitespace or
|
|
47
|
+
# shell metacharacters; argv is never shelled, belt and suspenders).
|
|
48
|
+
_DEFINE_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(=[A-Za-z0-9_.+-]+)?")
|
|
49
|
+
_VERSION_RE = re.compile(r"Yosys (\d+)\.(\d+)")
|
|
50
|
+
_ERROR_RE = re.compile(r"^(?:(.+?):(\d+): )?ERROR: (.*)$", re.M)
|
|
51
|
+
_WARNING_RE = re.compile(r"^(?:(.+?):(\d+): )?Warning: (.*)$", re.M)
|
|
52
|
+
_CELLS_RE = re.compile(r"^\s*(\d+)(?:\s+[\d.Ee+-]+)?\s+cells\s*$")
|
|
53
|
+
_CELL_LINE_RE = re.compile(r"^\s*(\d+)(?:\s+([\d.Ee+-]+))?\s+(\S+)\s*$")
|
|
54
|
+
_WIRES_RE = re.compile(r"^\s*(\d+)(?:\s+(?:[\d.Ee+-]+|-))?\s+wires\s*$")
|
|
55
|
+
_SUBMODULES_RE = re.compile(r"^\s*\d+\s+submodules\s*$")
|
|
56
|
+
_AREA_TOP_RE = re.compile(r"Chip area for top module '[^']*':\s*([\d.Ee+-]+)")
|
|
57
|
+
_AREA_MODULE_RE = re.compile(r"Chip area for module '[^']*':\s*([\d.Ee+-]+)")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _parse_errors(text):
|
|
61
|
+
"""Collect synth_error violations from yosys ERROR lines.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
text: Combined yosys log/stdout/stderr text.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Error violations with file/line when the log carries them.
|
|
68
|
+
"""
|
|
69
|
+
violations = []
|
|
70
|
+
for match in _ERROR_RE.finditer(text):
|
|
71
|
+
file, line, message = match.groups()
|
|
72
|
+
violation = {
|
|
73
|
+
"rule": "synth_error",
|
|
74
|
+
"severity": "error",
|
|
75
|
+
"message": message,
|
|
76
|
+
}
|
|
77
|
+
if file is not None:
|
|
78
|
+
violation["file"] = file
|
|
79
|
+
violation["line"] = int(line)
|
|
80
|
+
violations.append(violation)
|
|
81
|
+
return violations
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _warning_rule(message):
|
|
85
|
+
"""Classify one yosys warning into its finding rule.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
message: Warning body after the `Warning:` prefix.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Latch, undriven, or the generic synth_warning rule name.
|
|
92
|
+
"""
|
|
93
|
+
if "Latch inferred" in message:
|
|
94
|
+
return "latch"
|
|
95
|
+
if "has no driver" in message:
|
|
96
|
+
return "undriven"
|
|
97
|
+
return "synth_warning"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _parse_warnings(text):
|
|
101
|
+
"""Collect warning findings from yosys Warning lines.
|
|
102
|
+
|
|
103
|
+
ABC's own `ABC: Warning:` lines are optimizer chatter, and the
|
|
104
|
+
trailing `Warnings: N unique` line is a summary — neither is a
|
|
105
|
+
design finding, so both are skipped.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
text: Combined yosys log/stdout/stderr text.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Warning violations with file/line when the log carries them.
|
|
112
|
+
"""
|
|
113
|
+
violations = []
|
|
114
|
+
for match in _WARNING_RE.finditer(text):
|
|
115
|
+
start = match.start()
|
|
116
|
+
line_start = text.rfind("\n", 0, start) + 1
|
|
117
|
+
prefix = text[line_start:start]
|
|
118
|
+
if "ABC:" in prefix:
|
|
119
|
+
continue
|
|
120
|
+
file, line, message = match.groups()
|
|
121
|
+
violation = {
|
|
122
|
+
"rule": _warning_rule(message),
|
|
123
|
+
"severity": "warning",
|
|
124
|
+
"message": message,
|
|
125
|
+
}
|
|
126
|
+
if file is not None:
|
|
127
|
+
violation["file"] = file
|
|
128
|
+
violation["line"] = int(line)
|
|
129
|
+
violations.append(violation)
|
|
130
|
+
return violations
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _parse_stat(text):
|
|
134
|
+
"""Aggregate cell counts, wires, and area over stat sections.
|
|
135
|
+
|
|
136
|
+
Each `=== module ===` section reports local counts excluding
|
|
137
|
+
submodules, so summing across sections is the design total. The
|
|
138
|
+
`Chip area for top module` line wins when present; otherwise the
|
|
139
|
+
per-module areas sum (same total, verified on SERV).
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
text: Stat report text from `tee -o stat`.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Mapping with cells (type -> count), cell_count, wire_count,
|
|
146
|
+
area (None without a liberty-mapped stat), and
|
|
147
|
+
saw_cells (whether any cells block parsed).
|
|
148
|
+
"""
|
|
149
|
+
cells = {}
|
|
150
|
+
cell_count = 0
|
|
151
|
+
wire_count = 0
|
|
152
|
+
saw_cells = False
|
|
153
|
+
in_cells = False
|
|
154
|
+
for line in text.splitlines():
|
|
155
|
+
if _WIRES_RE.match(line):
|
|
156
|
+
wire_count += int(_WIRES_RE.match(line).group(1))
|
|
157
|
+
in_cells = False
|
|
158
|
+
continue
|
|
159
|
+
if _CELLS_RE.match(line):
|
|
160
|
+
saw_cells = True
|
|
161
|
+
in_cells = True
|
|
162
|
+
continue
|
|
163
|
+
if in_cells:
|
|
164
|
+
if not line.strip() or _SUBMODULES_RE.match(line):
|
|
165
|
+
in_cells = False
|
|
166
|
+
continue
|
|
167
|
+
entry = _CELL_LINE_RE.match(line)
|
|
168
|
+
if entry is not None:
|
|
169
|
+
count, _area, cell_type = entry.groups()
|
|
170
|
+
cells[cell_type] = cells.get(cell_type, 0) + int(count)
|
|
171
|
+
cell_count += int(count)
|
|
172
|
+
top_area = _AREA_TOP_RE.search(text)
|
|
173
|
+
if top_area is not None:
|
|
174
|
+
area = float(top_area.group(1))
|
|
175
|
+
else:
|
|
176
|
+
module_areas = _AREA_MODULE_RE.findall(text)
|
|
177
|
+
area = sum(float(a) for a in module_areas) if module_areas else None
|
|
178
|
+
return {
|
|
179
|
+
"cells": cells,
|
|
180
|
+
"cell_count": cell_count,
|
|
181
|
+
"wire_count": wire_count,
|
|
182
|
+
"area": area,
|
|
183
|
+
"saw_cells": saw_cells,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _parse_netlist_json(payload):
|
|
188
|
+
"""Aggregate cell counts and wires over a write_json netlist.
|
|
189
|
+
|
|
190
|
+
Blessed structured escape (ESCAPE-HARDEN): yosys `write_json`
|
|
191
|
+
carries the same design the scraped `stat` text summarizes.
|
|
192
|
+
Per-module netnames count as wires; per-module cells count
|
|
193
|
+
except cells whose type names another module (submodule
|
|
194
|
+
instances, never leaf cells) — this reproduces the stat text
|
|
195
|
+
local-count sums byte-identically (live-verified on yosys
|
|
196
|
+
0.70+219, including a hierarchical design). The JSON carries
|
|
197
|
+
no mapped area, so area still comes from the stat text.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
payload: Parsed `write_json` mapping, or None when yosys
|
|
201
|
+
wrote no JSON report.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Mapping with cells (type -> count), cell_count,
|
|
205
|
+
wire_count, and saw_cells, or None when the payload
|
|
206
|
+
carries no module table.
|
|
207
|
+
"""
|
|
208
|
+
if not isinstance(payload, dict):
|
|
209
|
+
return None
|
|
210
|
+
modules = payload.get("modules")
|
|
211
|
+
if not isinstance(modules, dict) or not modules:
|
|
212
|
+
return None
|
|
213
|
+
names = set(modules)
|
|
214
|
+
cells = {}
|
|
215
|
+
cell_count = 0
|
|
216
|
+
wire_count = 0
|
|
217
|
+
for module in modules.values():
|
|
218
|
+
if not isinstance(module, dict):
|
|
219
|
+
continue
|
|
220
|
+
netnames = module.get("netnames")
|
|
221
|
+
if isinstance(netnames, dict):
|
|
222
|
+
wire_count += len(netnames)
|
|
223
|
+
for cell in (module.get("cells") or {}).values():
|
|
224
|
+
cell_type = cell.get("type") if isinstance(cell, dict) else None
|
|
225
|
+
if not isinstance(cell_type, str) or cell_type in names:
|
|
226
|
+
continue
|
|
227
|
+
cells[cell_type] = cells.get(cell_type, 0) + 1
|
|
228
|
+
cell_count += 1
|
|
229
|
+
return {
|
|
230
|
+
"cells": cells,
|
|
231
|
+
"cell_count": cell_count,
|
|
232
|
+
"wire_count": wire_count,
|
|
233
|
+
"saw_cells": True,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _quote(path):
|
|
238
|
+
"""Quote one path for the yosys script language.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
path: Filesystem path to embed in the script.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Double-quoted path with backslashes and quotes escaped.
|
|
245
|
+
"""
|
|
246
|
+
return '"' + str(path).replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class YosysSynthAdapter(ToolAdapter):
|
|
250
|
+
"""run_synth via Yosys: RTL to mapped netlist plus evidence.
|
|
251
|
+
|
|
252
|
+
Serves the run_synth operation with deck.tool `yosys` (the only
|
|
253
|
+
tool). Synthesizes the sources for the named top with `synth
|
|
254
|
+
-top`; with a `liberty` input it maps to standard cells
|
|
255
|
+
(`dfflibmap` + `abc -liberty`) and reports the chip area.
|
|
256
|
+
Evidence is the mapped netlist artifact, per-family cell counts,
|
|
257
|
+
the area estimate when mapped, and warnings as findings.
|
|
258
|
+
|
|
259
|
+
Job keys:
|
|
260
|
+
sources (required): Verilog source paths under synth.
|
|
261
|
+
top (required): Top module name (plain identifier).
|
|
262
|
+
defines: Verilog `define list, optional =value each.
|
|
263
|
+
liberty: Liberty (.lib) path for standard-cell mapping;
|
|
264
|
+
generic synth without it.
|
|
265
|
+
timeout_s: Synth budget in seconds (default 120).
|
|
266
|
+
tool_dir: Directory holding yosys; PATH otherwise.
|
|
267
|
+
deck: Optional mapping; deck.tool must be yosys here.
|
|
268
|
+
workdir: Run directory (default ".").
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
runner: Optional isolated runner; direct subprocess when None.
|
|
272
|
+
|
|
273
|
+
Caution: Broken RTL and a missing top fail loudly (nonzero exit
|
|
274
|
+
plus synth_error findings) — a failed run never parses as
|
|
275
|
+
clean, and warnings alone never fail the verdict.
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
def __init__(self, runner=None):
|
|
279
|
+
self._runner = runner
|
|
280
|
+
|
|
281
|
+
def capabilities(self, target):
|
|
282
|
+
"""Report the served operation, tool, and mapping modes.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
target: Target the capabilities are reported for
|
|
286
|
+
(accepted, unused — one top per job).
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Capability mapping with operations, tool, defines, and
|
|
290
|
+
liberty support.
|
|
291
|
+
"""
|
|
292
|
+
return {
|
|
293
|
+
"operations": [OPERATION],
|
|
294
|
+
"tool": _TOOL,
|
|
295
|
+
"defines": True,
|
|
296
|
+
"liberty": True,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
def preflight(self, job):
|
|
300
|
+
"""Check operation, top, sources, defines, liberty, yosys.
|
|
301
|
+
|
|
302
|
+
Cheap deterministic checks first, tool resolution last; the
|
|
303
|
+
resolved binary must answer its version probe.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
job: Solver job with operation run_synth plus sources,
|
|
307
|
+
top, and optional defines/liberty/timeout inputs.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Checked mapping with the resolved yosys path and
|
|
311
|
+
version, sources, top, defines, liberty, timeout_s.
|
|
312
|
+
|
|
313
|
+
Raises:
|
|
314
|
+
AdapterError: unsupported_operation for another
|
|
315
|
+
operation, invalid_command for a bad top, timeout,
|
|
316
|
+
or defines, invalid_reference for missing sources
|
|
317
|
+
or liberty, missing_dependency for an unresolvable
|
|
318
|
+
or unassertable yosys.
|
|
319
|
+
"""
|
|
320
|
+
# Cheap deterministic checks first: operation, top, timeout,
|
|
321
|
+
# sources, defines, liberty. Tool resolution last, since it
|
|
322
|
+
# depends on the machine.
|
|
323
|
+
if job.get("operation") != OPERATION:
|
|
324
|
+
raise AdapterError(
|
|
325
|
+
"unsupported_operation",
|
|
326
|
+
f"Yosys synth handles {OPERATION}, got {job.get('operation')}.",
|
|
327
|
+
)
|
|
328
|
+
top = job.get("top")
|
|
329
|
+
if not isinstance(top, str) or not _TOP_RE.fullmatch(top):
|
|
330
|
+
raise AdapterError("invalid_command", f"Bad top: {top!r}.")
|
|
331
|
+
timeout = job.get("timeout_s", _TIMEOUT_S)
|
|
332
|
+
if (
|
|
333
|
+
not isinstance(timeout, (int, float))
|
|
334
|
+
or isinstance(timeout, bool)
|
|
335
|
+
or not timeout > 0
|
|
336
|
+
):
|
|
337
|
+
raise AdapterError("invalid_command", f"Bad timeout_s: {timeout!r}.")
|
|
338
|
+
sources = job.get("sources") or []
|
|
339
|
+
missing = [s for s in sources if not Path(s).is_file()]
|
|
340
|
+
if not sources or missing:
|
|
341
|
+
raise AdapterError(
|
|
342
|
+
"invalid_reference", f"Sources not found: {missing or sources}."
|
|
343
|
+
)
|
|
344
|
+
defines = job.get("defines") or []
|
|
345
|
+
if not isinstance(defines, list) or any(
|
|
346
|
+
not isinstance(d, str) or not _DEFINE_RE.fullmatch(d) for d in defines
|
|
347
|
+
):
|
|
348
|
+
raise AdapterError("invalid_command", f"Bad defines: {defines!r}.")
|
|
349
|
+
liberty = job.get("liberty")
|
|
350
|
+
if liberty is not None and not Path(liberty).is_file():
|
|
351
|
+
raise AdapterError("invalid_reference", f"Liberty not found: {liberty}.")
|
|
352
|
+
yosys = self._resolve(job, _TOOL)
|
|
353
|
+
checked = {
|
|
354
|
+
"yosys": yosys,
|
|
355
|
+
"yosys_version": self._version(yosys),
|
|
356
|
+
"sources": list(sources),
|
|
357
|
+
"top": top,
|
|
358
|
+
"defines": list(defines),
|
|
359
|
+
"liberty": liberty,
|
|
360
|
+
"timeout_s": timeout,
|
|
361
|
+
}
|
|
362
|
+
if self._runner is not None:
|
|
363
|
+
try:
|
|
364
|
+
self._runner.preflight()
|
|
365
|
+
except ExecutionError as exc:
|
|
366
|
+
raise AdapterError(exc.code, str(exc)) from exc
|
|
367
|
+
return checked
|
|
368
|
+
|
|
369
|
+
def execute(self, job):
|
|
370
|
+
"""Synthesize the sources for the top with yosys.
|
|
371
|
+
|
|
372
|
+
Writes the synth script into the workdir, runs it under the
|
|
373
|
+
timeout, and returns the log, stat report, JSON netlist, and
|
|
374
|
+
netlist path. A failed run still returns its log — the
|
|
375
|
+
verdict comes from parse, never the exit code alone.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
job: Solver job with operation run_synth plus sources,
|
|
379
|
+
top, and optional defines/liberty/timeout inputs.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
Native output with exit_code, stdout/stderr, log and
|
|
383
|
+
stat text, the parsed `write_json` netlist under
|
|
384
|
+
`netlist_json` (None when yosys wrote none), netlist
|
|
385
|
+
path, top, mapped flag, and the yosys version.
|
|
386
|
+
|
|
387
|
+
Raises:
|
|
388
|
+
AdapterError: invalid_reference for a missing workdir,
|
|
389
|
+
runner_unavailable on a synth timeout, plus the
|
|
390
|
+
preflight codes (execute re-checks first).
|
|
391
|
+
"""
|
|
392
|
+
checked = self.preflight(job)
|
|
393
|
+
workdir = job.get("workdir", ".")
|
|
394
|
+
if not Path(workdir).is_dir():
|
|
395
|
+
raise AdapterError(
|
|
396
|
+
"invalid_reference", f"Workdir not found: {workdir}."
|
|
397
|
+
)
|
|
398
|
+
script = self._script(checked)
|
|
399
|
+
(Path(workdir) / _SCRIPT_NAME).write_text(script)
|
|
400
|
+
roots = tool_roots([checked["yosys"]])
|
|
401
|
+
proc = self._run(
|
|
402
|
+
[checked["yosys"], "-q", "-s", _SCRIPT_NAME, "-l", _LOG_NAME],
|
|
403
|
+
workdir,
|
|
404
|
+
checked["timeout_s"],
|
|
405
|
+
roots,
|
|
406
|
+
)
|
|
407
|
+
log_path = Path(workdir) / _LOG_NAME
|
|
408
|
+
stat_path = Path(workdir) / _STAT_NAME
|
|
409
|
+
netlist_json_path = Path(workdir) / _NETLIST_JSON_NAME
|
|
410
|
+
netlist_json = None
|
|
411
|
+
if netlist_json_path.is_file():
|
|
412
|
+
try:
|
|
413
|
+
parsed_json = json.loads(netlist_json_path.read_text())
|
|
414
|
+
if isinstance(parsed_json, dict):
|
|
415
|
+
netlist_json = parsed_json
|
|
416
|
+
except (OSError, ValueError):
|
|
417
|
+
netlist_json = None
|
|
418
|
+
return {
|
|
419
|
+
"exit_code": proc.returncode,
|
|
420
|
+
"stdout": proc.stdout,
|
|
421
|
+
"stderr": proc.stderr,
|
|
422
|
+
"log": log_path.read_text() if log_path.is_file() else "",
|
|
423
|
+
"stat": stat_path.read_text() if stat_path.is_file() else "",
|
|
424
|
+
"netlist_json": netlist_json,
|
|
425
|
+
"netlist": str(Path(workdir) / _NETLIST_NAME),
|
|
426
|
+
"top": checked["top"],
|
|
427
|
+
"mapped": checked["liberty"] is not None,
|
|
428
|
+
"yosys_version": checked["yosys_version"],
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
def parse(self, native_output):
|
|
432
|
+
"""Parse errors, warnings, stat counts, and area evidence.
|
|
433
|
+
|
|
434
|
+
Cell counts come from the `write_json` netlist first
|
|
435
|
+
(byte-identical to the stat text; the mapped area still
|
|
436
|
+
comes from the stat text since JSON carries no area key),
|
|
437
|
+
falling back to the scraped stat text for older natives.
|
|
438
|
+
Failed runs surface their ERROR lines (synthesizing one when
|
|
439
|
+
the log carries none, so failure never parses as clean);
|
|
440
|
+
warnings become findings that never fail alone; a clean run
|
|
441
|
+
reports cell counts plus the mapped area.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
native_output: Native output with exit_code, log and
|
|
445
|
+
stat text, the optional parsed `netlist_json`, and
|
|
446
|
+
the netlist path.
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
{total, violations[], measurements[], source} plus
|
|
450
|
+
cells, cell_count, wire_count, area, mapped,
|
|
451
|
+
netlist, top, and yosys_version evidence; source
|
|
452
|
+
is `log` or `stdio_fallback`.
|
|
453
|
+
|
|
454
|
+
Raises:
|
|
455
|
+
AdapterError: runner_unavailable when the output has no
|
|
456
|
+
exit code, or a clean run left no stat verdict or
|
|
457
|
+
netlist — unparseable never parses as clean.
|
|
458
|
+
"""
|
|
459
|
+
exit_code = native_output.get("exit_code")
|
|
460
|
+
if exit_code is None:
|
|
461
|
+
raise AdapterError(
|
|
462
|
+
"runner_unavailable", "Native output has no exit code."
|
|
463
|
+
)
|
|
464
|
+
# MIGRATE-STDIO-FILE: synth.log (via -l) is the parse
|
|
465
|
+
# channel; with -q the stdout join could double-report
|
|
466
|
+
# lines present in both streams, so stdio survives only
|
|
467
|
+
# as a loud fallback for natives recorded before the
|
|
468
|
+
# file channel existed.
|
|
469
|
+
log = native_output.get("log") or ""
|
|
470
|
+
if log.strip():
|
|
471
|
+
text, source = log, "log"
|
|
472
|
+
else:
|
|
473
|
+
text = "\n".join(
|
|
474
|
+
native_output.get(key, "") or ""
|
|
475
|
+
for key in ("stdout", "stderr")
|
|
476
|
+
)
|
|
477
|
+
source = "stdio_fallback"
|
|
478
|
+
errors = _parse_errors(text)
|
|
479
|
+
warnings = _parse_warnings(text)
|
|
480
|
+
stat_text = _parse_stat(native_output.get("stat") or "")
|
|
481
|
+
stat_json = _parse_netlist_json(native_output.get("netlist_json"))
|
|
482
|
+
if stat_json is not None:
|
|
483
|
+
# Structured counts win; the mapped area has no JSON
|
|
484
|
+
# key, so it still comes from the stat text.
|
|
485
|
+
stat = {**stat_json, "area": stat_text["area"]}
|
|
486
|
+
else:
|
|
487
|
+
stat = stat_text
|
|
488
|
+
netlist = native_output.get("netlist")
|
|
489
|
+
if exit_code != 0:
|
|
490
|
+
if not errors:
|
|
491
|
+
# Defensive: a failed run must never parse as clean.
|
|
492
|
+
errors = [
|
|
493
|
+
{
|
|
494
|
+
"rule": "synth_error",
|
|
495
|
+
"severity": "error",
|
|
496
|
+
"message": f"yosys exited {exit_code}",
|
|
497
|
+
}
|
|
498
|
+
]
|
|
499
|
+
violations = errors + warnings
|
|
500
|
+
return {
|
|
501
|
+
"total": len(violations),
|
|
502
|
+
"violations": violations,
|
|
503
|
+
"measurements": [],
|
|
504
|
+
"cells": stat["cells"],
|
|
505
|
+
"cell_count": stat["cell_count"],
|
|
506
|
+
"wire_count": stat["wire_count"],
|
|
507
|
+
"area": None,
|
|
508
|
+
"mapped": bool(native_output.get("mapped")),
|
|
509
|
+
"netlist": netlist,
|
|
510
|
+
"top": native_output.get("top"),
|
|
511
|
+
"yosys_version": native_output.get("yosys_version"),
|
|
512
|
+
"source": source,
|
|
513
|
+
}
|
|
514
|
+
if not stat["saw_cells"]:
|
|
515
|
+
raise AdapterError(
|
|
516
|
+
"runner_unavailable", "yosys output has no stat verdict."
|
|
517
|
+
)
|
|
518
|
+
if netlist is None or not Path(netlist).is_file():
|
|
519
|
+
raise AdapterError(
|
|
520
|
+
"runner_unavailable", "yosys run left no netlist artifact."
|
|
521
|
+
)
|
|
522
|
+
violations = errors + warnings
|
|
523
|
+
measurements = [
|
|
524
|
+
{"name": "cell_count", "value": stat["cell_count"], "unit": "cells"},
|
|
525
|
+
{"name": "wire_count", "value": stat["wire_count"], "unit": "wires"},
|
|
526
|
+
]
|
|
527
|
+
if stat["area"] is not None:
|
|
528
|
+
measurements.append(
|
|
529
|
+
{"name": "area", "value": stat["area"], "unit": "um^2"}
|
|
530
|
+
)
|
|
531
|
+
return {
|
|
532
|
+
"total": len(violations),
|
|
533
|
+
"violations": violations,
|
|
534
|
+
"measurements": measurements,
|
|
535
|
+
"cells": stat["cells"],
|
|
536
|
+
"cell_count": stat["cell_count"],
|
|
537
|
+
"wire_count": stat["wire_count"],
|
|
538
|
+
"area": stat["area"],
|
|
539
|
+
"mapped": bool(native_output.get("mapped")),
|
|
540
|
+
"netlist": netlist,
|
|
541
|
+
"top": native_output.get("top"),
|
|
542
|
+
"yosys_version": native_output.get("yosys_version"),
|
|
543
|
+
"source": source,
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
@staticmethod
|
|
547
|
+
def _script(checked):
|
|
548
|
+
"""Render the yosys synth script for the checked job.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
checked: Preflight mapping with sources, top, defines,
|
|
552
|
+
and the optional liberty path.
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
Yosys script text (read, check, synth, map, stat, dump
|
|
556
|
+
plus the `write_json` structured netlist).
|
|
557
|
+
"""
|
|
558
|
+
lines = [
|
|
559
|
+
"read_verilog"
|
|
560
|
+
+ "".join(f" -D{d}" for d in checked["defines"])
|
|
561
|
+
+ "".join(f" {_quote(s)}" for s in checked["sources"]),
|
|
562
|
+
f"hierarchy -check -top {checked['top']}",
|
|
563
|
+
f"synth -top {checked['top']}",
|
|
564
|
+
]
|
|
565
|
+
if checked["liberty"] is not None:
|
|
566
|
+
quoted = _quote(checked["liberty"])
|
|
567
|
+
lines.extend(
|
|
568
|
+
[
|
|
569
|
+
f"dfflibmap -liberty {quoted}",
|
|
570
|
+
f"abc -liberty {quoted}",
|
|
571
|
+
"clean",
|
|
572
|
+
f"tee -o {_STAT_NAME} stat -liberty {quoted}",
|
|
573
|
+
]
|
|
574
|
+
)
|
|
575
|
+
else:
|
|
576
|
+
lines.append(f"tee -o {_STAT_NAME} stat")
|
|
577
|
+
lines.append(f"write_verilog {_NETLIST_NAME}")
|
|
578
|
+
lines.append(f"write_json {_NETLIST_JSON_NAME}")
|
|
579
|
+
return "\n".join(lines) + "\n"
|
|
580
|
+
|
|
581
|
+
@staticmethod
|
|
582
|
+
def _resolve(job, binary):
|
|
583
|
+
tool_dir = job.get("tool_dir")
|
|
584
|
+
if tool_dir:
|
|
585
|
+
candidate = Path(tool_dir) / binary
|
|
586
|
+
if candidate.is_file():
|
|
587
|
+
return str(candidate)
|
|
588
|
+
raise AdapterError(
|
|
589
|
+
"missing_dependency",
|
|
590
|
+
f"{binary} not found in {tool_dir} — install the pinned "
|
|
591
|
+
"oss-cad-suite bundle (see docs/provision-plan.md).",
|
|
592
|
+
)
|
|
593
|
+
found = shutil.which(binary)
|
|
594
|
+
if found is None:
|
|
595
|
+
raise AdapterError(
|
|
596
|
+
"missing_dependency",
|
|
597
|
+
f"{binary} not on PATH — install the pinned oss-cad-suite "
|
|
598
|
+
"bundle (see docs/provision-plan.md) or pass tool_dir.",
|
|
599
|
+
)
|
|
600
|
+
return found
|
|
601
|
+
|
|
602
|
+
@staticmethod
|
|
603
|
+
def _version(yosys):
|
|
604
|
+
"""Assert the binary answers with a parseable yosys banner.
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
yosys: Resolved yosys binary path.
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
Full `yosys -V` banner line (kept as evidence).
|
|
611
|
+
|
|
612
|
+
Raises:
|
|
613
|
+
AdapterError: missing_dependency when the probe fails,
|
|
614
|
+
times out, or answers no yosys banner.
|
|
615
|
+
"""
|
|
616
|
+
try:
|
|
617
|
+
proc = subprocess.run(
|
|
618
|
+
[yosys, "-V"],
|
|
619
|
+
capture_output=True,
|
|
620
|
+
text=True,
|
|
621
|
+
timeout=_VERSION_TIMEOUT_S,
|
|
622
|
+
)
|
|
623
|
+
except subprocess.TimeoutExpired as exc:
|
|
624
|
+
raise AdapterError(
|
|
625
|
+
"missing_dependency",
|
|
626
|
+
f"yosys version probe timed out at {yosys}.",
|
|
627
|
+
) from exc
|
|
628
|
+
banner = (proc.stdout + "\n" + proc.stderr).strip().splitlines()
|
|
629
|
+
versioned = next((line for line in banner if _VERSION_RE.search(line)), None)
|
|
630
|
+
if proc.returncode != 0 or versioned is None:
|
|
631
|
+
raise AdapterError(
|
|
632
|
+
"missing_dependency",
|
|
633
|
+
f"yosys at {yosys} answered no yosys banner — install the "
|
|
634
|
+
"pinned oss-cad-suite bundle (see docs/provision-plan.md).",
|
|
635
|
+
)
|
|
636
|
+
return versioned.strip()
|
|
637
|
+
|
|
638
|
+
def _run(self, argv, workdir, timeout_s, extra_binds=()):
|
|
639
|
+
if self._runner is not None:
|
|
640
|
+
try:
|
|
641
|
+
return self._runner.run(argv, workdir, timeout_s, extra_binds)
|
|
642
|
+
except ExecutionError as exc:
|
|
643
|
+
raise AdapterError(exc.code, str(exc)) from exc
|
|
644
|
+
try:
|
|
645
|
+
return subprocess.run(
|
|
646
|
+
argv,
|
|
647
|
+
capture_output=True,
|
|
648
|
+
text=True,
|
|
649
|
+
timeout=timeout_s,
|
|
650
|
+
cwd=workdir,
|
|
651
|
+
)
|
|
652
|
+
except subprocess.TimeoutExpired as exc:
|
|
653
|
+
raise AdapterError(
|
|
654
|
+
"runner_unavailable", f"synth timed out after {timeout_s}s."
|
|
655
|
+
) from exc
|