08 AXI4-FULL-MASTER IP FDMA详解

发布时间 2023-12-30 15:07:48作者: 米联客(milianke)

软件版本:vitis2021.1(vivado2021.1)

操作系统:WIN10 64bit

硬件平台:适用XILINX A7/K7/Z7/ZU/KU系列FPGA

登录"米联客"FPGA社区-www.uisrc.com视频课程、答疑解惑!

1概述

FDMA是米联客的基于AXI4总线协议定制的一个DMA控制器。本文对AXI4-FULL总线接口进行了封装,同时定义了简单的APP接口提供用户调用AXI4总线实现数据交互。这个IP 我们命名为FDMA(Fast Direct Memory Access)

有了这个IP我们可以统一实现用FPGA代码直接读写PLDDR或者ZYNQ/ZYNQMP SOC PSDDR或者BRAMFDMA IP CORE 已经广泛应用于ZYNQ SOC/Artix7/Kintex7 FPGA,同样适用于ultrascale/ultrascale+系列FPGA/SOC

如果用过ZYNQ/ZYNQMP的都知道,要直接操作PSDDR 通常是DMA 或者VDMA,然而用过XILINX DMA IP VDMA IP,总有一种遗憾,那就是不够灵活,还需要对寄存器配置,真是麻烦。XILINX 的总线接口是AXI4总线,自定义AXI4 IP挂到总线上就能实现对内存地址空间的读写访问。因此,我们只要掌握AXI4协议就能完成不管是PS还是PL DDR的读写操作。

米联客封装的AXI4总线协议命名位uiFDMA,自动2018年第一版本发布后,就引起了很多FPGA工程师的兴趣,并且得到了广大FPGA工程师的好评,但是FDMA1.0版本还是有一些局限和BUG,再实际的应用中被FPGA工程师发现,因此给了我们很多宝贵意见。借此2024版本教程更新发布之际,我们也对FDMA1.0版本升级到FDMA3.2版本。解决3.1版本中,当总的burst长度是奇数的时候出现错误,修改端口命名规则,设置I代表了输入信号,O代表了输出信号。

从本文开始,我们从多个应用方案来演示FDMA的用途。

本文实验目的:

1:分析FDMA源码,掌握基于FDMAAPP接口实现AXI4-FULL总线接口的访问。

2:掌握自定义总线接口封装方法

3:自定义AXI-FULL-Slave IP用于验证FDMA的工作情况。

2 FDMA源码分析

由于AXI4总线协议直接操作起来相对复杂一些,容易出错,因此我们封装一个简单的用户接口,间接操作AXI4总线会带来很多方便性。先看下我们计划设计一个怎么样的用户接口。

1:FDMA的写时序

fdma_wready设置为1,当fdma_wbusy=0的时候代表FDMA的总线非忙,可以进行一次新的FDMA传输,这个时候可以设置fdma_wreq=1,同时设置fdma burst的起始地址和fdma_wsize本次需要传输的数据大小(bytes为单位)。当fdma_wvalid=1的时候需要给出有效的数据,写入AXI总线。当最后一个数写完后,fdma_wvalidfdma_wbusy变为0

AXI4总线最大的burst lenth256,而经过封装后,用户接口的fdma_size可以任意大小的,fdma ip内部代码控制每次AXI4总线的Burst长度,这样极大简化了AXI4总线协议的使用。

2:FDMA的读时序

fdma_rready设置为1,当fdma_rbusy=0的时候代表FDMA的总线非忙,可以进行一次新的FDMA传输,这个时候可以设置fdma_rreq=1,同时设置fdma burst的起始地址和fdma_rsize本次需要传输的数据大小(bytes为单位)。当fdma_rvalid=1的时候需要给出有效的数据,写入AXI总线。当最后一个数写完后,fdma_rvalidfdma_rbusy变为0

