DeepDataFlow
Earlgrey in G2:用开源工具和 AI 助手跑百万单元 SoC · 第 4 篇,共 6 篇

在独立工具里做签核

用我们要交出去的文件给完成的芯片做时序分析,揭露了两个只在层次化流程里才出现的 OpenROAD bug。我们修好两个 bug、提交到上游,然后检查了自己的工作。

发布日期 2026-10-22 · 第 4 篇,共 6 篇 · DeepDataFlow

在第 3 篇的最后,芯片 v5 完成了绕线和 finish,15,056 个绕线违例都写成已知的遗留问题。下一步是签核时序。我们用业界的方式来做,而正是这个选择找到了 bug。

签核读的是你要交出去的东西

布局布线工具可以从自己的数据库报告时序,OpenROAD 也做得到。但是交给晶圆厂、或交给层次结构上一层的,是文件:由布局布线工具写出来的网表和寄生参数(SPEF)。签核应该在独立的工具里读取这些文件。所以这个项目的规则是:签核留在独立的工具里。OpenRCX 提取寄生参数,OpenROAD 写出网表和 SPEF,再由独立的 OpenSTA 读取。

在布局布线工具里分析时序,读的是它的数据库。签核读的是写出来的文件。
在布局布线工具里分析时序,读的是它的数据库。签核读的是写出来的文件。

寄生提取需要每一层金属的规则,而 sky130 只有五层。在我们 10 层的替代金属堆叠上,OpenRCX 用原本的规则直接崩溃,而不是报错。所以我们生成了规则,让上面几层复制下面几层(metal 6–8 当作 metal 4,metal 9–10 当作 metal 5),并由此做出四个 corner,cworst 和 cbest 是 ±10%。这些规则是假的,凡是用到它们的数字,我们都会说明。

在 G2 里,寄生提取是一个独立的阶段。它读取完成的设计,每个 corner 写一个 SPEF,并把文件签入设计数据管理器,放在网表旁边。OpenSTA 的场景再从那里取用两者。

2:46

在 YouTube 观看(英文) ↗在 Bilibili 观看(中文) ↗

Bug 1:98,251 个驱动端没有寄生参数

第一次独立的 OpenSTA 运行警告有 98,251 个驱动端没有寄生参数,还有 48,926 条「找不到实例」和 431,493 条「找不到 net」的信息。这些驱动端里大约 77,000 个根本没有绕线,这是正常的。另外 21,000 个是真的有线,而且主要是整棵时钟树。

可是 OpenROAD 用同一个 SPEF 读自己的数据库时,只有 490 个名字对不上。这个文件对写出它的工具没问题,对任何其他工具却是坏的。

最初错误的推论

Claude Code 的第一个答案是:时钟树工具给它的 buffer 取错了名字。这是一个说得通的说法,它还追踪了一个 buffer 来支持。工程师问了另一个问题:同一个文件把那个模块里综合出来的 net 都写对了,带着层次路径。写文件的程序在那里写对了什么,在这里又写错了什么?

这个问题把矛头指向写文件的程序,而不是 CTS。工具在子模块里建立的对象,存的是名字加上它所属的模块。网表的写出程序会读这个模块。write_spef 却没有,所以 CTS 在层次结构里建立的每一个 buffer,写出来时都没有路径。

找到原因的那个问题:和同一个写出程序写对的地方比较。
找到原因的那个问题:和同一个写出程序写对的地方比较。

我们在 OpenROAD 里修好了 write_spef,加上一个回归测试,并跑了原有的层次化测试:60 个全部没有变化。在芯片上,「找不到实例」从 48,926 条变成 0,「找不到 net」从 431,493 条变成 164。整个调查都写成了报告,包括最初错误的推论,以及修正前后的数字。

为什么之前没有出现?在 OpenROAD 里面,数据库是扁平而完整的,所以名字永远对得上。只有读取写出来的文件,才会揭露这个 bug。这就是签核要独立做的主要原因。由此还得到第二条规则:要修数据,而不是绕过去分析时序。

Bug 2:dummy load 放错了模块

最后还剩一个缺口,在时钟树的叶子 net 上。dummy load 是时钟树工具为了平衡分支而加的小单元。在我们的芯片里,每个时钟 buffer 都在 top_earlgrey 模块里,但 CTS 却把它的 14,348 个 dummy load 建立在芯片的最顶层模块,两者之间没有端口。

