PCIe知识
BAR
CPU通过地址总线访问空间,编址方式有两种,如下图所示:
在ARM中,CPU通过不同地址区分内存和外设。在x86中,内存地址和IO地址是不同的地址总线,CPU访问内存地址和访问IO地址使用不同的指令。
由于x86的CPU只有memory指令和IO指令,没有配置指令,因此X86的CPU可以直接访问memory空间和IO空间,但是不能直接访问PCIe配置空间。
BAR(base address registers)就是为了把设备的内部各种资源映射到IO空间(IO BAR)或者memory空间(memory BAR)。
FPGA访问PC,TLP包里包含地址信息,该地址信息即PC的物理地址。FPGA可以发送合法的PCIe TLP包,并得到PC端的响应。
PC在启动时,BIOS探测所有外设,BIOS检测PCIe板卡有多少个BAR空间,每个空间有多大,然后为这些BAR空间分配对应大小的地址。因此,对PC来说,能访问的只有BAR空间。
DMA模式
Block DMA
PC申请一小片连续物理空间,设置好对应寄存器后启动DMA。如果一次DMA传输的数据比较大,需要申请大片连续物理空间。在Linux中这需要修改内核设置来实现,在Windows中无法实现。
在Windows中的一种处理方法是在PC上申请小片物理空间,然后重复DMA多次。这样做的话,每次DMA都会产生中断,而且每次DMA完成后都需要将数据从物理空间拷走才能释放空间。中断处理和数据拷贝是Block DMA性能进一步提高的障碍。
Scatter Gather DMA
Scatter Gather DMA(SG DMA)可以方便解决Block DMA中的问题。SG DMA要建立链表式的描述符,每个描述符包含PC端的物理地址、传输长度、链表last标志、下一个链表的物理地址等信息,也就是说包含了一次DMA所需要的全部信息。
板卡顺序执行链表中的DMA任务,在完成最后一个描述符后通过一次中断或查询作为操作的结束。
在实现时,PC申请内存中的页表,然后将页表组织成描述符链表并启动SG DMA。板卡根据链表信息将所有页表填满数据。由于非分页内存在各级别均可以使用,用户程序可以直接处理这些数据而不再需要进行数据拷贝。与Block DMA相比,几百次中断处理变为1次,几百次的内存拷贝页没有了,从而提升了传输效率。
Altera的Avalon-MM DMA接口的PCIe IP就是使用SG DMA实现。
Minimum Latency DMA
有的应用需求的数据量不大,但是对于数据进入板卡到PC能处理的延时非常关注。
例如每到板卡32B后需要最短时间内能被应用程序处理。如果采用Block DMA,每收到32B就产生中断并DMA传至内存,首先中断处理是us级别,其次总体数据传输效率非常低,PC负载也很高。采用SG DMA效果也差不多。
对于这种情况,可以借鉴SG DMA,在PC内生成一个环状链表,链表内的信息比SG DMA多一个Stick Bit。板卡每收到32B就组织一个MWr包填充一个描述符指定的空间,同时再发送一个MWr修改这个Stick Bit。软件轮询Stick Bit的修改情况,就可以去取Stick Bit所属描述符的空间了。
由于是环状链表,板卡可以循环执行。要注意,PC端处理的速度需要快过板卡接收的速度,否则会出现数据被覆盖。
这种方式不经过中断,也不去查询板卡信息,仅查询PC本地内存的数据,系统负载以及时延都可以有效控制。
FPGA的PCIe实现
Arria10的PCIe实现
Arria10的PCIe接口有3种解决方案:Avalon-ST接口、Avalon-MM接口、Avalon-MM DMA接口。主要区别如下图所示:
其中,Gen3 x8 Avalon-MM只支持Root Port模式,Gen3 x8 Avalon-MM DMA只支持Endpoint模式。
Avalon-MM DMA中,PC是主设备,FPGA是从设备,通过SG DMA的方式实现主机DMA访问FPGA。
Avalon-MM中,FPGA是主设备,PC是从设备,IP替用户实现了Avalon-MM到TLP包之间的转换。
Avalon-ST接口是最基本的PCIe硬核,输入输出数据为TLP包数据,需要用户自行解包和组包。
Avalon-ST接口IP由IP Catalog生成,Avalon-MM和Avalon-MM DMA由Platform Designer生成。
Xilinx的PCIe实现
在最新的Xilinx系列器件中,PCIe IP不再使用ST接口,而是改为了AXI接口。
PCIe IP的用户侧有4路AXI接口(不含中断):m_axis_cq、s_axis_cc、s_axis_rq、m_axis_rc。
这4路接口命名的后缀含义如下:
- cq:Completer Request
- cc:Completer Completion
- rq:Requester Request
- rc:Requester Completion
由于DMA控制器是在FPGA端,因此Xilinx将FPGA端视为请求端Requester,PC端视为完成端Completer。
接口的主从是从PCIe IP的角度来划分:
- cq:完成端发起请求,PC主FPGA从
- cc:向完成端发送完成包,FPGA主PC从
- rq:请求端发起请求,FPGA主PC从
- rc:向请求端发送完成包,PC主FPGA从
TLP协议
TLP概念
PCIe设备为分层结构,有事务层、数据链路层和物理层组成。在FPGA中如果使用PCIe硬核,则不需要关心数据链路层和物理层,这两层由IP负责处理,用户只需要处理TLP层。
TLP由三部分组成,包头、数据、摘要(ECRC)。TLP包头长3或4DW,只有携带数据的TLP才有数据,摘要由IP填充。因此,PCIe的处理在用户层表现为处理TLP包的包头和数据。
TLP包请求有5种:Memory、IO、Configuration、Message、Completion。
- Memory:用于访问存储空间,是目前最常见的TLP包。
- IO:用于访问IO空间,现在基本已经很少使用,可以忽略。
- Configuration:用于访问配置空间,由Host发起,通常只在上电枚举和配置阶段会发起Configuration访问,不是常态,可以忽略。
- Message:只有有中断或者有错误等情况下才会有Message TLP,属于非主流,可以忽略。
- Completion:完成包,对于non-posted的TLP包都需要返回Completion包。
TLP包根据是否需要返回完成信息,又可以分为posted和non-posted两类。MWr是posted类型,不需要返回完成包。MRd是non-posted类型,必须返回完成包。
MRd的读数据是通过带数据的完成包返回的。
Arria10的PCIe常用TLP包格式
64bit地址访存读请求包格式如下:
64bit地址访存写请求包格式如下:
带数据完成包格式如下:
TLP包字段和参数说明
Fmt和Type
Fmt和Type字段联合确定当前TLP的事务类型。
Fmt用于标识包头长度和是否携带数据,格式如下:
| Fmt[1:0] | 描述 |
|---|---|
| 00 | 包头长度3DW,不携带数据 |
| 01 | 包头长度4DW,不携带数据 |
| 10 | 包头长度3DW,携带数据 |
| 11 | 包头长度4DW,携带数据 |
其中,包头长度为3DW则地址为32bit,包头长度为4DW则地址为64bit。
常用的TLP类型的Fmt和Type如下:
| TLP类型 | Fmt[1:0] | Type[3:0] | 描述 |
|---|---|---|---|
| Memory读 | 00/01 | 0_0000 | 访存读请求,包头为3DW或4DW |
| Memory写 | 10/11 | 0_0000 | 访存写请求,包头为3DW或4DW |
| 带数据完成包 | 10 | 0_1010 | 带数据完成包,包头为3DW |
在Xilinx器件的IP中,Type的含义为其自定义的传输类型,TLP包中不含Fmt。
TC
TC字段表示当前TLP的传输类型,PCIe总线规定了8种传输类型TC0~TC7,一般只用默认值TC0,即TC=3’d0。
TD、EP、AT
TD位表示TLP种的TLP Digest是否有效,1为有效,0为无效。即是否使用ECRC。
EP(Poisoned Request)位表示TLP中的数据是否有效,1为无效,0为有效。
AT字段与PCIe总线的地址转换有关,只有在支持IOMMU技术的处理器系统中才使用此字段。
- RX方向:EP、TD、AT均不需要解析。
- TX方向:TD=0,EP=0,AT字段保留。
Attr
Attr为2bit,Xilinx中为3bit,但一般只用低2bit。字段说明如下:
| Attr[1:0] | 值 | 描述 |
|---|---|---|
| bit[1] | 0 | 默认值,PCI强序模型 |
| bit[1] | 1 | PCI-X Relaxed Ordering |
| bit[0] | 0 | 默认值,硬件强制执行Cache共享一致性处理 |
| bit[0] | 1 | TLP不存在处理器Cache一致性问题,不需要监听处理器Cache |
- RX方向:不需要解析。
- TX方向:通常为0或1,需要软件驱动开发人员决定是否需要处理Cache共享一致性。
Length
对于读请求,Length为从目标设备EP的数据区读取的数据长度。
对于写请求,Length为当前TLP包的数据长度。
Length为10bit,当Length=0时表示长度1024DW,当Length非0时DW长度为Length。
Requester ID、TAG、Completer ID
Requester ID字段(16bit)包含生成这个TLP报文的PCIe设备的Bus Number[7:0]、Device Number[4:0]、Function Number[2:0]。
TAG字段(8bit)用来标识由请求者发出的未完成的非转发事务的请求序号,以便相应的完成返回时带有与原请求者相同的序号,请求者将事务包与相应挂起的请求对号,解除对应的挂起请求。
Completer ID字段存放发送完成TLP的PCIe设备的ID号,即EP的设备ID。
Last BE、First BE
这两个字段均为4bit。
Last BE的每一位对应数据Payload最后一个DW的Byte使能。
First BE的每一位对应数据Payload第一个DW的Byte使能。
如果传输数据长度不超过1个DW,则Last DW为4’b0000,First DW的对应位置置1。
如果传输数据长度超过1个DW,则First BE字段和Last BE字段都至少要有1bit使能。
如果传输数据长度大于2DW,则First BE字段和Last BE字段不允许出现不连续的置1位。
如果传输数据小于等于2DW,则First BE字段和Last BE字段允许有不连续的置1位。
另外,PCIe总线支持一种特殊的读操作,即"Zero-Length"读请求。此时Length=1,而First BE字段和Last BE字段都为4’b0000,即所有字节都无效。
0长度读请求TLP对应的读完成TLP中不包含有效数据。
0长度读请求用于确保之前posted类型传输的数据到达最终目的地。主设备发起访存写请求后并不知道是否已写完成,有可能数据还在从设备的FIFO中,没有真正写到目的地址。于是主设备可以发起1次0长度读请求,目的设备在收到0长度读请求后确认数据是否已写入完毕,在写入完毕后才返回0长度读请求的完成包。
Byte Count
当前完成包中携带的数据字节数量。Byte Count根据当前完成包对应的First BE字段和Last BE字段确定。
为了方便设计,通常将数据按64B对齐,于是Memory读写操作的First BE字段和Last BE字段均为4’b1111,Byte Count即为当前TLP包中携带数据的Dword_Count×4。
Status、BCM
Status字段保存当前完成TLP的完成状态。
| Status[2:0] | 描述 |
|---|---|
| 000 | SC,正常结束 |
| 001 | UR,不支持的数据请求 |
| 010 | CRS,要求数据请求方进行重试 |
| 100 | CA,数据夭折 |
| 其他 | 保留 |
BCM字段由PCI-X设备设置,该字段处理方法为:保留,设为0。
Max_Payload_Size参数
PCIe总线TLP包中有效数据负载最大值为4KB,但是PCIe设备不一定能够发送最大值的数据报文。
PCIe设备含有"Max_Payload_Size"和"Max_Payload_SizeSupported"参数。这两个参数分别在Device Capability寄存器和Device Control寄存器中定义。
- Max_Payload_SizeSupported为PCIe设备支持的TLP有效负载的最大值,由硬件决定,系统软件不能修改此参数。
- Max_Payload_Size为PCIe设备进行数据传输时实际使用的TLP有效负载的最大值,由PCIe链路两端设备协商决定。即选用PCIe链路两端最小的Max_Payload_SizeSupported参数来初始化Max_Payload_Size。
在多数x86处理器系统中,Max_Payload_SizeSupported为128B,在PowerPC处理器系统中,此参数大多为256B。
因此,FPGA端的Max_Payload_Size实际取决于PC端的Max_Payload_SizeSupported。
信用与流控(FC)
基本概念
PCIe的流控缓冲区对posted、non-posted、cpl三类TLP包分别有存储空间进行缓存,其中每一类又分成包头和数据分别缓存。
因此,在流控缓冲区一共有6个缓冲buf:PH、PD、NPH、NPD、CPLH、CPLD。
FC计数器对6个buf大小计数,并发送给对端。
发送端在每次发送TLP包时必须保证接收端的相应buf空间足够。
对于不同类型的包头和数据,1个信用单元大小的定义是不同的:
- 完成包包头:4DW
- 请求包包头:5DW
- 数据:4DW
接收端将FC计数结果通过Flow Control update DLLPs发送给发送端。格式如下:
可见,包头FC为8bit,数据FC为12bit。
为了提升系统性能,在FPGA中用户层的信用计算与流控需要自行设计实现。
信用计算与流控
相关定义
信用的一些相关定义如下:
| 名称 | 位置 | 描述 |
|---|---|---|
| CL | 发送端 | 信用限额 |
| CC | 发送端 | 信用消耗,即已使用的信用 |
| PTLP | 发送端 | 待发送的TLP |
| CrAl | 接收端 | 已分配的信用 |
| CrRcv | 接收端 | 收到的信用 |
信用空间足够时的信用计算与流控
假设流控缓冲区大小为2KB,初始化后发送第一个请求包,计算请求包包头的信用方法如下:
- 计算流控单元数量:2KB/5DW=102,即CL=102,CrAl=102。
- 已使用的信用CC=0,待发送的信用PTLP=1。
- 发送端计算总的需要的信用:CC+PTLP=1。
- 发送端计算剩余信用:CL-(CC+PTLP)=101。
- 计算按照8bit无符号数进行计算,也就是对 2 8 2^8 28取模,然后与 2 8 2^8 28/2比较。即:101 ≤ \leq ≤ 128。
- 信用空间足够,可以发送此TLP。
- TLP发送至链接层时,CC+1。
- TLP到达接收端时,将包头存入流控缓冲区,将CrRcv+1。注意CrAl不变。
信用空间不足时的信用计算与流控
假设上述流控缓冲区已满,需要发送一个请求包,计算请求包包头的信用方法如下:
- 已使用的信用CC=102,待发送的信用PTLP=1。
- 发送端计算总的需要的信用:CC+PTLP=103。
- 发送端计算剩余信用:CL-(CC+PTLP)=-1。计算按照8bit无符号数进行计算,因此剩余信用计算结果为255。
- 255 ≥ \geq ≥ 128,信用空间不足,不可以发送此TLP。
- 等待接收端从流控缓冲区移出一个包头,移出后CrAl+1,CrAl=103。
- 接收端将CrAl发送给发送端作为新的CL,发送端CL=103。
- 发送端重新计算剩余信用,CL-(CC+PTLP)=0。
- 0 ≤ \leq ≤ 128,信用空间足够,可以发送此TLP。
CrAl溢出时的信用计算与流控
由于CrAl为8bit无符号数,递增到255后,下一次增加会变为0。假设CrAl=255,CC=248,接收端移出一个包头后,计算请求包包头的信用方法如下:
- 接收端移出一个包头后,CrAl=0。
- 接收端将CrAl发送给发送端作为新的CL,发送端CL=0。
- 发送端计算总的需要的信用:CC+PTLP=249。
- 发送端计算剩余信用:CL-(CC+PTLP)=-249。计算按照8bit无符号数进行计算,因此剩余信用计算结果为7。
- 7 ≤ \leq ≤ 128,信用空间足够,可以发送此TLP。