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,181 @@
|
|
|
1
|
+
"""Gdsfactory v1 normalizer: v0 parse output to a ResultBundle.
|
|
2
|
+
|
|
3
|
+
Maps the ``generate_views`` parse shape (``measurements[]`` with
|
|
4
|
+
per-cell polygon/port/area counts plus ``mappings[]`` and
|
|
5
|
+
``violations[]``) onto a typed cell mappings table, a bound
|
|
6
|
+
view, PASS measurements, and findings. Pure function of the
|
|
7
|
+
parsed output: no I/O, no tool calls, deterministic ids derived
|
|
8
|
+
from the run id. Artifact paths stay in the table as evidence;
|
|
9
|
+
no bundle artifact is fabricated without a recorded digest.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
TASK = "generate_views"
|
|
13
|
+
|
|
14
|
+
_DIAG_CAP = 240
|
|
15
|
+
|
|
16
|
+
_SEVERITY = {"error": "error", "warning": "warning"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _cap(text):
|
|
20
|
+
"""Cap one diagnostic string at the bundle prose limit.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
text: Raw diagnostic text, possibly unbounded tool prose.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The text unchanged when short, else truncated with an
|
|
27
|
+
ellipsis marker.
|
|
28
|
+
"""
|
|
29
|
+
text = text if isinstance(text, str) else str(text)
|
|
30
|
+
if len(text) <= _DIAG_CAP:
|
|
31
|
+
return text
|
|
32
|
+
return text[: _DIAG_CAP - 3] + "..."
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _quantity(value, unit):
|
|
36
|
+
"""Build one v1 Quantity, mapping unitless to dimensionless.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
value: Numeric value.
|
|
40
|
+
unit: Unit string, possibly empty for unitless measures.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Quantity mapping with an explicit unit.
|
|
44
|
+
"""
|
|
45
|
+
return {"value": float(value), "unit": unit if unit else "1"}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _provenance(project_id, run_id, tools, provenance):
|
|
49
|
+
"""Build ResultProvenance, defaulting to a placeholder closure.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
project_id: Project the run belongs to.
|
|
53
|
+
run_id: Run the bundle describes.
|
|
54
|
+
tools: Tool ingredient mappings (id, version, digest?).
|
|
55
|
+
provenance: Full provenance mapping when the caller has
|
|
56
|
+
one; a deterministic placeholder otherwise.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Provenance mapping valid against the v1 schema.
|
|
60
|
+
"""
|
|
61
|
+
if provenance is not None:
|
|
62
|
+
return provenance
|
|
63
|
+
ref = {"scope": "project", "id": project_id, "revision": "r1"}
|
|
64
|
+
return {
|
|
65
|
+
"snapshot": dict(ref),
|
|
66
|
+
"input_lock": dict(ref),
|
|
67
|
+
"closure_hash": "0" * 64,
|
|
68
|
+
"tools": [
|
|
69
|
+
{
|
|
70
|
+
"id": item["id"],
|
|
71
|
+
"version": item.get("version", ""),
|
|
72
|
+
"digest": item.get("digest", "0" * 64),
|
|
73
|
+
}
|
|
74
|
+
for item in tools
|
|
75
|
+
],
|
|
76
|
+
"parents": [],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def normalize(parsed, *, run_id, project_id="project", target=None,
|
|
81
|
+
tools=(), provenance=None):
|
|
82
|
+
"""Normalize one gdsfactory views parse output to a ResultBundle.
|
|
83
|
+
|
|
84
|
+
Cell mappings become a table dataset (component, ports,
|
|
85
|
+
polygons, bbox area) plus a bound table view; per-cell
|
|
86
|
+
counts ride PASS measurements; violations become findings
|
|
87
|
+
with capped messages.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
parsed: v0 parse mapping with ``measurements[]``,
|
|
91
|
+
``mappings[]``, and ``violations[]``.
|
|
92
|
+
run_id: Run the bundle describes (anchors all ids).
|
|
93
|
+
project_id: Project the run belongs to.
|
|
94
|
+
target: v1 target mapping; project kind by default.
|
|
95
|
+
tools: Tool ingredient mappings for provenance.
|
|
96
|
+
provenance: Full provenance mapping when the caller has
|
|
97
|
+
one; a deterministic placeholder otherwise.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Schema-valid ResultBundle mapping.
|
|
101
|
+
"""
|
|
102
|
+
measurements_in = parsed.get("measurements") or []
|
|
103
|
+
mappings = parsed.get("mappings") or []
|
|
104
|
+
violations = parsed.get("violations") or []
|
|
105
|
+
table_id = f"{run_id}:dataset:cells"
|
|
106
|
+
datasets = [
|
|
107
|
+
{
|
|
108
|
+
"id": table_id,
|
|
109
|
+
"kind": "table",
|
|
110
|
+
"row_count": len(mappings),
|
|
111
|
+
"columns": [
|
|
112
|
+
{
|
|
113
|
+
"id": f"{table_id}:component",
|
|
114
|
+
"name": "component",
|
|
115
|
+
"dtype": "utf8",
|
|
116
|
+
"unit": "1",
|
|
117
|
+
"data": [m.get("component", "") for m in mappings],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": f"{table_id}:ports",
|
|
121
|
+
"name": "ports",
|
|
122
|
+
"dtype": "float64",
|
|
123
|
+
"unit": "1",
|
|
124
|
+
"data": [float(len(m.get("ports") or [])) for m in mappings],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": f"{table_id}:polygons",
|
|
128
|
+
"name": "polygons",
|
|
129
|
+
"dtype": "float64",
|
|
130
|
+
"unit": "1",
|
|
131
|
+
"data": [float(m.get("n_polygons", 0)) for m in mappings],
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
"conditions": [],
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
views = [
|
|
138
|
+
{
|
|
139
|
+
"id": f"{run_id}:view:cells",
|
|
140
|
+
"kind": "table",
|
|
141
|
+
"dataset_id": table_id,
|
|
142
|
+
"name": "cell mappings",
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
measurements = [
|
|
146
|
+
{
|
|
147
|
+
"name": item["name"],
|
|
148
|
+
"value": _quantity(item["value"], item.get("unit", "")),
|
|
149
|
+
"status": "PASS",
|
|
150
|
+
}
|
|
151
|
+
for item in measurements_in
|
|
152
|
+
]
|
|
153
|
+
findings = [
|
|
154
|
+
{
|
|
155
|
+
"id": f"{run_id}:finding:{index}",
|
|
156
|
+
"severity": _SEVERITY.get(v.get("severity"), "info"),
|
|
157
|
+
"code": v.get("rule", "views.failed"),
|
|
158
|
+
"message": _cap(v.get("message", "")),
|
|
159
|
+
}
|
|
160
|
+
for index, v in enumerate(violations)
|
|
161
|
+
]
|
|
162
|
+
return {
|
|
163
|
+
"schema": "rocketeda/results/v1",
|
|
164
|
+
"ref": {"scope": "project", "id": run_id, "revision": "r1"},
|
|
165
|
+
"run_id": run_id,
|
|
166
|
+
"task": TASK,
|
|
167
|
+
"target": dict(target)
|
|
168
|
+
if target is not None
|
|
169
|
+
else {"project_id": project_id, "kind": "project"},
|
|
170
|
+
"state": "complete",
|
|
171
|
+
"freshness": "current",
|
|
172
|
+
"convergence": "not_applicable",
|
|
173
|
+
"provenance": _provenance(project_id, run_id, tools, provenance),
|
|
174
|
+
"datasets": datasets,
|
|
175
|
+
"views": views,
|
|
176
|
+
"measurements": measurements,
|
|
177
|
+
"findings": findings,
|
|
178
|
+
"artifacts": [],
|
|
179
|
+
"logs": [],
|
|
180
|
+
"gaps": [],
|
|
181
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Icarus HDL simulation adapter
|
|
2
|
+
|
|
3
|
+
HdlSimAdapter serves run_hdl_test. It compiles project testbench and design sources with iverilog, runs them with vvp under a timeout, and parses compile diagnostics, FATAL, and ERROR output into normalized findings.
|
|
4
|
+
|
|
5
|
+
The adapter is registered in Engine as the icarus solver. Tool resolution uses PATH or an explicit job tool_dir. When a BwrapRunner is supplied, compile and simulation processes run inside the configured isolation boundary.
|
|
6
|
+
|
|
7
|
+
Some HDL failures can exit with status zero. Verdicts come from parsed diagnostics and simulation output, never the exit code alone.
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"""Icarus HDL sim solver adapter: run_hdl_test behind the ToolAdapter port.
|
|
2
|
+
|
|
3
|
+
Protocol, verified against iverilog 14.0 + vvp: compile the sources
|
|
4
|
+
(exit 2 + `file:line: msg` diagnostics on failure), run the sim under
|
|
5
|
+
a timeout, and parse `FATAL:`/`ERROR:` lines for the verdict. `$error`
|
|
6
|
+
exits 0, so the verdict always comes from parsed output, never from
|
|
7
|
+
the exit code alone. Hangs raise instead of hanging the engine.
|
|
8
|
+
|
|
9
|
+
Tool resolution: an explicit job `tool_dir` wins, else PATH. Where
|
|
10
|
+
the tools live on disk is B10 integration scope — this adapter only
|
|
11
|
+
requires them resolvable, and refuses loudly when they are not.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
import subprocess
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from ...execution.errors import ExecutionError
|
|
20
|
+
from ...execution.runner import tool_roots
|
|
21
|
+
from ...interfaces import ToolAdapter
|
|
22
|
+
from ..errors import AdapterError
|
|
23
|
+
|
|
24
|
+
OPERATION = "run_hdl_test"
|
|
25
|
+
_TIMEOUT_S = 120
|
|
26
|
+
_LOG_NAME = "sim.log"
|
|
27
|
+
|
|
28
|
+
_COMPILE_RE = re.compile(r"^([^:\s][^:]*):(\d+):\s*(.+)$", re.M)
|
|
29
|
+
_SIM_RE = re.compile(r"^(FATAL|ERROR):\s*(\S+):(\d+):\s*(.*)$")
|
|
30
|
+
_TIME_RE = re.compile(r"Time:\s*(\d+)")
|
|
31
|
+
# Verilog `define names, with an optional =value (no whitespace or
|
|
32
|
+
# shell metacharacters; argv is never shelled, belt and suspenders).
|
|
33
|
+
_DEFINE_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(=[A-Za-z0-9_.+-]+)?")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _parse_compile(text):
|
|
37
|
+
violations = []
|
|
38
|
+
for match in _COMPILE_RE.finditer(text):
|
|
39
|
+
file, line, message = match.group(1), match.group(2), match.group(3)
|
|
40
|
+
warning = message.lower().startswith("warning")
|
|
41
|
+
violations.append(
|
|
42
|
+
{
|
|
43
|
+
"rule": "compile",
|
|
44
|
+
"severity": "warning" if warning else "error",
|
|
45
|
+
"message": message,
|
|
46
|
+
"file": file,
|
|
47
|
+
"line": int(line),
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
return violations
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _block_text(block):
|
|
54
|
+
"""(text, source) for one compile/run block, log-first.
|
|
55
|
+
|
|
56
|
+
MIGRATE-STDIO-FILE: the `vvp -l sim.log` file is the parse
|
|
57
|
+
channel; stdio survives only as a loud fallback for natives
|
|
58
|
+
recorded before the file channel existed.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
block: Native block with optional `log` plus
|
|
62
|
+
stdout/stderr.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
(text, source) with source `log` or `stdio_fallback`.
|
|
66
|
+
"""
|
|
67
|
+
log = (block or {}).get("log") or ""
|
|
68
|
+
if log.strip():
|
|
69
|
+
return log, "log"
|
|
70
|
+
return (
|
|
71
|
+
(block or {}).get("stdout", "") + "\n" + (block or {}).get("stderr", ""),
|
|
72
|
+
"stdio_fallback",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _parse_sim(text):
|
|
77
|
+
violations = []
|
|
78
|
+
lines = text.splitlines()
|
|
79
|
+
for index, line in enumerate(lines):
|
|
80
|
+
match = _SIM_RE.match(line)
|
|
81
|
+
if match is None:
|
|
82
|
+
continue
|
|
83
|
+
kind, file, lineno, message = match.groups()
|
|
84
|
+
violation = {
|
|
85
|
+
"rule": "assert_fatal" if kind == "FATAL" else "assert_error",
|
|
86
|
+
"severity": "error",
|
|
87
|
+
"message": message,
|
|
88
|
+
"file": file,
|
|
89
|
+
"line": int(lineno),
|
|
90
|
+
}
|
|
91
|
+
if index + 1 < len(lines):
|
|
92
|
+
timed = _TIME_RE.search(lines[index + 1])
|
|
93
|
+
if timed:
|
|
94
|
+
violation["sim_time"] = int(timed.group(1))
|
|
95
|
+
violations.append(violation)
|
|
96
|
+
return violations
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class HdlSimAdapter(ToolAdapter):
|
|
100
|
+
"""run_hdl_test via Icarus: compile plus timed Verilog sim.
|
|
101
|
+
|
|
102
|
+
Serves the run_hdl_test operation with deck.tool `icarus` (the
|
|
103
|
+
only tool). Compiles the sources with iverilog, runs the sim
|
|
104
|
+
under vvp with a timeout, and parses compile diagnostics plus
|
|
105
|
+
FATAL/ERROR assertion lines.
|
|
106
|
+
|
|
107
|
+
Job keys:
|
|
108
|
+
sources (required): Verilog source paths, bench included.
|
|
109
|
+
defines: Verilog `define list, optional =value each.
|
|
110
|
+
timeout_s: Sim budget in seconds (default 120).
|
|
111
|
+
tool_dir: Directory holding iverilog/vvp; PATH otherwise.
|
|
112
|
+
deck: Optional mapping; deck.tool must be icarus here.
|
|
113
|
+
workdir: Run directory (default ".").
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
runner: Optional isolated runner; direct subprocess when None.
|
|
117
|
+
|
|
118
|
+
Caution: $error exits 0 and hangs raise instead of hanging the
|
|
119
|
+
engine — the verdict always comes from parsed output, never
|
|
120
|
+
the exit code alone.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self, runner=None):
|
|
124
|
+
self._runner = runner
|
|
125
|
+
|
|
126
|
+
def capabilities(self, target):
|
|
127
|
+
"""Report the served operation, tool, sim, and defines.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
target: Target the capabilities are reported for
|
|
131
|
+
(accepted, unused — one bench per job).
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Capability mapping with operations, tool, sim, and
|
|
135
|
+
defines.
|
|
136
|
+
"""
|
|
137
|
+
return {"operations": [OPERATION], "tool": "icarus", "sim": "vvp", "defines": True}
|
|
138
|
+
|
|
139
|
+
def preflight(self, job):
|
|
140
|
+
"""Check operation, timeout, sources, defines, and tools.
|
|
141
|
+
|
|
142
|
+
Cheap deterministic checks first, tool resolution last.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
job: Solver job with operation run_hdl_test plus
|
|
146
|
+
sources, defines, and timeout inputs.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Checked mapping with resolved iverilog/vvp paths,
|
|
150
|
+
sources, timeout_s, and defines.
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
AdapterError: unsupported_operation for another
|
|
154
|
+
operation, invalid_command for a bad timeout or
|
|
155
|
+
defines, invalid_reference for missing sources,
|
|
156
|
+
missing_dependency for unresolvable tools.
|
|
157
|
+
"""
|
|
158
|
+
# Cheap deterministic checks first: operation, timeout, sources.
|
|
159
|
+
# Tool resolution last, since it depends on the machine.
|
|
160
|
+
if job.get("operation") != OPERATION:
|
|
161
|
+
raise AdapterError(
|
|
162
|
+
"unsupported_operation",
|
|
163
|
+
f"HDL sim handles {OPERATION}, got {job.get('operation')}.",
|
|
164
|
+
)
|
|
165
|
+
timeout = job.get("timeout_s", _TIMEOUT_S)
|
|
166
|
+
if (
|
|
167
|
+
not isinstance(timeout, (int, float))
|
|
168
|
+
or isinstance(timeout, bool)
|
|
169
|
+
or not timeout > 0
|
|
170
|
+
):
|
|
171
|
+
raise AdapterError("invalid_command", f"Bad timeout_s: {timeout!r}.")
|
|
172
|
+
sources = job.get("sources") or []
|
|
173
|
+
missing = [s for s in sources if not Path(s).is_file()]
|
|
174
|
+
if not sources or missing:
|
|
175
|
+
raise AdapterError(
|
|
176
|
+
"invalid_reference", f"Sources not found: {missing or sources}."
|
|
177
|
+
)
|
|
178
|
+
defines = job.get("defines") or []
|
|
179
|
+
if not isinstance(defines, list) or any(
|
|
180
|
+
not isinstance(d, str) or not _DEFINE_RE.fullmatch(d) for d in defines
|
|
181
|
+
):
|
|
182
|
+
raise AdapterError("invalid_command", f"Bad defines: {defines!r}.")
|
|
183
|
+
checked = {
|
|
184
|
+
"iverilog": self._resolve(job, "iverilog"),
|
|
185
|
+
"vvp": self._resolve(job, "vvp"),
|
|
186
|
+
"sources": list(sources),
|
|
187
|
+
"timeout_s": timeout,
|
|
188
|
+
"defines": list(defines),
|
|
189
|
+
}
|
|
190
|
+
if self._runner is not None:
|
|
191
|
+
try:
|
|
192
|
+
self._runner.preflight()
|
|
193
|
+
except ExecutionError as exc:
|
|
194
|
+
raise AdapterError(exc.code, str(exc)) from exc
|
|
195
|
+
return checked
|
|
196
|
+
|
|
197
|
+
def execute(self, job):
|
|
198
|
+
"""Compile with iverilog, then run the sim under vvp.
|
|
199
|
+
|
|
200
|
+
A failed compile returns its block with no run; the verdict
|
|
201
|
+
still comes from parse, never the exit code alone. The sim
|
|
202
|
+
runs with `vvp -l sim.log` so assertion lines land in a
|
|
203
|
+
file first (MIGRATE-STDIO-FILE; live-verified
|
|
204
|
+
byte-identical to stdio on vvp 14.0).
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
job: Solver job with operation run_hdl_test plus
|
|
208
|
+
sources, defines, and timeout inputs.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Native output with exit_code plus compile and run
|
|
212
|
+
blocks (run is None when the build failed; the
|
|
213
|
+
run block carries the vvp log text under `log`).
|
|
214
|
+
|
|
215
|
+
Raises:
|
|
216
|
+
AdapterError: invalid_reference for a missing workdir,
|
|
217
|
+
runner_unavailable on a sim timeout, plus the
|
|
218
|
+
preflight codes (execute re-checks first).
|
|
219
|
+
"""
|
|
220
|
+
checked = self.preflight(job)
|
|
221
|
+
workdir = job.get("workdir", ".")
|
|
222
|
+
if not Path(workdir).is_dir():
|
|
223
|
+
raise AdapterError(
|
|
224
|
+
"invalid_reference", f"Workdir not found: {workdir}."
|
|
225
|
+
)
|
|
226
|
+
roots = tool_roots([checked["iverilog"], checked["vvp"]])
|
|
227
|
+
build = self._run(
|
|
228
|
+
[
|
|
229
|
+
checked["iverilog"],
|
|
230
|
+
"-o",
|
|
231
|
+
"sim.vvp",
|
|
232
|
+
*[f"-D{d}" for d in checked["defines"]],
|
|
233
|
+
*checked["sources"],
|
|
234
|
+
],
|
|
235
|
+
workdir,
|
|
236
|
+
checked["timeout_s"],
|
|
237
|
+
roots,
|
|
238
|
+
)
|
|
239
|
+
if build.returncode != 0:
|
|
240
|
+
return {
|
|
241
|
+
"exit_code": build.returncode,
|
|
242
|
+
"compile": _block(build),
|
|
243
|
+
"run": None,
|
|
244
|
+
}
|
|
245
|
+
run = self._run(
|
|
246
|
+
[checked["vvp"], "-l", _LOG_NAME, "sim.vvp"],
|
|
247
|
+
workdir,
|
|
248
|
+
checked["timeout_s"],
|
|
249
|
+
roots,
|
|
250
|
+
)
|
|
251
|
+
log_path = Path(workdir) / _LOG_NAME
|
|
252
|
+
run_block = _block(run)
|
|
253
|
+
run_block["log"] = (
|
|
254
|
+
log_path.read_text(encoding="utf-8", errors="replace")
|
|
255
|
+
if log_path.is_file()
|
|
256
|
+
else ""
|
|
257
|
+
)
|
|
258
|
+
return {
|
|
259
|
+
"exit_code": run.returncode,
|
|
260
|
+
"compile": _block(build),
|
|
261
|
+
"run": run_block,
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
def parse(self, native_output):
|
|
265
|
+
"""Parse compile diagnostics plus sim assertion lines.
|
|
266
|
+
|
|
267
|
+
Failed compiles and crashed sims with nothing parseable
|
|
268
|
+
synthesize error violations rather than going clean. The
|
|
269
|
+
decisive block reads from the vvp log file, falling back
|
|
270
|
+
to stdio only for older natives (loudly marked).
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
native_output: Native output with compile and run blocks.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
{total, violations[], source} with file/line
|
|
277
|
+
locations and sim_time where the sim reports it;
|
|
278
|
+
source is `log` or `stdio_fallback`.
|
|
279
|
+
|
|
280
|
+
Raises:
|
|
281
|
+
AdapterError: runner_unavailable when a compiled run
|
|
282
|
+
has no run block.
|
|
283
|
+
"""
|
|
284
|
+
compile_block = native_output.get("compile") or {}
|
|
285
|
+
compile_text, compile_source = _block_text(compile_block)
|
|
286
|
+
diags = _parse_compile(compile_text)
|
|
287
|
+
if compile_block.get("exit_code", 0) != 0:
|
|
288
|
+
if not diags:
|
|
289
|
+
# Defensive: a failed compile must never parse as clean.
|
|
290
|
+
diags = [
|
|
291
|
+
{
|
|
292
|
+
"rule": "compile",
|
|
293
|
+
"severity": "error",
|
|
294
|
+
"message": f"iverilog exited {compile_block.get('exit_code')}",
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
return {
|
|
298
|
+
"total": len(diags),
|
|
299
|
+
"violations": diags,
|
|
300
|
+
"source": compile_source,
|
|
301
|
+
}
|
|
302
|
+
run_block = native_output.get("run")
|
|
303
|
+
if run_block is None:
|
|
304
|
+
raise AdapterError(
|
|
305
|
+
"runner_unavailable", "Native output has no run block."
|
|
306
|
+
)
|
|
307
|
+
run_text, run_source = _block_text(run_block)
|
|
308
|
+
sim = _parse_sim(run_text)
|
|
309
|
+
if run_block.get("exit_code", 0) != 0 and not sim:
|
|
310
|
+
# Defensive: crashes print nothing parseable; the nonzero
|
|
311
|
+
# exit is the only signal, and it must fail loudly.
|
|
312
|
+
sim = [
|
|
313
|
+
{
|
|
314
|
+
"rule": "sim_fail",
|
|
315
|
+
"severity": "error",
|
|
316
|
+
"message": f"vvp exited {run_block.get('exit_code')}",
|
|
317
|
+
}
|
|
318
|
+
]
|
|
319
|
+
violations = diags + sim
|
|
320
|
+
return {
|
|
321
|
+
"total": len(violations),
|
|
322
|
+
"violations": violations,
|
|
323
|
+
"source": run_source,
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
@staticmethod
|
|
327
|
+
def _resolve(job, binary):
|
|
328
|
+
tool_dir = job.get("tool_dir")
|
|
329
|
+
if tool_dir:
|
|
330
|
+
candidate = Path(tool_dir) / binary
|
|
331
|
+
if candidate.is_file():
|
|
332
|
+
return str(candidate)
|
|
333
|
+
raise AdapterError(
|
|
334
|
+
"missing_dependency", f"{binary} not found in {tool_dir}."
|
|
335
|
+
)
|
|
336
|
+
found = shutil.which(binary)
|
|
337
|
+
if found is None:
|
|
338
|
+
raise AdapterError(
|
|
339
|
+
"missing_dependency",
|
|
340
|
+
f"{binary} not on PATH (pass tool_dir or integrate via B10).",
|
|
341
|
+
)
|
|
342
|
+
return found
|
|
343
|
+
|
|
344
|
+
def _run(self, argv, workdir, timeout_s, extra_binds=()):
|
|
345
|
+
if self._runner is not None:
|
|
346
|
+
try:
|
|
347
|
+
return self._runner.run(argv, workdir, timeout_s, extra_binds)
|
|
348
|
+
except ExecutionError as exc:
|
|
349
|
+
raise AdapterError(exc.code, str(exc)) from exc
|
|
350
|
+
try:
|
|
351
|
+
return subprocess.run(
|
|
352
|
+
argv,
|
|
353
|
+
capture_output=True,
|
|
354
|
+
text=True,
|
|
355
|
+
timeout=timeout_s,
|
|
356
|
+
cwd=workdir,
|
|
357
|
+
)
|
|
358
|
+
except subprocess.TimeoutExpired as exc:
|
|
359
|
+
raise AdapterError(
|
|
360
|
+
"runner_unavailable", f"sim timed out after {timeout_s}s."
|
|
361
|
+
) from exc
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _block(proc):
|
|
365
|
+
return {
|
|
366
|
+
"exit_code": proc.returncode,
|
|
367
|
+
"stdout": proc.stdout,
|
|
368
|
+
"stderr": proc.stderr,
|
|
369
|
+
}
|