请选择 进入手机版 | 继续访问电脑版
[X]关闭

2-3-09 使用fdma读写axi-bram

文档创建者:uisrc
浏览次数:600
最后更新:2023-12-30
文档课程分类
AMD: FPGA部分(2024样板资料) » 2_FPGA实验篇(仅旗舰) » 3-FPGA AXI 总线入门
软件版本:vitis2021.1(vivado2021.1)
操作系统:WIN10 64bit
硬件平台:适用XILINX A7/K7/Z7/ZU/KU系列FPGA
登录"米联客"FPGA社区-www.uisrc.com视频课程、答疑解惑!
1概述   
FDMA是米联客基于AXI4总线协议定制的一个DMA控制器。有了这个IP我们可以统一实现用FPGA代码直接读写PL的DDR或者ZYNQ/ZYNQMP SOC PS的DDR或者BRAM。在米联客的数据交互方案中,FDMA IP CORE 已经广泛应用于ZYNQ SOC/Artix7/Kintex7/ultrascale/ultrascale+系列FPGA/SOC。
如果用过ZYNQ/ZYNQMP SOC的都知道,要直接操作PS的DDR 通常是DMA 或者VDMA,然而用过XILINX 的DMA IP 和VDMA IP,总有一种遗憾,那就是不够灵活,还需要对寄存器配置,真是麻烦。XILINX 的总线接口是AXI4总线,自定义AXI4 IP挂到总线上就能实现对内存地址空间的读写访问。因此,我们只要掌握AXI4协议就能完成不管是PS还是PL DDR的读写操作。
本文实验目的:
1:掌握基于uiFDMA3.2的FPGA工程设计
2:利用uiFDMA3.2提供的接口,编写BRAM测试程序
3:对AXI-BRAM读写仿真和测试
我们第一入门的demo选择对BRAM仿真测试,是因为不管是仿真还是编译测试,对AXI-BRAM速度都非常快,我们在下面一个DEMO中会给出对DDR的读写仿真测试。
2系统框图
本系统中先将测试数据通过Milianke UiFDMA写入Bram,再通过Milianke UiFDMA将Bram中数据读出。将读写数据进行对比。通过在线逻辑分析仪抓取读写数据测试读写正确性。
2504661-20231230153953676-1628133675.jpg
3创建图形化逻辑工程1:创建工程命名为fpga_prj
双击VIVADO软件图标启动VIVADO
2504661-20231230153954084-980361983.jpg
设置工程路径,并且命名工程名为fpga_prj
2504661-20231230153954602-1118501954.jpg
2504661-20231230153955043-1726781608.jpg
以下设置FPGA或者ZYNQ或者ZYNQ-MPSOC芯片型号,必须和开发板保持一致,如果不清楚的请查阅自己开发板的硬件手册或者根据选型手册上参数确认
2504661-20231230153955499-1589696630.jpg
2504661-20231230153955918-1892596012.jpg
2504661-20231230153956350-1787093261.jpg
2:创建Block Design并且命名为system
2504661-20231230153956782-404660709.jpg
如下图所示,图形化system就是一个代码容器,接着我们要添加一些图像化的IP
2504661-20231230153957175-1158631374.jpg
3:添加图形化FPGA IP
首先设置自定义IP的路径,这里读者可以把我们配套工程根路径下的uisrc文件夹复制到目前的工程根路径,单击Settings在弹出的Settings窗口选择IP->Repository 设置如下路径
2504661-20231230153957580-2108679987.jpg
添加+号添加IP
2504661-20231230153957972-1428381401.jpg
比如输入关键词FDMA就可以搜索到我们米联客uiFDMA IP
2504661-20231230153958319-2115015244.jpg
2504661-20231230153958657-104863634.jpg
用类似的方法添加必要的IP如下图所示:
2504661-20231230153959019-1255190731.jpg
4:完成IP之间的信号自动
这种简单的工程可以先让软件先自动化线,然后根据结果手动一些调整
2504661-20231230153959454-1735786238.jpg

