第四节:DLLP与ACK/NAK机制——数据链路层的“交通警察”

各位同学,今天我们来聊聊PCIe数据链路层里最核心的几个机制。说实话,很多工程师对事务层(TLP)研究得很深,但一到数据链路层就有点含糊。我个人觉得,恰恰是这一层决定了你的PCIe链路到底稳不稳、快不快。

数据链路层主要干三件事:包类型管理、ACK/NAK重传、以及流控。这三者配合起来,就像高速公路上的交通警察——既要保证数据不撞车,又要保证不堵车。

4.1 DLLP:数据链路层的“信使”

DLLP的全称是Data Link Layer Packet,它和TLP最大的区别是什么?DLLP只在相邻的两个设备之间传递,不会像TLP那样一路跑到对端去。你可以把它想象成两个邻居之间的对话,而不是跨城市的快递。

DLLP主要分这么几类:

  • ACK/NAK DLLP:用于确认TLP是否被正确接收。我习惯叫它“回执包”。
  • InitFC1/InitFC2 DLLP:用于初始化流控信用量。这个后面细说。
  • UpdateFC DLLP:用于更新流控信用量,告诉对端“我又腾出空间了”。
  • PM DLLP:电源管理相关的包,比如进入L1低功耗状态。

嗯,这里要注意:DLLP的长度是固定的,只有6个字节。为什么这么短?因为它的使命就是快,不能拖泥带水。我在项目中曾经遇到过一个问题,某个IP的DLLP处理逻辑写得太复杂,导致链路层延迟飙升,最后不得不重写——说白了,DLLP的解析必须在一个时钟周期内完成。

关键点:DLLP的序列号(Sequence Number)是隐式的,不像TLP那样显式携带。它通过包的类型和顺序来区分。

4.2 ACK/NAK重传机制:丢了就重发

这个机制是PCIe可靠传输的基石。你想想看,高速信号在PCB上跑,难免会有误码。如果丢了一个包,整个链路就卡住了,那还得了?

ACK/NAK的流程其实很简单:

  1. 发送端给每个TLP分配一个序列号(Sequence Number)。
  2. 接收端收到TLP后,检查CRC是否正确。
  3. 如果正确,回复一个ACK DLLP,告诉发送端“我收到了,你可以释放这个包的缓存了”。
  4. 如果错误,回复一个NAK DLLP,发送端就得重传这个包以及之后的所有包。

我曾经在调试一个Gen3的板卡时,发现链路偶尔会卡死。查了半天,原来是接收端的NAK处理逻辑有bug——它只重传了出错的包,但忘了重传后面的包。结果发送端和接收端的序列号对不上了,整个链路就僵住了。这个坑,我到现在都记得很清楚。

避坑指南:我曾经见过有人为了省资源,把重传缓冲区(Replay Buffer)设得太小。结果链路一有重传,缓冲区就溢出了。记住:重传缓冲区的大小必须能容纳一个ACK延迟窗口内的所有TLP。这个窗口通常由硬件参数决定,但至少得能装下8-16个TLP。

4.3 流控机制:信用量初始化与更新

流控(Flow Control)是PCIe里一个非常巧妙的设计。它不像传统总线那样用“握手机制”来控制数据流,而是用信用量(Credits)的方式。说白了,就是发送端先看看接收端还有多少“空位”,再决定发多少数据。

信用量分两类:

  • Posted Credits:用于Posted请求(比如Memory Write)。这类请求不需要返回Completion,所以信用量管理相对简单。
  • Non-Posted Credits:用于Non-Posted请求(比如Memory Read)。这类请求需要返回Completion,所以信用量要同时考虑请求和响应。

初始化过程是这样的:

  1. 链路训练完成后,两端交换InitFC1 DLLP,告诉对方自己的接收缓冲区大小。
  2. 然后交换InitFC2 DLLP,确认初始化完成。
  3. 之后,接收端通过UpdateFC DLLP定期更新信用量。

我建议你在做验证时,重点关注信用量更新的时机。如果更新太频繁,会占用带宽;如果更新太慢,发送端会饿死。我记得有一次,某个IP的流控逻辑把信用量更新周期设成了固定值,结果在高速场景下,发送端总是等不到信用量,吞吐量直接腰斩。

小技巧:在仿真验证时,可以故意让接收端延迟发送UpdateFC DLLP,看看发送端会不会出现“信用量饥饿”。这是检验流控逻辑健壮性的好方法。

4.4 实战经验:如何优化DLLP和ACK/NAK

说了这么多理论,我们来点实际的。如果你想让PCIe链路的吞吐量更高,可以从这几个方向入手:

优化点 说明 我的建议
ACK延迟 接收端可以延迟发送ACK,合并多个ACK为一个DLLP 延迟不要超过4个TLP,否则重传延迟会变大
重传缓冲区大小 缓冲区越大,能容忍的延迟越大 至少设为16个TLP,Gen4以上建议32个
信用量更新频率 更新太慢会导致发送端等待 建议每释放4个TLP就更新一次
DLLP优先级 ACK/NAK DLLP应该比UpdateFC DLLP优先级高 在仲裁逻辑里,给ACK/NAK最高的优先级

嗯,最后再啰嗦一句:数据链路层的设计,本质上是在“可靠性”和“性能”之间做权衡。你不可能既要零丢包,又要无限带宽。理解了这一点,你就能在设计时做出更明智的选择。

好了,这一节就到这里。下一节我们会讲事务层的TLP格式和路由机制,到时候见。