本帖最后由 FPGA课程 于 2024-10-8 09:08 编辑
软件版本: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概述前文方案中,通过米联客自定义的AXI4-FDMA IP 可以把PS的DDR当作PL的DDR使用,这不仅仅可以省区PL DDR也可以节省很多PL资源。 本文方案中,通过米联客自定义的uifdmadbuf(数据缓存)IP 结合FDMA IP完成PL 数据发送到PS的应用方案 本文实验目的: 1:掌握uifdmadbuf ip的使用,以及分析uifdmadbuf的源码 2:基于米联客的uifdmadbuf和axi4-fdma完成从PL发送数据到PS DDR 2系统框图本系统中,测试数据通过FDMA_DBUF ip 写入到AXI-FDMA IP之后通过AXI4总线发送都PS DDR.和官方的DMA IP使用方法不同的是,对于数据在DDR中的起始地址设置、DDR内存中开辟的缓存数量、数据包大小都是FDMA_DBUF中设置,FDMA_DBUF可以自主发起数据的传输,控制内存地址的切换,以及中断的产生。PS获取数据的地址,获取数据的时机都是被动完成。
详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“3-1-01米联客2024版ZynqSocSDK入门篇”中第一个工程 “01Vitis Soc开发入门”这个实验。 本文开始不再详细描述图形化工程的搭建过程,对于VIVIADO软件工具使用可以参考前面的相关章节。 3搭建SOC系统工程
3.1Zynq IP PS部分设置本文中的PS设置内容是新增加的配置部分,关于DDR、MIO、CPU时钟等设置请参考“3-1-01米联客2024版ZynqSocSDK入门篇”中第一个工程 “01Vitis Soc开发入门”这个实验。 1:PS复位设置
2:设置 PS GT Master 接口和 HP Slave 接口
3:设置PL到PS的中断Interurptsà勾选 Fabric Interrupt,勾选IRQ_F2P[15:0]。
4:设置PL的时钟勾选FCLK_CLK0,设置为100M AXI-FDMA到AXI4总线的数据时钟 勾选FCLK_CLK1,设置50M 用户数据时钟接到ui_dbuf IP 本系统需要2个时钟完成数据方案传输, FCLK_CLK0大于FCLK_CLK1是为了满足AXI Stream部分的数据吞吐能力要大于用户写数据速度。
5:ZYNQ IP设置完成后
3.2添加IP首先设置好自定义IP的路径
单击添加IP按钮“+”,输入如下模块IP名字的关键词,并双击添加
其中ui_fdmabuf ip是米联客自定义IP用于和米联客fdma ip进行数据通信。用同样的方法添加axi-interconnect ip 以及 GPIO IP. 1:uifdma_dbuf参数设置
2:uifdma参数设置
3:axi-gpio参数设置
3.3PL图形化编程用户可以根据自己的习惯搭建以下工程,演示的方法为了让初学者看到过程,但不是效率最高,最优化的方法。
3.4添加测试数据代码新建顶层文件system_wrapper_top.v,并且添加代码,我们先看下添加好的代码和层次结构
可以看到system_wrapper_top.v中调用了刚才设计的图形化FPGA代码,具体源码如下: - /*******************************MILIANKE*******************************
- *Company : MiLianKe Electronic Technology Co., Ltd.
- *WebSite:https://www.milianke.com
- *TechWeb:https://www.uisrc.com
- *tmall-shop:https://milianke.tmall.com
- *jd-shop:https://milianke.jd.com
- *taobao-shop1: https://milianke.taobao.com
- *Create Date: 2021/10/15
- *File Name: system_wrapper.v
- *Description:
- *Declaration:
- *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 1 ns / 1 ns
- module system_wrapper
- (
- inout [14:0]DDR_addr,
- inout [2:0]DDR_ba,
- inout DDR_cas_n,
- inout DDR_ck_n,
- inout DDR_ck_p,
- inout DDR_cke,
- inout DDR_cs_n,
- inout [3:0]DDR_dm,
- inout [31:0]DDR_dq,
- inout [3:0]DDR_dqs_n,
- inout [3:0]DDR_dqs_p,
- inout DDR_odt,
- inout DDR_ras_n,
- inout DDR_reset_n,
- inout DDR_we_n,
- inout FIXED_IO_ddr_vrn,
- inout FIXED_IO_ddr_vrp,
- inout [53:0]FIXED_IO_mio,
- inout FIXED_IO_ps_clk,
- inout FIXED_IO_ps_porb,
- inout FIXED_IO_ps_srstb
- );
- wire pl_clk;
- wire user_rstn;
- wire user_start;
- wire [31:0]ud_wdata_0;
- wire ud_wde_0;
- wire ud_wvs_0;
- wire ud_wclk_0;
- reg [15:0]test_data;
- always@(posedge pl_clk)begin
- if(user_rstn == 1'b0)begin
- test_data <= 12'd0;
- end
- else if(ud_wde_0) begin // test_data为加计数器产生测试数据
- test_data <= test_data + 1'b1;
- end
- end
- assign ud_wdata_0 = {test_data,test_data};
- assign ud_wde_0 = user_start;
- assign ud_wclk_0 = pl_clk;
- assign ud_wvs_0 = user_start;
- ila_0 ila0_dg
- (
- .clk(pl_clk),
- .probe0({test_data[15:0],ud_wde_0,user_start,user_rstn})
- );
-
- system system_i
- (
- .DDR_addr(DDR_addr),
- .DDR_ba(DDR_ba),
- .DDR_cas_n(DDR_cas_n),
- .DDR_ck_n(DDR_ck_n),
- .DDR_ck_p(DDR_ck_p),
- .DDR_cke(DDR_cke),
- .DDR_cs_n(DDR_cs_n),
- .DDR_dm(DDR_dm),
- .DDR_dq(DDR_dq),
- .DDR_dqs_n(DDR_dqs_n),
- .DDR_dqs_p(DDR_dqs_p),
- .DDR_odt(DDR_odt),
- .DDR_ras_n(DDR_ras_n),
- .DDR_reset_n(DDR_reset_n),
- .DDR_we_n(DDR_we_n),
- .FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
- .FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
- .FIXED_IO_mio(FIXED_IO_mio),
- .FIXED_IO_ps_clk(FIXED_IO_ps_clk),
- .FIXED_IO_ps_porb(FIXED_IO_ps_porb),
- .FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
- .ud_wclk_0(ud_wclk_0),
- .ud_wdata_0(ud_wdata_0),
- .ud_wde_0(ud_wde_0),
- .ud_wvs_0(ud_wvs_0),
- .user_rstn(user_rstn),
- .user_start(user_start),
- .pl_clk(pl_clk)
- );
- endmodule
复制代码
以上代码中除了阴影部分,其他代码可以通过vivado自动产生图形化接口的顶层文件,然后复制该文件,修改而来。
3.5FDMA-DBUF IP代码分析FDMA-DBUF IP代码采用“对称设计”方法,读写代码对称,好处是代码结构清晰,读写过程一致,代码效率高,更加容易维护。本文只用到了写通道部分,读通道部分没有用到,但是我们还是介绍下。 关于更多AXI4总线相关知识可以阅读“3-2-02_axi_bus.pdf” 这个章节,这个章节专讲AXI4总线,其中也详细讲解了FDMA的代码分析。 1:FDMA-DBUF写状态机为了配合AXI-FDMA IP发送数据到PS,我们写了一个uifdmadbuf ip,通过这个IP把用户编写的数据时序,转为AXI-FMDA接口数据流。该IP支持视频格式的帧同步,每一帧都进行同步,也支持没有帧同步的数据流方式传输。
2: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总线协议的使用。 3:FDMA-DBUF写状态机读数据的过程和写数据的过程是对称的,状态机如下:
为了配合AXI-FDMA IP发送数据到PS,我们写了一个uifdmadbuf ip,通过这个IP把用户编写的数据时序,转为AXI-FMDA接口数据流。该IP支持视频格式的帧同步,每一帧都进行同步,也支持没有帧同步的数据流方式传输。 4: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总线协议的使用。 5:源码注释
米联客自定义IP路径在配套的soc_prj/uisrc/03_ip路径下,如下图所示:
代码的层次结构如下:
源码部分一共有包括4个文件:
fs_cap.v该文件在“03PL发数据到PS方案(DMA)”一文中有描述,这里不再重复介绍其功能。
uidbufirq.v文件用来保存一帧数据发送完毕后产生的中断,ps部分可以通过axi-lite接口读取中断值知道哪一个地址完成数据传输。
uidbuf.v文件是完成用户数据到FDMA接口数据转换的关键代码,同时该代码完成了中断控制,帧缓存控制。
uifdma_dbuf.v该文件是该IP的顶层文件
5-1:uidbuf.v注释
- /*******************************MILIANKE*******************************
- *Company : MiLianKe Electronic Technology Co., Ltd.
- *WebSite:https://www.milianke.com
- *TechWeb:https://www.uisrc.com
- *tmall-shop:https://milianke.tmall.com
- *jd-shop:https://milianke.jd.com
- *taobao-shop1: https://milianke.taobao.com
- *Create Date: 2021/10/15
- *File Name: uidbuf.v
- *Description:
- *Declaration:
- *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 uidbuf#(
- parameter integer VIDEO_ENABLE = 1,//使能视频帧支持功能
- parameter integer ENABLE_WRITE = 1,//使能写通道
- parameter integer ENABLE_READ = 1,//使能读通道
- parameter integer AXI_DATA_WIDTH = 128,//AXI总线数据位宽
- parameter integer AXI_ADDR_WIDTH = 32, //AXI总线地址位宽
- parameter integer W_BUFDEPTH = 2048, //写通道AXI设置FIFO缓存大小
- parameter integer W_DATAWIDTH = 32, //写通道AXI设置数据位宽大小
- parameter [AXI_ADDR_WIDTH -1'b1: 0] W_BASEADDR = 0, //写通道设置内存起始地址
- parameter integer W_DSIZEBITS = 24, //写通道设置缓存数据的增量地址大小,用于FDMA DBUF 计算帧缓存起始地址
- parameter integer W_XSIZE = 1920, //写通道设置X方向的数据大小,代表了每次FDMA 传输的数据长度
- parameter integer W_XSTRIDE = 1920, //写通道设置X方向的Stride值,主要用于图形缓存应用
- parameter integer W_YSIZE = 1080, //写通道设置Y方向值,代表了进行了多少次XSIZE传输
- parameter integer W_XDIV = 2, //写通道对X方向数据拆分为XDIV次传输,减少FIFO的使用
- parameter integer W_BUFSIZE = 3, //写通道设置帧缓存大小,目前最大支持128帧,可以修改参数支持更缓存数
- parameter integer R_BUFDEPTH = 2048, //读通道AXI设置FIFO缓存大小
- parameter integer R_DATAWIDTH = 32, //读通道AXI设置数据位宽大小
- parameter [AXI_ADDR_WIDTH -1'b1: 0] R_BASEADDR = 0, //读通道设置内存起始地址
- parameter integer R_DSIZEBITS = 24, //读通道设置缓存数据的增量地址大小,用于FDMA DBUF 计算帧缓存起始地址
- parameter integer R_XSIZE = 1920, //读通道设置X方向的数据大小,代表了每次FDMA 传输的数据长度
- parameter integer R_XSTRIDE = 1920, //读通道设置X方向的Stride值,主要用于图形缓存应用
- parameter integer R_YSIZE = 1080, //读通道设置Y方向值,代表了进行了多少次XSIZE传输
- parameter integer R_XDIV = 2, //读通道对X方向数据拆分为XDIV次传输,减少FIFO的使用
- parameter integer R_BUFSIZE = 3 //读通道设置帧缓存大小,目前最大支持128帧,可以修改参数支持更缓存数
- )
- (
- input wire ui_clk, //和FDMA AXI总线时钟一致
- input wire ui_rstn, //和FDMA AXI复位一致
- //sensor input -W_FIFO--------------
- input wire W_wclk_i, //用户写数据接口时钟
- input wire W_FS_i, //用户写数据接口同步信号,对于非视频帧一般设置为1
- input wire W_wren_i, //用户写数据使能
- input wire [W_DATAWIDTH-1'b1 : 0] W_data_i, //用户写数据
- output reg [7 :0] W_sync_cnt_o =0, //写通道BUF帧同步输出
- input wire [7 :0] W_buf_i, // 写通道BUF帧同步输入
- //----------fdma signals write-------
- output wire [AXI_ADDR_WIDTH-1'b1: 0] fdma_waddr, //FDMA写通道地址
- output wire fdma_wareq, //FDMA写通道请求
- output wire [15 :0] fdma_wsize, //FDMA写通道一次FDMA的传输大小
- input wire fdma_wbusy, //FDMA处于BUSY状态,AXI总线正在写操作
- output wire [AXI_DATA_WIDTH-1'b1:0] fdma_wdata, //FDMA写数据
- input wire fdma_wvalid, //FDMA 写有效
- output wire fdma_wready, //FDMA写准备好,用户可以写数据
- output reg [7 :0] fmda_wbuf =0, //FDMA的写帧缓存号输出
- output wire fdma_wirq, //FDMA一次写完成的数据传输完成后,产生中断。
- //----------fdma signals read-------
- input wire R_rclk_i, //用户读数据接口时钟
- input wire R_FS_i, //用户读数据接口同步信号,对于非视频帧一般设置1
- input wire R_rden_i, //用户读数据使能
- output wire [R_DATAWIDTH-1'b1 : 0] R_data_o, //用户读数据
- output reg [7 :0] R_sync_cnt_o =0, //读通道BUF帧同步输出
- input wire [7 :0] R_buf_i, //写通道BUF帧同步输入
- output wire [AXI_ADDR_WIDTH-1'b1: 0] fdma_raddr, // FDMA读通道地址
- output wire fdma_rareq, // FDMA读通道请求
- output wire [15: 0] fdma_rsize, // FDMA读通道一次FDMA的传输大小
- input wire fdma_rbusy, // FDMA处于BUSY状态,AXI总线正在读操作
- input wire [AXI_DATA_WIDTH-1'b1:0] fdma_rdata, // FDMA读数据
- input wire fdma_rvalid, // FDMA 读有效
- output wire fdma_rready, // FDMA读准备好,用户可以写数据
- output reg [7 :0] fmda_rbuf =0, // FDMA的读帧缓存号输出
- output wire fdma_rirq // FDMA一次读完成的数据传输完成后,产生中断
- );
- // 计算Log2
- function integer clog2;
- input integer value;
- begin
- value = value-1;
- for (clog2=0; value>0; clog2=clog2+1)
- value = value>>1;
- end
- endfunction
- //FDMA读写状态机的状态值,一般4个状态值即可
- localparam S_IDLE = 2'd0;
- localparam S_RST = 2'd1;
- localparam S_DATA1 = 2'd2;
- localparam S_DATA2 = 2'd3;
- // 通过设置通道使能,可以优化代码的利用率
- generate if(ENABLE_WRITE == 1)begin : FDMA_WRITE_ENABLE
- localparam WFIFO_DEPTH = W_BUFDEPTH; //写通道FIFO深度
- localparam W_WR_DATA_COUNT_WIDTH = clog2(WFIFO_DEPTH)+1; //计算FIFO的写通道位宽
- localparam W_RD_DATA_COUNT_WIDTH = clog2(WFIFO_DEPTH*W_DATAWIDTH/AXI_DATA_WIDTH)+1;//clog2(WFIFO_DEPTH/(AXI_DATA_WIDTH/W_DATAWIDTH))+1;
- localparam WYBUF_SIZE = (W_BUFSIZE - 1'b1); //写通道需要完成多少次XSIZE操作
- localparam WY_BURST_TIMES = (W_YSIZE*W_XDIV); //写通道需要完成的FDMA burst 操作次数,XDIV用于把XSIZE分解多次传输
- localparam FDMA_WX_BURST = (W_XSIZE*W_DATAWIDTH/AXI_DATA_WIDTH)/W_XDIV; //FDMA BURST 一次的大小
- localparam WX_BURST_ADDR_INC = (W_XSIZE*(W_DATAWIDTH/8))/W_XDIV; //FDMA每次burst之后的地址增加
- localparam WX_LAST_ADDR_INC = (W_XSTRIDE-W_XSIZE)*(W_DATAWIDTH/8) + WX_BURST_ADDR_INC; //根据stride值计算出来最后一次地址
- (*mark_debug = "true"*) (* KEEP = "TRUE" *) wire W_wren_ri = W_wren_i;
- assign fdma_wready = 1'b1;
- reg fdma_wareq_r= 1'b0;
- reg W_FIFO_Rst=0;
- (*mark_debug = "true"*) (* KEEP = "TRUE" *)wire W_FS;
- (*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [1 :0] W_MS=0;
- reg [W_DSIZEBITS-1'b1:0] W_addr=0;
- (*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [15:0] W_bcnt=0;
- (*mark_debug = "true"*) (* KEEP = "TRUE" *)wire[W_RD_DATA_COUNT_WIDTH-1'b1 :0] W_rcnt;
- (*mark_debug = "true"*) (* KEEP = "TRUE" *)reg W_REQ=0;
- (*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [5 :0] wirq_dly_cnt =0;
- reg [3 :0] wdiv_cnt =0;
- reg [7 :0] wrst_cnt =0;
- reg [7 :0] fmda_wbufn;
- (*mark_debug = "true"*) (* KEEP = "TRUE" *) wire wirq= fdma_wirq;
- assign fdma_wsize = FDMA_WX_BURST;
- assign fdma_wirq = (wirq_dly_cnt>0);
- assign fdma_waddr = W_BASEADDR + {fmda_wbufn,W_addr};//由于FPGA逻辑做乘法比较复杂,因此通过设置高位地址实现缓存设置
- reg [1:0] W_MS_r =0;
- always @(posedge ui_clk) W_MS_r <= W_MS;
- //每次FDMA DBUF 完成一帧数据传输后,产生中断,这个中断持续60个周期的uiclk,这里的延迟必须足够ZYNQ IP核识别到这个中断
- always @(posedge ui_clk) begin
- if(ui_rstn == 1'b0)begin
- wirq_dly_cnt <= 6'd0;
- fmda_wbuf <=0;
- end
- else if((W_MS_r == S_DATA2) && (W_MS == S_IDLE))begin
- wirq_dly_cnt <= 60;
- fmda_wbuf <= fmda_wbufn;
- end
- else if(wirq_dly_cnt >0)
- wirq_dly_cnt <= wirq_dly_cnt - 1'b1;
- end
- //帧同步,对于视频有效
- fs_cap #
- (
- .VIDEO_ENABLE(VIDEO_ENABLE)
- )
- fs_cap_W0
- (
- .clk_i(ui_clk),
- .rstn_i(ui_rstn),
- .vs_i(W_FS_i),
- .fs_cap_o(W_FS)
- );
- assign fdma_wareq = fdma_wareq_r;
- //写通道状态机,采用4个状态值描述
- always @(posedge ui_clk) begin
- if(!ui_rstn)begin
- W_MS <= S_IDLE;
- W_FIFO_Rst <= 0;
- W_addr <= 0;
- W_sync_cnt_o <= 0;
- W_bcnt <= 0;
- wrst_cnt <= 0;
- wdiv_cnt <= 0;
- fmda_wbufn <= 0;
- fdma_wareq_r <= 1'd0;
- end
- else begin
- case(W_MS)
- S_IDLE:begin
- W_addr <= 0;
- W_bcnt <= 0;
- wrst_cnt <= 0;
- wdiv_cnt <=0;
- if(W_FS) begin //帧同步,对于非视频数据一般常量为1
- W_MS <= S_RST;
- if(W_sync_cnt_o < WYBUF_SIZE) //输出帧同步计数器
- W_sync_cnt_o <= W_sync_cnt_o + 1'b1;
- else
- W_sync_cnt_o <= 0;
- end
- end
- S_RST:begin//帧同步,对于非视频数据直接跳过,对于视频数据,会同步每一帧,并且复位数据FIFO
- fmda_wbufn <= W_buf_i;
- wrst_cnt <= wrst_cnt + 1'b1;
- if((VIDEO_ENABLE == 1) && (wrst_cnt < 40))
- W_FIFO_Rst <= 1;
- else if((VIDEO_ENABLE == 1) && (wrst_cnt < 100))
- W_FIFO_Rst <= 0;
- else if(fdma_wirq == 1'b0) begin
- W_MS <= S_DATA1;
- end
- end
- S_DATA1:begin //发送写FDMA请求
- if(fdma_wbusy == 1'b0 && W_REQ )begin
- fdma_wareq_r <= 1'b1;
- end
- else if(fdma_wbusy == 1'b1) begin
- fdma_wareq_r <= 1'b0;
- W_MS <= S_DATA2;
- end
- end
- S_DATA2:begin //写有效数据
- if(fdma_wbusy == 1'b0)begin
- if(W_bcnt == WY_BURST_TIMES - 1'b1) //判断是否传输完毕
- W_MS <= S_IDLE;
- else begin
- if(wdiv_cnt < W_XDIV - 1'b1)begin//如果对XSIZE做了分次传输,一个XSIZE也需要XDIV次FDMA完成传输
- W_addr <= W_addr + WX_BURST_ADDR_INC; //计算地址增量
- wdiv_cnt <= wdiv_cnt + 1'b1;
- end
- else begin
- W_addr <= W_addr + WX_LAST_ADDR_INC; //计算最后一次地址增量,最后一次地址根据stride 计算
- wdiv_cnt <= 0;
- end
- W_bcnt <= W_bcnt + 1'b1;
- W_MS <= S_DATA1;
- end
- end
- end
- default: W_MS <= S_IDLE;
- endcase
- end
- end
- //写通道的数据FIFO,采用了原语调用xpm_fifo_async fifo,当FIFO存储的数据阈值达到一定量,一般满足一次FDMA的burst即可发出请求
- wire W_rbusy;
- always@(posedge ui_clk)
- W_REQ <= (W_rcnt > FDMA_WX_BURST - 2)&&(~W_rbusy);
-
- xpm_fifo_async # (
- .FIFO_MEMORY_TYPE ("auto"), //string; "auto", "block", or "distributed";
- .ECC_MODE ("no_ecc"), //string; "no_ecc" or "en_ecc";
- .RELATED_CLOCKS (0), //positive integer; 0 or 1
- .FIFO_WRITE_DEPTH (WFIFO_DEPTH), //positive integer
- .WRITE_DATA_WIDTH (W_DATAWIDTH), //positive integer
- .WR_DATA_COUNT_WIDTH (W_WR_DATA_COUNT_WIDTH), //positive integer
- .PROG_FULL_THRESH (20), //positive integer
- .FULL_RESET_VALUE (0), //positive integer; 0 or 1
- .USE_ADV_FEATURES ("0707"), //string; "0000" to "1F1F";
- .READ_MODE ("fwft"), //string; "std" or "fwft";
- .FIFO_READ_LATENCY (0), //positive integer;
- .READ_DATA_WIDTH (AXI_DATA_WIDTH), //positive integer
- .RD_DATA_COUNT_WIDTH (W_RD_DATA_COUNT_WIDTH), //positive integer
- .PROG_EMPTY_THRESH (10), //positive integer
- .DOUT_RESET_VALUE ("0"), //string
- .CDC_SYNC_STAGES (2), //positive integer
- .WAKEUP_TIME (0) //positive integer; 0 or 2;
- ) xpm_fifo_W_inst (
- .rst ((ui_rstn == 1'b0) || (W_FIFO_Rst == 1'b1)),
- .wr_clk (W_wclk_i),
- .wr_en (W_wren_i),
- .din (W_data_i),
- .full (),
- .overflow (),
- .prog_full (),
- .wr_data_count (),
- .almost_full (),
- .wr_ack (),
- .wr_rst_busy (),
- .rd_clk (ui_clk),
- .rd_en (fdma_wvalid),
- .dout (fdma_wdata),
- .empty (),
- .underflow (),
- .rd_rst_busy (W_rbusy),
- .prog_empty (),
- .rd_data_count (W_rcnt),
- .almost_empty (),
- .data_valid (W_dvalid),
- .sleep (1'b0),
- .injectsbiterr (1'b0),
- .injectdbiterr (1'b0),
- .sbiterr (),
- .dbiterr ()
- );
- end
- else begin : FDMA_WRITE_DISABLE
- //----------fdma signals write-------
- assign fdma_waddr = 0;
- assign fdma_wareq = 0;
- assign fdma_wsize = 0;
- assign fdma_wdata = 0;
- assign fdma_wready = 0;
- assign fdma_wirq = 0;
- end
- endgenerate
- generate if(ENABLE_READ == 1)begin : FDMA_READ// 通过设置通道使能,可以优化代码的利用率
- localparam RYBUF_SIZE = (R_BUFSIZE - 1'b1); //读通道需要完成多少次XSIZE操作
- localparam RY_BURST_TIMES = (R_YSIZE*R_XDIV); //读通道需要完成的FDMA burst 操作次数,XDIV用于把XSIZE分解多次传输
- localparam FDMA_RX_BURST = (R_XSIZE*R_DATAWIDTH/AXI_DATA_WIDTH)/R_XDIV; //FDMA BURST 一次的大小
- localparam RX_BURST_ADDR_INC = (R_XSIZE*(R_DATAWIDTH/8))/R_XDIV; //FDMA每次burst之后的地址增加
- localparam RX_LAST_ADDR_INC = (R_XSTRIDE-R_XSIZE)*(R_DATAWIDTH/8) + RX_BURST_ADDR_INC; //根据stride值计算出来最后一次地址
- localparam RFIFO_DEPTH = R_BUFDEPTH*R_DATAWIDTH/AXI_DATA_WIDTH;//R_BUFDEPTH/(AXI_DATA_WIDTH/R_DATAWIDTH);
- localparam R_WR_DATA_COUNT_WIDTH = clog2(RFIFO_DEPTH)+1; //读通道FIFO 输入部分深度
- localparam R_RD_DATA_COUNT_WIDTH = clog2(R_BUFDEPTH)+1; //写通道FIFO输出部分深度
- assign fdma_rready = 1'b1;
- reg fdma_rareq_r= 1'b0;
- reg R_FIFO_Rst=0;
- wire R_FS;
- reg [1 :0] R_MS=0;
- reg [R_DSIZEBITS-1'b1:0] R_addr=0;
- reg [15:0] R_bcnt=0;
- wire[R_WR_DATA_COUNT_WIDTH-1'b1 :0] R_wcnt;
- reg R_REQ=0;
- reg [5 :0] rirq_dly_cnt =0;
- reg [3 :0] rdiv_cnt =0;
- reg [7 :0] rrst_cnt =0;
- reg [7 :0] fmda_rbufn;
- assign fdma_rsize = FDMA_RX_BURST;
- assign fdma_rirq = (rirq_dly_cnt>0);
- assign fdma_raddr = R_BASEADDR + {fmda_rbufn,R_addr};//由于FPGA逻辑做乘法比较复杂,因此通过设置高位地址实现缓存设置
- reg [1:0] R_MS_r =0;
- always @(posedge ui_clk) R_MS_r <= R_MS;
- //每次FDMA DBUF 完成一帧数据传输后,产生中断,这个中断持续60个周期的uiclk,这里的延迟必须足够ZYNQ IP核识别到这个中断
- always @(posedge ui_clk) begin
- if(ui_rstn == 1'b0)begin
- rirq_dly_cnt <= 6'd0;
- fmda_rbuf <=0;
- end
- else if((R_MS_r == S_DATA2) && (R_MS == S_IDLE))begin
- rirq_dly_cnt <= 60;
- fmda_rbuf <= fmda_rbufn;
- end
- else if(rirq_dly_cnt >0)
- rirq_dly_cnt <= rirq_dly_cnt - 1'b1;
- end
- //帧同步,对于视频有效
- fs_cap #
- (
- .VIDEO_ENABLE(VIDEO_ENABLE)
- )
- fs_cap_R0
- (
- .clk_i(ui_clk),
- .rstn_i(ui_rstn),
- .vs_i(R_FS_i),
- .fs_cap_o(R_FS)
- );
- assign fdma_rareq = fdma_rareq_r;
- //读通道状态机,采用4个状态值描述
- always @(posedge ui_clk) begin
- if(!ui_rstn)begin
- R_MS <= S_IDLE;
- R_FIFO_Rst <= 0;
- R_addr <= 0;
- R_sync_cnt_o <= 0;
- R_bcnt <= 0;
- rrst_cnt <= 0;
- rdiv_cnt <= 0;
- fmda_rbufn <= 0;
- fdma_rareq_r <= 1'd0;
- end
- else begin
- case(R_MS) //帧同步,对于非视频数据一般常量为1
- S_IDLE:begin
- R_addr <= 0;
- R_bcnt <= 0;
- rrst_cnt <= 0;
- rdiv_cnt <=0;
- if(R_FS) begin
- R_MS <= S_RST;
- if(R_sync_cnt_o < RYBUF_SIZE) //输出帧同步计数器,当需要用读通道做帧同步的时候使用
- R_sync_cnt_o <= R_sync_cnt_o + 1'b1;
- else
- R_sync_cnt_o <= 0;
- end
- end
- S_RST:begin//帧同步,对于非视频数据直接跳过,对于视频数据,会同步每一帧,并且复位数据FIFO
- fmda_rbufn <= R_buf_i;
- rrst_cnt <= rrst_cnt + 1'b1;
- if((VIDEO_ENABLE == 1) && (rrst_cnt < 40))
- R_FIFO_Rst <= 1;
- else if((VIDEO_ENABLE == 1) && (rrst_cnt < 100))
- R_FIFO_Rst <= 0;
- else if(fdma_rirq == 1'b0) begin
- R_MS <= S_DATA1;
- end
- end
- S_DATA1:begin
- if(fdma_rbusy == 1'b0 && R_REQ)begin
- fdma_rareq_r <= 1'b1;
- end
- else if(fdma_rbusy == 1'b1) begin
- fdma_rareq_r <= 1'b0;
- R_MS <= S_DATA2;
- end
- end
- S_DATA2:begin //写有效数据
- if(fdma_rbusy == 1'b0)begin
- if(R_bcnt == RY_BURST_TIMES - 1'b1) //判断是否传输完毕
- R_MS <= S_IDLE;
- else begin
- if(rdiv_cnt < R_XDIV - 1'b1)begin//如果对XSIZE做了分次传输,一个XSIZE也需要XDIV次FDMA完成传输
- R_addr <= R_addr + RX_BURST_ADDR_INC; //计算地址增量
- rdiv_cnt <= rdiv_cnt + 1'b1;
- end
- else begin
- R_addr <= R_addr + RX_LAST_ADDR_INC; //计算最后一次地址增量,最后一次地址根据stride 计算
- rdiv_cnt <= 0;
- end
- R_bcnt <= R_bcnt + 1'b1;
- R_MS <= S_DATA1;
- end
- end
- end
- default:R_MS <= S_IDLE;
- endcase
- end
- end
- //写通道的数据FIFO,采用了原语调用xpm_fifo_async fifo,当FIFO存储的数据阈值达到一定量,一般满足一次FDMA的burst即可发出请求
- wire R_wbusy;
- always@(posedge ui_clk)
- R_REQ <= (R_wcnt < FDMA_RX_BURST - 2)&&(~R_wbusy);
- xpm_fifo_async # (
- .FIFO_MEMORY_TYPE ("auto"), //string; "auto", "block", or "distributed";
- .ECC_MODE ("no_ecc"), //string; "no_ecc" or "en_ecc";
- .RELATED_CLOCKS (0), //positive integer; 0 or 1
- .FIFO_WRITE_DEPTH (RFIFO_DEPTH), //positive integer
- .WRITE_DATA_WIDTH (AXI_DATA_WIDTH), //positive integer
- .WR_DATA_COUNT_WIDTH (R_WR_DATA_COUNT_WIDTH), //positive integer
- .PROG_FULL_THRESH (20), //positive integer
- .FULL_RESET_VALUE (0), //positive integer; 0 or 1
- .USE_ADV_FEATURES ("0707"), //string; "0000" to "1F1F";
- .READ_MODE ("fwft"), //string; "std" or "fwft";
- .FIFO_READ_LATENCY (0), //positive integer;
- .READ_DATA_WIDTH (R_DATAWIDTH), //positive integer
- .RD_DATA_COUNT_WIDTH (R_RD_DATA_COUNT_WIDTH), //positive integer
- .PROG_EMPTY_THRESH (10), //positive integer
- .DOUT_RESET_VALUE ("0"), //string
- .CDC_SYNC_STAGES (2), //positive integer
- .WAKEUP_TIME (0) //positive integer; 0 or 2;
- ) xpm_fifo_R_inst (
- .rst ((ui_rstn == 1'b0) || (W_FIFO_Rst == 1'b1)),
- .wr_clk (ui_clk),
- .wr_en (fdma_rvalid),
- .din (fdma_rdata),
- .full (),
- .overflow (),
- .prog_full (),
- .wr_data_count (R_wcnt),
- .almost_full (),
- .wr_ack (),
- .wr_rst_busy (R_wbusy),
- .rd_clk (R_rclk_i),
- .rd_en (R_rden_i),
- .dout (R_data_o),
- .empty (),
- .underflow (),
- .rd_rst_busy (),
- .prog_empty (),
- .rd_data_count (),
- .almost_empty (),
- .data_valid (),
- .sleep (1'b0),
- .injectsbiterr (1'b0),
- .injectdbiterr (1'b0),
- .sbiterr (),
- .dbiterr ()
- );
- end
- else begin : FDMA_READ_DISABLE
-
- assign fdma_raddr = 0;
- assign fdma_rareq = 0;
- assign fdma_rsize = 0;
- assign fdma_rdata = 0;
- assign fdma_rready = 0;
- assign fdma_rirq = 0;
- end
- endgenerate
- endmodule
复制代码
5-2:uidbufirq.v注释
这个部分的功能只有在PS需要获取中断后的帧缓存通道才会用到,PS通过axi-lite接口可以读取到寄存器的值。 关于更多AXI4总线相关知识可以阅读“3-2-02_axi_bus.pdf” 这个章节,这个章节专讲AXI4总线,其中也详细讲解了FDMA的代码分析。 这里主要看代码的最后,根据中断信号寄存内存的中断号 - // 当写中断产生的时候,寄存当前的帧缓存号到fdma_wbfu_irq寄存器
- always @(posedge S_AXI_ACLK) fdma_wirq_r <= fdma_wirq;
- always @(posedge S_AXI_ACLK)begin
- if( S_AXI_ARESETN == 1'b0)
- fdma_wbuf_irq <= 0;
- else if(fdma_wirq_r == 1'b0 & fdma_wirq == 1'b1)
- fdma_wbuf_irq <= fdma_wbuf;
- end
- // 当读中断产生的时候,寄存当前的帧缓存号到fdma_wbfu_irq寄存器
- always @(posedge S_AXI_ACLK) fdma_rirq_r <= fdma_rirq;
- always @(posedge S_AXI_ACLK)begin
- if( S_AXI_ARESETN == 1'b0)
- fdma_rbuf_irq <= 0;
- else if(fdma_rirq_r == 1'b0 & fdma_rirq == 1'b1)
- fdma_rbuf_irq <= fdma_rbuf;
- end
- // User logic ends
- endmodule
复制代码
3.6地址空间分配
3.7编译并导出平台文件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平台。
4搭建Vitis-sdk工程创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。 4.1创建SDK Platform工程
4.2创建axi_fdma_pl2ps APP工程
5程序分析
5.1总流程图本程序通过控制AXI-GPIO控制FPGA工程数据接口的复位,以及启动,停止。当启动FPGA数据发送后,每1MB数据都会产生一次中断,当中断到了后,我们设计了一个环形Buf用于记录哪一个缓存已经有数据了,并且做好标记。在fdma_dbuf_test()函数中,通过读取已经标记的缓存区,读取有效数据。我们这里还设计了把1MB分成16KB来处理。这种处理方式后面的demo方案中会具体应用到数据采集中。
5.2环形buf设计
在本demo中采用了米联客定义的环形buf数据结构,源码和定义如下:
- typedef struct fdma_buf_st
- {
- u8 record[16];//reg buffer
- u8 circle_cnt;//环形buf的计数器
- u8 next;//指向下一个应该处理的数据包缓存
- u8 pkg_done_cnt; //记录已经接收到的包
- u16 fram_cnt; //记录帧数量
- u16 pkg_cnt; //记录拆包后已经处理的包
- }fdma_buf_st;
- //在中断函数中记录缓存号,这里通过读取FDMA_DBUF的寄存器获取
- void PS_RX_intr_Handler(void *param)
- {
- fdma_buf.record[fdma_buf.circle_cnt]= Xil_In32((UINTPTR)FDMA_DBUF_BASE_ADDR);
- fdma_buf.pkg_done_cnt++;
- if(fdma_buf.circle_cnt<2)
- fdma_buf.circle_cnt ++ ;
- else
- fdma_buf.circle_cnt = 0;
- }
复制代码
5.3GPIO的初始化通过控制AXI-GPIO来控制复位和启动停止代码简单方便 - void gpio_init(void)
- {
- XGpio_Initialize(&gpio_user_rstn, XPAR_GPIO_USER_RSTN_DEVICE_ID);
- XGpio_SetDataDirection(&gpio_user_rstn, 1, 0x0);
- XGpio_DiscreteWrite(&gpio_user_rstn,1,0x0);//set gpio reset=0 reset user logic
- XGpio_Initialize(&gpio_user_start, XPAR_GPIO_USER_START_DEVICE_ID);
- XGpio_SetDataDirection(&gpio_user_start, 1, 0x0);//set gpio user_start=0 stop transmission
- XGpio_DiscreteWrite(&gpio_user_rstn,1,0x1);//set gpio reset=1 reset done
- }
复制代码
5.4启动传输控制- void fdma_wr_set(u32 set)
- {
- if(set==0)
- XGpio_DiscreteWrite(&gpio_user_start, 1, 0x0);//start dma
- else
- XGpio_DiscreteWrite(&gpio_user_start, 1, 0x1);//start dma
- }
复制代码
5.5地址参数定义该部分定义必须和fdma_dbuf 中的参数保持一致 - #define UIFDMA_DBUF_WBASEADDR 0x08000000
- #define UIFDMA_DBUF_BUFSIZE 0x100000 //2^20
- #define UIFDMA_DBUF_WBUF_DEPTH 2048
- #define UIFDMA_DBUF_WDATAWITH 32
- #define UIFDMA_DBUF_WXSIZE 2048
- #define UIFDMA_DBUF_WSTRIDE 2048
- #define UIFDMA_DBUF_WYSIZE 128
- #define RX_BUFFER0_BASE (UIFDMA_DBUF_WBASEADDR + UIFDMA_DBUF_BUFSIZE*0)
- #define RX_BUFFER1_BASE (UIFDMA_DBUF_WBASEADDR + UIFDMA_DBUF_BUFSIZE*1)
- #define RX_BUFFER2_BASE (UIFDMA_DBUF_WBASEADDR + UIFDMA_DBUF_BUFSIZE*2)
- #define FDMA_BUFSIZE UIFDMA_DBUF_WDATAWITH/8*UIFDMA_DBUF_WXSIZE*UIFDMA_DBUF_WYSIZE
复制代码
再看下FDMA DBUF IP设置,这里只用到写通道
5.6读取数据只要fdma_buf.pkg_done_cnt>0代表中断后有缓存已经准备好了数据,下面的代表把一包数据拆分为64份,每份大小16KB.通过fdma_buf.record[fdma_buf.next]知道下一个需要读取的数据缓存地址 - void fdma_dbuf_test(void)
- {
- RxBufAddr[0] =RX_BUFFER0_BASE;
- RxBufAddr[1] =RX_BUFFER1_BASE;
- RxBufAddr[2] =RX_BUFFER2_BASE;
- while(1)
- {
- if(first_trans_start==0)
- {
- first_trans_start =1;
- fdma_wr_set(1);
- fdma_buf.circle_cnt=0;
- fdma_buf.next=0;
- fdma_buf.pkg_done_cnt=0;
- fdma_buf.pkg_cnt=0;
- fdma_buf.fram_cnt=0;
- }
- if(fdma_buf.pkg_done_cnt> 0) // total_data_size divide in to 64 times as each time 16kbytes
- {
- if(fdma_buf.pkg_cnt==0)
- {
- BufStartPtr = (u32*)(RxBufAddr[fdma_buf.record[fdma_buf.next]]);//get new buffer address
- Xil_DCacheFlushRange((INTPTR) BufStartPtr, FDMA_BUFSIZE) ;
- }
- fdma_buf.pkg_cnt++;
- if(fdma_buf.pkg_cnt==64)
- {
- fdma_buf.pkg_done_cnt--;
- fdma_buf.pkg_cnt = 0;
- fdma_buf.fram_cnt++;
- if(fdma_buf.next<2)
- fdma_buf.next++;
- else
- fdma_buf.next=0;
- }
- }
- }
- }
复制代码
6方案演示
6.1硬件准备本实验需要用到 JTAG 下载器、USB 转串口外设,另外需要把核心板上的 2P 模式开关设置到 JTAG 模式,即 ON ON (注意新版本的 MLK-H3-CZ08-7100FC(米联客 7X 系列),支持 JTAG 模式,对于老版本的核心板,JTAG 调试 的时候一定要拔掉 TF 卡,并且设置模式开关为 OFF OFF) 以下图片中,TF 卡没有使用到
6.2实验结果右击工程,选择调试
添加3个缓存的起始地址,用于观察数据
设置代码调试断点
运行到断点处
通过JTAG观察到的内存数据和我们FPGA端发送的测试数据完全一致
FPGA端通过ila在线逻辑分析仪查看的测试数据
|