07AXI4-FULL-MASTER IP FDMA详解(AXI4总线实战)


软件版本:vitis2020.2(vivado2020.2)

操作系统:WIN10 64bit

硬件平台:适用XILINX A7/K7/Z7/ZU/KU系列FPGA(米联客(milianke)MZU07A-EG硬件开发平台)

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

7.1概述

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

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

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

米联客封装的AXI4总线协议命名位uiFDMA,自动2018年第一版本发布后,就引起了很多FPGA工程师的兴趣,并且得到了广大FPGA工程师的好评,但是FDMA1.0版本还是有一些局限和BUG,再实际的应用中被FPGA工程师发现,因此给了我们很多宝贵意见。借此2020版本教程更新发布之际,我们也对FDMA1.0版本升级到FDMA2.0版本。目前最新的版本是3.0版本,对2.0版本优化了自动burst的长度,提高了效率。

uiFDMA2.0/3.0新增特性:

1:支持多个FDMA IP同时挂带AXI-interconnect总线,同时工作

2:支持自动计算没错AXI-Burst长度,使用起来非常简单,只需要给出需要burst的长度。

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

本文实验目的:

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

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

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

7.2AXI总线协议介绍

7.2.1AXI总线概述

在XIINX FPGA的软件工具vivado以及相关IP中有支持三种AXI总线,拥有三种AXI接口,当然用的都是AXI协议。其中三种AXI总线分别为:

AXI4:(For high-performance memory-mapped requirements.)主要面向高性能地址映射通信的需求,是面向地址映射的接口,允许最大256轮的数据突发传输;

AXI4-Lite:(For simple, low-throughput memory-mapped communication )是一个轻量级的地址映射单次传输接口,占用很少的逻辑单元。

AXI4-Stream:(For high-speed streaming data.)面向高速流数据传输;去掉了地址项,允许无限制的数据突发传输规模。

由于AXI4和AXI4-Lite信号大部分一样,以下只介绍AXI4信号.另外对于AXI4-Stream协议不再本文中接收,后面有单独介绍的文章。

7.2.2AXI-4总线信号功能

1:时钟和复位

信号

方向

描述

ACLK  

时钟源  

全局时钟信号

ARESETn  

复位源

全局复位信号,低有效

 

写地址通道信号:

信号

方向

描述

AWID

主机to从机

写地址ID,用来标志一组写信号

AWADDR

主机to从机

写地址,给出一次写突发传输的写地址

AWLEN

主机to从机

AWLEN[7:0]决定写传输的突发长度。AXI3只支持1~16次的突发传输(Burst_length=AxLEN[3:0]+1),AXI4扩展突发长度支持INCR突发类型为1~256次传输,对于其他的传输类型依然保持1~16次突发传输(Burst_Length=AxLEN[7:0]+1)。

burst传输具有如下规则:

wraping burst ,burst长度必须是2,4,8,16

burst不能跨4KB边界

不支持提前终止burst传输

AWSIZE

主机to从机

写突发大小,给出每次突发传输的字节数支持1248163264128

AWBURST

主机to从机

突发类型:

2'b00 FIXED:突发传输过程中地址固定,用于FIFO访问

2'b01 INCR :增量突发,传输过程中,地址递增。增加量取决AxSIZE的值。

2'b10 WRAP:回环突发,和增量突发类似,但会在特定高地址的边界处回到低地址处。回环突发的长度只能是2,4,8,16次传输,传输首地址和每次传输的大小对齐。最低的地址整个传输的数据大小对齐。回环边界等于(AxSIZE*AxLEN

2'b11 Reserved

AWLOCK

主机to从机

总线锁信号,可提供操作的原子性

AWCACHE

主机to从机

内存类型,表明一次传输是怎样通过系统的

AWPROT

主机to从机

保护类型,表明一次传输的特权级及安全等级

AWQOS

主机to从机

质量服务QoS

AWREGION

主机to从机

区域标志,能实现单一物理接口对应的多个逻辑接口

AWUSER

主机to从机

用户自定义信号

AWVALID

主机to从机

有效信号,表明此通道的地址控制信号有效

AWREADY

从机to主机

表明""可以接收地址和对应的控制信号

2:写数据通道信号:

信号名    

方向    

描述      

WID

主机to从机

一次写传输的ID tag

WDATA

主机to从机

写数据

WSTRB

主机to从机

