关灯
请选择 进入手机版 | 继续访问电脑版
米联客uisrc 首页 课程 查看内容
1

米联客(MSXBO)浅谈XILINX FPGA FIFO使用

摘要: 考虑到很多客户对于FPGA的基础知识掌握不够扎实,也不是每个客户的悟性都非常高,所以准备在原来的FPGA基础入门10个课时基础上再增加一些demo,给大家FPGA学习使用。当然有基础的完全可以跳过基础部分内容。

XILINX FIFO使用入门

1.1概述

考虑到很多客户对于FPGA的基础知识掌握不够扎实,也不是每个客户的悟性都非常高,所以准备在原来的FPGA基础入门10个课时基础上再增加一些demo,给大家FPGA学习使用。当然有基础的完全可以跳过基础部分内容。

         首先来大概了解下说明是否FIFO ,FIFO( First Input First Output)简单说就是指先进先出。FIFO也是缓存机制的一种,下面是我总结的FIFO的三大用途:

1)、提高传输效率,增加DDR带宽的利用率。比如我们有4路视频数据缓存到DDR中去,比较笨的方法是,每个通道视频数据对应一颗DDR。现在对于DDR来说非常浪费,因为现在的DDR3可以跑1600Mbps DDR4可以跑到2400Mbps,如果你还是把一路视频数据对应一颗DDR显然严重浪费了带宽。加入FIFO后,只要把4路数据先缓存进入DDR,在缓存的过程中,快速得把数据从FIFO取出并且写入到DDR中,只要FIFO没有满就不会出现数据丢失。现在我们带宽够用,FIFO给的足够大就可以确保数据不丢失。

2)、数据位宽转换,比如我们有32bit的数据需要转换成128bit或者32bit的数据需要转换成8bit,那么用FIFO来转换也是非常方便的。

3)、跨时钟域的应用,比如数据是2个不同步的时钟,那么我们就可以用FIFO实现跨时钟域的传输。

以上总计的三点,很多时候是混合使用的。FIFO的用途非常大,我们在后面的例子中也看到,只要涉及到DDR传输的都和FIFO有关系。

我们这里的例子通过仿真告诉大家FIFO的基本用法,有两条我总结的办法,包括:

1)半空半/满法

2)关键信号法

1.2配置FIFO IP

点击软件左侧的IP Catalog

输入关键词fifo,会出来非常多的FIFO类型

1)、AXI4-Stream FIFO内核旨在提供对与其他IP连接的AXI4-Stream接口(例如AXI以太网内核)的内存映射访问。 必须通过Vivado Design Suite构建系统,以连接AXI4-Stream FIFO内核,AXI以太网内核,处理器,内存,互连总线,时钟和其他嵌入式组件。

2)、AXI4-Stream Data FIFO 支持AXI4-Stream协议,具备packet包传输模式。

3)、AXI Data FIFO 就是数据FIFO 功能较为单一,接口为Stream接口

4)、FIFO Generator 支持Native 模式,AXI Memory Mapped模式 AXI Steam模式功能比较齐全,在没有AXI4或者AXI Stream协议的场合下,我们更多使用Native模式,这里的课程也以Native模式讲解。


选择First Word Fall Through 这样写入的数据,会先在读端口准备好,否作如果选择Standard FIFO需要读使能后一个时钟输出才有效。

观察almost full 和almost empty flag 这两个信号是可编程的,一些应用场景也是可以用到。

设置读计数器和写计数器,这不是必须的,我们第一个半空半满方法需要用到。

1.3半空/半满法控制读写FIFO

半空/半满法,功法要点:半空是针对读FIFO计数器而言,半满是针对写FIFO计数器而言;这里强调一点,FIFO的计数器并不精准,计数器会有几个时钟的延迟,所以这里的半空,半满的计数器大部分时候都是不精确的,除非你把程序停下来等几个时钟周期,显然这样不科学,会降低程序的效率。虽然不精确但是完全够用。因为读写FIFO一直在一个动态平衡中。

