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,227 @@
|
|
|
1
|
+
"""Digital-flow v1 normalizer: v0 parse output to a ResultBundle.
|
|
2
|
+
|
|
3
|
+
Maps the ``run_digital_flow`` parse shape (per-stage gate table
|
|
4
|
+
with id/label/status/evidence plus top-level gds/netlist/spef
|
|
5
|
+
paths, drc_total, and lvs_match) onto a typed stages table, a
|
|
6
|
+
deliverables table, bound views, measurements, and findings.
|
|
7
|
+
Pure function of the parsed output: no I/O, no tool calls,
|
|
8
|
+
deterministic ids derived from the run id. File paths ride the
|
|
9
|
+
deliverables table as evidence; no bundle artifact is fabricated
|
|
10
|
+
without a recorded digest.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
TASK = "run_digital_flow"
|
|
14
|
+
|
|
15
|
+
_DIAG_CAP = 240
|
|
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 _provenance(project_id, run_id, tools, provenance):
|
|
50
|
+
"""Build ResultProvenance, defaulting to a placeholder closure.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
project_id: Project the run belongs to.
|
|
54
|
+
run_id: Run the bundle describes.
|
|
55
|
+
tools: Tool ingredient mappings (id, version, digest?).
|
|
56
|
+
provenance: Full provenance mapping when the caller has
|
|
57
|
+
one; a deterministic placeholder otherwise.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Provenance mapping valid against the v1 schema.
|
|
61
|
+
"""
|
|
62
|
+
if provenance is not None:
|
|
63
|
+
return provenance
|
|
64
|
+
ref = {"scope": "project", "id": project_id, "revision": "r1"}
|
|
65
|
+
return {
|
|
66
|
+
"snapshot": dict(ref),
|
|
67
|
+
"input_lock": dict(ref),
|
|
68
|
+
"closure_hash": "0" * 64,
|
|
69
|
+
"tools": [
|
|
70
|
+
{
|
|
71
|
+
"id": item["id"],
|
|
72
|
+
"version": item.get("version", ""),
|
|
73
|
+
"digest": item.get("digest", "0" * 64),
|
|
74
|
+
}
|
|
75
|
+
for item in tools
|
|
76
|
+
],
|
|
77
|
+
"parents": [],
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def normalize(parsed, *, run_id, project_id="project", target=None,
|
|
82
|
+
tools=(), provenance=None):
|
|
83
|
+
"""Normalize one digital-flow parse output to a v1 ResultBundle.
|
|
84
|
+
|
|
85
|
+
Stage gates become a table dataset (id, label, status) plus
|
|
86
|
+
a deliverables table (gds/netlist/spef paths); drc_total and
|
|
87
|
+
lvs_match ride measurements with PASS/FAIL status; failed
|
|
88
|
+
gates become findings with capped messages.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
parsed: v0 parse mapping with ``stages[]``,
|
|
92
|
+
``violations[]``, deliverable paths, ``drc_total``,
|
|
93
|
+
and ``lvs_match``.
|
|
94
|
+
run_id: Run the bundle describes (anchors all ids).
|
|
95
|
+
project_id: Project the run belongs to.
|
|
96
|
+
target: v1 target mapping; project kind by default.
|
|
97
|
+
tools: Tool ingredient mappings for provenance.
|
|
98
|
+
provenance: Full provenance mapping when the caller has
|
|
99
|
+
one; a deterministic placeholder otherwise.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Schema-valid ResultBundle mapping.
|
|
103
|
+
"""
|
|
104
|
+
stages = parsed.get("stages") or []
|
|
105
|
+
violations = parsed.get("violations") or []
|
|
106
|
+
stages_id = f"{run_id}:dataset:stages"
|
|
107
|
+
files_id = f"{run_id}:dataset:deliverables"
|
|
108
|
+
deliverables = [
|
|
109
|
+
(name, parsed.get(name))
|
|
110
|
+
for name in ("gds", "netlist", "spef")
|
|
111
|
+
if parsed.get(name)
|
|
112
|
+
]
|
|
113
|
+
datasets = [
|
|
114
|
+
{
|
|
115
|
+
"id": stages_id,
|
|
116
|
+
"kind": "table",
|
|
117
|
+
"row_count": len(stages),
|
|
118
|
+
"columns": [
|
|
119
|
+
{
|
|
120
|
+
"id": f"{stages_id}:id",
|
|
121
|
+
"name": "id",
|
|
122
|
+
"dtype": "utf8",
|
|
123
|
+
"unit": "1",
|
|
124
|
+
"data": [s.get("id", "") for s in stages],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": f"{stages_id}:label",
|
|
128
|
+
"name": "label",
|
|
129
|
+
"dtype": "utf8",
|
|
130
|
+
"unit": "1",
|
|
131
|
+
"data": [s.get("label", "") for s in stages],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"id": f"{stages_id}:status",
|
|
135
|
+
"name": "status",
|
|
136
|
+
"dtype": "utf8",
|
|
137
|
+
"unit": "1",
|
|
138
|
+
"data": [s.get("status", "") for s in stages],
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
"conditions": [],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"id": files_id,
|
|
145
|
+
"kind": "table",
|
|
146
|
+
"row_count": len(deliverables),
|
|
147
|
+
"columns": [
|
|
148
|
+
{
|
|
149
|
+
"id": f"{files_id}:artifact",
|
|
150
|
+
"name": "artifact",
|
|
151
|
+
"dtype": "utf8",
|
|
152
|
+
"unit": "1",
|
|
153
|
+
"data": [name for name, _ in deliverables],
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"id": f"{files_id}:path",
|
|
157
|
+
"name": "path",
|
|
158
|
+
"dtype": "utf8",
|
|
159
|
+
"unit": "1",
|
|
160
|
+
"data": [str(path) for _, path in deliverables],
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
"conditions": [],
|
|
164
|
+
},
|
|
165
|
+
]
|
|
166
|
+
views = [
|
|
167
|
+
{
|
|
168
|
+
"id": f"{run_id}:view:stages",
|
|
169
|
+
"kind": "table",
|
|
170
|
+
"dataset_id": stages_id,
|
|
171
|
+
"name": "stage gates",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"id": f"{run_id}:view:deliverables",
|
|
175
|
+
"kind": "table",
|
|
176
|
+
"dataset_id": files_id,
|
|
177
|
+
"name": "deliverables",
|
|
178
|
+
},
|
|
179
|
+
]
|
|
180
|
+
measurements = []
|
|
181
|
+
drc_total = parsed.get("drc_total")
|
|
182
|
+
if drc_total is not None:
|
|
183
|
+
measurements.append(
|
|
184
|
+
{
|
|
185
|
+
"name": "drc_total",
|
|
186
|
+
"value": _quantity(drc_total, "1"),
|
|
187
|
+
"status": "PASS" if drc_total == 0 else "FAIL",
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
if parsed.get("lvs_match") is not None:
|
|
191
|
+
match = bool(parsed.get("lvs_match"))
|
|
192
|
+
measurements.append(
|
|
193
|
+
{
|
|
194
|
+
"name": "lvs_match",
|
|
195
|
+
"value": _quantity(1.0 if match else 0.0, "1"),
|
|
196
|
+
"status": "PASS" if match else "FAIL",
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
findings = [
|
|
200
|
+
{
|
|
201
|
+
"id": f"{run_id}:finding:{index}",
|
|
202
|
+
"severity": _SEVERITY.get(v.get("severity"), "info"),
|
|
203
|
+
"code": v.get("rule", "digital_flow_gate"),
|
|
204
|
+
"message": _cap(v.get("message", "")),
|
|
205
|
+
}
|
|
206
|
+
for index, v in enumerate(violations)
|
|
207
|
+
]
|
|
208
|
+
return {
|
|
209
|
+
"schema": "rocketeda/results/v1",
|
|
210
|
+
"ref": {"scope": "project", "id": run_id, "revision": "r1"},
|
|
211
|
+
"run_id": run_id,
|
|
212
|
+
"task": TASK,
|
|
213
|
+
"target": dict(target)
|
|
214
|
+
if target is not None
|
|
215
|
+
else {"project_id": project_id, "kind": "project"},
|
|
216
|
+
"state": "complete",
|
|
217
|
+
"freshness": "current",
|
|
218
|
+
"convergence": "not_applicable",
|
|
219
|
+
"provenance": _provenance(project_id, run_id, tools, provenance),
|
|
220
|
+
"datasets": datasets,
|
|
221
|
+
"views": views,
|
|
222
|
+
"measurements": measurements,
|
|
223
|
+
"findings": findings,
|
|
224
|
+
"artifacts": [],
|
|
225
|
+
"logs": [],
|
|
226
|
+
"gaps": [],
|
|
227
|
+
}
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"""Run-dir machine-file readout for the digital_flow gates.
|
|
2
|
+
|
|
3
|
+
LibreLane writes one state_out.json plus metrics.json/metrics.csv
|
|
4
|
+
per step dir (docs/tools/librelane.md section 2). The D22/D25/D26/D27
|
|
5
|
+
verdicts read those machine files only: report text is evidence, never
|
|
6
|
+
the verdict. Steps without machine files fail loudly (key drift means
|
|
7
|
+
an upstream step change, never a silent pass).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import csv
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
STATE_FILES = ("state_out.json", "metrics.json", "metrics.csv")
|
|
15
|
+
|
|
16
|
+
# Signoff JSON reports the live 3.0.17 run writes under step dirs
|
|
17
|
+
# (docs/tools/librelane.md section 6): exact filenames, never globs.
|
|
18
|
+
SIGNOFF_DRC_NAME = "drc.klayout.json"
|
|
19
|
+
SIGNOFF_LVS_NAME = "lvs.netgen.json"
|
|
20
|
+
|
|
21
|
+
STA_WS_KEYS = (
|
|
22
|
+
"timing__setup__ws",
|
|
23
|
+
"timing__hold__ws",
|
|
24
|
+
"wns_setup",
|
|
25
|
+
"wns_hold",
|
|
26
|
+
)
|
|
27
|
+
STA_TNS_KEYS = (
|
|
28
|
+
"timing__setup__tns",
|
|
29
|
+
"timing__hold__tns",
|
|
30
|
+
"tns_setup",
|
|
31
|
+
"tns_hold",
|
|
32
|
+
)
|
|
33
|
+
DRC_KEYS = ("drc__violations", "drc_total", "violations")
|
|
34
|
+
# LibreLane 3.x signoff counts (exact names, no corner suffix).
|
|
35
|
+
DRC_ERROR_KEYS = ("magic__drc_error__count", "klayout__drc_error__count")
|
|
36
|
+
LVS_KEYS = ("lvs__match", "lvs_match", "match")
|
|
37
|
+
# LibreLane 3.x LVS mismatch counts (0 means match).
|
|
38
|
+
LVS_MISMATCH_KEYS = (
|
|
39
|
+
"design__lvs_unmatched_device__count",
|
|
40
|
+
"design__lvs_unmatched_net__count",
|
|
41
|
+
"design__lvs_unmatched_pin__count",
|
|
42
|
+
)
|
|
43
|
+
EQY_KEYS = ("equiv__equivalent", "equivalent", "equiv_pass", "equiv__proven")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def read_metrics(step_dir):
|
|
47
|
+
"""Merge one step dir's machine metrics into a flat mapping.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
step_dir: Step directory path holding state_out.json,
|
|
51
|
+
metrics.json, or metrics.csv.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
(metrics, files): flat metric mapping (later files fill
|
|
55
|
+
missing keys only) plus the state file paths read.
|
|
56
|
+
"""
|
|
57
|
+
metrics = {}
|
|
58
|
+
files = []
|
|
59
|
+
state = Path(step_dir) / "state_out.json"
|
|
60
|
+
raw = _read_json(state)
|
|
61
|
+
if isinstance(raw, dict):
|
|
62
|
+
for key, value in raw.get("metrics", raw).items():
|
|
63
|
+
metrics.setdefault(str(key), value)
|
|
64
|
+
files.append(str(state))
|
|
65
|
+
snap = Path(step_dir) / "metrics.json"
|
|
66
|
+
raw = _read_json(snap)
|
|
67
|
+
if isinstance(raw, dict):
|
|
68
|
+
for key, value in raw.items():
|
|
69
|
+
metrics.setdefault(str(key), value)
|
|
70
|
+
files.append(str(snap))
|
|
71
|
+
table = Path(step_dir) / "metrics.csv"
|
|
72
|
+
for key, value in _read_csv(table).items():
|
|
73
|
+
metrics.setdefault(key, value)
|
|
74
|
+
if metrics:
|
|
75
|
+
files.append(str(table)) if table.is_file() else None
|
|
76
|
+
return metrics, [path for path in files if Path(path).is_file()]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def read_run_states(run_dir):
|
|
80
|
+
"""Read machine metrics plus state views for every step dir.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
run_dir: runs/<tag>/ dir path, or None when the flow never
|
|
84
|
+
created it.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Step-dir name to {"metrics": mapping, "files": paths,
|
|
88
|
+
"views": paths}.
|
|
89
|
+
"""
|
|
90
|
+
states = {}
|
|
91
|
+
if run_dir is None or not Path(run_dir).is_dir():
|
|
92
|
+
return states
|
|
93
|
+
for child in sorted(Path(run_dir).iterdir()):
|
|
94
|
+
if child.is_dir():
|
|
95
|
+
metrics, files = read_metrics(child)
|
|
96
|
+
states[child.name] = {
|
|
97
|
+
"metrics": metrics,
|
|
98
|
+
"files": files,
|
|
99
|
+
"views": read_views(child),
|
|
100
|
+
}
|
|
101
|
+
return states
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def read_views(step_dir):
|
|
105
|
+
"""Read state view paths from one step dir's state_out.json.
|
|
106
|
+
|
|
107
|
+
LibreLane State dumps its DesignFormat views as path-valued
|
|
108
|
+
entries alongside the ``metrics`` mapping (docs/tools/librelane.md
|
|
109
|
+
section 2): a top-level string is one view path, a list holds
|
|
110
|
+
several, a mapping holds named ones. Only paths resolving to real
|
|
111
|
+
files count — dangling entries are dropped, never failed on.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
step_dir: Step directory path holding state_out.json.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Sorted existing view file paths (strings).
|
|
118
|
+
"""
|
|
119
|
+
raw = _read_json(Path(step_dir) / "state_out.json")
|
|
120
|
+
if not isinstance(raw, dict):
|
|
121
|
+
return []
|
|
122
|
+
candidates = []
|
|
123
|
+
for key, value in raw.items():
|
|
124
|
+
if key == "metrics":
|
|
125
|
+
continue
|
|
126
|
+
if isinstance(value, str):
|
|
127
|
+
candidates.append(value)
|
|
128
|
+
elif isinstance(value, list):
|
|
129
|
+
candidates.extend(item for item in value if isinstance(item, str))
|
|
130
|
+
elif isinstance(value, dict):
|
|
131
|
+
candidates.extend(
|
|
132
|
+
item for item in value.values() if isinstance(item, str)
|
|
133
|
+
)
|
|
134
|
+
found = []
|
|
135
|
+
for candidate in candidates:
|
|
136
|
+
path = Path(candidate)
|
|
137
|
+
if not path.is_absolute():
|
|
138
|
+
path = Path(step_dir) / candidate
|
|
139
|
+
if path.is_file():
|
|
140
|
+
found.append(str(path))
|
|
141
|
+
return sorted(set(found))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _signoff_file(run_dir, name):
|
|
145
|
+
"""First exact-name signoff report under a run root, or None.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
run_dir: runs/<tag>/ dir path, or None when the flow never
|
|
149
|
+
created it.
|
|
150
|
+
name: Exact report filename (no globs, no substrings).
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Sorted-first matching path, or None.
|
|
154
|
+
"""
|
|
155
|
+
if run_dir is None or not Path(run_dir).is_dir():
|
|
156
|
+
return None
|
|
157
|
+
matches = sorted(
|
|
158
|
+
path
|
|
159
|
+
for path in Path(run_dir).rglob(name)
|
|
160
|
+
if path.is_file() and path.name == name
|
|
161
|
+
)
|
|
162
|
+
return str(matches[0]) if matches else None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def signoff_drc_total(run_dir):
|
|
166
|
+
"""Read the signoff DRC total from drc.klayout.json.
|
|
167
|
+
|
|
168
|
+
The live 3.0.17 run writes ``{rule: count, ..., "total": N}``
|
|
169
|
+
(docs/tools/librelane.md section 6); only that exact ``total``
|
|
170
|
+
key judges — per-rule counts are evidence, never the verdict.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
run_dir: runs/<tag>/ dir path, or None when the flow never
|
|
174
|
+
created it.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
(total, path): integer total plus the report path, or
|
|
178
|
+
(None, None) when the report or its total is absent.
|
|
179
|
+
"""
|
|
180
|
+
path = _signoff_file(run_dir, SIGNOFF_DRC_NAME)
|
|
181
|
+
raw = _read_json(path) if path is not None else None
|
|
182
|
+
if not isinstance(raw, dict):
|
|
183
|
+
return None, None
|
|
184
|
+
total = number(raw.get("total"))
|
|
185
|
+
if total is None:
|
|
186
|
+
return None, None
|
|
187
|
+
return int(total), path
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def signoff_lvs_report(run_dir):
|
|
191
|
+
"""Locate the signoff lvs.netgen.json report under a run root.
|
|
192
|
+
|
|
193
|
+
The per-element pin-comparison list has no pinned pass shape, so
|
|
194
|
+
the report rides as evidence only — it never judges the gate.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
run_dir: runs/<tag>/ dir path, or None when the flow never
|
|
198
|
+
created it.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Report path, or None when absent.
|
|
202
|
+
"""
|
|
203
|
+
return _signoff_file(run_dir, SIGNOFF_LVS_NAME)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def lookup(metrics, *names):
|
|
207
|
+
"""Case-insensitive exact lookup over metric keys.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
metrics: Flat metric mapping from read_metrics.
|
|
211
|
+
names: Candidate key names in priority order.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
(key, value) for the first candidate present, else
|
|
215
|
+
(None, None).
|
|
216
|
+
"""
|
|
217
|
+
lowered = {str(key).lower(): key for key in metrics}
|
|
218
|
+
for name in names:
|
|
219
|
+
if name.lower() in lowered:
|
|
220
|
+
key = lowered[name.lower()]
|
|
221
|
+
return key, metrics[key]
|
|
222
|
+
return None, None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def number(value):
|
|
226
|
+
"""Coerce one metric value to float, or None when not numeric.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
value: Raw metric value (number, numeric string, or bool).
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Float value, or None when the value carries no number.
|
|
233
|
+
"""
|
|
234
|
+
if isinstance(value, bool):
|
|
235
|
+
return float(value)
|
|
236
|
+
if isinstance(value, (int, float)):
|
|
237
|
+
return float(value)
|
|
238
|
+
if isinstance(value, str):
|
|
239
|
+
try:
|
|
240
|
+
return float(value.strip())
|
|
241
|
+
except ValueError:
|
|
242
|
+
return None
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def boolean(value):
|
|
247
|
+
"""Coerce one metric value to bool, or None when unclear.
|
|
248
|
+
|
|
249
|
+
Accepts 1/0, true/false, yes/no, pass/fail, and match/mismatch
|
|
250
|
+
spellings (case-insensitive).
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
value: Raw metric value.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
True, False, or None when the value is not a verdict shape.
|
|
257
|
+
"""
|
|
258
|
+
if isinstance(value, bool):
|
|
259
|
+
return value
|
|
260
|
+
if isinstance(value, (int, float)):
|
|
261
|
+
return bool(value)
|
|
262
|
+
if isinstance(value, str):
|
|
263
|
+
word = value.strip().lower()
|
|
264
|
+
if word in ("1", "true", "yes", "pass", "passed", "match",
|
|
265
|
+
"equivalent", "proven"):
|
|
266
|
+
return True
|
|
267
|
+
if word in ("0", "false", "no", "fail", "failed", "mismatch",
|
|
268
|
+
"inequivalent", "unproven"):
|
|
269
|
+
return False
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _prefixed_numerals(metrics, *bases):
|
|
274
|
+
"""Collect numeric values for exact or corner-suffixed keys.
|
|
275
|
+
|
|
276
|
+
LibreLane 3.x writes corner-suffixed keys
|
|
277
|
+
(`timing__setup__ws__corner:nom_tt_025C_1v80`); older steps
|
|
278
|
+
write the bare base name. Both are machine evidence.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
metrics: Flat metric mapping.
|
|
282
|
+
bases: Base key names to accept exactly or with a
|
|
283
|
+
`__corner:*` (or any `__`-suffixed) extension.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
(key, numeral) pairs for every matching numeric entry.
|
|
287
|
+
"""
|
|
288
|
+
found = []
|
|
289
|
+
for key, value in metrics.items():
|
|
290
|
+
for base in bases:
|
|
291
|
+
if key == base or key.startswith(base + "__"):
|
|
292
|
+
numeral = number(value)
|
|
293
|
+
if numeral is not None:
|
|
294
|
+
found.append((str(key), numeral))
|
|
295
|
+
break
|
|
296
|
+
return found
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def sta_verdict(metrics):
|
|
300
|
+
"""Judge post-PNR timing from STA machine metrics.
|
|
301
|
+
|
|
302
|
+
Worst slack wins across corners: any negative corner slack
|
|
303
|
+
fails the gate.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
metrics: Merged STA step metrics (ws/tns slack keys,
|
|
307
|
+
bare or corner-suffixed).
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
(ok, detail): ok True/False, or None when no STA metric key
|
|
311
|
+
is present; detail names the deciding key or the keys
|
|
312
|
+
seen.
|
|
313
|
+
"""
|
|
314
|
+
slacks = _prefixed_numerals(metrics, *(STA_WS_KEYS + STA_TNS_KEYS))
|
|
315
|
+
if not slacks:
|
|
316
|
+
return None, f"keys present: {sorted(metrics) or 'none'}"
|
|
317
|
+
worst = min(slacks, key=lambda item: item[1])
|
|
318
|
+
if worst[1] < 0:
|
|
319
|
+
return False, f"timing violated ({worst[0]}={worst[1]})"
|
|
320
|
+
return True, f"worst slack {worst[1]} ns ({worst[0]})"
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def drc_verdict(metrics):
|
|
324
|
+
"""Judge foundry-deck DRC from DRC machine metrics.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
metrics: Merged DRC step metrics (violation-count keys).
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
(ok, detail, total): total None when no DRC count key is
|
|
331
|
+
present; detail names the deciding key or keys seen.
|
|
332
|
+
"""
|
|
333
|
+
seen = []
|
|
334
|
+
for name in DRC_KEYS + DRC_ERROR_KEYS:
|
|
335
|
+
key, value = lookup(metrics, name)
|
|
336
|
+
numeral = number(value) if key is not None else None
|
|
337
|
+
if numeral is not None:
|
|
338
|
+
seen.append((key, int(numeral)))
|
|
339
|
+
if seen:
|
|
340
|
+
total = sum(count for _, count in seen)
|
|
341
|
+
names = "+".join(key for key, _ in seen)
|
|
342
|
+
if total > 0:
|
|
343
|
+
return False, f"{total} DRC violations ({names})", total
|
|
344
|
+
return True, f"DRC total 0 ({names})", total
|
|
345
|
+
return None, f"keys present: {sorted(metrics) or 'none'}", None
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def lvs_verdict(metrics):
|
|
349
|
+
"""Judge LVS from LVS machine metrics.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
metrics: Merged LVS step metrics (match keys).
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
(ok, detail, match): match None when no LVS match key is
|
|
356
|
+
present; detail names the deciding key or keys seen.
|
|
357
|
+
"""
|
|
358
|
+
seen = []
|
|
359
|
+
for name in LVS_MISMATCH_KEYS:
|
|
360
|
+
key, value = lookup(metrics, name)
|
|
361
|
+
numeral = number(value) if key is not None else None
|
|
362
|
+
if numeral is not None:
|
|
363
|
+
mismatched = int(numeral)
|
|
364
|
+
seen.append((key, mismatched))
|
|
365
|
+
if mismatched > 0:
|
|
366
|
+
return False, f"{mismatched} unmatched ({key})", False
|
|
367
|
+
if seen:
|
|
368
|
+
return True, f"netlists match ({seen[0][0]}=0)", True
|
|
369
|
+
for name in LVS_KEYS:
|
|
370
|
+
key, value = lookup(metrics, name)
|
|
371
|
+
verdict = boolean(value) if key is not None else None
|
|
372
|
+
if verdict is not None:
|
|
373
|
+
if verdict:
|
|
374
|
+
return True, f"netlists match ({key})", True
|
|
375
|
+
return False, f"netlists do not match ({key})", False
|
|
376
|
+
return None, f"keys present: {sorted(metrics) or 'none'}", None
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def equiv_verdict(metrics):
|
|
380
|
+
"""Judge equivalence from EQY machine metrics.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
metrics: Merged equivalence step metrics (proven keys).
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
(ok, detail, proven): proven None when no equivalence key
|
|
387
|
+
is present; detail names the deciding key or keys seen.
|
|
388
|
+
"""
|
|
389
|
+
for name in EQY_KEYS:
|
|
390
|
+
key, value = lookup(metrics, name)
|
|
391
|
+
verdict = boolean(value) if key is not None else None
|
|
392
|
+
if verdict is not None:
|
|
393
|
+
if verdict:
|
|
394
|
+
return True, f"equivalence proven ({key})", True
|
|
395
|
+
return False, f"equivalence failure ({key})", False
|
|
396
|
+
return None, f"keys present: {sorted(metrics) or 'none'}", None
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def missing_reason(stage_id, label, metrics):
|
|
400
|
+
"""Loud missing-machine-file reason for one gated stage.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
stage_id: Gate id (D22/D25/D26/D27).
|
|
404
|
+
label: Short metric kind (STA/DRC/LVS/equivalence).
|
|
405
|
+
metrics: Merged step metrics (possibly empty).
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
Reason naming the machine files plus the keys seen.
|
|
409
|
+
"""
|
|
410
|
+
seen = sorted(metrics) or "none"
|
|
411
|
+
return (
|
|
412
|
+
f"no {label} machine-file metrics "
|
|
413
|
+
f"(state_out.json/metrics.csv) for {stage_id}; "
|
|
414
|
+
f"keys present: {seen}"
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _read_json(path):
|
|
419
|
+
"""Decode one JSON file, or None when absent/unparseable.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
path: JSON file path.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Decoded value, or None.
|
|
426
|
+
"""
|
|
427
|
+
try:
|
|
428
|
+
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
429
|
+
except (OSError, ValueError):
|
|
430
|
+
return None
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _read_csv(path):
|
|
434
|
+
"""Decode one Metric,Value CSV file into a flat mapping.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
path: CSV file path with a Metric,Value header row.
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
Key to coerced (number-or-string) value mapping.
|
|
441
|
+
"""
|
|
442
|
+
try:
|
|
443
|
+
rows = Path(path).read_text(encoding="utf-8").splitlines()
|
|
444
|
+
except OSError:
|
|
445
|
+
return {}
|
|
446
|
+
parsed = {}
|
|
447
|
+
reader = csv.DictReader(rows)
|
|
448
|
+
if reader.fieldnames != ["Metric", "Value"]:
|
|
449
|
+
return {}
|
|
450
|
+
for row in reader:
|
|
451
|
+
try:
|
|
452
|
+
key = (row["Metric"] or "").strip()
|
|
453
|
+
raw = (row["Value"] or "").strip()
|
|
454
|
+
except (AttributeError, KeyError):
|
|
455
|
+
continue
|
|
456
|
+
if not key:
|
|
457
|
+
continue
|
|
458
|
+
try:
|
|
459
|
+
parsed[key] = float(raw)
|
|
460
|
+
except ValueError:
|
|
461
|
+
parsed[key] = raw
|
|
462
|
+
return parsed
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# KLayout DRC adapter
|
|
2
|
+
|
|
3
|
+
KlayoutDrcAdapter serves run_drc when deck.tool is klayout. It supports synthetic width/space rules and foundry .lydrc decks. For a foundry deck it runs KLayout in batch mode and parses the report database; for synthetic rules it renders a pya check script and normalizes the findings.
|
|
4
|
+
|
|
5
|
+
The adapter is registered in Engine. The implementation supports synthetic GDS rules and the .lydrc report-database path. Qualification remains process-specific: readiness depends on the registered PDK deck and installed KLayout; use get_capabilities before relying on a deck.
|
|
6
|
+
|
|
7
|
+
KLayout can exit zero when violations are present. The verdict comes from parsed output, never the exit code alone. The older Ruby-DSL probe describes a separate batch limitation; XML-macro .lydrc decks use the current batch recipe. See the [KLayout DRC probe](../../../docs/probe-klayout-drc.md).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Magic DRC adapter
|
|
2
|
+
|
|
3
|
+
MagicDrcAdapter serves run_drc with deck.tool set to magic, the current default. It loads a cell, runs headless DRC, and parses rule messages and bounding boxes into normalized violations.
|
|
4
|
+
|
|
5
|
+
The adapter is registered in Engine. Its documented qualification uses Magic 8.3.685 with the scmos technology. Process-specific support depends on a loadable technology and deck; query get_capabilities and heed preflight results before promising PDK coverage.
|
|
6
|
+
|
|
7
|
+
Magic may exit successfully despite DRC errors or a bad cell. The verdict comes from parsed output, not the process exit code. The alternate KLayout adapter handles synthetic rules and foundry .lydrc decks.
|