Foundry-correlated RC parasitic extraction: a routed layout in, a SPEF parasitic model out.
Vyges open EDA tools. Commercial-grade silicon sign-off capability, built on open standards and plain file formats — and meant to be accessible to everyone, not only teams who can license a six-figure tool.
vyges-extractopens up parasitic extraction.
Docs: docs.vyges.com — this engine's chapter, the cross-engine integration guide (how the four Vyges engines work together and where each plugs into an OpenROAD / LibreLane flow), and the job-file formats. Integrating at the binary level and need help? → https://vyges.com/contact.
On modern nodes the interconnect — not the gate — sets timing and signal
integrity. Static timing analysis only sees that reality if it is handed the
wire resistance and capacitance for every net. That data lives in a SPEF
(Standard Parasitic Exchange Format) file, which something has to produce from
the placed-and-routed geometry. vyges-extract is that step.
In production, extraction means a commercial sign-off extractor,
run with foundry-certified tech files and a field solver for the critical nets —
powerful, but gated behind NDA and six-figure licenses. That gate is a big
reason open silicon stalls around 130 nm. The open option, OpenRCX
(in OpenROAD), is rule/pattern-based and community-calibrated. The hard part was
never writing an extractor — it is correlating one to silicon. vyges-extract
starts in that open tier, behind clean file formats, and is built to be
correlated upward without changing how anyone calls it.
Describe the job, not the script. Extraction and the tools around it are
typically driven by hand-written Tcl control scripts — a recurring source of
silent typos, copy-paste drift, and brittle maintenance. vyges-extract takes a
small declarative job file (.ext: design, DEF, rules, LEF) instead: readable,
diffable, schema-checkable, with no control flow to get wrong. This is a
toolchain-wide property — char, sta-si, and em-ir are configured the same way.
Validate fast, sign off with your tool. vyges-extract emits standard SPEF, so
it drops into any STA / sign-off tool unchanged. Iterate with vyges-extract in the fast
loop, and hand the same design to your commercial extractor for final sign-off parasitics if you
prefer — nothing locks you in. It sits alongside your flow (correlated to OpenRCX
within ~2% on a real block), the fast checker for the inner loop rather than a
replacement for your golden extractor.
Given:
- a routed design (
*.def— the wire geometry), - a per-layer RC rules deck (
*.rules— ohms/µm and fF/µm per metal layer), and - (optional) a tech LEF (
*.lef) for per-layer routing widths,
it emits an IEEE-1481 SPEF (*.spef): per net, the connected pins, the
grounded capacitance, the series resistance, and — the hardest, highest-value
term — the lateral coupling capacitance to neighbouring nets. Grounded R/C
come from per-layer Manhattan wirelength × the rules; resistance is
width-dependent when the deck gives a sheet resistance (R = rsheet × len / width, the width from a per-segment non-default rule if one applies, else the LEF),
otherwise the width-blind res × len. Coupling comes
from geometric adjacency: same-layer segments of different nets that run parallel
and overlap couple by coupling_per_um × overlap × (s_ref/gap), ignored beyond
couple_cutoff. The gap is the true edge-to-edge spacing when a LEF gives
the routing widths (gap = centerline − (w_a+w_b)/2), or the centerline distance
without one. Wires that cross on different layers add an inter-layer term —
interlayer[A,B] × footprint-overlap-area (needs LEF widths). A net routed on a
layer with no rule is a hard error, not silent under-extraction.
*.v ──[ place + route ]──► *.def
*.def ──[ vyges-extract ]──► *.spef
*.v + *.spef + *.lib ──[ STA ]──► timing sign-off
The boundary is files in / files out — no in-process API — so it drops into any flow (LibreLane/OpenROAD or your own) wherever extraction belongs: after detailed route, before timing/SI sign-off.
netlist ─[OpenROAD: place + route]─► *.def ─[vyges-extract]─► *.spef ─► STA
Run it after detailed route (once you have a routed *.def) and before
timing sign-off — static timing analysis can only see wire delay and crosstalk
if it is handed parasitics. Re-run it whenever the routing changes. The SPEF it
emits is exactly what vyges-sta-si (or any STA/SI tool) consumes for net
delay. In the open RTL→GDS flow this is the OpenRCX slot inside LibreLane,
between the router and the timing/SI step.
# build it yourself (std-only, no deps) -- or grab a binary from GitHub Releases:
cargo build --release # std-only, no external deps
# 1. write a per-layer rules deck (see examples/counter/sky130.rules)
# 2. write an extraction job pointing at your DEF + rules
# 3. extract:
vyges-extract run design.ext -o design.spef
vyges-extract run design.ext --json # per-net R/C summary instead of SPEF
vyges-extract check design.ext # validate the job + inputs
vyges-extract demo # print a sample SPEF (no inputs)
# common flags: -o FILE · --json · -q/--quiet · -v/--verbose · -h/--help · -V/--versionA job (*.ext) is a few key: value lines:
design: counter
def: counter.def # routed geometry
rules: sky130.rules # per-layer R/C
lef: counter.lef # optional: routing widths -> edge-to-edge coupling gaps
corner: typical
temp: 25
A rules deck is a whitespace table:
# layer res(ohm/um) cap(fF/um) [coupling(fF/um)] [s_ref(um)]
met1 0.125 0.078 0.050 0.14
via 9.3 # default per-via resistance (ohm)
rsheet met1 0.125 # sheet resistance (ohm/sq) -> width-dependent R = rsheet*len/width
couple_cutoff 2.0 # um — ignore lateral coupling beyond this gap
interlayer met1 met2 0.035 # fF/um^2 areal coupling where layers cross
A complete, runnable example is in examples/counter/;
vyges-extract run examples/counter/counter.ext prints its SPEF.
vyges-extract is open and contains no foundry-confidential data. It runs
out of the box on open PDKs (sky130, gf180) using bundled reference rules.
vyges-extract — OPEN engine (Apache-2.0, contains no fab data)
────────────────────────────────────────────────────────────────────
*.def ─► def.rs ─► rc.rs + tree.rs ─► spef.rs ─► *.spef
▲
└─ published plugin contract
(.rules: ohm/µm · fF/µm · coupling · per-via Ω)
│
loads ONE rules / calibration plugin
│
┌──────────────────────────────┴──────────────────────────────┐
│ │
OPEN reference plugin CERTIFIED per-fab plugins
(in-repo · no NDA) (private · one per fab/node 🔒)
• sky130A (.rules) ✓ M0/M3 validated • vyges-extract-tsmc28
• gf180mcu (.rules) • vyges-extract-sec28
• vyges-extract-micron…
open data, ships with the tool silicon-correlated coeffs +
certified deck — under NDA
sky130A is the starter / reference plugin — open, no NDA, and already proven
by the M0/M3 runs. Today a "plugin" is just the .rules deck you pass on the CLI;
formal per-fab plugin packaging (discovery, signing, repo-per-fab) is the
remaining open item. The calibrated sky130A deck lives at
pdk/sky130A/sky130A.vyges-extract.rules:
its per-layer ground, coupling and shielding terms are fit against the OpenRCX nom golden
across 10 routed blocks, and scored on an eleventh held out of every fit, where total
capacitance lands at 1.00× (ground 0.98, coupling 1.02). Method, harness and the bounds
that come with it in correlation/ground-vs-coupling.md;
the earlier single-block fit it replaced is
correlation/openrcx-counter.md.
Getting sign-off-grade output on a commercial node takes two things beyond the tool running: the result must be correlated to that foundry's silicon, and the foundry must accept the flow under an agreement. Both live in a separate, per-foundry plugin — never in this repository:
- the open tool defines a published rules/calibration contract (the
.rulesschema and its calibration extensions); - a certified per-foundry plugin supplies the silicon-correlated coefficients and rule sets for a specific node, delivered under that foundry's NDA;
- the open engine loads it through the contract and never embeds or references any foundry-confidential infrastructure. Each foundry has its own plugin.
So the engine and the contract are open for everyone, while the per-foundry
correlation is gated to those with the agreement — the same way a commercial
extractor separates its engine from the foundry-delivered techfile, except here
the engine is open. Use vyges-extract today on open PDKs and as an
estimation/verification adjunct on any PDK you have; certified sign-off output on
a commercial node comes with that node's plugin.
The RC model is geometry × rules (rc.rs, coupling.rs): per-net wirelength
per layer × ohm/µm and fF/µm, plus adjacency coupling. Nothing in that math is
std-cell-, clock-, or Liberty-specific — there is no Liberty dependency — so it
is domain-agnostic. What couples extraction to a domain is only the input
format: it consumes routed signal nets as DEF NETS (per-segment layer + Manhattan
endpoints + pins) via the shared vyges_loom DEF reader. Any routed layout in that
form extracts identically.
-
Analog routed layouts supplied as DEF extract today, unchanged.
examples/bias_gen/is a small analog bias generator with a long thin bias line (met1, resistance matters), a high-impedance sensitive node carried up the stack (met1→via→met2), and a wide supply tap (met3) — exercising multiple layers and vias.tests/analog.rsruns extraction and asserts sane RC: R and C > 0 per net, R scales with wirelength, the via adds its rule resistance, and the sensitive node's coupling to the bias line is captured (while the supply tap, beyondcouple_cutoff, is correctly left uncoupled).vyges-extract run examples/bias_gen/bias_gen.ext # -> SPEF vyges-extract run examples/bias_gen/bias_gen.ext --json # -> per-net R/C
-
GDS-only analog (no routed DEF) has two on-ramps. The simplest is to emit a routed DEF from your router and use the validated DEF path above. For raw GDS, an optional
gds → DefNetconnectivity-tracing front-end (src/gds.rs) traces connected wire geometry into the sameDefNetview the RC core consumes: it flattens the GDS, classifies rectangles by a small layer map (GDS layer/datatype → routing name | via), unions touching same-layer wires (cross-layer joins are contact-gated — only where a via rect overlaps both), reduces each net to centerline segments + a via count, and names nets from TEXT labels. It sits strictly aboverc.rs(it producesDefNets; the RC math is untouched) and is exercised bytests/gds_extract.rs(GDS → trace → RC → coupling). Its honest bounds: axis-aligned rectangles (polygon bends bbox'd), via = overlap (no enclosure DRC), and no instance/pin hookup (GDS carries none, so the SPEF uses the lumped form) — see the module header for the full list. For a digital block the routed-DEF path is and remains the primary input.
Scope here is physical RC extraction. Per-net field-solve accuracy (the ±40 % analytic ceiling below) and analog functional/timing sign-off remain external/ research-grade work.
v1 is a rule-based extractor: grounded R/C per net plus per-net-pair coupling caps,
emitted in SPEF as a distributed RC tree built from the routing geometry — routing vertices
become nodes, wire segments become resistors with end-split caps, and via stacks become resistors
between the per-layer sub-nodes, so the SPEF carries genuine internal wire-junction nodes rather
than a star. Segments are split wherever another vertex of the net lands mid-span (a via landing,
a same-layer T-junction), so every net is emitted as one connected RC network; a net whose
geometry will not resolve into one falls back to a lumped star and is counted, not silently
degraded. Node caps and resistances scale back to the calibrated per-net totals, so topology is
added without re-correlating magnitudes. Resistance is width-dependent when the deck supplies
a per-layer sheet resistance (R = rsheet × len / width), else the width-blind res × len.
Coupling extraction is spatially indexed, so cost scales with routed area rather than
net-count squared. Runs fully offline, no external deps, 76 tests green.
The sky130A deck is fitted against the foundry-reference extractor (OpenROAD OpenRCX) on
10 routed sky130 blocks, each scored against the SPEF its own OpenLane run produced.
fft_ctrl_tlul (14 238 nets) was held out of every fit and scored only afterwards:
| ratio vs sign-off | per-net median | |
|---|---|---|
| ground capacitance | 0.98× | 0.95 (p10 0.73, p90 1.20) |
| coupling capacitance | 1.02× | 1.04 (p10 0.83, p90 1.24) |
| total | 1.00× | — |
Ground is neighbour-dependent: shield_k reduces a net's grounded cap by shield_k · Cc_net,
because field terminating on a neighbour is field that did not terminate on ground. Without it a
deck fitted on a sparse block over-states ground on a dense one — which is what a single-block
calibration used to do, by 1.37×.
Regenerate or re-check any of this with correlation/calibrate_coupling.py,
correlation/calibrate_ground.py and correlation/decompose.py; the monthly
correlation (sky130 block) workflow re-runs the held-out comparison against a public design.
- Inter-layer (crossover) coupling is not modelled. The engine supports an
interlayercoefficient, but no shipped deck defines one, so layer-to-layer coupling computes as zero. Measured against the reference this accounts for roughly 2.5 % of its coupling — the pairs it reports and we never find. Fitting crossover explicitly was tried and not adopted: it produces a coefficient ~25× a parallel-plate estimate and widens the per-net spread rather than narrowing it. - The totals are calibrated and validated; the per-mechanism split is not. Compared pair by
pair rather than in total, the deck is uniformly ~20 % low on the net pairs it shares with the
reference and long on small ones, and the two roughly cancel. Do not read the lateral
coefficient as a physical sidewall capacitance, and treat the deck as an empirical fit over
sky130 std-cell digital routing rather than something that necessarily travels to very
different layer usage. Method and evidence:
correlation/ground-vs-coupling.md. - Calibrated to OpenRCX, not to silicon. A sign-off / certified per-fab deck is silicon-correlated and NDA; it is never in this repo.
- Resistance has not been correlated at all. It is the physical tech-LEF value, taken on faith. Capacitance is two calibrated terms deep; R is not.
- li1 and met5 coupling are unfitted — no li1 wire segments and almost no met5 routing exist anywhere in the calibration set, so nothing constrains them. Both carry stated placeholders.
- Per-net spread is the rule-based ceiling. A per-µm coefficient cannot resolve the exact multi-neighbour geometry that a table-based or field-solving extractor does; closing the remaining ±20 % needs true per-net field solving, not more global coefficients.
Same file formats and CLI; same run command, no license.
vyges-extract is a clean, std-only, fully open codebase with honest baselines and a
reproducible correlation harness — a good substrate for student research. Each item below
is a self-contained, publishable direction; the engine's file-in/file-out boundary means a
new method can be dropped in behind the same SPEF output and measured against the existing
baseline.
Several already have a working baseline you can build straight on top of — each item names the code anchor (the file / function that is the foundation and the natural drop-in point).
- 2.5-D field / pattern-matched extraction. Replace the analytic coupling kernel with a
real field solve or a pattern-matched library per net. Open question: close the ±40 %
per-net spread the analytic ceiling leaves, on open PDKs, without foundry data.
Start from: the analytic kernel in
field.rsand its call sitecoupling.rs::extract_coupling— swap the kernel behind the same SPEF output and score with [correlation/openrcx-counter.md]. - Calibration methodology to silicon. A principled, documented procedure to correlate the
rule/field coefficients to measured silicon (not just OpenRCX), with uncertainty bounds.
Start from: the per-layer cap fit and the
correlation/harness — extend it into a silicon-referenced flow + dataset for sky130/gf180. - Geometry-dependent resistance. Width dependence ships (
R = rsheet × len / width), with per-segment widths from non-default rules (NONDEFAULTRULE/TAPERRULE) now resolved by the DEF reader. Remaining: via-array (multi-cut) and per-layer / per-cut via resistance (today one globalviaohm), and non-Manhattan (corner / bend) wire resistance. Start from:RcRules::wire_res()and thersheetrule — add the via/corner terms alongside. - Moment-weighted RC reduction. Model-order reduction (AWE / PRIMA-style) to a compact,
delay-accurate equivalent — with a study of accuracy vs. node count for STA.
Start from: the
RcNetwork(nodes/edges) built intree.rs— add a reduction pass beforespef::render_distributedconsumes it. - Pin-access-accurate attachment. Bind parasitics to true pin-access locations instead of
the tree's leaf vertices, and quantify the delay impact. Start from: the leaf-binding loop
in
tree::build_network— replace the deterministic leaf assignment with LEF/DEFPINScoordinates. - Spatial-scaling & parallelism. Per-net RC, RC-tree construction, and coupling
aggregation now all run in parallel (
-j/--threads, rayon). Coupling is memory-bounded: keyed by net index, with a distinct-pair safety valve (VYGES_MAX_COUPLING_PAIRS) and a band-partitioned aggregation whose per-thread maps sum to ~one serial map (notthreads ×it) and merge bit-identically to the serial sweep — so dense blocks parallelize without exploding memory. The remaining frontier is out-of-core extraction for blocks whose routed geometry doesn't fit in RAM at all (stream the grid / spill the accumulator). Start from: the band partition + grid incoupling.rs::extract_coupling, and the dense-block harness intests/coupling_bench.rs. - Real-DEF scaling benchmark.
tests/coupling_bench.rsmeasures the coupling scan on a synthetic dense band — enough to show the memory bound holds and the parallel scan stays bit-identical, but a synthetic geometry doesn't capture how real routed blocks scale. A nice student-sized study: run the extractor across a ladder of real routed designs (e.g. sky130/OpenROAD-flow-scripts blocks from a small counter up to a full SoC), and plot scan time and peak memory vs. net count, segment count, and routing density, at-j1vs.-jN. Deliverables that would genuinely help: the parallel speedup curve and its knee (where memory bandwidth or the merge caps it), the memory-per-distinct-pair constant on real data, and guidance on when theVYGES_MAX_COUPLING_PAIRSsafety valve should trip. Start from:tests/coupling_bench.rs(swap the synthetic generator for a DEF/LEF/.rulesloader — therunsubcommand already parses those), andVYGES_TIMING=1for the per-phase wall-clock breakdown.
Working on one of these — or want to? We're actively pursuing several of these areas ourselves, but the open frontier is bigger than any one team, and we'd rather build it in the open than wait. If an item here fits your research, a student project, a thesis, or just an itch, we'd genuinely like to hear from you — open an issue, send a PR, or reach us at https://vyges.com/contact. Promising directions become collaborations; the correlation harness makes before/after numbers easy to report in a paper.