WSTRB[n:0]对应于对应的写字节,WSTRB[n]对应WDATA[8n+7:8n]WVALID为低时,WSTRB可以为任意值,WVALID为高时,WSTRB为高的字节线必须指示有效的数据。

WLAST

主机to从机

表明此次传输是最后一个突发传输

WUSER

主机to从机

用户自定义信号

WVALID

主机to从机

写有效,表明此次写有效

WREADY

从机to主机

表明从机可以接收写数据

写响应信号:

信号名    

方向    

描述      

BID

从机to主机

写响应ID tag

BRESP

从机to主机

写响应,表明写传输的状态

BUSER

从机to主机

用户自定义

BVALID

从机to主机

写响应有效

BREADY

主机to从机

表明主机能够接收写响应

3:读地址通道信号:

信号

方向

描述

ARID

主机to从机

读地址ID,用来标志一组写信号

ARADDR

主机to从机

读地址,给出一次读突发传输的读地址

ARLEN

主机to从机

ARLEN[7:0]决定读传输的突发长度。AXI3只支持1~16次的突发传输(Burst_length=AxLEN[3:0]+1),AXI4扩展突发长度支持INCR突发类型为1~256次传输,对于其他的传输类型依然保持1~16次突发传输(Burst_Length=AxLEN[7:0]+1)。

burst传输具有如下规则:

wraping burst ,burst长度必须是2,4,8,16

burst不能跨4KB边界

不支持提前终止burst传输

ARSIZE

主机to从机

读突发大小,给出每次突发传输的字节数支持1248163264128

ARBURST

主机to从机

突发类型:

2'b00 FIXED:突发传输过程中地址固定,用于FIFO访问

2'b01 INCR :增量突发,传输过程中,地址递增。增加量取决AxSIZE的值。

2'b10 WRAP:回环突发,和增量突发类似,但会在特定高地址的边界处回到低地址处。回环突发的长度只能是2,4,8,16次传输,传输首地址和每次传输的大小对齐。最低的地址整个传输的数据大小对齐。回环边界等于(AxSIZE*AxLEN

2'b11 Reserved

ARLOCK

主机to从机

总线锁信号,可提供操作的原子性

ARCACHE

主机to从机

内存类型,表明一次传输是怎样通过系统的

ARPROT

主机to从机

保护类型,表明一次传输的特权级及安全等级

ARQOS

主机to从机

质量服务QoS

ARREGION

主机to从机

区域标志,能实现单一物理接口对应的多个逻辑接口

ARUSER

主机to从机

用户自定义信号

ARVALID

主机to从机

有效信号,表明此通道的地址控制信号有效

ARREADY

从机to主机

表明""可以接收地址和对应的控制信号

4:读数据通道信号:

信号名    

方向    

描述      

RID

从机to主机

一次读传输的ID tag

RDATA

从机to主机

读数据

RRESP

从机to主机

读响应,表明读传输的状态

RLAST

从机to主机

表明此次传输是最后一个突发传输

RUSER

从机to主机

用户自定义信号

RVALID

从机to主机

写有效,表明此次写有效

RREADY

主机to从机

表明从机可以接收写数据

7.2.3数据有效的情况