可以看到连线结果
2504661-20231230153959832-1965947555.jpg
5:调整IP参数5-1:BRAM参数设置
首先把IP的配置参数修改下,双击需要设置的IP可以进行参数设置
FDMA设置数据位宽128bit 可以访问内存地址位宽32bit,最大AXI 最大burst的程度为256
2504661-20231230154000231-109484797.jpg
BRAM设置,使用BRAM Controller 为真双口RAM
2504661-20231230154000645-280836235.jpg
2504661-20231230154001077-1993666080.jpg
2504661-20231230154001513-957837008.jpg
2504661-20231230154001945-2131168886.jpg
5-2:BRAM Controller参数设置
AXI BRAM Controller设置axi4协议,数据位宽128bit 读延迟1个时钟
2504661-20231230154002394-639667495.jpg
5-3:Clocking Wizard参数设置
2504661-20231230154002776-507255849.jpg
2504661-20231230154003248-1320124344.jpg
5-4:AXI Interconnect IP设置
双击AXI Interconnect IP 进行设置
2504661-20231230154003688-1848241848.jpg
设置AXI Interconnect IP的性能参数,其中Enable Register Slice 用于改善时序,Enable Data FIFO用于增加FIFO大小,增加数据传输效率
2504661-20231230154004054-1303565372.jpg
6:引出FPGA接口信号
分别右击下图2个IP,然后选择Make External,把需要引出到顶层的FPGA信号引出
2504661-20231230154004469-114547417.jpg
为了引出时钟需先右击信号PIN脚断开连接,然后Make External,之后重新连接
2504661-20231230154004840-345292199.jpg
2504661-20231230154005202-1015884960.jpg
修改后重新连接时钟
2504661-20231230154010627-1315202511.jpg
7:修改复位和信号名字
修改默认的时钟名字,以相同的方法修改其他信号
2504661-20231230154011071-1475641198.jpg
信号名修改后,修改复位脚的接线
2504661-20231230154011490-1212781543.jpg
修改完成后
2504661-20231230154011889-589176318.jpg
8:视图优化
右击空白处,弹出菜单选择Regenerate Layout优化下视图
2504661-20231230154012295-182563365.jpg
2504661-20231230154012690-574009141.jpg
9:地址分配
AXI总线必须分配地址,设置uiFDMA的地址空间分配,起始地址可以任意设置,我们设置从0x0000_0000开始,大小64KB
2504661-20231230154013312-1552168523.jpg
10:自动校验
保存工程
2504661-20231230154013694-937955775.jpg
11:校验图形逻辑工程
单击下图中的控件可以对图形化编程进行校验

2504661-20231230154014093-718851730.jpg
2504661-20231230154014434-1648411012.jpg
12:自动产生顶层文件
右击system,在弹出的菜单中选择Create HDL Wrapper
2504661-20231230154014820-274497697.jpg
2504661-20231230154015174-1695519062.jpg
2504661-20231230154015591-994590265.jpg
4编写FDMABRAM测试代码
刚刚以上自动产生的顶层文件,只是引出的信号接口,并不能完成对FDMA的控制,所以我们需要自定义一个顶层文件,可以复制刚才产生的文件,修改,这样可以省一些编写调用接口的时间。
为了方便文件的管理,我们新建一个fdma_bram_test.v文件,并且复制以上代码,到这个文件夹。
2504661-20231230154015925-1318204830.jpg
右击删除刚刚自动产生的文件
2504661-20231230154021329-1767719872.jpg
添加fdma_bram_test.v文件
2504661-20231230154026827-1870310953.jpg
2504661-20231230154027307-1416871897.jpg

