第四章 TS1训练序列格式解析

4.1 训练序列到底是什么?

TS1,全称是 Training Sequence 1。说白了,它就是PCIe链路在初始化阶段,设备之间互相喊话用的「摩斯电码」。链路建立那会儿,两端的设备都还没搞明白对方是什么角色、在哪条通道上、有没有准备好。这时候就需要一种确定的、大家都能懂的格式来交换信息。

TS1就是一个有序集(Ordered Set)的集合。数据链路层和物理层通过它来完成链路宽度协商、极性反转、比特锁定这些事儿。我记得刚接触PCIe时,光看Spec上那堆十六进制数,脑子一直转不过弯来。后来抓了一次真实的Link Training波形,看着TS1里每个字段怎么变,瞬间就明白了。

核心认识:TS1是有序集,由物理层发送,用来在链路初始化阶段同步两端的状态。

4.2 TS1的完整帧结构

一个标准的TS1训练序列,在2.5 GT/s和5 GT/s下是16个符号(Symbol)一组。每个符号8位宽。结构如下:

符号序号字段名位宽(Symbol数)固定值/说明
0SYNC10xBC
1COM10xF7
2TS1 Identifier10x22
3Link Number1取值为0~31或0xFF
4Lane Number1取值为0~31或0xFF
5~15Reserved / TS2相关11TS1时为保留,通常填0

嗯,这里要注意。上表中的符号5~15在TS1里并不是完全无用的。有些PHY实现会拿它们做EIEOS之类的字符对齐用。不过在GEN1/GEN2下,这11个符号在TS1里就是保留位,填0就好。

4.3 逐个字段拆解

SYNC字段(符号0)

SYNC的固定值是0xBC。它的作用是对齐符号边界。接收端只要检测到连续多个0xBC,就知道一个有序集要开始了。我在调试一块FPGA的PCIe硬核时,遇到过SYNC检测不到位的情况,结果Link死活Training不起来。查到最后发现是PLL抖动偏大,导致符号边界偏移了半个UI。

避坑指南:SYNC的检测阈值一般是3个连续0xBC。少于这个数,链路就会认为没收到有效TS1,会一直反复发送。

COM字段(符号1)

COM的固定值是0xF7。它紧跟在SYNC后面,标志有序集类型的开始。COM其实是COM(Comma)字符,用于字节对齐。嗯,准确地说,8b/10b编码下的COM是一个特殊的K码字,不是普通数据字符。只有COM出现后,接收端才能确定后面符号的字节起始点。

举个例子,假设你从物理层抓到的原始比特流是:0xBC 0xF7 0x22 0x00 0x00 ...。前两个字节0xBC和0xF7就是进入有序集模式的「敲门砖」。后面的内容,才是真正的谈判内容。

TS1 Identifier(符号2)

这个字段固定为0x22。不过你可能会问,0x22就代表这是TS1而不是TS2吗?没错,TS2的Identifier是0x33。所以单看符号2的值,接收端就能区分出当前收到的训练序列是TS1还是TS2。

有次我模拟链路场景时,人为的把Identifier改成了0x22之外的数,结果接收端直接把它当成无效有序集丢弃了。所以,这个字段必须严格对齐规范,不能自己瞎改。

Link Number(符号3)

Link Number是个关键字段。它用来标识当前TS1发送端认为的链路号。链路号范围是0~31。或者如果链路号未知,就填0xFF。

在链路初始化的第一阶段(Detect/ Polling),两端的Link Number都是0xFF。随着Training的深入,设备会逐渐把多路通道合并成一条链路,这时候Link Number就会被设为具体的值。

我个人的理解是:Link Number是在链路合并时协商出来的。每个端口可以有多条通道,但只能有一个Link Number。如果两条通道的Link Number不一致,那它们就不可能归属于同一条链路。

Lane Number(符号4)

如果说Link Number是链路的编号,那Lane Number就是通道的编号。每条通道(Lane)都会带上自己的编号发送TS1。取值范围同样是0~31,未知时填0xFF。

为什么要有Lane Number?因为PCIe支持多通道配置(x1, x2, x4, x8, x16, x32)。举个例子,一个x4链路,4条通道的Lane Number分别是0, 1, 2, 3。发送端通过TS1把自己所在通道的编号告诉对端。