AXI4所采用的是一种READY,VALID握手通信机制,简单来说主从双方进行数据通信前,有一个握手的过程。传输源产生VLAID信号来指明何时数据或控制信息有效。而目地源产生READY信号来指明已经准备好接受数据或控制信息。传输发生在VALID和READY信号同时为高的时候。VALID和READY信号的出现有三种关系。

  1. VALID先变高READY后变高。时序图如下:

    在箭头处信息传输发生。

  2. READY先变高VALID后变高。时序图如下:

    同样在箭头处信息传输发生。

  3. VALID和READY信号同时变高。时序图如下:

    在这种情况下,信息传输立马发生,如图箭头处指明信息传输发生。

    7.2.4突发式读写

    1:突发式写时序图

    这一过程的开始时,主机发送地址和控制信息到写地址通道中,然后主机发送每一个写数据到写数据通道中。当主机发送最后一个数据时,WLAST信号就变为高。当设备接收完所有数据之后他将一个写响应发送回主机来表明写事务完成。

    2:突发式读的时序图

    当地址出现在地址总线后,传输的数据将出现在读数据通道上。设备保持VALID为低直到读数据有效。为了表明一次突发式读写的完成,设备用RLAST信号来表示最后一个被传输的数据。

    7.3FDMA源码分析

    由于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_wvalid和fdma_wbusy变为0。

    AXI4总线最大的burst lenth是256,而经过封装后,用户接口的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_rvalid和fdma_rbusy变为0。

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

    3:FDMA的AXI4-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个长度的数据为例,需要2次AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。

    4:FDMA的AXI4-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个长度的数据为例,需要2次AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。

     

     

    7.4FDMA IP的封装

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

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

    创建一个新的空的fpga工程

    添加uiFDMA.v源码

    创建IP

    选择Package your current project

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

    接口定义为slave,命名为FDMA_S

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

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

    建议把名字改简洁一些

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

    7.5saxi_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的长度,我们一次FDMA的burst长度应该小于等于USER_NUM_MEM这个参数。

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

    7.6创建FPGA图像化设计

    设置IP路径

     

    添加已经创建好的IP

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

    可以看到本文的FDMA版本升级到3.0版本,相比2.0而言,优化了burst传输效率

    完成连线

    继续添加剩余IP

     

     

     

     

    设置IP参数

     

     


    完成连线

    设置地址分配:

    7.7添加FDMA接口控制代码

     

    添加完成后如下图:

    fdma_axi_slave_test.v源码如下

     

    `timescale 1ns / 1ps

    //////////////////////////////////////////////////////////////////////////////////

    /*

    Company : Liyang Milian Electronic Technology Co., Ltd.

    Brand: (milianke)

    Technical forum:uisrc.com

    taobao: https://milianke.taobao.com https://osrc.taobao.com

    jd:https://milianke.jd.com

    Create Date: 2021/04/25

    Module Name: fdma_axi_slave_test

    Description:

    Copyright: Copyright (c) milianke

    Revision: 1.0

    Signal description:

    1) _i input

    2) _o output

    3) _n activ lowpai

    4) _dg debug signal

    5) _r delay or register

    6) _s state mechine

    */

    //////////////////////////////////////////////////////////////////////////////////

     

    module fdma_axi_slave_test(

    input sysclk_p,

    input sysclk_n

    );

     

    wire [31:0] fdma_raddr;

    reg fdma_rareq;

    wire fdma_rbusy;

    wire [31:0] fdma_rdata;

    wire [15:0] fdma_rsize;

    wire fdma_rvalid;

    wire [31:0] fdma_waddr;

    reg fdma_wareq;

    wire fdma_wbusy;

    wire [31: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 [31: 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]));

     

    //ila_0 ila_dbg (

    //    .clk(ui_clk),

    //    .probe0({fdma_wdata[15:0],fdma_wareq,fdma_wvalid,fdma_wbusy}),

    //    .probe1({fdma_rdata[15:0],t_data[15:0],fdma_rvalid,fdma_rbusy,T_S,test_error})

    //);

     

    system system_i

    (.FDMA_S_0_fdma_raddr(fdma_raddr),

    .FDMA_S_0_fdma_rareq(fdma_rareq),

    .FDMA_S_0_fdma_rbusy(fdma_rbusy),

    .FDMA_S_0_fdma_rdata(fdma_rdata),

    .FDMA_S_0_fdma_rready(1'b1),

    .FDMA_S_0_fdma_rsize(fdma_rsize),

    .FDMA_S_0_fdma_rvalid(fdma_rvalid),

    .FDMA_S_0_fdma_waddr(fdma_waddr),

    .FDMA_S_0_fdma_wareq(fdma_wareq),

    .FDMA_S_0_fdma_wbusy(fdma_wbusy),

    .FDMA_S_0_fdma_wdata(fdma_wdata),

    .FDMA_S_0_fdma_wready(1'b1),

    .FDMA_S_0_fdma_wsize(fdma_wsize),

    .FDMA_S_0_fdma_wvalid(fdma_wvalid),

    .sysclk_clk_n(sysclk_n),

    .sysclk_clk_p(sysclk_p),

    .ui_clk(ui_clk)

    );

     

    endmodule

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

    7.8仿真文件

    添加仿真文件

    添加完成后:

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

    module fdma_axi_slave_test_tb();

    reg sysclk_p;

    fdma_axi_slave_test fdma_axi_slave_test_inst

    (

    .sysclk_p(sysclk_p),

    .sysclk_n(~sysclk_p)

    );

     

    initial begin

    sysclk_p = 0;

    #100;

    end

     

    always #10 sysclk_p = ~sysclk_p;

    endmodule

     

     

     

    7.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


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM