把開源工具修到能用:幾個 fork EDA 的甘苦談

上一篇我把開源 EDA 工具 fork 下來、補完開源那一段,然後用一張整齊的小表把它講完。這一篇是那張表沒講的——亂糟糟的版本:三個從 fork 內部長出來的第一線故事,那些凌晨兩點的 debug 和小小的勝利,以及貫穿它們全部的那一條紀律。

上一篇沒講的那一段

上一篇我攤出一張整齊的小表:哪個開源工具追平了、哪個還差,十二個 fork、三十個已證明的 FAIL→PASS 修復。表格是誠實的,卻也是一種報喜不報憂——它把這些工作弄得看起來很乾淨。並不乾淨。那些平靜的每一列背後,都是一段我在凌晨兩點盯著一份 log、確信是工具壞了、然後慢慢發現原來是用錯了的時光。

所以這一篇是亂糟糟的版本。三個從 fork 內部長出來的故事——那些磨、那些走過的冤枉路,以及看著一支原本會 crash、或會默默說謊的原版工具,終於乾淨跑完時那份不成比例的喜悅。作為一個對這條流程還很陌生的軟體人,我本來預期硬體會很異星;真正讓我意外的,是它有多大一部分其實就是……軟體,只是錯誤訊息更糟而已。

故事一 —— 教 KLayout 讀懂晶圓廠的 DRC deck

晶圓廠的簽核 DRC 規則,是以 SVRF deck 的形式交付的——好幾萬行的幾何規則。KLayout 的 DRC 引擎其實非常出色,你想得到的幾何原語它都有,就是不會講 SVRF。所以那條最直覺的路——「拿開源引擎去跑 foundry deck」——開箱即用的狀態下,根本是斷的。

我決定不把它當死路。我寫了一個活在 KLayout 裡的原生 C++ 引擎,去 parse 一份真實的 foundry deck——約 8.7 萬行、224 層、4,533 條規則——把每一條規則翻成 KLayout DRC 引擎的原生 operation。講起來很簡單。實際上,是把 SVRF 的 operator 語意一個一個解出來,因為只要有一個翻得稍微不對,就會幻想出成千上萬條假違規。

SVRF 簡潔得有點騙人。OVERUNDERUNDEROVER 是形態學上的 close 與 open——把先放大再縮小的順序弄反,該有的圓角會消失、不該有的會冒出來。INTERACT 選的是碰到某個集合的形狀,但「碰到」在剛好重合的邊界上有一堆邊角情況。CUT 的 straddle 規則在意的是一條邊只是擦過邊界、還是真的跨了過去。OUTSIDE EDGE 則會翻轉這個檢查到底作用在多邊形的哪一側。每一個 operator 都是一個下午:讀那份稀疏的規格、實作、跑 deck、看著一萬條幻影違規、二分逼到那一條跟 reference 不合的 rule family、修好、再來一次。

我唯一信得過的驗證方式,是把輸出拿去跟一份 reference run 逐位元組(byte for byte)比對,一個 rule family 一個 rule family 地把不一致的數目往零壓。那份 4,533 條規則的完整 deck 回來是 {'PASS': 4533}0 個違規——端到端跑在 plugin 的 runner 裡、整個迴圈裡沒有任何一張商業授權——的那個早上,是我今年少數幾次真的對著空房間出聲的時刻。

但這個故事最精采的,是它差點出錯的那一刻。在我允許自己相信這個乾淨結果之前,我做了一件我一直逼自己做的事——對抗式自我審查:假設這個 PASS 是假的,想辦法把它弄破。結果它真的破了。有一個角落間距(corner-spacing)的 rule family 之所以回乾淨,不是因為幾何真的乾淨,而是因為我的引擎在建 edge set 的時候,默默漏掉了一個 corner case——它根本沒跑那條檢查,卻照樣回報 PASS。一個沉默的假通過。發生在簽核級的 DRC 上。這種東西,正是那種會讓晶片帶著潛在缺陷流片、而且沒有人察覺的地雷。

