4. 训练序列帧结构详解
各位做CXL验证的兄弟,咱们今天把训练序列的帧结构彻底聊透。你可能会问:不就是一串0和1吗?有什么好研究的?我刚开始接触CXL时也这么想,直到有一次在实验室里抓链路训练波形,发现总是丢TS1,怎么查都查不到原因……后来才发现,我对帧结构的理解太粗糙了。
说白了,CXL的训练序列不是随便发的比特流。它由一系列有序集合组成,每个有序集合又拆成若干符号,每个符号对应64B/67B编码后的一个块。这里我习惯先看整体骨架,再拆细节。
4.1 训练序列的整体结构
训练序列在链路训练阶段持续发送,直到状态机切换到L0。一个典型的训练序列帧长度是固定的——我记得PCIe时代用的是16个符号,CXL跟它一脉相承,但细节有区别。
每个训练序列帧实际上是一连串有序集合(Ordered Set, OS)的重复。常用的有序集合有:
- TS1:训练序列1,用于初始化、速度协商、位锁定
- TS2:训练序列2,用于进一步对齐和配置
- SKP:跳过有序集合,补偿时钟漂移
- EIEOS:电气空闲退出有序集合
你想想看,TS1和TS2长得像但用途不同。我在项目中遇到过一种情况——某家IP在TS2里塞了一个错误的训练控制字段,导致链路始终无法进入L0状态。当时排查了两天,最后把帧结构逐比特打印出来才发现问题。
4.2 有序集合的比特布局(以TS1为例)
TS1帧总共占16个符号位置,每个符号就是67 bits(编码后)。但硬件层面我们更关心的是编码前的数据,也就是原始数据比特。我习惯用下面这个表格来帮助团队记忆:
| 符号索引 | 数据内容 | 说明 |
|---|---|---|
| Symbol 0 | COM(符号) | 标识有序集合开始,固定值 |
| Symbol 1 | Link Number | 当前链路的编号 |
| Symbol 2 | Lane Number | 当前Lane的编号 |
| Symbol 3 | Training Control 1 | 训练控制字段,包含速率、宽度等 |
| Symbol 4–10 | 保留/扩展 | 留给链路层特定用途 |
| Symbol 11 | Training Control 2 | 更多控制位 |
| Symbol 12–14 | Framing | 帧标识,用于同步 |
| Symbol 15 | CRC / ECC | 循环冗余校验或纠错码 |
提示: 我建议你在做工程时,不要只盯着表格里的字段名。真正干活的时候,要把每个字段的比特宽度和偏移量吃透。比如Training Control 1的低2位控制着是否启用ECC,这在链路上经常被忽略。
4.3 帧定界与同步头
一个64B/67B编码的符号里,前两位是同步头,用来区分数据块和控制块。对于训练序列,所有符号的同步头都是10(二进制),表示这是一个控制块。嗯,这里要注意:一旦同步头变成01或00,说明收到了数据流,链路训练就结束了。
我记得在早期调试时,一度用逻辑分析仪抓到的同步头全是乱码,后来发现是参考时钟抖动太大导致位锁定不准。所以同步头不仅是类型标识,更是链路质量的晴雨表。
// 伪代码:判断符号类型
if (sync_header == 0b10) {
// 控制块,通常是训练序列或有序集合
process_ordered_set();
} else if (sync_header == 0b01) {
// 数据块,链路已进入L0
handle_data();
} else {
// 错误或者电气空闲
report_error();
}
警告: 千万不要把同步头和帧定界搞混。同步头是每个符号自带的;帧定界是训练序列帧末尾的Framing ,它告诉接收端“这一帧结束了”。我见过有人把二者混为一谈,结果在FPGA实现里加了一个多余的定界检测,浪费了大量逻辑资源。
4.4 训练序列的编码与解码流程
下面这张SVG图概括了训练序列从发送到接收的完整流动路径——包括编码、加扰、串行化、接收端的同步头检测、解码等。画这张图时我把重点放在了帧结构的处理节点上:
你从图上可以看到,发送端先生成TS序列的有序集合内容,然后做64B/67B编码——把同步头和数据拼成一个完整的块。接着加扰,加扰的目的是减少EMI,再串行化后扔到物理链路上。接收端反过来:先检测同步头,确认是控制块后,解码、去扰,最后解析帧结构里的各个字段。
这里我有个实际经验:加扰和解扰的LFSR初始值必须和发送端一致。如果复位不同步,第一个TS帧解析出来全是错的。我在做多Lane绑定测试时遇到过这种坑——两个Lane的LFSR初始值差了一个时钟周期,导致TS2永远无法到达。
4.5 关键字段解析:Training Control 1 和 Framing
Training Control 1 字段的比特分配如下(以x4链路为例):
| 比特位 | 名称 | 描述 |
|---|---|---|
| [7] | Hot Reset | 热复位指示 |
| [6] | Disable Scrambling | 禁用加扰 |
| [5:4] | Link Data Rate | 00 = 16GT/s, 01 = 24GT/s, 10 = 32GT/s |
| [3:2] | Lane Count | 链路宽度编码 |
| [1] | ECC Enable | 是否启用纠错 |
| [0] | Loopback | 回环测试模式 |
Framing 在Symbol 12–14,用于鉴别帧的边界。这三个符号的内容是固定模式,接收端把它们当同步锁相的依据。说实话,很多芯片在Framing 上简化了——只检查Symbol 12是否匹配,但我认为为了鲁棒性,至少检查两个连续符号。
4.6 训练序列的循环与重发
链路训练时,发送端不歇气地发TS1或TS2,每个帧16个符号。接收端收到后检查帧结构中的目标字段,如果发现不匹配,会回复NAK或者直接超时重发。我通常把这种机制称为“握手中的握手”。
- 正常场景:发送端发TS1,接收端匹配后回复同频TS1,双方升级到TS2。
- 异常场景:如果接收端检测到Training Control 1中的速率字段不支持,就发一个带有特定错误编码的TS1回去,发送端降速重试。
嗯,这里容易踩的坑是——有些实现把帧内所有字段都做了严格匹配,一点偏差就NAK。实际上,保留字段和扩展字段可以忽略,否则会过度敏感。我在某次互通测试中,把所有保留位强行当成检查项,结果人家的IP不填充保留位(默认0),而我要求全部匹配,自然通不过。后来改成掩码比较就解决了。
4.7 训练序列帧与L0数据帧的切换
当链路训练完成,状态机切到L0后,数据帧不再使用有序集合,而是用标准的data block(同步头为01)。但有个细节:L0期间偶尔还会插入SKP有序集合来补偿时钟漂移。SKP的帧结构比TS1简单,只有COM符号 + 几个SKP符号 + 一个END符号。
做收端设计时,我建议把训练序列帧解析和SKP解析放在同一个有限状态机里,因为它们的帧定界方式相似,可以复用很多逻辑。我当年就是偷懒单独写了一个SKP解析器,结果代码体积翻了一番,还引入了新的bug。
关键总结:训练序列帧结构看似复杂,其实核心就三点——①同步头标识控制块;②COM符号标志帧起始;③Training Control字段决定链路配置。搞懂这三个,你就能看穿所有类型的训练序列帧。