板子上有两颗大芯片:一颗 Zynq UltraScale+ RFSoC,旁边一颗 Kintex UltraScale FPGA,中间用八条 高速串行线连起来。哪一颗单独都不够大,装不下我们想跑的全部东西。于是计划是:把第二颗芯片变成 第一颗的”扩展仓”——一处放额外逻辑去跑的地方,加上它旁边那块内存,全都跨链路够得着。整个过程 由 AI 在 Python2Verilog 框架下完成,我只在几个关键节点给方向。

这一篇讲的是,第二颗芯片怎么成了第一颗的算力和内存,以及中间那一段——每一次仿真都通过,硬件 却依旧一动不动。

从上往下看的定制板。左边一列是射频接口。Zynq RFSoC 模块在中间偏左,更大的 Kintex FPGA 在中间偏右,四周是内存和高速接口。两颗芯片由串行线连在一起,芯片间的一切都走这些线。
跑这件事的板子。Zynq RFSoC 模块在中间偏左,更大的 Kintex FPGA 在中间偏右。两颗芯片由高速串行线绑在一起,从一颗到另一颗的一切都走这些线。

两颗芯片,一条接缝

两颗芯片之间,只有那八条串行线。第一颗芯片想从第二颗那里得到的任何东西——放一个模块过去跑、 从远端内存里读一个值——都得走这条接缝、再回来。于是整个设计归结成一个问题:这条接缝诚实吗? 如果一个字节出去、回来变了样,建在它上面的一切就都不能信。

这条链路是我们自己写的,刻意做得很薄。板子上厂商自带的那个链路模块没能用起来,于是我们没去靠 一套沉重的协议,而是自己做了一条,让它保持简单。它传的是一帧帧带边界的消息,而每条消息最前面 的那一小段,就说明了它是什么。有的是内存请求:读或写第二颗芯片的内存。有的是控制消息,甚至能 跨着这条链路把远端那颗芯片重启,不用去够任何一根线。还有的就只是给解码器的原始数据——根本没有 一条单独的”开始计算”命令,因为解码器就坐在通路里,一条消息到了,它本身就是解码器的输入。链路 薄,每条消息最前面的那一小段决定接下来发生什么。

我们只用一种真正算数的方式来定这件事:在真实硬件上,对着基准,逐字节比。不是一个自己跟自己自 洽的仿真,而是真芯片,对着一个已知正确的答案核。

三样活,每一样都在真实硬件上核过

到最后,第二颗芯片替第一颗干着三样不同的活,而每一样都用同一种方式验过——在板子上,不是在仿 真里,对着一个已知答案:

  • 一次数据往返。数据跨链路发到第二颗芯片,再回来。逐字节相符。
  • 一个解码器放在远端跑。解码器坐在第二颗芯片上,软信息跨链路进去,算完,硬判决交回来。它和基 准模型比对零错误——而且中间不复位,连着来两次仍然零错,证明它能自己重新就绪。
  • 远程内存,读回来。一个值写进远端内存再读回来,和写进去的一模一样,换一个深地址再来一遍,还 是一样。

这就是这里”扩展仓”的意思。第二颗芯片是一条管道、一个算力节点、一块内存,而这三样都可信,靠的 是同一个道理:每一样都在硬件本身上对着基准核过,不是在一个可能悄悄跟着错误一起自洽的模型上。

所有东西都在骗人的那一段

走到这一步并不顺,而中间这一段值得讲,因为那教训比这块板子活得更久。

链路有一个方向死活不通。接收端盯着一串合法的标记,连续看到足够多,就宣布自己锁定了。一个方向 立刻就锁上了,另一个方向永远锁不上。

于是就查,每一步都有干净的结果。厂商自带的例程在仿真里锁定了。自环仿真一帧数据逐位精确。按真 实接线连好的跨芯片仿真,两个方向都锁定,逐字节精确。把好设计和坏设计做布线后对比,逐引脚完全 相同。物理信号干净得挑不出毛病。电路每一层都查过、都清白,硬件却还是老样子。

