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

S03-CH02基于FDMA实现多缓存视频构架

摘要: 很多学习FPGA编程的人都想走捷径,然而捷径就是一步一个脚印,才能迈向最终的成功,否则即便是你刚开始跑的快一点,但是你还是失败,得不偿失。这是正是所谓磨刀不误砍柴工。对于小编原创的FDMA没有经过充分验证就去 ...

软件版本:VIVADO2017.4

操作系统:WIN10 64bit

硬件平台:适用米联客 ZYNQ系列开发板

米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!

2.1概述

      很多学习FPGA编程的人都想走捷径,然而捷径就是一步一个脚印,才能迈向最终的成功,否则即便是你刚开始跑的快一点,但是你还是失败,得不偿失。这是正是所谓磨刀不误砍柴工。对于小编原创的FDMA没有经过充分验证就去跑应用,万一那里出了问题,到底是硬件的问题,还是软件的问题,还是FDMA的问题,那就难排查了。所以我们还是要做一些准备工作。在这节课中,我们将用FPGA代码产生一个1080P60HZ的测试图像,然后经过FDMA进入DDR缓存3帧后再从FDMA读出来,经过HDMI显示器显示。做完这个实验下一节课我们就加入HDMI输入视频信号再经过3次缓存后输出到HDMI显示器。

     另外从本章开始,不提供详细的VIVADO软件使用或者配置步骤,除非一些新IP的出现有必要讲解的情况下才说明。

2.2基于FDMA搭建的BD工程

      这个BD文件和前面一节课的基本一样。但是有个IP换了,换成了axi_interconnect IP。有人可能会问,为什么要换这个IP,一开始我也不知道。但是可以肯定的是用上一课的IP无法达到FDMA和MIG之间AXI4的最大带宽。导致1080P视频不能正常传输。所以小编就尝试换了这个IP而且把这个IP里面的几个参数修改了。

这样修改完成后,带宽就能最大化,在MZ7X开发板上完美传输1080P@60fps的视频。

为了最大带宽,我们还要修改FDMA的IP参数如下图。

这里把BURST LEN设置为256 DATA WIDTH设置为128。为什么这样设置呢?假设100M时钟,如果设置128bit理论上有1600MB/S的带宽能力(实际不可能达到,效率一般在75%左右)。如下面图所示。

另外HP接口最大只有64Bit位宽,因此为了实现最大速度,把HP接口的时钟设置到200M

2.3基于FDMA多缓存视频构架fdma_controller

      一个优秀的工程,必然有一个优秀的构架。优秀的构架具备通用、简洁、高可靠、易维护等优秀属性。小编见过很多人写的代码,只在一个特定的工程里面可以很好的工作,但是如果要换个环境,几乎要推到重来。这种代码就是比较垃圾的代码。我们在做一件事情的时候一定要注重效率,注重可重复利用性。这就要求我们对现在的使用环境,以及对将来的使用环境有一个认知。然后构建一个比较通用的代码构架。虽然小编也是半路出道,人算不上聪明,而且具备一定的懒惰性。小编喜欢做一些重复性的工作,最喜欢修改个参数就能实现一些新的功能。基于以上目标,即便是我不是一个优秀的程序要,但是我依然要懂得偷懒,尽最大努力从一个小小的视频缓存构架开始,努力设计好这个简单的构架。那么对于正在阅读小编写的教程的初级程序猿,自然也要时刻考虑如何偷懒,如果你写的一个代码被成千上万,到几百万的人重复利用了,你一定成功了。

      对于本课内容,小编绘制了如下框图。可以看image_data_gen产生了测试图片,之后进入过W0 FIFO进行视频缓存。每次缓存1024个像素,就往通过FDMA往DDR里面搬运数据。另外VS信号经过滤波采集后用于启动一次写状态机。同理对于图像的输出部分采用HDMI输出,用Vga_lcd_driver产生输出的时序。视频经过R0 FIFO缓存后输出。R0 FIFO也是每次缓存1024个像素数据。

       为了进行图像的多缓存,一般非同步信号至少要满足3缓存才能最大减少图片的延迟,撕裂,丢帧问题。本构架只要DDR够大,理论上可以进行无限次缓存。

2.4代码叠层结构

2.5 fdma_controller

     当然针对上图还无法完全展示fdma_controller控制器的全部功能。下面贴出代码

module fdma_controller#