接收端根据收到的Lane Number,就能知道哪些通道是连通的,哪些是断的。比如你期望4条通道都收到,结果只收到了Lane 0和Lane 2的TS1,那Lane 1和Lane 3就有问题。我在项目中排查过一次类似的问题,就是因为PCB布线导致Lane 1的差分对虚焊,Lane Number始终是0xFF,链路只能降级到x2工作。

4.4 TS1训练序列的核心作用

  • 比特锁定:通过SYNC和COM确定时钟和符号边界。
  • 类型识别:通过TS1 Identifier区分TS1/TS2。
  • 链路号协商:两端的Link Number相互告知。
  • 通道号映射:每条通道的Lane Number用于通道映射与极性检测。
  • 极性反转检测:通过PAD/COM的极性来判断。

说白了,TS1就是链路建立前的「握手包」。两边的设备通过它互相认识,告诉对方自己是哪个链路里的哪条通道。

4.5 TS1与TS2的字段差异对比

字段TS1值TS2值
Identifier0x220x33
Link Number有效值或0xFF有效值或0xFF
Lane Number有效值或0xFF有效值或0xFF
Reserved Bits全0协议中可能包含部分训练控制位

你看,核心差异就在Identifier上。其他字段在格式上完全一致。所以,抓包分析时,先看符号2的值,马上就能判断出当前阶段是TS1还是TS2。

我曾经掉进过一个坑:在GEN3以后的协议中,TS1/TS2的帧长度不再是16个符号了。因为加入了更多的训练控制位。如果你还在用老一代的规则去解析GEN3的TS1,大概率会解析出乱码数据。所以,需要始终确认链路的速率版本再套用解析模板。

4.6 本章知识结构总览

我画了一张TS1训练序列的字段结构示意图。你看一眼就清楚每个字节在帧里的位置了。

TS1训练序列字段结构图 TS1训练序列字段结构 SYNC 0xBC (1 Symbol) COM 0xF7 (1 Symbol) Identifier 0x22 (1 Symbol) Link Number 0xFF (1 Symbol) Lane 0xFF Reserved (符号5~15,共11 Symbols,TS1时填0) 说明: • SYNC+COM组成有序集起始标识。 • Link Number和Lane Number在未知状态时填0xFF,协商后变为具体值。 • Identifier = 0x22 代表TS1;如果是TS2则为0x33。

4.7 几个你一定会踩的坑

第一个坑:误把Reserved字段当成有效数据解析。TS1的符号5~15是保留的,不要在里面找有效信息。但总有人看十六进制抓包时,觉得0xBC 0xF7 0x22 0xFF 0xFF后面跟了一堆0,就以为自己漏掉了什么。其实没漏,就是保留位。

第二个坑:Link Number和Lane Number弄混了。有一次我看到一个同事调试日志里一直抱怨链路字段对不上,原来他拿着Lane Number的值去匹配Link Number的预期,那肯定对不上。记住,一个是链路级编号,一个是通道级编号。

第三个坑:忘了检查Identifier的值就做后续处理。我建议,在解析TS1时,先判断符号2是不是0x22。不是?马上丢弃,不要继续解析后面的内容。否则容易把TS2的帧当成TS1来解码,导致状态机混乱。

小技巧:抓TS1波形时,直接看符号0和符号1就能快速定位有序集边界。符号0必须是0xBC,符号1必须是0xF7。两个都对了,再往下看Identifier。这几个固定值可以做成硬件匹配器,一秒过滤几百万个符号,完全不占CPU。

好了,这就是TS1训练序列的完整格式。公式化一点说,它的格式可以用下面这个十六进制序列来表示:

TS1(GEN1/GEN2):
      Symbol 0: 0xBC (SYNC)
      Symbol 1: 0xF7 (COM)
      Symbol 2: 0x22 (Identifier)
      Symbol 3: Link Number (有效值或0xFF)
      Symbol 4: Lane Number (有效值或0xFF)
      Symbol 5~15: 0x00 (Reserved)

嗯,掌握了这个结构,你就掌握了TS1的解析密钥。接下来在调试时,只要抓到一个Training波形,按符号拆开,对号入座就行了。我自己分析协议时就是用的这方法,快狠准。