各种FIFO硬件设计(FIFO概念、异步、同步、非2次幂深度FIFO)

本文详细介绍了FIFO(先进先出)存储器的概念、分类及在IC设计中的应用,并深入探讨了同步FIFO与异步FIFO的设计原理。此外,文章还讲解了非2的次幂FIFO的设计思路与实现方法,以及如何加入将空将满信号和FIFO余量等高级特性。


(码字不易、三连支持)

一、FIFO概述

  FIFO(first in first out)是一种先进先出的存储器,与栈不同,栈对应的是一种先进后出的数据存储理念。
  FIFO无论是在IC设计中、IP核设计中、SOC设计中都存在广泛的应用。特别是随着设计复杂度的提高,在一个系统中往往会引入多个时钟,这也就使得数据的跨时钟域处理显得尤为重要,而FIFO正是解决这一问题的有效方法。
  FIFO的设计应用:
  1.德州仪器tm4c123g单片机的UART模块使用FIFO做缓冲。
  2.NXP单片机正交解码器缓冲。
  3.摄像头的数据缓冲。
  4.使用ADC配合FIFO与DMA实现高速采集。
  5.提高状态机模块的数据吞吐率(软件不需要再读状态机的busy标志位,只需关心fifo是否空满)。

二、FIFO分类

  FIFO根据读写时钟域的不同可分为同步FIFO与异步FIFO。
  同步FIFO是指读写通道均在相同的时钟下进行信号采样的FIFO,主要用于设备之间数据传输速率的匹配。
在这里插入图片描述
  异步FIFO是指读写通道在不同的时钟下进行信号采样的FIFO,主要处理跨时钟域之间的数据传输问题。
  系统2如果直接去采样处于时钟域1的系统数据,很有可能会采样到处于亚稳态的数据。使用异步FIFO对数据进行缓冲一定程度上减少了亚稳态发生的概率。
在这里插入图片描述
  (无论是同步还是异步的FIFO,都是面向数据流的一种数据存储器,都具有数据缓冲作用)。


三、FIFO重要信号与参数

3.1 信号

通道 信号 位宽 描述
nrst 1 复位信号,有效时将清空FIFO中已缓冲的数据
写通道 clk_w 1 写数据时钟,上升沿采样写通道信号
写通道 wr_en 1 写有效信号,当其无效时,FIFO将忽略写通道传输的数据;也可视为写数据的同步帧;当其有效时,FIFO会写入当前数据;
写通道 wrdata 任意(需与RAM匹配) 写数据输入,位宽视需求任意;
写通道 full 1 FIFO满信号,当写满时,此信号有效,此时FIFO将阻塞数据的输入;
写通道 almost_full 1 将满信号,当FIFO中存储的数据达到或超过设定的余量时,将有效;此时FIFO不会阻塞数据的写入,但设备可以通过此信号来决定是否继续写入数据;
读通道 clk_r 1 读数据通道时钟;(在同步FIFO中,该信号必须与clk_w接至同一时钟)
读通道 rd_en 1 读有效信号,当其无效时,FIFO停止数据的读出;也可视为读数据的同步帧;当其有效时,FIFO会读出数据;
读通道 rddata 任意(需与RAM匹配) 读数据输出,位宽视需求任意;
读通道 empty 1 空信号,当FIFO中数据为空时有效;此时FIFO将阻塞数据的读取;
读通道 almost_empty 1 将空信号,当FIFO中存储的数据达到或低于设定的最小余量时,将有效;此时FIFO不会阻塞数据的读出,但设备可以通过此信号来判断是否继续读出数据;
data_count 任意(与数据深度匹配) 存储计数器,表示当前FIFO中存储数据的数量,设备可根据该信号来判断是否写入或读出数据;

在FIFO实际的应用中,一般将empty与almost_full配合起来使用。为了减少FIFO上溢的风险,full信号很少使用。

3.2 参数

参数 描述
data_depth 数据深度,代表了FIFO缓冲数据的能力;一般为2的次幂;在支持非2次幂深度的FIFO中,可任意;
data_width 数据读写宽度,一般与内部RAM匹配数据宽度,在不匹配情况下需要对数据进行补位处理;
addr_width 地址宽度,与深度对应,其关系式:addr_width = log2(data_depth)

