3. PCIe协议基础与分层模型
PCIe 协议并不是一个“单层”的传输协议,而是一套严格分层的通信规范。理解它的关键,不是记住每一层的名字,而是搞清楚:谁负责生成请求、谁负责保证可靠、谁负责把比特送出去。在 Lattice FPGA 上做 PCIe 高速互连时,这三层职责划分直接决定了你该看哪个寄存器、抓哪种包、以及问题出在链路上还是事务上。
PCIe 的分层模型自上而下分为三层:事务层(Transaction Layer)、数据链路层(Data Link Layer)和物理层(Physical Layer)。每一层只与相邻层交互,发送方向逐层封装,接收方向逐层解封装。这种“各管一段”的设计,使得协议可以在不同速率、不同宽度、不同介质上保持一致性。
事务层:请求的起点与终点
事务层是 PCIe 协议中与软件最接近的一层。它负责把来自应用或驱动的一次访问,翻译成一个标准的事务层包(TLP,Transaction Layer Packet)。TLP 是 PCIe 中真正承载“业务语义”的单元,它携带了请求类型、目标地址、数据长度、完成状态等信息。
事务层的主要职责包括:
- 请求生成:根据访问类型生成 Memory Read、Memory Write、Configuration Read/Write、Message 等 TLP。
- 地址路由:根据 TLP 头中的地址或 ID,决定该请求发往哪个设备。
- 流量控制:通过 Credit 机制,确保发送端不会压垮接收端的缓冲区。
- 完成包处理:对于非 Posted 请求(如读请求),接收端需要返回 Completion TLP。
在 Lattice FPGA 的 PCIe 硬核或软核中,事务层通常由 IP 核内部实现,用户逻辑只需要通过 AXI 或 Avalon 接口发起读写。但你必须理解:一次读操作在事务层会变成“请求 TLP + 完成 TLP”两个包,而写操作通常是 Posted 的,只有一个 TLP。
数据链路层:可靠性的守门人
如果事务层负责“说什么”,数据链路层就负责“确保对方听清”。它位于事务层和物理层之间,核心任务是保证 TLP 在链路上可靠传输。为此,数据链路层引入了两个关键机制:序列号(Sequence Number)和重传(Replay)。
发送时,数据链路层会给每个 TLP 加上一个 12 位的序列号,并计算 LCRC(Link CRC),然后交给物理层。接收端收到后,会检查序列号是否连续、LCRC 是否正确。如果发现错误或丢失,接收端会通过 DLLP(Data Link Layer Packet) 中的 NAK 通知发送端重传。
DLLP 是数据链路层自己的“内部消息”,它不承载事务语义,只用于链路管理。常见的 DLLP 包括:
- Ack/Nak:确认或否认 TLP 的接收状态。
- InitFC1/InitFC2:初始化流量控制 Credit。
- UpdateFC:更新 Credit 余量。
- PM:电源管理状态切换。
在调试时,如果你发现链路能建立但数据传输不稳定,往往不是事务层的问题,而是数据链路层的重传在频繁发生。此时应重点观察 LCRC 错误计数和重传次数。
物理层:比特的搬运工
物理层是 PCIe 协议的最底层,直接与 SerDes、差分对和参考时钟打交道。它的任务很纯粹:把数据链路层交下来的包,转换成高速串行比特流发送出去,并从接收到的比特流中恢复出包。
物理层内部又可分为逻辑物理层和电气物理层。逻辑物理层负责:
- 字节条带化(Byte Striping):把数据分散到多个 Lane 上。
- 加扰(Scrambling):避免长连 0 或长连 1,保证时钟恢复。
- 编码:PCIe 3.0 以前使用 8b/10b 编码,PCIe 3.0 及以后使用 128b/130b 编码。
- Ordered Set 生成与识别:用于链路训练、时钟补偿和状态切换。
Ordered Set 是物理层特有的概念,它是一组预定义的 16 字节序列,不参与事务传输,只用于链路管理。最常见的 Ordered Set 包括 TS1、TS2、SKP、EIEOS 等。例如,链路训练时,两端通过交换 TS1/TS2 来协商速率、宽度和均衡参数;SKP Ordered Set 则用于补偿收发端时钟频率差异,防止弹性缓冲区溢出或下溢。
在 Lattice FPGA 中,物理层通常由硬核 SerDes 实现,用户可以通过 IP 核配置 Lane 数量、参考时钟频率、预加重和均衡参数。如果链路训练失败,问题往往出在物理层:参考时钟不干净、差分对极性接反、或者均衡参数不匹配。
三层协作:一次 Memory Read 的完整旅程
为了把三层职责串起来,我们看一次典型的 Memory Read 在 PCIe 链路中发生了什么。假设 RC 要读取 EP 中某个 BAR 空间的数据:
- 事务层:RC 生成一个 Memory Read Request TLP,包含目标地址和请求长度。
- 数据链路层:给该 TLP 加上序列号和 LCRC,形成 DLLP 封装后的帧。
- 物理层:将帧进行加扰、编码,通过 Lane 发送出去。
- EP 物理层:接收比特流,解码、解扰,恢复出帧。
- EP 数据链路层:检查序列号和 LCRC,正确则回 Ack,错误则回 Nak。
- EP 事务层:解析 TLP,发现是读请求,从内部取出数据,生成 Completion TLP。
- 反向路径:Completion TLP 沿相同路径返回 RC,最终完成一次读操作。
可以看到,事务层关心“读什么”,数据链路层关心“有没有读丢”,物理层关心“能不能读过去”。三层各司其职,任何一层出问题,都会表现为上层功能异常,但根因却在不同位置。
关键概念对照与调试切入点
把三层职责和关键概念放在一起对照,能帮助你在调试时快速定位问题层级。下表总结了每层的核心对象、典型问题和观察点:
| 层次 | 核心对象 | 主要职责 | 典型问题 | 调试观察点 |
|---|---|---|---|---|
| 事务层 | TLP | 请求生成、地址路由、流量控制 | 读超时、完成包错误、地址越界 | TLP 类型、Tag、Completion 状态 |
| 数据链路层 | DLLP | 序列号、LCRC、重传、Credit 管理 | 重传频繁、Credit 耗尽、Ack/Nak 异常 | LCRC 错误计数、重传次数、FC Credit |
| 物理层 | Ordered Set | 链路训练、编码、加扰、时钟补偿 | 链路训练失败、Lane 错位、SKP 溢出 | LTSSM 状态、TS1/TS2、SKP 间隔 |
在 Lattice FPGA 的实际工程中,建议遵循“自下而上排查”的原则:先确认物理层链路训练完成、LTSSM 进入 L0 状态;再检查数据链路层是否有 LCRC 错误或重传;最后才去分析事务层的 TLP 是否正确。很多看似“事务层读不到数据”的问题,根因其实是物理层 Lane 极性接反或参考时钟抖动过大。
最后需要强调的是,三层之间并不是简单的“上下级”关系,而是通过严格的接口和信用机制协同工作。事务层不知道链路是 x1 还是 x8,数据链路层不知道 TLP 是读还是写,物理层更不关心包的内容。正是这种职责隔离,让 PCIe 能够在不同速率、不同宽度和不同介质上保持稳定。理解这一点,你在阅读 Lattice PCIe IP 文档、配置参数和调试链路时,就能清楚地知道每个设置项到底影响哪一层。