掰开揉碎讲 FIFO

发布时间 2023-04-28 15:33:38作者: Doreen的FPGA自留地

一、什么是FIFO

FIFO 是 First In First Out 的简称。是指在FPGA内部用逻辑资源实现的能对数据的存储具有先进先出特性的一种缓存器。

FIFO 与 FPGA 内部的 RAM 和 ROM 的区别是 FIFO 没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,其数据地址由内部读写指针自动加1完成。FIFO 使用起来简单方便,由此带来的缺点是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。

二、为什么要用FIFO

FPGA程序实现的电路实际上是由一个个独立的功能模块组成的,各个模块又通过相关信号关联在一起,当存在模块间处理数据速度不同(有快有慢)时,处理得快的模块就需要等一等处理得慢的模块,这个等待其实就是缓存的实现。我们可以采用FIFO来解决数据的缓存。

打个比方,就像水龙头放水慢(输入慢),但我们人提水的时候是一次处理一桶水(输出快),所以需要一个水桶作为缓存,等存满一桶水,再一次被人提走。

又或者像我们人喝水,一次接一杯水(输入快), 渴的时候喝两口(输出慢)。这里,杯子作为缓存。

另外,在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,此时,异步时钟之间的接口电路的设计将成为关键。而使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据。

三、什么时候用FIFO

数据缓存、协议处理、串并转换、跨时钟域数据处理。

四、FIFO分类

FIFO根据读写时钟是否为同一时钟分为同步FIFO和异步FIFO。

同步FIFO是指读时钟和写时钟为同一个时钟,在时钟沿来临时可同时发生读写操作。

异步FIFO是指读写时钟不一致,读写时钟是互相独立的2个时钟。

同步FIFO在实际应用中比较少见,常用的是异步FIFO,但基于学习的目的,下文对两种FIFO都进行讲解。

五、同步FIFO

同步FIFO电路框图:

  简单来说,FIFO其实就是一个双口RAM加上两个读写控制模块。FIFO的常见参数和信号如下:

  • FIFO的宽度:即FIFO一次读写操作的数据位;
  • FIFO的深度:指的是FIFO可以存储多少个N位的数据(如果宽度为N)。
  • 满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
  • 空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
  • 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。(同步FIFO 读写只有一个时钟)
  • 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。(异步FIFO读写时钟分开)
  • 读指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0)
  • 写指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)

 其实可以把FIFO比作一个单向行驶的隧道,隧道两端都有一个门进行控制,FIFO宽度就是这个隧道单向有几个车道,FIFO的深度就是一个车道能容纳多少辆车,当隧道内停满车辆时,这就是FIFO的写满状态,当隧道内没有一辆车时,这便是FIFO的读空状态。

FIFO 的设计原则是任何时候都不能向满FIFO中写入数据(写溢出),任何时候都不能从空FIFO中读取数据(读溢出)。FIFO 设计的核心是空满判断。FIFO设置读,写地址指针,FIFO初始化的时候 读指针和写指针都指向地址为0的位置, 当往FIFO里面每写一个数据,写地址指针自动加1指向下一个要写入的地址。当从FIFO里面每读一个数据,读地址指针自动加1指向下一个要读出的地址,最后通过比较读地址指针和写地址指针的大小来确定空满状态。

 

读地址指针追上写地址指针,写地址指针跟读地址指针相等,此时FIFO是读空状态。

 当写地址指针再次追上读地址指针,写指针跟读地址指针再次相等的时候,此时FIFO是写满状态。

 

 

可以设置一个计数器,当写使能有效的时候计数器加一;当读使能有效的时候,计数器减一,将计数器与FIFO的size进行比较来判断FIFO的空满状态。这种方法设计比较简单,但是需要的额外的计数器,就会产生额外的资源,而且当FIFO比较大时,会降低FIFO最终可以达到的速度。

6、同步FIFO设计代码

 同步FIFO基本接口:


 

信号 描述
clk 系统时钟
rstn 系统复位信号
wr_en 写使能端
wr_data FIFO写数据
fifo_full FIFO的满标志位
 rd_en 读使能端 
rd_data FIFO读数据
fifo_empty FIFO的空标志位

 

同步FIFO实现代码如下:

 1 module sync_fifo#(parameter BUF_SIZE=8, BUF_WIDTH=8) (
 2     //FIFO的数据位宽默认为8bit
 3     //FIFO深度默认为8
 4    
 5 
 6     input                     i_clk,//输入时钟
 7     input                      i_rst,//复位信号
 8     input                      i_w_en,//写使能信号
 9     input                      i_r_en,//读使能信号
