rtl-acquire · diff

git:20260820.56a0143 to git:20260923.9fbe734

1 added, 1 removed. Audit A to A.

---
name: rtl-acquire
description: Discover, screen, and acquire RTL at corpus scale (local trees, repo manifests, keyword search) and expand it — synthesis-only, MANY designs per round — into pre-layout netlist_graph.pt PyG graphs with dedup, quality scoring, and publish gating. Use for growing a training corpus of netlist graphs from found RTL. NOT for taking one design to GDS/signoff (that is signoff-loop) and NOT for post-layout graph datasets from DEF/SPEF (that is def-graph).
---
# rtl-acquire — the RTL corpus supplier
Execute a staged, artifact-first corpus-expansion workflow for discovered RTL:
**acquire → expand (synth-only) → repair → validate → publish**. Prefer
deterministic scripts and policy files; treat the workspace ledgers and
manifests as the source of truth.
For Internet-scale acquisition, use the embedded
`vendor/rtl-expander/` engine. It owns safe repository discovery, immutable
revision acquisition, top/closure recovery, corpus certification, family
deduplication, and scheduler statistics. R2G consumes only a `CERTIFIED`
rtl-expander snapshot through
`scripts/acquire/import_expander_snapshot.py`; never read its live frontier,
queues, mutable manifests, or partially completed rounds directly.
Positioned **upstream** of the other r2g-skills: it feeds a stream of screened,
synthesized, graph-converted designs. It **never runs place/route or signoff**
— hand a promising design to `signoff-loop` for that.
## The scoped-reuse contract (what this skill OWNS vs BORROWS)
OWNS (the heart — genuinely net-new for r2g):
- **acquire/** — discovery/search/clone/screen of candidate RTL at corpus scale
(local `_downloads` trees, repo manifests, keyword search), RAM/macro
exclusion, bundle-aware candidate CSVs, incremental scan ledgers.
- **corpus hygiene + publish** — rtl/netlist signature dedup, repo/design
quality scoring, publish eligibility gating, the merged corpus manifest.
- **repair/** — deterministic frontend repair (include dirs, stubs, memory
limits, template materialization) + the JSON failure casebook (journal-side).
The embedded expander is an acquisition engine, not a fifth R2G sub-skill.
After its certified snapshot is imported, the existing rtl-acquire expansion,
repair, graph, publish, and signoff handoff contracts remain authoritative.
## rtl-expander snapshot handoff
```bash
python3 scripts/run_expansion_round.py \
--expander-corpus-root /path/to/rtl_corpus \
--expander-view public_export_allowed \
--priorities high medium
```
The importer fails closed unless the snapshot is certified and all release,
manifest, source-path, source-byte, commit, and public-license bindings verify.
It emits a digest-bound bridge manifest, which `expand_candidates.py` verifies
again immediately before synthesis.
For a bounded formal Experiment-1 batch, use the complete two-level handoff:
```bash
python3 scripts/run_expansion_round.py \
--expander-corpus-root /path/to/rtl_corpus \
--expander-view public_export_allowed \
--expander-formal-target 25 \
--expander-min-mapped-cells 100 \
--expander-max-mapped-cells-exclusive 100000 \
--expander-max-per-repository 4 \
--expander-defer-high-resource \
--run-retry
```
This mode preserves the certified broad corpus, runs the ordinary R2G
Sky130HD qualification path, rejects incomplete compile collateral, and writes
a deterministic selection balanced across repository, Expander functional
ontology, and mapped-cell stratum. It fails closed when the requested target
cannot be filled. `resource_tier=high` candidates are retained in
`rtl_expander_cost_deferred.csv`; measured candidates at or above the cell
ceiling are retained in `rtl_expander_large_design_track.csv`. Neither class
is deleted or mislabeled as an RTL correctness failure.
Formal artifacts are:
- `workspace/candidates/rtl_expander_qualified_selected.csv`
- `workspace/manifests/rtl_expander_qualified_selection.json`
- `workspace/candidates/rtl_expander_large_design_track.csv`
- `workspace/candidates/rtl_expander_cost_deferred.csv`
The selection manifest binds the candidate CSV, certified bridge,
qualification index, policy, output digest, exclusion reasons, and selected
repository/function/size distributions.
For formal Cold/Warm acquisition comparisons, Cold uses a fresh corpus root.
Warm also uses a fresh corpus root, then imports only a frozen scheduler-memory
artifact with `vendor/rtl-expander/scripts/scheduler_memory.py`. Never reuse a
prior corpus root: that would leak candidates, query cursors, and provider state
in addition to the intended aggregate scheduling evidence.
BORROWS (converged onto sibling sub-skills — never reimplement these here):
- **env/toolchain** — the shared byte-identical `scripts/flow/_env.sh` +
`references/env.local.sh` pin written by eda-install (`skill_env.py` is a
thin delegate over it).
- **ORFS synthesis** — `signoff-loop/scripts/flow/run_orfs.sh` with
`ORFS_STAGES=synth` (per-candidate project dirs; the r2g Hard Rules hold by
construction).
- **graph format** — `def-graph/scripts/extract/graph/netlist_graph.py`
produces every corpus graph (`netlist_graph.pt`); the legacy 30pt converter
is retired (2026-07-09 amendment).
- **failure learning** — every flow ingests into signoff-loop's
`knowledge/knowledge.sqlite` (`R2G_FLOW_SCOPE = synth_only`); frontend
failure classes land as `synth-frontend-<class>` failure_events.
## Default Paths (all overridable, `R2G_ACQUIRE_*`)
- acquire root: `<repo>/design_cases/_rtl_acquire` (`R2G_ACQUIRE_ROOT`)
- downloads root: `$R2G_ACQUIRE_ROOT/_downloads` (`R2G_ACQUIRE_DOWNLOADS`)
- workspace: `$R2G_ACQUIRE_ROOT/workspace` (`R2G_ACQUIRE_WORKSPACE`)
- `candidates/` `scan_state/` `failures/` `quality/` `audits/` `runs/`
`manifests/` — the ledger surfaces
- `synth_projects/<design>/` — per-candidate ORFS project dirs
(constraints/config.mk + constraint.sdc + backend/ + reports/)
- corpus (success root): `$R2G_ACQUIRE_ROOT/corpus` (`R2G_ACQUIRE_OUT`) —
per-design `netlist_graph.pt`, `mapped_netlist.v`, `cell_stats.json`,
`design_meta.json`, plus `index.csv` + `_design_status/`
- ORFS seed corpus (optional): `$R2G_ACQUIRE_ROOT/orfs_seed_designs`
(`R2G_ACQUIRE_SEED_ROOT`)
- merged manifest: `$R2G_ACQUIRE_ROOT/netlist_graph_corpus_manifest.csv`
(`R2G_ACQUIRE_MERGED_MANIFEST`)
## Environment Resolution
Never hand-configure tools here. Resolution order (same as every r2g skill):
shell env > `$R2G_ENV_FILE` > `references/env.local.sh` (written by
eda-install's `write_env_local.sh`) > `_env.sh` autodetection. Verify with
`python3 scripts/skill_env.py` (prints every resolved root/tool) or the
comprehensive `signoff-loop/scripts/flow/check_env.sh`.
Knobs specific to this skill:
- `R2G_ACQUIRE_PLATFORM` — target ORFS platform (default `nangate45`; v1 scope)
- `R2G_ACQUIRE_SYNTH_TIMEOUT` — per-candidate synth timeout s (default 3600)
- `R2G_ACQUIRE_NUM_CORES` — cap ORFS `NUM_CORES` per flow (**Hard Rule**:
concurrent flows × cores ≈ machine cores)
- `R2G_GRAPH_PYTHON` — torch venv for graph conversion + scale reports; when
unset those stages **SKIP with a HINT** and designs record `graph_skipped`
(never `success`)
- `R2G_KNOWLEDGE_DB` — override the knowledge DB (tests only; default is the
committed signoff-loop store)
- `R2G_ACQUIRE_ENABLE_LLM=1` — opt-in for the LLM patch path (default OFF)
## Mandatory Stage Order
### 1. Acquire
Gather candidate RTL from `_downloads`, repo manifests, or prebuilt CSVs.
- `scripts/acquire/discover_download_candidates.py`
- `scripts/acquire/discover_repo_manifest_candidates.py`
- `scripts/acquire/clone_repo_manifest.py`
Outputs: candidate CSV + updated `scan_state/downloads_scan_state.json`.
RAM/hard-macro keywords are **risk markers, never hard rejects** (2026-07-10;
the old whole-text substring reject threw picorv32 away on its formal-only
`RISCV_FORMAL_BLACKBOX_*` macro names): tokenized + comment-stripped matching
lives in `scripts/common/rtl_risk.py`, the flags ride the candidate `notes`
column (`risk_flags=…`), and the synth attempt is the real arbiter — a true
hard-macro dependency fails with evidence and the repair-side classifier
excludes it then. `--retry-excluded` re-emits candidates parked in
`failed_candidates_exclude.csv` (a past failure must not permanently block a
retry after a fix).
### 2. Expand (synth-only, converged)
`scripts/execute/expand_candidates.py` per candidate: sanitize RTL (encoding,
helper modules, iscas89 dff) → write `synth_projects/<design>/` → synth via
**run_orfs.sh** (`ORFS_STAGES=synth`, FLOW_VARIANT = the unique candidate id)
- → sv2v/vhd2vl fallback + LEC-lite when needed → dedup by rtl/netlist
+ → GHDL (`yosys -m ghdl`, then `-fsynopsys`) / vhd2vl / sv2v fallback + LEC-lite when needed → dedup by rtl/netlist
signature → convert via **def-graph netlist_graph.py** → `cell_stats.json`
(liberty-driven seq/comb split) → **ingest into knowledge.sqlite** (every
flow, pass or fail).
Outputs: corpus dir updates, refreshed `index.csv`, `_design_status/`.
Failed candidates in the index always retry on the next run (only
`status==success` skips); `--force` re-runs successes too (regeneration after
a synth/extractor fix). Candidate paths are CWD-proof: `~`/`$VAR` expand, and
relative paths bind to the candidate CSV's directory, then the repo root —
never the caller's CWD (see `references/candidate_csv_schema.md`).
### 3. Repair
- `scripts/repair/classify_failed_candidates.py` → retry vs exclude + class
- `scripts/repair/auto_fix_failures.py` (deterministic first-line)
- `scripts/knowledge/project_frontend_diagnosis.py` — projects the final class
into each failed project's `reports/diagnosis.json` + `fix_log.jsonl` and
re-ingests, so knowledge carries `synth-frontend-<class>` events and the
exclude decision (negative learning)
- casebook/diagnosis/strategy refreshers (journal-side JSON)
### 4. Validate
- `scripts/validate/check_mapped_netlist_duplicates.py` (cross-corpus dedup)
- `scripts/validate/audit_near_duplicates.py`
- `scripts/validate/validate_publish_readiness.py` → `quality/publish_validation.json`
### 5. Publish
- `scripts/report/score_design_quality.py` + `score_download_repos.py`
- `scripts/publish/build_publish_candidates.py` → publish eligibility
- `scripts/publish/refresh_expanded_raw_manifest.py` → the merged
netlist-graph corpus manifest (only when the validation gate passes)
### 6. Snapshot
- `scripts/publish/record_dataset_snapshot.py` → `runs/dataset_snapshot_latest.json`
### 7. Promote (optional bridge to signoff-loop)
`scripts/promote/promote_candidates.py <design …>|--all [--require-publish-eligible]
[--platform P] [--run]` — one-click conversion of a synth-proven candidate
(index `status==success`) into a ready-to-run **full-flow** project under
`design_cases/`: init_project skeleton, RTL **vendored** into `<project>/rtl/`
(self-contained — the synth workspace is cleanable scratch), config.mk from
signoff-loop's template carrying the proven knobs (VERILOG_FILES/INCLUDE_DIRS,
ABC_AREA, SYNTH_MEMORY_MAX_BITS, SYNTH_HDL_FRONTEND, VERILOG_TOP_PARAMS) plus
the floorplan directive (CORE_UTILIZATION, PLACE_DENSITY_LB_ADDON ≥ 0.10) and
WITHOUT `R2G_FLOW_SCOPE=synth_only` (a promoted project ingests full-flow),
constraint.sdc with a detected clock port (virtual-clock fallback), then
**validate_config.py as the readiness gate**. Verdict in
`<project>/reports/promote.json` + provenance in `metadata.json`; `--run`
kicks `run_orfs.sh` immediately. Hand the promoted project to signoff-loop.
### Running a full round
```bash
python3 scripts/run_expansion_round.py --discover --run-retry
# loops: scripts/run_until_empty.py, scripts/search_and_expand_until_target.py
```
Check `workspace/runs/run_manifest_latest.json` + `quality/publish_validation.json`;
a round is fully successful only when publish gating and the manifest refresh
completed as expected.
## Hard Rules
- **Never two concurrent candidates with the same DESIGN_NAME + FLOW_VARIANT.**
The expand stage derives FLOW_VARIANT from the unique candidate id — keep
candidate `design` values unique per round.
- **Cap concurrency**: `R2G_ACQUIRE_NUM_CORES` so flows × cores ≈ machine.
- **Ingest after EVERY flow** — pass or fail. A synth-fail run with no
`failure_event` in knowledge.sqlite is a loop bug (check with
`scripts/knowledge/project_frontend_diagnosis.py --check <db>`).
- **A SKIPped graph stage is not success** — `graph_skipped` designs are not
publish-eligible; provision `R2G_GRAPH_PYTHON` and re-run.
- **Do not treat synthesis success as publish success** — publish gating +
the merged-manifest refresh define publish.
- **Deterministic repair before LLM.** The LLM patch path is default OFF
(`R2G_ACQUIRE_ENABLE_LLM=1` to opt in; OpenAI fallback additionally needs
`OPENAI_API_KEY`). Do not route template-placeholder failures to LLM when
deterministic `template_materialization` applies.
- **Never mutate the ORFS checkout or another skill's tree** — this skill
writes only under its own roots + the knowledge DB via the ingest contract.
- v1 is **nangate45-scoped**; platform generalization is a separate effort.
## Definition Of Success (keep these separate)
- **execution success** — ORFS synth + graph conversion succeeded (`success`
in `index.csv`, loadable `netlist_graph.pt`, `cells > 0`)
- **repair success** — a retry/deterministic fix made a failing design
runnable (closes its fix trajectory as `cleared`)
- **validation success** — dedup/quality/publish checks passed
- **publish success** — design appears in `publish_eligible_designs` AND the
merged manifest refresh included it
- **learning success** — the round's runs are in knowledge.sqlite
(`flow_scope='synth_only'`), synth-fails carry `synth-frontend-*` events
## References
- task → script lookup + command templates: `references/operation_matrix.md`
- full script inventory: `references/script_index.md`
- candidate CSV schema: `references/candidate_csv_schema.md`
- failure KB + taxonomy: `references/failure_knowledge_base.md`,
`references/failure_family_taxonomy.md`
- policy files (candidate/repair/quality/publish/…): `references/*.json`
- ingestion plan + convergence decisions:
`docs/superpowers/plans/rtl-acquire-ingestion-2026-07-09.md`