同样对于AXI4总线的读操作,AXI4总线最大的burst lenth256,而经过封装后,用户接口的fdma_size可以任意大小的,fdma ip内部代码控制每次AXI4总线的Burst长度,这样极大简化了AXI4总线协议的使用。

3:FDMAAXI4-Master写操作

以下代码中我们给出axi4-master写操作的代码分析注释

//fdma axi write----------------------------------------------

reg     [M_AXI_ADDR_WIDTH-1 : 0]  axi_awaddr  =0; //AXI4 写地址

reg                         axi_awvalid = 1'b0; //AXI4 写地有效

wire    [M_AXI_DATA_WIDTH-1 : 0]  axi_wdata   ; //AXI4 写数据

wire                        axi_wlast   ; //AXI4 LAST信号

reg                         axi_wvalid  = 1'b0; //AXI4 写数据有效

wire                             w_next= (M_AXI_WVALID & M_AXI_WREADY);//valid ready信号都有效,代表AXI4数据传输有效

reg   [8 :0]                     wburst_len  = 1  ; //写传输的axi burst长度,代码会自动计算每次axi传输的burst 长度

reg   [8 :0]                     wburst_cnt  = 0  ; //每次axi bust的计数器

reg   [15:0]                     wfdma_cnt   = 0  ;//fdma的写数据计数器

reg                              axi_wstart_locked  =0;  //axi 传输进行中,lock住,用于时序控制

wire  [15:0] axi_wburst_size   =   wburst_len * AXI_BYTES;//axi 传输的地址长度计算

 

assign M_AXI_AWID       = M_AXI_ID; //写地址ID,用来标志一组写信号, M_AXI_ID是通过参数接口定义

assign M_AXI_AWADDR     = axi_awaddr;

assign M_AXI_AWLEN      = wburst_len - 1;//AXI4 burst的长度

assign M_AXI_AWSIZE     = clogb2(AXI_BYTES-1);

assign M_AXI_AWBURST    = 2'b01;//AXI4busr类型INCR模式,地址递增

assign M_AXI_AWLOCK     = 1'b0;

assign M_AXI_AWCACHE    = 4'b0010;//不使用cache,不使用buffer

assign M_AXI_AWPROT     = 3'h0;

assign M_AXI_AWQOS      = 4'h0;

assign M_AXI_AWVALID   = axi_awvalid;

assign M_AXI_WDATA      = axi_wdata;