10    input      [BUF_WIDTH-1:0] i_data,//写入数据
11   
12    output reg [BUF_WIDTH-1:0] o_data,//读出数据
13     output                     o_buf_empty,//FIFO空标志
14    output                     o_buf_full );//FIFO满标志
15 
16    reg [3:0] fifo_cnt;  //记录FIFO数据个数
17     reg [$clog2(BUF_SIZE)-1:0] r_ptr,w_ptr;  //数据指针为3位宽度,0-7索引,8个数据深度,循环指针0-7-0-7
18    reg [BUF_WIDTH-1:0] buf_mem[0:BUF_SIZE-1]; //定义FIFO大小
19 
20     
21 //判断空满
22     assign o_buf_empty=(fifo_cnt==4'd0)?1'b1:1'b0;
23     assign o_buf_full=(fifo_cnt==4'd8)?1'b1:1'b0;
24 
25     
26     always@(posedge i_clk or posedge i_rst) //用于修改计数器
27         begin
28             if(i_rst)
29                 fifo_cnt<=4'd0;
30             else if((!o_buf_full&&i_w_en)&&(!o_buf_empty&&i_r_en)) //同时读写,计数器不变
31                 fifo_cnt<=fifo_cnt;
32             else if(!o_buf_full&&i_w_en) //写数据,计数器加1
33                 fifo_cnt<=fifo_cnt+1;
34             else if(!o_buf_empty&&i_r_en) //读数据,计数器减1
35                 fifo_cnt<=fifo_cnt-1;
36             else 
37                 fifo_cnt <= fifo_cnt; //其他情况,计数器不变
38         end
39 
40     always@(posedge i_clk or posedge i_rst) //读数据
41         begin 
42             if(i_rst)
43                 o_data<=8'd0;
44             else if(!o_buf_empty&&i_r_en)
45                 o_data<=buf_mem[r_ptr];
46         end
47 
48     always@(posedge i_clk)  //写数据
49         begin
50             if(!o_buf_full&&i_w_en)
51                 buf_mem[w_ptr]<=i_data;
52         end
53 
54     always@(posedge i_clk or posedge i_rst) //读写地址指针变化
55         begin
56             if(i_rst) begin
57                 w_ptr <= 0;
58                 r_ptr <= 0;
59             end
60             else begin
61                 if(!o_buf_full&&i_w_en) // 写数据,地址加1,溢出后自动回到0开始
62                         w_ptr <= w_ptr + 1;
63                 if(!o_buf_empty&&i_r_en) // 读数据,地址加1,溢出后自动回到0开始
64                         r_ptr <= r_ptr + 1;
65             end
66         end
67 
68 endmodule

 

 同步FIFO仿真测试文件

  1 `timescale 1ns/1ns
  2 
  3 module sync_fifo_tb;
  4     reg i_clk,i_rst;
  5     reg i_w_en,i_r_en;
  6     reg [7:0] i_data;
  7     wire [7:0] o_data;
  8     wire o_buf_empty,o_buf_full;
  9 
 10     sync_fifo dut(
 11         .i_clk(i_clk),
 12         .i_rst(i_rst),
 13         .i_data(i_data),
 14         .i_w_en(i_w_en),
 15         .i_r_en(i_r_en),
 16         .o_buf_empty(o_buf_empty),
 17         .o_buf_full(o_buf_full),
 18         .o_data(o_data)
 19     );
 20 
 21     initial begin
 22         #30;
 23         forever #10 i_clk = ~i_clk; //时钟
 24     end
 25     reg [7:0] r_data=8'd0;
 26     initial begin
 27           i_clk=1'b0;
 28            i_rst=1'b0;
 29            i_w_en=1'b0;
 30            i_r_en=1'b0;
 31             i_data=8'd0;
 32            #5 i_rst=1'b1;
 33         #10 i_rst=1'b0;
 34     
 35            push(1);
 36         fork  //同时执行push和pop
 37               push(2);
 38                pop(r_data);
 39         join 
 40            push(3);
 41         push(4);
 42         push(5);
 43         push(6);
 44         push(7);
 45         push(8);
 46         push(9);
 47         push(10);
 48         push(11);
 49         push(12);
 50         push(13);
 51         push(14);
 52         push(15);
 53         push(16);
 54         push(17);
 55         pop(r_data);
 56         push(18);
 57         pop(r_data);
 58         pop(r_data);
 59         pop(r_data);
 60         pop(r_data);
 61         push(19);
 62         pop(r_data);
 63         push(20);
 64         pop(r_data);
 65         pop(r_data);
 66         pop(r_data);
 67         pop(r_data);
 68         pop(r_data);
 69         pop(r_data);
 70         pop(r_data);
 71         pop(r_data);
 72         pop(r_data);
 73         pop(r_data);
 74         pop(r_data);
 75         push(21);
 76         pop(r_data);
 77         pop(r_data);
 78          pop(r_data);
 79          pop(r_data);
 80         #100 $stop;
 81     end
 82     
 83     task push (input [7:0] data);
 84         if(o_buf_full)
 85                $display("Cannot push %d: Buffer Full",data);
 86         else begin
 87             $display("Push",,data);
 88             i_data=data;
 89             i_w_en=1;
 90             @(posedge i_clk) #4 i_w_en= 0; //时钟上升沿后4ns,写使能清零
 91         end
 92     endtask
 93     
 94     task pop(output[7:0] data);
 95         if(o_buf_empty)
 96             $display("Cannot Pop: Buffer Empty");
 97         else begin
 98             i_r_en=1;
 99             @(posedge i_clk) #4 i_r_en= 0; //时钟上升沿4ns后,读使能清零
100             data = o_data;
101             $display("Pop:",,data);
102         end        
103     endtask
104 endmodule

 

采用Modelsim仿真得到如下波形:

 

 可以在Modelsim的View——Transcript窗口看到有如下打印信息:

# run -all
# Push   1
# Push   2
# Pop:   1
# Push   3
# Push   4
# Push   5
# Push   6
# Push   7
# Push   8
# Push   9
# Cannot push  10: Buffer Full
# Cannot push  11: Buffer Full
# Cannot push  12: Buffer Full
# Cannot push  13: Buffer Full
# Cannot push  14: Buffer Full
# Cannot push  15: Buffer Full
# Cannot push  16: Buffer Full
# Cannot push  17: Buffer Full
# Pop:   2
# Push  18
# Pop:   3
# Pop:   4
# Pop:   5
# Pop:   6
# Push  19
# Pop:   7
# Push  20
# Pop:   8
# Pop:   9
# Pop:  18
# Pop:  19
# Pop:  20
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Push  21
# Pop:  21
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty

 

六、异步FIFO

 虽然各大厂商都有自己的FIFO IP可供调用,但是我们仍然需要学习FIFO的设计原理,这样我们在移植或设计中查找问题起来将有据可循。所以掌握FIFO设计原理是一名合格FPGA工程师的基本功。

下面是异步FIFO的系统框图:

 

 

可以看到异步FIFO实质上也是基于中间的双口RAM,外加一些读写控制电路组成的。但是这里读写用的是两个不同的时钟。这将涉及到跨时钟域问题。

 

1 、亚稳态

同步时钟:

 

假设数据从0跳变到1,一般数据的跳变不是立马跳变,而是有一个上升时间,有个斜坡。如果是同步时钟采集数据则不会有什么影响。

异步时钟:

 

 如果是异步时钟,第2个时钟比第1个时钟滞后一点点,有可能时钟上升沿正好对应在数据跳变的阶段,那此时读到的数据可能是0, 可能是1, 是个随机态。这就是出现了亚稳态。

亚稳态不可避免, 只能通过一些手段(如 打2拍 以及 格雷码)来降低亚稳态出现的机率。

3、打两拍

 

 

2、格雷码

 格雷码是一种相邻数据只有1bit变化的码制。

自然二进制码 格雷码
0000 0000
0001 0001
0010 0011
0011 0010
0100 0110
0101 0111
0110 0101
0111 0100

 降低概率。

格雷码是二进制码右移1位再与原码相异或的结果。

 

 

4、如何判断FIFO的空和满

 

 

 

 (1)空判断

(2)满判断

(3)虚空、虚满

 

 

 

 

 

 

 

 虚空虚满不会产生错误, 只是影响FIFO 效率。 理解这些原理后,分析问题就知道去哪里分析。

 

(4)FIFO的乒乓操作

 

 

5、如何选择FIFO深度

 

6、顶层模块

 

7、双端口RAM模块

 

8、同步模块1

 

9、同步模块2

 

10、空判断模块

 

11、满判断模块

 

七、FIFO IP调用

 

八、FIFO应用实例 (ADC)