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,330 @@
|
|
|
1
|
+
"""Postlayout v1 normalizer: v0 parse output to a ResultBundle.
|
|
2
|
+
|
|
3
|
+
Maps the ``run_post_layout`` parse shape (``measurements[]`` with
|
|
4
|
+
pre/post ``comparison[]`` rows, ``waveforms[]`` with scale plus
|
|
5
|
+
samples, and ``sim_fail`` violations) onto typed v1 datasets,
|
|
6
|
+
views, measurements, and findings. Pure function of the parsed
|
|
7
|
+
output: no I/O, no tool calls, deterministic ids derived from the
|
|
8
|
+
run id. Long waveforms are stride-downsampled to the inline cap;
|
|
9
|
+
canonical units live on ``measurements[]``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
TASK = "run_post_layout"
|
|
13
|
+
|
|
14
|
+
_DIAG_CAP = 240
|
|
15
|
+
_INLINE_CAP = 1024
|
|
16
|
+
|
|
17
|
+
_SEVERITY = {"error": "error", "warning": "warning"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _cap(text):
|
|
21
|
+
"""Cap one diagnostic string at the bundle prose limit.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
text: Raw diagnostic text, possibly unbounded tool prose.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The text unchanged when short, else truncated with an
|
|
28
|
+
ellipsis marker.
|
|
29
|
+
"""
|
|
30
|
+
text = text if isinstance(text, str) else str(text)
|
|
31
|
+
if len(text) <= _DIAG_CAP:
|
|
32
|
+
return text
|
|
33
|
+
return text[: _DIAG_CAP - 3] + "..."
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _quantity(value, unit):
|
|
37
|
+
"""Build one v1 Quantity, mapping unitless to dimensionless.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
value: Numeric value.
|
|
41
|
+
unit: Unit string, possibly empty for unitless measures.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Quantity mapping with an explicit unit.
|
|
45
|
+
"""
|
|
46
|
+
return {"value": float(value), "unit": unit if unit else "1"}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _limit(item):
|
|
50
|
+
"""Pick the binding target bound for one measurement.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
item: v0 measurement with a ``target`` min/max mapping.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The bound closest to the measured value, or None when the
|
|
57
|
+
target is empty.
|
|
58
|
+
"""
|
|
59
|
+
target = item.get("target") or {}
|
|
60
|
+
scored = []
|
|
61
|
+
if target.get("min") is not None:
|
|
62
|
+
scored.append((float(item["value"]) - float(target["min"]),
|
|
63
|
+
float(target["min"])))
|
|
64
|
+
if target.get("max") is not None:
|
|
65
|
+
scored.append((float(target["max"]) - float(item["value"]),
|
|
66
|
+
float(target["max"])))
|
|
67
|
+
if not scored:
|
|
68
|
+
return None
|
|
69
|
+
return min(scored)[1]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _downsample(values):
|
|
73
|
+
"""Stride-downsample one sample list to the inline cap.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
values: Sample values in stored order.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
(samples, downsampled) pair with at most _INLINE_CAP
|
|
80
|
+
entries, stride-picked deterministically.
|
|
81
|
+
"""
|
|
82
|
+
values = list(values)
|
|
83
|
+
if len(values) <= _INLINE_CAP:
|
|
84
|
+
return values, False
|
|
85
|
+
step = -(-len(values) // _INLINE_CAP)
|
|
86
|
+
return values[::step], True
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _provenance(project_id, run_id, tools, provenance):
|
|
90
|
+
"""Build ResultProvenance, defaulting to a placeholder closure.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
project_id: Project the run belongs to.
|
|
94
|
+
run_id: Run the bundle describes.
|
|
95
|
+
tools: Tool ingredient mappings (id, version, digest?).
|
|
96
|
+
provenance: Full provenance mapping when the caller has
|
|
97
|
+
one; a deterministic placeholder otherwise.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Provenance mapping valid against the v1 schema.
|
|
101
|
+
"""
|
|
102
|
+
if provenance is not None:
|
|
103
|
+
return provenance
|
|
104
|
+
ref = {"scope": "project", "id": project_id, "revision": "r1"}
|
|
105
|
+
return {
|
|
106
|
+
"snapshot": dict(ref),
|
|
107
|
+
"input_lock": dict(ref),
|
|
108
|
+
"closure_hash": "0" * 64,
|
|
109
|
+
"tools": [
|
|
110
|
+
{
|
|
111
|
+
"id": item["id"],
|
|
112
|
+
"version": item.get("version", ""),
|
|
113
|
+
"digest": item.get("digest", "0" * 64),
|
|
114
|
+
}
|
|
115
|
+
for item in tools
|
|
116
|
+
],
|
|
117
|
+
"parents": [],
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def normalize(parsed, *, run_id, project_id="project", target=None,
|
|
122
|
+
tools=(), provenance=None):
|
|
123
|
+
"""Normalize one postlayout parse output to a v1 ResultBundle.
|
|
124
|
+
|
|
125
|
+
Measurement rows become a table dataset plus a bound table
|
|
126
|
+
view; comparison rows become a second table when present;
|
|
127
|
+
every waveform becomes a transient series dataset with an
|
|
128
|
+
explicit time axis plus a bound trace view; violations become
|
|
129
|
+
findings with capped messages.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
parsed: v0 parse mapping with ``measurements[]``,
|
|
133
|
+
``comparison[]``, ``waveforms[]``, and
|
|
134
|
+
``violations[]``.
|
|
135
|
+
run_id: Run the bundle describes (anchors all ids).
|
|
136
|
+
project_id: Project the run belongs to.
|
|
137
|
+
target: v1 target mapping; project kind by default.
|
|
138
|
+
tools: Tool ingredient mappings for provenance.
|
|
139
|
+
provenance: Full provenance mapping when the caller has
|
|
140
|
+
one; a deterministic placeholder otherwise.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Schema-valid ResultBundle mapping.
|
|
144
|
+
"""
|
|
145
|
+
measurements_in = parsed.get("measurements") or []
|
|
146
|
+
comparison_in = parsed.get("comparison") or []
|
|
147
|
+
waveforms_in = parsed.get("waveforms") or []
|
|
148
|
+
violations = parsed.get("violations") or []
|
|
149
|
+
table_id = f"{run_id}:dataset:measurements"
|
|
150
|
+
datasets = [
|
|
151
|
+
{
|
|
152
|
+
"id": table_id,
|
|
153
|
+
"kind": "table",
|
|
154
|
+
"row_count": len(measurements_in),
|
|
155
|
+
"columns": [
|
|
156
|
+
{
|
|
157
|
+
"id": f"{table_id}:name",
|
|
158
|
+
"name": "name",
|
|
159
|
+
"dtype": "utf8",
|
|
160
|
+
"unit": "1",
|
|
161
|
+
"data": [item["name"] for item in measurements_in],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"id": f"{table_id}:value",
|
|
165
|
+
"name": "value",
|
|
166
|
+
"dtype": "float64",
|
|
167
|
+
"unit": "1",
|
|
168
|
+
"data": [float(item["value"]) for item in measurements_in],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": f"{table_id}:margin",
|
|
172
|
+
"name": "margin",
|
|
173
|
+
"dtype": "float64",
|
|
174
|
+
"unit": "1",
|
|
175
|
+
"data": [float(item["margin"]) for item in measurements_in],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"id": f"{table_id}:status",
|
|
179
|
+
"name": "status",
|
|
180
|
+
"dtype": "utf8",
|
|
181
|
+
"unit": "1",
|
|
182
|
+
"data": [item["status"] for item in measurements_in],
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
"conditions": [],
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
views = [
|
|
189
|
+
{
|
|
190
|
+
"id": f"{run_id}:view:measurements",
|
|
191
|
+
"kind": "table",
|
|
192
|
+
"dataset_id": table_id,
|
|
193
|
+
"name": "measurements",
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
if comparison_in:
|
|
197
|
+
cmp_id = f"{run_id}:dataset:comparison"
|
|
198
|
+
datasets.append(
|
|
199
|
+
{
|
|
200
|
+
"id": cmp_id,
|
|
201
|
+
"kind": "table",
|
|
202
|
+
"row_count": len(comparison_in),
|
|
203
|
+
"columns": [
|
|
204
|
+
{
|
|
205
|
+
"id": f"{cmp_id}:name",
|
|
206
|
+
"name": "name",
|
|
207
|
+
"dtype": "utf8",
|
|
208
|
+
"unit": "1",
|
|
209
|
+
"data": [row["name"] for row in comparison_in],
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"id": f"{cmp_id}:delta",
|
|
213
|
+
"name": "delta",
|
|
214
|
+
"dtype": "float64",
|
|
215
|
+
"unit": "1",
|
|
216
|
+
"data": [float(row["delta"]) for row in comparison_in],
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"id": f"{cmp_id}:status",
|
|
220
|
+
"name": "status",
|
|
221
|
+
"dtype": "utf8",
|
|
222
|
+
"unit": "1",
|
|
223
|
+
"data": [row["status"] for row in comparison_in],
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
"conditions": [],
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
views.append(
|
|
230
|
+
{
|
|
231
|
+
"id": f"{run_id}:view:comparison",
|
|
232
|
+
"kind": "table",
|
|
233
|
+
"dataset_id": cmp_id,
|
|
234
|
+
"name": "pre-post comparison",
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
for index, wave in enumerate(waveforms_in):
|
|
238
|
+
samples, _ = _downsample(wave.get("samples") or [])
|
|
239
|
+
scale, _ = _downsample(wave.get("scale") or [])
|
|
240
|
+
ds_id = f"{run_id}:dataset:waveform:{index}"
|
|
241
|
+
axis_id = f"{ds_id}:time"
|
|
242
|
+
sig_id = f"{ds_id}:signal"
|
|
243
|
+
datasets.append(
|
|
244
|
+
{
|
|
245
|
+
"id": ds_id,
|
|
246
|
+
"kind": "series",
|
|
247
|
+
"analysis": "tran",
|
|
248
|
+
"axes": [
|
|
249
|
+
{
|
|
250
|
+
"id": axis_id,
|
|
251
|
+
"name": "time",
|
|
252
|
+
"quantity": "time",
|
|
253
|
+
"unit": "s",
|
|
254
|
+
"kind": "continuous",
|
|
255
|
+
"scale": "linear",
|
|
256
|
+
"values": [float(v) for v in scale],
|
|
257
|
+
}
|
|
258
|
+
],
|
|
259
|
+
"signals": [
|
|
260
|
+
{
|
|
261
|
+
"id": sig_id,
|
|
262
|
+
"name": wave.get("name", f"waveform_{index}"),
|
|
263
|
+
"quantity": "voltage",
|
|
264
|
+
"unit": "V",
|
|
265
|
+
"values": {
|
|
266
|
+
"storage": "inline",
|
|
267
|
+
"dtype": "float64",
|
|
268
|
+
"shape": [len(samples)],
|
|
269
|
+
"data": [float(v) for v in samples],
|
|
270
|
+
},
|
|
271
|
+
}
|
|
272
|
+
],
|
|
273
|
+
"conditions": [],
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
views.append(
|
|
277
|
+
{
|
|
278
|
+
"id": f"{run_id}:view:waveform:{index}",
|
|
279
|
+
"kind": "traces",
|
|
280
|
+
"dataset_id": ds_id,
|
|
281
|
+
"name": wave.get("name", f"waveform_{index}"),
|
|
282
|
+
"x_axis": axis_id,
|
|
283
|
+
"traces": [
|
|
284
|
+
{"signal": sig_id, "transform": "identity",
|
|
285
|
+
"axis": "left"}
|
|
286
|
+
],
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
measurements = []
|
|
290
|
+
for item in measurements_in:
|
|
291
|
+
entry = {
|
|
292
|
+
"name": item["name"],
|
|
293
|
+
"value": _quantity(item["value"], item.get("unit", "")),
|
|
294
|
+
"margin": _quantity(item["margin"], item.get("unit", "")),
|
|
295
|
+
"status": "PASS" if item["status"] == "pass" else "FAIL",
|
|
296
|
+
}
|
|
297
|
+
bound = _limit(item)
|
|
298
|
+
if bound is not None:
|
|
299
|
+
entry["limit"] = _quantity(bound, item.get("unit", ""))
|
|
300
|
+
measurements.append(entry)
|
|
301
|
+
findings = [
|
|
302
|
+
{
|
|
303
|
+
"id": f"{run_id}:finding:{index}",
|
|
304
|
+
"severity": _SEVERITY.get(v.get("severity"), "info"),
|
|
305
|
+
"code": v.get("rule", "sim_fail"),
|
|
306
|
+
"message": _cap(v.get("message", "")),
|
|
307
|
+
}
|
|
308
|
+
for index, v in enumerate(violations)
|
|
309
|
+
]
|
|
310
|
+
converged = not any(v.get("rule") == "sim_fail" for v in violations)
|
|
311
|
+
return {
|
|
312
|
+
"schema": "rocketeda/results/v1",
|
|
313
|
+
"ref": {"scope": "project", "id": run_id, "revision": "r1"},
|
|
314
|
+
"run_id": run_id,
|
|
315
|
+
"task": TASK,
|
|
316
|
+
"target": dict(target)
|
|
317
|
+
if target is not None
|
|
318
|
+
else {"project_id": project_id, "kind": "project"},
|
|
319
|
+
"state": "complete",
|
|
320
|
+
"freshness": "current",
|
|
321
|
+
"convergence": "converged" if converged else "not_converged",
|
|
322
|
+
"provenance": _provenance(project_id, run_id, tools, provenance),
|
|
323
|
+
"datasets": datasets,
|
|
324
|
+
"views": views,
|
|
325
|
+
"measurements": measurements,
|
|
326
|
+
"findings": findings,
|
|
327
|
+
"artifacts": [],
|
|
328
|
+
"logs": [],
|
|
329
|
+
"gaps": [],
|
|
330
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""KLT to vendor translation for the postlayout path."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from ..errors import AdapterError
|
|
6
|
+
|
|
7
|
+
_KLT_RE = re.compile(r"klt", re.IGNORECASE)
|
|
8
|
+
_DEVICE_RE = re.compile(r"^([XMRCL]\S*)\s+(\S+)\s+(\S+)\s+(\S+)(.*)$",
|
|
9
|
+
re.M)
|
|
10
|
+
|
|
11
|
+
_VENDOR_MAP = {
|
|
12
|
+
"sky130_klt_nmos": "sky130_fd_pr__nfet_01v8",
|
|
13
|
+
"sky130_klt_pmos": "sky130_fd_pr__pfet_01v8",
|
|
14
|
+
"klt_nmos": "nfet_01v8",
|
|
15
|
+
"klt_pmos": "pfet_01v8",
|
|
16
|
+
"klt_res": "resistor",
|
|
17
|
+
"klt_cap": "capacitor",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_klt_netlist(text):
|
|
22
|
+
"""Report whether a netlist carries klt generic devices.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
text: Netlist text under check.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
True when klt markers appear, else False.
|
|
29
|
+
"""
|
|
30
|
+
return bool(_KLT_RE.search(text or ""))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def translate_klt_netlist(text, vendor="sky130"):
|
|
34
|
+
"""Translate klt generic devices to vendor models.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
text: Extracted netlist text with klt device classes.
|
|
38
|
+
vendor: Vendor family the models translate to.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
(translated, mapping) with the translated netlist plus the
|
|
42
|
+
device rename rows.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
AdapterError: unsupported_operation when a klt device has
|
|
46
|
+
no vendor mapping — loud refusal naming the gap, never
|
|
47
|
+
a silent sim_fail.
|
|
48
|
+
"""
|
|
49
|
+
mapping = []
|
|
50
|
+
out_lines = []
|
|
51
|
+
for line in (text or "").splitlines():
|
|
52
|
+
mapped = line
|
|
53
|
+
lowered = line.lower()
|
|
54
|
+
if "klt" in lowered:
|
|
55
|
+
hit = None
|
|
56
|
+
for klt, vend in _VENDOR_MAP.items():
|
|
57
|
+
if klt in lowered:
|
|
58
|
+
hit = (klt, vend)
|
|
59
|
+
break
|
|
60
|
+
if hit is None:
|
|
61
|
+
raise AdapterError(
|
|
62
|
+
"unsupported_operation",
|
|
63
|
+
f"KLT device has no {vendor} vendor mapping: "
|
|
64
|
+
f"{line.strip()[:160]} (POSTLAYOUT-REAL gap).",
|
|
65
|
+
)
|
|
66
|
+
klt, vend = hit
|
|
67
|
+
pattern = re.compile(re.escape(klt), re.IGNORECASE)
|
|
68
|
+
mapped = pattern.sub(vend, line)
|
|
69
|
+
mapping.append({"from": klt, "to": vend, "vendor": vendor})
|
|
70
|
+
out_lines.append(mapped)
|
|
71
|
+
return "\n".join(out_lines) + ("\n" if out_lines else ""), mapping
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""PDK profile records: data-driven simulator resolution (PDK-PROFILE).
|
|
2
|
+
|
|
3
|
+
Adapters read PDK profile records (data), never PDK special-cases
|
|
4
|
+
(code): per-PDK simulator needs (model relpaths, corner names,
|
|
5
|
+
spiceinit lines) live in profile data under docs/pdk/<lane>/,
|
|
6
|
+
with a builtin copy here for contexts where the docs tree is not
|
|
7
|
+
shipped. New PDKs arrive as profile entries, never adapter edits.
|
|
8
|
+
|
|
9
|
+
Shape follows the PDK-PROFILE direction lock: {gdsfactory package
|
|
10
|
+
pin, KLayout tech, decks, models}. The `sim` section carries what
|
|
11
|
+
a SPICE adapter needs: `model_rel` (default deck relpath under
|
|
12
|
+
the registered store entry), `corners` (valid corner names), and
|
|
13
|
+
`spiceinit_extra` (PDK-only .spiceinit lines beyond the base).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from ruamel.yaml import YAML
|
|
19
|
+
|
|
20
|
+
from ..interfaces.paths import slugify
|
|
21
|
+
from .errors import AdapterError
|
|
22
|
+
|
|
23
|
+
PROFILE_SCHEMA = "rocketeda/pdk-profile/v0.1"
|
|
24
|
+
|
|
25
|
+
_BASE_SPICEINIT = (
|
|
26
|
+
"set ngbehavior=hsa ; HSPICE-ish compatibility for PDK model libs",
|
|
27
|
+
"set ng_nomodcheck ; do not check model parameters",
|
|
28
|
+
"option noinit ; do not dump operating point data into the log",
|
|
29
|
+
"option klu ; KLU direct linear solver",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
#: Builtin sim record, mirroring docs/pdk/sky130a/profile.yaml
|
|
33
|
+
#: (the authority). Used only when the docs tree is not beside
|
|
34
|
+
#: the package (installed wheels ship code, not docs/).
|
|
35
|
+
_BUILTIN_SIM = {
|
|
36
|
+
"sky130a": {
|
|
37
|
+
"model_rel": "libs.tech/combined/sky130.lib.spice",
|
|
38
|
+
"corners": ["tt", "ss", "ff", "sf", "fs"],
|
|
39
|
+
"spiceinit_extra": [
|
|
40
|
+
"set skywaterpdk ; skip some checks for faster lib loading",
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _checkout_root():
|
|
47
|
+
"""Engine checkout root, or None outside a source tree.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Checkout root when this file sits inside one (a
|
|
51
|
+
pyproject marker beside src/), else None.
|
|
52
|
+
"""
|
|
53
|
+
root = Path(__file__).resolve().parents[2]
|
|
54
|
+
if (root / "pyproject.toml").is_file():
|
|
55
|
+
return root
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def lane_profile_path(pdk_id):
|
|
60
|
+
"""Profile file for one PDK lane, when the docs tree is present.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
pdk_id: Canonical PDK display id (either spelling resolves).
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Profile path, or None when the id is missing, when no
|
|
67
|
+
checkout docs tree is beside the package, or when the lane
|
|
68
|
+
file is absent.
|
|
69
|
+
"""
|
|
70
|
+
if not pdk_id or not isinstance(pdk_id, str):
|
|
71
|
+
return None
|
|
72
|
+
root = _checkout_root()
|
|
73
|
+
if root is None:
|
|
74
|
+
return None
|
|
75
|
+
candidate = root / "docs" / "pdk" / slugify(pdk_id) / "profile.yaml"
|
|
76
|
+
if candidate.is_file():
|
|
77
|
+
return candidate
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def load_profile_record(path):
|
|
82
|
+
"""Load and validate one lane profile file.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
path: Profile YAML path.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Validated profile mapping.
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
AdapterError: invalid_profile for an unreadable file or a
|
|
92
|
+
record failing schema validation.
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
data = YAML(typ="safe").load(Path(path).read_text())
|
|
96
|
+
except OSError as exc:
|
|
97
|
+
raise AdapterError(
|
|
98
|
+
"invalid_profile", f"Cannot read PDK profile {path}: {exc}."
|
|
99
|
+
) from exc
|
|
100
|
+
return validate_record(data, source=str(path))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def validate_record(record, source="record"):
|
|
104
|
+
"""Validate one profile mapping against the profile schema.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
record: Profile mapping under check.
|
|
108
|
+
source: Where the record came from (for messages).
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
The same mapping, validated.
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
AdapterError: invalid_profile for a non-mapping record, a
|
|
115
|
+
wrong schema version, or a missing id.
|
|
116
|
+
"""
|
|
117
|
+
if not isinstance(record, dict):
|
|
118
|
+
raise AdapterError(
|
|
119
|
+
"invalid_profile", f"PDK profile must be a mapping: {source}."
|
|
120
|
+
)
|
|
121
|
+
if record.get("schema") != PROFILE_SCHEMA:
|
|
122
|
+
raise AdapterError(
|
|
123
|
+
"invalid_profile",
|
|
124
|
+
f"PDK profile {source} must carry schema {PROFILE_SCHEMA}.",
|
|
125
|
+
)
|
|
126
|
+
if not record.get("id") or not isinstance(record.get("id"), str):
|
|
127
|
+
raise AdapterError(
|
|
128
|
+
"invalid_profile", f"PDK profile {source} misses id."
|
|
129
|
+
)
|
|
130
|
+
return record
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def sim_for(pdk_id, record=None):
|
|
134
|
+
"""Sim record for one PDK: lane file first, builtin next.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
pdk_id: Canonical PDK display id (either spelling resolves).
|
|
138
|
+
record: Optional already-loaded profile mapping, winning
|
|
139
|
+
over the lane file and the builtin.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Sim mapping (model_rel, corners, spiceinit_extra), or None
|
|
143
|
+
when the PDK declares no sim profile.
|
|
144
|
+
"""
|
|
145
|
+
if isinstance(record, dict):
|
|
146
|
+
sim = record.get("sim")
|
|
147
|
+
if isinstance(sim, dict):
|
|
148
|
+
return sim
|
|
149
|
+
if not pdk_id or not isinstance(pdk_id, str):
|
|
150
|
+
return None
|
|
151
|
+
path = lane_profile_path(pdk_id)
|
|
152
|
+
if path is not None:
|
|
153
|
+
try:
|
|
154
|
+
sim = load_profile_record(path).get("sim")
|
|
155
|
+
except AdapterError:
|
|
156
|
+
sim = None
|
|
157
|
+
if isinstance(sim, dict):
|
|
158
|
+
return sim
|
|
159
|
+
return _BUILTIN_SIM.get(slugify(pdk_id))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def default_model_rel(pdk_id, record=None):
|
|
163
|
+
"""Default store model relpath for one PDK, if profiled.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
pdk_id: Canonical PDK display id (either spelling resolves).
|
|
167
|
+
record: Optional already-loaded profile mapping.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Default model relpath, or None when the PDK declares no
|
|
171
|
+
sim profile (callers fail loudly, never guess a relpath).
|
|
172
|
+
"""
|
|
173
|
+
sim = sim_for(pdk_id, record=record)
|
|
174
|
+
if not isinstance(sim, dict):
|
|
175
|
+
return None
|
|
176
|
+
rel = sim.get("model_rel")
|
|
177
|
+
return rel if isinstance(rel, str) and rel else None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def base_spiceinit():
|
|
181
|
+
"""PDK-neutral .spiceinit lines every SPICE run stages.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Base .spiceinit lines (no PDK-only flags; those ride the
|
|
185
|
+
profile `spiceinit_extra`).
|
|
186
|
+
"""
|
|
187
|
+
return list(_BASE_SPICEINIT)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def spiceinit_for(pdk_id, record=None):
|
|
191
|
+
"""Full .spiceinit lines for one PDK: base plus profile extras.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
pdk_id: Canonical PDK display id (either spelling resolves).
|
|
195
|
+
record: Optional already-loaded profile mapping.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
.spiceinit lines; base only when the PDK declares no sim
|
|
199
|
+
profile.
|
|
200
|
+
"""
|
|
201
|
+
lines = base_spiceinit()
|
|
202
|
+
sim = sim_for(pdk_id, record=record)
|
|
203
|
+
if isinstance(sim, dict):
|
|
204
|
+
for line in sim.get("spiceinit_extra") or []:
|
|
205
|
+
if isinstance(line, str) and line and line not in lines:
|
|
206
|
+
lines.append(line)
|
|
207
|
+
return lines
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def render_spiceinit(lines):
|
|
211
|
+
"""Render .spiceinit text from staged lines.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
lines: .spiceinit lines (base plus profile extras).
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
.spiceinit file text with a profile-driven header.
|
|
218
|
+
"""
|
|
219
|
+
head = "* rocket_engine ngspice init (profile-driven: see docs/pdk/)."
|
|
220
|
+
return head + "\n" + "\n".join(lines) + "\n"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# adapters/sax — SAX photonic sim solver
|
|
2
|
+
|
|
3
|
+
`run_optical` behind the `ToolAdapter` port: preflight refuses the
|
|
4
|
+
wrong operation, bad timeouts, missing/extra sources, unparseable
|
|
5
|
+
specs, and unresolvable interpreters; execute stages the sim driver
|
|
6
|
+
plus a job JSON into the workdir and runs
|
|
7
|
+
`python sax_driver.py sax_job.json`; parse normalizes the JSON
|
|
8
|
+
envelope into `{total, violations[], measurements[], spectra[],
|
|
9
|
+
sax_version}` with file/line locations where meaningful.
|
|
10
|
+
|
|
11
|
+
Verified against sax 0.18.2 on MZI/ring fixtures
|
|
12
|
+
(`tests/adapters/fixtures/sax/`). The balanced MZI reproduces its
|
|
13
|
+
documented transmission exactly (cross 1.0, bar 0.0); the mistuned
|
|
14
|
+
MZI misses its target with an engine-computed margin; a malformed
|
|
15
|
+
circuit synthesizes an error violation naming the offender with
|
|
16
|
+
file + line rather than going clean. A sax-less interpreter
|
|
17
|
+
raises `missing_dependency`, never a fake verdict.
|
|
18
|
+
|
|
19
|
+
Tool resolution is the engine interpreter (sax arrives via the pip
|
|
20
|
+
`photonic` group) plus an explicit job `tool_dir` holding an
|
|
21
|
+
alternate python; where the tools live is B10 scope. Registered in
|
|
22
|
+
the Engine as `run_optical` → `sax` per F8, so `deck.tool: sax`
|
|
23
|
+
selects it and a missing hint falls back to it as the default.
|