r2g-skills
ShenShan123/r2g-skills/CLAUDE.md
AI-driven open-source EDA flow: natural-language spec → GDSII via OpenROAD-flow-scripts (ORFS), with full signoff (DRC, LVS, RCX), then a training-ready graph dataset for GNN predictors. Implemented as the r2g-skills Claude Code skill collection — four sub-skills (signoff-loop + def-graph from the 2026-07-07 split, see docs/superpowers/plans/r2g-skills-split-2026-07-07.md; eda-install added 2026-07-08, see docs/superpowers/plans/r2g-skills-bootstrap-2026-07-08.md; rtl-acquire ingested 2026-07-09, see docs/superpowers/plans/rtl-acquire-ingestion-2026-07-09.md): - eda-install — detects the machine and installs + verifies the toolchain the others run (ORFS + openroad/yosys, iverilog, klayout, magic/netgen, sky130A PDK, torch venv).…
# Agent-with-OpenROAD — Project Guide
AI-driven open-source EDA flow: natural-language spec → GDSII via OpenROAD-flow-scripts
(ORFS), with full signoff (DRC, LVS, RCX), then a **training-ready graph dataset** for GNN
predictors. Implemented as the `r2g-skills` Claude Code skill collection — **four sub-skills**
(`signoff-loop` + `def-graph` from the 2026-07-07 split, see
`docs/superpowers/plans/r2g-skills-split-2026-07-07.md`; `eda-install` added 2026-07-08, see
`docs/superpowers/plans/r2g-skills-bootstrap-2026-07-08.md`; `rtl-acquire` ingested 2026-07-09, see
`docs/superpowers/plans/rtl-acquire-ingestion-2026-07-09.md`):
- **`eda-install`** — detects the machine and installs + verifies the toolchain the others run
(ORFS + openroad/yosys, iverilog, klayout, magic/netgen, sky130A PDK, torch venv). One command,
`bootstrap.sh` (detect → plan → install → pin `env.local.sh` → verify); no-sudo conda path by default.
- **`rtl-acquire`** — the RTL corpus supplier, UPSTREAM of the others: discovers/screens/acquires RTL
at corpus scale (local trees, repo manifests, keyword search) and expands it **synth-only** into
pre-layout `netlist_graph.pt` graphs with dedup, quality scoring, and publish gating. Owns
acquire + corpus publish + the one-click **promote** of a synth-proven candidate into a
signoff-loop full-flow project (`scripts/promote/promote_candidates.py`, 2026-07-10); BORROWS
env (`_env.sh`), synth (`run_orfs.sh`, `ORFS_STAGES=synth`),
the graph format (def-graph `netlist_graph.py`), and failure learning (`knowledge.sqlite`,
runs stamped `flow_scope='synth_only'`; frontend classes land as `synth-frontend-*` events).
- **`signoff-loop`** — drives the flow RTL→GDS with full signoff *and* the self-improvement loop
(the two memory DBs + `engineer_loop`) that eliminates DRC/LVS violations and closes timing at Fmax.
- **`def-graph`** — converts the clean, signed-off physical design (the ORFS `6_final.odb`/`.def`/
`.spef` + platform liberty/LEF) into PyTorch-Geometric graph datasets: five graph views (b–f), emitted
as `HeteroData` by default (2026-07-16; `R2G_GRAPH_KIND=homo` for the legacy flat tensors), the
shared tech-lib/LEF/DEF parser, and feature (X) / label (Y) extraction — labels are congestion,
wirelength, per-path timing slack, IR drop, and SPEF-derived RC parasitics (the last a `y[N,6]` node
label + a separate `rc_edge_*` parasitic edge set, merged 2026-07-07).
**Each skill has ONE heart; everything else is plumbing** — read the two ⭐ sections below:
1. **`signoff-loop` → The Closed Learning Loop** — the two memory DBs (`knowledge.sqlite` = what
*resulted*, `journal.sqlite` = what was *done*) + `engineer_loop`, the autonomous driver that closes
the wheel unattended (flow → fix → learn → A/B-promote) and learns repair recipes that transfer
across designs/platforms.
2. **`def-graph` → The Dataset-Construction Pipeline** — three composable stages (labels → features →
graphs) keyed to the *same* DEF so X and Y join, whose failure mode is a plausible CSV with silently
*wrong values*.
This file is *orientation*; the skills document *how* to run/debug/tune. **Don't duplicate skill content
or per-run results here** — when you fix a bug, update the relevant sub-skill under `r2g-skills/`
(`signoff-loop/` for flow/signoff/learning, `def-graph/` for dataset construction), not this file. Prefer
editing existing `scripts/` over adding new ones; use the documented steps, not ad-hoc shell, in production.
## Project Layout
```
r2g-skills/ # The skill collection — installs THREE Claude Code skills
install.sh # Installs all four sub-skills (symlinks each into .claude/skills/)
bootstrap.sh # Shim → eda-install/bootstrap.sh (documented one-command setup)
eda-install/ # SKILL 0 — detect + install + verify the EDA toolchain (no-sudo default)
SKILL.md # detect → plan → install → pin env.local.sh → verify
bootstrap.sh # The orchestrator (--dry-run plans; --yes installs)
scripts/setup/ # detect_env.sh, write_env_local.sh (+ install_<tier>.sh)
scripts/flow/ # _env.sh (byte-identical copy), check_env.sh (comprehensive verifier)
signoff-loop/ # SKILL 1 — RTL→GDS flow + signoff + the self-improvement loop
SKILL.md # Workflow, hard rules, env knobs (PLACE_FAST, ROUTE_FAST, …)
scripts/flow/ # Stage runners: run_orfs.sh, run_drc/lvs/rcx.sh, fix_signoff.sh, _env.sh
orfs_hooks/ # ORFS stage-hook Tcl (POST_GLOBAL_PLACE_TCL, …)
scripts/extract/ # Tool output → JSON: extract_ppa/drc/lvs/rcx/route + report_io, presynth
scripts/project/ # init_project, normalize_spec, validate_config
scripts/reports/ # check_timing, diagnose_signoff_fix, fmax_search, build_*
scripts/loop/ # engineer_loop.py — the autonomous campaign driver
scripts/dashboard/ # render_gds_preview, generate/serve dashboard
knowledge/ # The two memory DBs + learn/ingest/A-B Python (self-contained)
references/ # Signoff detailed docs (see "Where to find X")
assets/ tests/ # Templates + bundled platform rule decks; pytest suite
def-graph/ # SKILL 2 — graph dataset construction from signed-off DEF/LEF/SPEF
SKILL.md # Labels → features → PyG graphs (b–f); torch-venv stage
scripts/flow/ # run_labels.sh, run_features.sh, run_graphs.sh, resolve_platform_paths.sh, _env.sh
scripts/extract/techlib/ # Per-platform tech/LEF/liberty/DEF/SPEF parser (shared by both stages)
scripts/extract/{labels,features,graph}/ # Y labels, X features, the five graph topologies (+ odb_to_def)
scripts/r2g2/ # VENDORED R2G2.0 four-stage builder (01..05 + checks/ + encode_map)
# R2G2_UPSTREAM.md = provenance + the 11 local deltas (read before editing)
scripts/stage_dataset/ # our ORFS glue for it: make_sample_config, build_encode_map, emit_timing_reports
references/ tests/ # graph-dataset/feature/label/four-stage docs; def-graph pytest + corner-case suites
rtl-acquire/ # SKILL 3 — corpus-scale RTL acquisition → synth-only netlist graphs
SKILL.md # acquire → expand → repair → validate → publish; scoped-reuse contract
scripts/{acquire,execute,repair,validate,publish,report,hygiene,knowledge}/ # stage-grouped
scripts/execute/expand_candidates.py # per-candidate: run_orfs synth + netlist_graph.pt + ingest
scripts/knowledge/project_frontend_diagnosis.py # journal→knowledge projection + honesty --check
scripts/skill_env.py scripts/flow/_env.sh # thin env delegate over the shared _env.sh
references/ tests/ # policy JSONs, operation matrix, failure KB; pytest suite
tools/ # Repo-level operator tooling + installers (incl. verify_graph_dataset.py)
design_cases/ # All design runs + built datasets (gitignored); _batch/, _dashboard/
```
## Skill Deployment (must be a symlink, not a copy)
Claude Code loads each sub-skill from `.claude/skills/{eda-install,signoff-loop,def-graph,rtl-acquire}/`
(gitignored), **not** the canonical `r2g-skills/` tree. Deploy all four with
`bash r2g-skills/install.sh --project . --link` so each path is a **symlink**. A plain `cp` install
silently goes stale — the harness then loads an old `SKILL.md` while the canonical skill evolves. If
a session's loaded skill disagrees with `r2g-skills/<skill>/SKILL.md`, re-run with `--link --force`.
(Root cause of the 2026-06-08 stale-skill defect.)
## Toolchain (autodetected by the skill)
`<skill>/scripts/flow/_env.sh` autodetects ORFS + tool paths — nothing to source manually. All
**four** sub-skills ship a copy that is **byte-identical** (md5 `ebd7e1f5…`); keep them in sync when
editing any. Override via `$R2G_ENV_FILE`, `<skill>/references/env.local.sh`, or by exporting
`ORFS_ROOT`/`OPENROAD_EXE`/`YOSYS_EXE`/`KLAYOUT_CMD`/… **Required:** python3 (3.10+), yosys, openroad,
ORFS checkout. **Optional:** iverilog/vvp, verilator, klayout, magic, netgen-lvs, opensta, sky130A PDK;
a torch+torch_geometric+pandas venv for the `def-graph` PyG graph-assembly stage only (`R2G_GRAPH_PYTHON`;
`run_graphs.sh` SKIPs cleanly without it, and exits 4 when a configured one cannot start). Verify with `signoff-loop/scripts/flow/check_env.sh`.
**Provisioning is its own skill — `eda-install`** (`bash r2g-skills/bootstrap.sh` is a shim to
`eda-install/bootstrap.sh`): one command to *detect → plan → install → pin `env.local.sh` → verify*
the toolchain. `--dry-run` prints a per-tier plan and installs nothing; without root it auto-selects a
**no-sudo** path (pre-built conda `litex-hub` binaries + a torch venv on a big volume, never a full
`$HOME`). Detection (`eda-install/scripts/setup/detect_env.sh`) + the pin generator
(`eda-install/scripts/setup/write_env_local.sh`, which writes `env.local.sh` into signoff-loop AND
def-graph) are the honesty layer — a bootstrapped env is auto-found next session. Design + rationale:
`docs/superpowers/plans/r2g-skills-bootstrap-2026-07-08.md`.
**This machine:** signoff tools (iverilog/vvp, magic, netgen) live in
`/proj/workarea/user5/miniconda3/envs/eda` (relocated 2026-07-09 from a now-deleted `~/miniconda3` to
free a full `$HOME`; the conda root is on `/proj` too); the sky130A PDK is staged at
`/proj/workarea/user5/sky130_pdk/share/pdk/sky130A`; all pinned in `references/env.local.sh` and green
in `check_env.sh` (enables real sky130 Magic DRC + Netgen LVS).
The graph-stage torch venv is at `/proj/workarea/user5/pyenvs/rtl2graph` (torch 2.12.1+cpu, PyG 2.8.0,
pandas, pytest) — point `R2G_GRAPH_PYTHON` at its `bin/python`. Install recipe in `README.md`.
**Never install large packages into `$HOME` (full) — use `/proj`.** Platforms in this checkout:
`nangate45`, `sky130hd` (default), `sky130hs`, `gf180`, `ihp-sg13g2`. **`asap7` is NOT SUPPORTED in
this version** — `run_orfs.sh` refuses it (exit 65) and `platform_capability.py` reports tier
`unsupported`; it can never read DRC-clean (irreducible false-violation floor, no LVS deck, Calibre
not installed), so it can never promote a recipe. Override for experiments only:
`R2G_ALLOW_UNSUPPORTED_PLATFORM=1`. The nangate45 LVS rule is
bundled (`tools/install_nangate45_lvs.sh`).
## Hard Rules (skill-level)
- **Never run two configs with the same `DESIGN_NAME` + `FLOW_VARIANT` concurrently.** `run_orfs.sh`
derives `FLOW_VARIANT` from the project-dir basename — keep names unique within a `DESIGN_NAME`.
- **Never set `PLACE_DENSITY_LB_ADDON` below 0.10.** Placer divergence is irrecoverable.
- **For >100K-cell designs, never run multiple LVS jobs concurrently** (3-5GB RAM each → 2-3× wall time).
- **Parallel ORFS:** when running flows concurrently, cap per-flow threads with `NUM_CORES` so
`flows × NUM_CORES ≈ cores` — the default grabs `nproc` (96) per flow, so N flows oversubscribe N×
and thrash. `run_orfs.sh` wraps each stage in `timeout` (plain, NOT `setsid` — `setsid` made
timeout a group leader and silently disabled its tree-kill, orphaning a stage that hit
`ORFS_TIMEOUT`; failure-patterns #40, 2026-07-12). `timeout` now group-reaps the whole stage tree
on expiry; a manually-killed driver can still leave the stage's own group running — `kill -9
-<pgid>` the process group, not just the python. A tool process older than `ORFS_TIMEOUT` with
`PPID=1` beside a frozen ledger is a hang alarm the honesty DBs cannot see (a hang writes no run).
Signoff CHECKERS (DRC/LVS/netgen/magic) never use raw `timeout | tee` — they run under
`_bounded_run.sh`'s session supervisor (`r2g_bounded_run`: group TERM→grace→KILL + survivor
reap; failure-patterns #55, 2026-07-24).
- **Escalate to the user before attempting CDC, multi-clock, DFT, or signoff-quality closure.**
Single-clock flows incl. macro designs (`fakeram45`) are supported; the rest is out of scope.
- **Don't skip a failed stage** — diagnose first via `references/failure-patterns.md`. The strict
flow order (spec → … → RCX → reports) lives in `SKILL.md`; **ingest after every flow** (clean,
failed, or partial) so the learning loop sees it.
## The Closed Learning Loop ⭐ (signoff-loop: memory databases + engineer_loop)
The skill *learns from every run*. **Full detail — schema, CLI, numbered invariants — lives in
`signoff-loop/knowledge/README.md`; the autonomous driver in `references/engineer-loop.md`.** Orientation:
### Two memory databases (distinct roles + distinct git status — never conflate)
- **`knowledge.sqlite` — what *resulted*; the durable knowledge+experience, TRACKED** (the committed
binary IS the shipped, pre-trained store; so is `heuristics.json`). One `runs` row per flow
(clean/failed/partial) + derived projections: `failure_events` (one per backend abort/diagnosis,
signature-keyed e.g. `orfs-fail-place-DPL-0024`), `run_violations` (the DRC/LVS/timing **+ backend-abort**
landscape of *every* run), `fix_events`+`fix_trajectories` (every fix attempt incl. *abandoned/failed*
ones — negative learning), `symptoms`+`lessons` (repair experience keyed by a **symptom signature**
`{check,class,predicates}`, NOT a family name — so a fix learned on nangate45 transfers to sky130hd),
`config_lineage`. `learn_heuristics.py`+`mine_rules.py` roll these into `heuristics.json` (Tier-3 recipes).
- **`journal.sqlite` — what was *done*, and why; ALL detailed status+actions, GITIGNORED** (high-volume,
machine-local, rotatable): `actions` (every `loop|agent|operator` action — `config_knob_delta`,
`sdc_edit`, `stage_rerun`, `ab_launch`, `promote`/`demote`, with `parent_action_id` for stacked fixes),
`log_summaries`, `tool_bugs`. The decision ledger; `ingest_run.py` back-fills each row's `run_id`.
**The firewall — the journal never lies into the learner.** Every runtime/inference path and both learners
read ONLY `knowledge.sqlite`, never the journal, so a fresh clone behaves identically off committed
knowledge. The journal contributes *hypotheses* only: it is mined ONLY at ingest time (where it's local)
by a promoter that projects net-new evidence into knowledge **tables** — each mined pattern is a hypothesis
validated against a knowledge-side *outcome* (`runs.outcome_score`, `ab_trials.verdict`,
`fix_trajectories.outcome`); distilled content lands in tables, never directly in
`heuristics.json`/`failure_candidates.json` (both full-rewritten from knowledge each `learn()`/`mine()`).
To share experience across operators, `knowledge_sync.py` exports a git-friendly NDJSON bundle
(`knowledge/store/`, regenerable + **gitignored** — the tracked binary is the shipped store) and `merge`s
another operator's store additively under an honesty gate.
### `engineer_loop` — the autonomous driver (`scripts/loop/engineer_loop.py`)
Drives the whole wheel unattended: **pull design → flow → signoff → fix → ingest → learn → recipe-diff →
A/B arms → verdict → promote/demote.** Unknowns go to the `escalations` queue; the loop NEVER blocks on
them. Recipe lifecycle **`shadow → candidate → promoted`** (or `→ demoted`), gated by a variance-aware LCB
over *k* repeats. Buttons: `learn()` (every rebuild) enqueues new/changed recipes as `candidate` (Gate A);
`ab-drain` plans+runs+judges the A/B arms (arm A control vs arm B forced recipe); `ab-enqueue`
force-validates a grandfathered recipe; `--workers N` / `R2G_AB_WORKERS` runs arms concurrently (cap
`NUM_CORES`, see Hard Rules).
### One turn of the wheel
1. Flow → extraction scripts emit `reports/*.json`.
2. **Ingest** (`knowledge/ingest_run.py`) writes `runs` + `failure_events` + `run_violations` +
`fix_events`; stamps `run_id` onto the journal.
3. **Learn** (`learn_heuristics.py`, `mine_rules.py`) → symptom-indexed recipes in `heuristics.json`;
genuinely new signatures land in `failure_candidates.json` (human-review queue, never auto-merged into
`failure-patterns.md`). `learn()` also enqueues A/B candidates (Gate A).
4. **Apply** on the next similar issue: `suggest_config.py` (per-family medians, hard-clamped) +
`diagnose_signoff_fix.py` (symptom-keyed, evidence-ranked, cross-platform prior).
5. **Campaign**: `engineer_loop` runs this unattended with A/B-gated promotion + escalation.
> Dated per-wave campaign narratives are NOT kept here (the "no per-run results" rule) — see
> `references/lessons-learned.md` + `failure-patterns.md`. Their durable lessons are the invariants below.
### Honesty invariants (violate one and the loop silently lies)
- **Ingest after EVERY flow** — clean, failed, or partial. A failed run never ingested teaches nothing.
- **`failure_events` mirrors `runs.orfs_status`/`orfs_fail_stage`** — every writer of those columns (live
ingest, `repair_run_status.py`) must maintain the event. A `fail` run with empty `failure_events` = the
learner is blind to the whole backend-failure class.
- **The A/B machinery must be EXECUTED + VERIFIED, not just shipped** (Gate A). For every run that fails a
stage or leaves signoff incomplete: confirm `learn()` enqueued a `candidate` in `recipe_status`; actually
run `ab-drain` (or `ab-enqueue`); verify `ab_trials` gained a row and the recipe transitioned. **Empty
`ab_trials` alongside `fail`/`partial` rows is the alarm** — the loop is inert and lying; treat it like an
empty `heuristics.json`.
- **EXECUTED is not enough — the two arms must do DIFFERENT work** (2026-06-24 audit). Arms must not inherit
a clean `reports/` (the copytree excludes `reports/`), a signoff `ab_arm` must always reach `_run_fix`, and
the success-tie cost tiebreak must be variance-aware (`se==0` is MAXIMAL confidence). VERIFY a trial's
`metrics_json` shows the arms diverging (not identical `is_success`+`outcome_score`+`fix_iters`);
`_symptom_check` routes by strategy (place vs timing vs DRC/LVS/route); an `inconclusive` verdict NEVER
demotes; `recipe_status` is a function of the FULL `ab_trials` corpus, not the last trial. Detail:
`failure-patterns.md` ("Learning-Loop Closure Failures") + `docs/…/r2g-loop-closure-audit-2026-06-24.md`.
- **Concurrent ingests share one file** — `connect` arms a `busy_timeout` so a lock waits instead of
erroring; confirm the row landed.
- **A design can have many runs.** Reconciliation/repair touches only the **latest-ingested row per
project**; older rows are immutable history (an old `fail` and a new `pass` coexist). Ingest keys `run_id`
on `project_path:ppa.json-mtime` — regenerate `ppa.json` before re-ingesting a fixed design.
- **`heuristics.json` is advisory + safety-clamped**; lineage/observability panels (`build_lineage_view.py`)
are READ-ONLY projections, never auto-tuners.
- **A cross-operator `merge` is honesty-gated, never trusted** — ADDITIVE (dedups by portable `symptom_id`
+ per-table content keys; surrogate ids re-assigned, never a merge key), in ONE transaction ROLLED BACK
if `honesty.run_all` fails post-merge or the bundle has dangling FKs (incl. the inverse-H3 gate: an
`orfs-fail-%` event landing on a `partial` run via a run_id collision). Run the gates over the REAL
committed store in CI: `python3 knowledge/honesty.py --db knowledge/knowledge.sqlite`. Detail:
`knowledge/README.md` (invariants 26-27).
**Fast honesty check:** `count(runs where orfs_status='fail')` must equal the count carrying an
`orfs-fail-%` `failure_event`; once the corpus has `fail`/`partial` rows, `ab_trials` must be non-empty —
AND, once trials exist, `promoted` must eventually grow **per-platform** (an `ab_trials`-grows-but-
`promoted`-flat-for-a-whole-platform state is the 2026-06-24 arms-identical alarm — subtler than empty
`ab_trials`). The dashboard's **Knowledge Store Health** panel goes red when `heuristics.json` is empty —
that red is the alarm.
## The Dataset-Construction Pipeline ⭐ (def-graph: ODB → PyG graph datasets)
The sibling skill turns a **signed-off** backend run into **training-ready PyG graph datasets** for GNN
predictors — autonomously, per design. It reads only physical-design artifacts (`6_final.odb`/`.def`,
optional `6_final.spef`, platform liberty/LEF; `odb_to_def.py` bridges ODB→DEF where needed) and **never
runs or fixes PnR** — produce those inputs with `signoff-loop` first. **Full detail lives in
`def-graph/SKILL.md` + `references/{label,feature}-extraction.md` + `graph-dataset.md`.** Orientation:
### Three composable stages (all keyed to the SAME `6_final.def`)
1. **Labels (Y)** — `run_labels.sh` → `labels/*.csv` + `reports/labels_stats.json`. Per-cell/per-net
regression targets: **congestion** (dense placement util → pure-python scipy-equivalent radius-4
gaussian → orientation-aware bbox mapping → 2-vector `label`/`label_raw`), **wirelength** (routed
centerline length, `log1p` µm), **timing** (per-cell path delay `clk_period − worst_slack` over ALL
STA paths, via OpenROAD), **IR drop** (per-gate, PDNSim), **RC parasitics** (from SPEF: ground cap,
coupling cap, equivalent resistance).
2. **Features (X)** — `run_features.sh` → `features/*.csv` + `reports/features_stats.json`. Per-node
(`nodes_{gate,net,iopin,pin}`), per-edge (`edges_{gate_pin,pin_net,iopin_net}`), and graph-level
(`metadata.csv`) tables.
3. **Graphs** — `run_graphs.sh` → `dataset/{b..f}_graph.pt` + `netlist_graph.pt` + `graph_manifest.json`.
Joins X+Y into the five PyG topologies, emitted as **`HeteroData` by default** (2026-07-16;
`R2G_GRAPH_KIND=homo`/`both` overrides — see below). The **only** stage needing the torch venv; SKIPs
cleanly with a HINT when absent (so a missing venv looks like success — verify the manifest `status`).
Auto-runs stages 1–2 when their CSVs are stale — freshness judged by the `reports/{features,labels}_stats.json`
stage-completion markers (written LAST), **not** an early CSV (the 2026-07-05 irdrop half-finish incident).
### The SECOND dataset product — the R2G2.0 four-stage causal graphs (2026-08-01)
`scripts/flow/run_stage_dataset.sh` builds **four `HeteroData` graphs per design** — `floorplan`,
`placement`, `cts`, `route` — from the *same* backend run: one shared canonical topology taken from
`1_2_yosys.v` (NOT the DEF, so the node set is identical in all four and a resizer-deleted gate stays
a node with `placement_valid=0`), one shared set of post-route labels, and per-stage features limited
to what that prediction cutoff could legally see (`2_floorplan`/`3_place`/`4_cts` DEF respectively;
floorplan reads no DEF). `5_route.def`/SPEF/STA are **label-only**. Labels here are RAW physical
values with a boolean `y_valid_mask` — deliberately NOT the `y`/`y_raw` log-twin convention of b–f;
don't "align" them. The upstream logic is vendored verbatim in `scripts/r2g2/`; **read
`scripts/r2g2/R2G2_UPSTREAM.md` before editing it** — a new drop is re-vendored and the eleven recorded
deltas (D1–D11) re-applied, never hand-merged. Detail: `references/four-stage-dataset.md`.
Its honesty gate is *two* checkers, both mandatory: `checks/validate_four_stage.py` (contract, NaN↔
valid parity, stage input whitelist, route-DEF leakage) and `checks/summarize_four_stage_graph_data.py`
(`structural_issues=0`). A built `heterograph.pt` is not evidence on its own.
### The data contract (never break this)
- X and Y read the SAME `6_final.def`, so rows **join on `graph_id`(=`DESIGN_NAME`) + `inst_name`/`net_name`**.
Overriding the DEF is via the namespaced `R2G_DEF` ONLY — the bare ORFS `DEF_FILE` is intentionally NOT
honored (an operator export would silently pin every batch design to one DEF).
- **Default `graph_kind` is heterogeneous** (`HeteroData`): per-type node stores + `(src_type, relation,
dst_type)` edge stores (relation = folded entity — b `connects`; c/d/e/f from `edge_schema`; RC →
`rc_coupling`/`rc_resistance`; view **e**'s pin↔pin gate-vs-net cliques *need* the relation in the key).
The homogeneous `Data` below is still built first as the **verified source of truth** —
`graph_lib.homo_to_hetero` is a value-preserving re-view, `hetero_to_homo` the exact inverse, and
`verify_graph_dataset.py` reconstructs homo (independently) at load so the full homo check surface certifies
it. `netlist_graph.pt` stays homogeneous (shared rtl-acquire artifact). Never make a builder emit hetero
natively — the homo path is where every filter/sort/label-join is verified.
- Tensor schema (uniform across views; the homo source of truth): `x[N,10]` (node_type, graph_id, 8 per-type
feature slots), `y[N,6]` (node_type, congestion, IR drop, timing, wirelength, **RC ground cap `y5`**; NaN
where a label doesn't apply). Folded entities carry features/labels on `edge_attr[E,8]`/`edge_y[E,6]`,
INTERLEAVED `[fwd0,rev0,fwd1,rev1,…]` so pairwise-repeated attr rows align (do not "simplify" back to
`[all-fwd|all-rev]` — audit bug #5). `edge_y[:,5]` stays all-NaN (ground cap is never an edge label). (In
the hetero re-view the redundant `node_type`/`edge_type` col0 is dropped: hetero `x` is width 9, `y`/`edge_y`
width 5; `hetero_to_homo` re-inserts it.)
- **Every label tensor has a RAW twin** (2026-07-14 RTL2Graph alignment): `y_raw`/`edge_y_raw`/
`rc_edge_y_raw` mirror `y`/`edge_y`/`rc_edge_y` slot-for-slot but hold the raw physical value
(EDA-Schema/CircuitNet convention: demand/cap ratio, mV, **path-delay ns** (`Path_Delay_ns`, NOT raw
slack — `Cell_Slack_ns` is `"INF"` off-path → CSV-only), um, fF, Ohm) instead of the normalized log/sqrt
target — the graph carries both so a trainer picks either without a regen. The raw columns already lived
in the label CSVs; the twins just surface them. Invariants the verifier enforces: same shape, NaN-parity
with the normalized twin per slot, and `y[:,3/4/5]==log1p(y_raw)` (timing/wirelength/ground cap),
`rc_edge_y[:,k]==log1p(rc_edge_y_raw)` (congestion/IR use a different base, so no such identity).
- **RC parasitics are LABELS (Y), never features.** Ground cap is the `y5` node label; **coupling cap +
equivalent resistance ride a SEPARATE parasitic edge set** (`rc_edge_index`/`rc_edge_type`/`rc_edge_y[E,3]`,
0=coupling net-pair, 1=resistance intra-net pin-pair), present-but-empty where RC doesn't apply so the
schema stays uniform. RC is populated only when a SPEF exists (RCX ran) → else `rc_health="no_rc_labels"`.
- `cell_type_id` + every `*_type_id` column are **categorical and per-platform** (stable within a platform,
NOT comparable across them) — filter datasets by `platform`.
### The five views (b–f) — progressive folding of the b-view bipartite graph
| View | Nodes kept | Folded into (clique) edges |
| ---- | ---------- | -------------------------- |
| **b** | gate, net, iopin, pin | — (gate-pin, pin-net, iopin-net edges) |
| **c** | gate, net, iopin | pins → gate-net edges (pin features on `edge_attr`) |
| **d** | gate, iopin, pin | nets → pin-clique edges (net features on `edge_attr`) |
| **e** | iopin, pin | gates AND nets → pin-clique edges |
| **f** | gate, iopin | nets → gate-clique edges |
Node layout is **block-positional** (a fixed type-block order per view, mergesort within each block); every
`y`-slice and name lookup assumes that exact order, so changing a sort key or block order silently misaligns
labels with no error. The **clock tree is deliberately not in the graph** — only signal nets (`net_type_id==0`)
survive; power/ground/clock/reset/scan nets, FILL/TAP cells, and gates with no signal pin are filtered. Plus
`netlist_graph.pt` — the pre-layout synthesis-netlist bipartite cell/net graph (from `1_2_yosys.v`), sharing
the feature stage's per-platform `cell_type_id` vocabulary so ids agree across a platform corpus.
### The shared techlib parser (`scripts/extract/techlib/`) — both stages consume it
`profile.py` (per-platform supply voltage / tap patterns / cell-type strategy), `resolve.py` (liberty/LEF/tech
path resolution — same `KEY=VALUE` contract as `resolve_platform_paths.sh`, byte-for-byte), `def_parse.py`
(the single DEF/SDC parser; COMPONENTS order == `nodes_gate` row order), `lef.py` (routing-layer
names/pitch/direction + the RECT-patch-aware `route_segments`), `liberty.py` (cell/pin/net classifiers,
quote-tolerant), `cell_types.py` (`cell_type_id` map — runtime-built from **standard-cell** liberty per
platform, `UNKNOWN=N`, dedicated `MACRO=N+1`; the curated nangate45 map was retired 2026-07-06 and its
import shim deleted 2026-07-09). `R2G_LIB_FILES` (full) and `R2G_SC_LIB_FILES` (std-cell-only = `LIB_FILES` minus `ADDITIONAL_LIBS`)
must both be exported and stay consistent across the feature and netlist-graph stages, or macros collapse to
`UNKNOWN` / `connects_macro_flag` sticks at 0. **Fix a parse bug ONCE here, never inline in a worker copy** —
congestion + wirelength share `route_segments`; metadata + `nodes_pin` + `extract_rc` share the SPEF unit
scaling — so a worker-local patch fixes one consumer and silently leaves the other wrong.
### Honesty / verification invariants (violate one and the dataset silently lies)
- **A `6_final.def` alone is NOT sign-off — the signoff gate blocks unsigned builds** (2026-07-10,
failure-patterns #34). Every stage runs the shared `signoff_gate.py` (drc/lvs/route reports +
the DEF-run's `stage_log.jsonl`, fail-closed on MISSING reports); `run_graphs.sh` enforces,
labels/features warn, `R2G_SIGNOFF_GATE` overrides; the verdict rides the manifest as
`signoff_health` and the verifier fails unrecorded/dirty provenance. Repair/resume lineage is
DIGEST-VERIFIED at gate time (stage contract v2, `stage_artifacts.py`; bytes re-hashed against
recorded parent digests) — null/mismatched/foreign/cyclic lineage hard-blocks (#55).
- **Fail-soft is by design, NOT a pass.** Each stage's workers are independent — a missing input degrades
ONE column and records a per-item status; it never aborts the others. ALWAYS check
`reports/{labels,features}_stats.json` + the manifest's `status`/`label_health`/`rc_health` before
training. A non-empty CSV does NOT mean correct values (`status:"ok_with_label_gaps"` ⇒ ≥1 label file
couldn't join and its `y` slot is all-NaN). The `compute_{label,feature}_stats.py` gates classify
`skipped`/`invalid`/`ok` and are the honesty firewall — an all-NaN or raw-schema CSV must read `invalid`,
never `ok`; never relax `REQUIRED_COLS` to make a raw dump pass.
- **NEVER declare a regenerated corpus good without `tools/verify_graph_dataset.py`** (run with
`$R2G_GRAPH_PYTHON`; `--batch <root>` exits non-zero on any failure). It re-derives every structural +
label expectation from the CSVs with separate pandas code (not `graph_lib`), independently re-parses the
SPEF + raw liberty/LEF/DEF, and recomputes congestion with an independent radius-4 gaussian. **Silent-value
defects are invisible in the manifest's row counts** and have shipped repeatedly. Its checks span three
dimensions — **topology** (all five views b–f), **feature statistics** (column re-derivation + stats-gate
honesty + vocab coverage), and **labels ↔ sign-off reports** (DRC/LVS gate, `ppa.json` geometry,
timing↔SDC, RC/`C_total` vs SPEF; opt-in `--signoff-recheck` re-runs PDNSim for the IR-drop label);
detail + the group functions are in `def-graph/references/graph-dataset.md` ("Comprehensive verification").
Its blind spot — code paths the real designs never exercise — is covered by the synthetic corner-case
suites (`tests/fixtures/corner_synth.py` + `test_corner_case_{pipeline,units}.py`) plus the group-level
clean+negative controls in `test_verify_comprehensive.py` (every new check proven to FAIL on a deliberate
corruption). (Fixture gotcha: a fixture liberty MUST be one-attribute-per-line — the parser uses anchored
`re.match`, so a crammed pin drops direction/clock/cap and the test passes vacuously.)
- **Regenerate stale corpora after any extractor fix** — features AND labels AND graphs. The graph stage's
staleness marker protects a single design, not a whole corpus you edited the code under (RC labels in
particular need a forced label rebuild to backfill).
### Silent-value defect catalog (each shipped once; the guard that now catches it)
This skill's failure mode is a plausible-looking CSV with **wrong values**. Full table:
`signoff-loop/references/failure-patterns.md` "Dataset-Extraction Silent-Value Defects". Landmark cases:
- **Transposed congestion vertical demand** keyed `(y,x)` → ~79.7% of labels wrong on every platform;
guard: demand always keyed `(x_gcell, y_gcell)` + the verifier's independent demand/gaussian recompute.
- **All-NaN IR drop under a manifest `"ok"`** (interrupted PDNSim RAW dump left at the canonical path);
guard: raw→side-path + atomic tmp→rename, and `compute_label_stats` reports `invalid` for a raw/NaN CSV.
- **Quoted-unit liberty defects (sky130)**: cap `"pf"` unparsed (pin caps 1000× too small) + quoted
`direction`/`clock` collapsing 95% of pin types; guard: quote-tolerant regexes across `techlib.liberty`.
- **SPEF↔DEF name-escaping join** dropping 79–92% of hierarchical-net / double-bus-register RC labels (and
the analogous STA-name miss that zeroed every bus-named register's timing); guard: `_deesc` de-escapes all
but `[` `]`, and STA joins on a backslash-stripped canonical name.
- **`connects_macro_flag` / `sum_pin_cap_fF` / `num_drivers`-`num_sinks` / `tracks_per_layer` semantics**:
macro flag stuck 0 (SC-libs ⊇ ADDITIONAL_LIBS), pin-cap inflated ~20× by a driver's `max_capacitance`,
port direction read from the instance instead of the chip, and a pipe-joined string coercing
`global_feat[12]` to 0 — all corrected 2026-07; pre-fix CSVs are wrong.
- **`num_drivers` force-fill fabricated a driver** (`num_drivers=0 → 1`, also overwriting `num_sinks` to
`pin_count-1`) whenever liberty direction parsing missed a driver — masking a parse-miss AND corrupting the
true sink count. Removed 2026-07-14 (matches upstream RTL2Graph + the verifier's own no-fill recompute); a
genuinely undriven/unresolved net now honestly reads `num_drivers=0`. The verifier's `>= 1 on ALL nets`
assert (which depended on the fill) was relaxed to `>= 1 on SOME net` (all-zero-column guard).
- **`hpwl_um` / `pin_x/y_std_um` collapsed every pin onto the instance origin** (a documented approximation);
2026-07-14 added the LEF per-MACRO PIN-geometry parser (`techlib.lef.macro_pin_geometry` + `apply_orient`),
so pins sit at their true orientation-aware in-cell centers (matters for macros). Needs `SC_LEF`/
`ADDITIONAL_LEFS` in the env — `run_features.sh` now exports them; empty ⇒ instance-origin fallback. The
verifier reproduces pin-center HPWL with an independent geometry parse.
- **RECT patch metal misread as route points** inflating wirelength ~100–400×; guard: `route_segments`
strips RECT patch groups → centerline length.
- **nangate45 curated cell map drifted 22 masters** onto `UNKNOWN`; guard: retired for the runtime std-cell
map + `MACRO` id. **Verifier oracle radius mismatch** (r1 vs the r4 kernel) false-failing every build;
guard: `dense_gaussian_r4`.
## Where to Find X
| Question | File |
| ------------------------------------------------------------------- | ------------------------------------------------- |
| How do I install/verify the EDA toolchain (detect → install → pin)? | `r2g-skills/eda-install/SKILL.md` + `references/setup.md` |
| How does the flow run RTL→GDS? | `r2g-skills/signoff-loop/SKILL.md` |
| How do I grow the RTL corpus / expand netlist graphs at scale? | `r2g-skills/rtl-acquire/SKILL.md` |
| Promote a synth-proven corpus candidate to a full-flow project | `r2g-skills/rtl-acquire/scripts/promote/promote_candidates.py` (+ SKILL.md stage 7) |
| rtl-acquire task → script lookup, candidate CSV schema, failure KB | `r2g-skills/rtl-acquire/references/{operation_matrix,candidate_csv_schema,failure_knowledge_base}.md` |
| Memory DBs: schema, CLI, full invariants list | `r2g-skills/signoff-loop/knowledge/README.md` |
| `engineer_loop`: autonomous campaign + escalation + provenance | `r2g-skills/signoff-loop/references/engineer-loop.md` |
| Fix-learning loop (record → learn → apply, symptom index) | `r2g-skills/signoff-loop/references/signoff-fixing.md` |
| Phase-by-phase workflow | `r2g-skills/signoff-loop/references/workflow.md` |
| ORFS backend setup, env knobs, macro designs | `r2g-skills/signoff-loop/references/orfs-playbook.md` |
| Fmax search (loose-first fastest period; place-proxy + deterioration model) | `r2g-skills/signoff-loop/references/orfs-playbook.md` ("Fmax Search") + `SKILL.md` step 5a |
| A specific flow/signoff failure (DRC stuck, route congestion, CDL, …) | `r2g-skills/signoff-loop/references/failure-patterns.md` |
| Historical debug narratives + corpus results | `r2g-skills/signoff-loop/references/lessons-learned.md` |
| How to read PPA / signoff JSON | `r2g-skills/signoff-loop/references/ppa-report-guide.md` |
| How does def-graph build the dataset (labels → features → graphs)? | `r2g-skills/def-graph/SKILL.md` |
| Four-stage causal dataset (floorplan/placement/cts/route HeteroData) | `r2g-skills/def-graph/references/four-stage-dataset.md` + `scripts/flow/run_stage_dataset.sh` |
| Vendored R2G2.0 provenance + the 11 local deltas (D1–D11) | `r2g-skills/def-graph/scripts/r2g2/R2G2_UPSTREAM.md` |
| Dataset label/feature extraction (Y/X CSV columns, units, joins) | `r2g-skills/def-graph/references/{label,feature}-extraction.md` |
| PyG graph datasets (b–f views, tensor schema, RC edges, netlist graph, torch venv) | `r2g-skills/def-graph/references/graph-dataset.md` |
| Per-platform tech handling (voltage, tap cells, layers, liberty, SPEF) | `r2g-skills/def-graph/scripts/extract/techlib/` |
| Verify a built graph dataset vs raw DEF/LEF/liberty ground truth | `tools/verify_graph_dataset.py` (`--batch`) |
| Run EVERY gate condition across all four skills (one command) | `python3 tools/run_v1_validation_registry.py gates` (37 `GC-*`; `lint` / `--dry-run` / `--gate X`) |
| What each gate covers, which are executable vs deferred-formal/operator | `tools/v1_validation_registry.yaml` + `docs/superpowers/plans/V1_ASSURANCE_SPECIFICATION_AND_VALIDATION_PROTOCOL.md` |
| Dataset silent-value defects (transposed congestion, all-NaN IR, cap units, SPEF join) | `r2g-skills/signoff-loop/references/failure-patterns.md` ("Dataset-Extraction Silent-Value Defects") |
| Spec / config / SDC templates | `r2g-skills/signoff-loop/references/spec-template.md`, `r2g-skills/signoff-loop/assets/` |
| DRC/LVS/route fixing (antenna diode, density/route relief, LVS) | `r2g-skills/signoff-loop/references/signoff-fixing.md` |
## When You Fix a Bug
Skill scripts + references are the source of truth — not this file. Steps 1–2 and 4 are common; step 3 and
the verification in step 5 differ by which skill you touched.
1. **Find the existing bucket** — `references/failure-patterns.md` (one section per failure mode) or
`lessons-learned.md` for signoff-loop; the `def-graph` extractor/graph fixes bucket under
failure-patterns.md "Dataset-Extraction Silent-Value Defects". Append a sub-section to an existing mode;
only open a new top-level heading for a genuinely new failure class.
2. **Update the offending script** in `scripts/` to detect + self-heal or emit a clear HINT; reference the
failure-pattern file from the script comments.
3. **Re-validate on the triggering design, then close the honesty loop for that skill:**
- **signoff-loop** — **ingest** (`knowledge/ingest_run.py`) and re-run `learn_heuristics.py` if a new
rule is implied; drive the A/B arms (`engineer_loop ab-drain`) for any fail/partial run.
- **def-graph** — **regenerate the affected `labels/`/`features/`/`dataset/` artifacts** and run
`tools/verify_graph_dataset.py --batch` against ground truth + the `def-graph/tests` suite (incl. the
corner-case pipeline). There is no knowledge-DB ingest on this side.
4. **Commit** with a `feat(skill):`/`fix(skill):` prefix — the commit log is the long-term record.
5. **Verify the skill reflects reality per its ⭐ section's invariants:**
- **signoff-loop** — run ingested; `failure_events` mirrors `orfs_status`; `fix_events`/`fix_trajectories`
captured the attempt; heuristics re-derived; `actions`/`tool_bugs` journaled. A `fail` run with no
`failure_event` is itself a loop bug — fix it, don't paper over it.
- **def-graph** — `reports/{labels,features}_stats.json` classify `ok`/`invalid`/`skipped` correctly;
the manifest `status`/`label_health`/`rc_health` reflect any degraded column; `verify_graph_dataset.py`
is green. A degraded column reported as `ok` is itself a dataset bug — fix the gate, don't paper over it.
The skill must keep evolving with each step on the issue trajectory.
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
No one has posted yet. Be the first.

