米联客FDMA IP在安路FPGA上实现DDR视频缓存(带源码)

时间:2025-03-08 16:23:51

`timescale 1ns / 1ns

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                                  I_ui_clk, //和FDMA AXI总线时钟一致

input wire                                  I_ui_rstn, //和FDMA AXI复位一致

//sensor input -W_FIFO--------------

input wire                                  I_W_clk, //用户写数据接口时钟

input wire                                  I_W_FS, //用户写数据接口同步信号,对于非视频帧一般设置为1

input wire                                  I_W_wren, //用户写数据使能

input wire     [W_DATAWIDTH-1'b1 : 0]       I_W_data, //用户写数据

output reg     [7   :0]                     O_W_sync_cnt =0, //写通道BUF帧同步输出

input  wire    [7   :0]                     I_W_buf, // 写通道BUF帧同步输入

output wire                                 O_W_full,

//----------fdma signals write-------      

output wire    [AXI_ADDR_WIDTH-1'b1: 0]     O_fdma_waddr, //FDMA写通道地址

output wire                                 O_fdma_wareq, //FDMA写通道请求

output wire    [15  :0]                     O_fdma_wsize, //FDMA写通道一次FDMA的传输大小                                    

input  wire                                 I_fdma_wbusy, //FDMA处于BUSY状态,AXI总线正在写操作  

output wire    [AXI_DATA_WIDTH-1'b1:0]      O_fdma_wdata, //FDMA写数据

input  wire                                 I_fdma_wvalid, //FDMA 写有效

output wire                                 O_fdma_wready, //FDMA写准备好,用户可以写数据

output reg     [7   :0]                     O_fmda_wbuf =0, //FDMA的写帧缓存号输出

output wire                                 O_fdma_wirq, //FDMA一次写完成的数据传输完成后,产生中断。  

//----------fdma signals read-------  

input  wire                                 I_R_clk, //用户读数据接口时钟

input  wire                                 I_R_FS, //用户读数据接口同步信号,对于非视频帧一般设置1

input  wire                                 I_R_rden, //用户读数据使能

output wire    [R_DATAWIDTH-1'b1 : 0]       O_R_data, //用户读数据

output reg     [7   :0]                     O_R_sync_cnt =0, //读通道BUF帧同步输出

input  wire    [7   :0]                     I_R_buf, //写通道BUF帧同步输入

output wire                                 O_R_empty,

output wire    [AXI_ADDR_WIDTH-1'b1: 0]     O_fdma_raddr, // FDMA读通道地址

output wire                                 O_fdma_rareq, // FDMA读通道请求

output wire    [15: 0]                      O_fdma_rsize, // FDMA读通道一次FDMA的传输大小                                    

input  wire                                 I_fdma_rbusy, // FDMA处于BUSY状态,AXI总线正在读操作    

input  wire    [AXI_DATA_WIDTH-1'b1:0]      I_fdma_rdata, // FDMA读数据

input  wire                                 I_fdma_rvalid, // FDMA 读有效

output wire                                 O_fdma_rready, // FDMA读准备好,用户可以读数据

output reg     [7  :0]                      O_fmda_rbuf =0, // FDMA的读帧缓存号输出

output wire                                 O_fdma_rirq // FDMA一次读完成的数据传输完成后,产生中断

);    

// 计算Log2

function integer clog2;

  input integer value;

  begin 

    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); //计算FIFO的写通道位宽

localparam W_RD_DATA_COUNT_WIDTH = clog2(WFIFO_DEPTH*W_DATAWIDTH/AXI_DATA_WIDTH);//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 = I_W_wren;

assign                                  O_fdma_wready = 1'b1;

reg                                     O_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]                              O_fmda_wbufn;

(*mark_debug = "true"*) (* KEEP = "TRUE" *) wire wirq= O_fdma_wirq;

assign O_fdma_wsize = FDMA_WX_BURST;

assign O_fdma_wirq = (wirq_dly_cnt>0);

assign O_fdma_waddr = W_BASEADDR + {O_fmda_wbufn,W_addr};//由于FPGA逻辑做乘法比较复杂,因此通过设置高位地址实现缓存设置

reg [1:0] W_MS_r =0;

always @(posedge I_ui_clk) W_MS_r <= W_MS;

//每次FDMA DBUF 完成一帧数据传输后,产生中断,这个中断持续60个周期的uiclk,这里的延迟必须足够ZYNQ IP核识别到这个中断

always @(posedge I_ui_clk) begin

    if(I_ui_rstn == 1'b0)begin

        wirq_dly_cnt <= 6'd0;

        O_fmda_wbuf <=0;

    end

    else if((W_MS_r == S_DATA2) && (W_MS == S_IDLE))begin

        wirq_dly_cnt <= 60;

        O_fmda_wbuf <= O_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

(

 .I_clk(I_ui_clk),

 .I_rstn(I_ui_rstn),

 .I_vs(I_W_FS),

 .O_fs_cap(W_FS)

);

assign O_fdma_wareq = O_fdma_wareq_r;

//写通道状态机,采用4个状态值描述

 always @(posedge I_ui_clk) begin

    if(!I_ui_rstn)begin

        W_MS         <= S_IDLE;

        W_FIFO_Rst   <= 0;

        W_addr       <= 0;

        O_W_sync_cnt <= 0;

        W_bcnt       <= 0;

        wrst_cnt     <= 0;

        wdiv_cnt     <= 0;

        O_fmda_wbufn    <= 0;

        O_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(O_W_sync_cnt < WYBUF_SIZE) //输出帧同步计数器

                O_W_sync_cnt <= O_W_sync_cnt + 1'b1;

            else 

                O_W_sync_cnt <= 0;  

          end

       end

       S_RST:begin//帧同步,对于非视频数据直接跳过,对于视频数据,会同步每一帧,并且复位数据FIFO

           O_fmda_wbufn <= I_W_buf;

           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(O_fdma_wirq == 1'b0) begin

                W_MS <= S_DATA1;

           end

       end

        S_DATA1:begin //发送写FDMA请求

          if(I_fdma_wbusy == 1'b0 && W_REQ )begin

             O_fdma_wareq_r  <= 1'b1;

          end 

          else if(I_fdma_wbusy == 1'b1) begin

             O_fdma_wareq_r  <= 1'b0;

             W_MS    <= S_DATA2;

          end          

         end

        S_DATA2:begin //写有效数据

            if(I_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 I_ui_clk)    

     W_REQ  <= (W_rcnt > FDMA_WX_BURST - 2)&&(~W_rbusy);

wfifo #(

.DATA_WIDTH_W(W_DATAWIDTH),

.DATA_WIDTH_R(AXI_DATA_WIDTH),

.ADDR_WIDTH_W(W_WR_DATA_COUNT_WIDTH),

.ADDR_WIDTH_R(W_RD_DATA_COUNT_WIDTH),

.AL_FULL_NUM(WFIFO_DEPTH-2),

.AL_EMPTY_NUM(2),

.SHOW_AHEAD_EN(1'b1) ,

.OUTREG_EN ("NOREG")

)

u_wfifo(

.rst((I_ui_rstn == 1'b0) || (W_FIFO_Rst == 1'b1)), //asynchronous port,active hight

.clkw(I_W_clk),  //write clock

.clkr(I_ui_clk),  //read clock

.we(I_W_wren),  //write enable,active hight

.di(I_W_data),  //write data

.re(I_fdma_wvalid),  //read enable,active hight

.dout(O_fdma_wdata),  //read data

//.valid(),  //read data valid flag

.full_flag(O_W_full),  //fifo full flag

//.empty_flag(),  //fifo empty flag

//.afull(),  //fifo almost full flag

//.aempty(),  //fifo almost empty flag

//.wrusedw(W_rcnt),  //stored data number in fifo

.rdusedw(W_rcnt) //available data number for read      

) ;

end

else begin : FDMA_WRITE_DISABLE

//----------fdma signals write-------      

assign O_fdma_waddr = 0;

assign O_fdma_wareq = 0;

assign O_fdma_wsize = 0;                                    

assign O_fdma_wdata = 0;

assign O_fdma_wready = 0;

assign O_fdma_wirq = 0;

assign O_W_full = 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); //读通道FIFO 输入部分深度

localparam R_RD_DATA_COUNT_WIDTH = clog2(R_BUFDEPTH); //写通道FIFO输出部分深度

assign                                  O_fdma_rready = 1'b1;

reg                                     O_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]                              O_fmda_rbufn;

assign O_fdma_rsize = FDMA_RX_BURST;

assign O_fdma_rirq = (rirq_dly_cnt>0);

assign O_fdma_raddr = R_BASEADDR + {O_fmda_rbufn,R_addr};//由于FPGA逻辑做乘法比较复杂,因此通过设置高位地址实现缓存设置

reg [1:0] R_MS_r =0;

always @(posedge I_ui_clk) R_MS_r <= R_MS;

//每次FDMA DBUF 完成一帧数据传输后,产生中断,这个中断持续60个周期的uiclk,这里的延迟必须足够ZYNQ IP核识别到这个中断

always @(posedge I_ui_clk) begin

    if(I_ui_rstn == 1'b0)begin

        rirq_dly_cnt <= 6'd0;

        O_fmda_rbuf <=0;

    end

    else if((R_MS_r == S_DATA2) && (R_MS == S_IDLE))begin

        rirq_dly_cnt <= 60;

        O_fmda_rbuf <= O_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

(

  .I_clk(I_ui_clk),

  .I_rstn(I_ui_rstn),

  .I_vs(I_R_FS),

  .O_fs_cap(R_FS)

);

assign O_fdma_rareq = O_fdma_rareq_r;

//读通道状态机,采用4个状态值描述

 always @(posedge I_ui_clk) begin

   if(!I_ui_rstn)begin

        R_MS          <= S_IDLE;

        R_FIFO_Rst   <= 0;

        R_addr       <= 0;

        O_R_sync_cnt <= 0;

        R_bcnt       <= 0;

        rrst_cnt     <= 0;

        rdiv_cnt      <= 0;

        O_fmda_rbufn    <= 0;

        O_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(O_R_sync_cnt < RYBUF_SIZE) //输出帧同步计数器,当需要用读通道做帧同步的时候使用

                O_R_sync_cnt <= O_R_sync_cnt + 1'b1;

            else 

                O_R_sync_cnt <= 0;  

          end

       end

       S_RST:begin//帧同步,对于非视频数据直接跳过,对于视频数据,会同步每一帧,并且复位数据FIFO

           O_fmda_rbufn <= I_R_buf;

           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(O_fdma_rirq == 1'b0) begin

                R_MS <= S_DATA1;

           end

       end

       S_DATA1:begin 

         if(I_fdma_rbusy == 1'b0 && R_REQ)begin

            O_fdma_rareq_r  <= 1'b1;  

         end

         else if(I_fdma_rbusy == 1'b1) begin

            O_fdma_rareq_r  <= 1'b0;

            R_MS    <= S_DATA2;

         end        

        end

        S_DATA2:begin //写有效数据

            if(I_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 I_ui_clk)      

     R_REQ  <= (R_wcnt < FDMA_RX_BURST - 2)&&(~R_wbusy);

rfifo #(

.DATA_WIDTH_W(AXI_DATA_WIDTH),

.DATA_WIDTH_R(R_DATAWIDTH),

.ADDR_WIDTH_W(R_WR_DATA_COUNT_WIDTH),

.ADDR_WIDTH_R(R_RD_DATA_COUNT_WIDTH),

.AL_FULL_NUM(RFIFO_DEPTH-2),

.AL_EMPTY_NUM(2),

.SHOW_AHEAD_EN(1'b1) ,

.OUTREG_EN ("NOREG")

)

u_rfifo(

.rst((I_ui_rstn == 1'b0) || (R_FIFO_Rst == 1'b1)), //asynchronous port,active hight

.clkw(I_ui_clk),  //write clock

.clkr(I_R_clk),  //read clock

.we(I_fdma_rvalid),  //write enable,active hight

.di(I_fdma_rdata),  //write data

.re(I_R_rden),  //read enable,active hight

.dout(O_R_data),  //read data

//.valid(),  //read data valid flag

//.full_flag(),  //fifo full flag

.empty_flag(O_R_empty),  //fifo empty flag

//.afull(),  //fifo almost full flag

//.aempty(),  //fifo almost empty flag

.wrusedw(R_wcnt) //stored data number in fifo

//.rdusedw(W_rcnt) //available data number for read      

) ;

end

else begin : FDMA_READ_DISABLE

   

assign O_fdma_raddr = 0;

assign O_fdma_rareq = 0;

assign O_fdma_rsize = 0;                                    

//assign I_fdma_rdata = 0;

assign O_fdma_rready = 0;

assign O_fdma_rirq = 0;

assign O_R_empty   = 1'b0;

end

endgenerate

endmodule