在 OpenROAD 里数据库是扁平的,所以每一条都还是一条 net。但是网表写出程序会写出两条同名的 net:一条有驱动、没有线,另一条有线、没有驱动。13,566 条时钟 net 就这样被拆开,619 pF 的时钟线电容里,有 206.7 pF(33%)落在错误的那一半。

1:59

在 YouTube 观看(英文) ↗在 Bilibili 观看(中文) ↗

放错模块的 dummy load,会在写出来的网表里把一条时钟 net 拆成两条。
放错模块的 dummy load,会在写出来的网表里把一条时钟 net 拆成两条。

CTS 的修正:把每个 dummy load 建立在它所负载的 buffer 所在的模块里。新的测试还发现了另一件事:OpenROAD 一个现有的参考输出(gated_clock4)早就有同样的拆分,有 18 个 dummy load 在最顶层模块里。参考文件里也可能藏着 bug。其他 53 个 CTS 测试都没有变化。

从 CTS 开始重跑芯片,要超过一天。我们改用一个五分钟的脚本,在完成的 v5 数据库里把 13,553 个 dummy load 移到正确的模块,然后重新做寄生提取和时序分析。

24,531 → 13,531 → 8没有寄生参数的时钟 buffer:修正前、SPEF 修正后、CTS 修正后
142,733 → 112OpenSTA 里「未连接」的警告
98,251 → 73,824没有寄生参数的驱动端,大部分根本没有线

提交到上游

两个 bug 都会影响每一个用 OpenROAD 跑层次化流程的人,所以我们把它们提交到上游:

  • 每个 bug 都缩小到几个单元、用 OpenROAD 自己的测试风格重现,并先在最新的上游版本上确认。
  • 完整的测试套件都和参考输出比对过:rcx、cts 和 dbSta 通过 142 个中的 142 个,rsz 通过 295 个中的 294 个,和修改之前一样。
  • 每个修正一个分支。
  • 在工程师同意之前,什么都没有发布。

结果是两个 issue 和两个 pull request:issue #11839 和 #11840,以及 pull request #11841 和 #11842。截至 2026-10-22,它们都还开着,正在等待审查。

然后,我们检查了自己的工作

G2 工具包里的 OpenROAD 升级之后,较小的沙盒芯片显示了两个问题:pad 环上的地线缺口,以及没有寄生参数的时钟 net。Earlgrey 也有吗?这些检查放进一个测试版本 v5a:两个阶段,不重新布局布线,和其他运行一样记录在版本表里。

1:54

在 YouTube 观看(英文) ↗在 Bilibili 观看(中文) ↗

电源网络。 在 floorplan 上的检查发现 VDD 断开:416 个电源网络图形和一千多个 pad 电源 pin 没有连上。我们的电源网络脚本连到 pad 时,没有指定 pad 的金属层。这是我们自己的错误,从芯片 v2 就藏着,而下一个版本的修法已经知道了。

检查本身也出了问题。断开检查只花了大约一分钟;接着 OpenROAD 的短路检查在一个核上跑了 9 小时 20 分钟,没有任何输出。我们把它停掉,保留它已经写下的结果,并把它记成一个 bug。VSS 没有检查,这也列在清单上。

寄生参数。 在同样的网表和 SPEF 上跑了一个探测。73,824 个没有寄生参数的驱动端听起来很糟,但按原因分类之后,几乎全部都是没有线(71,633 个)或没有负载(1,939 个),或者是端口。只有 120 个真的驱动着东西:这才是真正的缺口,而且很小。

G2 里的 v5a 测试版本:README 里写着目的、结果和下一步,结果也显示在版本表里。
G2 里的 v5a 测试版本:README 里写着目的、结果和下一步,结果也显示在版本表里。

一个发现是坏消息,一个是好消息,两个都写在下一个人会去看的地方:版本表里的一行笔记、README、bug 清单,以及接续页面。

签核的数字,以及附带的说明

两个修正都做了之后,慢速 corner(ss、cworst)顶层 reg2reg 的 setup slack 在 10 MHz 下是 −7.87 ns,而且没有任何寄存器的时钟 pin 缺少时钟。输入输出路径还没有时序例外,所以它们的数字还没有意义,也还没有 hold 报告。这些以及 VDD 的修正,都在芯片 v6 的计划里。

下一篇:工程师和 Claude Code 怎么分工,包括工程师睡着的那个晚上。