我把角落的處理收緊,讓那條規則正確地跑起來,重跑,確認這份 deck 乾淨是因為它真的乾淨,而不是因為我把頭別開了。那件事比 4,533 這個數字本身更讓我在意。一個你不能信的綠燈,比一個紅燈還糟——這一點,我會在下一篇好好再談。

故事二 —— 吃掉 GDS 的那個 crash

第二個故事比較短,也比較火大。在真實設計上——detailed route 之後、把 SPEF 寄生標註回去之後——OpenROAD 的 resizer 會死。不是丟一個錯,是死掉。Signal 11、segmentation fault,process 直接不見。而因為那是發生在原生 code 深處的硬 segfault,流程上層任何 try/catch 都攔不住;runner 只撿到一具屍體,沒有任何輸出。那個設計有真實的 setup 與 slew 違規,正需要 post-route repair 那個 pass 去修——而會 crash 的偏偏就是那個 pass。沒有 repair,就沒有乾淨的時序,而且,因為 crash 發生在寫出之前,根本連一份 GDS 都沒被寫出來。

追一個 segfault,跟追一個答錯,是兩種不同的累。沒有 log 告訴你它當時在想什麼;只有一個它倒下去的位址。最後查出來,是 post-route 寄生的 RC lookup 裡一個 null 解參考——那段 code 假設某個寄生網路的節點永遠存在,這在繞線前是對的,一旦你把真實的 post-route RC 標註回去,就悄悄變成錯的。原版 OpenROAD 就直接走下那道懸崖。

我把那個 lookup 加了防護,讓 repair pass 能在真實 post-route 寄生下活下來,而不是往虛無裡解參考。回報是那種讓凌晨兩點變得值得的:一個最差 slew 有好幾百個違規的設計,在現在能活下來的 repair pass 終於跑完之後,回來是 0。同一個 case 上原版還是 segfault;fork 跑完、寫出 GDS。跟其他所有修復一樣的可重現 FAIL→PASS 證明——跑原版,看它死;跑 fork,看它收斂。

故事三 —— 安靜的一勝:教合成器一個更好的加法器

不是每個故事都是火場。有些只是教一支工具做得聰明一點點的那種安靜的滿足。合成的時候,Yosys 把一塊算術映射成一大攤通用邏輯閘——技術上正確,但臃腫,因為它沒認出那個結構其實是一個可以映射到更好架構的加法器。我教那個 mapping pass(我的 lift_adder)在它撞上製程單元之前,先把那個 pattern lift 成一個正經的加法器結構。

回報就是看著一個臃腫的 netlist 直接縮小——有一處映射從 128 個 cell 掉到大約 73 個——而且關鍵是,它是 correct-by-construction 地縮小:這個轉換是一個保結構的改寫,依定義就保住功能,每一次跑等價檢查都確認無誤。沒有那種可能會錯的小聰明;只是給同一個真值一個更好的形狀。一個只刪不增的 diff,有它特別的一種舒服。

這三件事的共同點

三個很不一樣的下午——一個 C++ DRC 引擎、一個 resizer 裡的 null pointer、一個算術改寫——但每一次的形狀都一樣。開源流程在某個地方有個洞,讓它到不了 production-ready、簽核級的結果,而誠實的做法就是把它 fork 下來、把洞補起來。不是要贏過誰;是要把我自己流程裡開源的那一段補完,讓它能一路跑通。如果有商業工具想接手其中某一步,隨時歡迎它插進來——使用者付費使用它,就跟平台上任何一顆 IP 一樣。這從來就不是站隊的事。

唯一一條我從不放鬆的規矩:每一個修復都要附一份可重現的 FAIL→PASS 證明。 跑沒改過的原版,抓它 crash/默默算錯/變臃腫的樣子;跑 fork 過同一個 case,抓它算對的樣子;兩份並排留著;而且要被獨立重跑過才算數。這條紀律,大概就是這份 commit 歷史為什麼長這樣的主因——從 v1.0.0 到今天的 v1.4.x,六百多個 commit,你若把訊息掃過一遍,大多數不是新功能。它們是某一類 bug,被關掉,附著一張收據。


