前段时间在公司项目中调试了PCIE,正好做一个总结,那些介绍XDMA、PCIE之类的多余的东西网上能搜到很多,我这里就不多说。我写的只是自己的一些想法,以及自己的设计思路。
同每一个刚开始调试PCIE的人一样,作为初学者大家都是先去网上搜集大量的资料学习,我也搜集了很多,看完之后其实还是有点泪崩,不知道怎么做。
在Vivado的IP Catalog中我们可以看到有三个可用于PCIE控制的IP核,其中第三个IP使用起来是最简单的,是将PCIE快速应用到项目中的最好的选择。
打开第三个IP核配置界面如下:
Mode: 配置模式, 选择 Advanced 高级配置
Lane Width:根据硬件选择支持 X1 、X2、X4
Max Link Speed: 选择 5.0GT/s 即 PCIE2.0
Reference Clock : 100MHZ, 参考时钟 100M
DMA Interface Option: 接口选择 AXI4 Stream或者AXI,因为我的项目中接口不是AXI4接口,所以我选择了AXI4 Stream
AXI Data Width: 128bit, 即 AXI4 数据总线宽度为 128bit
AXI Clock : 125M, 即 AXI4 接口时钟为 125MHZ(如果有125M和250M供选择的话,在带宽允许的情况下,建议选择125M的,可以避免综合实现后出现IP核内部时序不收敛的情况出现),我在项目调试的初期选用的250Mhz,整个工程搭好综合实现就出现了IP核内部时序问题,无赖只好降低时钟到125Mhz)
AXI-Lite Slave Interface:我们不勾选,因为XDMA的所有寄存器都由PS端配置,fpga用户逻辑这边不做任何的操作
PCIE ID配置里面的参数我们选择默认参数即可
PCIE BAR 配置,这个配置比较重要!!!
PCIE to AXI Lite Master Interface:不勾选 , 我们不使用这个接口来访问用户逻辑,如果有人看过《PCIE_xdma教程_LINUX.pdf》这个pdf文档的话,应该知道这个文档里讲的是使用这个接口来进行访问用户逻辑,而不使用DMA Bypass 接口,我刚开始也是参照这个文档使用这个接口,但是后面在使用的时候发现了问题,这个接口的数据输出是经过XDMA内部过滤的,也就是说这个接口出来的地址并不是所有的都能给用户使用,有部分地址是用来对XDMA的寄存器配置使用的,容易造成用户地址和配置地址混合,如果用户地址只有几个十几个还好,要是有几十上百个的话,这个接口并不实用。
PCIE to DMA Bypass Interface:勾选上,我们用这个接口来访问用户逻辑,映射空间选择 1M基本够用, 当然用户也可以根据实际需要来自定义大小。
PCIE to AXI Translation: 这个设置比较重要, 通常情况下, 主机侧 PCIE BAR 地址与用户逻辑侧地址是不一样的, 这个设置就是进行 BAR 地址到 AXI 地址的转换, 比如主机一侧 BAR地址为 0, IP 里面转换设置为 0x80000000, 则主机访问 BAR 地址 0 转换到 AXI LIte 总线地址就是 0x80000000
在Ubuntu中对Bypass配置的命令是:./xdma_bypass 地址 类型 数据
如果没有数据即为读操作,如果有数据则为写操作。地址根据类型来决定,为什么这么说呢,请看下面:
./xdma_bypass 0x0 w 0x12345678 (当类型为w时,表示是以word传输数据,32bit,地址只能是4的倍数)
./xdma_bypass 0x0 h 0x1234 (当类型为w时,表示是以半个word传输数据,16bit,地址只能是2的倍数)
./xdma_bypass 0x0 b 0x12 (当类型为w时,表示是以半个字节传输数据,8bit)
又因为数据是128bit,地址0~15,每个地址其实只有一个字节的数据:
./xdma_bypass 0 w 0x12345678,fpga用户收到的数据为:128'h00000000_00000000_00000000_12345678,地址为0
./xdma_bypass 4 w 0x23456789,fpga用户收到的数据为:128'h00000000_00000000_23456789_00000000,地址为4
./xdma_bypass 8 w 0x3456789a,fpga用户收到的数据为:128'h00000000_3456789a_00000000_00000000,地址为8
./xdma_bypass 12 w 0x456789ab,fpga用户收到的数据为:128'h456789ab_00000000_00000000_00000000,地址为12
./xdma_bypass 16 w 0x56789abc,fpga用户收到的数据为:128'h00000000_00000000_00000000_56789abc,地址为16
./xdma_bypass 3 b 0x56,fpga用户收到的数据为:128'h00000000_00000000_00000000_56000000,地址为3
./xdma_bypass 6 b 0x1256,fpga用户收到的数据为:128'h00000000_00000000_12560000_00000000,地址为6
上面这种数据传输方式可以自己去研究一下,我也是自己调试的时候看出来的现象。
PCIE 中断设置
User Interrupts: 用户中断, XDMA 提供 16 条中断线给用户逻辑, 这里面可以配置使用几条中断线。
Legacy Interrupt: XDMA 支持 Legacy 中断
选择 MSI 中断:注意: MSI 中断和 MSI-X 中断只能选择一个, 否则会报错, 如果选择了 MSI 中断, 则可以选择 Legacy 中断, 如果选择了 MSI-X 中断, 那么 MSI 必须取消选择, 同时 Legacy 也必须选择 None。 此 IP 对于 7 系列设置有这么个问题, 如果使用 Ultrascale 系列, 则可以全部选择
配置 DMA 相关内容
Number of DMA Read Channel(H2C) 和 Number of DMA Write Channel(C2H) 通道数:对于 PCIE2.0 来说最大只能选择 2, 也就是 XDMA 可以提供最多两个独立的写通道和两个独立的读通道, 独立的通道对于实际应用中有很大的作用, 在带宽允许的前提下, 一个 PCIE可以实现多种不同的传输功能, 并且互不影响。
Number of Request IDs for Read (Write) channel : 这个是每个通道设置允许最大的outstanding 数量, 按照默认即可
打开xdma_0的源文件树,可以看到其内部有一个xdma0_pcie2_ip,双击打开这个xdma0_pcie2_ip可以看到就是7 Series Integrated Block for PCI Express IP,也就是说XDMA其实是将7 Series Integrated Block for PCI Express IP进行封装而产生的。如果使用7 Series Integrated Block for PCI Express IP进行PCIE开发我们就得自己编写剩下得其它模块。
一般来讲对于一个自己不太了解的IP核生成后,刚开始可能不太会使用,我一般产用的方法就是生成一个Example Design,
从example的工程中我们可以看到顶层就例化了两个模块,一个XDMA IP核和一个xdma_app模块,xdma_app模块内部就是一个AXI4接口的RAM,用来存放DMA Bypass Interface接口用户寄存器的数据,通道0和通道1的数据h2c出来后直接回环到c2h,
我在第一次使用这个IP核的时候把IP核手册看完,都还是没有看明白h2c通道出来的数据流到底包含哪些信息,相信不止我一个人有这样的感觉,后来经过上板测试,才知道h2c通道出来的是只是纯数据流,所有的数据都是我们所需要的数据,往c2h通道灌输数据的时候也是直接灌数据就可以了,不需要组帧啥的。
// Ref clock buffer
IBUFDS_GTE2 refclk_ibuf (.O(sys_clk), .ODIV2(), .I(sys_clk_p), .CEB(1'b0), .IB(sys_clk_n));
// Reset buffer
IBUF sys_reset_n_ibuf (.O(sys_rst_n_c), .I(sys_rst_n));
// Core Top Level Wrapper
xdma_0 xdma_0_i
(
//---------------------------------------------------------------------------------------//
// PCI Express (pci_exp) Interface //
//---------------------------------------------------------------------------------------//
.sys_clk ( sys_clk ),
.sys_rst_n ( sys_rst_n_c ),
// Tx
.pci_exp_txn ( pci_exp_txn ),
.pci_exp_txp ( pci_exp_txp ),
// Rx
.pci_exp_rxn ( pci_exp_rxn ),
.pci_exp_rxp ( pci_exp_rxp ),
// CQ Bypass ports
.m_axib_awid (m_axib_awid),
.m_axib_awaddr (m_axib_awaddr),
.m_axib_awlen (m_axib_awlen),
.m_axib_awsize (m_axib_awsize),
.m_axib_awburst (m_axib_awburst),
.m_axib_awprot (m_axib_awprot),
.m_axib_awvalid (m_axib_awvalid),
.m_axib_awready (m_axib_awready),
.m_axib_awlock (m_axib_awlock),
.m_axib_awcache (m_axib_awcache),
.m_axib_wdata (m_axib_wdata),
.m_axib_wstrb (m_axib_wstrb),
.m_axib_wlast (m_axib_wlast),
.m_axib_wvalid (m_axib_wvalid),
.m_axib_wready (m_axib_wready),
.m_axib_bid (m_axib_bid),
.m_axib_bresp (m_axib_bresp),
.m_axib_bvalid (m_axib_bvalid),
.m_axib_bready (m_axib_bready),
.m_axib_arid (m_axib_arid),
.m_axib_araddr (m_axib_araddr),
.m_axib_arlen (m_axib_arlen),
.m_axib_arsize (m_axib_arsize),
.m_axib_arburst (m_axib_arburst),
.m_axib_arprot (m_axib_arprot),
.m_axib_arvalid (m_axib_arvalid),
.m_axib_arready (m_axib_arready),
.m_axib_arlock (m_axib_arlock),
.m_axib_arcache (m_axib_arcache),
.m_axib_rid (m_axib_rid),
.m_axib_rdata (m_axib_rdata),
.m_axib_rresp (m_axib_rresp),
.m_axib_rlast (m_axib_rlast),
.m_axib_rvalid (m_axib_rvalid),
.m_axib_rready (m_axib_rready),
// AXI streaming ports
.s_axis_c2h_tdata_0 (m_axis_h2c_tdata_0),
.s_axis_c2h_tlast_0 (m_axis_h2c_tlast_0),
.s_axis_c2h_tvalid_0 (m_axis_h2c_tvalid_0),
.s_axis_c2h_tready_0 (m_axis_h2c_tready_0),
.s_axis_c2h_tkeep_0 (m_axis_h2c_tkeep_0),
.s_axis_c2h_tdata_1 (m_axis_h2c_tdata_1),
.s_axis_c2h_tlast_1 (m_axis_h2c_tlast_1),
.s_axis_c2h_tvalid_1 (m_axis_h2c_tvalid_1),
.s_axis_c2h_tready_1 (m_axis_h2c_tready_1),
.s_axis_c2h_tkeep_1 (m_axis_h2c_tkeep_1),
.m_axis_h2c_tdata_0 (m_axis_h2c_tdata_0),
.m_axis_h2c_tlast_0 (m_axis_h2c_tlast_0),
.m_axis_h2c_tvalid_0 (m_axis_h2c_tvalid_0),
.m_axis_h2c_tready_0 (m_axis_h2c_tready_0),
.m_axis_h2c_tkeep_0 (m_axis_h2c_tkeep_0),
.m_axis_h2c_tdata_1 (m_axis_h2c_tdata_1),
.m_axis_h2c_tlast_1 (m_axis_h2c_tlast_1),
.m_axis_h2c_tvalid_1 (m_axis_h2c_tvalid_1),
.m_axis_h2c_tready_1 (m_axis_h2c_tready_1),
.m_axis_h2c_tkeep_1 (m_axis_h2c_tkeep_1),
.usr_irq_req (usr_irq_req),
.usr_irq_ack (usr_irq_ack),
.msi_enable (msi_enable),
.msi_vector_width (msi_vector_width),
//-- AXI Global
.axi_aclk ( user_clk ),
.axi_aresetn ( user_resetn ),
.user_lnk_up ( user_lnk_up )
);
example工程自带有仿真,我们可以使用Vivado自带的仿真工程仿真。
如果仔细分析仿真工程我们可以看到在pci_exp_usrapp_tx模块中用task封装了很多类型的PCIE数据包,有TSK_TX_TYPE0_CONFIGURATION_READ、TSK_TX_TYPE1_CONFIGURATION_READ、TSK_TX_TYPE0_CONFIGURATION_WRITE、TSK_TX_TYPE1_CONFIGURATION_WRITE、TSK_TX_MEMORY_READ_64、TSK_TX_MEMORY_WRITE_32。。。。。等等,基本上所有的PCIE数据包都在这里面,想了解的自己可以去细看,仿真的切入点是在sample_tests.vh文件中,在该文件中有很多testcase,在不同的testcase中调用相应的TSK发送相应的PCIE数据包,默认仿真testcase是dma_test0,如果想看懂仿真平台,这部分代码得去细看,这里我就不做详细描述了,,,,
看懂上面得仿真平台基本上也就知道在Ubuntu中是如何配置XDMA,关于寄存器的配置,等有时间再补充。。。。。
另外:
经过测试DMA目前最大只能传输8Mbytes数据,如果要传输大于8Mbytes数据则需要多次传输,
声明:所有文章属于个人在工作中所记下和搜集的笔记,不得转载