Physical verification for integrated circuits: DRC, LVS, ERC and PEX, in one tool.
Most verification tools tell you how many violations they found. The number you actually need is a different one: how much of your chip did the tool look at? An empty violation table and a run that never executed produce the same report, and a tool that cannot tell those apart will eventually sign off a chip it did not check. GPurify reports both. Every rule says whether it ran and how many shapes it examined, and a run that could not check everything you asked it to check does not pass.
| Domain | Implemented | Expressible | Partial | Missing |
|---|---|---|---|---|
| DRC | 56 | 6 | 44 | 0 |
| LVS | 31 | 1 | 15 | 1 |
| ERC | 64 | 4 | 13 | 0 |
| PEX | 9 | 0 | 7 | 0 |
| Flow | 2 | 0 | 8 | 0 |
Each row of docs/check-catalog.tsv is one check a
commercial verification tool performs, with its public source. Expressible
means the existing rule kinds can write it; partial rows say what is still
missing; work not yet started is in the
issues. The table is recomputed
from the catalog by tests/doc_counts.rs, so it cannot drift.
Every check is tested against an answer derived independently of the code (hand-worked values, closed forms, published examples), and wherever an open-source tool performs the same check, against that tool too:
| Checked against | What |
|---|---|
| KLayout | design rules on the corpus, every exhaustive fixture and random layouts (rectilinear, 45-degree, any angle); antenna ratios; layout XOR; fill |
| Foundry KLayout decks | the shipped sky130, gf180mcu and IHP SG13G2 decks, rule by rule, on the foundries' test structures and standard cells |
| Netgen | LVS on every small circuit with every single fault, black boxes, multi-die stacks |
| Magic | analytical resistance and capacitance |
| FastCap, FastHenry | field-solved capacitance and inductance |
| OpenROAD | antenna (check_antennas), IR drop and currents (PDNSim), route-level extraction (OpenRCX), LEF/DEF reading |
| Yosys | floating gates |
| ngspice | Liberty timing and power tables |
The KLayout, foundry and open-tool comparisons run nightly; fuzzing (10 input formats), mutation testing and a coverage floor run with them. How each is set up is in docs/testing-plan.md.
cargo build --releaseThe binary lands at target/release/gpurify. Any stable Rust toolchain builds
it; nix develop gives you a pinned one.
gpurify drc my_layout.gds --deck my_process.deckrules:
met1.min_width: ran, examined 1 shapes, found 1 violations
met1.min_edge_length: ran, examined 4 shapes, found 2 violations
met1.min_spacing: ran, examined 0 shapes, found 0 violations
met1.min_area: ran, examined 1 shapes, found 0 violations
off_grid: ran, examined 4 shapes, found 0 violations
diff.li.max_distance_to_tap: skipped, the layer it names holds no geometry, examined 0 shapes, found 0 violations
...
violations: 4
met1.min_width error layer 7 at (40 nm, 500 nm) measured 80 nm against limit 100 nm on shape 0
met1.min_edge_length error layer 7 at (40 nm, 0 nm) measured 80 nm against limit 100 nm on shape 0
summary:
drc: ran
erc: not selected
lvs: not selected
pex: not selected
1 rules skipped
22 rules clean
4 violations: 4 errors, 0 warnings
Read 1 rules skipped before you read the violation count. One rule skipped
means one rule did not look at your layout, and the run does not pass even
though nothing was found.
| Command | What it checks | Extra input |
|---|---|---|
drc |
Design rules: widths, spacings, areas, density | none |
erc |
Electrical rules: antenna, ties, shorts, power grid | --intent <file> for some rules |
lvs |
Layout against your schematic | a reference netlist, required |
pex |
Parasitic resistance and capacitance | --quasistatic <net>… to field-solve a net; the deck's report lines for point-to-point and net-to-net figures |
all |
Everything | a missing input marks its check skipped, never passed |
lib |
Nothing: writes a hard macro's Liberty .lib per corner, and its Verilog blackbox |
--spec <file>, which also states the accuracy, and --output-dir <dir>; ngspice above interface accuracy |
xor |
Two layouts against each other, layer by layer | a second layout: gpurify xor <layout> <other> --deck <deck> |
fill |
Nothing: writes the layout with density fill added, then reports DRC on it | the deck's fill lines; gpurify fill <layout> <output.gds> --deck <deck> |
enhance |
Nothing: writes the layout with redundant cuts and widened straps, then reports DRC on it | the deck's via_redundancy rules and widen lines; gpurify enhance <layout> <output.gds> --deck <deck> |
system |
Several dies as one assembly: bump bonding, LVS and system ESD | a system file and netlist: gpurify system <stack.json> --reference <netlist> |
compare |
Two parasitic netlists (SPEF or DSPF), net by net | gpurify compare <reference> <candidate> [--spec <compare.json>], the spec stating the tolerance (default 5 %); no layout or deck |
The only choice made on the command line is --quasistatic; the rest are
input and output files and --format. Settings that shape a run live with
what they describe: the deck (report lines, temperature, layers to skip),
lib's macro spec (accuracy) and compare's spec (tolerance). Every option,
the report format and the design intent file are described in
docs/usage.md.
The exit code is 0 only when every check you selected ran and passed. Anything
else is 1: violations found, a rule skipped, an input missing, a file it could
not read. There is no exit code that means "mostly fine".
A deck is a plain-text description of your process: layers, connectivity, devices, the parasitic stack and the rules.
grid 5nm
layer met1 = gds(68, 20)
layer via1 = gds(68, 44)
rule m1.1 width(met1) >= 140nm
rule m1.2 space(met1) >= 140nm
rule m1.6 area(met1) >= 0.083um2
rule via.4a enclosure(via1, met1) >= 55nm
It is strict. Every parameter must be written out, every number carries its unit, and a typo is an error with a line and column, never a rule that silently does nothing. There are 82 design-rule kinds and 57 electrical-rule kinds. docs/deck.md covers the language and every rule.
Four decks ship in pdks/, each citing the rule manual release its numbers
come from and using the foundry's rule ids:
| File | Drawn layers | Rules | Devices |
|---|---|---|---|
ihp_sg13g2 |
75 | 465 | 12 |
gf180mcu |
56 | 328 | 27 |
sky130 |
74 | 302 | 30 |
generic_finfet (ASAP7) |
45 | 239 | 0 |
From Rust, each is also compiled into the library as a constant
(gpurify::pdks::SKY130 and so on), so a tool built on GPurify reads the
same deck instead of a copy. None has been compared against foundry signoff. What each leaves out is
listed at the end of the deck and in docs/limitations.md.
The same inputs produce a byte-identical report on every run. The test suite holds every corpus report as a golden file and runs each twice, and each field solve on one thread and on many, failing on any byte that differs. A report that differs from itself cannot be diffed against yesterday's, so you could not tell a fixed violation from one that merely vanished.
GPurify is tested against 167 GDSII cells, drawn with sky130 layer names on a synthetic deck, each containing a deliberate defect at a known coordinate with a measurement worked out by hand. All 167 agree with their expected answer. The shipped foundry decks are tested separately, on hand-drawn layouts at each rule's limit, and nightly against the foundries' own KLayout decks on their test structures and standard cells; every difference is recorded rule by rule in docs/xcheck/.
DRC ██████████████████████████████████████ 94
ERC ████████████ 30
PEX ███████████ 27
LVS ██████ 16
Expected answers were derived from the geometry and the physics first, then compared against the previous implementation's output. 117 agree, so two independent routes reached the same number. 27 disagree, and each records which side is wrong and why.
Per-rule accounting closes a hole most suites have. In the previous implementation, 45 of 94 design-rule cases asserted only that nothing was found, so a rule that never executed passed all 45 of them:
Cases that would still pass if the rule never ran
before ████████████████████████ 45 of 94 (48%)
now ▏ 0 of 94 (0%)
A suite that reports only pass or fail flatters itself, so each case carries a grade for how much evidence is behind it:
strong ███████████████████████████████████ 123 the rule ran, had real geometry to look at, and the measurement came close to the limit
underivable ███ 11 nothing in the test data determines the answer
blocked ███ 9 the expected answer is sound but the tool cannot currently reach it
vacuous ██ 7 the rule ran but never came close to the limit
skipped ██ 7 the rule reported skipped, and a clean count here would be a lie
weak ██ 7 nothing to check, for a legitimate reason
refused █ 3 the rule correctly refuses this input rather than guessing
Nearly three quarters genuinely discriminates. The rest is accounted for by name, which is not the same as being counted as a pass.
An independent reference matters more than our own suite, so 43 of the 94 design-rule cases were re-run through KLayout 0.30.8, using the nine rule families whose semantics map onto KLayout region operations. Both tools are scored against the same physics-derived answer, independent of either:
GPurify ███████████████████████████████████████████ 43 / 43
KLayout ██████████████████████████████████████████ 42 / 43
That was not GPurify being more correct. The one difference was
DRC_MW_DIAG_FAIL, a shape drawn at 45 degrees: KLayout measured it at 80.6 nm
against a 100 nm limit and flagged it, correctly, while GPurify, then
rectilinear-only, refused the input and scored the point only because refusing
was the documented expectation. GPurify now measures that shape too (80 nm,
floored), and the case expects the measurement.
On the other 42, two independently written tools reach the same number.
Whether a tool falls over on a large design is the question worth asking. Across 1,000 then 10,000 then 100,000 polygons, the cost per polygon falls rather than climbs:
1k 10k 100k
building the layout 283.5 300.6 113.9 ns per polygon
extracting nets 759.5 564.0 358.1
finding shape pairs 22.4 16.3 6.5
Ten times the input costs this much more time:
building the layout 10.6× then 3.8×
extracting nets 7.4× then 6.3×
finding shape pairs 7.3× then 4.0×
linear would be 10.0× 10.0×
quadratic would be 100.0× 100.0×
Nothing here is quadratic, which is the failure mode that makes a tool unusable on a real chip rather than merely slow.
Same rule (min_width, 100 nm, one layer), same files, both tools single
threaded, median of five runs, identical violation counts at every size. The
layouts were written by KLayout itself, so this is reproducible without any file
GPurify prepared for itself:
KLayout GPurify end to end
1,000 pt 1061 ms 4.7 ms 226x
10,000 pt 1083 ms 13.0 ms 83x
100,000 pt 1309 ms 72.4 ms 18x
1,000,000 pt 3653 ms 661.9 ms 5.5x
Most of that gap is not the checking. KLayout pays about 1,056 ms of Ruby and Qt startup per invocation, measured with an empty script, against GPurify's 3.8 ms floor. Subtract each tool's own floor and only the geometry work is left:
100,000 pt KLayout 253 ms GPurify 68.5 ms 3.7x
1,000,000 pt KLayout 2597 ms GPurify 658.1 ms 3.9x
So the honest number is about four times faster on the actual checking, with a much larger margin per invocation in a scripted flow that starts the tool once per cell. At a thousand polygons neither tool is doing enough work to measure.
This is one rule on synthetic rectilinear layout. It does not exercise KLayout's hierarchical or tiled modes and is not a claim about its DRC engine in general. KLayout's DRC is a programmable Ruby DSL, so it can express rules a fixed vocabulary of rule kinds cannot; a rule count is not a comparison between the two.
Everything above is one release build on an Intel i9-14900HX. These timings
are recorded, not enforced; what is enforced is their shape. The nightly tier
times the engine on one generated layout at 1, 4 and 16 times a base size and
fails if the cost per polygon at 16 times is over three times the base's
(tests/scaling.rs), so a path that grows faster than the input fails a build
whatever the machine. cargo run --release --example bench_all reprints the
tables on your own machine, including a per-rule breakdown of which rule costs
what.
This is a working tool that has not been proven against foundry signoff on production silicon. Do not tape out on its verdict alone. On a layout with edges at 45 degrees or any other angle, width, notch, space, area, hole area, edge length, forbidden shapes, inside, contains and enclosure are measured; the other rules refuse such a layer. Several rules measure less than a foundry deck does, and the shipped decks are incomplete. docs/limitations.md lists every known gap.
There is no licence file yet.
| File | What is in it |
|---|---|
| docs/usage.md | Commands, options, reading the report, design intent, parasitics. |
| docs/deck.md | Writing a deck, and what every rule checks. |
| docs/liberty.md | Liberty for a hard macro: accuracy levels, the macro spec, how each number is measured. |
| docs/limitations.md | What GPurify cannot check yet. |
| docs/check-catalog.tsv, docs/check-sources.md | Every check a commercial tool performs, its public source and GPurify's status. |
| docs/klayout-mirror.md | How each rule kind maps onto KLayout, and where the two differ. |
| docs/testing-plan.md | How GPurify is tested: oracles, tiers, findings. |
| tests/fixtures/README.md | The test corpus and how each expected answer was derived. |
cargo test --workspace # the test suite, golden reports included
cargo run --release --example bench_all # the timing tables above