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,287 @@
|
|
|
1
|
+
"""Magic DRC solver adapter (I14): batch DRC behind the ToolAdapter port.
|
|
2
|
+
|
|
3
|
+
Protocol, verified against magic 8.3.685 + scmos tech 8.2.8: Tcl over
|
|
4
|
+
stdin, `drc check` + `drc catchup` under an explicit box (`check`
|
|
5
|
+
rechecks the box area; `count` needs the catchup), `drc count total`
|
|
6
|
+
for the verdict, then `drc find N` + box readout per error area until
|
|
7
|
+
"There aren't that many errors". Magic exits 0 even for unknown techs
|
|
8
|
+
and broken cells, so the verdict always comes from parsed output,
|
|
9
|
+
never from the exit code.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from ...execution.errors import ExecutionError
|
|
18
|
+
from ...interfaces import ToolAdapter
|
|
19
|
+
from ...interfaces.runparams import MAX_TIMEOUT_S, MIN_TIMEOUT_S
|
|
20
|
+
from ..errors import AdapterError
|
|
21
|
+
|
|
22
|
+
OPERATION = "run_drc"
|
|
23
|
+
_TIMEOUT_S = 120
|
|
24
|
+
_BOX_MARGIN = 5
|
|
25
|
+
_DEFAULT_BOX = (-5, -5, 15, 10)
|
|
26
|
+
|
|
27
|
+
_TOTAL_RE = re.compile(r"Total DRC errors found: (\d+)")
|
|
28
|
+
_AREA_RE = re.compile(r"Error area #(\d+):\n([^\n]+)\n(-?\d+) (-?\d+) (-?\d+) (-?\d+)")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _budget_or_raise(job):
|
|
32
|
+
"""Job solver budget: validated, or the adapter default.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
job: Solver job, maybe with a `timeout_s`.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Budget seconds for every `_run` call.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
AdapterError: invalid_command for a boolean,
|
|
42
|
+
non-number, or out-of-bounds budget.
|
|
43
|
+
"""
|
|
44
|
+
timeout = job.get("timeout_s", _TIMEOUT_S)
|
|
45
|
+
if (
|
|
46
|
+
isinstance(timeout, bool)
|
|
47
|
+
or not isinstance(timeout, (int, float))
|
|
48
|
+
or not MIN_TIMEOUT_S <= timeout <= MAX_TIMEOUT_S
|
|
49
|
+
):
|
|
50
|
+
raise AdapterError(
|
|
51
|
+
"invalid_command", f"Bad timeout_s: {timeout!r}."
|
|
52
|
+
)
|
|
53
|
+
return timeout
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _box_for(cell_file):
|
|
57
|
+
x0 = y0 = x1 = y1 = None
|
|
58
|
+
for line in Path(cell_file).read_text().splitlines():
|
|
59
|
+
parts = line.split()
|
|
60
|
+
if len(parts) == 5 and parts[0] == "rect":
|
|
61
|
+
try:
|
|
62
|
+
coords = tuple(int(v) for v in parts[1:])
|
|
63
|
+
except ValueError:
|
|
64
|
+
continue
|
|
65
|
+
if x0 is None:
|
|
66
|
+
x0, y0, x1, y1 = coords
|
|
67
|
+
else:
|
|
68
|
+
x0 = min(x0, coords[0])
|
|
69
|
+
y0 = min(y0, coords[1])
|
|
70
|
+
x1 = max(x1, coords[2])
|
|
71
|
+
y1 = max(y1, coords[3])
|
|
72
|
+
if x0 is None:
|
|
73
|
+
return _DEFAULT_BOX
|
|
74
|
+
m = _BOX_MARGIN
|
|
75
|
+
return (x0 - m, y0 - m, x1 + m, y1 + m)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class MagicDrcAdapter(ToolAdapter):
|
|
79
|
+
"""run_drc via Magic: batch DRC over .mag cells, lambda-unit boxes.
|
|
80
|
+
|
|
81
|
+
Serves the run_drc operation with deck.tool `magic` (the
|
|
82
|
+
operation default). Tcl over stdin against a loaded cell; the
|
|
83
|
+
result is {total, violations[]} with rule/message plus
|
|
84
|
+
lambda-unit bbox areas.
|
|
85
|
+
|
|
86
|
+
Job keys:
|
|
87
|
+
cell (required): Cell name loaded inside magic.
|
|
88
|
+
cell_file (required): Path to the .mag cell file.
|
|
89
|
+
tech: Magic tech name (default scmos).
|
|
90
|
+
deck: Optional mapping; deck.tool selects the solver
|
|
91
|
+
(magic here, klayout for the KLayout path).
|
|
92
|
+
workdir: Run directory (default ".").
|
|
93
|
+
timeout_s: Sim budget in seconds (default 120).
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
runner: Optional isolated runner; direct subprocess when None.
|
|
97
|
+
|
|
98
|
+
Caution: Magic exits 0 even for broken cells and unknown techs —
|
|
99
|
+
the verdict always comes from parsed output, never the exit
|
|
100
|
+
code. Timeout rides the job budget.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(self, runner=None):
|
|
104
|
+
self._runner = runner
|
|
105
|
+
|
|
106
|
+
def capabilities(self, target):
|
|
107
|
+
"""Report the served operation, tech, and tool name.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
target: Target the capabilities are reported for
|
|
111
|
+
(accepted, unused — one tech per job).
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Capability mapping with operations, tech, and tool.
|
|
115
|
+
"""
|
|
116
|
+
return {"operations": [OPERATION], "tech": "scmos", "tool": "magic"}
|
|
117
|
+
|
|
118
|
+
def preflight(self, job):
|
|
119
|
+
"""Check operation, magic binary, tech probe, and cell file.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
job: Solver job with operation run_drc plus cell,
|
|
123
|
+
cell_file, and tech inputs.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Checked mapping with tech, cell, and cell_file.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
AdapterError: unsupported_operation for another
|
|
130
|
+
operation, missing_dependency for a missing magic
|
|
131
|
+
binary or unloadable tech, invalid_reference for a
|
|
132
|
+
missing cell file.
|
|
133
|
+
"""
|
|
134
|
+
if job.get("operation") != OPERATION:
|
|
135
|
+
raise AdapterError(
|
|
136
|
+
"unsupported_operation",
|
|
137
|
+
f"Magic DRC handles {OPERATION}, got {job.get('operation')}.",
|
|
138
|
+
)
|
|
139
|
+
timeout = _budget_or_raise(job)
|
|
140
|
+
if shutil.which("magic") is None:
|
|
141
|
+
raise AdapterError("missing_dependency", "magic binary not found.")
|
|
142
|
+
tech = job.get("tech", "scmos")
|
|
143
|
+
probe = self._run(
|
|
144
|
+
tech, "quit\n", job.get("workdir", "."), timeout=timeout
|
|
145
|
+
)
|
|
146
|
+
if f'Using technology "{tech}"' not in probe.stdout:
|
|
147
|
+
raise AdapterError(
|
|
148
|
+
"missing_dependency", f"Cannot load magic tech: {tech}."
|
|
149
|
+
)
|
|
150
|
+
cell_file = job.get("cell_file")
|
|
151
|
+
if not cell_file or not Path(cell_file).is_file():
|
|
152
|
+
raise AdapterError(
|
|
153
|
+
"invalid_reference", f"Cell file not found: {cell_file}."
|
|
154
|
+
)
|
|
155
|
+
return {
|
|
156
|
+
"tech": tech,
|
|
157
|
+
"cell": job.get("cell"),
|
|
158
|
+
"cell_file": cell_file,
|
|
159
|
+
"timeout_s": timeout,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
def execute(self, job):
|
|
163
|
+
"""Run magic DRC: a count pass, then per-area detail.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
job: Solver job with operation run_drc plus cell,
|
|
167
|
+
cell_file, and tech inputs.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Native output with exit_code, combined stdout/stderr,
|
|
171
|
+
tech, and cell.
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
AdapterError: missing_dependency when the tech fails
|
|
175
|
+
to load mid-run, runner_unavailable when magic
|
|
176
|
+
output carries no DRC total line, plus the
|
|
177
|
+
preflight codes (execute re-checks first).
|
|
178
|
+
"""
|
|
179
|
+
checked = self.preflight(job)
|
|
180
|
+
workdir = job.get("workdir", ".")
|
|
181
|
+
box = _box_for(checked["cell_file"])
|
|
182
|
+
count_script = (
|
|
183
|
+
"load {cell}\nbox values {x0} {y0} {x1} {y1}\n"
|
|
184
|
+
"drc check\ndrc catchup\ndrc count total\nquit\n"
|
|
185
|
+
).format(cell=checked["cell"], x0=box[0], y0=box[1], x1=box[2], y1=box[3])
|
|
186
|
+
first = self._run(
|
|
187
|
+
checked["tech"], count_script, workdir,
|
|
188
|
+
timeout=checked["timeout_s"],
|
|
189
|
+
)
|
|
190
|
+
if "Failed to load technology" in first.stdout:
|
|
191
|
+
raise AdapterError(
|
|
192
|
+
"missing_dependency", f"Cannot load magic tech: {checked['tech']}."
|
|
193
|
+
)
|
|
194
|
+
match = _TOTAL_RE.search(first.stdout)
|
|
195
|
+
if match is None:
|
|
196
|
+
raise AdapterError(
|
|
197
|
+
"runner_unavailable", "magic output has no DRC total line."
|
|
198
|
+
)
|
|
199
|
+
total = int(match.group(1))
|
|
200
|
+
# Error areas never outnumber error tiles, so total+1 finds always
|
|
201
|
+
# reach the "aren't that many" terminator.
|
|
202
|
+
finds = "".join(
|
|
203
|
+
f"drc find {n}\nputs [box values]\ndrc why\n"
|
|
204
|
+
for n in range(1, total + 2)
|
|
205
|
+
)
|
|
206
|
+
detail_script = (
|
|
207
|
+
"load {cell}\nbox values {x0} {y0} {x1} {y1}\n"
|
|
208
|
+
"drc check\ndrc catchup\n{finds}quit\n"
|
|
209
|
+
).format(
|
|
210
|
+
cell=checked["cell"],
|
|
211
|
+
x0=box[0],
|
|
212
|
+
y0=box[1],
|
|
213
|
+
x1=box[2],
|
|
214
|
+
y1=box[3],
|
|
215
|
+
finds=finds,
|
|
216
|
+
)
|
|
217
|
+
if total:
|
|
218
|
+
second = self._run(
|
|
219
|
+
checked["tech"], detail_script, workdir,
|
|
220
|
+
timeout=checked["timeout_s"],
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
second = None
|
|
224
|
+
return {
|
|
225
|
+
"exit_code": first.returncode,
|
|
226
|
+
"stdout": first.stdout + (second.stdout if second else ""),
|
|
227
|
+
"stderr": first.stderr + (second.stderr if second else ""),
|
|
228
|
+
"tech": checked["tech"],
|
|
229
|
+
"cell": checked["cell"],
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
def parse(self, native_output):
|
|
233
|
+
"""Parse the DRC total plus per-area rule boxes.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
native_output: Native output with magic stdout.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
{total, violations[]} with rule plus lambda-unit
|
|
240
|
+
bbox areas.
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
AdapterError: runner_unavailable when the output has no
|
|
244
|
+
DRC total line.
|
|
245
|
+
"""
|
|
246
|
+
stdout = native_output.get("stdout", "")
|
|
247
|
+
match = _TOTAL_RE.search(stdout)
|
|
248
|
+
if match is None:
|
|
249
|
+
raise AdapterError(
|
|
250
|
+
"runner_unavailable", "magic output has no DRC total line."
|
|
251
|
+
)
|
|
252
|
+
violations = [
|
|
253
|
+
{
|
|
254
|
+
"area": int(area),
|
|
255
|
+
"rule": rule,
|
|
256
|
+
"bbox": [int(x0), int(y0), int(x1), int(y1)],
|
|
257
|
+
}
|
|
258
|
+
for area, rule, x0, y0, x1, y1 in _AREA_RE.findall(stdout)
|
|
259
|
+
]
|
|
260
|
+
return {"total": int(match.group(1)), "violations": violations}
|
|
261
|
+
|
|
262
|
+
def _run(self, tech, script, workdir, timeout=_TIMEOUT_S):
|
|
263
|
+
argv = ["magic", "-dnull", "-noconsole", "-T", tech]
|
|
264
|
+
if self._runner is not None:
|
|
265
|
+
try:
|
|
266
|
+
return self._runner.run(
|
|
267
|
+
argv, workdir, timeout, (), input_text=script
|
|
268
|
+
)
|
|
269
|
+
except ExecutionError as exc:
|
|
270
|
+
raise AdapterError(exc.code, str(exc)) from exc
|
|
271
|
+
if not Path(workdir).is_dir():
|
|
272
|
+
raise AdapterError(
|
|
273
|
+
"invalid_reference", f"Workdir not found: {workdir}."
|
|
274
|
+
)
|
|
275
|
+
try:
|
|
276
|
+
return subprocess.run(
|
|
277
|
+
argv,
|
|
278
|
+
input=script,
|
|
279
|
+
capture_output=True,
|
|
280
|
+
text=True,
|
|
281
|
+
timeout=timeout,
|
|
282
|
+
cwd=workdir,
|
|
283
|
+
)
|
|
284
|
+
except subprocess.TimeoutExpired as exc:
|
|
285
|
+
raise AdapterError(
|
|
286
|
+
"runner_unavailable", f"magic timed out after {timeout}s."
|
|
287
|
+
) from exc
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Magic DRC v1 normalizer: v0 parse output to a ResultBundle.
|
|
2
|
+
|
|
3
|
+
Maps the ``run_drc`` magic parse shape (``violations[]`` with
|
|
4
|
+
rule, lambda-unit area, and bbox) onto a typed rule table, a
|
|
5
|
+
bound view, and findings. Pure function of the parsed output: no
|
|
6
|
+
I/O, no tool calls, deterministic ids derived from the run id.
|
|
7
|
+
Magic reports no severity or message per box (see ESCAPE-HARDEN),
|
|
8
|
+
so every box fails as an error finding naming its rule.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
TASK = "run_drc"
|
|
12
|
+
|
|
13
|
+
_DIAG_CAP = 240
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _cap(text):
|
|
17
|
+
"""Cap one diagnostic string at the bundle prose limit.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
text: Raw diagnostic text, possibly unbounded tool prose.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
The text unchanged when short, else truncated with an
|
|
24
|
+
ellipsis marker.
|
|
25
|
+
"""
|
|
26
|
+
text = text if isinstance(text, str) else str(text)
|
|
27
|
+
if len(text) <= _DIAG_CAP:
|
|
28
|
+
return text
|
|
29
|
+
return text[: _DIAG_CAP - 3] + "..."
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _bbox_text(bbox):
|
|
33
|
+
"""Render one lambda-unit bbox as comma-joined coordinates.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
bbox: Coordinate list from the v0 violation, if any.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Comma-joined coordinate text, or an empty string when the
|
|
40
|
+
violation carries no bbox.
|
|
41
|
+
"""
|
|
42
|
+
if not bbox:
|
|
43
|
+
return ""
|
|
44
|
+
return ",".join(str(c) for c in bbox)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _message(violation):
|
|
48
|
+
"""Render one violation message from its rule, area, and bbox.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
violation: v0 violation with rule, area, and bbox.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Capped message naming the rule and its geometry.
|
|
55
|
+
"""
|
|
56
|
+
rule = violation.get("rule", "drc")
|
|
57
|
+
area = violation.get("area")
|
|
58
|
+
bbox = _bbox_text(violation.get("bbox"))
|
|
59
|
+
text = f"{rule} at [{bbox}] area {area}"
|
|
60
|
+
return _cap(text)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _provenance(project_id, run_id, tools, provenance):
|
|
64
|
+
"""Build ResultProvenance, defaulting to a placeholder closure.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
project_id: Project the run belongs to.
|
|
68
|
+
run_id: Run the bundle describes.
|
|
69
|
+
tools: Tool ingredient mappings (id, version, digest?).
|
|
70
|
+
provenance: Full provenance mapping when the caller has
|
|
71
|
+
one; a deterministic placeholder otherwise.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Provenance mapping valid against the v1 schema.
|
|
75
|
+
"""
|
|
76
|
+
if provenance is not None:
|
|
77
|
+
return provenance
|
|
78
|
+
ref = {"scope": "project", "id": project_id, "revision": "r1"}
|
|
79
|
+
return {
|
|
80
|
+
"snapshot": dict(ref),
|
|
81
|
+
"input_lock": dict(ref),
|
|
82
|
+
"closure_hash": "0" * 64,
|
|
83
|
+
"tools": [
|
|
84
|
+
{
|
|
85
|
+
"id": item["id"],
|
|
86
|
+
"version": item.get("version", ""),
|
|
87
|
+
"digest": item.get("digest", "0" * 64),
|
|
88
|
+
}
|
|
89
|
+
for item in tools
|
|
90
|
+
],
|
|
91
|
+
"parents": [],
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def normalize(parsed, *, run_id, project_id="project", target=None,
|
|
96
|
+
tools=(), provenance=None):
|
|
97
|
+
"""Normalize one magic DRC parse output to a v1 ResultBundle.
|
|
98
|
+
|
|
99
|
+
Rule boxes become a table dataset (rule, area, bbox) plus a
|
|
100
|
+
bound table view; every box also becomes an error finding
|
|
101
|
+
naming its rule.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
parsed: v0 parse mapping with ``violations[]``.
|
|
105
|
+
run_id: Run the bundle describes (anchors all ids).
|
|
106
|
+
project_id: Project the run belongs to.
|
|
107
|
+
target: v1 target mapping; project kind by default.
|
|
108
|
+
tools: Tool ingredient mappings for provenance.
|
|
109
|
+
provenance: Full provenance mapping when the caller has
|
|
110
|
+
one; a deterministic placeholder otherwise.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Schema-valid ResultBundle mapping.
|
|
114
|
+
"""
|
|
115
|
+
violations = parsed.get("violations") or []
|
|
116
|
+
table_id = f"{run_id}:dataset:violations"
|
|
117
|
+
datasets = [
|
|
118
|
+
{
|
|
119
|
+
"id": table_id,
|
|
120
|
+
"kind": "table",
|
|
121
|
+
"row_count": len(violations),
|
|
122
|
+
"columns": [
|
|
123
|
+
{
|
|
124
|
+
"id": f"{table_id}:rule",
|
|
125
|
+
"name": "rule",
|
|
126
|
+
"dtype": "utf8",
|
|
127
|
+
"unit": "1",
|
|
128
|
+
"data": [v.get("rule", "") for v in violations],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": f"{table_id}:area",
|
|
132
|
+
"name": "area",
|
|
133
|
+
"dtype": "float64",
|
|
134
|
+
"unit": "1",
|
|
135
|
+
"data": [float(v.get("area", 0)) for v in violations],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"id": f"{table_id}:bbox",
|
|
139
|
+
"name": "bbox",
|
|
140
|
+
"dtype": "utf8",
|
|
141
|
+
"unit": "1",
|
|
142
|
+
"data": [_bbox_text(v.get("bbox")) for v in violations],
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
"conditions": [],
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
views = [
|
|
149
|
+
{
|
|
150
|
+
"id": f"{run_id}:view:violations",
|
|
151
|
+
"kind": "table",
|
|
152
|
+
"dataset_id": table_id,
|
|
153
|
+
"name": "drc violations",
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
findings = [
|
|
157
|
+
{
|
|
158
|
+
"id": f"{run_id}:finding:{index}",
|
|
159
|
+
"severity": "error",
|
|
160
|
+
"code": v.get("rule", "drc") or "drc",
|
|
161
|
+
"message": _message(v),
|
|
162
|
+
}
|
|
163
|
+
for index, v in enumerate(violations)
|
|
164
|
+
]
|
|
165
|
+
return {
|
|
166
|
+
"schema": "rocketeda/results/v1",
|
|
167
|
+
"ref": {"scope": "project", "id": run_id, "revision": "r1"},
|
|
168
|
+
"run_id": run_id,
|
|
169
|
+
"task": TASK,
|
|
170
|
+
"target": dict(target)
|
|
171
|
+
if target is not None
|
|
172
|
+
else {"project_id": project_id, "kind": "project"},
|
|
173
|
+
"state": "complete",
|
|
174
|
+
"freshness": "current",
|
|
175
|
+
"convergence": "not_applicable",
|
|
176
|
+
"provenance": _provenance(project_id, run_id, tools, provenance),
|
|
177
|
+
"datasets": datasets,
|
|
178
|
+
"views": views,
|
|
179
|
+
"measurements": [],
|
|
180
|
+
"findings": findings,
|
|
181
|
+
"artifacts": [],
|
|
182
|
+
"logs": [],
|
|
183
|
+
"gaps": [],
|
|
184
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""KLayout DRC v1 normalizer: v0 parse output to a ResultBundle.
|
|
2
|
+
|
|
3
|
+
Maps the ``run_drc`` klayout parse shape (``violations[]`` with
|
|
4
|
+
rule, message, and optional metric ``bbox_um``) onto a typed rule
|
|
5
|
+
table, a bound view, and findings. Pure function of the parsed
|
|
6
|
+
output: no I/O, no tool calls, deterministic ids derived from
|
|
7
|
+
the run id. Deck mode carries messages; synthetic mode carries
|
|
8
|
+
only rule plus bbox, so a missing message falls back to the rule
|
|
9
|
+
and its geometry (see ESCAPE-HARDEN).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
TASK = "run_drc"
|
|
13
|
+
|
|
14
|
+
_DIAG_CAP = 240
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _cap(text):
|
|
18
|
+
"""Cap one diagnostic string at the bundle prose limit.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
text: Raw diagnostic text, possibly unbounded tool prose.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
The text unchanged when short, else truncated with an
|
|
25
|
+
ellipsis marker.
|
|
26
|
+
"""
|
|
27
|
+
text = text if isinstance(text, str) else str(text)
|
|
28
|
+
if len(text) <= _DIAG_CAP:
|
|
29
|
+
return text
|
|
30
|
+
return text[: _DIAG_CAP - 3] + "..."
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _bbox_text(bbox_um):
|
|
34
|
+
"""Render one metric bbox as comma-joined coordinates.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
bbox_um: Metric coordinate list, if any.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Comma-joined coordinate text, or an empty string when the
|
|
41
|
+
violation carries no geometry.
|
|
42
|
+
"""
|
|
43
|
+
if not bbox_um:
|
|
44
|
+
return ""
|
|
45
|
+
return ",".join(str(c) for c in bbox_um)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _message(violation):
|
|
49
|
+
"""Render one violation message, falling back to rule geometry.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
violation: v0 violation with rule, message, and bbox_um.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Capped message text naming the rule.
|
|
56
|
+
"""
|
|
57
|
+
message = violation.get("message")
|
|
58
|
+
if message:
|
|
59
|
+
return _cap(message)
|
|
60
|
+
rule = violation.get("rule", "drc")
|
|
61
|
+
bbox = _bbox_text(violation.get("bbox_um"))
|
|
62
|
+
return _cap(f"{rule} at [{bbox}] um")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _provenance(project_id, run_id, tools, provenance):
|
|
66
|
+
"""Build ResultProvenance, defaulting to a placeholder closure.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
project_id: Project the run belongs to.
|
|
70
|
+
run_id: Run the bundle describes.
|
|
71
|
+
tools: Tool ingredient mappings (id, version, digest?).
|
|
72
|
+
provenance: Full provenance mapping when the caller has
|
|
73
|
+
one; a deterministic placeholder otherwise.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Provenance mapping valid against the v1 schema.
|
|
77
|
+
"""
|
|
78
|
+
if provenance is not None:
|
|
79
|
+
return provenance
|
|
80
|
+
ref = {"scope": "project", "id": project_id, "revision": "r1"}
|
|
81
|
+
return {
|
|
82
|
+
"snapshot": dict(ref),
|
|
83
|
+
"input_lock": dict(ref),
|
|
84
|
+
"closure_hash": "0" * 64,
|
|
85
|
+
"tools": [
|
|
86
|
+
{
|
|
87
|
+
"id": item["id"],
|
|
88
|
+
"version": item.get("version", ""),
|
|
89
|
+
"digest": item.get("digest", "0" * 64),
|
|
90
|
+
}
|
|
91
|
+
for item in tools
|
|
92
|
+
],
|
|
93
|
+
"parents": [],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def normalize(parsed, *, run_id, project_id="project", target=None,
|
|
98
|
+
tools=(), provenance=None):
|
|
99
|
+
"""Normalize one klayout DRC parse output to a v1 ResultBundle.
|
|
100
|
+
|
|
101
|
+
Violations become a table dataset (rule, message, bbox) plus
|
|
102
|
+
a bound table view; every violation also becomes an error
|
|
103
|
+
finding with a capped message.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
parsed: v0 parse mapping with ``violations[]``.
|
|
107
|
+
run_id: Run the bundle describes (anchors all ids).
|
|
108
|
+
project_id: Project the run belongs to.
|
|
109
|
+
target: v1 target mapping; project kind by default.
|
|
110
|
+
tools: Tool ingredient mappings for provenance.
|
|
111
|
+
provenance: Full provenance mapping when the caller has
|
|
112
|
+
one; a deterministic placeholder otherwise.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Schema-valid ResultBundle mapping.
|
|
116
|
+
"""
|
|
117
|
+
violations = parsed.get("violations") or []
|
|
118
|
+
table_id = f"{run_id}:dataset:violations"
|
|
119
|
+
datasets = [
|
|
120
|
+
{
|
|
121
|
+
"id": table_id,
|
|
122
|
+
"kind": "table",
|
|
123
|
+
"row_count": len(violations),
|
|
124
|
+
"columns": [
|
|
125
|
+
{
|
|
126
|
+
"id": f"{table_id}:rule",
|
|
127
|
+
"name": "rule",
|
|
128
|
+
"dtype": "utf8",
|
|
129
|
+
"unit": "1",
|
|
130
|
+
"data": [v.get("rule", "") for v in violations],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": f"{table_id}:message",
|
|
134
|
+
"name": "message",
|
|
135
|
+
"dtype": "utf8",
|
|
136
|
+
"unit": "1",
|
|
137
|
+
"data": [_message(v) for v in violations],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"id": f"{table_id}:bbox",
|
|
141
|
+
"name": "bbox",
|
|
142
|
+
"dtype": "utf8",
|
|
143
|
+
"unit": "1",
|
|
144
|
+
"data": [
|
|
145
|
+
_bbox_text(v.get("bbox_um")) for v in violations
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
"conditions": [],
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
views = [
|
|
153
|
+
{
|
|
154
|
+
"id": f"{run_id}:view:violations",
|
|
155
|
+
"kind": "table",
|
|
156
|
+
"dataset_id": table_id,
|
|
157
|
+
"name": "drc violations",
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
findings = [
|
|
161
|
+
{
|
|
162
|
+
"id": f"{run_id}:finding:{index}",
|
|
163
|
+
"severity": "error",
|
|
164
|
+
"code": v.get("rule", "drc") or "drc",
|
|
165
|
+
"message": _message(v),
|
|
166
|
+
}
|
|
167
|
+
for index, v in enumerate(violations)
|
|
168
|
+
]
|
|
169
|
+
return {
|
|
170
|
+
"schema": "rocketeda/results/v1",
|
|
171
|
+
"ref": {"scope": "project", "id": run_id, "revision": "r1"},
|
|
172
|
+
"run_id": run_id,
|
|
173
|
+
"task": TASK,
|
|
174
|
+
"target": dict(target)
|
|
175
|
+
if target is not None
|
|
176
|
+
else {"project_id": project_id, "kind": "project"},
|
|
177
|
+
"state": "complete",
|
|
178
|
+
"freshness": "current",
|
|
179
|
+
"convergence": "not_applicable",
|
|
180
|
+
"provenance": _provenance(project_id, run_id, tools, provenance),
|
|
181
|
+
"datasets": datasets,
|
|
182
|
+
"views": views,
|
|
183
|
+
"measurements": [],
|
|
184
|
+
"findings": findings,
|
|
185
|
+
"artifacts": [],
|
|
186
|
+
"logs": [],
|
|
187
|
+
"gaps": [],
|
|
188
|
+
}
|