data_depth是否越大越好?
否,data_depth应该根据读写数据方的速率进行合理确定。
过大的data_depth会消耗过多的资源(若RAM是LUT实现,则消耗大量的LUT,若是Block RAM 则消耗Block RAM资源)

3.2.1 data_depth的确定

  深度的确定既要满足数据不丢失,且不能过多的浪费资源;
  这里首先考虑极端情况:
  如果写数据方的写入速率大于读数据方的读出速率,则FIFO的数据深度只有无穷大时,才能确保数据不溢出;显然无穷大深度的FIFO是不存在的;这种情况是无解的;
  相较于上述的极端情况,更多的是考虑写入Burst数据的情况,虽然写入数据流是连续的,但写Burst之间数据往往存在时间间隔;
  fw>fr,且读写之间没有空闲周期:
  假设fw为100Mhz,fr为80Mhz,一个Burst传输长度为2400。
  根据fw,可以知道写入一个数据需要10ns
  同理根据fr,可知读出一个数据需要12.5ns
  将2400个数据写入FIFO需要2400*10ns = 24000ns
  而在这2400个数据被写入期间,FIFO实际被读出的数据为24000/12.5 = 1920个
  则该情况下FIFO深度需要2400-1920 = 480

  其他情况下的FIFO深度计算也可以参考上述的情况,无论情形如何,最终需要计算的都是写入数据与读出数据之差;
  如写时钟大于读时钟,且连写入之间会有1时钟空闲,读出之间会有3时钟空闲;
  这种情况下其实写入的频率变为了100Mhz/(1+1) = 50Mhz,读出频率变为了80Mhz/(3+1) = 20Mhz,后面的解法就和上面一样了;
  还有些情况会给出读写使能信号的占空比,这其实也变相的给出了读写频率,如wr_en占空比50%,rd_en占空比25%则fw = 100Mhz*(50%) = 50Mhz,fr = 80Mhz*(25%) = 20Mhz;总之万变不离其宗;


四、FIFO存储原理

  FIFO的存储地址是不需要设备给出的,每次读使能有效则地址自增1,每次写使能有效则地址自增1;
  如果说RAM对应的是存储地址首尾不相接的数据空间,则FIFO的地址是首位相连的;
  如下图在初始化后,写指针与读指针指向地址0,此时FIFO为空状态;此时若强行读出数据,r_ptr则增1,到地址1的位置,很显然此处并无数据写入这个数据是错误的,这种情况为为数据下溢;
在这里插入图片描述
  当FIFO写入了7个数据后,w_ptr指向地址7,此时若再写入一个数据,写指针w_ptr将回到地址0,w_ptr与r_ptr相同此时FIFO写满,若继续写入数据,则会覆盖之前的数据,这就是数据的上溢。
在这里插入图片描述
  在FIFO实际使用中,写和读操作往往同时进行,此时w_ptr与r_ptr就会在这个环式地址空间上赛跑;假设写入了5个数据,则此时w_ptr指向了地址5,此时读出了两个数据,则r_ptr指向了地址2,如下图(红色为写入数据,蓝色为已读出的数据)。
  被读出的数据可以看作是被释放了出来,可以再次被写入数据;读写指针就这不断进行绕圈;
在这里插入图片描述

  在绕圈情况中,如果r_ptr超过了w_ptr(蓝色部分完全覆盖了红色部分),则会读出错误数据;
  若w_ptr超过r_ptr(红色部分覆盖了蓝色部分),则会出现覆盖数据的情况;
  上述两种情况需要避免,可以通过空满信号来避免;
  当r_ptr 赶上w_ptr时,则判断为空,此时FIFO阻止数据继续读出;
  当w_ptr赶上r_ptr时,则判断为满,此时FIFO阻止数据继续写入;


五、同步FIFO

