TS1 协议详解:传输协议头、有效载荷、自适应字段、同步字节

TS1 是做什么用的?说白了,它就是 PCIe 链路训练阶段握手的第一步。我刚开始接触时,以为它只是一串随机的比特流,后来踩过坑才明白——TS1 里的每一个字节都有明确含义,读懂了它,你就掌握了链路训练的核心。

TS1 的构成:一个有序集的骨架

TS1 属于 Ordered Set,跑在物理层的逻辑子块里。标准的 TS1 由 16 个符号组成,我习惯把它切开来看:

  • 同步字节(COM):1 个符号,标记帧起始
  • 传输协议头:3 个符号,包含 TS1 标识和类型
  • 有效载荷:8 个符号,携带链路和通道信息
  • 自适应字段:4 个符号,用于扩展或厂商自定义

你想想看,总共 16 个符号,编码后就是 20 位宽的并行数据。为什么是 16?嗯,这和 8b/10b 编码后的对齐需求有关,不多不少。

关键点: 任何链路训练状态机(LTSSM)的切换,都必须先收到对端的有效 TS1 或 TS2。我曾遇到一块 FPGA 板,因同步字节 COM 被噪声干扰,导致链路一直卡在 Detect 状态。检查了三天才发现是 COM 的 10b 编码有误。

同步字节(COM)—— 帧的锚点

COM 符号的 10b 编码是固定的:0011111010 或 1100000101。它在 8b/10b 码表中是唯一的 K28.5 码型,不会出现在数据流中。所以接收器只要检测到这个模式,就知道“哦,一个新的有序集开始了”。

我记得有一次在示波器上抓链路训练波形,看到反复出现的 0xBC 字节(COM 的 8b 值),心里就踏实了——至少物理层通着。反过来,如果 COM 对不上,后面的所有符号都会错位。

传输协议头 —— 我是谁

COM 后面的三个符号构成协议头:

符号序号字段名宽度(bits)含义
1TS1 Identifier8固定 0x1E,标识这是一个 TS1
2Link Number4请求的链路编号,训练中动态协商
2(高4位)Reserved4保留,必须为 0
3Lane Number4当前通道编号,从 0 开始
3(高4位)Reserved4保留

这里有个容易掉坑的地方:Link Number 在训练初期总是 0xFF(表示未知),等到链路宽度协商完成后才填入真实值。我曾经在产品联调时发现两边 Link Number 不匹配,结果链路宽度始终被锁定在 x1,浪费了不少带宽。

有效载荷 —— 身体里的秘密

传输协议头之后的 8 个符号是有效载荷区。每个符号 8 位,但实际有用的只有低 4 位或低 6 位,高位多数保留。具体分布如下:

  • 符号 4~7:FTS(Fast Training Sequence)计数,用于恢复相位信息
  • 符号 8~9:比特锁定状态和符号锁定状态标志
  • 符号 10~11:速率 ID 和扩频时钟(SSC)信息

这些字段在 LTSSM 的不同子状态下的含义略有不同。比如在 Polling 状态下,FTS 计数被用来做比特对齐;到了 Recovery 状态,它的优先级降低。

实践技巧: 调试时,把 TS1 的有效载荷逐字节打印出来,对照 PCIe 规范 Table 4-44 核对每一位。我习惯用 160 行的逻辑分析仪抓 16 个符号,解析出每个字段后画成时序图,问题往往一眼就能看出来。

自适应字段 —— 未来的预留地

最后 4 个符号是自适应字段。标准规定这些符号的值为 0x00,但允许厂商用于扩展。比如某些处理器会在自适应字段里携带电源管理请求或错误指示。

说实话,大多数商用芯片并没有用这个字段。但我碰到过一家做定制交换机芯片的客户,他们在 TS1 的自适应字段里塞了温度告警信息,结果跟标准解码器冲突。最后只能通过软件把这个字段的值过滤掉。

注意: 自适应字段虽然叫“adaptive”,但接收端默认必须忽略其内容。任何非零的自适应字段都应被视为可选特征,如果对方不支持,会产生未定义行为。

TS1 完整的帧结构

把上面所有部分拼在一起,就是一帧标准的 TS1:

符号序号: 0     1     2     3     4     5     6     7     8     9     10    11    12    13    14    15
内容:     COM   ID    LN    Ln    FTS0  FTS1  FTS2  FTS3  BI    SI    Rate  SSC   0     0     0     0

其中 ID=0x1E,LN=Link Number,Ln=Lane Number,FTS0~FTS3 是 4 个 FTS 计数字节,BI=Bit Lock indicator,SI=Symbol Lock indicator,Rate=Rapid ID,SSC=SSC Status。最后四个 0 是自适应字段。

SVG:TS1 协议帧结构图

TS1 有序集帧结构(16 符号) 同步字节 COM (K28.5) 传输协议头 ID=0x1E Link# + Lane# 有效载荷 FTS计数字节(×4) 锁定标志+速率/SSC 自适应字段 保留/厂商自定义 1 符号 8b/10b 帧起始 3 符号 标识+链路/通道号 8 符号 训练参数与状态 4 符号 预留/扩展 总计 16 符号,经过 8b/10b 编码后形成 20-bit 并行数据

解码 TS1 的实用步骤

如果你需要自己实现解码器,我建议按这个顺序来:

  1. 等待 COM 符号出现,标记帧起始
  2. 读取第 1~3 符号,检查 ID 是否为 0x1E,不是则丢弃
  3. 提取 Link Number 和 Lane Number,注意初始值为 0xFF
  4. 读取 8 个有效载荷字节,解析 FTS、Bit Lock、Symbol Lock 和速率信息
  5. 忽略最后 4 个自适应字段(除非你知道对方有扩展)

我曾经偷懒直接用一个状态机连续接收 TS1,结果没对 COM 做余数检查,导致某个时钟周期偏移后整个解析错位。后来我加了一个滑动窗口校验,每次收到 16 个符号后验证最后一个自适应字段是否为 0,才算过关。

避坑指南: PCIe 规范允许接收端在任意时刻丢弃 TS1(比如遇到了 Link Number 不匹配)。但千万别在训练没完成时就把有效载荷里的 Bit Lock 当作最终锁定信号——那个只是中间状态。我见过有人直接读它去触发 DFE 系数更新,结果滤波器从来没收敛过。

TS1 协议本身其实不复杂,难的是在高速串行环境下的边界处理和状态机同步。你只要把帧结构背下来,再配合示波器或逻辑分析仪看过几次波形,自然就能一眼识别出每个字段的位置。嗯,就是这样。