✨前言:
Flow Control即流量控制,这一概念起源于网络通信中。PCIe总线采用Flow Control的目的是,保证发送端的PCIe设备永远不会发送接收端的PCIe设备不能接收的TLP(事务层包)。也就是说,发送端在发送前可以通过Flow Control机制知道接收端能否接收即将发送的TLP。
✨Flow Control详解
在PCI总线中,并没有Flow Control这样的机制,因此发送端并不知道当前时刻,接收端能否接收对应的TLP。因此,发送端只能先尝试发送,期间可能会被插入多个等待周期(接收设备尚未就绪等原因),甚至是重发(Retries)等。
PCIe Spec规定,PCIe设备的每一个端口(Ports)都必须支持Flow Control机制,在发送TLP之前,Flow Control必须先检查接收端口是否有足够的Buffer空间来接收这个TLP。当PCIe设备支持多个VC(Virtual Channel)时,Flow Control机制可以显著地提高总线的传输效率。
PCIe Spec规定,每个PCIe设备最多支持8个VC,并且每个VC的Flow Control Buffer是完全独立的。也就是说,某一个VC的Flow Control Buffer满了,并不会影响其他的VC的通信。
前面的文章中介绍过,Flow Control机制是通过相邻两个端口(Ports)的数据链路层之间发送DLLP(Flow Control DLLPs)来实现的。也就是说Flow Control是一种点到点(Point to Point)的方式,而非端到端(End to End)。在进行初始化的时候,接收端需要向发送端报告(reports)其Buffer的大小,在正常运行状态(Run-time)时,会周期性地通过Flow Control DLLPs来告知发送端,接收端的各个Buffer的大小。
需要注意的是,虽然Flow Control DLLP只在相邻的数据链路层之间传输,但是相关的Buffer和计数器(FC Counter)确是在事务层(Transaction Layer)的,即事务层参与了Flow Control机制的管理。如下图所示:
✨Flow Control Buffer类型
前面的文章中多次介绍过,TLP一共有三大类:Posted Transactions(包括Memory Writes和Messages)、Non-Posted Transactions(包括Memory Reads、Configuration Reads and Writes、IO Reads and Writes)以及Completions(包括Read and Write Completion)。并且知道,TLP可以分为两个部分,Header和Data部分。Flow Control为了获得更高的数据传输效率,将这三类TLP分开存放,同时将Header与Data部分也分开存放。因此,一共存在六种不同的Flow Control Buffer类型,如下图所示:
📌
1.PH: Posted Header Credit Posted事务包括那些没有直接响应的事务,例如写操作。PH用于控制Posted类型事务的头部信息的流量控制。
2.PD: Posted Data Credit 对于带有数据负载的Posted事务,PD用于控制数据部分的流量控制。
3.NPH: Non-Posted Header Credit Non-Posted事务是那些期望收到响应的事务,例如读操作。NPH用于控制Non-Posted类型事务的头部信息的流量控制。
4.NPD: Non-Posted Data Credit 支持Non-Posted事务的数据负载流量控制的是NPD信用。
5.CPLH: Completion Header Credit 对于其他设备发起的操作的响应,称为Completion,CPLH用于控制Completion事务的头部信息的流量控制。
6.CPLD: Completion Data Credit 和PH、PD类似,CPLD则用于控制Completion事务的数据负载的流量控制。
✨Flow Control Credits
Flow Control Buffer的存储单元(Unit)被称作Flow Control Credits。对于Header来说,Requests TLP每个unit等于5DW,而Completions TLP每个unit等于4DW。对于Data来说,每个unit等于4DW,即Data Buffer是按照16个字节对齐的。对于各种类型的Buffer的最小值如下表所示:
最大值如下表所示:
在任何事务层包(TLP)发送之前,PCIe总线必须要先完成Flow Control初始化。当物理层完成链路初始化后,便会将LinkUp信号变为有效,告知数据链路层可以开始Flow Control初始化了。如下图所示:
📌 注:由于VC0是默认使能的,所以当Flow Control初始化开始时,其会被自动的初始化。其他的Virtual Channel是可选的,只有当被配置为使能的时候才会被初始化。
Flow Control初始化被分为两个步骤,FC_Init1和FC_Init2,其在整个数据链路控制和管理状态机(Data Link Control & Management State Machine)的位置如下图所示:
✨FC_INIT1:
在FC_Init1步骤中,PCIe设备会连续地发送三个InitFC1类型的Flow Control DLLP来报告其接收Buffer 的大小。三个DLLP的顺序是固定的:Posted、Non-Posted然后是Completions。如下图所示:
✨FC_INIT2:
FC_Init2与FC-Init1类似,同样是连续的发送三个InitFC2类型的DLLP,当完成后,DLCMSM(上一篇文章中提到的状态机)会切换到DL_Active状态,表明数据链路层初始化完成。
📌 注:可能有人会有疑惑了,FC_Init1和FC_Init2干的活不是差不多嘛,为什么还需要FC_Init2呢?原因是,不同的设备完成FC_Init1的时间可能是不同的,增加FC_Init2是为了保证每个设备都能收到FC初始化DLLP。
FC_Init DLLP的格式如下图所示:
在完成FC初始化之后,相邻的两个设备之间会周期性的通过Updated FC DLLP更新接收Buffer的大小。如下图所示:
Update FC DLLP的格式与FC_Init的格式是类似的,具体如下:
前面说到。Update FC DLLP是周期性发送的,周期的值可以通过以下公式计算得:
具体可以参考PCIe的Spec,这里不再详细介绍,下面给出Gen1和Gen2的周期表格(根据公式计算的结果)。其中UF为UpdateFactor。
Gen1 (2.5GT/s)如下表所示:
Gen2(5GT/s)如下表所示:
Gen3 (8GT/s)如下表所示:
✨小结:
在PCIe系统中,每个设备维护着一组信用计数器,这些计数器记录了对端设备在PH, PD, NPH, NPD, CPLH, CPLD这些缓冲中各自可以接受的最大数据量。当设备发送一个TLP时,它会检查相关的信用计数器以确认是否有足够的信用。如果信用足够,该TLP被发送并适当地减少信用。接收设备处理完TLP后,会通过ACK包返还信用,以允许发送更多的数据。
流量控制机制确保了PCIe通信的可靠性和效率,同时允许高速和高数据量的传输,而不会造成接收端的缓冲区溢出。通过管理不同类型事务的流控信用,PCIe在多种操作和数据传输场景中保持高性能和低延迟。