[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA_SDK高级篇连载-03PL发数据到PS方案(DMA)

文档创建者:FPGA课程
浏览次数:232
最后更新:2024-09-29
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 1_SDK应用方案(仅旗舰型号) » 2-SDK高级应用方案
本帖最后由 FPGA课程 于 2024-9-29 13:17 编辑


软件版本: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概述
通过前面AXI-DMA的环路数据通信测试,我们掌握了AXI-DMA的用法,本文再此基础上加入FPGA的代码部分,完成数据从FPGA(FPGA端通常简称为PL)端发送到PS DDR。本文的demo具有项目的实际意义,比如我们经常需要做的数据采集方案基于此方案就可以快速搭建完成。
本文实验目的:
1:掌握编程PL代码,以AXI-Stream协议把数据通过DMA发送到PS DDR
2:通过VITIS-SDK编程实现数据的接收
3:通过VITIS-SDK观察PS内存中接收到的数据是否正确。
2系统框图
本系统中搭建了基于AXI-DMA的数据上传通路,通过uiaxisbufw ip完成测试数据转成axi-stream流数据进入AXI-DMA。Vitis-sdk代码中需要配置AXI-DMA的写读通道,设置正确的数据包大小,当测试数据通过AXI-DMA的数据量到达了Vitis-sdk代码设置的数据量后参数AXI-DMA IP会产生一个PL中断通知PS获取数据。
a2dd42a7c77f497ba5326aedc3e7a974.jpg
3搭建SOC系统工程
详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“3-1-01米联客2024版ZynqSocSDK入门篇”中第一个工程 “01Vitis Soc开发入门”这个实验。
3.1Zynq IP PS部分设置
本文中的PS设置内容是新增加的配置部分,关于DDR、MIO、CPU时钟等设置请参考“3-1-01米联客2024版ZynqSocSDK入门篇”中第一个工程 “01Vitis Soc开发入门”这个实验。
1:PS复位设置
79421243abda4e0f9d7f03e60e911114.jpg
2:设置PS GT Master接口和HPSlave接口
49abbec9596b4d5e8fcc5a2675368f3f.jpg
3:设置PL到PS的中断
Interurptsà勾选 Fabric Interrupt,勾选IRQ_F2P[15:0]。
d4a77a8904e9445b96f984c23ea42af2.jpg
4:设置PL的时钟
勾选FCLK_CLK0,设置为100M AXI-Stream到AXI4总线的数据时钟
勾选FCLK_CLK1,设置50M 用户数据时钟
本系统需要2个时钟完成数据方案传输, FCLK_CLK0大于FCLK_CLK1是为了满足AXI Stream部分的数据吞吐能力要大于用户写数据速度。
6ebeb278c08142d2aa19e99f8a85d0ee.jpg
5:ZYNQ IP设置完成后
8eb85c5af3ed4a1d80d89532e547b0d3.jpg
3.2添加IP
单击添加IP按钮“+”,输入如下模块IP名字的关键词,并双击添加,其中ui_axisbufw ip是我们自定义IP用于把非axistream协议数据转为axistream协议数据。用户可以根据自己的习惯搭建以下工程,演示的方法为了让初学者看到过程,但不是效率最高,最优化的方法。
d3e04f1b247e4894a5ff15dc8e3324dd.jpg
3.3IP设置
1:AXI-DMA IP设置
设置Wideh of buffer length register :23。由于我们的用户测试数据一次发送4MB数据,因此这个值设置23(8MB)可以最大支持8MB的数据。
eb3efbefc8314c399f53a8018a016316.jpg
2:ui_axisbufw ip设置
ui_axisbufw是米联客自定义IP用于配套AXI-DMA IP完成写数据通路的传输,可以实现用户接口数据转为axi-stream接口数据。
Video Enable:用于使能是否支持视频帧传输,这里不选择
AXI Data Width:必须设置和AXI-DMA的Stream Data Width 这里设置32
WBufdepth:设置ui_axisbufw FIFO的深度,一般为2倍的WXsize大小
WDatawidth:设置用户接口的数据数据位宽
WXsize:设置ui_axisbufw IP内部控制器一次burst的大小
WYsize:设置ui_axisbufw IP内部控制器进行多少次burst
可以看出数据量大小=WXsize* WYsize* WDatawidth/8
所以这里计算得出一次DMA完成1024*1024*32/8=4MB数据
9e8f6b89da024d7a821c9d59ef909be6.jpg
为了可以调用自定义IP,必须添加好IP路径
fc4b2a687b38413c9a33878de6e06638.jpg
3:AXI-GPIO IP设置
通过定义2个AXI-GPIO用于控制信号,控制复位和数据传输的启动/停止
8535605019f747f094fbb7637cbb1723.jpg
daec0a02ebdc4c7a8313837164470ec3.jpg
可以看到AXI-GPIO的IP名被修改成gpio_user_start和gpio_user_rstn,这样有利于我们识别每个IP的功能。选择IP即可修改。
4a58ff3b75f24898a53fcb10e91074d2.jpg
3.4PL图形编程
点击Run Connection Automation 快速自动连线。
21628518dddb4619b45eacb28354773d.jpg
只要软件提示你需要自动连线,一般都需要进行自动连线,除非自己知道如何连线,有特殊需求。
c1debe7e8ce24ec4b9468183b45dbe5e.jpg
这步完成后,部分连线已经完成,如果还有提示需要自动连线的继续让软件自动连线
be533b718b3649b7b647e7258ffeaca2.jpg
9d56a814d6424693918079e542150173.jpg
直到出现如下。可以看到,还有未连线的模块,这部分就需要手动完成了。
638a8cf6f5914c90a282771cd830aae0.jpg
完成后如下:
2b93f7159c404aacba5cebad7e74cc14.jpg
3.5添加ila在线逻辑分析仪
添加ila ip核的方法这里不再重复
e82f783853fd437ebc3289a0f553096b.jpg
3.6完成图形编程设计
71da929cee524920b5212c8df08e22eb.jpg
至此,就完成了工程架构的搭建。后面的操作过程是Validate Design->Generate Out products->Create wrappers-> Generate Bitstream ,产生完成后导出硬件,加载Vitis IDE。
3.7FPGA代码分析
1:写状态机
为了配合AXI-DMA IP发送数据到PS,我们写了一个简单ui_axisbufw ip 通过这个IP把用户编写的数据时序,转为AXI-Stream数据流,给DMA。该IP支持视频格式的帧同步,每一帧都进行同步,也支持没有帧同步的数据流方式传输。
54bf7bf6a29c46fab9e1c344ac2ee715.jpg
2:AXI-Stream时序波形图
以下时序波形图中主要描述了写状态机与AXIStream通信接口之间的时序关系。波形中没有给出axis_keep信号,该信号所有位都为1代表数据所有字节都是有效字节。
99ce3d8dfc9244b2a0c8499cc361ab43.jpg
3:源码注解
3-1-ui_axisbufw.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: 2021/10/15
  9. *Module Name:ui_axisbufw
  10. *File Name:ui_axisbufw.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 / 1ns
  28. module ui_axisbufw#(
  29. parameter  integer                  VIDEO_ENABLE   = 0,  //是否使能视频模式
  30. parameter  integer                  AXI_DATA_WIDTH = 128, //AXI-STREAM的数据位宽
  31. parameter  integer                  W_BUFDEPTH     = 2048, //设置FIFO深度
  32. parameter  integer                  W_DATAWIDTH    = 32, //设置用户写数据的数据位宽
  33. parameter  integer                  W_XSIZE        = 1024, //设置每次burst的数据长度
  34. parameter  integer                  W_YSIZE        = 1024//设置需要Burst的次数,所以总数据量=W_DATAWIDTH*W_XSIZE* W_YSIZE/8
  35. )
  36. (
  37. input wire                                  ui_rstn, //系统复位低电平有效
  38. //sensor input -W_FIFO--------------
  39. input wire                                  W_wclk_i, //用户写数据时钟
  40. input wire                                  W_FS_i,//用户写数据同步,视频帧起到同步作用,非视频帧设置为1启动传输
  41. input wire                                  W_wren_i,//用户写数据使能
  42. input wire     [W_DATAWIDTH-1'b1 : 0]       W_data_i,//用户写数据
  43. //----------axis signals write-------         
  44. input wire                                  axis_clk,//axi stream接口时钟,同时也是本IP的系统时钟   
  45. output wire    [AXI_DATA_WIDTH/8-1'b1:0]    axis_keep,//axi stream 数据有效byte设置,本IP设置全部为1                       
  46. output wire    [AXI_DATA_WIDTH-1'b1:0]      axis_wdata,//axi stream 写数据
  47. output wire                                 axis_wvalid,//axi stream 写有效
  48. input  wire                                 axis_wready,//axi stream 写准备好
  49. output wire                                 axis_last//axi stream 最后一个数据
  50. );  
  51.   
  52. //初始化axis_kepp所有bit位为1
  53. genvar var;
  54. generate
  55.   for (var=0; var < (AXI_DATA_WIDTH/8); var=var+1)
  56.   begin: init_keep
  57.       assign axis_keep[var] = 1'b1;
  58.   end
  59. endgenerate
  60. //计算数据位宽
  61. function integer clog2;
  62.   input integer value;
  63.   begin
  64.     value = value-1;
  65.     for (clog2=0; value>0; clog2=clog2+1)
  66.       value = value>>1;
  67.     end
  68. endfunction
  69. //状态机的状态值
  70. localparam S_IDLE  =  2'd0;  
  71. localparam S_RST   =  2'd1;  
  72. localparam S_DATA1 =  2'd2;   
  73. localparam S_DATA2 =  2'd3;
  74. //初始化FIFO的深度,位宽等参数
  75. localparam WFIFO_DEPTH = W_BUFDEPTH;
  76. localparam W_WR_DATA_COUNT_WIDTH = clog2(WFIFO_DEPTH)+1;
  77. localparam W_RD_DATA_COUNT_WIDTH = clog2(WFIFO_DEPTH*W_DATAWIDTH/AXI_DATA_WIDTH)+1;//clog2(WFIFO_DEPTH/(AXI_DATA_WIDTH/W_DATAWIDTH))+1;
  78. //初始化需要多少次BURST完成一次axistream传输
  79. localparam FDMA_WX_BURST        = (W_XSIZE*W_DATAWIDTH/AXI_DATA_WIDTH);
  80. (*mark_debug = "true"*) (* KEEP = "TRUE" *)wire  W_wren_ri = W_wren_i;
  81. reg                                                                                W_FIFO_Rst=0;
  82. (*mark_debug = "true"*) (* KEEP = "TRUE" *)wire                                    W_FS;
  83. (*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [1 :0]                              W_MS=0;
  84. reg [1 :0]                                                                         W_MS_r = 1'b0;
  85. (*mark_debug = "true"*) (* KEEP = "TRUE" *)reg                                     axis_wvalid_r = 1'b0;
  86. (*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [15:0]                              W_xcnt=0;
  87. (*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [15:0]                              W_ycnt=0;
  88. (*mark_debug = "true"*) (* KEEP = "TRUE" *)wire[W_RD_DATA_COUNT_WIDTH-1'b1 :0]     W_rcnt;
  89. (*mark_debug = "true"*) (* KEEP = "TRUE" *)reg                                     W_REQ=0;
  90. reg [7 :0]                                                                         wrst_cnt =0;
  91. (*mark_debug = "true"*) (* KEEP = "TRUE" *)wire                                    axis_rd_en;
  92. (*mark_debug = "true"*) (* KEEP = "TRUE" *)wire                                    axis_wready_r;
  93. assign axis_wready_r = axis_wready;
  94. assign axis_wready_r = axis_wready;
  95. //帧同步,或者传输使能
  96. fs_cap #
  97. (
  98. .VIDEO_ENABLE(VIDEO_ENABLE)
  99. )
  100. fs_cap_W0
  101. (
  102. .clk_i(axis_clk),
  103. .rstn_i(ui_rstn),
  104. .vs_i(W_FS_i),
  105. .fs_cap_o(W_FS)
  106. );
  107. assign axis_wvalid = (W_MS == S_DATA2);
  108. assign axis_rd_en = axis_wvalid_r&axis_wready; //读FIFO数据
  109. assign axis_last = (W_xcnt == FDMA_WX_BURST - 1'b1 && axis_rd_en == 1'b1) && (W_ycnt == W_YSIZE - 1'b1);
  110. //axistream 的last信号,代表本次DMA传输结束,DMA会产生一个中断
  111. always @(posedge axis_clk) begin
  112.     if(!ui_rstn)begin
  113.         W_MS         <= S_IDLE;
  114.         W_FIFO_Rst   <= 0;
  115.         W_xcnt       <= 0;
  116.         W_ycnt       <= 0;
  117.         wrst_cnt     <= 0;
  118.     end   
  119.     else begin
  120.       case(W_MS)
  121.         S_IDLE:begin
  122.           W_xcnt      <= 0;
  123.           W_ycnt      <= 0;
  124.           wrst_cnt    <= 0;
  125.           if(W_FS) W_MS <= S_RST; //担当W_FS为1启动本次传输
  126.        end
  127.        S_RST:begin//如果是视频帧,这个阶段同步视频,否则直接进入S_DATA1状态
  128.            wrst_cnt <= wrst_cnt + 1'b1;
  129.            if((VIDEO_ENABLE == 1) && (wrst_cnt < 40))
  130.                 W_FIFO_Rst <= 1;
  131.            else if((VIDEO_ENABLE == 1) && (wrst_cnt < 100))
  132.                 W_FIFO_Rst <= 0;
  133.            else
  134.                 W_MS <= S_DATA1;
  135.        end
  136.        S_DATA1:begin //当axistream总线空闲,并且FIFO中的一次burst数据达到发送要求进入S_DATA2
  137.           if(axis_wready == 1'b1 && W_REQ )
  138.              W_MS    <= S_DATA2;     
  139.        end  
  140.        S_DATA2:begin //写数据到DMA
  141.             if(axis_wready == 1'b1 && W_xcnt < FDMA_WX_BURST - 1'b1) //计算每次burst的数据数量
  142.                 W_xcnt <= W_xcnt + 1'b1;
  143.             else if(axis_wready == 1'b1 && W_xcnt == FDMA_WX_BURST - 1'b1)begin
  144.                 W_xcnt <= 0;
  145.                 W_ycnt <= W_ycnt + 1'b1; //计算一共进行了多少次burst
  146.                 if(W_ycnt == W_YSIZE - 1'b1) //发送完毕后,进入S_IDLE进行下次发送
  147.                      W_MS <= S_IDLE;
  148.                 else
  149.                      W_MS <= S_DATA1; //没有发送进入S_DATA1等待FIFO数据准备好继续发送
  150.             end
  151.          default: W_MS <= S_IDLE;
  152.        endcase
  153.     end
  154. end
  155. //FIFO中读通道的数据量是否达到或者超过一次burst需要准备好的数据量
  156. wire W_rbusy;
  157. always@(posedge axis_clk)     
  158.      W_REQ  <= (W_rcnt > FDMA_WX_BURST - 2)&&(~W_rbusy);
  159. // xpm_fifo_async源语调用
  160. xpm_fifo_async # (
  161.   .FIFO_MEMORY_TYPE          ("auto"),           //string; "auto", "block", or "distributed";
  162.   .ECC_MODE                  ("no_ecc"),         //string; "no_ecc" or "en_ecc";
  163.   .RELATED_CLOCKS            (0),                //positive integer; 0 or 1
  164.   .FIFO_WRITE_DEPTH          (WFIFO_DEPTH),     //positive integer
  165.   .WRITE_DATA_WIDTH          (W_DATAWIDTH),               //positive integer
  166.   .WR_DATA_COUNT_WIDTH       (W_WR_DATA_COUNT_WIDTH),               //positive integer
  167.   .PROG_FULL_THRESH          (20),               //positive integer
  168.   .FULL_RESET_VALUE          (0),                //positive integer; 0 or 1
  169.   .USE_ADV_FEATURES          ("0707"),           //string; "0000" to "1F1F";
  170.   .READ_MODE                 ("fwft"),            //string; "std" or "fwft";
  171.   .FIFO_READ_LATENCY         (0),                //positive integer;
  172.   .READ_DATA_WIDTH           (AXI_DATA_WIDTH),               //positive integer
  173.   .RD_DATA_COUNT_WIDTH       (W_RD_DATA_COUNT_WIDTH),               //positive integer
  174.   .PROG_EMPTY_THRESH         (10),               //positive integer
  175.   .DOUT_RESET_VALUE          ("0"),              //string
  176.   .CDC_SYNC_STAGES           (2),                //positive integer
  177.   .WAKEUP_TIME               (0)                 //positive integer; 0 or 2;
  178. ) xpm_fifo_W_inst (
  179.       .rst              ((ui_rstn == 1'b0) || (W_FIFO_Rst == 1'b1)),
  180.       .wr_clk           (W_wclk_i),
  181.       .wr_en            (W_wren_i),
  182.       .din              (W_data_i),
  183.       .full             (),
  184.       .overflow         (),
  185.       .prog_full        (),
  186.       .wr_data_count    (),
  187.       .almost_full      (),
  188.       .wr_ack           (),
  189.       .wr_rst_busy      (),
  190.       .rd_clk           (axis_clk),
  191.       .rd_en            (axis_rd_en),
  192.       .dout             (axis_wdata),
  193.       .empty            (),
  194.       .underflow        (),
  195.       .rd_rst_busy      (W_rbusy),
  196.       .prog_empty       (),
  197.       .rd_data_count    (W_rcnt),
  198.       .almost_empty     (),
  199.       .data_valid       (W_dvalid),
  200.       .sleep            (1'b0),
  201.       .injectsbiterr    (1'b0),
  202.       .injectdbiterr    (1'b0),
  203.       .sbiterr          (),
  204.       .dbiterr          ()
  205. );
  206. endmodule
复制代码

3-2-fs_cap.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: 2021/10/15
  9. *Module Name:fs_cap
  10. *File Name:fs_cap.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 fs_cap#(
  29. parameter  integer  VIDEO_ENABLE   = 1
  30. )
  31. (
  32. input  clk_i,
  33. input  rstn_i,
  34. input  vs_i,
  35. output reg fs_cap_o
  36. );
  37.    
  38. reg[4:0]CNT_FS   = 6'b0;
  39. reg[4:0]CNT_FS_n = 6'b0;
  40. reg     FS       = 1'b0;
  41. //异步信号定义,告诉工具异步信号不用分析时序
  42. (* ASYNC_REG = "TRUE" *)   reg vs_i_r1;
  43. (* ASYNC_REG = "TRUE" *)   reg vs_i_r2;
  44. (* ASYNC_REG = "TRUE" *)   reg vs_i_r3;
  45. (* ASYNC_REG = "TRUE" *)   reg vs_i_r4;
  46. //异步转同步
  47. always@(posedge clk_i) begin
  48.       vs_i_r1 <= vs_i;
  49.       vs_i_r2 <= vs_i_r1;
  50.       vs_i_r3 <= vs_i_r2;
  51.       vs_i_r4 <= vs_i_r3;
  52. end
  53. //同步vs信号,只有当使能了VIDEO_ENABLE才会启作用
  54. always@(posedge clk_i) begin
  55.    if(!rstn_i)begin
  56.       fs_cap_o <= 1'd0;
  57.    end
  58.    else if(VIDEO_ENABLE == 1)begin //当VIDEO_ENABLE=1,vs的上升沿有效,用于视频同步
  59.       if({vs_i_r4,vs_i_r3} == 2'b01)begin
  60.          fs_cap_o <= 1'b1;
  61.       end
  62.       else begin
  63.          fs_cap_o <= 1'b0;
  64.       end
  65.    end
  66.    else begin//当VIDEO_ENABLE=0,直接寄存一次vs_i_r4
  67.          fs_cap_o <= vs_i_r4;
  68.    end
  69. end
  70.         
  71. endmodule
复制代码

3-3-system_wrapper_top.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: 2021/10/15
  9. *Module Name:system_wrapper
  10. *File Name:system_wrapper_top.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 1 ps / 1 ps
  28. module system_wrapper
  29. (
  30. inout [14:0]DDR_addr,
  31. inout [2:0]DDR_ba,
  32. inout DDR_cas_n,
  33. inout DDR_ck_n,
  34. inout DDR_ck_p,
  35. inout DDR_cke,
  36. inout DDR_cs_n,
  37. inout [3:0]DDR_dm,
  38. inout [31:0]DDR_dq,
  39. inout [3:0]DDR_dqs_n,
  40. inout [3:0]DDR_dqs_p,
  41. inout DDR_odt,
  42. inout DDR_ras_n,
  43. inout DDR_reset_n,
  44. inout DDR_we_n,
  45. inout FIXED_IO_ddr_vrn,
  46. inout FIXED_IO_ddr_vrp,
  47. inout [53:0]FIXED_IO_mio,
  48. inout FIXED_IO_ps_clk,
  49. inout FIXED_IO_ps_porb,
  50. inout FIXED_IO_ps_srstb
  51. );
  52. reg  [31:0]W_data_i_0;
  53. wire W_wren_i_0;
  54. wire [0:0]dma_rstn;
  55. wire [0:0]dma_start;
  56. wire pl_clk;
  57. assign W_wren_i_0 = dma_start;
  58. //通过一个写计数器用于产生测试数据
  59. always @(posedge pl_clk)begin
  60.     if(dma_rstn == 1'b0)
  61.         W_data_i_0 <= 0;
  62.     else if(dma_start )
  63.         W_data_i_0 <= W_data_i_0 + 1'b1;
  64. end
  65. //调用图形化的BD工程
  66. system system_i
  67. (
  68. .DDR_addr(DDR_addr),
  69. .DDR_ba(DDR_ba),
  70. .DDR_cas_n(DDR_cas_n),
  71. .DDR_ck_n(DDR_ck_n),
  72. .DDR_ck_p(DDR_ck_p),
  73. .DDR_cke(DDR_cke),
  74. .DDR_cs_n(DDR_cs_n),
  75. .DDR_dm(DDR_dm),
  76. .DDR_dq(DDR_dq),
  77. .DDR_dqs_n(DDR_dqs_n),
  78. .DDR_dqs_p(DDR_dqs_p),
  79. .DDR_odt(DDR_odt),
  80. .DDR_ras_n(DDR_ras_n),
  81. .DDR_reset_n(DDR_reset_n),
  82. .DDR_we_n(DDR_we_n),
  83. .FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
  84. .FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
  85. .FIXED_IO_mio(FIXED_IO_mio),
  86. .FIXED_IO_ps_clk(FIXED_IO_ps_clk),
  87. .FIXED_IO_ps_porb(FIXED_IO_ps_porb),
  88. .FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
  89. .W_data_i_0(W_data_i_0),
  90. .W_wren_i_0(W_wren_i_0),
  91. .dma_rstn(dma_rstn),
  92. .dma_start(dma_start),
  93. .pl_clk(pl_clk)
  94. );
  95. endmodule
复制代码

3.8地址空间分配
fcac5474b7694bbea2d0a7e3c8ed0210.jpg
3.9编译并导出平台文件
1:单击Block文件à右键àGenerate the Output ProductsàGlobalàGenerate。
2:单击Block文件à右键à Create a HDL wrapper(生成HDL顶层文件)àLet vivado manager wrapper and auto-update(自动更新)。
3:生成Bit文件。
4:导出到硬件: FileàExport HardwareàInclude bitstream
5:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。
061d3e2d646a4465b8f54d8eceb122d9.jpg
4搭建Vitis-sdk工程
创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。
4.1创建SDK Platform工程
ec53b87b3d7048c7921ad73d66ed6c1c.jpg
4.2创建axi_dma_pl2ps APP工程
0a0d4985b31943be9eb105f63d630a57.jpg
5程序分析
5.1总流程图
如下图所示,本文的程序工作流程如下,包括初始化中断、初始化16个缓存地址用于接收PL发送过来的数据帧。当16个缓存都存好数据后,停止传输。我们可以在这个位置设置断点,通过JTAG在线观察内存中的数据是否正确。之后再次启动传输。
2609b1207f0742458d3f4d5b340088e3.jpg
5.2main.c源码的分析
1:gpio_init函数
此函数通过设置PL AXI-GPIO,让PL逻辑完成一次复位,并且通过AXI-GPIO控制数据传输的启动和停止
  1. void gpio_init(void)
  2. {
  3. //设置GPIO 复位控制输出方向,并且设置0
  4.         XGpio_Initialize(&gpio_user_rstn, XPAR_GPIO_USER_RSTN_DEVICE_ID);
  5.         XGpio_SetDataDirection(&gpio_user_rstn, 1, 0x0);
  6.         XGpio_DiscreteWrite(&gpio_user_rstn,1,0x0);
  7. //设置GPIO 启动控制输出方向,并且设置0
  8.         XGpio_Initialize(&gpio_user_start, XPAR_GPIO_USER_START_DEVICE_ID);
  9.         XGpio_SetDataDirection(&gpio_user_start, 1, 0x0);
  10. //完成复位
  11.         XGpio_DiscreteWrite(&gpio_user_rstn,1,0x1);//reset done
  12. }
复制代码

2:init_intr_sys函数
功能:对中断资源的初始化,使能中断资源。
说明:这个函数里面调用的函数是米联客封装好的初始化函数,使用起来比较方便。一般只要给出中断对象,中断号,就可以对中断进行初始化。
init_intr_sys函数
  1. int init_intr_sys(void)
  2. {
  3.         DMA_Intr_Init(&AxiDma,0);//初始化DMA中断
  4.         Init_Intr_System(&Intc); //初始化系统中断
  5.         Setup_Intr_Exception(&Intc); //设置全局中断
  6.         DMA_Setup_Intr_System(&Intc,&AxiDma,TX_INTR_ID,RX_INTR_ID);//设置DMA中断,把DMA中断函数关联到全局中断
  7.         DMA_Intr_Enable(&Intc,&AxiDma); //使能DMA中断
  8.         return 0;
  9. }
复制代码

下面对init_intr_sys函数中调用的函数功能进行说明:
DMA_Intr_Init(&AxiDma,0)
第一参数是DMA的对象;第二参数是硬件ID。
Init_Intr_System(&Intc)
初始化系统中断系统
DMA_Setup_Intr_System(&Intc,&AxiDma, RX_INTR_ID)
初始化并设置DMA的中断系统。
第一参数是一个指向INTC实例指针;
第二个参数是一个指向DMAengine的实例指针;
第三个参数是AXI-DMA RX通道中断ID;
DMA_Intr_Enable(&Intc,&AxiDma)
DMA中断使能
2:axi_dma_test()函数
找个函数中的工程流程如下:
1:初始化地址空间一共初始化16个缓存空间用于存放数据帧
  1. //initialize buffer address
  2. for(i=0;i<16;i++)
  3. {
  4. RxBufferPtr[i] = RX_BUFFER_BASE + 0x00400000*i;
  5. }
复制代码

2:在启动第一次DMA接收
  1. if(first_transmit)
  2. {
  3. Status = XAxiDma_SimpleTransfer(&AxiDma,(u32)RxBufferPtr[pkg_cnt],(u32)(MAX_PKT_LEN), XAXIDMA_DEVICE_TO_DMA);
  4. first_transmit = 0;
  5. }
复制代码

3:每次DMA中断后启动剩余15次DMA
  1. else if(RxDone && pkg_cnt <15 )
  2. {
  3.         RxDone =0;
  4.         Status = XAxiDma_SimpleTransfer(&AxiDma,(u32)RxBufferPtr[pkg_cnt+1],(u32)(MAX_PKT_LEN), XAXIDMA_DEVICE_TO_DMA);
  5. Xil_DCacheInvalidateRange((u32)RxBufferPtr[pkg_cnt], MAX_PKT_LEN);
  6.   pkg_cnt++;
  7. }
复制代码

4:当最后一次DMA中断产生后,重新初始化相关参数,进行重新开始传输
  1. else if(RxDone && pkg_cnt ==15)
  2. {
  3.         Xil_DCacheInvalidateRange((u32)RxBufferPtr[pkg_cnt], MAX_PKT_LEN);
  4.         RxDone =0;
  5.         pkg_cnt = 0;
  6.         dma_wr_set(0);//stop
  7.         first_transmit = 1;
  8.         DMA_Intr_Init(&AxiDma,0);
  9.         dma_wr_set(1);//restart
  10. }
复制代码

以上代码中,为了确保可以获取到DDR的正确数据,必须,当从DMA接收中断接收数据后,访问DDR中的数据必须采用Xil_DCacheInvalidateRange 确保cache中的数据都在DDR中。
5.3dma_intr.c源码分析

1:DMA中断函数的设置和中断的使能
DMA_Setup_Intr_System函数
该函数用于设置接收和发送中断函数的回调函数。
首先通过XScuGic_SetPriorityTriggerType函数设置了PL中断的优先级为0Xa0,采用上升沿出发方式。
其次通过XScuGic_Connect完成中断函数的绑定
最后通过XScuGic_Enable函数完成DMA的PL中断绑定到INTC系统中断中,这样系统中断就能根据中断号回调之前绑定的函数。
  1. int DMA_Setup_Intr_System(XScuGic * IntcInstancePtr,XAxiDma * AxiDmaPtr, u16 RxIntrId)
  2. {
  3.         int Status;
  4.         XScuGic_SetPriorityTriggerType(IntcInstancePtr, RxIntrId, 0xA0, 0x3); //设置PL中断上升沿触发
  5.         
  6.         Status = XScuGic_Connect(IntcInstancePtr, RxIntrId, //关联DMA PL中断函数到全局中断
  7.                                 (Xil_InterruptHandler)DMA_RxIntrHandler,
  8.                                 AxiDmaPtr);
  9.         if (Status != XST_SUCCESS) {
  10.                 return Status;
  11.         }
  12.         //XScuGic_Enable(IntcInstancePtr, TxIntrId);
  13.         XScuGic_Enable(IntcInstancePtr, RxIntrId); //使能DMA接收中断
  14.         return XST_SUCCESS;
  15. }
复制代码

DMA_Intr_Enable函数:
由于AXI-DMA控制器是FPGA端实现的,因此为了让其产生中断还要使能其中断能能寄存器,通过调用DMA_Intr_Enable使能DMA控制器的发送和接收中断。
  1. int DMA_Intr_Enable(XScuGic * IntcInstancePtr,XAxiDma *DMAPtr)
  2. {
  3.         /* Disable all interrupts before setup */
  4.         XAxiDma_IntrDisable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
  5.         /* Enable all interrupts */
  6.         XAxiDma_IntrEnable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
  7.         return XST_SUCCESS;
  8. }
复制代码

2:DMA接收中断函数DMA_RxIntrHandler
XAxiDma_SimpleTransfer(&AxiDma,(u32)RxBufferPtr[pkg_cnt],(u32)(MAX_PKT_LEN), XAXIDMA_DEVICE_TO_DMA)函数启动DMA接收,PL持续写入数据到AXI-DMA控制器,当数据从AXI-DMA控制器发送到PS后,会产生一个DMA接收中断。中断系统会回调DMA_RxIntrHandler函数。DMA_TxIntrHandler函数中的内容分析如下:
XAxiDma *AxiDmaInst = (XAxiDma *)Callback;这句代码是为了获取当前中断的对象。void *Callback是一个无符号的指针,传递进来的阐述可以强制转换成其他任何的对象,这里就是强制转换成 XAxiDma 对象了。
IrqStatus =XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA)这个函数获取当前中断号。
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);这个函数是响应当前中断,通知CPU 当前中断已经被接收,并且清除中断标志位。如果中断全部正确,TxDone将被置为1表示发送中断完成。如果有错误,则复位DMA,并且设置超时参数。
函数源码如下:
  1. static void DMA_RxIntrHandler(void *Callback)
  2. {
  3.         u32 IrqStatus;
  4.         int TimeOut;
  5.         XAxiDma *AxiDmaInst = (XAxiDma *)Callback;
  6.         /* Read pending interrupts */
  7.         IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);
  8.         /* Acknowledge pending interrupts */
  9.         XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);
  10.         /*
  11.          * If no interrupt is asserted, we do not do anything
  12.          */
  13.         if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
  14.                 return;
  15.         }
  16.         /*
  17.          * If error interrupt is asserted, raise error flag, reset the
  18.          * hardware to recover from the error, and return with no further
  19.          * processing.
  20.          */
  21.         if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
  22.                 Error = 1;
  23.                 /* Reset could fail and hang
  24.                  * NEED a way to handle this or do not call it??
  25.                  */
  26.                 XAxiDma_Reset(AxiDmaInst);
  27.                 TimeOut = RESET_TIMEOUT_COUNTER;
  28.                 while (TimeOut) {
  29.                         if(XAxiDma_ResetIsDone(AxiDmaInst)) {
  30.                                 break;
  31.                         }
  32.                         TimeOut -= 1;
  33.                 }
  34.                 return;
  35.         }
  36.         /*
  37.          * If completion interrupt is asserted, then set RxDone flag
  38.          */
  39.         if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
  40.                 RxDone = 1;
  41.         }
  42. }
复制代码

6方案结果
6.1硬件准备
本实验需要用到JTAG下载器、USB转串口外设,另外需要把核心板上的2P模式开关设置到JTAG模式,即ON ON
b7c7462c19cf4d5fb693fd7666ecc8ad.jpg
6.2实验结果

01942e84212d42728237f1e4bb78379d.jpg
Debug程序,单程序停止main函数处,打开VIVADO,扫描芯片,这个时候会自动之前添加的在线逻辑分析仪IP核。如下红框的按键先不要单击。
ae6760a6d6254031b23c394eeea4166e.jpg
具体步骤如下:
在VIVADO工程中点击Open Target 然后点击Auto Connect
34969b46852343459b2d136f7c0c0d14.jpg
连接成功后,双击hw_ila_2 如下图
63ee00e5b5ae428a8ab16864f24eaeec.jpg
下图中,我们利用axi-tlast信号作为触发信号
3f1ff4ff3d3940c0b5394dfedcaf9b94.jpg
单击如下图片,让在线逻辑分析仪的第2个窗口都处于等待触发状态
4c72851fccd641399c907f27b6c3b0fa.jpg
回到SDK,继续设置,打开Memory:Window->Show View->Memory
b1838ae45e824b20952f94ae8f6d924a.jpg
点击添加接收内存部分地址用于观察内存中的数据
030e8330e43c4541bdde1bb33257280a.jpg

f87b8617b7d1407298c125ecf107b892.jpg
一共添加16个起始地址和16个结束地址,起始地址用于观察数据开头是否正确,结束地址用于官方数据结束是否正确。该组地址的就是以下程序完成的初始化。其中RX_BUFFER_BASE=0x080000000,对于ZYNQ SOC 低地址空间的20MB最好不要访问。
for(i=0;i<16;i++) {RxBufferPtr = RX_BUFFER_BASE + 0x04000000*i; }
501ad2882fda4043879760e069994820.jpg
为了观察一次收发数据:设置断点,重新让收发程序跑一次。双击以下程序处可以设置断点。
14e248c18755461597994b8b7c4cd6c7.jpg
设置完成后,单击如下可以单击如下红框的按键
8bd8744b46c841b39858dcb20857a4b6.jpg
通过人工校验的方式查看内存数据是否正确,我们PL部分是一个加计数器,PL代码如下
  1. assign W_wren_i_0 = dma_start;
  2. always @(posedge pl_clk)begin
  3.     if(dma_rstn == 1'b0)
  4.         W_data_i_0 <= 0;
  5.     else if(dma_start )
  6.         W_data_i_0 <= W_data_i_0 + 1'b1;
  7. end
复制代码

内存中数据是连续的加计数器,由于16个内存地址,每个地址存放4MB数据,我们截图几张证明数据无丢失传输正确。
af6c4cdd54fb44af8109fafbb0137edb.jpg
ed9262c023f54a3e8c14685d8257ca7b.jpg
在线逻辑分析仪
69cc322634c44cdd8a9dbc1943ff76e6.jpg

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

本版积分规则