5.1 空满信号判断

  同比FIFO设计中,空满信号的判断比较简单,我们只需要想办法描述上述的两种“赶上”情况就可以了;
  描述方法有很多,这里给出一个最简单的方法:
  假设深度为8,则地址宽度为3,我们在设计中对实际的地址进行1位扩位来存储“圈数”。
  此时地址枚举出来如下:
  我们可以看到在自增8之后,高位从0变为了1,此时就代表该指针已经开始跑第二圈了;若此时写指针追上了还在跑上一圈(高位不同)的读指针,则说明此时FIFO满;
  空则是相同圈内(高位相同),读指针赶上了写指针;
在这里插入图片描述
上述逻辑则可以写为:

assign fifo_empty = ((w_ptr == r_ptr)||((w_ptr=='b0)&(r_ptr=='b0))) ? 1:0;
assign  fifo_full = ((w_ptr[FIFO_ADDR_WIDTH] != r_ptr[FIFO_ADDR_WIDTH])&&(w_ptr[FIFO_ADDR_WIDTH-1:0] == r_ptr[FIFO_ADDR_WIDTH-1:0])) ? 1:0;

5.2 同步FIFO源码

module Synchronous_FIFO#(
        parameter   integer RAM_ADDR_WIDTH = 5,
        parameter   integer FIFO_DATA_DEPTH = 8,
        parameter   integer FIFO_ADDR_WIDTH = $clog2(FIFO_DATA_DEPTH),
        parameter   integer FIFO_DATA_WIDTH = 8
    )(
        input   wire                              nrst,
        input   wire                             clk_w,
        input   wire                             wr_en,
        input   wire [FIFO_DATA_WIDTH-1:0]      wrdata,
        output  wire                         fifo_full,
 
        input   wire                             clk_r,
        input   wire                             rd_en,
        output  wire [FIFO_DATA_WIDTH-1:0]      rddata,
        output  wire                        fifo_empty
    );

