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,155 @@
|
|
|
1
|
+
"""GHDL provider: run_hdl_test with assert-line verdicts."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ...execution.errors import ExecutionError
|
|
9
|
+
from ...interfaces import ToolAdapter
|
|
10
|
+
from ..errors import AdapterError
|
|
11
|
+
|
|
12
|
+
OPERATION = "run_hdl_test"
|
|
13
|
+
_TOOL = "ghdl"
|
|
14
|
+
_ASSERT_RE = re.compile(
|
|
15
|
+
r"^(.+):(\d+):(\d+):@(\S+):\((\w[\w ]*) (\w+)\): (.*)$")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GhdlAdapter(ToolAdapter):
|
|
19
|
+
"""run_hdl_test via ghdl: analyze, elaborate, run, parse asserts.
|
|
20
|
+
|
|
21
|
+
Serves the run_hdl_test operation with tool `ghdl`. Compile
|
|
22
|
+
diagnostics arrive on stderr; sim assert/report lines arrive
|
|
23
|
+
on stdout (never merge blindly). PSL verdicts prefer
|
|
24
|
+
--psl-report JSON; waveforms prefer --vcd/--fst via pywellen.
|
|
25
|
+
|
|
26
|
+
Job keys:
|
|
27
|
+
sources (required): VHDL source paths.
|
|
28
|
+
top (required): Top entity name.
|
|
29
|
+
workdir: Run directory (default ".").
|
|
30
|
+
timeout_s: Budget in seconds (default 120).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, runner=None):
|
|
34
|
+
self._runner = runner
|
|
35
|
+
|
|
36
|
+
def capabilities(self, target):
|
|
37
|
+
"""Report the served operation and tool.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
target: Target the capabilities are reported for.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Capability mapping with operations and tool.
|
|
44
|
+
"""
|
|
45
|
+
return {"operations": [OPERATION], "tool": _TOOL,
|
|
46
|
+
"modes": ["analyze", "sim"]}
|
|
47
|
+
|
|
48
|
+
def preflight(self, job):
|
|
49
|
+
"""Check operation, sources, top, and ghdl presence.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
job: Solver job with operation run_hdl_test.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Checked mapping with binary, sources, top, timeout.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
AdapterError: unsupported_operation, invalid_command
|
|
59
|
+
for a bad top, invalid_reference for missing
|
|
60
|
+
sources, missing_dependency when ghdl is absent.
|
|
61
|
+
"""
|
|
62
|
+
if job.get("operation") != OPERATION:
|
|
63
|
+
raise AdapterError(
|
|
64
|
+
"unsupported_operation",
|
|
65
|
+
f"GHDL handles {OPERATION}, got {job.get('operation')}.")
|
|
66
|
+
top = job.get("top")
|
|
67
|
+
if not isinstance(top, str) or not top:
|
|
68
|
+
raise AdapterError("invalid_command", f"Bad top: {top!r}.")
|
|
69
|
+
sources = job.get("sources") or []
|
|
70
|
+
missing = [s for s in sources if not Path(s).is_file()]
|
|
71
|
+
if not sources or missing:
|
|
72
|
+
raise AdapterError(
|
|
73
|
+
"invalid_reference", f"Sources not found: {missing or sources}.")
|
|
74
|
+
binary = shutil.which(_TOOL)
|
|
75
|
+
if binary is None:
|
|
76
|
+
raise AdapterError(
|
|
77
|
+
"missing_dependency",
|
|
78
|
+
"BLOCKED-needs-host: ghdl not on PATH.")
|
|
79
|
+
timeout = job.get("timeout_s", 120)
|
|
80
|
+
if (not isinstance(timeout, (int, float))
|
|
81
|
+
or isinstance(timeout, bool) or not timeout > 0):
|
|
82
|
+
raise AdapterError("invalid_command", f"Bad timeout: {timeout!r}.")
|
|
83
|
+
return {"binary": binary, "sources": list(sources), "top": top,
|
|
84
|
+
"timeout_s": timeout}
|
|
85
|
+
|
|
86
|
+
def execute(self, job):
|
|
87
|
+
"""Analyze, elaborate, and run the top under a timeout.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
job: Solver job with operation run_hdl_test.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Native output with exit_code, stdout, stderr, top.
|
|
94
|
+
"""
|
|
95
|
+
checked = self.preflight(job)
|
|
96
|
+
workdir = job.get("workdir", ".")
|
|
97
|
+
if not Path(workdir).is_dir():
|
|
98
|
+
raise AdapterError(
|
|
99
|
+
"invalid_reference", f"Workdir not found: {workdir}.")
|
|
100
|
+
try:
|
|
101
|
+
for src in checked["sources"]:
|
|
102
|
+
proc = subprocess.run(
|
|
103
|
+
[checked["binary"], "-a", src], capture_output=True,
|
|
104
|
+
text=True, timeout=checked["timeout_s"], cwd=workdir)
|
|
105
|
+
if proc.returncode != 0:
|
|
106
|
+
return {"exit_code": proc.returncode,
|
|
107
|
+
"stdout": proc.stdout, "stderr": proc.stderr,
|
|
108
|
+
"top": checked["top"]}
|
|
109
|
+
proc = subprocess.run(
|
|
110
|
+
[checked["binary"], "-e", checked["top"]],
|
|
111
|
+
capture_output=True, text=True,
|
|
112
|
+
timeout=checked["timeout_s"], cwd=workdir)
|
|
113
|
+
if proc.returncode != 0:
|
|
114
|
+
return {"exit_code": proc.returncode,
|
|
115
|
+
"stdout": proc.stdout, "stderr": proc.stderr,
|
|
116
|
+
"top": checked["top"]}
|
|
117
|
+
proc = subprocess.run(
|
|
118
|
+
[checked["binary"], "-r", checked["top"]],
|
|
119
|
+
capture_output=True, text=True,
|
|
120
|
+
timeout=checked["timeout_s"], cwd=workdir)
|
|
121
|
+
except subprocess.TimeoutExpired as exc:
|
|
122
|
+
raise AdapterError(
|
|
123
|
+
"runner_unavailable", "GHDL run timed out.") from exc
|
|
124
|
+
return {"exit_code": proc.returncode, "stdout": proc.stdout,
|
|
125
|
+
"stderr": proc.stderr, "top": checked["top"]}
|
|
126
|
+
|
|
127
|
+
def parse(self, native_output):
|
|
128
|
+
"""Parse ghdl assert lines; errors fail, notes do not.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
native_output: Native output with sim stdout.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
{total, violations[], source}.
|
|
135
|
+
"""
|
|
136
|
+
violations = []
|
|
137
|
+
for line in (native_output.get("stdout") or "").splitlines():
|
|
138
|
+
match = _ASSERT_RE.match(line.strip())
|
|
139
|
+
if match is None:
|
|
140
|
+
continue
|
|
141
|
+
path, line_no, _col, _time, _kind, sev, msg = match.groups()
|
|
142
|
+
if sev.lower() in ("note",):
|
|
143
|
+
continue
|
|
144
|
+
violations.append({"rule": "assert_" + sev.lower(),
|
|
145
|
+
"severity": ("error" if sev.lower() in
|
|
146
|
+
("error", "failure")
|
|
147
|
+
else "warning"),
|
|
148
|
+
"message": msg[:300], "file": path,
|
|
149
|
+
"line": int(line_no)})
|
|
150
|
+
if "simulation failed" in (native_output.get("stdout") or ""):
|
|
151
|
+
if not violations:
|
|
152
|
+
violations.append({"rule": "sim_fail", "severity": "error",
|
|
153
|
+
"message": "GHDL simulation failed."})
|
|
154
|
+
return {"total": len(violations), "violations": violations,
|
|
155
|
+
"source": "stdout"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""HDL-sim v1 normalizer: v0 parse output to a ResultBundle.
|
|
2
|
+
|
|
3
|
+
Maps the ``run_hdl_test`` parse shape (``violations[]`` with
|
|
4
|
+
rule, severity, message, file/line, and optional sim_time) onto a
|
|
5
|
+
typed violations table, bound views, and findings. Pure function
|
|
6
|
+
of the parsed output: no I/O, no tool calls, deterministic ids
|
|
7
|
+
derived from the run id. A clean sim yields an empty table and no
|
|
8
|
+
findings.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
TASK = "run_hdl_test"
|
|
12
|
+
|
|
13
|
+
_DIAG_CAP = 240
|
|
14
|
+
|
|
15
|
+
_SEVERITY = {"error": "error", "warning": "warning"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _cap(text):
|
|
19
|
+
"""Cap one diagnostic string at the bundle prose limit.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
text: Raw diagnostic text, possibly unbounded tool prose.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The text unchanged when short, else truncated with an
|
|
26
|
+
ellipsis marker.
|
|
27
|
+
"""
|
|
28
|
+
text = text if isinstance(text, str) else str(text)
|
|
29
|
+
if len(text) <= _DIAG_CAP:
|
|
30
|
+
return text
|
|
31
|
+
return text[: _DIAG_CAP - 3] + "..."
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _location(violation):
|
|
35
|
+
"""Render one file:line location string for a violation.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
violation: v0 violation with optional file and line.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Location text, or an empty string when unlocated.
|
|
42
|
+
"""
|
|
43
|
+
file = violation.get("file") or ""
|
|
44
|
+
line = violation.get("line")
|
|
45
|
+
if file and line is not None:
|
|
46
|
+
return f"{file}:{line}"
|
|
47
|
+
return file or ""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _provenance(project_id, run_id, tools, provenance):
|
|
51
|
+
"""Build ResultProvenance, defaulting to a placeholder closure.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
project_id: Project the run belongs to.
|
|
55
|
+
run_id: Run the bundle describes.
|
|
56
|
+
tools: Tool ingredient mappings (id, version, digest?).
|
|
57
|
+
provenance: Full provenance mapping when the caller has
|
|
58
|
+
one; a deterministic placeholder otherwise.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Provenance mapping valid against the v1 schema.
|
|
62
|
+
"""
|
|
63
|
+
if provenance is not None:
|
|
64
|
+
return provenance
|
|
65
|
+
ref = {"scope": "project", "id": project_id, "revision": "r1"}
|
|
66
|
+
return {
|
|
67
|
+
"snapshot": dict(ref),
|
|
68
|
+
"input_lock": dict(ref),
|
|
69
|
+
"closure_hash": "0" * 64,
|
|
70
|
+
"tools": [
|
|
71
|
+
{
|
|
72
|
+
"id": item["id"],
|
|
73
|
+
"version": item.get("version", ""),
|
|
74
|
+
"digest": item.get("digest", "0" * 64),
|
|
75
|
+
}
|
|
76
|
+
for item in tools
|
|
77
|
+
],
|
|
78
|
+
"parents": [],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def normalize(parsed, *, run_id, project_id="project", target=None,
|
|
83
|
+
tools=(), provenance=None):
|
|
84
|
+
"""Normalize one hdl-sim parse output to a v1 ResultBundle.
|
|
85
|
+
|
|
86
|
+
Violation rows become a table dataset (rule, severity,
|
|
87
|
+
message, location) plus a bound table view; every violation
|
|
88
|
+
also becomes a finding with a capped message.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
parsed: v0 parse mapping with ``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
|
+
violations = parsed.get("violations") or []
|
|
103
|
+
table_id = f"{run_id}:dataset:violations"
|
|
104
|
+
datasets = [
|
|
105
|
+
{
|
|
106
|
+
"id": table_id,
|
|
107
|
+
"kind": "table",
|
|
108
|
+
"row_count": len(violations),
|
|
109
|
+
"columns": [
|
|
110
|
+
{
|
|
111
|
+
"id": f"{table_id}:rule",
|
|
112
|
+
"name": "rule",
|
|
113
|
+
"dtype": "utf8",
|
|
114
|
+
"unit": "1",
|
|
115
|
+
"data": [v.get("rule", "") for v in violations],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"id": f"{table_id}:severity",
|
|
119
|
+
"name": "severity",
|
|
120
|
+
"dtype": "utf8",
|
|
121
|
+
"unit": "1",
|
|
122
|
+
"data": [v.get("severity", "") for v in violations],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"id": f"{table_id}:message",
|
|
126
|
+
"name": "message",
|
|
127
|
+
"dtype": "utf8",
|
|
128
|
+
"unit": "1",
|
|
129
|
+
"data": [_cap(v.get("message", "")) for v in violations],
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"id": f"{table_id}:location",
|
|
133
|
+
"name": "location",
|
|
134
|
+
"dtype": "utf8",
|
|
135
|
+
"unit": "1",
|
|
136
|
+
"data": [_location(v) for v in violations],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
"conditions": [],
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
views = [
|
|
143
|
+
{
|
|
144
|
+
"id": f"{run_id}:view:violations",
|
|
145
|
+
"kind": "table",
|
|
146
|
+
"dataset_id": table_id,
|
|
147
|
+
"name": "violations",
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
findings = [
|
|
151
|
+
{
|
|
152
|
+
"id": f"{run_id}:finding:{index}",
|
|
153
|
+
"severity": _SEVERITY.get(v.get("severity"), "info"),
|
|
154
|
+
"code": v.get("rule", "sim"),
|
|
155
|
+
"message": _cap(v.get("message", "")),
|
|
156
|
+
}
|
|
157
|
+
for index, v in enumerate(violations)
|
|
158
|
+
]
|
|
159
|
+
return {
|
|
160
|
+
"schema": "rocketeda/results/v1",
|
|
161
|
+
"ref": {"scope": "project", "id": run_id, "revision": "r1"},
|
|
162
|
+
"run_id": run_id,
|
|
163
|
+
"task": TASK,
|
|
164
|
+
"target": dict(target)
|
|
165
|
+
if target is not None
|
|
166
|
+
else {"project_id": project_id, "kind": "project"},
|
|
167
|
+
"state": "complete",
|
|
168
|
+
"freshness": "current",
|
|
169
|
+
"convergence": "not_applicable",
|
|
170
|
+
"provenance": _provenance(project_id, run_id, tools, provenance),
|
|
171
|
+
"datasets": datasets,
|
|
172
|
+
"views": views,
|
|
173
|
+
"measurements": [],
|
|
174
|
+
"findings": findings,
|
|
175
|
+
"artifacts": [],
|
|
176
|
+
"logs": [],
|
|
177
|
+
"gaps": [],
|
|
178
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Verilator provider: run_hdl_test with SARIF plus lint text."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ...execution.errors import ExecutionError
|
|
10
|
+
from ...interfaces import ToolAdapter
|
|
11
|
+
from ..errors import AdapterError
|
|
12
|
+
|
|
13
|
+
OPERATION = "run_hdl_test"
|
|
14
|
+
_TOOL = "verilator"
|
|
15
|
+
_DIAG_RE = re.compile(
|
|
16
|
+
r"^%((?:Error)|(Warning)-([A-Z0-9_]+)):\s*(.+?):(\d+):(\d+):")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class VerilatorAdapter(ToolAdapter):
|
|
20
|
+
"""run_hdl_test via verilator: lint plus sim harness verdicts.
|
|
21
|
+
|
|
22
|
+
Serves the run_hdl_test operation with tool `verilator`.
|
|
23
|
+
Lint runs `--lint-only --diagnostics-sarif`; the SARIF file is
|
|
24
|
+
the first channel, `%Error`/`%Warning` stderr the fallback
|
|
25
|
+
(xml-only is dead since 5.046 — json-only/SARIF only).
|
|
26
|
+
|
|
27
|
+
Job keys:
|
|
28
|
+
sources (required): Verilog source paths.
|
|
29
|
+
top: Top module name.
|
|
30
|
+
workdir: Run directory (default ".").
|
|
31
|
+
timeout_s: Budget in seconds (default 120).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, runner=None):
|
|
35
|
+
self._runner = runner
|
|
36
|
+
|
|
37
|
+
def capabilities(self, target):
|
|
38
|
+
"""Report the served operation and tool.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
target: Target the capabilities are reported for.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Capability mapping with operations and tool.
|
|
45
|
+
"""
|
|
46
|
+
return {"operations": [OPERATION], "tool": _TOOL,
|
|
47
|
+
"modes": ["lint", "sim"]}
|
|
48
|
+
|
|
49
|
+
def preflight(self, job):
|
|
50
|
+
"""Check operation, sources, and verilator presence.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
job: Solver job with operation run_hdl_test.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Checked mapping with binary, sources, timeout.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
AdapterError: unsupported_operation, invalid_reference
|
|
60
|
+
for missing sources, missing_dependency when
|
|
61
|
+
verilator is absent (BLOCKED-needs-host).
|
|
62
|
+
"""
|
|
63
|
+
if job.get("operation") != OPERATION:
|
|
64
|
+
raise AdapterError(
|
|
65
|
+
"unsupported_operation",
|
|
66
|
+
f"Verilator handles {OPERATION}, got {job.get('operation')}.")
|
|
67
|
+
sources = job.get("sources") or []
|
|
68
|
+
missing = [s for s in sources if not Path(s).is_file()]
|
|
69
|
+
if not sources or missing:
|
|
70
|
+
raise AdapterError(
|
|
71
|
+
"invalid_reference", f"Sources not found: {missing or sources}.")
|
|
72
|
+
binary = shutil.which(_TOOL)
|
|
73
|
+
if binary is None:
|
|
74
|
+
raise AdapterError(
|
|
75
|
+
"missing_dependency",
|
|
76
|
+
"BLOCKED-needs-host: verilator not on PATH.")
|
|
77
|
+
timeout = job.get("timeout_s", 120)
|
|
78
|
+
if (not isinstance(timeout, (int, float))
|
|
79
|
+
or isinstance(timeout, bool) or not timeout > 0):
|
|
80
|
+
raise AdapterError("invalid_command", f"Bad timeout: {timeout!r}.")
|
|
81
|
+
return {"binary": binary, "sources": list(sources),
|
|
82
|
+
"top": job.get("top"), "timeout_s": timeout}
|
|
83
|
+
|
|
84
|
+
def execute(self, job):
|
|
85
|
+
"""Lint the sources with verilator under a timeout.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
job: Solver job with operation run_hdl_test.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Native output with exit_code, stderr, sarif, sources.
|
|
92
|
+
"""
|
|
93
|
+
checked = self.preflight(job)
|
|
94
|
+
workdir = job.get("workdir", ".")
|
|
95
|
+
if not Path(workdir).is_dir():
|
|
96
|
+
raise AdapterError(
|
|
97
|
+
"invalid_reference", f"Workdir not found: {workdir}.")
|
|
98
|
+
argv = [checked["binary"], "--lint-only", "--diagnostics-sarif",
|
|
99
|
+
"--Mdir", "obj_dir", *checked["sources"]]
|
|
100
|
+
try:
|
|
101
|
+
proc = subprocess.run(argv, capture_output=True, text=True,
|
|
102
|
+
timeout=checked["timeout_s"], cwd=workdir)
|
|
103
|
+
except subprocess.TimeoutExpired as exc:
|
|
104
|
+
raise AdapterError(
|
|
105
|
+
"runner_unavailable", "Verilator run timed out.") from exc
|
|
106
|
+
sarif = None
|
|
107
|
+
for cand in (Path(workdir) / "obj_dir").glob("*.sarif"):
|
|
108
|
+
try:
|
|
109
|
+
sarif = json.loads(cand.read_text())
|
|
110
|
+
except ValueError:
|
|
111
|
+
sarif = None
|
|
112
|
+
break
|
|
113
|
+
return {"exit_code": proc.returncode, "stdout": proc.stdout,
|
|
114
|
+
"stderr": proc.stderr, "sarif": sarif,
|
|
115
|
+
"sources": checked["sources"]}
|
|
116
|
+
|
|
117
|
+
def parse(self, native_output):
|
|
118
|
+
"""Parse SARIF results, else lint stderr diagnostics.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
native_output: Native output with sarif/stderr.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
{total, violations[], source}.
|
|
125
|
+
"""
|
|
126
|
+
sarif = native_output.get("sarif")
|
|
127
|
+
if isinstance(sarif, dict):
|
|
128
|
+
violations = []
|
|
129
|
+
for run in sarif.get("runs") or []:
|
|
130
|
+
for res in run.get("results") or []:
|
|
131
|
+
locs = res.get("locations") or [{}]
|
|
132
|
+
phys = locs[0].get("physicalLocation") or {}
|
|
133
|
+
region = phys.get("region") or {}
|
|
134
|
+
art = phys.get("artifactLocation") or {}
|
|
135
|
+
violations.append({
|
|
136
|
+
"rule": res.get("ruleId", "verilator"),
|
|
137
|
+
"severity": ("error" if res.get("level") == "error"
|
|
138
|
+
else "warning"),
|
|
139
|
+
"message": ((res.get("message") or {}).get("text")
|
|
140
|
+
or "Verilator diagnostic."),
|
|
141
|
+
"file": art.get("uri", ""),
|
|
142
|
+
"line": int(region.get("startLine", 1)),
|
|
143
|
+
})
|
|
144
|
+
errors = [v for v in violations if v["severity"] == "error"]
|
|
145
|
+
if native_output.get("exit_code", 0) != 0 and not violations:
|
|
146
|
+
violations = [{"rule": "lint_error", "severity": "error",
|
|
147
|
+
"message": "Verilator exited "
|
|
148
|
+
f"{native_output.get('exit_code')}."}]
|
|
149
|
+
return {"total": len(violations), "violations": violations,
|
|
150
|
+
"source": "sarif"}
|
|
151
|
+
violations = []
|
|
152
|
+
for line in (native_output.get("stderr") or "").splitlines():
|
|
153
|
+
match = _DIAG_RE.match(line)
|
|
154
|
+
if match is None:
|
|
155
|
+
continue
|
|
156
|
+
kind, _warn, code, path, line_no, _col = match.groups()
|
|
157
|
+
violations.append({
|
|
158
|
+
"rule": code or ("lint_error"
|
|
159
|
+
if kind == "Error" else "lint_warning"),
|
|
160
|
+
"severity": "error" if kind == "Error" else "warning",
|
|
161
|
+
"message": line[:300], "file": path, "line": int(line_no)})
|
|
162
|
+
if native_output.get("exit_code", 0) != 0 and not violations:
|
|
163
|
+
violations = [{"rule": "lint_error", "severity": "error",
|
|
164
|
+
"message": "Verilator exited "
|
|
165
|
+
f"{native_output.get('exit_code')}."}]
|
|
166
|
+
return {"total": len(violations), "violations": violations,
|
|
167
|
+
"source": "stderr"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# HTTP JSON command transport
|
|
2
|
+
|
|
3
|
+
HttpTransport.handle parses one JSON object containing project_id, operation, optional arguments, and optional request_id. It enforces an optional bound project and delegates to Engine.submit. `serve_http` wraps the same transport in a FastAPI app on uvicorn: POST a JSON envelope to `/` for an admission ack or query result, GET `/health` for a liveness probe, GET `/events` for the runtime lifecycle log as Server-Sent Events (PUSH-EVENTS; pass a runtime to `serve_http`/`create_server`/`create_app` to bind it — replay since `cursor`, then live frames with heartbeat comments). Without a bound runtime `/events` refuses loudly with `runner_unavailable` (503); polling get_events_since stays the watch path there.
|
|
4
|
+
|
|
5
|
+
## Listener hardening
|
|
6
|
+
|
|
7
|
+
POST `/` requires `Content-Type: application/json` (415 otherwise), refuses bodies over 1 MiB (413), accepts a missing Origin or a loopback one only (403 for foreign origins — the browser-CSRF class), and bounds concurrency at 32 in-flight requests (503 + Retry-After on overflow). Engine errors serialize with their v0 code, message, and fields at the status from the shared code table (`adapters/errors.py`, pinned to the v1 errors catalog); uncoded crashes become a generic 500 with the traceback server-side only. Every request writes one audit line (`METHOD /path -> status`) to the `rocket_engine.http` logger.
|
|
8
|
+
|
|
9
|
+
## Trust boundary (ADR-006)
|
|
10
|
+
|
|
11
|
+
This adapter is localhost-only with no auth, serving the same caller-blind API as every other transport. The trust boundary is the loopback interface: `serve_http`/`create_server` bind loopback hosts only (127.0.0.1 by default; any 127/8 address, `localhost`, or `::1`) and refuse any non-loopback bind with `non_loopback_bind_denied`. Startup prints a banner stating the model. Remote/multi-user access needs a separate authenticated adapter — never this one with a flag. See the [HTTP boundary decision](../../../docs/adr/006-localhost-only-http.md) and the [provision plan](../../../docs/provision-plan.md#loopback-only-http-no-remote-deploy).
|
|
12
|
+
|
|
13
|
+
## Boundaries
|
|
14
|
+
|
|
15
|
+
The adapter owns JSON parsing, request IDs, and project-scope checks. It does not own operation validation, state, solver selection, or verdicts. Command shapes are shared with [interfaces](../../interfaces/README.md); the parent transport map is in [adapters](../README.md).
|