4. PCIe协议基础(三):数据链路层与物理层

各位同学,今天我们聊聊PCIe协议栈里最「接地气」的两层——数据链路层和物理层。说实话,很多工程师对事务层很熟悉,毕竟那是和软件打交道的。但真正出问题的地方,往往就在链路层和物理层。我在调试PCIe的时候,十次有八次是在跟DLLP和PIPE接口较劲。

4.1 数据链路层:可靠传输的守护者

数据链路层夹在事务层和物理层之间,说白了就是个「快递中转站」。它负责两件事:一是保证TLP包能完整无误地送到对端,二是管理链路的流量控制。

4.1.1 DLLP:链路层的「小纸条」

DLLP(数据链路层包)是链路层自己产生的报文,长度固定为6字节。它不像TLP那样需要端到端传递,只在相邻的两个PCIe设备之间交换。我习惯把DLLP理解成「小纸条」——不承载用户数据,只传递控制信息。

常见的DLLP类型有这么几种:

  • ACK/NAK:确认TLP是否被正确接收
  • InitFC1/InitFC2:初始化流量控制信用
  • UpdateFC:更新流量控制信用值
  • PM_Enter_L1/PM_Exit_L1:电源状态管理

嗯,这里要注意:DLLP的发送优先级高于TLP。为什么?因为ACK/NAK必须尽快传回去,否则发送端会一直重传,链路效率就下来了。

4.1.2 ACK/NAK机制:丢了就重传

这个机制其实很简单。发送端每发一个TLP,就把它存到重传缓冲区里。接收端收到后,如果CRC校验通过,就回一个ACK;如果校验失败,就回一个NAK。

发送端收到NAK,或者超时没收到ACK,就会重传那个TLP。我刚开始做PCIe的时候,总觉得这个机制有点「笨」——为什么不直接重传坏的包呢?后来才明白,PCIe是乱序传输的,接收端必须按顺序把TLP交给事务层。所以一旦某个包丢了,后面的包都得等着。

关键点:ACK/NAK机制保证了TLP的可靠传输,但代价是增加了延迟。对于延迟敏感的应用(比如DMA),需要仔细权衡重传缓冲区的深度。

我曾经在一个项目中遇到过这样的问题:重传缓冲区设得太小,导致频繁超时重传,链路利用率直接掉到60%以下。后来把缓冲区从8个TLP增加到32个,问题就解决了。你想想看,缓冲区大小这个参数,真不是随便设的。

4.1.3 流量控制:别把对端撑爆了

流量控制是数据链路层的另一个核心功能。它用「信用」机制来管理——接收端告诉发送端自己有多少缓冲区空间,发送端根据这个信用值来决定发多少数据。

信用分两种:

  • Posted信用:用于Memory Write等不需要响应的请求
  • Non-Posted信用:用于Memory Read等需要响应的请求

我建议你在设计时,把Posted和Non-Posted的信用分开管理。因为Non-Posted请求需要等待完成包,如果混在一起,很容易造成死锁。

小技巧:在仿真阶段,可以故意把信用值设得很小,来验证流量控制逻辑是否正确。我每次做验证都会跑这个场景,能发现不少边界问题。

4.2 物理层:从逻辑到电信号的桥梁

物理层是PCIe协议栈的最底层,负责把数据链路层的包转换成电信号,通过差分对传出去。它分为两个子层:逻辑子层和电气子层。

4.2.1 PIPE接口:物理层的「标准插座」

PIPE(物理接口用于PCI Express)是Intel定义的一个标准接口,用来连接MAC层和PHY层。说白了,它就是个「插座」——MAC层通过PIPE接口控制PHY层,收发数据。

PIPE接口的主要信号包括:

信号名方向描述
TxData[7:0]MAC→PHY发送数据
TxDataKMAC→PHY发送数据是否为控制字符
RxData[7:0]PHY→MAC接收数据
RxDataKPHY→MAC接收数据是否为控制字符
RxValidPHY→MAC接收数据有效
PowerDown[1:0]MAC→PHY电源状态控制

我个人习惯把PIPE接口的时序图画出来,对照着看。因为PIPE接口的延迟是固定的——从MAC发出数据到PHY串行输出,中间有固定的时钟周期数。这个延迟在做链路训练时特别重要。

4.2.2 8b/10b编码:PCIe Gen1/2的「老伙计」

8b/10b编码是IBM发明的,把8位数据编码成10位符号。为什么要这么做?

  • 直流平衡:保证0和1的数量大致相等,避免信号漂移
  • 时钟恢复:保证有足够的跳变沿,接收端能恢复时钟
  • 特殊字符:用K码表示控制信息(比如包起始、包结束)

8b/10b编码的效率是80%,也就是说每传10位数据,只有8位是有效数据。PCIe Gen1和Gen2都用这个编码。我刚开始做Gen1的时候,觉得编码表好复杂,后来发现其实只需要记住几个常用的K码就行:

K28.5 (0xBC):逗号字符,用于位对齐
K27.7 (0xFB):SKP字符,用于时钟补偿
K30.7 (0xFE):包起始
K29.7 (0xFD):包结束

注意:8b/10b编码有「游程长度」限制——连续相同位的数量不能超过5个。如果数据本身有长串的0或1,编码器会自动插入跳变。我在调试时遇到过因为编码器实现有bug,导致链路训练失败的情况,查了两天才找到原因。

4.2.3 128b/130b编码:Gen3/4/5的「新宠」

到了PCIe Gen3,速率提升到8GT/s,8b/10b编码的80%效率就有点不够看了。于是引入了128b/130b编码,效率提升到98.46%。

128b/130b编码的原理是:把128位数据加上2位同步头,组成130位的块。同步头有两种:

  • 01:数据块(128位全是数据)
  • 10:控制块(128位中包含控制信息)

为什么不用00和11?因为要保证有跳变沿,方便时钟恢复。嗯,这个设计很巧妙。

128b/130b编码还引入了加扰机制——用一个LFSR(线性反馈移位寄存器)对数据进行加扰,避免出现长串的0或1。接收端再用同样的LFSR解扰。我建议你在实现加扰器时,注意LFSR的初始值,不同厂商的PHY可能要求不同。

对比总结

  • 8b/10b:简单可靠,但效率低,适合Gen1/2
  • 128b/130b:效率高,但实现复杂,需要加扰,适合Gen3及以上

我曾经在Gen3的项目中,因为加扰器的种子没设对,导致链路训练时CRC校验一直失败。后来翻spec才发现,Gen3的加扰器种子和Gen4不一样。你想想看,这种细节问题,光靠仿真很难发现,必须做硬件测试才能暴露。

4.3 本章小结

数据链路层和物理层,是PCIe协议栈里最「硬核」的部分。数据链路层负责可靠传输和流量控制,物理层负责编码和信号传输。我个人觉得,理解这两层的关键在于:

  • 数据链路层:ACK/NAK和信用机制是核心,缓冲区大小要仔细设计
  • 物理层:PIPE接口是MAC和PHY的桥梁,编码方式决定了链路效率

好了,今天就聊到这里。下一章我们讲PCIe的配置空间和枚举过程,那是系统软件和硬件交互的关键环节。

公众号:蓝海资料掘金营,微信deep3321