米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳
对于做图像处理的兄弟来说,图像缓存是基本操作,一般是图像三帧缓存于DDR3,然后再读出显示,DDR3操作很复杂,所以Xilinx官方出了个MIG 的IP核供开发者使用,但对于像我这样的little_white来说,操作MIG 的用户接口还是不方便,所以又有了挂载AXI4总线的AXI4_MIG,这下不就简单了,直接操作AXI不就完了吗?不再需要关心底层怎么搞了。
基于此,米联客搞了一个叫做FDMA的东西,实质就是一个AXI4_FULL的主机总线,即使有了FDMA还不行,还得有控制图像读写发热控制器,米联客都给出了源码,也给了文档,但无奈,米联客的代码虽然写得精简漂亮,但文档写得确实一般,加之代码的有些变量命名也不太恰当,使得像我这样的little_white有些云雾缭绕
所以我对FDMA及其控制器的源码进行了优化,现在逐行解读:
FDMA部分:

module uiFDMA#(parameter  integer        C_M_AXI_BURST_LEN    = 64 , //AXI的一次读写突发长度parameter  integer        C_M_AXI_ID_WIDTH    = 1  ,parameter  integer        C_M_AXI_ID         = 0  ,parameter  integer        C_M_AXI_ADDR_WIDTH = 32 , parameter  integer        C_M_AXI_DATA_WIDTH = 32  //AXI的数据位宽                 )(//user logic input   wire                               pkg_wr_areq         , //FDMS包写请求,一个时钟脉冲        output  wire                               pkg_wr_last         ,    //FDMS包写数据结尾input   wire [C_M_AXI_DATA_WIDTH-1 :0]     pkg_wr_data         ,    //FDMS包写数据包output   wire                               pkg_wr_en           ,    //指示FDMS正在写数据,高有效input   wire [C_M_AXI_ADDR_WIDTH-1 :0]     pkg_wr_addr         ,    //FDMS包写数据包地址input   wire [C_M_AXI_ADDR_WIDTH-1 :0]     pkg_wr_size         ,   //FDMS包写数据包长度,应为//C_M_AXI_BURST_LEN的整数倍 input   wire                               pkg_rd_areq         ,     output  wire                               pkg_rd_last          ,output  wire [C_M_AXI_DATA_WIDTH-1 :0]     pkg_rd_data         ,   output  wire                               pkg_rd_en           ,    input   wire [C_M_AXI_ADDR_WIDTH-1 :0]     pkg_rd_addr          ,   input   wire [C_M_AXI_ADDR_WIDTH-1 :0]     pkg_rd_size         ,            //input     wire                            INIT_AXI_TXN        ,input  wire                                M_AXI_ACLK          ,input  wire                                M_AXI_ARESETN       ,output     wire [C_M_AXI_ID_WIDTH-1 : 0]       M_AXI_AWID          ,    output     wire [C_M_AXI_ADDR_WIDTH-1 : 0]     M_AXI_AWADDR        ,    output     wire [7 : 0]                        M_AXI_AWLEN         ,    output     wire [2 : 0]                        M_AXI_AWSIZE        ,    output     wire [1 : 0]                        M_AXI_AWBURST       ,    output     wire                                M_AXI_AWLOCK        ,    output     wire [3 : 0]                        M_AXI_AWCACHE       ,    output     wire [2 : 0]                        M_AXI_AWPROT        ,    output     wire [3 : 0]                        M_AXI_AWQOS         ,     output    wire                                M_AXI_AWVALID       ,    input  wire                                M_AXI_AWREADY       ,    output  wire [C_M_AXI_DATA_WIDTH-1 : 0]    M_AXI_WDATA         ,    output  wire [C_M_AXI_DATA_WIDTH/8-1 : 0]  M_AXI_WSTRB         ,    output  wire                               M_AXI_WLAST         ,               output  wire                                M_AXI_WVALID        ,    input   wire                               M_AXI_WREADY        ,    input   wire [C_M_AXI_ID_WIDTH-1 : 0]      M_AXI_BID           ,       input   wire [1 : 0]                        M_AXI_BRESP         ,       input   wire                                M_AXI_BVALID        ,   output  wire                                M_AXI_BREADY        ,    output  wire [C_M_AXI_ID_WIDTH-1 : 0]      M_AXI_ARID          ,    output  wire [C_M_AXI_ADDR_WIDTH-1 : 0]    M_AXI_ARADDR        ,       output  wire [7 : 0]                        M_AXI_ARLEN         ,    output  wire [2 : 0]                       M_AXI_ARSIZE        ,    output  wire [1 : 0]                       M_AXI_ARBURST       ,    output  wire                               M_AXI_ARLOCK        ,    output  wire [3 : 0]                       M_AXI_ARCACHE       ,    output  wire [2 : 0]                       M_AXI_ARPROT        ,    output  wire [3 : 0]                       M_AXI_ARQOS         ,          output  wire                                 M_AXI_ARVALID       ,    input   wire                               M_AXI_ARREADY       ,    input   wire [C_M_AXI_ID_WIDTH-1 : 0]      M_AXI_RID           ,    input   wire [C_M_AXI_DATA_WIDTH-1 : 0]    M_AXI_RDATA         ,    input   wire [1 : 0]                       M_AXI_RRESP         ,    input   wire                               M_AXI_RLAST         ,    input   wire                               M_AXI_RVALID        ,    output  wire                               M_AXI_RREADY             );function integer clogb2 (input integer bit_depth);              begin                                                           for(clogb2=0; bit_depth>0; clogb2=clogb2+1)                   bit_depth = bit_depth >> 1;                                 end                                                           endfunction                                                     localparam integer C_TRANSACTIONS_NUM = clogb2(C_M_AXI_BURST_LEN-1);   localparam integer BURST_SIZE = C_M_AXI_BURST_LEN * C_M_AXI_DATA_WIDTH/8;  //一包FDMA数据包所占的字节数(或理解为一包数据的总地址)// AXI4LITE signals//AXI4 internal temp signals
//writereg  [C_M_AXI_ADDR_WIDTH-1 : 0]  axi_awaddr  ;   //写地址reg                                axi_awvalid ;   //写地址有效wire     [C_M_AXI_DATA_WIDTH-1 : 0]  axi_wdata   ;   //写数据reg                                axi_wlast   ;   //写数据结尾reg                                  axi_wvalid  ;   //写数据有效
//read  reg     [C_M_AXI_ADDR_WIDTH-1 : 0]  axi_araddr  ;   //读地址reg                                axi_arvalid ;   //读地址有效reg                                  axi_rready  ;   //读数据准备//   wire [C_TRANSACTIONS_NUM+2 : 0]    burst_size_bytes;   assign M_AXI_AWID       = C_M_AXI_ID;assign M_AXI_AWADDR       = axi_awaddr;assign M_AXI_AWLEN        = C_M_AXI_BURST_LEN - 1;assign M_AXI_AWSIZE        = clogb2((C_M_AXI_DATA_WIDTH/8)-1);assign M_AXI_AWBURST    = 2'b01;assign M_AXI_AWLOCK       = 1'b0;assign M_AXI_AWCACHE   = 4'b0010;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     = {(C_M_AXI_DATA_WIDTH/8){1'b1}}; //每个字节都选通,都有效assign M_AXI_WLAST      = axi_wlast;assign M_AXI_WVALID        = axi_wvalid;assign M_AXI_BREADY       = axi_bready;assign M_AXI_ARID     = C_M_AXI_ID;assign M_AXI_ARADDR       = axi_araddr;assign M_AXI_ARLEN        = C_M_AXI_BURST_LEN - 1;assign M_AXI_ARSIZE        = clogb2((C_M_AXI_DATA_WIDTH/8)-1);assign M_AXI_ARBURST    = 2'b01;assign M_AXI_ARLOCK       = 1'b0;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;reg [7 :0 ]                      w_axi4_cnt   ; reg [C_M_AXI_ADDR_WIDTH-1 : 0]   WR_BASE_ADDR  ;reg [C_M_AXI_ADDR_WIDTH-1 : 0]   RD_BASE_ADDR  ;reg [C_M_AXI_ADDR_WIDTH-1 : 0]   w_fdma_cnt   ;reg [C_M_AXI_ADDR_WIDTH-1 : 0]   r_fdma_cnt   ;  reg                              w_axi4_flag  ;reg                              r_axi4_addr_flag ;reg                              r_axi4_data_flag;wire w_next = (axi_wvalid && M_AXI_WREADY);wire r_next = (M_AXI_RVALID && axi_rready);assign pkg_wr_en      =   w_next;                                 //写一包FDMAMA数据标志 assign pkg_wr_last    =  (w_next && w_fdma_cnt==pkg_wr_size-1); //写一包FDMAMA数据结尾 assign axi_wdata      =  pkg_wr_data;assign pkg_rd_en      =  r_next;                              //读一包FDMAMA数据标志    assign pkg_rd_last    =  (r_next && r_fdma_cnt==pkg_rd_size-1); //读一包FDMAMA数据结尾  assign pkg_rd_data    =  M_AXI_RDATA;//----------------------------------------------------------------------------
//AXI4 FULL Write
//AXI4 data is ready for axi master write to slave  reg w_fdma_flag;    //这里原来不叫这个名字,由于命名不直观,我改了
//标志fdma传输过程
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) w_fdma_flag <= 1'b0;else if(w_fdma_flag==1'b0 && pkg_wr_areq== 1'b1) w_fdma_flag <= 1'b1;                                   else if(pkg_wr_last == 1'b1) w_fdma_flag <= 1'b0;
end     //AXI4 write burst lenth busrt addr --------------------------------------------
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) axi_awaddr <= 'd0;else if(w_fdma_flag==1'b0 && pkg_wr_areq) axi_awaddr <= pkg_wr_addr;  //FDMA给axi_awaddr赋初值else if(axi_awvalid == 1'b1 && M_AXI_AWREADY == 1'b1) axi_awaddr <= axi_awaddr + BURST_SIZE ;  //AXI4地址总线准备好后,再增加一包FDMA地址
end                 //AXI4 write cycle flag---AXI4写周期标志
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) w_axi4_flag <= 1'b0;else if(w_axi4_flag == 1'b0 && w_fdma_flag) w_axi4_flag <= 1'b1;       //与w_fdma_flag同步拉高                            else if(w_axi4_flag == 1'b1 && axi_wlast == 1'b1) w_axi4_flag <= 1'b0; //拉的条件是axi_wlast
end//AXI4 write addr valid---------------------------------------------
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) axi_awvalid<= 1'b0;else if(w_axi4_flag == 1'b1 && M_AXI_AWREADY==1'b1) axi_awvalid <= 1'b0;else if(w_axi4_flag  == 1'b0 && w_fdma_flag == 1'b1) axi_awvalid <= 1'b1;
end//AXI4 write data---------------------------------------------------
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN  == 1'b0) axi_wvalid <= 1'b0;else if(w_axi4_flag  == 1'b0 && w_fdma_flag == 1'b1) axi_wvalid <= 1'b1;    //FDMA请求拉,axi_wvalid就拉,提前于M_AXI_WREADYelse if(w_axi4_flag == 1'b1 && axi_wlast == 1'b1) axi_wvalid <= 1'b0;
end//AXI4 写数据计数器,在M_AXI_ACLK下,axi4数据总线每次写128bit数据
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) w_axi4_cnt <= 'd0;else if(w_axi4_cnt==C_M_AXI_BURST_LEN) w_axi4_cnt <= 'd0; //写完256个128bit的数据时else if(w_next) w_axi4_cnt <= w_axi4_cnt + 1'b1;            else w_axi4_cnt <= w_axi4_cnt ;
end//FDMA 写数据计数器,在M_AXI_ACLK下,axi4数据总线每次写128bit数据,FDMA的pkg_wr_size是C_M_AXI_BURST_LEN的整数倍:256 256x2。。。
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) w_fdma_cnt <= 'd0;else if(w_next && w_fdma_cnt== pkg_wr_size-1) w_fdma_cnt <= 'd0;else if(w_next) w_fdma_cnt <= w_fdma_cnt + 1'b1;    else w_fdma_cnt <= w_fdma_cnt;
end//AXI4 write data last data-----------------------------------------
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) axi_wlast <= 1'b0;else if(w_axi4_cnt == M_AXI_AWLEN-1) axi_wlast <= 1'b1;else axi_wlast <= 1'b0;
end
assign  axi_bready = 1'b1;            //----------------------------------------------------------------------------
//AXI4 FULL Read-----------------------------------------
reg r_fdma_flag;always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) r_fdma_flag <= 1'b0;else if(r_fdma_flag==1'b0 && pkg_rd_areq==1'b1) r_fdma_flag <= 1'b1;                                    else if(pkg_rd_last == 1'b1) r_fdma_flag <= 1'b0;
end//AXI4 read addr read addr burst-------------------------
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) axi_araddr <='d0;else if(r_fdma_flag==1'b0 && pkg_rd_areq==1'b1) axi_araddr <= pkg_rd_addr;                      //axi_araddr赋初值else if(axi_arvalid == 1'b1 && M_AXI_ARREADY == 1'b1) axi_araddr <= axi_araddr + BURST_SIZE;  //axi_araddr+FDMA_addr
end//AXI4 r_axi4_addr_flag----瞬时拉信号
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 0) r_axi4_addr_flag <= 1'b0;else if(r_fdma_flag &&  M_AXI_ARREADY && axi_arvalid ) r_axi4_addr_flag <= 1'b0; //此时地址总线跳变,表明下一包数据即将开始else if(r_fdma_flag && r_axi4_data_flag == 1'b0 ) r_axi4_addr_flag <= 1'b1;
end//AXI4 read addr valid----瞬时拉信号
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) axi_arvalid <= 1'b0;else if(r_axi4_addr_flag && axi_arvalid && M_AXI_ARREADY ) axi_arvalid <= 1'b0;else if(r_axi4_addr_flag && axi_arvalid == 1'b0) axi_arvalid <= 1'b1;
end//AXI4 r_axi4_data_flag ready for read data----------------
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) beginr_axi4_data_flag <= 1'b0;axi_rready     <= 1'b0;     //这行原本是缺少的,我加上了endelse if(r_axi4_addr_flag && axi_arvalid && M_AXI_ARREADY)beginr_axi4_data_flag <= 1'b1;axi_rready     <= 1'b1;endelse if(r_axi4_data_flag && r_next && M_AXI_RLAST)beginr_axi4_data_flag <= 1'b0;axi_rready     <= 1'b0;end
end//AXI4 data user counter for frame space lenth-----------
always @(posedge M_AXI_ACLK) beginif(M_AXI_ARESETN == 1'b0) r_fdma_cnt <= 'd0;else if(r_next && r_fdma_cnt== pkg_rd_size-1) r_fdma_cnt <= 'd0;else if(r_next) r_fdma_cnt <= r_fdma_cnt + 1'b1;    else r_fdma_cnt <= r_fdma_cnt;
end endmodule

再来FDMA控制器部分,这是重点:
在此之前,先来说说三帧缓存的架构,图就不画了,可以自行脑补:
一帧图像的像素时钟是不定的,分辨率不同则像素时钟不同,DDR3的IP的用户时钟也是不同的,所以输入图像进入DDR3之前肯定是要做跨时钟域处理的,实质就是把输入图像的像素时钟转换到DDR3的操作时钟下来,这样DDR3才能吞吐图像嘛。。。那肯定就用FIFO咯。。。
把图像写入DDR3的FIFO叫做写FIFO,记作W0_FIFO,W0_FIFO自身带有读写端口
W0_FIFO的写端口对接输入图像,W0_FIFO的读端口对接FDMA的写端口,即对接DDR3
把图像从DDR3读出的FIFO叫做读FIFO,记作R0_FIFO,R0_FIFO自身带有读写端口
R0_FIFO的写端口对接FDMA的读端口,即对接DDR3,R0_FIFO的读端口对接显示接口
这个框图你能想象出来吗?实在想不通可以画个图。。。
这个读写FIFO的配置有讲究,请看:
W0_FIFO:

写位宽选择32位,你可能会疑问,我的输入视频是24位RGB的数据,这里为啥要搞个32位的呢?
读位宽直接对接AXI4,进而对接DDR3是吧?AXI4的数据位宽只有那么几个选择啊,最高256位,这里选个128位不过分吧?
既然读位宽定了128,如果你的写位宽保持24位,那么128/24=5.333333,这样没法儿搞了,读写肯定是错的,如果写位宽搞个32位,那么128/32=4,这下好了。。。
R0_FIFO的配置反过来,这里就不说了,截个图吧:

再来说说图像缓存的过程:
写过程:
首先检测输入图像VS的上升沿,这个信号很重要:他标志一帧图像的到来,同时也标志一帧图像的结束或者下一帧图像的到来,当检测到VS的上升沿时,需要做两件事,因为他是一帧图像的到来的标志,所以要进入写数据状态,又因为他是一帧图像的结束或者下一帧图像到来的标志,所以要更新AXI4的写地址或者读地址。
在写数据时,W0_FIFO的写入端:
以输入视频的de信号作为写有效,这样可以保证写入的数据时有效的像素数据;
在写数据时,W0_FIFO的读出端:
以FDMA的pkg_wr_en作为读有效,这样,这样又可以保证写入的数据时有效的像素数据;
这里有个关键的问题,那就是W0_FIFO读写时钟的问题:
以1080p图像为例:
W0_FIFO的写时钟=148.5M;
W0_FIFO的读时钟=DDR3的用户时钟=100M;
有兄弟就会疑惑了:写比读块,FIFO不卡?
当然不会,请看下面:
虽然写时钟是148.5M,但写入端的数据位宽只有32位;
虽然读时钟是100M,但读出端的数据位宽是128位;
128/32=4;
也就是说,写入端写了4个数据,读出端才读出1个数据;
也就是说,读出端相当于148.5X4被的速度,咋可能卡呢。。。。。

读过程和谐过程反向,就不解释了

下面你看FDMA控制器源码,我做了小幅改动:

module fdma_controller#
(parameter  integer  ADDR_OFFSET     = 0   ,parameter  integer  BUF_SIZE        = 3   ,parameter  integer  H_CNT           = 1920, parameter  integer  V_CNT           = 1080,parameter  integer  W_ADDR_START    = 0   ,parameter  integer  R_ADDR_START    = 0   ,parameter  integer  FDMA_DATA_WIDTH = 128 ,parameter  integer  FDMA_BURST_LEN  = 256 ,parameter  integer  M_AXI_ID_WIDTH  = 1   ,parameter  integer  M_AXI_ADDR_WIDTH= 32
)
(input           ui_clk,input           ui_rstn,
//sensor input -W0_FIFO--------------input           i_video_vs,input           i_video_clk,input           i_video_de,input  [23:0]   i_video_rgb,
//hdmi output -R0_FIFO--------------- input           o_video_vs,input           o_video_clk,input           o_video_de,output [23:0]   o_video_rgb,
//AXI4_FULL_MASTER-------input                               M_AXI_ACLK     ,input                               M_AXI_ARESETN  ,output [M_AXI_ID_WIDTH-1 : 0]   M_AXI_AWID     ,   output [M_AXI_ADDR_WIDTH-1 : 0]  M_AXI_AWADDR   ,   output [7 : 0]                   M_AXI_AWLEN    ,   output [2 : 0]                   M_AXI_AWSIZE   ,   output [1 : 0]                   M_AXI_AWBURST  ,   output                           M_AXI_AWLOCK   ,   output [3 : 0]                   M_AXI_AWCACHE  ,   output [2 : 0]                   M_AXI_AWPROT   ,   output [3 : 0]                   M_AXI_AWQOS    ,   output                           M_AXI_AWVALID  ,   input                            M_AXI_AWREADY  ,   output [FDMA_DATA_WIDTH-1 : 0]   M_AXI_WDATA    ,   output [FDMA_DATA_WIDTH/8-1 : 0] M_AXI_WSTRB    ,   output                           M_AXI_WLAST    ,   output                           M_AXI_WVALID   ,   input                            M_AXI_WREADY   ,   input  [M_AXI_ID_WIDTH-1 : 0]    M_AXI_BID      ,   input  [1 : 0]                   M_AXI_BRESP    ,   input                            M_AXI_BVALID   ,   output                           M_AXI_BREADY   ,   output [M_AXI_ID_WIDTH-1 : 0]    M_AXI_ARID     ,   output [M_AXI_ADDR_WIDTH-1 : 0]  M_AXI_ARADDR   ,   output [7 : 0]                   M_AXI_ARLEN    ,   output [2 : 0]                   M_AXI_ARSIZE   ,   output [1 : 0]                   M_AXI_ARBURST  ,   output                           M_AXI_ARLOCK   ,   output [3 : 0]                   M_AXI_ARCACHE  ,   output [2 : 0]                   M_AXI_ARPROT   ,   output [3 : 0]                   M_AXI_ARQOS    ,   output                           M_AXI_ARVALID  ,   input                            M_AXI_ARREADY  ,   input  [M_AXI_ID_WIDTH-1 : 0]    M_AXI_RID      ,   input  [FDMA_DATA_WIDTH-1 : 0]   M_AXI_RDATA    ,   input  [1 : 0]                   M_AXI_RRESP    ,   input                            M_AXI_RLAST    ,   input                            M_AXI_RVALID   ,   output                           M_AXI_RREADY       );//parameter FBUF_SIZE = BUF_SIZE -1'b1;
parameter FBUF_2 = BUF_SIZE -1'b1;
//parameter BURST_SIZE  = 1024*4;  // one time 4KB ,一次突发读写的地址
//                              //FDMA设置的是128*256,所以BURST_SIZE=128x256/8=4096=4KB的存储空间
parameter BURST_UP_ADDR  = FDMA_DATA_WIDTH*FDMA_BURST_LEN/8;   // one time 4KB ,一次突发读写的地址//FDMA设置的是128*256,所以BURST_SIZE=128x256/8=4096=4KB的存储空间
//parameter BURST_TIMES = H_CNT*V_CNT/1024;// one frame burst times,突发一次的数据是256*128=32768bit/(4*8)=1024
parameter FRAME_BURST_OK = (H_CNT*V_CNT)/(FDMA_DATA_WIDTH*FDMA_BURST_LEN/8/(3+1));//one frame burst times,突发一次的数据是256*128=32768bit/(4*8)=1024//本来一个像素是3个字节,这里+1变为4个字节是因为:以32位作为AXI4的传输,128位宽的总线除不尽24//这里也是一帧图像传输完成的标志
parameter PKG_SIZE    = 256;
assign pkg_wr_size = PKG_SIZE;
assign pkg_rd_size = PKG_SIZE;//------------vs 滤波---------------
reg  W0_FIFO_Rst;
reg  R0_FIFO_Rst;wire W0_FS;
wire R0_FS;  reg [6:0] W0_Fbuf; //写帧缓存buf,从0开始写,写完到2后再回到0循环写 :0-->1-->2-->0
reg [6:0] R0_Fbuf;  //读帧缓存buf,从2开始读,读完到0后再回到2循环读 :2-->1-->0-->2//----------fdma signals write-------
reg           pkg_wr_areq;
wire          pkg_wr_en  ;
wire          pkg_wr_last;
wire  [31:0]  pkg_wr_addr;
wire  [127:0] pkg_wr_data;
wire  [31:0]  pkg_wr_size;
//----------fdma signals read---------
reg           pkg_rd_areq;
wire          pkg_rd_en  ;
wire          pkg_rd_last;
wire  [31:0]  pkg_rd_addr;
wire  [127:0] pkg_rd_data;
wire  [31:0]  pkg_rd_size;   wire [31:0] i_video_rgb_r;
assign i_video_rgb_r = {8'h11,i_video_rgb};
wire [31:0] o_video_rgb_r;
assign o_video_rgb = o_video_rgb_r[23:0];fs_cap fs_cap_W0(.clk_i(ui_clk),.rstn_i(ui_rstn),.vs_i(i_video_vs),.fs_cap_o(W0_FS)
);fs_cap fs_cap_R0(.clk_i(ui_clk),.rstn_i(ui_rstn),.vs_i(o_video_vs),.fs_cap_o(R0_FS)
);parameter S_IDLE  =  2'd0;
parameter S_RST   =  2'd1;
parameter S_DATA1 =  2'd2;
parameter S_DATA2 =  2'd3; reg [1  :0]  W_MS;      //写状态机
reg [22:0]   W0_addr;    //写数据地址
reg [31 :0]  W0_fs_cnt;  //输入图像的vs信号计数器
reg [10  :0] W0_pkg_cnt; //写fdma包计数器
wire [10:0]  W0_rcnt;    //写FIFO中可读的数据量,128bit的数据
reg W0_REQ;
reg [1 :0]  R_MS;       //读状态机
reg [22 :0] R0_addr;    //读数据地址
reg [31 :0] R0_fs_cnt;  //输入图像的vs信号计数器
reg [10 :0] R0_pkg_cnt; //写fdma包计数器
wire [10:0] R0_wcnt;    //读FIFO中已写的数据量,128bit的数据
reg R0_REQ;assign pkg_wr_addr = {W0_Fbuf,W0_addr}+ ADDR_OFFSET;
assign pkg_rd_addr = {R0_Fbuf,R0_addr}+ ADDR_OFFSET;
//assign pkg_wr_data = W0_fs_cnt;
//--------一副图像写入DDR------------always @(posedge ui_clk) beginif(!ui_rstn)beginW_MS <= S_IDLE;W0_addr <= 21'd0;pkg_wr_areq <= 1'd0;W0_FIFO_Rst <= 1'b1;W0_fs_cnt <= 0;W0_pkg_cnt <= 0;W0_Fbuf <= W_ADDR_START;   //从0buf处开始写endelse begincase(W_MS)  //空闲状态:检测输入图像的vs,然后进入写FIFO的复位     S_IDLE:beginW0_addr <= 21'd0;W0_fs_cnt <= 0;W0_pkg_cnt <= 11'd0;if(W0_FS) W_MS <= S_RST;endS_RST:begin    //复位状态:对输入图像的vs进行计数,一者用vs复位写FIFO,二者,如果vs突然中断,则不会写图像进DDRif(W0_fs_cnt > 8'd20 ) W_MS <= S_DATA1;W0_FIFO_Rst <= (W0_fs_cnt < 8'd10); W0_fs_cnt <= W0_fs_cnt +1'd1;end          S_DATA1:begin if(W0_pkg_cnt == FRAME_BURST_OK) begin //一帧写完if(W0_Fbuf == FBUF_2) W0_Fbuf <= W_ADDR_START;  //如果写帧缓存buf到了2,则回到0,以此循环写                     else W0_Fbuf <= W0_Fbuf + 1'b1;               //如果写帧缓存buf没到2,则从0开始向上加W_MS <= S_IDLE;                               //同时,一帧写完了,回到S_IDLE状态,写下一帧endelse if(W0_REQ) beginW0_fs_cnt <=0;        //此处对W0_fs_cnt置零,为下一帧到来前做准备pkg_wr_areq <= 1'b1;W_MS <= S_DATA2;  end           endS_DATA2:beginpkg_wr_areq <= 1'b0; if(pkg_wr_last)begin  //一包fdma数据写完!=一帧图像写完W_MS <= S_DATA1;   //一包fdma数据写完后,进入S_DATA1状态,检测一帧图像是否写完W0_pkg_cnt <= W0_pkg_cnt + 1'd1;    //一包fdma数据写完后,W0_pkg_cnt+1,当W0_pkg_cnt==FRAME_BURST_OK时,一帧图像写完W0_addr <= W0_addr + BURST_UP_ADDR; //一包fdma数据写完后,AXI4地址+BURST_UP_ADDRendendendcaseendend //--------一副图像读出DDR------------always @(posedge ui_clk) beginif(!ui_rstn)beginR_MS <= S_IDLE;R0_addr <= 21'd0;pkg_rd_areq <= 1'd0;R0_fs_cnt <=0;R0_pkg_cnt <=0;R0_FIFO_Rst <= 1'b1;R0_Fbuf <= R_ADDR_START;       endelse begincase(R_MS)S_IDLE:beginR0_addr <= 21'd0;R0_fs_cnt <=0;R0_pkg_cnt <=0;if(R0_FS) R_MS <= S_RST;end S_RST:beginif(R0_fs_cnt > 8'd20 ) R_MS <= S_DATA1;R0_FIFO_Rst <= (R0_fs_cnt < 8'd10); R0_fs_cnt <= R0_fs_cnt + 1'd1;end  S_DATA1:begin if(R0_pkg_cnt == FRAME_BURST_OK ) begin  //读完一帧图像R_MS <= S_IDLE;if(W0_Fbuf == W_ADDR_START) R0_Fbuf <= FBUF_2; //如果此时正在写FBUF_0,则从FBUF_2开始读,反之亦然,形成交错读写else R0_Fbuf <= W0_Fbuf - 1'b1;                  //如果此时未写FBUF_0,  endelse if(R0_REQ) beginpkg_rd_areq <= 1'b1;R_MS <= S_DATA2;  end           endS_DATA2:beginpkg_rd_areq <= 1'b0;   if(pkg_rd_last)begin //一包fdma数据读完!=一帧图像读完R_MS <= S_DATA1; //一包fdma数据读完后,进入S_DATA1状态,检测一帧图像是否读完R0_pkg_cnt <= R0_pkg_cnt + 1'd1;      //一包fdma数据读完后,R0_pkg_cnt+1,当R0_pkg_cnt==FRAME_BURST_OK时,一帧图像读完 R0_addr <= R0_addr + BURST_UP_ADDR;  //一包fdma数据读完后,AXI4地址+BURST_UP_ADDRendendendcaseend
end always@(posedge ui_clk)begin     W0_REQ    <= (W0_rcnt    > PKG_SIZE -5); //当写FIFO中有PKG_SIZE -5个128bit的可读数据时,发起写请求,将数据写入DDRR0_REQ    <= (R0_wcnt    < PKG_SIZE -5); //当读FIFO中存在已写数据时(128bit),发起读请求,将数据从DDR读出 endW0_FIFO W0_FIFO_0 (.rst(W0_FIFO_Rst),  // input wire rst.wr_clk(i_video_clk),  // input wire wr_clk.din(i_video_rgb_r),        // input wire [31 : 0] din.wr_en(i_video_de),    // input wire wr_en.rd_clk(ui_clk),  // input wire rd_clk .rd_en(pkg_wr_en),    // input wire rd_en.dout(pkg_wr_data),      // output wire [63 : 0] dout.rd_data_count(W0_rcnt)  // output wire [10 : 0] wr_data_count
);R0_FIFO R0_FIFO_0 (.rst(R0_FIFO_Rst),  // input wire rst.wr_clk(ui_clk),  // input wire wr_clk.din(pkg_rd_data),        // input wire [63 : 0] din.wr_en(pkg_rd_en),    // input wire wr_en.wr_data_count(R0_wcnt),  // output wire [6 : 0] rd_data_count.rd_clk(o_video_clk),  // input wire rd_clk .rd_en(o_video_de),    // input wire rd_en.dout(o_video_rgb_r)      // output wire [31 : 0] dout
);

最后,我将FDMA及其控制器进行了封装,IP截图如下:

配置如下:

其中的R_Addr_Start和R_Addr_Start的设置很重要,比如:

整个工程以动态彩条为视频源,1080p,最后一HDMI输出,很完美,BD工程如下:

输出效果:

打码了,所以有点花。。。
整个动态彩条工程是以K7410T的FPGA作为平台的,重在解决视频三帧缓存的问题,兄弟们拿过去可以修改后用到自己的工程,我也可以提供有限的技术交流,因为我的水平很low
需要工程的兄弟可以加我WX(hllsq22)

米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳相关推荐

  1. FPGA图像三帧缓存,手写米联客FDMA3.0控制器,逐行讲解代码

    米联客推出了FDMA3.0,下载试用了将近一个月,感觉还行,总体比较平衡,相比于FDMA1.0操作也更简单,适用于基于AXI4_FULL的数据缓存方案,源码可以去米联客官网下载. 其实这种ASXI M ...

  2. FPGA运动目标检测,基于米联客FDMA设计开发,A7和zynq两个版本

    FPGA运动目标检测,基于米联客FDMA设计开发,A7和zynq两个版本 开发环境如下: 纯FPGA开发板:米联客MA703FA,A7-35T的FPGA: ZYNQ开发板:米联客MZ7100FA,zy ...

  3. 米联客fdma搭建单摄像头ov5640传输

    前言 最近在研究米联客fdma的使用,在正点原子达芬奇开发板上搭建了ov5640-hdmi的传输框架. 效果 block design 结构 代码 https://download.csdn.net/ ...

  4. zynq linux如何使用pl ip,米联客FDMA IP在LINUX下实现PL和PS数据共享测试总结

    本帖最后由 ぉ沙皮狗的忧伤 于 2019-10-12 10:22 编辑 1.先讲一下一个小问题,我将FDMA裸机测试的.bit文件重命名为system.bit.bin文件放入SD卡启动时,在VDMA的 ...

  5. 米联客FDMA3.0代码逐行讲解,全网最细看了包学会,肝了一夜写出来的

    目录 1.FDMA的好处 2.AXI4-FULL时序分析 3.FDMA逐行代码分析 4.FDMA仿真时序分析 1.FDMA的好处 想要玩儿Xilinx系列FPGA(包括zynq),AXI4总线是绕不开 ...

  6. 米联客FDMA3.1数据缓存方案全网最细讲解,自创升级版,送3套视频和音频缓存工程源码

    米联客的FDMA数据缓存方案发布也有五六年了,但真正能熟练使用的兄弟却很少,其实还是没有好的例程作为参考和同熟易懂的讲解,这里我做如下解析: FDMA部分:这部分是米联客封装了用户接口的AXI4-FU ...

  7. 基于米联客zynq7020的fpga实现的帧差法

    博主是一个年初刚刚接触FPGA的新人,说实话觉得自己还是挺水的.上年的12月份刚刚了解了什么是FPGA,然后就参加了一个叫全国大学生集成电路创新大赛的比赛,里面的robei杯(当然可以后续和我交流一下 ...

  8. 基于zynq的千兆网udp项目_米联客 ZYNQ/SOC 精品教程 S05-CH05 PS 千兆 UDP 加速

    软件版本:VIVADO2017.4 操作系统:WIN10 64bit 硬件平台:适用米联客 ZYNQ系列开发板 米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!! ...

  9. 米联客 ZYNQ/SOC 精品教程 S02-CH25 利用OSD实现双目摄像头字幕叠加

    软件版本:VIVADO2017.4 操作系统:WIN10 64bit 硬件平台:适用米联客 ZYNQ系列开发板 米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!! ...

最新文章

  1. 如何在nuget上传自己的包+搭建自己公司的NuGet服务器(新方法)
  2. 如何修改select的样式
  3. java 查找一行_Java培训之工具类通用的查询一行多列,非实体
  4. perl 引用(一)
  5. 一起学React--组件定义和组件通讯
  6. python中类的方法里面变量前加self与不加self的区别
  7. 适用于树莓派Raspberry Pi的嵌入式QT平台(二) -- 在Windows下用Qt Creator开发编译Raspberry Qt 5应用程序...
  8. 使用Flash Builder 4.5进行多平台游戏开发
  9. UVa1225 - Digit Counting
  10. jQuery核心函数
  11. JavaScript高级程序设计(第3版)中文在线阅读
  12. 用word模仿手写字体
  13. oracle 11g 解决临时表空间占满问题
  14. Mycat分库分表优缺点分析
  15. Battle Mages (魔法之战,精灵传说)修改器
  16. 游戏化方式学习Git指令
  17. 提取图像中颜色交界处的坐标
  18. 深入Golang内存管理(三) 内存对齐篇
  19. 仿联想商城laravel实战---1、仿联想商城需求和数据库设计(lavarel如何搭建项目)...
  20. MySQL创建用户添加权限

热门文章

  1. 基于JSP的网上机票销售系统
  2. 【蓝桥杯选拔赛真题40】python小写字母转大写 青少年组蓝桥杯python 选拔赛STEMA比赛真题解析
  3. markdown数据转换,处理html2canvas+jsPDF下载后文字截断问题(记录)
  4. java转换docx为html_Java使用Jacob转换Word为HTML
  5. centos7 安装netdata及使用
  6. postfix邮件服务器的关键安全策略
  7. zip压缩包上传linux文件名乱码
  8. FM收音机模块TEA5767介绍
  9. rand在c语言那一个函数库,C语言中rand()函数及time库相关
  10. PHP数据类型有几种?