这两年聊 AI 服务器,谁也绕不开一个词:Scale-up。特别是大模型把显存和内存吃干榨净之后,单机算力不够就开始堆节点,节点堆到一定程度,瓶颈反而回到了 CPU、GPU、加速卡和内存之间的互连上。Scale-up 域里跑的不再是简单的 PCIe 读写,而是一整套像 CHI、PBR、CXL 这样的互连协议在管一致性、管路由、管缓存状态。这些东西平时藏在芯片里,一调就是两三个月,文档厚得能砸死人,而你真正用到的可能只是其中几个状态机。
这篇文章我想把 Scale-up 互连里六种能拿得到规范、有开源实现或者开放生态的协议,拉到比特和状态机的层面上横着比一遍。会重点拆 CHI 七态、PBR 路由,再带上 TileLink、AXI/ACE、CXL、OpenCAPI/OMI 和 UCIe。内容偏硬核,适合做 FPGA 原型、芯片集成、交换路由设计的工程师,也适合想弄懂服务器里那块总线到底在干嘛的系统软件同学。放心,我不堆术语,尽量把每个动作背后的设计逻辑讲清楚。
1. Scale-up 互连:为什么大家开始啃协议最底层
1.1 从 PCIe 交换到 CXL/CHI:Scale-up 域的需求在变
先说清楚场景。Scale-up 和 Scale-out 的区别,一句话:Scale-out 是加机器,Scale-up 是把一台机器里的 CPU、内存、GPU 连成一个更大的"逻辑单机"。传统 x86 服务器里靠 UPI、QPI 这类私有总线把多路 CPU 连在一起,但那是封闭的,第三方加速卡进不来,内存池也拉不出去。这几年风向变了,大家都在做开放互连的 Scale-up 域,目标是很朴素的:让加速器能访问主机内存,让内存能被池化,让多个计算芯片像一块芯片一样协同。
这个目标一旦落到底层硬件,就跑不了三件事:缓存一致性、路由、链路带宽。缓存一致性决定你在别的地方改了数据,我这个核心能不能立刻看到最新值;路由决定一个地址请求到底该送到哪颗芯片;链路带宽决定一次读内存要等多少个周期。这三件事全部落在互连协议的比特和状态机里。所以你去看 CHI 的规格书,前几章全是缓存状态、消息类型、flit 格式,而不是什么"架构愿景",就是这个原因。
1.2 六个协议不是随便凑的,这是"开放可获取"清单
标题里说"六个开源协议",我得先做个严谨的限定。真正严格开源许可的互连协议其实很少,TileLink 算一个。其余像 CHI、AXI/ACE、CXL、OpenCAPI/OMI、UCIe,更准确的说法是"开放规范,且有可获取的开源参考实现或开源 IP"。比如 Xilinx 的 AXI IP、OpenCAPI 的开源 FPGA 实现、瑞士那边实验室放出来的 AXI 一致性 IP、各个开源的 CHI 验证模型,都是能直接拿到的。所以这六个放在一起比,核心是它们的规范和实现你有渠道看到完整细节,能自己搭环境去验,而不是被一家公司锁死在黑盒里。
这个清单本身覆盖得也全。CHI 是 ARM 高端互连的统一语言,走 CPU 一致性;TileLink 是 RISC-V 生态的轻量通道;AXI/ACE 是 FPGA 和嵌入式 SoC 里的主力;CXL 是 PCIe 物理层上长出来的内存扩展和一致性方案;OpenCAPI/OMI 是 IBM 系为加速器做的开放互连;UCIe 则是 chiplet 之间的物理层协议,没有它上面那些一致性协议都跑不到另一个 die 上。逻辑上正好从"最高的缓存一致性"一路到"最底层的裸管脚"。
1.3 对比的三个维度:状态机、链路层、路由层
我在标题里写了"比特和状态机层面",这两个东西其实就是互连协议的两大灵魂。状态机管的是缓存行和事务的生死:一个请求发出去,缓存行从 Invalid 变 Shared,再变 Modified,中间的每个跳变都必须符合协议规则,否则就是数据竞争和死锁。比特层面管的是链路怎么把消息和一整条缓存行塞进通道:flit 多大、头部字段怎么排、CRC 怎么算、credit 怎么回,直接决定带宽利用率和延迟。
再加上路由,互连协议在 Scale-up 域里不是只有一个点对点链路,它是很多 Request Node 和 Home Node 之间的网状关系。一个地址请求从发起端出发,经过哈希、查表、端口选择,最后落到正确的目标节点,这一路走得对不对,也得靠协议层的路由规则约束。所以后面我拆协议,基本就沿着状态机、链路比特、路由三根线走,这样对比才不会乱。
2. CHI 七态全解:缓存状态机和它背后的设计哲学
在我的经验里,CHI 是 Scale-up 互连里文档最深、也最值得先啃的一个协议。它全称是 AMBA 5 Coherent Hub Interface,ARM 定义的一套基于报文(packet-based)的一致性互连协议。跟传统 AXI 那种基于握手通道的做法完全不同,CHI 定义的是 REQ、RSP、DAT、SNP 四组通道,所有操作都是一条条独立的消息。理解它,建议直接从那著名的七态入手。
2.1 七态到底是哪七态
CHI 的缓存状态,很多资料里喜欢叫"七态"。但严格拆开看,主状态是五个:Invalid(I)、UniqueClean(UC)、UniqueDirty(UD)、SharedClean(SC)、SharedDirty(SD)。Unique 意思是这条缓存行你是唯一持有者,写的时候不用跟别人打招呼;Shared 就是多个节点可能都有副本;Clean 和 Dirty 是缓存行内容跟内存一致还是不一致。
另外两个状态,是加了 Empty 语义的 UniqueCleanEmpty(UCE)和 UniqueDirtyEmpty(UDE)。Empty 这个说法很妙,意思是:你获得了这条缓存行的所有权限,但数据内容本身是无效的。它主要用于加速器做零填充、DMA 写的场景——我先跟 Home Node 拿到独占,但我不关心里面原来是什么,我马上要整个覆盖掉。这样一来可以省掉一次内存读返回,降低延迟和带宽占用。很多不熟悉 CHI 的人第一次看状态表会懵,就是因为这俩带 Empty 的状态。
整理一个快速对照表:
| 状态 | 语义 | 数据是否有效 | 是否可写 | 典型出现场景 |
|---|---|---|---|---|
| I | 无效 | 否 | 否 | 初始状态、被侦测失效后 |
| UC | 唯一且干净 | 是 | 可 | ReadUnique 返回后 |
| UD | 唯一且脏 | 是 | 可 | 写之前做过 MakeReadUnique |
| SC | 共享且干净 | 是 | 否 | ReadShared 命中多副本 |
| SD | 共享且脏 | 是 | 否 | 多节点读到脏数据,Home 尚未回写 |
| UCE | 唯一权限、数据空 | 否 | 可 | 零填充、write streaming 分配 |
| UDE | 唯一权限、数据脏空 | 否 | 可 | 带分配的高效 DMA 写 |
2.2 一个 Read 请求走过的状态机路径
举个例子,一个 CPU 核想读一个地址,发出 ReadShared。这条消息走到 Home Node,Home 查 snoop filter,发现另一个节点持有 UD。这时候 Home 不能直接把数据给请求者,因为持有者手里的数据比内存新,直接给会读到旧数据。协议走的是这么一条链:
- Home 朝持有 UD 的节点发 SNP(snoop)消息。
- 持有者把数据沿 DAT 通道传回 Home 或请求者,同时把自己的状态从 UD 降到 SC 或 I。
- 请求者收到数据,状态变成 SC。
- Home 更新 snoop filter,把这条缓存行标记为共享。
整个过程里,状态机的每一步跳变都有严格的"必须条件"。比如持有者从 UD 不能直接跳 I,除非它先把脏数据回写;请求者从 I 也不能直接跳 UD,除非它确保所有权已经握在自己手里。这种"宁可多等一个响应,也不允许出错"的设计,就是一致性协议的全部哲学。你写 RTL 时如果偷懒跳了一步,仿真库里通常过不了 100 万周期就挂给你看。
2.3 CHI 链路层比特:flit、credit、重传
状态机之上是报文,报文再往下是链路层的 flit。CHI 报文不是 AXI 那样一拍一拍打握手,而是打包成固定长度的 flit 在链路上传。每个 flit 里有消息类型字段、操作码、8-bit 或 16-bit 的节点 ID、地址、数据字段、以及控制用的 CRC 和链路层状态位。常用实现里 REQ/RSP/SNP 相关的 flit 可以做得很薄,而 DAT flit 至少要能承载一个完整缓存行,所以数据 bit 宽度通常会按 256-bit 或 512-bit 的物理通道设计。
Credit 机制是链路层最容易出错的地方。发送方每发一个 flit,就消耗一个对应通道的 credit;接收方消化完数据之后,通过专门的 credit return 消息把 credit 还给发送方。如果你 credit 计数算错,要么把通道堵死,要么发出去超过接收方缓冲区容量的报文造成丢包。CHI 链路层还允许做重传,但重传需要接收方记录 flit 序号,所以很多验证环境里会专门做"注入 CRC 错误,观察重传恢复"的用例。这个在实测里特别重要,因为真实链路上噪声、时钟抖动都会造成偶发错误,没有重传机制,整个互连域一次 bit 翻转就可能崩。
2.4 FPGA/验证视角:CHI 协议为何难调
我见过不少团队在 FPGA 上做 CHI 验证,第一周全是懵的。原因有两个:一是 CHI 的协议状态太多了,光 Debug、Cache Stash、DVM 操作就够喝一壶,你如果只做 CPU 一致性,千万别一上来就把整个协议栈全点亮;二是链路层的时序收斂很难,credit 回传和多通道仲裁稍有偏差,仿真过了上板就死。
我自己的做法是先在 UVM 环境里对着 ARM 提供的 reference model 跑一致性测试,再小步切入 RTL。先只挂两个 request node 和一个 home node,把 ReadShared、ReadUnique、CleanUnique、WriteBack 这四类基本消息调通,再引入 snoop。这个顺序很重要,我从没见过哪个团队跳着调能顺利跑起来。
3. 另外五个协议的状态机对比
CHI 只是 Scale-up 互连宇宙里的一颗恒星。接下来把另外五个协议的状态机放上台面,虽然它们不一定都有严格意义上的"七态",但每个都有自己的一致性或事务管理逻辑。
3.1 TileLink-C:小而美的五态机与主动消息
TileLink 是 SiFive 和 Berkeley 推的开源互连协议,RISC-V 生态里用得极多。它把消息切成 A、B、C、D、E 五个 channel:A 是发起请求,D 是返回响应,C 是释放和探听响应,B 是探听请求,E 是最终完成确认。Channel 本身名字起得很工程化,你写 RTL 时看到 A_valid、D_ready 就知道自己在调哪条通道。
有意思的是,TileLink 规范本身没有把缓存状态机"写死",它只约定消息该怎么发,状态让实现方自己定。所以市面上看到 TileLink-C 的一致缓存实现,有做标准五态的,也有做更复杂优化状态的。好处是灵活,坏处是你换一个 vendor 的 cache agent,状态机可能就不完全一样。如果你做的是开源 RISC-V SoC 集成,TileLink-C 是成本最低的选择,它不像 CHI 那样需要庞大的 home agent,一个支持 TL-C 的 manager 就能管理多核一致性。
3.2 AXI/ACE:非一致世界里的"补丁式一致"
AXI 本身是没有一致性的,它是主从之间一拍一拍握手的总线。后来 ARM 给它补了一个 ACE(AXI Coherency Extensions),在普通 AXI 读写的通道之上,加了一套系统级缓存一致性的信号,比如 ReadOnce、ReadClean、ReadNotSharedDirty、CleanUnique、MakeInvalid 这些操作,目的只有一个:让一个不具备复杂互连协议的外设,也能参与系统一致性管理。
从状态机角度看,ACE 更接近"带锁的读写操作",而不是完整的多节点缓存状态机。它适合小规模、单簇的 SoC,几个 CPU 核和少量加速器挂在一个一致性 interconnect 里,问题不大。但一旦节点数上了十几个,ACE 的信号布满芯片,面积、时序、验证复杂度都跟着爆。所以高端场景全在往 CHI 方向走,AXI 只剩在事务层做桥接的角色。
3.3 CXL.cache 与 OpenCAPI:外设一致性路线
CXL 的 CXL.cache 协议,本质上是想让外设直接活在主机的一致性域里。它定义了设备缓存状态和主机缓存状态,设备可以有 HDM(Host-managed Device Memory)和 cache,主机则可以给它发 snoop 请求。CXL.cache 的消息类型、状态转换跟 CHI 有相似处,但目标是轻量接入 PCIe 环境,所以链路层的开销必须更小心。比如 CXL 一个 flit 是 68 字节,其中实际数据、元数据、CRC 做了非常紧的打包,目的就是让带宽效率在 PCIe 物理层上尽量高。
OpenCAPI 和它的内存接口 OMI 也走一致性路线,但更偏 IBM POWER 生态。它把一致性的范围做得更开放,允许第三方加速器通过 OpenCAPI 连接访问主机内存并参与一致性。不过 OpenCAPI 这些年生态推进一般,你在实际数据中心里更可能遇到的是 CXL。学它主要价值在于理解"一致性的另一端"可以怎么设计——它没有 CXL 那么重的 PCIe 兼容包袱,报文设计上有不少值得借鉴的地方。
3.4 UCIe:没有一致性状态机,但卡住了所有上层
UCIe 很有意思,它是六个里唯一"没有缓存状态机"的协议。UCIe 管的是 die-to-die 物理层,管脚、时钟、翻转训练、适配层,它自己不做一致性,它只为上层的一致性协议提供一条稳定的"隧道"。你跑 CHI 也好,跑 CXL 也好,只要把消息封装进 UCIe 的 flit,就能跨到另一个 chiplet 上。
所以对比 UCIe 的状态机其实没有意义,但对比它的比特含义极其重要。UCIe 定义了 raw mode 和 protocol-specific mode,前者让你直接往里塞私有协议消息,后者则面向 CXL/PCIe 的标准封装。它定了很强的链路训练和错误检测,保证上层那些对延迟敏感的一致性协议不会因为物理链路抖动而反复重传。
3.5 协议状态机横向对比表
把这六个协议放在一张表里,能看出来它们的定位差异:
| 协议 | 是否有完整一致性 | 缓存状态 | 典型物理层 | 适合规模 | 开源/开放程度 |
|---|---|---|---|---|---|
| CHI | 是 | 七态(MESI + Empty) | 自定义串行/并行链路 | 大规模多核、多 die | 规范开放,有开源验证模型 |
| TileLink-C | 是,但状态由实现决定 | 不强制 | 并行总线式信道 | 中规模 RISC-V SoC | 真正的开源 |
| AXI/ACE | 部分是,靠外部 agent | 由 ACE 信号推导 | AXI 并行总线 | 小规模 SoC | 规范公开,开源 IP 多 |
| CXL.cache | 是,面向外设 | 设备/主机双侧 | PCIe 物理层 + 68B flit | 服务器内存扩展/加速器 | 开放规范,开源实现陆续出现 |
| OpenCAPI/OMI | 是,面向加速器 | 设备侧缓存一致 | 自定义串行链路 | POWER/FPGA 加速器 | 规范开放,有开源 FPGA 实现 |
| UCIe | 否,只做传输 | 无 | die-to-die PHY | chiplet 内部 | 开放标准,有开源 PHY 参考 |
看到没有,"有没有一致性状态机"这件事,并不是所有互连协议都必须做。UCIe 这种把状态机上移的设计,反而让它特别稳定。选择和组合的时候,要根据你手里到底有多少个需要一致性的主设备,而不是看谁名气大。
4. PBR 路由与请求路径:从地址到端口的全链路
Scale-up 域变大以后,最容易被忽视的就是路由。很多人以为请求发到总线上就完事了,实际上一个地址请求在几千兆赫兹的链路里要经过哈希、查表、端口选择,才可能准确落到目标节点。这部分的复杂度,CHI 和 CXL 各有各的解法,而标题里的 PBR 路由就在这条路径上。
4.1 PBR 路由到底指什么
先说个容易踩的坑:PBR 在不同领域可能是不同缩写。网络设备里的 PBR 是 Policy-Based Routing(策略路由),一条一条规则决定数据包往哪儿走;但在很多互连协议和交换式内存系统里,PBR 更常指 Port-Based Routing(基于端口的路由),也就是不靠地址逐段查找,而是按照交换节点的端口号和路由表直接决定报文出口。这篇文章聊的是后者,尤其是 CXL 交换和类似 Scale-up fabric 里的端口直通策略。
这个机制类比一下特别简单:快递员送件,如果每次都看完整地址到每条街去问,效率很低;如果片区中转站已经知道某个收货码对应的就是 3 号网点,看一眼表就直接送过去了。PBR 就是这张"网点对应表",它让报文在交换节点上不需要做复杂的全地址匹配,只需要一个好的端口映射就能完成转发。
4.2 CHI 的 Home Node 与地址哈希路由
CHI 里的路由核心是 Home Node 的选择。系统里有好几个内存控制器和 snoop filter,一个请求到达后,得先算出这条地址归哪个 Home 管。最常用的做法是地址哈希。把物理地址按一定 bit 位切分、取模或 XOR,映射到一组 Home Node ID 上。这个哈希设计得好不好,直接关系到各 Home 之间的负载均衡。
然后麻烦就来了。因为每个节点 ID 不一定跟物理链路端口号线性对应,CHI 在 Fabric 层其实需要一个"节点 ID 到物理端口"的路由表。很多实现里这张表由系统启动时固件填好,平时基本不变。做验证的时候,我最常遇到的路由问题就是哈希段配置错了,导致一个请求发到了错误的 Home,然后那个 Home 回一个 error 响应,CPU 侧还要花好长时间去处理异常。这种事情在 FPGA 原型上非常隐蔽,因为逻辑上链路都是通的,就是"目的地错得很合理"。
4.3 CXL 交换机的端口路由与 BDF
CXL 3.x 引入真正的交换能力以后,路由就不能再靠点对点自然到达了。CXL 交换机一方面要兼容 PCIe 的 BDF(总线号、设备号、功能号)寻址,另一方面对 CXL.mem 的类型化内存请求,要做基于地址区间的路由。
端口路由在这里起作用:交换机的每个下游端口可以配置一组地址范围,凡是落在某个范围内的内存请求,直接送到对应的端口,不需要逐跳查询。这种静态配置的方式很高效,但代价是灵活性有限——如果某个端口对应的内存设备热插拔了,路由表必须由软件及时更新,否则请求就会送进一个已经不存在的端口,产生完读、完写失败。所以在 CXL 交换的可靠性设计里,路由表更新、端口状态同步、地址范围冲突检测,这三件事必须一起测。
4.4 TileLink 与 ACE 的 manager 路由
TileLink 里没有 Home Node、没有哈希,它的路由主要靠每个节点持有的地址段映射表。管理器(Manager)声明自己负责的一段地址,发起端按地址匹配到对应的管理器端口。这个模型很"扁平",好处是简单、可预测,缺点是当节点数多、地址段需要动态改时,改表的代价不小。RISC-V 多核 SoC 里,地址映射通常是编译期或启动时定死的,所以问题不大。
ACE 的路由则是"隐式"的,因为 AXI 本身就是直接点到点的连接。一个 AXI interconnect 的矩阵决定读请求送到哪个 slave,这个矩阵就是工程上的交叉开关/NoC。从纯协议角度看,ACE 没有显式的路由字段,节点的选路完全由集成者决定。这也是它不适合做大规模 Scale-up 的原因之一——路由的灵活性完全取决于 NoC 设计,而不在协议标准里。
4.5 路由错误引发的坑
我在调试中见过最典型的路由问题有三类:一是地址哈希的位选择没有避开非 interleave 地址段,导致某些内存区间的请求全部打在同一个 Home 上,热点严重;二是 CXL 交换机端口路由表里两个地址区间重叠,低优先级的端口悄悄把请求"抢走",数据拷回来发现目标完全不对;三是 TileLink 的地址段表有空洞,未被映射的地址发出去没有响应,然后在总线上挂死。这些问题在测试计划里都应该当成常规项,别等到系统联调才想起来。
5. 比特级解剖:flit 格式、编码效率与带宽计算
状态机决定逻辑对不对,比特层面决定系统快不快。Scale-up 互连一跑就是几百 Gbps 甚至上 Tbps,任何一点编码冗余都会带来巨大的带宽损失。这章我们算细账。
5.1 各协议在链路上怎么打包
AXI 和 TileLink 的传输是"beat 化"的,一拍一拍在并行总线上传,一拍里 carry 多少字节取决于数据宽度,做成 128-bit、256-bit 都有。它们的头部和数据是在不同周期打出去的,控制信号和数据信号交织,所以协议开销主要来自额外的握手周期。
CHI 则是 flit 化传输,报文按固定长度 flit 打包,管路的控制信息和数据放一起,链路层对每个 flit 统一做 CRC,这样更容易做流水线化,也更容易做错误重传。CXL 的 68 字节 flit 则是在 PCIe 物理层上为了兼顾带宽和延迟调出来的。这个 68 字节拆开看,实际跟 PCIe 报文的对齐、Meta、CRC 绑得非常紧,让链路在承载 CXL.io、CXL.cache 和 CXL.mem 三种流量时都能保持高效率。
5.2 编码、CRC 和 Credit:谁的链路预算更健康
物理编码上,PCIe 用 128/130b 编码,UCIe 也沿用了类似的加扰 + 编码策略,目标是直流平衡和时钟恢复。这部分开销接近 1.6%,看起来不大,但到了几百 Gbps 的链路上,就是十几 Gbps 的带宽没了。CHI 和 OpenCAPI 如果跑在专用 SerDes 上,则通常用 64b/66b、64b/67b 一类的编码,也有一部分开销。
更重要的是 CRC。CRC 字段放在 flit 尾部,接收端要先收完整 flit 才能校验,这决定了链路的比较延迟。CRC 覆盖范围越大,检测越全面,但计算路径越长。所以很多实现里会做"快速通过"(fast forward)优化:头部的路由字段先被使用,数据字段边收边算 CRC,等整个 flit 收完时立即出结果。这个优化很考验 RTL 工程师的流水线功底。
5.3 手把手算一个 Scale-up 域带宽
举个例子。假设用 CXL 做内存池,一条通道是 PCIe Gen5 x16,物理速率 32GT/s。原始比特率就是 32 GT/s × 16 lane = 512 Gbps = 64 GB/s。走 128/130b 编码后,约 63 GB/s。再用 68B CXL flit 承载,假设其中 64 字节是有效载荷,那么有效带宽就是 63 × 64 / 68 ≈ 59.3 GB/s。这是一个方向的理论天花板,实际还要扣掉刷新、重传、头尾间隙,跑到 50 GB/s 左右已经很健康。
如果是 CHI 跑在专用链路上,假设单链路 64 Gbps 有效带宽,链路层开销如果控制在 5% 以内,那么一套 8 链路互连域的理论聚合带宽就能做到约 8 × 64 Gbps × 0.95 / 8 ≈ 60.8 GB/s。不同协议在比特层面差几个百分点,一乘上端口数量,就是个大数字。所以协议选择不能只看"有没有一致性",还要看链路层的效率能不能撑住 Scale-up 域的流量模型。
5.4 开源参考实现去哪找
聊了这么多理论,总得有地方下手。如果你想去读 RTL,可以看 Berkeley 的 Rocket Chip 和 BOOM 里的 TileLink 实现,那里面对 TL-C 状态的用法非常标准。AXI 的实现更不用说了,Xilinx 官方 IP、PULP 平台的开源 AXI 都有很完整的代码。CHI 需要花点心思去找开源验证模型和参考实现,GitHub 上有一些基于 SystemVerilog/UVM 的 CHI agent,可以拿来搭测试环境。CXL 的开源生态起步稍晚,但现在也有基于开源 PHY 的 controller 参考设计。UCIe 的物理层也有开源 PHY 选项,就是对上层的兼容层还要自己补。
6. 常见问题速查与选型建议
6.1 我在调试中踩过的坑
先列一个速查表,这些问题我基本全踩过:
| 问题现象 | 大概率原因 | 排查思路 |
|---|---|---|
| 请求发出去后无响应 | 路由表未配置或端口映射错误 | 先看地址哈希/地址区间表,再看 credit 是否耗尽 |
| 数据读到旧值 | 一致性状态未从共享降级 | 查 cache agent 状态机,看 snoop 处理路径的响应有没有真正完成 |
| 链路偶发丢包重传风暴 | CRC 覆盖范围不足或信号完整性差 | 先跑链路线性测试,再把 CRC 错误注入用例拿出来跑 |
| CXL 内存写失败 | 端口路由表和热插拔状态不同步 | 检查交换机端口状态和地址区间更新的一致性 |
| TileLink 总线挂死 | 地址段表有空洞,未映射请求无响应 | 补一个缺省错误响应管理器 |
| Credit 计数不对导致吞吐骤降 | credit return 时延太长或计数遗漏 | 在协议监测器里加 credit 断言,跑长回归 |
这里最想强调的一点:互连协议出问题,往往不是单一原因,而是路由表、credit 链路、状态机三个环节互相叠加。比如一个请求因为路由表错误被送到错误 Home,Home 回 error,请求端又有一个 bug 没处理 error,于是事务一直挂在状态机里,后续请求把 credit 耗尽,最后整个域看起来像"死锁",但根因其实在最开始的路由配置里。所以排查的时候一定要建立"从源头到终点"的全局视角,不要只盯着当前状态机表项。
6.2 怎么选:按场景而不是按名气
经常有人问我,做 Scale-up 用 CHI 还是 CXL,还是 TileLink。我的回答是看你处在什么生态、要解决什么规模。
- 做服务器 CPU/大内存池,绕不开 CHI 和 CXL。CHI 适合做统一的内部一致互连,CXL 适合跟外部 PCIe 生态对接。
- 做 RISC-V 多核 SoC、想快速出原型,TileLink 是成本最低的,文档清楚,开源代码多。
- 处在 FPGA 加速卡世界,AXI/ACE 仍然最成熟,外设访问和一致性桥接都有现成 IP。
- 做 chiplet,UCIe 是避不开的底层,不管上层跑什么,先把物理层练稳。
千万别一上来就觉得"协议越高级越好"。我见过有人在小核 SoC 里硬上一套完整 CHI,花了半年做一致性验证,最后性能提升不到 10%。Scale-up 互连是系统工程,协议选型只是一张入场券。
最后再分享一个我自己的习惯:每次接触新互连协议,我先不读完整文档,而是把协议规定的状态迁移图抄到一张纸上,然后用 SystemVerilog assertion 把这些迁移写成断言,放到 regression 里跑。这套方法帮我抓住过不少 RTL 里偷偷跳过中间态的问题。协议对比的文章写再多,最后还是要回到波形和真值表面前,点开那条失败 log,一行一行看它到底卡在哪个状态里。