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 0COM(符号)标识有序集合开始,固定值
Symbol 1Link Number当前链路的编号
Symbol 2Lane Number当前Lane的编号
Symbol 3Training Control 1训练控制字段,包含速率、宽度等
Symbol 4–10保留/扩展留给链路层特定用途
Symbol 11Training Control 2更多控制位
Symbol 12–14Framing 帧标识,用于同步
Symbol 15CRC / 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图概括了训练序列从发送到接收的完整流动路径——包括编码、加扰、串行化、接收端的同步头检测、解码等。画这张图时我把重点放在了帧结构的处理节点上:

训练序列帧结构处理流程图 链路状态机(LTSSM) TS序列生成 64B/67B编码 加扰 串行化 物理链路 同步头检测 64B/67B解码 去扰 帧结构解析 根据TS1/TS2中的训练控制字段决定下一状态(L0 / Recovery / Detect)

你从图上可以看到,发送端先生成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 Rate00 = 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字段决定链路配置。搞懂这三个,你就能看穿所有类型的训练序列帧。