(

parameter  integer  ADDR_OFFSET = 0,

parameter  integer  BUF_SIZE = 3,

parameter  integer  H_CNT = 640,

parameter  integer  V_CNT = 480

)

(

    input           ui_clk,

    input           ui_rstn,

//sensor input -W0_FIFO--------------

    input           W0_FS_i,

    input           W0_wclk_i,

    input           W0_wren_i,

    input  [31:0]   W0_data_i,

//hdmi output -R0_FIFO---------------

    input           R0_FS_i,

    input           R0_rclk_i,

    input           R0_rden_i,

    output[31:0]    R0_data_o,

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

    output  reg     pkg_wr_areq,       

    input           pkg_wr_en,

    input           pkg_wr_last,

    output  [31:0]  pkg_wr_addr,

    output  [127:0] pkg_wr_data,

    output  [31:0]  pkg_wr_size,

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

    output  reg     pkg_rd_areq,

    input           pkg_rd_en,   

    input           pkg_rd_last,         

    output  [31:0]  pkg_rd_addr,

    input   [127:0] pkg_rd_data,

    output  [31:0]  pkg_rd_size    

    );

    

parameter FBUF_SIZE = BUF_SIZE -1'b1;

parameter BURST_SIZE  = 1024*4;// one time 4KB

parameter BURST_TIMES = H_CNT*V_CNT/1024;// one frame burst times

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;

reg [6:0] R0_Fbuf;

 

reg W0_s_rdy;

reg R0_s_rdy;

 

fs_cap fs_cap_W0(

  .clk_i(ui_clk),

  .rstn_i(ui_rstn),

  .vs_i(W0_FS_i),

  .s_rdy_i(W0_s_rdy),

  .fs_cap_o(W0_FS)

);

