PCIe 概述:总线架构、分层模型、链路训练在初始化中的作用

做 PCIe 开发这些年,我每次带新人时都会先问一个问题:你知道一根 PCIe 链路从插上电到能传数据,中间经历了什么吗? 很多人只记得“插卡就能用”,却不知道背后有一整套复杂的握手过程。这一章我们先搭个整体的架子,把总线长什么样、分层怎么分、链路训练到底扮演什么角色——一次说清楚。

总线架构概览——从一条 Lane 说起

PCIe 不是一条“总线”共享带宽,而是点对点的串行连接。每个设备之间通过一组 Lane 通信。一条 Lane 就是一对差分发送和一对差分接收。我习惯把这个结构想象成一条专用高速公路:每个方向有自己的车道,互不干扰。

核心特性:
  • 点对点架构,取代了 PCI/PCI-X 的共享总线
  • 使用差分信号(TX+/TX-,RX+/RX-),抗干扰强
  • 带宽可扩展:×1、×2、×4、×8、×16、×32
  • 支持热插拔、电源管理、错误恢复

你可能要问:为什么不用并行总线了?我早期做过一次 PCI 调试,遇到信号反射和 skew 问题,调了一个月才稳定。PCIe 用串行差分,其实把物理层麻烦事简化了,反而把复杂度转移到了链路训练和协议层。

分层模型——三层架构的职责

PCIe 的分层模型和网络有点像,从上到下分别是:事务层(Transaction Layer)、数据链路层(Data Link Layer)、物理层(Physical Layer)。每一层只跟邻居通信,上层不知道下层在干嘛。

层次主要职责我的备注
事务层 处理读写请求、完成包(TLP);负责流量控制、事务排序 我调试时最常抓的是 TLP 头,看地址和长度对不对
数据链路层 加序列号、CRC 校验、确认重传(ACK/NAK) 这一层能保证链路可靠传输,但不是每个工程师都愿意花时间看 DLLP
物理层 串并转换、8b/10b 或 128b/130b 编解码、电源管理、链路训练 链路训练全在这层,尤其是物理层的 LTSSM 状态机
提示: 链路训练的终点是 Symbol Lock,但这属于物理层的范畴。后面的数据链路层和事务层要等链路进入 L0 后才能启动。

链路训练在初始化中的作用

说白了,链路训练就是让两端的物理层“对上暗号”。为什么需要它?

  • Bit Lock:接收器从串行数据流中恢复出时钟,并且找到每个 bit 的边界。没有它,数据都是乱码。
  • Symbol Lock:在 8b/10b 编码中,接收器识别出 10-bit 的符号边界。我记得第一次看到示波器上的眼图,怎么都想不到那个同步过程这么精妙。
  • Lane 协商:确定链路宽度(×1 还是 ×16)、速率(Gen1/2/3/4)、极性反转等。
  • 链路状态管理:进入 L0 正常工作,或者进入 L0s/L1 省电。

我曾经碰过一个坑:某款 PCIe 交换机在物理层训练时总卡在 Polling.Active 状态,后来发现是因为参考时钟抖动太大,导致 Bit Lock 无法建立。换了一颗低抖动的晶振,问题就解决了。你看,链路训练的起点——Bit Lock 有多关键。

一张图看懂整体流程

下面这张图把三层模型和链路训练的起点画在一起了。注意看最下面的 Bit Lock 和 Symbol Lock 阶段——这是我们这门课的核心。

PCIe 分层模型与链路训练过程 事务层(Transaction Layer) 数据链路层(Data Link Layer) 物理层(Physical Layer) Detect Polling Configuration L0 (正常工作) ★ Bit Lock 在 Polling 阶段完成 ★ Symbol Lock 在 Configuration 阶段开始 链路训练阶段 从 Detect → L0 逐步建立
注意: 上图中的 Polling 阶段完成后,设备才会进入 Configuration 阶段做 lane 协商和 symbol lock。实际操作中,如果 Polling 阶段因电气噪声失败,LTSSM 会回退到 Detect 重新开始。我以前在实验室就遇到过因为电源纹波太大反复回退的情况。

我的避坑指南

  • 时钟必须稳定: Bit Lock 的第一步是 CDR(时钟数据恢复),参考时钟抖动超过 ±300ppm 就容易失败。我习惯在 PCB 设计时给参考时钟加 π 型滤波。
  • 端接电阻不能省: 差分对的共模电压要靠电阻提供,忘记加终端电阻会导致 LTSSM 一直卡在 Detect.Quiet。
  • 眼图测试是基本功: 我每次新板回来,第一件事就是用示波器看 TX 的眼图。眼图张开高度不足 200mV,基本别想通过 Bit Lock。
  • 设备复位时序: PERST# 信号必须在主电源稳定后至少 100μs 再去释放,否则物理层根本起不来。我见过有人把 PERST# 跟电源同时释放,结果链路训练永远超时。