FIFO

FIFO即First In First Out,是一种先进先出数据存储、缓冲器,我们知道一般的存储器是用外部的读写地址来进行读写,而FIFO这种存储器的结构并不需要外部的读写地址而是通过自动的加一操作来控制读写,这也就决定了FIFO只能顺序的读写数据。下面我们就介绍一下同步FIFO和异步FIFO

1、FIFO分类

同步FIFO,读和写应用同一个时钟。它的作用一般是做交互数据的一个缓冲,也就是说它的主要作用就是一个buffer。
异步FIFO,读写应用不同的时钟,它有两个主要的作用,一个是实现数据在不同时钟域进行传递,另一个作用就是实现不同数据宽度的数据接口。

2、FIFO的主要参数

同步FIFO和异步FIFO略有不同,下面的参数适用于两者。

  • 宽度,用参数FIFO_data_size表示,也就是FIFO存储的数据宽度;
  • 深度,用参数FIFO_addr_size表示,也就是地址的大小,也就是说能存储多少个数据;
  • 满标志full,当FIFO中的数据满了以后将不再能进行数据的写入;
  • 空标志empty,当FIFO为空的时候将不能进行数据的读出;
  • 写地址w_addr,由自动加一生成,将数据写入该地址;
  • 读地址r_addr,由自动加一生成,将该地址上的数据读出;

同步FIFO和异步FIFO的最主要的不同就体现在空满标志产生的方式上,由此引出两者一些不同的参数。
同步FIFO

  • 时钟clk,rst,读写应用同一个时钟;
  • 计数器count,用计数器来进行空满标志的判断;

异步FIFO

  • 时钟clk_w,rst_w,clk_r,rst_r,读写应用不同的时钟;
  • 指针w_pointer_gray,r_pointer_gray,用指针来判断空满标识;
  • 同步指针w_pointer_gray_sync,r_pointer_gray_sync,指针的同步操作,用来做对比产生空满标志符;

3、同步FIFO

FIFO主要的设计难点在于如何产生空满标志,在同步FIFO中,我们定义一个计数器,当计数器的值为0时,产生空标志,当计数器的值为FIFO的深度时,产生满标志。基于以上的思想,可以将同步FIFO划分为以下几个模块:write、read、count、RAM

3.1 模块划分

同步FIFO主要划分为四个模块,RAM模块是用来读取和写入数据;write模块是用来产生写地址;read模块是用来产生读地址;count模块是用来产生空满标志符,每写入一位数,count加一,每读出一位数,count减一。
下面是各个模块的连接框图:

同步FIFO.png

3.2 同步FIFO代码

源文件

