PCIe学习(二)——PCIe DMA关键模块分析之一

简介
经过一段时间的学习,这里将PCIe DMA模式的学习结果做一个总结,由于手里没有包含PCIe的板子,因此和学习PIO一样对DMA模式中的关键模块的代码进行逐条分析,希望对和我一样的初学者有所帮助。

软件:VIVADO2017.4。

第一步:PCIe DMA基础知识
在上一篇博客 PCIe学习(一)中已经对PCIe的部分基础知识进行了陈述,这里就不再赘述。DMA模式与PIO相比有很大优势,PIO数据传输直接由CPU执行,通常每次只能传输一个DW数据,在大量数据传输中会占用较多CPU资源,导致传输速度不足;然而对于大数据传输,DMA实现会带来更高的数据吞吐量,因为DMA硬件引擎不限于一个或两个DW传输。此外,DMA引擎通过直接传输数据来卸载CPU,从而通过较低的CPU利用率提高整个系统的性能。

DMA体系结构

Target logic负责捕获在接口上显示的单个DW内存写入和内存读取 TLPs。MWr和MRd TLPs通过程序输入/输出发送到端点,用于监视和控制DMA硬件。目标逻辑的功能是在MWr中更新状态和控制寄存器,并将所有传入的MRd的数据返回完成。所有传入的MWr包都是32位的,包含一个Dword(32位)有效负载。输入的MRd信息包每次只能请求1个Dword数据,从而完成单个Dword数据的完成。

Control and Status Registers包含DMA控制器的操作信息。值得注意的是,提供的BMD设计示例主要用于测量数据传输的性能,因此,它包含了典型设计中可能不需要的状态寄存器。如果需要,您可以选择删除它们及其相关的逻辑。

Initiator Logic的功能是根据是否选择上游或下游传输来生成内存写入或内存读取TLPs。总线主设计只支持一次生成一种类型的数据流。总线主启用位(PCI命令寄存器的位2)必须设置为启动TLP流量的上游。不允许任何事务跨越4K边界。

当从端点传输数据到系统内存时,启动器逻辑生成内存写入TLPs。DMA控件和状态寄存器指定要发送数据的地址、大小、有效负载内容和TLPs的数量。

FPGA写数据到PC步骤

1、Assert Initiator Reset:PC向BARO空间 DCR1地址写0x1值,表示对PCIe 初始化复位。
2、De-assert Initiator Reset:PC向BARO空间 DCR1地址写0x0值,表示清除对PCIe 初始化复位。
3、Write DMA H/W Address:PC先申请一块物理地址连续的缓冲区,然后PC向BARO空间 WDMATLPA写 DMA目的起始地址。
4、Write DMA TLP Size : PC向BARO空间 WDMATLPS写 TLP包大小。
5、Write DMA TLP Count : PC向BARO空间 WDMATLPC写 TLP包个数。
6、TLP Payload Pattern:PC向BARO空间 WDMATLPP写要发送的数据。
7、Write DMA Start:PC向BARO空间 DCR2写0x1值,表示开启DMA传输。
8、Wait for Interrupt TLP
9、Write DMA performance:PC读BARO空间 WDMAPERF,表示传输性能。

大概过程说明:首先电脑申请一片连续内存,接着电脑通过PIO模式配置FPGA上的控制状态寄存器(就是表格中的Step1~Step6),然后FPGA检测到写DMA请求后,发送引擎组装存储器写请求报文(包含要发送的数据)并发送,最后FPGA发送中断结束传输。

PC发数据到FPGA步骤

PC发数据到FPGA,步骤根据表格上step即可。
大概过程说明:首先电脑申请一片连续内存,接着电脑通过PIO模式配置FPGA上的控制状态寄存器(就是表格中的Step1~Step6),然后FPGA检测到读DMA请求后,发送引擎组装存储器读请求报文并发送,接收引擎接收电脑反馈的完成包,最后FPGA发送中断结束传输。

第二步:PCIe DMA关键模块分析
使用的是官方demo xapp1052,使用的是64bit数据位宽,PCIe DMA工程结构与PIO类似,同样关键模块有发送引擎、接收引擎和存储器模块。