fdma_bram_test.v文件
`timescale 1ns / 1ps
module fdma_bram_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 [0:0]    fdma_rstn;
wire          ui_clk;

parameter TEST_MEM_SIZE   = 32'd64*1024;//64KB
parameter FDMA_BURST_LEN  = 16'd512; //测试一次的长度
parameter ADDR_MEM_OFFSET = 0; //地址偏移量
parameter ADDR_INC = FDMA_BURST_LEN * 16;

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; //设置 FDMA 控制器一次写 burst 的数据长度
assign fdma_rsize = FDMA_BURST_LEN; //设置 FDMA 控制器一次读 burst 的数据长度
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;

//FDMA 读写控制器,每次先写后读,读出后对比数据正确性
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 进入空闲,fdma_wbusy=0,请求写
                    fdma_wareq  <= 1'b1; //设置写请求
                    t_data  <= 0; //设置初值
                end
                if(fdma_wareq&&fdma_wbusy)begin //当 fdma 响应请求后,fdma_wbusy=1,进入下一个状态
                    fdma_wareq  <= 1'b0; //清除写请求
                    T_S         <= WRITE2;
                end
        end
        WRITE2:begin
            if(!fdma_wbusy) begin //当 fdma 完成请求后,fdma_wbusy=0,进入下一个状态
                 T_S <= WAIT;
                 t_data  <= 32'd0;
            end
            else if(fdma_wvalid) begin //当 fdma_wvalid 有效期间必须写入有效数据
                t_data <= t_data + 1'b1;
            end
        end
        WAIT:begin//not needed
            T_S <= READ1;
        end
        READ1:begin
            if(!fdma_rbusy)begin //当 fdma 进入空闲,fdma_rbusy=0,请求读
                fdma_rareq  <= 1'b1; //设置读请求
                t_data   <= 0; //设置初值
            end
            if(fdma_rareq&&fdma_rbusy)begin //当 fdma 响应请求后,fdma_rbusy=1,进入下一个状态
                 fdma_rareq  <= 1'b0; //清除读请求
                 T_S         <= READ2;
            end
        end
        READ2:begin
            if(!fdma_rbusy) begin //当 fdma 完成请求后,fdma_rbusy=0,进入下一个状态
                 T_S <= WRITE1;
                 t_data  <= 32'd0;
                 fdma_waddr_r  <= fdma_waddr_r + ADDR_INC;//128/8=16 //当本次读写周期完成增加地址,地址以 BYTE 计算
            end
            else if(fdma_rvalid) begin //当 fdma_rvalid 有效期间读出的数据有效
                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 在线调试核的调用用于观察 fdma 运行的情况
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(sysclk),
        .ui_clk(ui_clk)
        );        
endmodule


5程序分析1:总流程图
如下图所示,本文的程序工作流程如下,包括请求写数据、FDMA收到写数据请求、写数据完成、请求读数据、FDMA收到读数据请求和读数据完成、校验。
2504661-20231230154027682-1554833083.jpg
2:FDMA的读写时序
以下是fdma_test.v中读写fdma ip接口的状态机,由于读写代码对称,对fdma的读写操作可以分为2步完成,分别是:1:发送读/写请求 2:读/写有效数据。 2504661-20231230154028056-1287590684.jpg
2-1:FDMA的写时序
2504661-20231230154028633-2078132619.jpg
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-2:FDMA的读时序
2504661-20231230154028996-1690566019.jpg
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总线协议的使用。
6RTL仿真1:仿真tb文件
编写仿真tb文件,fdma_bram_test_tb.v,非常简单,只需要给一个时钟
/*******************************MILIANKE*******************************
*Company : MiLianKe Electronic Technology Co., Ltd.
*Create Date: 2021/04/25
*Module Name:fdma_bram_test_tb
*File Name:fdma_bram_test_tb.v
*Description:
*The reference demo provided by Milianke is only used for learning.
*We cannot ensure that the demo itself is free of bugs, so users
*should be responsible for the technical problems and consequences
*caused by the use of their own products.
*Copyright: Copyright (c) MiLianKe
*All rights reserved.
*Revision: 1.0
*Signal description
*1) _i input
*2) _o output
*3) _n activ low
*4) _dg debug signal
*5) _r delay or register
*6) _s state mechine
*********************************************************************/
`timescale 1ns / 1ps
module fdma_bram_test_tb();
reg sysclk;
fdma_bram_test fdma_bram_test_inst
(
    .sysclk(sysclk)
);
initial begin
     sysclk  = 0;
     #100;
end
    always #10 sysclk = ~sysclk;  

endmodule


添加fdma_bram_test_tb.v到工程中
2504661-20231230154034430-589226159.jpg
2504661-20231230154034896-1152689439.jpg

2:仿真测试
进行RTL行为仿真
2504661-20231230154035338-1373801726.jpg
2504661-20231230154035802-118624363.jpg
放大后观察数据,我们可以看到test_error有毛刺,但是test_error打拍后,毛刺就没有了。
2504661-20231230154036268-267652529.jpg
FDMA 源码部分的分析,在前面课程中已经分析过,这里不再重复。
7编译测试
添加ila IP CORE 和fpga pin定义
2504661-20231230154036691-858560253.jpg
ila的设置如下
2504661-20231230154037081-542117720.jpg
2504661-20231230154037485-396117748.jpg
编译产生bit
2504661-20231230154037907-579253163.jpg
下载程序到开发板测试,通过在线逻辑分析仪观察
2504661-20231230154038385-553488893.jpg

2504661-20231230154038743-2058582858.jpg

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

本版积分规则