/////////////////////////////////////////////////////////////////////////////////////////////
/*
    w_ptr : the write data pointer (RAM Write address)
    r_ptr : the read data pointer (RAM Read address)
*/
////////////////////////////////////////////////////////////////////////////////////////////
    reg   [FIFO_ADDR_WIDTH:0] w_ptr;
    reg   [FIFO_ADDR_WIDTH:0] r_ptr;
    wire [RAM_ADDR_WIDTH-1:0] ram_wr_addr;
    wire [RAM_ADDR_WIDTH-1:0] ram_rd_addr;

    assign ram_wr_addr = {
   
   {
   
   (RAM_ADDR_WIDTH-FIFO_ADDR_WIDTH){
   
   1'b0}},w_ptr[FIFO_ADDR_WIDTH-1:0]};
    assign ram_rd_addr = {
   
   {
   
   (RAM_ADDR_WIDTH-FIFO_ADDR_WIDTH){
   
   1'b0}},r_ptr[FIFO_ADDR_WIDTH-1:0]};
/////////////////////////////////////////////////////////////////////////////////////////////
/*
    fifo_wr :  (RAM Write enable). valid only when fifo is not full and fifo data write enable  
    fifo_rd :  (RAM Read enable). valid only when fifo is not empty and fifo data read enable  
*/
////////////////////////////////////////////////////////////////////////////////////////////
    wire   fifo_wr;
    wire   fifo_rd;
    assign fifo_wr = (~fifo_full)&wr_en;
    assign fifo_rd = (~fifo_empty)&rd_en;

/////////////////////////////////////////////////////////////////////////////////////////////
/*
    fifo_empty : valid only when w_ptr is equal with r_ptr   
    fifo_full :  valid only when w_ptr's 1 MSB is equal with not( r_ptr's 1 MSB) and w_ptr's 
                 other bits equals r_ptr's
*/
////////////////////////////////////////////////////////////////////////////////////////////
    assign fifo_empty = ((w_ptr == r_ptr)||((w_ptr=='b0)&(r_ptr=='b0))) ? 1:0;
    assign  fifo_full = ((w_ptr[FIFO_ADDR_WIDTH] != r_ptr[FIFO_ADDR_WIDTH])&&(w_ptr[FIFO_ADDR_WIDTH-1:0] == r_ptr[FIFO_ADDR_WIDTH-1:0])) ? 1:0;


    always @(posedge clk_w,negedge nrst) begin
        if (~nrst) begin
            // reset
            w_ptr <= 'b0;
        end
        else begin
            if(fifo_wr)
                w_ptr <= w_ptr + 'b1;
            else
                w_ptr <= w_ptr ;
        end
    end

    always @(posedge clk_r,negedge nrst) begin
        if (~nrst) begin
            // reset
            r_ptr <= 'b0;
        end
        else begin
            if(fifo_rd)
                r_ptr <= r_ptr + 'b1;
            else
                r_ptr <= r_ptr ;
        end
    end


    blk_mem_gen_0 ram0(
            .addra (ram_wr_addr),
            .clka  (clk_w),
            .dina  (wrdata),
            .wea   (1),
            .ena   (fifo_wr),
            
            .addrb (ram_rd_addr),
            .clkb  (clk_r),
            .doutb (rddata),
            .enb   (fifo_rd)
    );
endmodule

5.3 测试源码

module Synchronous_FIFO_tb(

    );

    parameter   integer FIFO_DATA_DEPTH = 8;
    parameter   integer FIFO_DATA_WIDTH = 8;

        reg                              nrst;
        reg                             clk;
        reg                             wr_en;
        reg [FIFO_DATA_WIDTH-1:0]      wrdata;
        wire                        fifo_full;
        reg                             rd_en;
        wire [FIFO_DATA_WIDTH-1:0]     rddata;
        wire                       fifo_empty;
    
    integer i;
    initial begin
        clk = 0;
        forever begin
            #1 clk = ~clk;
        end
    end


    initial begin
            nrst = 0;
        #2  nrst = 1;
            wr_en = 1;
            wrdata = 0;
            rd_en = 0;
            while(wrdata <= 8)
            begin
               #2 wr_en  = 1;
                  wrdata = wrdata + 1;
            end
            wr_en = 0;
            rd_en = 1;
    end


   Synchronous_FIFO#(
       . FIFO_DATA_DEPTH(FIFO_DATA_DEPTH),
       . FIFO_DATA_WIDTH(FIFO_DATA_WIDTH)
    )Synchronous_FIFO_inist0(
        . nrst(nrst),
        . clk_w(clk),
        . wr_en(wr_en),
        . wrdata(wrdata),
        . fifo_full(fifo_full),
 
        . clk_r(clk),
        . rd_en(rd_en),
        . rddata(rddata),
        . fifo_empty(fifo_empty)
    );
endmodule

5.4 功能仿真结果

在这里插入图片描述

六、异步FIFO

6.1 异步FIFO架构

  异步FIFO设计则需要考虑同步问题;
  此时同步FIFO中指针计数器会出现亚稳态问题,因为地址从上一个变化到下一个会产生多个比特位的变化,如0111到1000,变化的比特位为4位,由于布局布线的问题,每一比特位的变化速度不同,这将导致采集到不可预测的错误数据;
  因此我们需要另一种比特位变化更少的编码,即格雷码;
*二进制码与格雷码对照表
在这里插入图片描述
  如上表,相邻的两位变化的比特位仅为1;除此之外,格雷码还具有很重要的对称性,我们可以在上图的7到8之间画一条分界线,除了高位,其余为按照这条分界线对称;
  另一方面,需要将转换后的格雷码进行时钟域同步,这里采用典型的二级同步器;
  空逻辑判断:
  将w_ptr对应的格雷码w2r_ptr_gray同步到读时钟域,再将同步后的w2r_ptr_gray与r_ptr对应的格雷码r_ptr_gray进行对比,若两者相等则判定为空;

assign fifo_empty = (r_ptr_gray == w2r_ptr_gray) ? 1:0;

  满逻辑判断:
  将r_ptr对应的格雷码r2w_ptr_gray同步到写时钟域,再将同步后的r2w_ptr_gray与w_ptr对应的格雷码w_ptr_gray进行对比,若两者高两位为取反关系,剩余位相同,则判断为满;

 assign
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PPRAM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值