fs_cap fs_cap_R0(

  .clk_i(ui_clk),

  .rstn_i(ui_rstn),

  .vs_i(R0_FS_i),

  .s_rdy_i(R0_s_rdy),

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

reg [10  :0] W0_bcnt;

wire [10:0]  W0_rcnt;

reg W0_REQ;

reg [1 :0]  R_MS;

reg [22 :0] R0_addr;

reg [31 :0] R0_fcnt;

reg [10 :0] R0_bcnt;

wire [10:0] R0_wcnt;

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_fcnt;

//--------一帧图像写入DDR------------

 always @(posedge ui_clk) begin

    if(!ui_rstn)begin

        W_MS <= S_IDLE;

        W0_addr <= 21'd0;

        pkg_wr_areq <= 1'd0;

        W0_FIFO_Rst <= 1'b1;

        W0_fcnt <= 0;

        W0_bcnt <= 0;

        W0_s_rdy <= 1'b0;

        W0_Fbuf <= 7'd0;

    end

    else begin

      case(W_MS)

       S_IDLE:begin

          W0_addr <= 21'd0;

          W0_fcnt <= 0;

          W0_bcnt <= 11'd0;

          W0_s_rdy <= 1'b1;

          if(W0_FS) W_MS <= S_RST;

       end

       S_RST:begin

           W0_s_rdy <= 1'b0;  

          if(W0_fcnt > 8'd30 ) W_MS <= S_DATA1;

          W0_FIFO_Rst <= (W0_fcnt < 8'd20);

          W0_fcnt <= W0_fcnt +1'd1;

        end          

        S_DATA1:begin

            if(W0_bcnt == BURST_TIMES) begin

                if(W0_Fbuf == FBUF_SIZE)

                    W0_Fbuf <= 7'd0;

                 else

                    W0_Fbuf <= W0_Fbuf + 1'b1;

                 W_MS <= S_IDLE;

            end

            else if(W0_REQ) begin

                W0_fcnt <=0;

                pkg_wr_areq <= 1'b1;

                W_MS <= S_DATA2;  

            end           

         end

         S_DATA2:begin

            pkg_wr_areq <= 1'b0;

            if(pkg_wr_last)begin

                W_MS <= S_DATA1;

                W0_bcnt <= W0_bcnt + 1'd1;

                W0_addr <= W0_addr + BURST_SIZE;

            end

         end

       endcase

    end

 end

 

//--------一帧图像读出DDR------------

 always @(posedge ui_clk) begin

   if(!ui_rstn)begin

       R_MS <= S_IDLE;

       R0_addr <= 21'd0;

       pkg_rd_areq <= 1'd0;

       R0_fcnt <=0;

       R0_bcnt <=0;

       R0_FIFO_Rst <= 1'b1;

       R0_s_rdy <= 1'b0;

       R0_Fbuf <= 7'd0;       

   end

   else begin

     case(R_MS)

       S_IDLE:begin

         R0_addr <= 21'd0;

         R0_fcnt <=0;

         R0_bcnt <=0;

         R0_s_rdy <= 1'b1;

         if(R0_FS) R_MS <= S_RST;

       end

       S_RST:begin

         R0_s_rdy <= 1'b0;

         if(R0_fcnt > 8'd30 ) R_MS <= S_DATA1;

         R0_FIFO_Rst <= (R0_fcnt < 8'd20);

         R0_fcnt <= R0_fcnt + 1'd1;

       end  

       S_DATA1:begin

           if(R0_bcnt == BURST_TIMES ) begin

               R_MS <= S_IDLE;

               if(W0_Fbuf == 7'd0)

                    R0_Fbuf <= FBUF_SIZE;

                else

                    R0_Fbuf <= W0_Fbuf - 1'b1;   

           end

           else if(R0_REQ) begin

               pkg_rd_areq <= 1'b1;

               R_MS <= S_DATA2;  

           end           

        end

        S_DATA2:begin

           pkg_rd_areq <= 1'b0;   

           if(pkg_rd_last)begin

               R_MS <= S_DATA1;

               R0_bcnt <= R0_bcnt + 1'd1;

               R0_addr <= R0_addr + BURST_SIZE;

           end

        end

      endcase

   end

end

 

 always@(posedge ui_clk)

 begin     

     W0_REQ    <= (W0_rcnt    >= PKG_SIZE);

     R0_REQ    <= (R0_wcnt    <= PKG_SIZE);

 end

 

W0_FIFO W0_FIFO_0 (

  .rst(W0_FIFO_Rst),  // input wire rst

  .wr_clk(W0_wclk_i),  // input wire wr_clk

  .din(W0_data_i),        // input wire [31 : 0] din

  .wr_en(W0_wren_i),    // 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(R0_rclk_i),  // input wire rd_clk

  .rd_en(R0_rden_i),    // input wire rd_en

  .dout(R0_data_o)      // output wire [31 : 0] dout

);

 

endmodule

 

截取上面完整代码中部分代码如下,可以看到小编通过控制高地址,轻松完成缓存地址切换。

assign pkg_wr_addr = {W0_Fbuf,W0_addr}+ ADDR_OFFSET;

assign pkg_rd_addr = {R0_Fbuf,R0_addr}+ ADDR_OFFSET;

 

再来看下顶层接口,这里面4个参数分辨率代表内存地址的偏移,帧缓存数量,图像的水平像素 和垂直像素。

module fdma_controller#

(

parameter  integer  ADDR_OFFSET = 0,

parameter  integer  BUF_SIZE = 3,

parameter  integer  H_CNT = 640,

parameter  integer  V_CNT = 480

)

 

 

最后看下我们如何如何调用fdma_controller,并且设置1080P的分辨率的。可以看到小编这里设置的偏移地址为0,缓存为3缓存,分辨率为1920X1080。就是这么简单。目前fdma_controller不修改源码的情况下支持任意是1024个像素的整数倍的图像。比如1280X720/640X800/1024X600,如果不是整数倍那就需要你自己修改fdma_controller和FDMA的参数了。注意PS DDR地址偏移,parameter DDR_BASE = (10*1024*1024)

//---------------fdma image buf controller---------------------------  

fdma_controller # (

.ADDR_OFFSET(DDR_BASE),

.BUF_SIZE(3),

.H_CNT (1920),

.V_CNT (1080)

) fdma_controller_u0

(

      //FDAM signals

      .ui_clk(ui_clk),

      .ui_rstn(ui_rstn),

1

路过

雷人

握手

鲜花

鸡蛋

刚表态过的朋友 (1 人)

最新评论

本文作者
2019-9-10 10:06
  • 1
    粉丝
  • 3189
    阅读
  • 0
    回复

关注米联客

扫描关注,了解最新资讯

联系人:汤经理
电话:0519-80699907
EMAIL:270682667@qq.com
地址:常州溧阳市天目云谷3号楼北楼201B
热门评论
排行榜