BMD_64_RX_ENGINE.v
在DMA工程中,发送引擎、接收引擎以及存储器模块接口不再是AXI接口,而是BMD接口,但是这两者差不多的,其转换规则也比较简单,XILINX提供了转换的模块axi_trn_top.v ,其底层有两个子模块axi_trn_rx.v 和axi_trn_tx.v ,打开子模块后能看出转换的规则。

可以看出,转换是将一帧的高低两个DW交换位置。

在接收引擎中有4种标头

需要说明的是,BMD_MEM_RD32_FMT_TYPE 和BMD_MEM_WR32_FMT_TYPE 两种标头对应的TLP是以PIO的模式传输,其作用是上位机读写DMA控制状态寄存器,就是上文的DMA体系结构中的Control and Status Registers。而BMD_CPL_FMT_TYPE 和BMD_CPLD_FMT_TYPE 是FPGA发送DMA读请求后,PC端反馈的完成包,是DMA模式。

分析接收引擎的关键在于分析状态机的跳转以及在不同状态下执行的操作,首先是复位BMD_64_RX_RST 状态。

在BMD_64_RX_RST 状态下,首先判断三个信号trn_rsof_n 、trn_rsrc_rdy_n 和trn_rdst_rdy_n ,着三个信号的作用已在注释中进行了解释;条件成立之后开始判断接收包的类型,这里使用的是数据的56~62位,与PIO中24~31位不同,这是因为AXI转BMD时,数据的高低DW进行了交换。

1、假设接收到BMD_MEM_RD32_FMT_TYPE 存储器读请求包

和PIO一样,先将数据中的功能字段解析出来,然后跳转到下一状态。

到BMD_64_RX_MEM_RD32_QW1 状态下,首先判定trn_reof_n 、trn_rsrc_rdy_n 以及trn_rdst_rdy_n 信号;上文中分析过,BMD_MEM_RD32_FMT_TYPE 和BMD_MEM_WR32_FMT_TYPE 两种标头对应的TLP是以PIO的模式传输,所以只会传输一个DW数据,再加上3DW标头一共两帧数据,而在上一个状态已经传输了一帧,本状态传输最后一帧数据,所以需要判断trn_reof_n 帧结束信号,同样需要主、从设备都准备好,即trn_rsrc_rdy_n 和trn_rdst_rdy_n 同时有效才能传输。

同样,从数据中解析出需要读取的存储器地址;由于接收到存储器读请求,需要通过发送引擎返回完成包;接收引擎是接收PC的请求包,所以FPGA此时是从设备,然而此时完成包还没发送,因此将trn_rdst_rdy_n 置1,表明还没有准备好接收下一个包。跳转到下一状态。

这一状态是等待传输完成状态,和上一篇博客分析的一样,需要等待发送引擎发送完成包之后才能表示一次存储器读操作完成,状态回到复位状态。否则一直等待。
2、假设接收到BMD_MEM_WR32_FMT_TYPE 包,分析和上一篇博客中分析的存储器写操作一致,这里就不赘述。
3、假设接收到BMD_CPL_FMT_TYPE 包,这个标头是说明接收到一个不带数据的完成包,这个包的作用是配置一些简单的寄存器。


寄存器配置会在下文叙述。

4、假设接收到BMD_CPLD_FMT_TYPE 包,这是接收到一个带数据的完成包。

这是一个重要的包,其作用是接收从PC端经过DMA传输到FPGA中的数据,32~41表示这个包中数据的长度(单位DW),cpld_data_size_o 将所有的Length 累加起来,用于统计所有完成包一共返回了多少DW数据。

cpld_tlp_size 表示当前完成包中的数据的量(DW),这里只截取了Length 的低7位应该会出问题,因为上位机配置每个包的负载时128个DW,Length中的低7位为0,尽管如此,分析后发现这也不影响DMA传输。

cpld_found_o 用于统计完成包的数量。cpld_real_size 统计当前完成包中数据的量(DW),当前完成包传输完成后,该值与cpld_tlp_size 进行比较。跳转到下一状态。

该状态开始接收第一DW数据,所有状态名是QW1,之所以是接收第一DW数据,是因为完成包的标头是3DW,而现在分析的是64bit数据总线,所有接收的第0帧数据是2DW标头,第1帧是1DW标头+1DW数据。