比如,我们这里的FIFO输入32bit 深度1024;输出128bit 深度256,这里的半空值就是128,半满值就是512。下面设计我们的状态机:

1)、状态0:当写入FIFO计数器小于512 则进入状态1

2)、状态1:当连续写入FIFO 512个数据后,再次进入状态0等待

读状态机的设计,每次读出128bt数据:

1)、状态0:当读FIFO计数器大于128 则进入状态1

2)、状态1:连续读出FIFO 128个数据后,再次进入状态0等待

1.4测试代码

fifo_test.v

module fifo_test(

input sys_clk_i,

input rstn_i

    );


wire clk_100m;

wire clk_150m;

wire clk_locked;


clk_wiz_0 clk_inst

   (

    // Clock out ports

    .clk_out1(clk_100m),     // output clk_out1

    .clk_out2(clk_150m),     // output clk_out2

    // Status and control signals

    .resetn(rstn_i), // input resetn

    .locked(clk_locked),       // output locked

   // Clock in ports

    .clk_in1(sys_clk_i)

);      // input clk_in1


// write process

reg WR_REQ = 1'b0;

reg [0:0]WR_S;

reg [9:0]wr_cnt;

reg wr_en;

always @(posedge clk_100m)begin

    if(!clk_locked)begin

        WR_S <= 1'b0;

        wr_cnt <= 10'd0;

        wr_en <= 1'b0;

    end

    else begin

        case(WR_S)

        0:begin

            wr_cnt <= 10'd0;

            if(WR_REQ)

                WR_S <= 1'b1;

        end

        1:begin

            if(wr_cnt < 512)begin

                wr_en  <= 1'b1;

                wr_cnt <= wr_cnt+1'b1;

            end

            else begin

                wr_en <= 1'b0;

                WR_S <= 1'b0;

            end

        end

        endcase

    end

end


// read process

// write process

reg RD_REQ = 1'b0;

reg [0:0]RD_S;

reg [7:0]rd_cnt;

reg rd_en;

always @(posedge clk_150m)begin

    if(!clk_locked)begin

        RD_S <= 1'b0;

        rd_cnt <= 8'd0;

        rd_en <= 1'b0;

    end

    else begin

        case(RD_S)

        0:begin

            rd_cnt <= 8'd0;

            if(RD_REQ)

                RD_S <= 1'b1;

        end

        1:begin

            if(rd_cnt < 128)begin

                rd_en  <= 1'b1;

                rd_cnt <= rd_cnt+1'b1;

            end

            else begin

                rd_en <= 1'b0;

                RD_S <= 1'b0;

            end

        end

        endcase

    end

end


wire [7 : 0] rd_data_count;

wire [9 : 0] wr_data_count;