assign M_AXI_WSTRB      = {(AXI_BYTES){1'b1}};//设置所有的WSTRB1代表传输的所有数据有效

assign M_AXI_WLAST      = axi_wlast;

assign M_AXI_WVALID     = axi_wvalid & fdma_wready;//写数据有效,这里必须设置fdma_wready有效

assign M_AXI_BREADY     = 1'b1;

//----------------------------------------------------------------------------  

//AXI4 FULL Write

assign  axi_wdata        = fdma_wdata;

assign  fdma_wvalid      = w_next;

reg     fdma_wstart_locked = 1'b0;

wire    fdma_wend;

wire    fdma_wstart;

assign   fdma_wbusy = fdma_wstart_locked ;

//在整个写过程中fdma_wstart_locked将保持有效,直到本次FDMA写结束

always @(posedge M_AXI_ACLK)

    if(M_AXI_ARESETN == 1'b0 || fdma_wend == 1'b1 )

        fdma_wstart_locked <= 1'b0;

    else if(fdma_wstart)

        fdma_wstart_locked <= 1'b1;                                

//产生fdma_wstart信号,整个信号保持1  M_AXI_ACLK时钟周期

assign fdma_wstart = (fdma_wstart_locked == 1'b0 && fdma_wareq == 1'b1);    

         

//AXI4 write burst lenth busrt addr ------------------------------

//fdma_wstart信号有效,代表一次新的FDMA传输,首先把地址本次fdmaburst地址寄存到axi_awaddr作为第一次axi burst的地址。如果fdma的数据长度大于256,那么当axi_wlast有效的时候,自动计算下次axiburst地址

always @(posedge M_AXI_ACLK)

    if(fdma_wstart)    

        axi_awaddr <= fdma_waddr;

    else if(axi_wlast == 1'b1)

        axi_awaddr <= axi_awaddr + axi_wburst_size ;                    

//AXI4 write cycle -----------------------------------------------

//axi_wstart_locked_r1, axi_wstart_locked_r2信号是用于时序同步

reg axi_wstart_locked_r1 = 1'b0, axi_wstart_locked_r2 = 1'b0;

always @(posedge M_AXI_ACLK)begin

    axi_wstart_locked_r1 <= axi_wstart_locked;

    axi_wstart_locked_r2 <= axi_wstart_locked_r1;

end

// axi_wstart_locked的作用代表一次axiburst操作正在进行中。

always @(posedge M_AXI_ACLK)

    if((fdma_wstart_locked == 1'b1) &&  axi_wstart_locked == 1'b0)

        axi_wstart_locked <= 1'b1;

    else if(axi_wlast == 1'b1 || fdma_wstart == 1'b1)

        axi_wstart_locked <= 1'b0;

         

//AXI4 addr valid and write addr-----------------------------------

always @(posedge M_AXI_ACLK)

     if((axi_wstart_locked_r1 == 1'b1) &&  axi_wstart_locked_r2 == 1'b0)

         axi_awvalid <= 1'b1;

     else if((axi_wstart_locked == 1'b1 && M_AXI_AWREADY == 1'b1)|| axi_wstart_locked == 1'b0)

         axi_awvalid <= 1'b0;      

//AXI4 write data---------------------------------------------------        

always @(posedge M_AXI_ACLK)

    if((axi_wstart_locked_r1 == 1'b1) &&  axi_wstart_locked_r2 == 1'b0)

        axi_wvalid <= 1'b1;

    else if(axi_wlast == 1'b1 || axi_wstart_locked == 1'b0)

        axi_wvalid <= 1'b0;//  

//AXI4 write data burst len counter----------------------------------

always @(posedge M_AXI_ACLK)

    if(axi_wstart_locked == 1'b0)

        wburst_cnt <= 'd0;

    else if(w_next)

        wburst_cnt <= wburst_cnt + 1'b1;    

             

assign axi_wlast = (w_next == 1'b1) && (wburst_cnt == M_AXI_AWLEN);

//fdma write data burst len counter----------------------------------

reg wburst_len_req = 1'b0;

reg [15:0] fdma_wleft_cnt =16'd0;

 

// wburst_len_req信号是自动管理每次axi需要burst的长度

always @(posedge M_AXI_ACLK)

        wburst_len_req <= fdma_wstart|axi_wlast;

 

// fdma_wleft_cnt用于记录一次FDMA剩余需要传输的数据数量  

always @(posedge M_AXI_ACLK)

    if( fdma_wstart )begin

        wfdma_cnt <= 1'd0;

        fdma_wleft_cnt <= fdma_wsize;

    end

    else if(w_next)begin

        wfdma_cnt <= wfdma_cnt + 1'b1;  

        fdma_wleft_cnt <= (fdma_wsize - 1'b1) - wfdma_cnt;

    end

//当最后一个数据的时候,产生fdma_wend信号代表本次fdma传输结束

assign  fdma_wend = w_next && (fdma_wleft_cnt == 1 );

//一次axi最大传输的长度是256因此当大于256,自动拆分多次传输

always @(posedge M_AXI_ACLK)begin

     if(wburst_len_req)begin

        if(fdma_wleft_cnt[15:8] >0)  wburst_len <= 256;

        else

            wburst_len <= fdma_wleft_cnt[7:0];

     end

     else wburst_len <= wburst_len;

end

以上代码我们进行了详细的注释性分析。以下给出FDMA写操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例,需要2AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。

4:FDMAAXI4-Master读操作

以下代码中我们给出axi4-master读操作的代码分析注释

//fdma axi read----------------------------------------------

reg     [M_AXI_ADDR_WIDTH-1 : 0]    axi_araddr =0   ; //AXI4 读地址

reg                         axi_arvalid  =1'b0; //AXI4读地有效

wire                        axi_rlast   ; //AXI4 LAST信号

reg                         axi_rready  = 1'b0;//AXI4读准备好

wire                              r_next      = (M_AXI_RVALID && M_AXI_RREADY);// valid ready信号都有效,代表AXI4数据传输有效

reg   [8 :0]                        rburst_len  = 1  ; //读传输的axi burst长度,代码会自动计算每次axi传输的burst 长度

reg   [8 :0]                        rburst_cnt  = 0  ; //每次axi bust的计数器

reg   [15:0]                       rfdma_cnt   = 0  ; //fdma的读数据计数器

reg                               axi_rstart_locked =0; //axi 传输进行中,lock住,用于时序控制

wire  [15:0] axi_rburst_size   =   rburst_len * AXI_BYTES; //axi 传输的地址长度计算  

 

assign M_AXI_ARID       = M_AXI_ID; //读地址ID,用来标志一组写信号, M_AXI_ID是通过参数接口定义

assign M_AXI_ARADDR     = axi_araddr;

assign M_AXI_ARLEN      = rburst_len - 1; //AXI4 burst的长度

assign M_AXI_ARSIZE     = clogb2((AXI_BYTES)-1);

assign M_AXI_ARBURST    = 2'b01; //AXI4busr类型INCR模式,地址递增

assign M_AXI_ARLOCK     = 1'b0; //不使用cache,不使用buffer

assign M_AXI_ARCACHE    = 4'b0010;

assign M_AXI_ARPROT     = 3'h0;

assign M_AXI_ARQOS      = 4'h0;

assign M_AXI_ARVALID    = axi_arvalid;

assign M_AXI_RREADY     = axi_rready&&fdma_rready; //读数据准备好,这里必须设置fdma_rready有效

assign fdma_rdata       = M_AXI_RDATA;    

assign fdma_rvalid      = r_next;    

 

//AXI4 FULL Read-----------------------------------------  

 

reg     fdma_rstart_locked = 1'b0;

wire    fdma_rend;

wire    fdma_rstart;

assign   fdma_rbusy = fdma_rstart_locked ;

//在整个读过程中fdma_rstart_locked将保持有效,直到本次FDMA写结束

always @(posedge M_AXI_ACLK)

    if(M_AXI_ARESETN == 1'b0 || fdma_rend == 1'b1)

        fdma_rstart_locked <= 1'b0;

    else if(fdma_rstart)

        fdma_rstart_locked <= 1'b1;                                

//产生fdma_rstart信号,整个信号保持1  M_AXI_ACLK时钟周期

assign fdma_rstart = (fdma_rstart_locked == 1'b0 && fdma_rareq == 1'b1);    

//AXI4 read burst lenth busrt addr ------------------------------

//fdma_rstart信号有效,代表一次新的FDMA传输,首先把地址本次fdmaburst地址寄存到axi_araddr作为第一次axi burst的地址。如果fdma的数据长度大于256,那么当axi_rlast有效的时候,自动计算下次axiburst地址

always @(posedge M_AXI_ACLK)

    if(fdma_rstart == 1'b1)    

        axi_araddr <= fdma_raddr;

    else if(axi_rlast == 1'b1)

        axi_araddr <= axi_araddr + axi_rburst_size ;                                                

//AXI4 r_cycle_flag-------------------------------------    

//axi_rstart_locked_r1, axi_rstart_locked_r2信号是用于时序同步

reg axi_rstart_locked_r1 = 1'b0, axi_rstart_locked_r2 = 1'b0;

always @(posedge M_AXI_ACLK)begin

    axi_rstart_locked_r1 <= axi_rstart_locked;

    axi_rstart_locked_r2 <= axi_rstart_locked_r1;

end

// axi_rstart_locked的作用代表一次axiburst操作正在进行中。

always @(posedge M_AXI_ACLK)

    if((fdma_rstart_locked == 1'b1) &&  axi_rstart_locked == 1'b0)

        axi_rstart_locked <= 1'b1;

    else if(axi_rlast == 1'b1 || fdma_rstart == 1'b1)

        axi_rstart_locked <= 1'b0;

//AXI4 addr valid and read addr-----------------------------------  

always @(posedge M_AXI_ACLK)

     if((axi_rstart_locked_r1 == 1'b1) &&  axi_rstart_locked_r2 == 1'b0)

         axi_arvalid <= 1'b1;

     else if((axi_rstart_locked == 1'b1 && M_AXI_ARREADY == 1'b1)|| axi_rstart_locked == 1'b0)

         axi_arvalid <= 1'b0;      

//AXI4 read data---------------------------------------------------    

always @(posedge M_AXI_ACLK)

    if((axi_rstart_locked_r1 == 1'b1) &&  axi_rstart_locked_r2 == 1'b0)

        axi_rready <= 1'b1;

    else if(axi_rlast == 1'b1 || axi_rstart_locked == 1'b0)

        axi_rready <= 1'b0;//  

//AXI4 read data burst len counter----------------------------------

always @(posedge M_AXI_ACLK)

    if(axi_rstart_locked == 1'b0)

        rburst_cnt <= 'd0;

    else if(r_next)

        rburst_cnt <= rburst_cnt + 1'b1;            

assign axi_rlast = (r_next == 1'b1) && (rburst_cnt == M_AXI_ARLEN);

//fdma read data burst len counter----------------------------------

reg rburst_len_req = 1'b0;

reg [15:0] fdma_rleft_cnt =16'd0;

// rburst_len_req信号是自动管理每次axi需要burst的长度  

always @(posedge M_AXI_ACLK)

        rburst_len_req <= fdma_rstart | axi_rlast;  

// fdma_rleft_cnt用于记录一次FDMA剩余需要传输的数据数量          

always @(posedge M_AXI_ACLK)

    if(fdma_rstart )begin

        rfdma_cnt <= 1'd0;

        fdma_rleft_cnt <= fdma_rsize;

    end

    else if(r_next)begin

        rfdma_cnt <= rfdma_cnt + 1'b1;  

        fdma_rleft_cnt <= (fdma_rsize - 1'b1) - rfdma_cnt;

    end

//当最后一个数据的时候,产生fdma_rend信号代表本次fdma传输结束

assign  fdma_rend = r_next && (fdma_rleft_cnt == 1 );

//axi auto burst len caculate-----------------------------------------

//一次axi最大传输的长度是256因此当大于256,自动拆分多次传输

always @(posedge M_AXI_ACLK)begin

     if(rburst_len_req)begin

        if(fdma_rleft_cnt[15:8] >0)  

            rburst_len <= 256;

        else

            rburst_len <= fdma_rleft_cnt[7:0];

     end

     else rburst_len <= rburst_len;

end

以上代码我们进行了详细的注释性分析。FDMA的读写代码高度对称,以上源码和以下波形图都和写操作类似,理解起会提高很多效率。

以下给出FDMA写操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例,需要2AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。

4FDMA IP的封装

我先讲解如何封装FDMA IP,之后再分析源码。封装IP少不了源码,这里是利用已经编写好的uiFDMA.v进行封装。

默认的源码路径在配套的工程uisrc/uifdma路径下

创建一个新的空的fpga工程

添加uiFDMA.v源码

创建IP

选择Package your current project

按住shift全选后,右击弹出菜单后选择Create Interface Definition

接口定义为slave,命名为FDMA

设置完成,uisrc/03_ip/uifdma路径下多出2个文件,这个两个文件就是定义了自定义的总线接口。

 

现在可以看到封装后的总线

建议把名字改简洁一些

可以看到封装好的接口,更加美观

 

5 saxi_full_mem IP

这个IP的源码可以基于axi-full-slave的模板简单修改就可以实现。找到以下路径,中saxi_full_v1_0_S00_AXI.v文件,并且对齐修改。

我们把修改后的代码命名为saxi_full_mem.v修改其中的部分代码,关键部分是memory部分定义。

修改的这部分代码支持Memory的任意长度设置(FPGA内部RAM会消耗资源),其中参数USER_NUM_MEM用于定义RAM的长度,我们一次FDMAburst长度应该小于等于USER_NUM_MEM这个参数。

我们来看下IP的接口参数设置:这里我们计划FDMA的读写长度是262,设置USER_NUM_MEM=300完全够用。

6创建FPGA图像化设计

设置IP路径

 

添加已经创建好的IP

输入关键词fdma,在最后可以看到,双击添加Ip

可以看到本文的FDMA版本升级到3.2版本, 解决3.1版本中,当总的burst长度是奇数的时候出现错误,修改端口命名规则,设置I代表了输入信号,O代表了输出信号

完成连线

继续添加剩余IP

设置IP参数

完成连线

设置地址分配:

7添加FDMA接口控制代码

 

添加完成后如下图:

fdma_axi_slave_test.v源码如下

`timescale 1ns / 1ps

module fdma_axi_slave_test(

  input sysclk

);

 

wire [31:0]   fdma_raddr;

reg           fdma_rareq;

wire          fdma_rbusy;

wire [127:0]   fdma_rdata;

wire [15:0]   fdma_rsize;

wire          fdma_rvalid;

wire [31:0]   fdma_waddr;

reg           fdma_wareq;

wire          fdma_wbusy;

wire [127:0]   fdma_wdata;

wire [15:0]   fdma_wsize;

wire          fdma_wvalid;

wire          ui_clk;

 

parameter TEST_MEM_SIZE   = 32'd4*20;

parameter FDMA_BURST_LEN  = 16'd262;

parameter ADDR_MEM_OFFSET = 0;

parameter ADDR_INC = 0;

   

parameter WRITE1 = 0;

parameter WRITE2 = 1;

parameter WAIT   = 2;

parameter READ1  = 3;

parameter READ2  = 4;

 

reg [127: 0] t_data;

reg [31: 0] fdma_waddr_r;

reg [2  :0] T_S = 0;

 

assign fdma_waddr = fdma_waddr_r + ADDR_MEM_OFFSET;

assign fdma_raddr = fdma_waddr;

 

assign fdma_wsize = FDMA_BURST_LEN;

assign fdma_rsize = FDMA_BURST_LEN;

assign fdma_wdata ={t_data,t_data,t_data,t_data};

   

   

//delay reset

reg [8:0] rst_cnt = 0;

always @(posedge ui_clk)

    if(rst_cnt[8] == 1'b0)

         rst_cnt <= rst_cnt + 1'b1;

     else

         rst_cnt <= rst_cnt;

 

always @(posedge ui_clk)begin

    if(rst_cnt[8] == 1'b0)begin

        T_S <=0;  

        fdma_wareq  <= 1'b0;

        fdma_rareq  <= 1'b0;

        t_data<=0;

        fdma_waddr_r <=0;      

    end

    else begin

        case(T_S)      

        WRITE1:begin

            if(fdma_waddr_r==TEST_MEM_SIZE) fdma_waddr_r<=0;

                if(!fdma_wbusy)begin

                    fdma_wareq  <= 1'b1;

                    t_data  <= 0;

                end

                if(fdma_wareq&&fdma_wbusy)begin

                    fdma_wareq  <= 1'b0;

                    T_S         <= WRITE2;

                end

        end

        WRITE2:begin

            if(!fdma_wbusy) begin

                 T_S <= WAIT;

                 t_data  <= 32'd0;

            end

            else if(fdma_wvalid) begin

                t_data <= t_data + 1'b1;

            end

        end

        WAIT:begin//not needed

            T_S <= READ1;

        end

        READ1:begin

            if(!fdma_rbusy)begin

                fdma_rareq  <= 1'b1;

                t_data   <= 0;

            end

            if(fdma_rareq&&fdma_rbusy)begin

                 fdma_rareq  <= 1'b0;

                 T_S         <= READ2;

            end

        end

        READ2:begin

            if(!fdma_rbusy) begin

                 T_S <= WRITE1;

                 t_data  <= 32'd0;

                 fdma_waddr_r  <= fdma_waddr_r + ADDR_INC;//128/8=16

            end

            else if(fdma_rvalid) begin

                t_data <= t_data + 1'b1;

            end

        end  

        default:

            T_S <= WRITE1;    

        endcase

    end

  end

   

wire test_error = (fdma_rvalid && (t_data[15:0] != fdma_rdata[15:0]));

   

 

  system system_i

       (.FDMA_S_0_i_fdma_raddr(fdma_raddr),

        .FDMA_S_0_i_fdma_rareq(fdma_rareq),

        .FDMA_S_0_o_fdma_rbusy(fdma_rbusy),

        .FDMA_S_0_o_fdma_rdata(fdma_rdata),

        .FDMA_S_0_i_fdma_rready(1'b1),

        .FDMA_S_0_i_fdma_rsize(fdma_rsize),

        .FDMA_S_0_o_fdma_rvalid(fdma_rvalid),

        .FDMA_S_0_i_fdma_waddr(fdma_waddr),

        .FDMA_S_0_i_fdma_wareq(fdma_wareq),

        .FDMA_S_0_o_fdma_wbusy(fdma_wbusy),

        .FDMA_S_0_i_fdma_wdata(fdma_wdata),

        .FDMA_S_0_i_fdma_wready(1'b1),

        .FDMA_S_0_i_fdma_wsize(fdma_wsize),

        .FDMA_S_0_o_fdma_wvalid(fdma_wvalid),    

        .sysclk(sysclk),

        .ui_clk(ui_clk)

        );        

 

endmodule

以上代码中调用的system.bd的图形代码接口。在状态机中,每次写262个长度32bit的数据,再读出来判断数据是否正确。

8仿真文件

添加仿真文件

添加完成后:

仿真文件非常简单,只要提供时钟激励就可以。

`timescale 1ns / 1ps

module fdma_axi_slave_test_tb();

 reg sysclk;

 fdma_axi_slave_test fdma_axi_slave_test_inst

(

    .sysclk(sysclk)

 );

initial begin

     sysclk  = 0;

     #100;

end

    always #10 sysclk = ~sysclk;  

endmodule

9实验结果

FDMA写操作仿真波形图,一次完成的FDMA写操作时序图如下:

这里一次wburst_len_req多产生一次,但是结果却不影响,大家可以思考下。如何设计出来和我们之前绘制的波形图一样。

一次FDMA写传输的起始时序

连续burst,自动管理burst长度,以及一次FDMA写传输结束时序

FDMA读操作仿真波形图,一次完成的FDMA读操作时序图如下:

这里一次rburst_len_req多产生一次,但是结果却不影响,大家可以思考下。如何设计出来和我们之前绘制的波形图一样。和写操作不同,可以看到读操作的等待较长时间后才获取到数据。

一次FDMA读传输的起始时序

连续burst,自动管理burst长度,以及一次FDMA读传输结束时序

另外放到后可以看到rvalid不是连续的,这个读者也可以自己去优化saxi_ful_mem ip让这IP支持连续的rvalid