bmd_64_rx_state_q 状态以及trn_rd_q 等后缀带_q信号,都是后面用于数据比较,使用非阻塞赋值,所以bmd_64_rx_state_q 会比bmd_64_rx_state 落后一个周期,这几个非阻塞复制此处先不用管。

接下来这里会判断trn_reof_n 信号是否有效,这是最后一帧标志,在这里检测这个信号是为了判断这个完成包是否只有两帧,包含3DW包头+1DW数据,如果这个条件成立,说明这个完成包只有1DW数据,所以cpld_real_size 只加1,同时cpld_tlp_size 必须为1’b1(只有1DW数据)且trn_rrem_n 必须有效,否则这个包就不对。

如果trn_reof_n 不成立,说明这个包不止两帧,所以跳转到下一状态,同时这一帧数据还是只包含一DW数据(1DW标头+1DW数据),所以cpld_real_size 还是只加1,然后状态跳转到BMD_64_RX_CPLD_QWN ,表示接收剩下的包。

还是暂时先不管那几个非阻塞赋值,首先判断trn_reof_n 信号判断是否是最后一帧,这个和上一个状态中的判断在意义上不一样,上一个状态是说明只有两帧数据,而这里的判断是已经接收了多帧数据后到最后一帧数据了。

如果这是最后一帧数据了,需要通过判断trn_rrem_n 信号判断这最后一帧数据是否都有效,其中该为0表示数据trn_td[63:0]有效,为1则表示数据trn_td[63:32]有效。所以如果trn_rrem_n 为1,说明这个帧只有一个DW有效,则cpld_real_size 只加1,否则就说明这一帧两个DW数据都有效,则cpld_real_size 加2。这一帧是最后一帧了,表明这个包已经接收了,所以同上文所说的一样需要比较一下cpld_tlp_size 和cpld_real_size 是否一样。因为这是非阻塞赋值,数据只有在下一个时钟到来时才能改变,所以在比较两个信号是否相等时cpld_real_size 需要加上1或者2。

如果这不是最后一帧数据,则状态就一直在BMD_64_RX_CPLD_QWN 状态中,同时这是64bit数据位宽,所以每一帧都两个DW数据,所以cpld_real_size 每次加2。

接着分析数据对比

接收引擎接收下来的数据并没有进行存储器,而是将接收到的数据与上位机设置数据进行对比,以验证传输的正确性;通过bmd_64_rx_state_q 确定当前状态,而这个信号就是上文中暂时没有分析的状态信号。

需要注意的是,bmd_64_rx_state_q 比bmd_64_rx_state 落后一个时钟周期,这是因为数据需要先接收下来再进行对比,而且也将接收到的数据进行了非阻塞赋值,在后续的比较中也是使用trn_rd_q 等信号,这样既进行了数据验证,又不阻碍原数据的接收。

1、假设进入BMD_64_RX_CPLD_QW1 状态

由上文分析可知,这种状态是只包含一个DW数据,而且数据在低32位(高32位是标头),所以只需要比较低32位数据是否等于cpld_data_i_sw (电脑设置的数据),不等就让cpld_data_err_o 为1,说明这一帧传输错误。

2、假设进入BMD_64_RX_CPLD_QWN 状态

此时如果是最后一帧数据,且trn_rrem_n_q 为1,则说明最后一帧数据只有高32位有效,所以比较高32位;若trn_rrem_n_q 为0,则表示最后一帧数据都有效,所以比较这两个DW是否都等于设定值,不等就返回错误。

此时如果不是最后一帧数据,由于一次传输2DW,那么就需要比较这两个DW是否与设定值相同。

至此,PCIe DMA的接收引擎的分析就结束了,下一次分析发送引擎。

结束
接收引擎是PCIe DMA传输的关键模块之一,总结来看,它的作用有:1、PIO模式接收电脑(上位机)发送的写存储器请求,从而配置控制寄存器;2、PIO模式接收电脑发送的读存储器请求,指示发送引擎发送完成包到电脑;3、接收以完成包的形式封装的电脑返回的DMA传输的数据。

通过上面的分析来看,官方提供的实例工程并没有将电脑发送的FPGA的数据存储下来,如果以后需要将数据存储下来的话,应该修改产生cpld_data_err_o 信号那里的代码。

本文转载自:CLGo的博客
*本文由作者授权转发,如需转载请联系作者本人

推荐阅读