写在前面
笔者在工作中需要包个 PCIe wrapper,正在努力飞快学习 PCIe ing.
本文系转载,略做格式调整与增加解释(使用斜体表示),转自:
PCIe扫盲系列博文连载目录篇(第三阶段)-Felix-电子技术应用-AET-中国科技核心期刊-最丰富的电子设计资源平台
转载正文
DLLP(数据链路层包)详解
首先说明一下,在本次连载的博文中,DLLP一般指的是由发送端的数据链路层发送,接收端的数据链路层接收的数据包,其和事务层(Transaction Layer)一般没有什么关系。本文将要介绍的DLLP指的正是这样的数据包,其一般用于Ack/Nak机制、功耗管理、Flow Control(流量控制)和一些厂商自定义用途等。示意图如下:
DLLP的格式是固定的,一共有8个字节,包括Framing(SDP & END)。和TLP不一样的地方是,DLLP并未携带任何路由信息,原因很简单,因为DLLP只在相邻的两个设备的数据链路层之间通信,根本不需要路由。并且DLLP一般也不需要和事务层交换信息。
注:前面文章中介绍的Flow Control、Ack/Nak等都是针对TLP,并不会对DLLP产生影响。但是这些功能正是借助DLLP才得以实现的。
DLLP的一般格式如下图所示:
DLLP的类型与目标应用如下表所示:
其中,用于Ack/Nak的DLLP的格式如下:
当用于 Ack/Nak 时,在 Byte 2&3 有 12 个有效比特,用于放置 PCIE 报文序列号(NEXTRCVSEQ)。
当收到完好的 TLP 报文时,
- 如果接收报文序列号等于 NEXTRCVSEQ,那填入当前序列号,发送 ACK 类型报文给对端。
- 如果接收报文序列号小于 NEXTRCVSEQ,即受到了重复报文,则填入 NEXTRCVSEQ -1,即上一个完好 TLP 报文序列号,以 ACK 类型报文发送给对端。
如果收到错误的/序列号大于 NEXTRCVSEQ 的 TLP 报文,
- 则发送 NCK 报文给对端,填入 NEXTRCVSEQ -1,即上一个完好 TLP 报文序列号。
用于功耗管理(Power Management)的DLLP的格式如下:
用于Flow Control的DLLP的格式如下:
厂商自定义的DLLP的格式如下:
Ack/Nak 机制详解(一)
前面在数据链路层入门的文章中简单地提到过Ack/Nak机制的原理和作用,接下来的两篇文章中将对Ack/Nak机制进行详细地介绍。
Ack/Nak是一种由硬件实现的,完全自动的机制,目的是保证TLP有效可靠地传输。
Ack DLLP用于确认TLP被成功接收,Nak DLLP则用于表明TLP传输中遇到了错误。
如上图所示,发送方会对每一个TLP在Replay Buffer中做备份,直到其接收到来自接收方的Ack DLLP,确认该DLP已经成功的被接受,才会删除这个备份。
如果接收方发现TLP存在错误,则会向发送方发送Nak DLLP,然后发送方会从Replay Buffer中取出数据,重新发送该TLP。
Ack/Nak机制内部的详细结构图如下图所示:
下面对图中的各个Elements分别做一个简单地介绍。
首先是发送端的Elements:
来个大图特写:
· NEXT_TRANSMIT_SEQ Counter
NEXT_TRANSMIT_SEQ Counter,即NTS计数器,是一个12位的计数器。当数据链路层处于DL-Down状态或者复位时,该计数器会被初始化为0。
该计数器只会执行加一操作,也就是说当其到达最大值4095时,在进行加一操作则会变成0(Roll Over)。
该计数器用于产生下一个待发送的TLP的序列号(Sequence Number)。每一个序列号都是与一个TLP所唯一对应的,可以说这个序列号正是整个Ack/Nak机制的关键。
· LCRC Generator
LCRC产生器用于产生一个32位的CRC值,其作用于整个TLP和其对应的序列号。
· Replay Buffer
Replay Buffer是Mindshare书中的叫法,在PCIe Spec中,这个Buffer的名称叫做Retry Buffer。
Replay Buffer中按照传输顺序,存储了整个TLP、序列号和LCRC,如下图所示:
当发送端收到来自接收端的Ack DLLP时,会将Buffer中相应的TLP(包括对应的序列号和LCRC)移除;
如果接收到的是Nak DLLP,则会将Buffer中响应的TLP(包括对应的序列号和LCRC)重新发送给接收端。
· REPLAY_TIMER Count
REPLAY_TIMER是一种看门狗定时器,当该定时器溢出,则表明发送端已经发送了一个或者多个TLP,但是并未收到来自接收端的应答信号(Ack/Nak)。
此时,发送端会将Replay Buffer中的TLP重新发送,并将看门狗定时器重启。
只要发送端发送了任何TLP,该定时器便会启动,在接收到来自接收端的应答信号之前都会持续地运行。
当收到应答信号之后,定时器会立即被清零。此时如果Replay Buffer仍然有TLP(表明还有TLP被发送,但是仍未得到应答),定时器又会被立即被重新启动。如果Buffer中是空的,则定时器不会被重新启动,直到新的TLP被发送。
· REPLAY_NUM Count
这是一个2位的计数器,用于记录同一个TLP发送失败的次数,当其值从11b变为00b时(溢出了,表示尝试发送某个TLP失败了4次),数据链路层会自动地强制物理层重新进行链路训练(即LTSSM进入Recovery状态)。当完成链路训练之后,便会重新发送之前发送失败的TLP。
当发送端接收到来自接收端的Nak DLLP或者发送端的看门狗定时器(REPLAY_TIMER)溢出时,该计数器都会被加一;当接收到Ack DLLP时,该计数器则会被清零。
· ACKD_SEQ Register
ACKD_SEQ寄存器用于存储最近接收到的Ack或者Nak DLLP中的序列号。当复位或者数据链路层处于无效状态时,该寄存器会被初始化为全1。关于ACKD_SEQ寄存器的具体用法会在后续的文章中,用例子的形式详细说明。
· DLLP CRC Check
接收端在接收到来自发送端的DLLP后,首先会检查其DLLP CRC,如果发现有错误,则会直接将其丢弃,认为其是无效的DLLP。
然后是接收端的Elements:
首先来一张大图特写:
· LCRC Error Check
顾名思义,LCRC Error Check用于检查接收到的TLP是否存在错误。如果存在错误,则将对应的TLP直接丢弃,然后产生一个Nak DLLP发送给发送端,让其重新发送该TLP。
· NEXT_RCV_SEQ Counter
NEXT_RCV_SEQ是一个12位的计数器,即Next Receive Sequence Number,其值为已经成功接收的TLP的序列号加1。主要用于检查当前接收到的TLP是不是应该接收到的TLP。
如果NEXT_RCV_SEQ和当前接收到的TLP中的序列号的值相等,则认为这是一个有效的TLP,但是接收端并不会立即向发送端发送Ack DLLP,而是等到AckNak_LATENCY_TIMER溢出时才向发送端发送Ack DLLP。
也就是说,一个Ack DLLP可能会对应多个TLP,接收端不会每成功接收到一个TLP便向发送端发送Ack DLLP。
如果当前接收到的TLP中的序列号小于NEXT_RCV_SEQ(且差值不超过2048),则认为该TLP之前已经成功发送过了,此次是重复发送。
需要注意的是,PCIe Spec认为重复发送并不是一个错误,只是直接将该TLP丢弃,并没有Nak或者Error Reporting,但是会返回一个包含有上一次成功接收的TLP的序列号(NRS-1)的Ack DLLP给发送端。
如果当前接收到的TLP的序列号大于NEXT_RCV_SEQ,表明传输过程中漏掉了一些TLP。此时,接收端会返回Nak DLLP,并直接丢弃该TLP。
一个简单的例子如下图所示:
· NAK_SCHEDULED Flag
NAK_SCHEDULED是一个标志位,当接收端产生Nak DLLP时,该标志位会被置位。当接收端成功接收到有效的TLP时,该标志位会被清零。需要特别注意的是,当该标志位处于置位状态时,接收端不应产生其他的Nak DLLP。
· AckNak_LATENCY_TIMER
AckNak_LATENCY_TIMER定时器会在接收端成功接收到有效的TLP,且并未向发送端返回Ack DLLP之前运行。当AckNak_LATENCY_TIMER定时器溢出时,接收端会立即向发送端返回Ack DLLP(携带的序列号为NRS-1,即一个Ack对应多个有效的TLP)。
无论接收端返回Ack还是Nak,该定时器都会被复位,但是只有当接收端再次收到有效的TLP时,该定时器才会被重新启动。
该定时器(REPLAY_TIMER)的值是由PCIe Spec规定的和Lane的数量与Max_Payload有关,Gen1的值如下图所示:
Gen2(5GT/s)如下图所示:
注:该定时器(REPLAY_TIMER)的值是AckNak_LATENCY_TIMER定时器值的三倍。
而REPLAY_TIMER的值则如下表所示(Gen1和Gen2):
· Ack/Nak Generator
显然,Ack/Nak Generator的功能是产生Ack或者Nak DLLP。其格式如下:
最后,介绍一下PCIe Spec中推荐的包优先级顺序。我们知道,PCIe总线通信中,存在多种类型的包,包括TLP、DLLP和Ordered Sets等。
为了能够是总线达到最优的传输效率,PCIe Spec推荐对这些包的优先级做如下的设置:(当然这只是推荐,并没有强制厂商一定要这要去实现)
- Completion of any TLP or DLLP currently in progress (highest priority)
- Ordered Set
- Nak
- Ack
- Flow Control
- Replay Buffer re‐transmissions
- TLPs that are waiting in the Transaction Layer
- All other DLLP transmissions (lowest priority)
注:这里说的优先级和QoS中说的优先级是两码事,千万不要搞混了。
Ack/Nak 机制详解(二)
这一篇文章来简单地分析几个Ack/Nak机制的例子。
Example 1. Example of Ack
Step1 设备A准备依次向设备B发送5个TLP,其对应的序列号分别为3,4,5,6,7;
Step2 设备B成功的接收到了TLP3,并将NEXT_RCV_SEQ从3加到4,但是设备B没有立即向设备A返回Ack(此时AckNak_LATENCY_TIMER尚未溢出);
Step3 设备B又成功地接收到了TLP4和TLP5;
Step4 假设此时AckNak_LATENCY_TIMER溢出了,则设备B会向设备A返回一个带有序列号为5的Ack DLLP。同时,设备B将AckNak_LATENCY_TIMER复位,但是并未重新启动,直到设备B成功地接收到了TLP6。
Step5 设备A接收到了Ack5,将REPLAY_TIMER和REPLAY_NUM复位,然后将Buffer中的序列号5(和5之前)的TLP备份移除;
Step6 一旦设备B接收到TLP6,AckNak_LATENCY_TIMER又会被重新启动。
Example 2. Ack with Sequence Number Rollover
Step1 设备A准备依次向设备B发送序列号为4094,4095,0,1,2的TLP,注意第一个发送的是TLP4094,最后一个发送的是TLP2。也就是说序列号Rollover了;
Step2 设备B成功接收到TLP4094~TLP1后,假设此时AckNak_LATENCY_TIMER溢出了,则设备B向设备A返回Ack1 DLLP;
Step3 设备A接收到Ack1,并将Buffer中的序列号为1(和之前的,包括TLP4094~TLP1)的TLP备份移除。同时将REPLAY_TIMER和REPLAY_NUM复位。
Example 3. Example of Nak
Step1 假设设备A准备依次向设备B发送序列号为4094,4095,0,1,2的TLP;
Step2 设备B成功地接受了TLP4094,并将NEXT_RCV_SEQ加1,变为4095;
Step3 设备B接收到了TLP4095,但是该TLP并未通过CRC校检(即存在错误)。此时无论AckNak_LATENCY_TIMER处于何种状态,设备B都会立即向设备A返回Nak4094(注意返回的Nak DLLP中的序列号为上一次成功接收的TLP的序列号)。
同时设备B将AckNak_LATENCY_TIMER停止并复位;
Step4 设备B会一直等待设备A向其发送TLP4095,但是设备A却并不知发生了什么,在接收到设备B向其返回的Ack/Nak之前,会继续发送TLP0~TLP2,只是设备B会直接忽略这些TLP。
Step5 当设备A接收到来自设备B的Nak4094 DLLP时,会将Buffer中的TLP4094(和之前的TLP)移除,并从TLP4095从新开始发送。同时,将REPLAY_TIMER和REPLAY_NUM复位。
Step6 由于设备A接收到的是Nak,而不是Ack,因此设备A会重新启动REPLAY_TIMER并将REPLAY_NUM加一;
Step7 一旦设备B成功地接收到TLP4095,设备B便会清除NAK_SCHEDULED标志位,并将NEXT_RCV_SEQ计数器加一,同时重启AckNak_LATENCY_TIMER。
Example 4. Example of Lost TLPs
Step1 假设设备A准备依次向设备B发送TLP 4094,4095,0,1,2;
Step2 设备B成功地接收了TLP4094~TLP0,并向设备A返回Ack0,此时设备B的NEXT_RCV_SEQ为1;
Step3 设备A接收到设备B返回的Ack0,从Buffer中移除相应的TLP备份;
Step4 设备B接收到了TLP2(而不是TLP1),也就是说TLP1在传输过程中丢失了。此时,设备B会直接将TLP2丢弃,并将NAK_SCHEDULED标志位置位,同时向设备A返回Nak0 DLLP;
Step5 设备A接收到Nak0 DLLP后,会将Buffer中的TLP0(以及之前的,如果有的话)移除。同时,从TLP1开始,重新向设备B发送。
Example 5. Example of Bad Nak
Step1 设备A准备依次向设备B发送TLP 4094,4095,0,1,2;
Step2 设备B成功的接收了TLP4094~TLP0,但是由于AckNak_LATENCY_TIMER尚未溢出,所以设备B没有立即向设备A返回Ack DLLP;
Step3 设备B发现TLP1中存在错误,于是向设备A返回Nak0 DLLP,并将NAK_SCHEDULED标志位置位;
Step4 设备A发现其接收到的Nak0 DLLP中也存在错误(CRC校检不通过),于是直接丢弃了Nak0;
Step5 然而设备B却一直在等待设备A向其发送TLP1,在其成功接收TLP1之前,设备B不会返回任何Ack或者Nak,不管设备A向其发送什么(除TLP1之外的)。设备B的NAK_SCHEDULED标志位也一直保持置位;
Step6 尴尬的是,设备A并不知道设备B想要其重发TLP1(由于没有成功接收到Nak0)。因此,设备A会继续向设备B发送之后的TLP,但是由于一直没有接收到设备B的Ack/Nak DLLP,设备A的REPLAY_TIMER会在一段时间后溢出;
Step7 当设备A的REPLAY_TIMER溢出后,设备A会向Buffer中的所有TLP都重新发送一遍,并重启REPLAY_TIMER,同时将REPLAY_NUM计数器加一;
Step8 设备B会再次接收到TLP4094~TLP0,但是这在之前就已经成功接受到过了。因此设备B会直接将其丢弃,且不会像设备A返回任何的Ack或者Nak
Step9 此时,设备B再次接收到了TLP1,并未发现错误(成功接收)。于是,设备B将NAK_SCHEDULED标志位清零,并重启AckNak_LATENCY_TIMER,将NEXT_RCV_SEQ加一。
版权声明
版权声明:本文为 AET 博主「Felix」的原创文章,转载请附上原文出处链接及本声明。
原文链接:PCIe扫盲系列博文连载目录篇(第三阶段)-Felix-电子技术应用-AET-中国科技核心期刊-最丰富的电子设计资源平台
转载自:知乎
作者:Felix
推荐阅读
更多招聘及面经请关注FPGA的逻辑。