回頭看,這三個修復本身都不是最難的地方。最難的,是圍著它們的那份紀律——拒絕相信一個我重現不出來的綠燈,還有抓到那一次、我自己的引擎試圖遞給我一個假 PASS。那份直覺,才是這裡真正的產品,比任何單一的 fork 都更是。

還有一個根本的不同:我們的使用者是 AI

前面三個故事都是在補功能。但 fork 開源 EDA 的過程裡,還有一個更根本、卻很容易被忽略的不同:我們幾乎完全不用管 UI/UX。

商用 EDA 有很大一部分的工程投入,是花在把介面做得順手——選單、視窗、快捷鍵、報告排版,讓一個真人工程師能舒服地達成目的。那是對的,因為他們的使用者是人。

而我們 enhance 開源工具的方向只有一個:讓 AI 去用它。使用者是 AI,不是人。所以大多數工具,一行 CLI command 就解決了;很陽春、很土炮,沒有網頁要設計,沒有 App 按鈕要設計,也沒有那些為了「讓人順手」而存在的東西。

這既是優勢,也是劣勢。優勢是我們把所有力氣都留給「功能是否正確、AI 能不能穩定驅動」,不必分神在介面上;劣勢是一個習慣了商用 EDA 那種順手介面的人,第一眼會覺得它簡陋、甚至難用。這沒辦法——使用者是誰,決定了你會怎麼設計,而我們從一開始就選了 AI。

唯一的例外,是我們確實需要一雙眼睛:在 vibe-ic 跑的過程中,看得到它現在走到哪、每一步過了沒。所以我們做了一個很輕的 dashboard——vibe-ic-studio——它不是給終端使用者操作的介面,而是給我們(和 AI)看執行狀況的儀表板。

為什麼我相信它會指數成長:因為中間不再有卡點

我對這條 end-to-end 流程有一個樂觀的預期:它會持續指數式地變好。而我覺得最關鍵的原因,就是我們把中間用到的每一個開源工具都 fork 了下來——所以整條路徑上的每一樣東西,都在我們自己手裡,AI 可以用最直接、最有效率的方式去收斂問題。

就在昨天一天,以我自己的粗略計數,Claude 一口氣補掉了上百個開源 EDA 與商用 EDA 之間的 gap,這種速度,老實說連我自己都有點難以想像。(這是「當天補掉」的數量,跟另一頁那份約 63 項、還在排隊的 backlog 是兩回事——一個是流出量、一個是還沒做的存量。)

加速的原因其實很單純:中間不再有卡點。如果你用的工具還得依賴某個上游 open source——你想再往前,就得等他們 commit、等他們發 release、等他們修 bug——那就是一個卡點。我們之所以堅持端到端,就是不想在流程裡留下任何「要等別人」的卡點;而如果只是拿開源 EDA 直接來跑、卻不 fork 下來,那不過是把卡點從一個團隊轉移到另一個「虛擬的維護團隊」身上,本質上並沒有達到 end-to-end 的初衷。

所以我要的是:AI 能掌握整條路徑、能改動裡面的每一行 code。對我來說,這才是真正的 breakthrough。

也因此我會說:我們的做法,和「直接拿開源 EDA 跑起來」的路線,未來性是不同層級的——當然,如果對方也把自己的 EDA 工具 fork 出來、握在自己手裡,那大家就站在同一條起跑線上。我對任何走這條路的團隊都樂見其成,包括 Kimi;我要指出的,純粹是「有沒有掌握整條工具鏈」這個技術差異,而不是誰比誰強。

下一篇,我想直接把這件事講明白:為什麼在一個你交給 AI 跑的 IC 流程裡,誠實才是護城河——為什麼一個虛報的 PASS,是唯一那種會悄悄跟著你一路跟到矽晶圓的失敗模式,以及要打造一條寧可大聲失敗、也不願默默說謊的流程,需要什麼。