Fixing the open tools: a few war stories from forking EDA

Last post I forked the open-source EDA tools to complete the open part of the flow, and laid it out in a neat little table. This is the part the table left out — the messy version: three behind-the-scenes stories from inside the forks, the 2am debugging and the small victories, and the one discipline that runs through all of them.

The part I left out last time

Last post I spread out a tidy little table: which open-source tool has caught up, which is still behind, twelve forks, thirty proven FAIL→PASS fixes. Tables are honest, but they're also a lie of omission — they make the work look clean. It wasn't. Behind every one of those calm rows is a stretch of me staring at a log at 2am, certain the tool was broken, slowly discovering that I was the one holding it wrong.

So this post is the messy version. Three stories from inside the forks — the grind, the false starts, and the disproportionate joy of watching a stock tool that used to crash or quietly lie finally come back clean. As a software person still new to this flow, I went in expecting hardware to feel alien; what actually surprised me is how much of it was just… software, with worse error messages.

Story 1 — Teaching KLayout to read a foundry's DRC deck

A foundry ships its sign-off DRC rules as an SVRF deck — tens of thousands of lines of geometric rules. KLayout's DRC engine is genuinely excellent; it has every geometric primitive you could want. It just doesn't speak SVRF. So the obvious path — "run the foundry deck in an open engine" — is, out of the box, simply broken.

I decided not to accept that as a dead end. I wrote a native C++ engine that lives inside KLayout, parses a real foundry deck — about 87,000 lines, 224 layers, 4,533 rules — and translates every rule into a native KLayout DRC operation. Simple to say. The reality was decoding SVRF's operator semantics one at a time, because every one I got subtly wrong hallucinated thousands of fake violations.

SVRF is deceptively terse. OVERUNDER and UNDEROVER are morphological close and open — reverse the order of the size-up and size-down and rounded corners appear or vanish where they shouldn't. INTERACT selects shapes that touch a set, but "touch" has edge cases right at exact coincidence. A CUT straddle rule cares whether an edge merely grazes a boundary or genuinely crosses it. OUTSIDE EDGE flips which side of a polygon the check even applies to. Each operator was an afternoon: read the sparse spec, implement it, run the deck, watch ten thousand phantom violations, bisect down to the one rule family that disagreed with the reference, fix, repeat.

The only way I trusted any of it was to compare the output byte for byte against a reference run, rule family by rule family, driving the count of disagreements down toward zero. The morning the whole 4,533-rule deck came back {'PASS': 4533} with 0 violations — end-to-end inside the plugin's runner, with no commercial license anywhere in the loop — was one of the few times this year I actually said something out loud to an empty room.

But the best part of this story is the moment it almost went wrong. Before I let myself believe the clean result, I did what I always force myself to do — an adversarial self-review: assume the PASS is fake, and try to break it. And it broke. One corner-spacing rule family was coming back clean not because the geometry was clean, but because my engine was quietly dropping a corner case in how it built the edge set — it was skipping the check and reporting PASS anyway. A silent false pass. On a sign-off DRC. That is exactly the kind of thing that gets a chip taped out with a latent defect and nobody the wiser.

I tightened the corner handling so the rule fired properly, re-ran, and confirmed the deck was clean because it was actually clean, not because I'd looked away. That felt more important than the 4,533 itself. A green number you can't trust is worse than a red one — a point I'll come back to hard in the next post.

Story 2 — The crash that ate the GDS

The second story is shorter and angrier. On real designs — post-detailed-route, with SPEF parasitics annotated back in — OpenROAD's resizer would die. Not throw an error. Die. Signal 11, segmentation fault, process gone. And because it was a hard segfault down in native code, no try/catch up in the flow could contain it; the runner just found a corpse and no output. The design had real setup and slew violations I needed the post-route repair pass to fix — and that pass was precisely the one that crashed. No repair, no clean timing, and, because the crash landed before write, no GDS ever got written at all.

Chasing a segfault is a different kind of tired than chasing a wrong answer. There's no log line telling you what it thought; there's just the address where it fell over. It turned out to be a null-dereference in the RC lookup on post-route parasitics — a code path that assumed a certain parasitic-network node always exists, which is true pre-route and quietly false once you've annotated real post-route RC. Stock OpenROAD walks straight off that cliff.

I guarded the lookup so the repair pass could survive real post-route parasitics instead of dereferencing into the void. The payoff was the kind that makes the 2am worth it: a design whose worst-case slew had hundreds of violations came back, after the now-survivable repair pass finally ran, at 0. Stock still segfaults on the same case; the fork finishes and writes a GDS. Same reproducible FAIL→PASS proof as everything else — run the stock tool, watch it die; run the fork, watch it converge.

Story 3 — A quieter win: teaching the synthesizer a better adder

