链路层数据帧格式:从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 直接对齐这个尺寸,硬件好做。
| 字段 | 大小 | 说明 |
|---|---|---|
| Header | 8 字节 | 协议类型、序列号、控制信息 |
| Payload | 0~112 字节 | 实际数据(控制帧为 0) |
| LCRC | 4 字节 | 链路层 CRC,校验 Header+Payload |
| FEC | 4 字节 | 前向纠错,保护 Header 和 LCRC |
核心观点: 128 字节的 FIT 帧不是协议拍脑袋想的,是为了让硬件能在一个 burst 里完整收发,省去碎片重组的开销。
控制帧与数据帧的区别——两种东西,两种使命
CXL 链路层只认两种帧:数据帧和控制帧。单从外观上看,数据帧有 Payload,控制帧没有。但内行人看的是 Header 里的 Interleave 字段。
我记得第一次调试链路层 CRC 异常时,发现连续丢帧,后来定位到是接收端把控制帧当数据帧解析了。原因就是对 Interleave 位理解不到位。
| 对比项 | 数据帧 | 控制帧 |
|---|---|---|
| Payload 长度 | 1~112 字节 | 0 字节(全部为控制字段保留) |
| Header.Interleave | 0b00 | 0b01 或 0b10 |
| 用途 | 传输请求、响应、数据 | 流量控制、重传管理、信用通知 |
| LCRC 覆盖 | Header + Payload | Header 本身(Payload 为 0) |
| FEC 保护 | Header + LCRC | Header + 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 把控制帧和数据帧并排放,字段位置一目了然。
画这个图时,我特意把控制帧的 Payload 区域留空,数据帧则用紫色标出可变长度。你在实际电路里看到的就是这样:硬件收到一帧,先判定 Interleave,再决定是否需要等 Payload,然后并行计算 LCRC 和 FEC,最后决定是收下来还是触发重传。
一个小技巧: 调试时我是先看 FEC 纠错是否工作,再看 LCRC 是否通过。如果 FEC 纠完还过不了 LCRC,说明是 payload 本身损坏而不是链路噪声。