always @(posedge clk_100m)begin

    WR_REQ <= (wr_data_count < 10'd512);

end


always @(posedge clk_150m)begin

    RD_REQ <= (rd_data_count > 8'd128);

end


wire [127:0]rd_data;

wire full;

wire empty;

wire almost_full;

wire almost_empty;


FIFO32_2_64 FIFO32_2_64_inst (

  .wr_clk(clk_100m),                // input wire wr_clk

  .wr_rst(1‘b0),                // input wire wr_rst

  .rd_clk(clk_150m),                // input wire rd_clk

  .rd_rst(1‘b0),                // input wire rd_rst

  .din({wr_cnt[7:0],8'hff,8'h00,wr_cnt[7:0]}),                      // input wire [31 : 0] din

  .wr_en(wr_en),                  // input wire wr_en

  .rd_en(rd_en),                  // input wire rd_en

  .dout(rd_data),                    // output wire [127 : 0] dout

  .full(full),                    // output wire full

  .almost_full(almost_full),      // output wire almost_full

  .empty(empty),                  // output wire empty

  .almost_empty(almost_empty),    // output wire almost_empty

  .rd_data_count(rd_data_count),  // output wire [7 : 0] rd_data_count

  .wr_data_count(wr_data_count)  // output wire [9 : 0] wr_data_count

);   


以上代码中关键的控制状态机写FIFO读FIFO的代码如下:

always @(posedge clk_100m)begin

    WR_REQ <= (wr_data_count < 10'd512);

end


always @(posedge clk_150m)begin

    RD_REQ <= (rd_data_count > 8'd128);

end

对于初学者一定要注意,FIFO复位需要经过多个时钟周期后才能完成复位,所以我们这里把FIFO复位设置了常量0,让FIFO在有时钟后就能自己完成复位,FIFO 复位的信号可以根据实际情况去应用,比如我们在后面的图像缓存方面,我们会用图像的VS去复位和同步FIFO,具体的理解还要在实际应用中加深。

下面我们进行仿真,如何编写tb文件,和调用仿真波形图,我就不详细讲了,不懂的人看前面FPGA入门的几个例子。我下面直接给出仿真代码。

仿真TB文件

module tb_fifo_test;


reg sys_clk_i;

reg rstn_i;


fifo_test fifo_test_inst(

.sys_clk_i(sys_clk_i),

.rstn_i(rstn_i)

);

   

   initial begin

      sys_clk_i = 1'b0;

      rstn_i    = 1'b0;

      #5;sys_clk_i = 1'b1;

      #5;sys_clk_i = 1'b0;

      #5;sys_clk_i = 1'b1;

      rstn_i    = 1'b1;

      forever

         #5 sys_clk_i = ~sys_clk_i;

   end

                           

endmodule


仿真结果

写入FIFO的计数器值第一个值是1



每次写数据计数器最后一个值是512

读出FIFO的计数器值1

最后一个读出的值,由于采取8bit写入FIFO,所以512对应的是0

1.5关键信号法

关键信号法就是利用关键的信号,比如FIFO满标准,FIFO空标志,FIFO可编程空标志,和FIFO可编程满标志,FIFO读Valid标志,去控制FIFO的读写。

事实上,FIFO的满标志是正确的,也就是说FIFO输出满标志的。

修改FIFO IP 增加valid信号观测

1.5.1 关键信号理解

我们先用一段有问题的代码,也是对于初学者常规的理解思路去测试FIFO

1)、当FIFO非满的时候写

2)、当FIFO非空的时候读

以下是测试代码

module fifo_test(

input sys_clk_i,

input rstn_i

    );


wire clk_100m;

wire clk_150m;

wire clk_locked;


clk_wiz_0 clk_inst

   (

    // Clock out ports

    .clk_out1(clk_100m),     // output clk_out1

    .clk_out2(clk_150m),     // output clk_out2

    // Status and control signals

    .resetn(rstn_i), // input resetn

    .locked(clk_locked),       // output locked

   // Clock in ports

    .clk_in1(sys_clk_i)

);      // input clk_in1


// write process


wire [127:0]rd_data;

wire full;

wire empty;

wire almost_full;

wire almost_empty;

reg [7:0]wr_cnt;

always @(posedge clk_100m)begin

    if(!clk_locked)begin

        wr_cnt <= 10'd0;

    end

    else begin

        if(!full) wr_cnt <= wr_cnt+1'b1;

    end

end



wire [7 : 0]rd_data_count;

wire [9 : 0] wr_data_count;


FIFO32_2_64 FIFO32_2_64_inst (

  .wr_clk(clk_100m),                // input wire wr_clk

  .wr_rst(1'b0),                // input wire wr_rst

  .rd_clk(clk_150m),                // input wire rd_clk

  .rd_rst(1'b0),                // input wire rd_rst

  .din({wr_cnt[7:0],wr_cnt[7:0],wr_cnt[7:0],wr_cnt[7:0]}),                      // input wire [31 : 0] din

  .wr_en(!full&&clk_locked),                  // input wire wr_en

  .rd_en(empty&&clk_locked),                  // input wire rd_en

  .dout(rd_data),                    // output wire [127 : 0] dout

  .full(full),                    // output wire full

  .almost_full(almost_full),      // output wire almost_full

  .empty(empty),                  // output wire empty

  .almost_empty(almost_empty),    // output wire almost_empty

  .rd_data_count(rd_data_count),  // output wire [7 : 0] rd_data_count

  .wr_data_count(wr_data_count)  // output wire [9 : 0] wr_data_count

);   

在这种情况下,由于我们的写时钟满于读时钟,FIFO full信号一直为0,但是读FIFO的empty,作为读信号,显然是不对的,这也证明的empty信号有延迟输出的。但是我们看到valid信号是正确的,所以下面再改为valid去读FIFO.

修改后的代码

module fifo_test(

input sys_clk_i,

input rstn_i

    );


wire clk_100m;

wire clk_150m;

wire clk_locked;


clk_wiz_0 clk_inst

   (

    // Clock out ports

    .clk_out1(clk_100m),     // output clk_out1

    .clk_out2(clk_150m),     // output clk_out2

    // Status and control signals

    .resetn(rstn_i), // input resetn

    .locked(clk_locked),       // output locked

   // Clock in ports

    .clk_in1(sys_clk_i)

);      // input clk_in1


// write process


wire [127:0]rd_data;

wire full;

wire empty;

wire almost_full;

wire almost_empty;

wire valid;

reg [7:0]wr_cnt;

always @(posedge clk_100m)begin

    if(!clk_locked)begin

        wr_cnt <= 10'd0;

    end

    else begin

        if(!full) wr_cnt <= wr_cnt+1'b1;

    end

end



wire [7 : 0]rd_data_count;

wire [9 : 0] wr_data_count;


FIFO32_2_64 FIFO32_2_64_inst (

  .wr_clk(clk_100m),                // input wire wr_clk

  .wr_rst(1'b0),                // input wire wr_rst

  .rd_clk(clk_150m),                // input wire rd_clk

  .rd_rst(1'b0),                // input wire rd_rst

  .din({wr_cnt[7:0],wr_cnt[7:0],wr_cnt[7:0],wr_cnt[7:0]}),                      // input wire [31 : 0] din

  .wr_en(!full&&clk_locked),                  // input wire wr_en

  .rd_en(valid&&clk_locked),                  // input wire rd_en

  .dout(rd_data),                    // output wire [127 : 0] dout

  .full(full),                    // output wire full

  .almost_full(almost_full),      // output wire almost_full

  .empty(empty),                  // output wire empty

  .valid(valid),                  // output wire valid

  .almost_empty(almost_empty),    // output wire almost_empty

  .rd_data_count(rd_data_count),  // output wire [7 : 0] rd_data_count

  .wr_data_count(wr_data_count)  // output wire [9 : 0] wr_data_count

);   



对于代码简单修改,仿真可以线点击1的位置重新load代码,然后再点击2运行。运行一会可以手动停止。

相信阅读完本文,根据本文把FIFO的例子做一遍你就能基本掌握FIFO的使用了,我是比较推荐第一种的半空半满法。

对于Stream接口的FIFO使用起来也差不多,因为本质上他们都是FIFO。在后期的课程中你们会看到Stream FIFO的应用,使用Steam接口有利于在新的FPGA设计中统一接口,方便代码的标准化,我们这里暂时就不讨论了。


路过

雷人

握手

鲜花

鸡蛋

说点什么...

已有1条评论

最新评论...

旺大侠2020-1-2 14:41引用

感谢分享

查看全部评论(1)

本文作者
2019-9-23 16:48
  • 4
    粉丝
  • 506
    阅读
  • 1
    回复
相关分类
资讯幻灯片
热门评论
热门专题
排行榜

关注我们:微信公众号

官方微信

官方微信

客服热线:

0519-80699907

公司地址:常州溧阳市天目云谷3号楼北楼2楼

运营中心:常州溧阳市天目云谷3号楼北楼2楼

邮编:213300 Email:270682667#qq.com

Copyright   ©2019-2026  米联客uisrc内容版权归©UISRC.COM技术支持:UISRC.COM  备案号:苏ICP备19046771号