第四章 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数) | 固定值/说明 |
|---|---|---|---|
| 0 | SYNC | 1 | 0xBC |
| 1 | COM | 1 | 0xF7 |
| 2 | TS1 Identifier | 1 | 0x22 |
| 3 | Link Number | 1 | 取值为0~31或0xFF |
| 4 | Lane Number | 1 | 取值为0~31或0xFF |
| 5~15 | Reserved / TS2相关 | 11 | TS1时为保留,通常填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值 |
|---|---|---|
| Identifier | 0x22 | 0x33 |
| Link Number | 有效值或0xFF | 有效值或0xFF |
| Lane Number | 有效值或0xFF | 有效值或0xFF |
| Reserved Bits | 全0 | 协议中可能包含部分训练控制位 |
你看,核心差异就在Identifier上。其他字段在格式上完全一致。所以,抓包分析时,先看符号2的值,马上就能判断出当前阶段是TS1还是TS2。
我曾经掉进过一个坑:在GEN3以后的协议中,TS1/TS2的帧长度不再是16个符号了。因为加入了更多的训练控制位。如果你还在用老一代的规则去解析GEN3的TS1,大概率会解析出乱码数据。所以,需要始终确认链路的速率版本再套用解析模板。
4.6 本章知识结构总览
我画了一张TS1训练序列的字段结构示意图。你看一眼就清楚每个字节在帧里的位置了。
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波形,按符号拆开,对号入座就行了。我自己分析协议时就是用的这方法,快狠准。