module FIFO_sync(
              clk,
              rst,
              w_en,
              r_en,
              data_in,
              data_out,
              count,
              full,
              empty
              );

  parameter FIFO_data_size=3,
            FIFO_addr_size=2;


   input clk,rst;
   input w_en,r_en;
   input[FIFO_data_size-1:0] data_in;
   output[FIFO_data_size-1:0] data_out;
   output full,empty;
   output[FIFO_addr_size:0]count;

  reg [FIFO_data_size-1:0] data_out;
  reg [FIFO_addr_size:0]count;
  reg [FIFO_addr_size-1:0]w_addr,r_addr;
  reg [FIFO_data_size-1:0]mem[{FIFO_addr_size{1'b1}}:0];
  integer i;

 //memory的初始化以及写操作
  always@(posedge clk or negedge rst)
    begin
     if(!rst)
begin
  w_addr<=0;
  for(i=0;i<={FIFO_addr_size{1'b1}};i=i+1)
  mem[i]<={FIFO_data_size{1'b0}};
end
   else if(w_en&(~full))
     begin
     mem[w_addr]<=data_in;
     w_addr<=w_addr+1;
   end
end

//读操作
    always@(posedge clk or negedge rst)
    begin
   if(!rst)
begin
data_out<={(FIFO_data_size-1){1'b0}};
r_addr<=0;
end
 else if(r_en&(~empty))
   begin
    data_out<=mem[r_addr];
    r_addr<=r_addr+1;
  end
end

 //count产生空满标志符
always@(posedge clk or negedge rst)
  begin
     if(!rst)
count<=0;
    else if(((w_en)&(~full))&(~((r_en)&(~empty))))
     count<=count+1;
    else if(((r_en)&(~empty))&(~((w_en)&(~full)))) 
    count<=count-1;
end 

  assign empty=(count==0);
 assign full=(count=={FIFO_addr_size{1'b1}}+1);

 endmodule 

测试代码

`timescale 1ns/1ns
 module FIFO_sync_top;

  reg clk,rst,w_en,r_en;
  reg[2:0]data_in;

  wire[2:0]count;
  wire[2:0]dtat_out;

   reg[2:0]i;

  initial
 begin
   clk=0;
   rst=1;
   data_in=3'b000;
   w_en=0;
   r_en=0;
   #25
   rst=0;
   #50
   rst=1;
   #25
   w_en=1;
   #100
   r_en=1;
   #100
   w_en=0;
   r_en=0;
   #100
   w_en=1;
   #400
   r_en=1;
 end
 
  initial
begin
  for(i=0;i<=50;i=i+1)
  #100 data_in=i;
end
 
always
#50 clk=~clk;

  FIFO_sync  #(.FIFO_data_size(3),.FIFO_addr_size(2)) ut(
                                                       .clk(clk),
                                                       .rst(rst),
                                                       .data_in(data_in),
                                                       .data_out(data_out),
                                                       .w_en(w_en),
                                                       .r_en(r_en),
                                                       .count(count),
                                                       .full(full),
                                                       .empty(empty)
                                                      );
           
        
        
endmodule
同步FIFO波形图.png

4、异步FIFO

异步FIFO的设计难点在于空满标志符的产生,由于异步FIFO的读写是用不同的时钟来控制的,所以不能采用计数器的方法来产生空满标志符,就好像同一个变量不能再两个always块里赋值一样,所以我们必须寻求新的方法来产生空满标志符。

4.1 空满标志

我们知道FIFO的状态是满还是空,他们的相同的判断条件都是w_addr=r_addr,但到底是空还是满我们还不能确定。在这里介绍一种方法来判断空满状态。我们设定一个指针r_pointer_bin,w_pointer_bin,宽度为[FIFO_addr_size:0],也就是说比传统的地址多一位,我们就用这多出来的一位做空满判断。

  • 如果是满状态的话,也就是说w_pointer_binr_pointer_bin多走了一圈,反应在数值上就是w_pointer_bin和r_pointer_bin的最高位不相同
  • 如果是空状态的话,也就是说w_pointer_binr_pointer_bin的路径相同,反应在数值上就是w_pointer_bin和r_pointer_bin的每一位相等

如下例子所示:
FIFO_addr_size=2;FIFO_data_size=3;

异步FIFO满标志的产生.png

异步FIFO空标志的产生.png

4.2 格雷码

将一个时钟域上的指针r_pointer_bin/w_pointer_bin同步到另一个时钟域,如果数据用二进制的方式进行同步的话就会出现多位数据同时跳变的问题,比如3'b011到3'b100即3到4跳变会引起多位数据的改变,这样会大大增加出错的概率。Gray 码就很好的解决了上述问题,gray码相邻数据只有一位跳变,这样就大大降低了数据出错的概率。下面 以一个例子介绍一下二进制码向格雷码的转化的算法。

二进制转化为格雷码.png

在不同时钟域进行数据交换的时候我们一般采用格雷码的数据形式进行数据传递,这样能很大程度上降低出错的概率。
引入格雷码同时也引入一个问题,就是数据空满标志的判断不再是二进制时候的判断标准。

  • 如果是空状态的话,无可厚非,仍然是要满足r_pointer_gray和w_pointer_gray每一位都相等
  • 如果是满状态的话,我们以二进制为例,应该满足r_pointer_bin=3'b111,w_pointer_bin=3'b011,相对应的格雷码应该满足r_pointer_gray=3'b100,w_pointer_gray=3'b010,通俗来讲,满状态要满足r_pointer_gray和w_pointer_gray的高位和次高位相反,其余各位相等

同时由于格雷码的引入,使得FIFO的深度只能是2的幂次方。

4.3 数据同步

我们知道满状态以后数据就不能进行写入,空状态以后数据就不能进行读出。由此,我们在write模块进行满状态的判断,在read模块进行空状态的判断

  • 在满状态的判断时,我们要用到r_pointer_gray,为了避免亚稳态,选择两级D触发器相串联构成的同步模块来传送r_pointer_gray,最后用r_pointer_gray_sync和w_pointer_gray相比较产生full信号。
  • 在空状态的判断时,同理我们要用w_pointer_gray_sync和r_pointer_gray相比较产生empty信号。

两拍延时的数据同步对空满标志产生的影响
由此信号r_pointer_gray经过两级D触发器,就会有两拍的延时形成r_pointer_gray_sync信号,所以在进行比较的时候就不是实时的r_pointer_gray与w_pointer_gray进行比较,而是两拍之前的r_pointer_gray即r_pointer_gray_sync与此刻的w_pointer_gray进行比较。那么问题就来了这与我们的本意其实是不相符的,其实是这样的,这是一种最坏情况的考虑,将r_pointer_gray_sync与w_pointer_gray相比较是为了产生full信号,在用于数据同步的这两拍里面有可能再进行读操作,所以用于比较时的读地址一定小于或等于当前的读地址,就算此刻产生full信号,其实FIFO有可能还没有满。这也就为设计留了一些设计的余量。同理,就算有empty信号的产生,FIFO有可能还有数据。这种留余量的设计在实际的工程项目中是很常见的。

4.4 模块的划分

异步FIFO将模块划分为4个部分,RAM、write_full、read_empty、synchronization。RAM根据读写地址进行数据的写入和读出,write_full根据clk_w产生写地址和full信号,read_empty根据clk_r产生读地址和empty信号,synchronization用于同步w_pointer_gray到读时钟域或者同步r_pointer_gray到写时钟域。
下面是各个模块的连接图:

异步FIFO.png

4.5 异步FIFO代码

源文件

  • 顶层文件

    module FIFO_async(
                clk_w,
                rst_w,
                w_en,
                clk_r,
                rst_r,
                r_en,
                data_in,
                data_out,
                empty,
                full
                );
    parameter FIFO_data_size=6;
    parameter FIFO_addr_size=5;
    
    input clk_w,rst_w,w_en;
    input clk_r,rst_r,r_en;
    input[FIFO_data_size-1:0]data_in;
    output[FIFO_data_size-1:0]data_out;
    output empty,full;
    
    wire[FIFO_addr_size:0]r_pointer_gray_sync,w_pointer_gray_sync;
    wire[FIFO_addr_size:0]r_pointer_gray,w_pointer_gray;
    wire[FIFO_addr_size-1:0]w_addr,r_addr;
    
    RAM #(FIFO_data_size,FIFO_addr_size)
      I0(.clk_w(clk_w),
         .rst_w(rst_w),
         .clk_r(clk_r),
         .rst_r(rst_r),
         .full(full),
         .empty(empty),
          .w_en(w_en),
          .r_en(r_en),
          .w_addr(w_addr),
          .r_addr(r_addr),
          .data_in(data_in),
          .data_out(data_out)); 
          
    synchronization #(FIFO_addr_size)
        I1(.clk(clk_r),
           .rst(rst_r),
           .din(w_pointer_gray),
           .dout(w_pointer_gray_sync));
           
    synchronization #(FIFO_addr_size)
        I2(.clk(clk_w),
           .rst(rst_w),
           .din(r_pointer_gray),
           .dout(r_pointer_gray_sync));
           
    write_full #(FIFO_addr_size)
         I3(.clk_w(clk_w),
            .rst_w(rst_w),
            .w_en(w_en),
            .r_pointer_gray_sync(r_pointer_gray_sync),
            .w_pointer_gray(w_pointer_gray),
            .w_addr(w_addr),       
            .full(full));
            
    read_empty #(FIFO_addr_size)
          I4(.clk_r(clk_r),
             .rst_r(rst_r),
             .r_en(r_en),
             .w_pointer_gray_sync(w_pointer_gray_sync),
             .r_pointer_gray(r_pointer_gray),
             .r_addr(r_addr),       
             .empty(empty));
             
    endmodule
    

RAM

module RAM(
        clk_w,
        rst_w,
        clk_r,
        rst_r,
        full,
        empty,
        w_en,
        r_en,
        r_addr,
        w_addr,
        data_in,
        data_out
              );

parameter FIFO_data_size=3,
          FIFO_addr_size=2;
      
input clk_w,rst_w;
input clk_r,rst_r;
input w_en,r_en;
input full,empty;
input [FIFO_addr_size-1:0]w_addr,r_addr;
input [FIFO_data_size-1:0]data_in;
output[FIFO_data_size-1:0]data_out;
reg[FIFO_data_size-1:0]data_out;

reg[FIFO_data_size-1:0]mem[{FIFO_addr_size{1'b1}}:0];
integer i;

always@(posedge clk_w or negedge rst_w)
begin
  if(!rst_w)
for(i=1;i<=FIFO_data_size;i=i+1)
mem[i]<={FIFO_data_size{1'b0}};
  else if((w_en==1)&&(full==0))
mem[w_addr]<=data_in;
 end

always@(posedge clk_r or negedge rst_r)
 begin
  if(!rst_r)
    data_out<={(FIFO_data_size-1){1'b0}};
  else if((r_en==1)&&(empty==0))
   data_out<=mem[r_addr];
 end                     
    
endmodule

write_full

module write_full(
              clk_w,
              rst_w,
              w_en,
              r_pointer_gray_sync,
              //w_pointer_bin,
              w_pointer_gray,
              w_addr,       
              full
              );
              
 parameter FIFO_addr_size=2;

  input clk_w,rst_w,w_en;
  input [FIFO_addr_size:0]r_pointer_gray_sync;
  output full;
  output [FIFO_addr_size-1:0]w_addr;
  output [FIFO_addr_size:0]w_pointer_gray;
  reg [FIFO_addr_size:0]w_pointer_bin;

  wire [FIFO_addr_size:0]w_pointer_gray;
  wire [FIFO_addr_size-1:0]w_addr;

  always@(posedge clk_w or negedge rst_w)
   begin
     if(!rst_w)
   w_pointer_bin<={(FIFO_addr_size){1'b0}};
    else if((w_en==1)&&(full==0))
   w_pointer_bin<=w_pointer_bin+1;
 end
   
assign w_pointer_gray=(w_pointer_bin>>1)^w_pointer_bin; 
assign w_addr=w_pointer_bin[FIFO_addr_size-1:0];
assign full=w_pointer_gray=={~r_pointer_gray_sync[FIFO_addr_size:FIFO_addr_size-1],r_pointer_gray_sync[FIFO_addr_size-2:0]}? 1:0;

endmodule 

read_empty

 module read_empty(
              clk_r,
              rst_r,
              r_en,
              w_pointer_gray_sync,
             // r_pointer_bin,
              r_pointer_gray,
              r_addr,       
              empty
              );
              
parameter FIFO_addr_size=2;

 input clk_r,rst_r,r_en;
 input [FIFO_addr_size:0]w_pointer_gray_sync;
 output empty;
 output [FIFO_addr_size-1:0]r_addr;
 output [FIFO_addr_size:0]r_pointer_gray;
 reg [FIFO_addr_size:0]r_pointer_bin;

 wire [FIFO_addr_size:0]r_pointer_gray;
 wire [FIFO_addr_size-1:0]r_addr;

 always@(posedge clk_r or negedge rst_r)
 begin
   if(!rst_r)
   r_pointer_bin<={(FIFO_addr_size){1'b0}};
  else if((r_en==1)&&(empty==0))
   r_pointer_bin<=r_pointer_bin+1;
end
   
assign r_pointer_gray=(r_pointer_bin>>1)^r_pointer_bin; 
assign r_addr=r_pointer_bin[FIFO_addr_size-1:0];
assign empty=r_pointer_gray==w_pointer_gray_sync?1:0;

endmodule 

synchroization

module synchronization(
                   clk,
                   rst,
                   din,
                   dout
                   );

   parameter FIFO_addr_size=2;
          
  input clk,rst;
  input[FIFO_addr_size:0] din;
  output[FIFO_addr_size:0] dout;
  reg[FIFO_addr_size:0] dout;

  reg [FIFO_addr_size:0] dout1;

  always@(posedge clk or negedge rst)
  begin
if(!rst)
  begin
  dout<={(FIFO_addr_size+1){1'b0}};
  dout1<={(FIFO_addr_size+1){1'b0}};
end
  else
begin 
 dout1<=din;
dout<=dout1;
 end
end
    
endmodule

测试文件

 `timescale 1ns/1ns
  module FIFO_async_top;

  parameter FIFO_data_size=3,
      FIFO_addr_size=2;
 
   reg clk_r,rst_r,w_en,r_en,clk_w,rst_w;
   reg[FIFO_data_size-1:0]data_in;
   wire[FIFO_addr_size-1:0]data_out;
   wire empty,full;
 
   reg[FIFO_data_size-1:0]i;

  initial
 begin
   clk_w=0;
   rst_w=1;
   data_in={FIFO_data_size{1'b0}};
   #15
   rst_w=0;
   #20
   rst_w=1;
 end

   initial
 begin
   clk_r=0;
   rst_r=1;
   r_en=0;
   #25
   rst_r=0;
   #50
   rst_r=1;
 end
 
 initial
  begin
    w_en=0;
    #450
    w_en=1;
    #400
     w_en=0;
     #750
     w_en=1;
  end
  
  initial
    begin
      r_en=0;
      #900
      r_en=1;
      #400
      r_en=0;
      #300
      r_en=1;
    end
    
   initial
begin
  for(i=0;i<=50;i=i+1)
  #100 data_in=i;
end
 
always
#25 clk_w=~clk_w;
always
#50 clk_r=~clk_r;


  FIFO_async #(.FIFO_data_size(FIFO_data_size),.FIFO_addr_size(FIFO_addr_size)) 
            u1(.clk_w(clk_w),
              .rst_w(rst_w),
              .w_en(w_en),
              .clk_r(clk_r),
              .rst_r(rst_r),
              .r_en(r_en),
              .data_in(data_in),
              .data_out(data_out),
              .empty(empty),
              .full(full)
              );
           
endmodule
异步FIFO波形图.png

5、FIFO的深度计算

其实FIFO的深度可大可小,并没有一个具体的公式能够精确计算出FIFO深度的大小。在FIFO实际工作中,其数据的满/空标志可以控制数据的继续写入或读出。在一个具体的应用中也不可能由一些参数算数精确的所需FIFO深度为多少,这在写速度大于读速度的理想状态下是可行的,但在实际中用到的FIFO深度往往要大于计算值。一般来说根据电路的具体情况,在兼顾系统性能和FIFO成本的情况下估算一个大概的宽度和深度就可以了。而对于写速度慢于读速度的应用,FIFO的深度要根据读出的数据结构和读出数据的由那些具体的要求来确定。下面我们以一道简单的题目来估算一下FIFO的深度。
一个8bit宽的异步FIFO,输入时钟为100MHz,输出时钟为95MHz,设一个package为4Kbit,且两个package之间的发送间距足够大。问异步FIFO的深度。
解答:8bit位宽的异步FIFO,一个package的大小为4※1024/8=512Word,100MHz的输入时钟,传送一个Word需要的时间为1/100MHz,则发送一个package需要的时间T=512/100MHz,95MHz的输出时钟,接受一个Word需要的时间为1/95MHz,发送一个package的时间所能接受的数据量为(512※95)/100word=486.4word,所以FIFO的深度至少为512-486.4=25.6=26。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,192评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,186评论 1 303
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,844评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,471评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,876评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,891评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,068评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,791评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,539评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,772评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,250评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,577评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,244评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,146评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,949评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,995评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,812评论 2 276

推荐阅读更多精彩内容