PCIE_DMA实例四:xapp1052在Xilinx 7系列(KC705/VC709)FPGA上的移植
一:前言
这段时间有个朋友加微信请求帮忙调试一块PCIe采集卡。该采集卡使用xilinx xc7k410t做控制器,上位机为XP系统,原有的驱动和测试软件都是基于xapp1052写的。众所周知,Xilinx升级到7系列后,原来的pcie ip核trn接口统统转换成了axis接口,这可愁坏了之前用xapp1052的朋友,一下子不好用了,如何把xapp1052移植到K7系列FPGA上,貌似很有市场。博主搜了以下XILINX官网,发现V3.3版本的xapp1052已经能够在K7上使用了。但毕竟只是一个示例工程,数传接口并不友好。针对专门做采集卡的朋友,博主提供了一个由FIFO作为用户接口的BMD工程。
二:前期准备
1、pcie基础还是要有,尤其是协议部分。推荐一本电子书,很经典,请耐心读它(Addison.Wesley.PCI.Express.System.Architecture.eBook-LiB.chm)下载地址:http://download.csdn.net/download/yuzeren48/7723815
2、pg054
3、Vivado2018.2套件
4、Windriver
5、Visual studio 2010
三:移植步骤
1、在vivado中创建一个K7 pcie ipcore的example工程。
2、在xilinx官网下载xapp1052.pdf与xapp1052.zip
3、将1和2两个工程的代码融合并稍作修改,形成BMD工程,可选择64位宽(4x 2.5G )或128位宽(4x 5G),代码层级如下:
4,关键代码分析
1)、BMD_EP_MEM-Control/Status Registers 所有的用户状态寄存器在模块BMD_EP_MEM中。我们一切的DMA传输首先是要控制这 些寄存器,然后进行DMA传输,传输完成后在读取状态寄存器的值获取传输状态。
2)、EP_RX_ENGINE-Target 这个模块的功能在EP_RX_ENGINE里面实现,负责接收读写TLP命令,并且提交完成读 写内存的完成响应。EP_RX_ENGINE里面,Target接收PC发过来的32bit不带数据的存储 器读请求和带1个DW字的32bit 存储器写请求。控制状态寄存器就是通过Target读写。
3)、EP_RX_ENGINE-Rx引擎 Rx引擎除了负责接PC读写存储器请求,也要完成开发板发出的读内存请求的完成响应 (DMA传输)。
4)、BMD_EP_MEM-Tx引擎 Tx引擎除了负责接发送DMA数据到PC,也要发送PC发送的读写TLP包的完成响应。
5)、BMD_EP_MEM-Control/Status Registers 所有的用户状态寄存器在模块BMD_EP_MEM中。我们一切的DMA传输首先是要控制这 些寄存器,然后进行DMA传输,传输完成后在读取状态寄存器的值获取传输状态。
6)、Interface 总线接口,pcie-app_7x就是包含了BMD所有的总线接口。
7)、AXI4-Stream转BMD协议接口 axi_trn_rx模块和axi_trn_tx实现了最新的AXI4-Stream协议转BMD协议。有读者可能 会怀疑转换的协议可能影响传输效率,实际上不会有任何效率的牺牲,因为这里是FPGA直 接完成了协议的转换,没有任何的延迟。
笔者对关键逻辑部分代码做了非常详细的注释,读者可在文末根据需求自行购买。
5,上位机软件代码分析
装好windriver后,我们可以在windriver安装目录下找到BMD工程对应的驱动文件
使用VS2010打开后,文件目录如下,如需获取每个C文件中函数的功能定义,请在文末购买相应资料。
运行该测试程序,并打开VIVADO工程抓包,看上去是V5的测试代码,但可以通过输入VendorID和DeviceID来找到我们自己的板卡。
在4x GEN1(2.5G)的情况下,做连续读写测试,实测PCIe写带宽约为840MB/s,PCIe读带宽约为761MB/s,基本上接近满带宽了。
四、工程化范例
以上工程就是xapp1052在K7上的移植测试,但对于做工程应用的朋友来说,这个工程并不实用,所有DMA读写的数据都是根据我们用户自己配置的一个patten寄存器固定死的,如果要把FIFO中的数据通过xapp1052 DMA传输到系统内存,则需要修改部分源代码。这里,博主有偿为大家提供了一个FIFO接口的BMD工程。
用户接口如下:
module pcie_app_7x#( parameter C_DATA_WIDTH = 64, // RX/TX interface data width // Do not override parameters below this line parameter KEEP_WIDTH = C_DATA_WIDTH / 8 , // TKEEP width parameter REM_WIDTH = (C_DATA_WIDTH == 128) ? 2 : 1 // trem/rrem width )( input user_clk, input user_reset, input user_lnk_up, // Tx input [5:0] tx_buf_av, input tx_cfg_req, input tx_err_drop, output tx_cfg_gnt, input s_axis_tx_tready, output [C_DATA_WIDTH-1:0] s_axis_tx_tdata, output [KEEP_WIDTH-1:0] s_axis_tx_tkeep, output [3:0] s_axis_tx_tuser, output s_axis_tx_tlast, output s_axis_tx_tvalid, // Rx output rx_np_ok, output rx_np_req, input [C_DATA_WIDTH-1:0] m_axis_rx_tdata, input [KEEP_WIDTH-1:0] m_axis_rx_tkeep, input m_axis_rx_tlast, input m_axis_rx_tvalid, output m_axis_rx_tready, input [21:0] m_axis_rx_tuser, // Flow Control input [11:0] fc_cpld, input [7:0] fc_cplh, input [11:0] fc_npd, input [7:0] fc_nph, input [11:0] fc_pd, input [7:0] fc_ph, output [2:0] fc_sel, // CFG input [31:0] cfg_do, input cfg_rd_wr_done, output [31:0] cfg_di, output [3:0] cfg_byte_en, output [9:0] cfg_dwaddr, output cfg_wr_en, output cfg_rd_