链路层数据帧格式:从FIT到FEC

讲 CXL 链路层,第一关就是搞懂数据帧长什么样。

我当年刚接触 CXL 协议时,翻 spec 看到 FIT 这个词,心想这跟健身有关系吗?后来才明白——FIT 是 Flit(流量单元)的别名,CXL 链路层把一切数据都切成固定大小的令牌,每令牌叫一帧。

那你问我:为什么不叫 packet 而叫 flit?说白了,flit 是硬件可以流水线处理的最小粒度,一帧一到两个时钟就能推出去。我踩过坑:如果你把协议栈当成网络包来处理,延迟立马爆炸。

FIT 帧结构——128 字节的硬规矩

每帧固定 128 字节,前面是 8 字节 Header,后面跟着不定长的数据载荷,最后塞 4 字节 LCRC 和 4 字节 FEC。

为什么是 128?CXL 走的是 PCIe 物理层,PCIe TLP 最多 128 字节 payload 加头尾,CXL 直接对齐这个尺寸,硬件好做。

字段大小说明
Header8 字节协议类型、序列号、控制信息
Payload0~112 字节实际数据(控制帧为 0)
LCRC4 字节链路层 CRC,校验 Header+Payload
FEC4 字节前向纠错,保护 Header 和 LCRC

核心观点: 128 字节的 FIT 帧不是协议拍脑袋想的,是为了让硬件能在一个 burst 里完整收发,省去碎片重组的开销。

控制帧与数据帧的区别——两种东西,两种使命

CXL 链路层只认两种帧:数据帧和控制帧。单从外观上看,数据帧有 Payload,控制帧没有。但内行人看的是 Header 里的 Interleave 字段。

我记得第一次调试链路层 CRC 异常时,发现连续丢帧,后来定位到是接收端把控制帧当数据帧解析了。原因就是对 Interleave 位理解不到位。

对比项数据帧控制帧
Payload 长度1~112 字节0 字节(全部为控制字段保留)
Header.Interleave0b000b01 或 0b10
用途传输请求、响应、数据流量控制、重传管理、信用通知
LCRC 覆盖Header + PayloadHeader 本身(Payload 为 0)
FEC 保护Header + LCRCHeader + LCRC

避坑指南: 我曾经写控制帧时忘了构造 Interleave 字段,硬生生把控制帧当成数据帧发出去,对方重传模块直接罢工。嗯,从那以后我每次收帧都先读 Interleave 再决定如何解析。

Header 字段深度解析——8 字节里藏着所有秘密

Header 的前 2 个字节是协议版本和类型,中间 4 个字节是序列号和重传控制,最后 2 个字节是分片信息。你拆开来看其实不复杂。

  • Byte 0 [7:6] – Protocol Ver:当前只有 0b01,留给未来扩展。
  • Byte 0 [5:4] – Frame Type:0b00 = 数据帧,0b01 = 控制帧,0b10/11 保留。
  • Byte 0 [3:0] – Interleave + Reserved:控制帧用 Interleave 区分不同子类型。
  • Byte 1 – Sequence Number:8 位循环计数,重传时依赖它检测重复。
  • Byte 2-5 – Control / Retry Info:包含 AckNak 序列号、Retry 计数器、Sbit 等。
  • Byte 6-7 – Payload Length:以 DW(4 字节)为单位,数据帧范围 1~28,控制帧固定为 0。

这个 Sequence Number 我特别想强调。为什么只有 8 位?因为重传窗口很小,链路层最多缓存 256 帧,溢出就丢给你上层处理。在我参与的一款 CXL 控制器里,Sequence Number 溢出导致重传环越转越乱,最后我们在 Retry Info 里加了一个 Rsvd 位做溢出标记才解决。

LCRC 与 FEC 字段位置——保险丝装在哪

FIT 帧的末尾 8 字节由 LCRC 和 FEC 平分。LCRC 紧贴着数据业务,FEC 在最尾巴。为什么这么排?

我个人的理解是:LCRC 校验的是相对稳定的 Payload,计算延迟可以容忍。而 FEC 必须最先被硬件校验,因为它纠正的是链路随机 bit 错误,放在最后才能做到“先收帧尾、先做校正”,减少 pipeline stall。

注意: LCRC 只覆盖 Header+Payload,不包含 FEC 自身。FEC 覆盖的是 Header+LCRC(即前 8+PayLoadLen+4 个字节)。设计上保证 FEC 错误不会因为 LCRC 错误而误判。

我曾经犯过一个低级错误——把 FEC 放在了 LCRC 前面,结果硬件纠错后拿去算 CRC 发现不对,因为 LCRC 已经混入错误 bits。后来看 spec 才发现官方顺序是固定的:不要自己创新,CXL 的序列经过大量验证,动一个字节可能引发级联错误。

一张图看懂 FIT 帧结构

下面这张 SVG 把控制帧和数据帧并排放,字段位置一目了然。

FIT 帧结构(控制帧 vs 数据帧) 控制帧(Interleave≠0) Header (8B) LCRC FEC (无 Payload) 数据帧(Interleave=0) Header (8B) Payload (0~112B) LCRC FEC 说明 • 控制帧 Payload 被控制字段复用 • FEC 保护范围 = Header+LCRC FEC 保护边界

画这个图时,我特意把控制帧的 Payload 区域留空,数据帧则用紫色标出可变长度。你在实际电路里看到的就是这样:硬件收到一帧,先判定 Interleave,再决定是否需要等 Payload,然后并行计算 LCRC 和 FEC,最后决定是收下来还是触发重传。

一个小技巧: 调试时我是先看 FEC 纠错是否工作,再看 LCRC 是否通过。如果 FEC 纠完还过不了 LCRC,说明是 payload 本身损坏而不是链路噪声。