[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA_PCIE通信(win)连载-03基于XDMA实现PCIE通信

文档创建者:FPGA课程
浏览次数:440
最后更新:2024-09-12
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-FPGA部分 » 2_FPGA实验篇(仅旗舰) » 6-FPGA PCIE通信(Win)
本帖最后由 FPGA课程 于 2024-9-12 09:27 编辑

​ 软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!



1概述
本方案基于XDMAIP搭建FPGA工程,并且以“01WIN系统PCIE驱动编译”中已经编译好的驱动和测试程序为演示demo。
本方案内容作为通用的教程内容,适合XILINX各类支持PCIE通信的板卡。并且米联客在XDMA中使用了自己编写的FDMA控制IP,可以简单方便的完成数据之间的交换。
2系统构架
本系统中演示的关键在于我们编写了一个uixdmairq的IP。该用来配合驱动处理中断,uixdmairq提供了AXI-LITE接口,上位机通过访问user空间地址读写uixdmairq的寄存器。该IP在user_irq_req_i输入的中断位,寄存中断位号,并且输出给XDMAIP,当上位机的驱动响应中断的时候,在中断里面写uixdmairq的寄存器,清除已经处理的中断。
另外本方案中通过AXI-BRAM来演示用户user空间的读写访问测试。
5db1d8c86bf54731a3561cad7be59e22.jpg
3XMDA概述
Xilinx提供的DMASubsystemforPCIExpressIP是一个高性能,可配置的适用于PCIE2.0,PCIE3.0的SG模式DMA,提供用户可选择的AXI4接口或者AXI4-Stream接口。一般情况下配置成AXI4接口可以加入到系统总线互联,适用于大数据量异步传输,通常情况都会使用到DDR,AXI4-Stream接口适用于低延迟数据流传输。
XDMA是SGDMA,并非BlockDMA,SG模式下,主机会把要传输的数据组成链表的形式,然后将链表首地址通过BAR传送给XDMA,XDMA会根据链表结构首地址依次完成链表所指定的传输任务。

AXI4、AXI4-Stream,必须选择一个,用于数据传输
AXI4-LiteMaster可选,用于实现PCIEBAR地址到AXI4-lite寄存器地址的映射,可以用于读写用户逻辑寄存器。
AXI4-LiteSlave可选,用来将XDMA内部寄存器开放给用户逻辑,用户逻辑可以通过此接口访问XDMA内部寄存器,不会映射到BAR。
AXI4Bypass接口,可选,用来实现PCIE直通用户逻辑访问,可用于低延迟数据传输。
4基于XDMA的PCIEFPGA工程搭建
4.1XDMAIP配置
1:添加XDMAIP核
59027b1f14f14c4eae7517bc3b5e5591.jpg
5665239c3dbe4a228a52b08b6b20dfc3.jpg
2:配置XDMA IP
双击XDMA IP进行配置
Mode:配置模式,选择 BASE配置
Lane Width:选择PCIE的通道数量对于MZ7035FC为8个通道,每个开发板支持通道数量不一样,通道数量越多通信速度越快,用户需要根据硬件的实际通达数量选择正确的通道数。
Max Link Speed:选择5.0GT/s 即PCIE2.0,对于ultrascale或者ultrascale+的FPGA可以支持PCIE3.0实现更高速度Reference Clock :100MHZ,参考时钟 100M
DMA Interface Option:接口选择 AXI4 接口
AXI Data Width:128bit,即 AXI4 数据总线宽度为128bit
AXI Clock :250M,即AXI4 接口时钟为250MHZ
DMA Interface option 设置为AXI Memory Mapped方式
9d848d396fcc4c3f93cbce74c28f3417.jpg
PCIE ID 配置,这里选择默认的配置就可以,默认的设备类型是Simple communication controllers
e0902335d1c24dd79f44ddd3269ddbbd.jpg
PCIE BAR 配置,这里面的配置比较重要,首先使能 PCIE to AXI Lite Master Interface ,这样可以在主机一侧通过PCIE 来访问用户逻辑侧寄存器或者其他 AXI4-Lite 总线设备映射空间选择 1M,当然用户也可以根据实际需要来自定义大小。
PCIE to AXI Translation:这个设置比较重要,通常情况下,主机侧PCIE BAR 地址与用户逻辑侧地址是不一样的, 这个设置就是进行BAR 地址到AXI 地址的转换,比如主机一侧 BAR 地址为0,IP 里面转换设置为0x44A00000, 则主机访问 BAR 地址 0 转换到AXI LIte 总线地址就是0x44A00000
PCIE to DMA Interface :选择64bit 使能
DMA Bypass 暂时不用
bc2fdca096fb472b94beee9cff38bc99.jpg
PCIE 中断设置
User Interrupts:用户中断,XDMA提供16条中断线给用户逻辑,这里面可以配置使用几条中断线。
Legacy Interrupt:XDMA支持Legacy中断,我们这么不选
MSI Capabilities:选择支持MSI中断 ,支持4个中断消息向量
注意:MSI 中断和 MSI-X 中断只能选择一个,否则会报错,如果选择了 MSI 中断,则可以选择 Legacy 中断, 如果选择了 MSI-X 中断,那么 MSI 必须取消选择,同时Legacy 也必须选择None。此 IP 对于7 系列设置有这个问题,如果使用Ultrascale 系列,则可以全部选择
MSI-X Capabilities:不选
Miscellaneous:选Extended Tag Field
Link Status Register:选Enable Slot Clock Configuration

配置DMA 相关内容
Number of DMA Read Channel(H2C)和Number of DMA Write Channel(C2H)通道数,对于PCIE2.0 来说最大只能选择2,也就是 XDMA 可以提供最多四个独立的写通道和四个独立的读通道,独立的通道对于实际应用中有很大的作用,在带宽允许的前提前,一个PCIE 可以实现多种不同的传输功能,并且互不影响。这里我们选择2 Number of Request IDs for Read (Write)channel :这个是每个通道设置允许最大的 outstanding 数量,按照默认即可
051d79f93c1a4d8dbb1934aa0692f156.jpg
4.2完成自动连线
配置完成以后,点击 Run Block Auto,可以看到之前的配置信息,如果有发现和目标配置不一样的,需要手动 修改,点击 OK,完成配置。
29fe27e2035146acae375fa0e6906206.jpg


配置完成后,VIVADO会自动进行必要的连线
7394b046abf34c6c81960f9d2aae1fb7.jpg
到此为止,XDMA IP 配置就完成了。为了让XDMA和上位机可以密切配和工作,我们还要继续搭建其他部分的功能模块。
4.3基于图形设计的XDMA工程
4.4添加中断测试代码
  1. `timescale 1ns / 1ps

  2. /*******************************MILIANKE*******************************
  3. *Company : MiLianKe Electronic Technology Co., Ltd.
  4. *WebSite:https://www.milianke.com
  5. *TechWeb:https://www.uisrc.com
  6. *tmall-shop:https://milianke.tmall.com
  7. *jd-shop:https://milianke.jd.com
  8. *taobao-shop1: https://milianke.taobao.com
  9. *Create Date: 2021/10/15
  10. *File Name: pcie_top.v
  11. *Description:
  12. *Declaration:
  13. *The reference demo provided by Milianke is only used for learning.
  14. *We cannot ensure that the demo itself is free of bugs, so users
  15. *should be responsible for the technical problems and consequences
  16. *caused by the use of their own products.
  17. *Copyright: Copyright (c) MiLianKe
  18. *All rights reserved.
  19. *Revision: 1.0
  20. *Signal description
  21. *1) _i input
  22. *2) _o output
  23. *3) _n activ low
  24. *4) _dg debug signal
  25. *5) _r delay or register
  26. *6) _s state mechine
  27. *********************************************************************/

  28. module pcie_top
  29. (
  30.   output [14:0]DDR3_addr,
  31.   output [2:0]DDR3_ba,
  32.   output DDR3_cas_n,
  33.   output [0:0]DDR3_ck_n,
  34.   output [0:0]DDR3_ck_p,
  35.   output [0:0]DDR3_cke,
  36.   output [0:0]DDR3_cs_n,
  37.   output [7:0]DDR3_dm,
  38.   inout [63:0]DDR3_dq,
  39.   inout [7:0]DDR3_dqs_n,
  40.   inout [7:0]DDR3_dqs_p,
  41.   output [0:0]DDR3_odt,
  42.   output DDR3_ras_n,
  43.   output DDR3_reset_n,
  44.   output DDR3_we_n,
  45.   input  sysclk_p,
  46.   input  sysclk_n,
  47.   
  48.   input  [7 :0]pcie_mgt_rxn,
  49.   input  [7 :0]pcie_mgt_rxp,
  50.   output [7 :0]pcie_mgt_txn,
  51.   output [7 :0]pcie_mgt_txp,
  52.   input  [0 :0]pcie_ref_clk_n,
  53.   input  [0 :0]pcie_ref_clk_p,
  54.   input pcie_rst_n
  55.     );

  56. wire sysclk;
  57. IBUFDS CLK_U(.I(sysclk_p),.IB(sysclk_n),.O(sysclk));

  58. wire axi_aclk;
  59. wire user_irq_en_o;
  60. reg  [21:0]timer_cnt;
  61. reg  timer_r1,timer_r2;
  62. reg [1:0]int_p;
  63. reg [3:0]user_irq_req_i;
  64. wire inter = !timer_r2 && timer_r1;

  65. always @(posedge axi_aclk)begin
  66.     if(user_irq_en_o == 1'b0)begin
  67.             timer_cnt <= 22'd0;
  68.     end
  69.     else begin
  70.             timer_cnt <= timer_cnt + 1'b1;
  71.     end
  72. end

  73. always @(posedge axi_aclk)begin
  74.     if(user_irq_en_o == 1'b0)begin
  75.             timer_r1 <= 1'd0;
  76.             timer_r2 <= 1'd0;
  77.     end
  78.     else begin
  79.             timer_r1 <= timer_cnt[20];
  80.             timer_r2 <= timer_r1;
  81.     end
  82. end

  83. always @(posedge axi_aclk)begin
  84.     if(user_irq_en_o == 1'b0)begin
  85.             int_p[1:0]  <= 4'd0;
  86.             user_irq_req_i <= 4'd0;
  87.     end
  88.     else begin
  89.         if(inter)  int_p <= int_p + 1'b1;
  90.         user_irq_req_i <= 4'd0;
  91.         user_irq_req_i[int_p] <= 1'b1;   
  92.     end
  93. end

  94. pcie_system pcie_system_i(
  95. .DDR3_addr(DDR3_addr),
  96. .DDR3_ba(DDR3_ba),
  97. .DDR3_cas_n(DDR3_cas_n),
  98. .DDR3_ck_n(DDR3_ck_n),
  99. .DDR3_ck_p(DDR3_ck_p),
  100. .DDR3_cke(DDR3_cke),
  101. .DDR3_cs_n(DDR3_cs_n),
  102. .DDR3_dm(DDR3_dm),
  103. .DDR3_dq(DDR3_dq),
  104. .DDR3_dqs_n(DDR3_dqs_n),
  105. .DDR3_dqs_p(DDR3_dqs_p),
  106. .DDR3_odt(DDR3_odt),
  107. .DDR3_ras_n(DDR3_ras_n),
  108. .DDR3_reset_n(DDR3_reset_n),
  109. .DDR3_we_n(DDR3_we_n),
  110.    
  111. .pcie_mgt_rxn(pcie_mgt_rxn),
  112. .pcie_mgt_rxp(pcie_mgt_rxp),
  113. .pcie_mgt_txn(pcie_mgt_txn),
  114. .pcie_mgt_txp(pcie_mgt_txp),
  115. .pcie_ref_clk_n(pcie_ref_clk_n),
  116. .pcie_ref_clk_p(pcie_ref_clk_p),
  117. .pcie_rst_n(pcie_rst_n),
  118. .axi_aclk(axi_aclk),
  119. .user_irq_en_o(user_irq_en_o),
  120. .user_irq_req_i(user_irq_req_i),
  121. .sysclk(sysclk)
  122. );
  123.         
  124.       
  125. endmodule
复制代码


4.5地址分配
进行地址分配:
这里我们把挂在M_AXI上的DDR地址分配从0开始(对于widnows系统必须为0)M_AXI是需要进行DMA操作的。而M_AXI_LITE挂载的BRAM和uixdmairq中断控制单元是映射到用户BAR地址空间,这个地址就是前面我们XDMA IP里面设置的地址。默认情况下,需要设置uixdmairq中断控制单元的地址和XDMA里面设置的用户BAR地址空间一致。如下图0x44A0_0000。BRAM的地址空间0x44A01_0000。关于地址空间的具体含义,结合软件的使用会更加清晰,初学者暂且根据教程设置。
3f30983f5d9d485997134d07f4b4e5c2.jpg
5硬件安装
注意确保TF卡里面没有程序吗,或者拔掉TF卡。
先下载程序,调试阶段下载bit文件,然后再开电脑。这样才能正确识别和后续测试工作正常开展。
ad54372420ab441db0e2ab100128e93c.jpg
6硬件识别
800c67ac28924e7d930e3521ddc6fb37.jpg
硬件识别失败自查方法:

7应用程序测试
7.1xdma_rw.exe功能介绍
我们打开一个终端(如果双击运行会很快退出来),进入到上一节编译生成的应用程序目录找到xdma_rw.exe,这个应用程序是操作pcie的所有设备的,我们在终端只输入xdma_rw.exe,可以看到提示信息,告诉用户这个程序如何使用。
0395e9914fb3495f9a020143c46e2c93.jpg
1:DEVNODE
control代表控制通道,用于控制XDMA的寄存器,由于精力原因,对于控制通道对XDMA寄存器的设置米联客没有深入研究。
event_*代表中断事件,其中*代表那中断号的中断事件
user代表用户空间,数据走AXI4-LITE接口
h2c_*代表hosttocardPC发送DMA数据到板卡,其中*代表那个通道,一般只用通道0,数据走AXI4-FULL通道
c2h_*代表cardtohost板卡发数据到PC,其中*代表那个通道,一般只用通道0,数据走AXI4-FULL通道
2:ADDR重点知识
读写地址偏移,对于DMA通道地址都是从0开始,但是对于PSDDR内存,必须偏移至少20MB开始读写PSDDR
对于user的读写,偏移地址是axi-lite接口的IP地址,减去在XDMAIP中配置的PCIEtoAXITranslation地址。对于米联客的XDMA方案由于修改了驱动中对于中断的响应,所以PCIEtoAXITranslation必须和默认的uixdmairq地址一致。之后再分区其他AXI-LITE接口外设。
71e02474d6114a0d9d9235d8e50785d1.jpg
3:OPTION
a设置内存对齐
b打开一个二进制文件
f读或者写文件
l数据长度
v更详细的输出
4:DATA
十进制或者十六进制数,必须用空格间隔
如:
17345168
0x110x220x330x44
7.2DMA批量数据测试
DMA传输是我们用的最多的一种,需要用到h2c或者ch2通道。
可以看到在当前目录有一个datafile4k.bin文件,那就测试一下将这个文件传输到FPGA的DDR或者(MA703FA-35T是BRAM),然后读出来。
在终端输入指令:
xdma_rw.exeh2c_0write0x0000000-b-fdatafile4K.bin-l4096
意思就是使用h2c_0设备以二进制的形式读取文件datafile4k.bin写入到BRAM内存地址0x0000000长度为4096字节。
ea36cba18d5e49bdaa8219190c801466.jpg
使用命令
xdma_rw.exec2h_0read0x0000000-b-fdatafile4K_recv.bin-l4096
9c86deb0e9514cec9eb58e54f9679608.jpg
接下来我们可以使用winhex等软件来检查一下两个文件数据是否一直,经过检查,是一致的则说明传输功能正常。
aecb75c4cdb7494286d47baeca9b82c1.jpg
7.3user通道测试
通过AXI-LITE接口写2个数据到挂在AXI-LITE接口的BRAM中
xdma_rw.exeuserwrite0x100000x110x22
通过AXI-LITE接口读2个数据到挂在AXI-LITE接口的BRAM中
xdma_rw.exeuserread0x10000-l2
7.4event中断测试
1:XDMA中断FPGA部分代码
首先我们要理解下XDMA的中断类型,以及控制时序:
1)、LegacyInterrupts:
对于LegacyInterrupts中断,当user_irq_ack第一次为1的时候usr_irq_req可以清0,当user_irq_ack第二次为1的时候,可以重新设置usr_irq_req发起中断。
在PCI总线里面INTx中断是由四条可选的中断线决定的,这种中断方式是共享式的,所有的pci设备把中断信号在一条中断线上相与,再上报给cpu,cpu收到中断以后再查询具体是哪个设备产生了中断。
在PCIE总线里面已经没有了实体的INTx物理中断线了,PCIE标准使用专门的Message事务包来实现INTx中断,这是为了兼容以前的PCI软件。INTx是共享式的,cpu相应中断后还需要查询具体中断源,效率比较低
5fe430977315485880fc534a23632e49.jpg
2)、MSIInterrupts:
MSI发出usr_irq_req中断请求后,user_irq_ack为1只是说明中断已经北主机接收了,但是不代表已经处理,软件或者驱动层可以去清零usr_irq_req。
MSI中断和MSI-X都是往配置的CPU中断寄存器里进行memory写操作,来产生中断,效率比INTx是共享式高,其中MSI最多支持32个中断向量,而MSI-X最多支持2048个中断向量。
81fc7f6acbda44b4a285d1e85d47c814.jpg
3)、MSI-XInterrupts:
当usr_irq_req中断请求后,只要user_irq_ack为1  就可以清零usr_irq_req,但是没说明扫码时候可以置1,重启下次中断。
55fee163594040388300c600c349530e.jpg
经过以上所有中断方式测试,发目前使用Legacy和MSI已经够用,而且相对稳定,上位机驱动通过访问用户bar地址空间和米联客编写的Uixdmairqip-core一起管理接收的中断。
84dde96fd2524f1a8b24c6f20d8570c7.jpg
Uixdmairq.v源码
  1. /*******************************MILIANKE*******************************
  2. *Company : MiLianKe Electronic Technology Co., Ltd.
  3. *WebSite:https://www.milianke.com
  4. *TechWeb:https://www.uisrc.com
  5. *tmall-shop:https://milianke.tmall.com
  6. *jd-shop:https://milianke.jd.com
  7. *taobao-shop1: https://milianke.taobao.com
  8. *Create Date : 2022/05/01
  9. *Module Name:uixdmairq
  10. *File Name:uixdmairq.v
  11. *Description :
  12. *The reference demo provided by Milianke is only used for learning.
  13. *We cannot ensure that the demo itself is free of bugs, so users
  14. *should be responsible for the technical problems and consequences
  15. *caused by the use of their own products.
  16. *Copyright: Copyright (c) MiLianKe
  17. *All rights reserved.
  18. *Revision : 1.0
  19. *Signal description
  20. *1) _i input
  21. *2) _o output
  22. *3) _n activ low
  23. *4) _dg debug signal
  24. *5) _r delay or register
  25. *6) _s state mechine
  26. *********************************************************************/
  27. `timescale 1ns / 1ps

  28. module uixdmairq #
  29. (
  30. parameter integer XMDA_REQ_NUM = 8
  31. )
  32. (
  33. // Users to add ports here
  34. input  wire [XMDA_REQ_NUM-1 :0] user_irq_req_i,
  35. //input  wire [XMDA_REQ_NUM-1 :0] xdma_irq_ack_i,
  36. output wire [XMDA_REQ_NUM-1 :0] xdma_irq_req_o,
  37. output wire user_irq_en_o,
  38. input wire  S_AXI_ACLK,
  39. input wire  S_AXI_ARESETN,
  40. input wire [3 : 0] S_AXI_AWADDR,
  41. input wire [2 : 0] S_AXI_AWPROT,
  42. input wire  S_AXI_AWVALID,
  43. output wire  S_AXI_AWREADY,
  44. input wire [31 : 0] S_AXI_WDATA,
  45. input wire [3  : 0] S_AXI_WSTRB,
  46. input wire  S_AXI_WVALID,
  47. output wire  S_AXI_WREADY,
  48. output wire [1 : 0] S_AXI_BRESP,
  49. output wire  S_AXI_BVALID,
  50. input wire  S_AXI_BREADY,
  51. input wire [3 : 0] S_AXI_ARADDR,
  52. input wire [2 : 0] S_AXI_ARPROT,
  53. input wire  S_AXI_ARVALID,
  54. output wire  S_AXI_ARREADY,
  55. output wire [31 : 0] S_AXI_RDATA,
  56. output wire [1  : 0] S_AXI_RRESP,
  57. output wire  S_AXI_RVALID,
  58. input wire  S_AXI_RREADY
  59. );



  60. reg  [XMDA_REQ_NUM -1 :0] user_irq_req;
  61. reg  [XMDA_REQ_NUM -1 :0] user_irq_req_r1;
  62. reg  [XMDA_REQ_NUM -1 :0] user_irq_req_r2;
  63. reg  [XMDA_REQ_NUM -1 :0] user_irq_req_r3;
  64. reg  [XMDA_REQ_NUM -1 :0] xdma_irq_ack_r1;
  65. reg  [XMDA_REQ_NUM -1 :0] xdma_irq_ack_r2;
  66. reg  [XMDA_REQ_NUM -1 :0] xdma_irq_ack_r3;
  67. //reg  [XMDA_REQ_NUM -1 :0] xdma_irq_ack;
  68. reg  [XMDA_REQ_NUM -1 :0] xdma_irq_req;
  69. // AXI4LITE signals
  70. reg [3:0]        axi_awaddr;
  71. reg     axi_awready;
  72. reg     axi_wready;
  73. reg [1 : 0]     axi_bresp;
  74. reg     axi_bvalid;
  75. reg [3:0]       axi_araddr;
  76. reg     axi_arready;
  77. reg [31 : 0]    axi_rdata;
  78. reg [1  : 0]    axi_rresp;
  79. reg     axi_rvalid;

  80. // Example-specific design signals
  81. // local parameter for addressing 32 bit / 64 bit C S AXI_DATA_WIDTH
  82. // ADDR_LSB is used for addressing 32/64 bit registers/memories
  83. // ADDR_LSB = 2 for 32 bits (n downto 2)
  84. // ADDR_LSB = 3 for 64 bits (n downto 3)
  85. localparam integer ADDR_LSB = 2;
  86. localparam integer OPT_MEM_ADDR_BITS = 1;
  87. //----------------------------------------------
  88. //-- Signals for user logic register space example
  89. //------------------------------------------------
  90. //-- Number of Slave Registers 4
  91. reg [31:0]  slv_reg0;
  92. reg [31:0]  slv_reg1;
  93. wire     slv_reg_rden;
  94. wire     slv_reg_wren;
  95. reg [31:0]   reg_data_out;
  96. integer  byte_index;
  97. reg  aw_en;

  98. // I/O Connections assignments

  99. assign S_AXI_AWREADY    = axi_awready;
  100. assign S_AXI_WREADY = axi_wready;
  101. assign S_AXI_BRESP  = axi_bresp;
  102. assign S_AXI_BVALID = axi_bvalid;
  103. assign S_AXI_ARREADY = axi_arready;
  104. assign S_AXI_RDATA  = axi_rdata;
  105. assign S_AXI_RRESP  = axi_rresp;
  106. assign S_AXI_RVALID = axi_rvalid;
  107. // Implement axi_awready generation
  108. // axi_awready is asserted for one S_AXI_ACLK clock cycle when both
  109. // S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_awready is
  110. // de-asserted when reset is low.
  111. always @( posedge S_AXI_ACLK )
  112. begin
  113. if ( S_AXI_ARESETN == 1 'b0 )
  114. begin
  115. axi_awready <= 1 'b0;
  116. aw_en <= 1 'b1;
  117. end
  118. else
  119. begin
  120. if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)
  121. begin
  122. // slave is ready to accept write address when
  123. // there is a valid write address and write data
  124. // on the write address and data bus. This design
  125. // expects no outstanding transactions.
  126. axi_awready <= 1 'b1;
  127. aw_en <= 1 'b0;
  128. end
  129. else if (S_AXI_BREADY && axi_bvalid)
  130. begin
  131. aw_en <= 1 'b1;
  132. axi_awready <= 1 'b0;
  133. end
  134. else
  135. begin
  136. axi_awready <= 1 'b0;
  137. end
  138. end
  139. end

  140. // Implement axi_awaddr latching
  141. // This process is used to latch the address when both
  142. // S_AXI_AWVALID and S_AXI_WVALID are valid.

  143. always @( posedge S_AXI_ACLK )
  144. begin
  145. if ( S_AXI_ARESETN == 1 'b0 )
  146. begin
  147. axi_awaddr <= 0;
  148. end
  149. else
  150. begin
  151. if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)
  152. begin
  153. // Write Address latching
  154. axi_awaddr <= S_AXI_AWADDR;
  155. end
  156. end
  157. end

  158. // Implement axi_wready generation
  159. // axi_wready is asserted for one S_AXI_ACLK clock cycle when both
  160. // S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_wready is
  161. // de-asserted when reset is low.

  162. always @( posedge S_AXI_ACLK )
  163. begin
  164. if ( S_AXI_ARESETN == 1 'b0 )
  165. begin
  166. axi_wready <= 1 'b0;
  167. end
  168. else
  169. begin
  170. if (~axi_wready && S_AXI_WVALID && S_AXI_AWVALID && aw_en )
  171. begin
  172. // slave is ready to accept write data when
  173. // there is a valid write address and write data
  174. // on the write address and data bus. This design
  175. // expects no outstanding transactions.
  176. axi_wready <= 1 'b1;
  177. end
  178. else
  179. begin
  180. axi_wready <= 1 'b0;
  181. end
  182. end
  183. end

  184. assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;

  185. always @( posedge S_AXI_ACLK )
  186. begin
  187. if ( S_AXI_ARESETN == 1 'b0 )begin
  188. slv_reg0 <= 0;
  189. end
  190. else if(slv_reg_wren)begin
  191. if (axi_awaddr[3:2] == 2 'd0)
  192. slv_reg0[31:0] <= S_AXI_WDATA[31:0];
  193. else if (axi_awaddr[3:2] == 2 'd1)
  194. slv_reg1[31:0] <= S_AXI_WDATA[31:0];
  195. end else begin
  196. slv_reg0 <= 0;
  197. slv_reg1 <= slv_reg1;
  198. end
  199. end

  200. // Implement write response logic generation
  201. // The write response and response valid signals are asserted by the slave
  202. // when axi_wready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted.
  203. // This marks the acceptance of address and indicates the status of
  204. // write transaction.

  205. always @( posedge S_AXI_ACLK )
  206. begin
  207. if ( S_AXI_ARESETN == 1 'b0 )
  208. begin
  209. axi_bvalid<=0;
  210. axi_bresp<=2'b0;
  211. end
  212. else
  213. begin
  214. if (axi_awready && S_AXI_AWVALID && ~axi_bvalid && axi_wready && S_AXI_WVALID)
  215. begin
  216. // indicates a valid write response is available
  217. axi_bvalid <= 1 'b1;
  218. axi_bresp  <= 2 'b0; // 'OKAY ' response
  219. end                   // work error responses in future
  220. else
  221. begin
  222. if (S_AXI_BREADY && axi_bvalid)
  223. //check if bready is asserted while bvalid is high)
  224. //(there is a possibility that bready is always asserted high)
  225. begin
  226. axi_bvalid <= 1 'b0;
  227. end
  228. end
  229. end
  230. end

  231. // Implement axi_arready generation
  232. // axi_arready is asserted for one S_AXI_ACLK clock cycle when
  233. // S_AXI_ARVALID is asserted. axi_awready is
  234. // de-asserted when reset (active low) is asserted.
  235. // The read address is also latched when S_AXI_ARVALID is
  236. // asserted. axi_araddr is reset to zero on reset assertion.

  237. always @( posedge S_AXI_ACLK )
  238. begin
  239. if ( S_AXI_ARESETN == 1 'b0 )
  240. begin
  241. axi_arready <= 1 'b0;
  242. axi_araddr  <= 32 'b0;
  243. end
  244. else
  245. begin
  246. if (~axi_arready && S_AXI_ARVALID)
  247. begin
  248. // indicates that the slave has acceped the valid read address
  249. axi_arready <= 1 'b1;
  250. // Read address latching
  251. axi_araddr  <= S_AXI_ARADDR;
  252. end
  253. else
  254. begin
  255. axi_arready <= 1 'b0;
  256. end
  257. end
  258. end

  259. // Implement axi_arvalid generation
  260. // axi_rvalid is asserted for one S_AXI_ACLK clock cycle when both
  261. // S_AXI_ARVALID and axi_arready are asserted. The slave registers
  262. // data are available on the axi_rdata bus at this instance. The
  263. // assertion of axi_rvalid marks the validity of read data on the
  264. // bus and axi_rresp indicates the status of read transaction.axi_rvalid
  265. // is deasserted on reset (active low). axi_rresp and axi_rdata are
  266. // cleared to zero on reset (active low).
  267. always @( posedge S_AXI_ACLK )
  268. begin
  269. if ( S_AXI_ARESETN == 1 'b0 )
  270. begin
  271. axi_rvalid <= 0;
  272. axi_rresp  <= 0;
  273. end
  274. else
  275. begin
  276. if (axi_arready && S_AXI_ARVALID && ~axi_rvalid)
  277. begin
  278. // Valid read data is available at the read data bus
  279. axi_rvalid <= 1 'b1;
  280. axi_rresp  <= 2 'b0; // 'OKAY ' response
  281. end
  282. else if (axi_rvalid && S_AXI_RREADY)
  283. begin
  284. // Read data is accepted by the master
  285. axi_rvalid <= 1 'b0;
  286. end
  287. end
  288. end

  289. // Implement memory mapped register select and read logic generation
  290. // Slave register read enable is asserted when valid address is available
  291. // and the slave is ready to accept the read address.
  292. assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
  293. always @(*)
  294. if(axi_awaddr[3:2] == 2 'd0)
  295. reg_data_out[31 : 0] <= slv_reg0;
  296. else if(axi_awaddr[3:2] == 2 'd1)
  297. reg_data_out[31 : 0] <= slv_reg1;
  298. else
  299. reg_data_out <= reg_data_out;

  300. // Output register or memory read data
  301. always @( posedge S_AXI_ACLK )
  302. begin
  303. if ( S_AXI_ARESETN == 1 'b0 )
  304. begin
  305. axi_rdata  <= 0;
  306. end
  307. else
  308. begin
  309. // When there is a valid read address (S_AXI_ARVALID) with
  310. // acceptance of read address by the slave (axi_arready),
  311. // output the read dada
  312. if (slv_reg_rden) begin
  313. axi_rdata <= reg_data_out;     // register read data
  314. end
  315. end
  316. end

  317. // Add user logic here

  318. reg  [4 :0] i;
  319. reg  [4 :0] j;
  320. reg  [4 :0] k;
  321. assign xdma_irq_req_o = xdma_irq_req;

  322. assign user_irq_en_o = slv_reg1[31];

  323. wire [XMDA_REQ_NUM-1:0] xdma_irq_ack = slv_reg0[XMDA_REQ_NUM-1:0];

  324. always @( posedge S_AXI_ACLK ) begin
  325. if ( S_AXI_ARESETN == 1 'b0 || user_irq_en_o == 1 'b0 )begin
  326. user_irq_req_r1 = 0;
  327. user_irq_req_r2 = 0;
  328. user_irq_req_r3 = 0;
  329. end
  330. else begin
  331. user_irq_req_r1 <= user_irq_req_i;
  332. user_irq_req_r2 <= user_irq_req_r1;
  333. user_irq_req_r3 <= user_irq_req_r2;
  334. end
  335. end
  336. always @( posedge S_AXI_ACLK ) begin
  337. if ( S_AXI_ARESETN == 1 'b0 || user_irq_en_o == 1 'b0)begin
  338. j <= 5 'd0;
  339. user_irq_req <= 0;
  340. end
  341. else begin
  342. for(j = 0; j <=XMDA_REQ_NUM-1; j = j +1 )
  343. user_irq_req[j] <= !user_irq_req_r3[j] & user_irq_req_r2[j];
  344. end
  345. end

  346. always @( posedge S_AXI_ACLK ) begin
  347. if ( S_AXI_ARESETN == 1 'b0 || user_irq_en_o == 1 'b0)begin
  348. i <= 5 'd0;
  349. xdma_irq_req <= 0;
  350. end
  351. else begin
  352. for(i = 0; i <=XMDA_REQ_NUM-1; i = i +1 )begin
  353. if(xdma_irq_ack[i]) begin
  354. xdma_irq_req[i] <= 1 'b0;
  355. end
  356. else if(user_irq_req[i])begin
  357. xdma_irq_req[i] <= 1 'b1;
  358. end
  359. end
  360. end
  361. end
  362. // User logic ends

  363. endmodule
复制代码
  1. wireaxi_aclk;
  2. wireaxi_aresetn;
  3. wireuser_irq_en_o;
  4. reg [21:0]timer_cnt;

  5. always@(posedgeaxi_aclk)begin
  6.   if(!axi_aresetn||!user_irq_en_o)begin
  7.       timer_cnt<=22'd0;
  8.   end
  9.   elsebegin
  10.       timer_cnt<=timer_cnt+1'b1;
  11.   end
  12. end

  13. regtimer_r1,timer_r2;
  14. wireinter=!timer_r2&&timer_r1;

  15. always@(posedgeaxi_aclk)begin
  16.   if(!axi_aresetn||!user_irq_en_o)begin
  17.       timer_r1<=1'd0;
  18.       timer_r2<=1'd0;
  19.   end
  20.   elsebegin
  21.       timer_r1<=timer_cnt[20];
  22.       timer_r2<=timer_r1;
  23.   end
  24. end

  25. reg[1:0]int_p;
  26. reg[3:0]user_irq_req_i;

  27. always@(posedgeaxi_aclk)begin
  28.   if(!axi_aresetn||!user_irq_en_o)begin
  29.       int_p[1:0] <=4'd0;
  30.       user_irq_req_i<=4'd0;
  31.   end
  32.   elsebegin
  33.     if(inter) int_p<=int_p+1'b1;
  34.     user_irq_req_i<=4'd0;
  35.     user_irq_req_i[int_p]<=1'b1;
  36.   end
  37. end
复制代码

2:XDMA中断上位机部分代码
实现中断程序的源码intr_event.c:
  1. #include<Windows.h>
  2. #include<assert.h>
  3. #include<stdlib.h>
  4. #include<stdio.h>
  5. #include<strsafe.h>
  6. #include<stdint.h>
  7. #include<SetupAPI.h>
  8. #include<INITGUID.H>
  9. #include<WinIoCtl.h>
  10. //#include<AtlBase.h>
  11. #include<io.h>
  12. #include"xdma_public.h"

  13. #pragmacomment(lib,"setupapi.lib")
  14. #pragmawarning(disable:4996)
  15. BYTE start_en;
  16. HANDLEh_user;
  17. DWORD user_irq_ack[1];
  18. char base_path[MAX_PATH+1]="";

  19. #defineMAX_BYTES_PER_TRANSFER0x800000

  20. staticintverbose_msg(constchar*constfmt,...){

  21.   intret=0;
  22.   va_listargs;
  23.   if(1){
  24.     va_start(args,fmt);
  25.     ret=vprintf(fmt,args);
  26.     va_end(args);
  27.   }
  28.   returnret;

  29. }
  30. staticBYTE*allocate_buffer(size_tsize,size_talignment){

  31.   if(size==0){
  32.     size=4;
  33.   }

  34.   if(alignment==0){
  35.     SYSTEM_INFOsys_info;
  36.     GetSystemInfo(&sys_info);
  37.     alignment=sys_info.dwPageSize;
  38.     //printf("alignment=%d\n",alignment);
  39.   }
  40.   verbose_msg("Allocatinghost-sidebufferofsize%d,alignedto%dbytes\n",size,alignment);
  41.   return(BYTE*)_aligned_malloc(size,alignment);

  42. }

  43. staticintget_devices(GUIDguid,char*devpath,size_tlen_devpath){

  44.   SP_DEVICE_INTERFACE_DATAdevice_interface;
  45.   PSP_DEVICE_INTERFACE_DETAIL_DATAdev_detail;
  46.   DWORDindex;
  47.   HDEVINFOdevice_info;
  48.   wchar_ttmp[256];
  49.   device_info=SetupDiGetClassDevs((LPGUID)&guid,NULL,NULL,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
  50.   if(device_info==INVALID_HANDLE_VALUE){
  51.     fprintf(stderr,"GetDevicesINVALID_HANDLE_VALUE\n");
  52.     exit(-1);
  53.   }

  54.   device_interface.cbSize=sizeof(SP_DEVICE_INTERFACE_DATA);
  55.   //enumeratethroughdevices

  56.   for(index=0;SetupDiEnumDeviceInterfaces(device_info,NULL,&guid,index,&device_interface);++index){

  57.     //getrequiredbuffersize
  58.     ULONGdetailLength=0;
  59.     if(!SetupDiGetDeviceInterfaceDetail(device_info,&device_interface,NULL,0,&detailLength,NULL)&&GetLastError()!=ERROR_INSUFFICIENT_BUFFER){
  60.       fprintf(stderr,"SetupDiGetDeviceInterfaceDetail-getlengthfailed\n");
  61.       break;
  62.     }

  63.     //allocatespacefordeviceinterfacedetail
  64.     dev_detail=(PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,detailLength);
  65.     if(!dev_detail){
  66.       fprintf(stderr,"HeapAllocfailed\n");
  67.       break;
  68.     }
  69.     dev_detail->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

  70.     //getdeviceinterfacedetail
  71.     if(!SetupDiGetDeviceInterfaceDetail(device_info,&device_interface,dev_detail,detailLength,NULL,NULL)){
  72.       fprintf(stderr,"SetupDiGetDeviceInterfaceDetail-getdetailfailed\n");
  73.       HeapFree(GetProcessHeap(),0,dev_detail);
  74.       break;
  75.     }

  76.     StringCchCopy(tmp,len_devpath,dev_detail->DevicePath);
  77.     wcstombs(devpath,tmp,256);
  78.     HeapFree(GetProcessHeap(),0,dev_detail);
  79.   }

  80.   SetupDiDestroyDeviceInfoList(device_info);

  81.   returnindex;
  82. }

  83. HANDLEopen_devices(char*device_base_path,char*device_name,DWORDaccessFlags)
  84. {
  85.   chardevice_path[MAX_PATH+1]="";
  86.   wchar_tdevice_path_w[MAX_PATH+1];
  87.   HANDLEh;

  88.   //extenddevicepathtoincludetargetdevicenode(xdma_control,xdma_useretc)
  89.   verbose_msg("Devicebasepath:%s\n",device_base_path);
  90.   strcpy_s(device_path,sizeofdevice_path,device_base_path);
  91.   strcat_s(device_path,sizeofdevice_path,device_name);
  92.   verbose_msg("Devicenode:%s\n",device_name);
  93.   //opendevicefile
  94.   mbstowcs(device_path_w,device_path,sizeof(device_path));
  95.   h=CreateFile(device_path_w,accessFlags,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
  96.   if(h==INVALID_HANDLE_VALUE)
  97.   {
  98.     fprintf(stderr,"Erroropeningdevice,win32errorcode:%ld\n",GetLastError());
  99.   }

  100.   returnh;
  101. }

  102. staticintread_device(HANDLEdevice,longaddress,DWORDsize,BYTE*buffer)
  103. {
  104.   DWORDrd_size=0;

  105.   unsignedinttransfers;
  106.   unsignedinti;
  107.   if(INVALID_SET_FILE_POINTER==SetFilePointer(device,address,NULL,FILE_BEGIN)){
  108.     fprintf(stderr,"Errorsettingfilepointer,win32errorcode:%ld\n",GetLastError());
  109.     return-3;
  110.   }
  111.   transfers=(unsignedint)(size/MAX_BYTES_PER_TRANSFER);
  112.   for(i=0;i<transfers;i++)
  113.   {
  114.     if(!ReadFile(device,(void*)(buffer+i*MAX_BYTES_PER_TRANSFER),(DWORD)MAX_BYTES_PER_TRANSFER,&rd_size,NULL))
  115.     {
  116.       return-1;
  117.     }
  118.     if(rd_size!=MAX_BYTES_PER_TRANSFER)
  119.     {
  120.       return-2;
  121.     }
  122.   }
  123.   if(!ReadFile(device,(void*)(buffer+i*MAX_BYTES_PER_TRANSFER),(DWORD)(size-i*MAX_BYTES_PER_TRANSFER),&rd_size,NULL))
  124.   {
  125.     return-1;
  126.   }
  127.   if(rd_size!=(size-i*MAX_BYTES_PER_TRANSFER))
  128.   {
  129.     return-2;
  130.   }

  131.   returnsize;
  132. }

  133. staticintwrite_device(HANDLEdevice,longaddress,DWORDsize,BYTE*buffer)
  134. {
  135.   DWORDwr_size=0;
  136.   unsignedinttransfers;
  137.   unsignedinti;
  138.   transfers=(unsignedint)(size/MAX_BYTES_PER_TRANSFER);
  139.   //printf("transfers=%d\n",transfers);
  140.   if(INVALID_SET_FILE_POINTER==SetFilePointer(device,address,NULL,FILE_BEGIN)){
  141.     fprintf(stderr,"Errorsettingfilepointer,win32errorcode:%ld\n",GetLastError());
  142.     return-3;
  143.   }
  144.   for(i=0;i<transfers;i++)
  145.   {
  146.     if(!WriteFile(device,(void*)(buffer+i*MAX_BYTES_PER_TRANSFER),MAX_BYTES_PER_TRANSFER,&wr_size,NULL))
  147.     {
  148.       return-1;
  149.     }
  150.     if(wr_size!=MAX_BYTES_PER_TRANSFER)
  151.     {
  152.       return-2;
  153.     }
  154.   }
  155.   if(!WriteFile(device,(void*)(buffer+i*MAX_BYTES_PER_TRANSFER),(DWORD)(size-i*MAX_BYTES_PER_TRANSFER),&wr_size,NULL))
  156.   {
  157.     return-1;
  158.   }
  159.   if(wr_size!=(size-i*MAX_BYTES_PER_TRANSFER))
  160.   {
  161.     return-2;
  162.   }
  163.   returnsize;
  164. }

  165. DWORDWINAPIthread_event0(LPVOIDlpParam)
  166. {
  167.   BYTEval0[1]="";
  168.   DWORDi=0;
  169.   BYTEstatu;
  170.   char*device_name1="\\event_0";
  171.   HANDLEh_event0=open_devices(base_path,device_name1,GENERIC_READ);
  172.     while(1)
  173.     {
  174.       if(start_en){
  175.         read_device(h_event0,0,1,val0);//waiteirq
  176.         Sleep(1);
  177.         if(val0[0]==1)
  178.           printf("event_0done!\n");
  179.         else
  180.           printf("event_0timeout!\n");
  181.         i++;

  182.       }
  183.   }
  184.   CloseHandle(h_event0);
  185.   return0;
  186. }

  187. DWORDWINAPIthread_event1(LPVOIDlpParam)
  188. {
  189.   BYTEval0[1]="";
  190.   DWORDi=0;
  191.   BYTEstatu;
  192.   char*device_name1="\\event_1";
  193.   HANDLEh_event1=open_devices(base_path,device_name1,GENERIC_READ);
  194.   while(1)
  195.   {
  196.     if(start_en){
  197.       read_device(h_event1,0,1,val0);//waiteirq
  198.       Sleep(1);
  199.       if(val0[0]==1)
  200.         printf("event_1done!\n");
  201.       else
  202.         printf("event_1timeout!\n");
  203.       i++;

  204.     }
  205.   }
  206.   CloseHandle(h_event1);
  207.   return0;
  208. }

  209. DWORDWINAPIthread_event2(LPVOIDlpParam)
  210. {
  211.   BYTEval0[1]="";
  212.   DWORDi=0;
  213.   BYTEstatu;
  214.   char*device_name1="\\event_2";
  215.   HANDLEh_event2=open_devices(base_path,device_name1,GENERIC_READ);
  216.   while(1)
  217.   {
  218.     if(start_en){
  219.       read_device(h_event2,0,1,val0);//waiteirq
  220.       Sleep(1);
  221.       if(val0[0]==1)
  222.         printf("event_2done!\n");
  223.       else
  224.         printf("event_2timeout!\n");
  225.       i++;

  226.     }
  227.   }
  228.   CloseHandle(h_event2);
  229.   return0;
  230. }

  231. DWORDWINAPIthread_event3(LPVOIDlpParam)
  232. {
  233.   BYTEval0[1]="";
  234.   DWORDi=0;
  235.   BYTEstatu;
  236.   char*device_name1="\\event_3";
  237.   HANDLEh_event3=open_devices(base_path,device_name1,GENERIC_READ);
  238.   while(1)
  239.   {
  240.     if(start_en){
  241.       read_device(h_event3,0,1,val0);//waiteirq
  242.       Sleep(1);
  243.       if(val0[0]==1)
  244.         printf("event_3done!\n");
  245.       else
  246.         printf("event_3timeout!\n");
  247.       i++;

  248.     }
  249.   }
  250.   CloseHandle(h_event3);
  251.   return0;
  252. }

  253. int__cdeclmain(intargc,char*argv[])
  254. {

  255.   HANDLEh_event0;
  256.   HANDLEh_event1;
  257.   HANDLEh_event2;
  258.   HANDLEh_event3;
  259.   HANDLEh_event4;
  260.   HANDLEh_event5;
  261.   HANDLEh_event6;
  262. HANDLEh_event7;

  263.   char*device_name="\\user";
  264.   DWORDnum_devices=get_devices(GUID_DEVINTERFACE_XDMA,base_path,sizeof(base_path));

  265.   verbose_msg("Devicesfound:%d\n",num_devices);
  266.   if(num_devices<1)
  267.   {
  268.     printf("error\n");
  269.   }

  270.   h_user=open_devices(base_path,device_name,GENERIC_READ|GENERIC_WRITE);
  271.   if(h_user==INVALID_HANDLE_VALUE)
  272.   {
  273.     fprintf(stderr,"Erroropeningdevice,win32errorcode:%ld\n",GetLastError());
  274.   }

  275.   h_event0=CreateThread(NULL,0,thread_event0,NULL,0,NULL);
  276.   h_event1=CreateThread(NULL,0,thread_event1,NULL,0,NULL);
  277.   h_event2=CreateThread(NULL,0,thread_event2,NULL,0,NULL);
  278.   h_event3=CreateThread(NULL,0,thread_event3,NULL,0,NULL);

  279.   user_irq_ack[0]=0xffff0000;
  280.   write_device(h_user,0x00004,4,(BYTE*)user_irq_ack);//startirq
  281.   start_en=1;

  282.   printf("start\n");
  283.   WaitForSingleObject(h_event0,INFINITE);
  284.   WaitForSingleObject(h_event1,INFINITE);
  285.   WaitForSingleObject(h_event2,INFINITE);
  286.   WaitForSingleObject(h_event3,INFINITE);

  287.   user_irq_ack[0]=0x00000000;
  288.   write_device(h_user,0x00004,4,(BYTE*)user_irq_ack);//stop irq
  289.   CloseHandle(h_user);
  290.   CloseHandle(h_event0);
  291.   CloseHandle(h_event1);
  292.   CloseHandle(h_event2);
  293.   CloseHandle(h_event3);
  294.   return0;
  295. }
复制代码
以下指令启动中断
user_irq_ack[0]=0xffff0000;
write_device(h_user,0x00004,4,(BYTE*)user_irq_ack);
以下指令关闭中断
user_irq_ack[0]=0x00000000;
write_device(h_user,0x00004,4,(BYTE*)user_irq_ack);
以上程序设置了4个中断事件,每个事件开启了一个线程,当中断等待的时候线程是挂起的,当中断产生后,继续执行线程。对于XDMA最大可以支持32个中断
3:XDMA中断测试
执行xdma_event.exe程序

298eb3e219214982b0986edcb4ad664c.jpg

336857164bcf4b748f52b7ac2f2da38b.jpg
可以看到运行结果是4个中断事件,实际上XMDA最大支持16个中断事件。更多的中断时间可以更好的发挥CPU多核多线程的性能。
看FPGA抓的波形信号
c9bfb58e2910426b9e8db47cd2f3ebec.jpg
好了,到此XDMA的PCIE方案核心内容就已经讲完了。XILINX官方给的资料往往没有细化,我们米联客已经对以上驱动稍加修改,以更好地支持中断。







5db1d8c86bf54731a3561cad7be59e22.jpg
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则