这些检查里的每一个,都是一个统计出来的数字——一个锁定、一个通过、一个百分比——而统计把信号本 身是什么给藏了起来。所以 AI 趁链路声称锁定的时候,把导线上连续几个原始字直接抓回来。抓回来是 一个干干净净的循环图案:一个裸时钟,一个方波,上面什么数据都没驮。

一个方波被切成等长的帧。因为帧长除以图案周期有余数,每帧那两位同步头总落在合法值上,于是锁定检测、眼图、合法率三项都被喂饱,导线上却没有数据。
一个裸时钟图案为什么能过掉所有健康检查。切成帧之后,它的头总落在合法值上,锁定、眼图、合法率全读作完美。没有一项去看载荷。

让这件事如此以假乱真的,是一个又小又刁的巧合。切成帧之后,一个纯时钟图案,每一帧那两位同步头 都落在合法值上。于是它满足锁定、满足眼图、满足合法率——同时不带一丁点信息。一个毫无意义的循环 图案,冒充着一条健康的链路。

既然导线上跑的是自由振荡的时钟,那就说明没有人在驱动它。于是 AI 回头把通道接线直接量了一遍, 只认两颗芯片上印着的物理名字,不认工具内部的序号,往每条通道上发一个不一样的编码标记,好把它 们区分开。结果,和所有人一直信着的那份记录,正好相反。

两个面板。橙色的记录映射,通道正着连,第 i 条对第 i 条。绿色的实测映射,通道反着连,第 i 条对第 7 减 i 条。设计监听的那个输入,实际连着一条从未配置的通道,它自由振荡出方波。
左边是所有人都信的记录:通道正着连。右边是实测的真相:通道反着连。发送端把正确数据发给了一条没人监听的通道;监听端连着一条从未配置的通道,它自己振荡出方波。

陷阱就在这里。发送端确实在发正确的数据——只是发给了一条没人在听的通道。而所有人盯着的那个输 入,连着一条从未配置的通道,于是自己振荡起来。每一次仿真都照着同一份记录连线。记录内部自洽, 所以每一次仿真都复现了同一个错误连接,然后通过。一层又一层的验证彼此一致,是因为它们都读了同 一个共享的错误假设。

还有个扎心的细节:那份记录甚至带着一句”已上硅验证”的注记。注记是真的,但它背后的”验证”只是第 一条通道的巧合锁定。答案其实一直画在原理图上。把设计指向正确的两条通道,链路一次就通,逐字节 精确。

而且,这个反接不只是一份记录写反了。它是刻在板子本身里的:两颗芯片之间的那些通道,在铜线上就 是反着连的,改不了。这才是厂商那个现成链路模块在这块板子上根本走不通的真正原因。它非要两颗芯 片先对上通道的顺序,才肯把这些通道合到一起,而铜线上的顺序既然是反的,它就永远锁不上。这正是 我们一开始就自己写一条薄链路的原因:它压根不用那种方式去合并通道,所以通道顺序反没反,它根本 不在乎。

留下什么

当电路每一层都查过、都清白,硬件却依旧一动不动,剩下该怀疑的,就是测量本身。那张反的记录能活 过一整天,只有一个原因:每一次仿真都在老老实实照着它连线。电路从没骗人,骗人的是那张地图。

信硬件,别信那张描述它的文件。用物理名字去读接线,别信表里的序号。而且,别说一条链路通过了, 除非你从它上面解出真实载荷、逐字节核对过——锁定、眼图、一个读作合格的指标,都能被一个什么也不 带的信号满足。

接缝诚实了,剩下的就顺了,第二颗芯片就位,成了第一颗的算力和内存:一块板子,两颗 FPGA,还有一 条你真能信得过的链路。

备注

  • 硬件:一块定制板,把一颗 AMD Zynq UltraScale+ RFSoC(XCZU67DR)和一颗 AMD Kintex UltraScale FPGA(XCKU115)配在一起,用八条高速串行链路相连。
  • 链路层、远程算力与远程内存的逻辑,以及全部排查,都由 AI 在 Python2Verilog 框架下完成。文中提 到的一切,都是在板子上读到的,或在同一次会话里对着基准核对过的。
  • 本文接续《一天,把 Linux 跑上一块 RFSoC 板子》。