Not every story is a fire. Some are just the quiet satisfaction of teaching a tool to do something a little smarter. During synthesis, Yosys was mapping a chunk of arithmetic into a sprawl of generic gates — technically correct, but bloated, because it wasn't recognizing the structure as an adder it could map to a better architecture. I taught the mapping pass (my lift_adder work) to lift that pattern up into a proper adder structure before it hit the technology cells.

The reward was watching a bloated netlist simply shrink — one mapping went from 128 cells down to about 73 — and, crucially, shrink correct-by-construction: the transformation is a structural rewrite that preserves function by definition, and equivalence checking confirms it on every run. No cleverness that might be wrong; just a better shape for the same truth. There's a particular kind of contentment in a diff that only deletes.

What the three have in common

Three very different afternoons — a C++ DRC engine, a null pointer in a resizer, an arithmetic rewrite — but the shape is the same each time. Somewhere the open-source flow had a hole that stopped it reaching a production-ready, sign-off-grade result, and the honest thing to do was fork it and fill the hole. Not to beat anyone; to complete the open-source part of my own flow so it runs all the way through. If a commercial tool would rather do one of these steps, it's welcome to plug in — users pay to use it, the same as any IP on the platform. This was never about sides.

The one rule I never let slip: every fix ships with a reproducible FAIL→PASS proof. Run the unmodified stock tool, capture it crashing / silently erring / bloating; run the fork on the same case, capture it correct; keep both side by side; and only count it once it's been independently re-run. That discipline is most of why the commit history reads the way it does — somewhere north of 600 commits from v1.0.0 to today's v1.4.x, and if you skim the messages, most of them aren't features. They're one class of bug, closed, with a receipt.


Looking back, none of these three fixes was the hard part. The hard part was the discipline around them — refusing to trust a green result I couldn't reproduce, and catching the one time my own engine tried to hand me a fake PASS. That instinct is the real product here, more than any single fork.

One more fundamental difference: our user is an AI

The three stories above were all about filling in missing capability. But there's a more fundamental difference in forking open-source EDA, one that's easy to miss: we barely think about UI/UX at all.

A large share of commercial EDA's engineering goes into making the interface pleasant — menus, windows, shortcuts, report layouts — so a human engineer can comfortably get where they're going. That's the right call, because their user is a person.

Our enhancement direction has exactly one goal: let the AI use it. The user is an AI, not a person. So most tools are solved with a single CLI command; it's bare-bones and hand-rolled — no web page to design, no app buttons to design, none of the affordances that exist purely to make a human comfortable.

That's both an edge and a weakness. The edge: every ounce of effort goes into "is the function correct, and can the AI drive it reliably," with little spent on the interface. The weakness: someone used to a polished commercial EDA interface will find it crude, even hard to use, at first glance. There's no way around it — who your user is decides how you design, and we chose the AI from day one.

The one exception is that we genuinely need a pair of eyes: while vibe-ic runs, we want to see where it is and whether each step passed. So we built a very light dashboard — vibe-ic-studio — not an interface for an end user to operate, but a status panel for us (and the AI) to watch the run.

Why I believe it compounds: there's no bottleneck left in the middle

I have an optimistic expectation for this end-to-end flow: it will keep getting better, exponentially. And the key reason, I think, is that we forked every open-source tool the flow uses — so every single thing along the path is in our own hands, and the AI can converge on problems in the most direct, most efficient way.

Just yesterday alone, by my own rough count, Claude closed on the order of a hundred gaps between open-source EDA and commercial EDA. That pace is, honestly, a little hard for even me to picture. (That's the number closed that day — a different thing from the ~63-item backlog still queued on the other page: one is throughput out, the other is the stock not yet done.)

The reason for the acceleration is simple: there's no bottleneck left in the middle. If a tool you depend on still relies on some upstream open source — to move forward you have to wait for their commit, wait for their release, wait for their bug-fix — that's a bottleneck. The whole point of going end-to-end is to leave no "wait-for-someone-else" bottleneck in the flow; and simply running open-source EDA without forking it just shifts the bottleneck from one team onto another "virtual maintenance team," which never really achieves the end-to-end intent.

So what I want is: the AI can command the whole path, and change every line of code inside it. To me, that's the real breakthrough.

And so I'd say this: our approach and the "just run open-source EDA" route are on different levels when it comes to future potential — though of course, if a team has also forked its own EDA tools and taken them into its own hands, then we're all on the same starting line. I welcome any team going down this road, Kimi included; the only thing I'm pointing out is the technical difference of whether you command the whole toolchain, not who's better than whom.

In the next post I want to make the case for it directly: why, in an IC flow you've handed to AI, honesty is the moat — why a fabricated PASS is the one failure mode that can quietly follow you all the way to silicon, and what it takes to build a flow that would rather fail loudly than lie quietly.