FIFO简介

FIFO的英文全称是First In First Out,即先进先出。FPGA使用的FIFO一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递它与FPGA内部的RAM和ROM的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,其数据地址由内部读写指针自动加1完成,由此带来的缺点就是不能像RAM和ROM那样可以由地址线决定读取或写入某个指定的地址。

同步/异步FIFO

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

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

Xilinx 的FIFO IP 核可以被配置为同步FIFO 或异步FIFO,其信号框图如下图所示。从图中可以了解到,当被配置为同步FIFO 时,只使用 wr_clk,所有的输入输出信号都同步于wrclk 信号。而当被配置为异步FIFO 时,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟wr_clk,所有与读相关的信号都是同步于读时钟rdclk。

image-20220622113651743

FIFO的常见参数

FIFO 的宽度:FIFO 一次读写操作的数据位N;

FIFO 的深度:FIFO 可以存储多少个宽度为N 位的数据。

空标志:empty。FIFO 已空时由 FIFO 的状态电路送出的一个信号,以阻止FIFO 的读操作继续从FIFO中读出数据而造成无效数据的读出。

将空标志:almost_ empty。FIFO 即将被读空。

满标志:full。FIFO 已满时由 FIFO 的状态电路送出的一个信号,以阻止FIFO 的写操作继续向 FIFO 中写数据而造成溢出。

将满标志:almost_full。FIFO 即将被写满。

读时钟:读FIFO 时所遵循的时钟,在每个时钟的上升沿触发。

这里请注意,“almost_ empty”和“almost_full”这两个信号分别被看作“empty”和“full”的警告信号,他们距离真正的空(empty)和满(full)都一个时钟的延时。本实验将使用这两个信号。

写时钟:写FIFO 时所遵循的时钟,在每个时钟的上升沿触发。

这里请注意,“almost_empty”和“almost_full”这两个信号分别被看作“empty”和“full”的警告信号,他们相对于真正的空(empty)和满(full)都会提前一个时钟周期拉高。

将读指针传递到写时钟域才能产生满信号,将写指针传递到读时钟域才能产生空信号,因此,这里就涉及到如何处理信号传输的亚稳态问题。

FIFO的功能

FIFO的功能类似于一个调节上下游水量的一个蓄水池。

img

1.FIFO 的上游结点是FIFO的数据输入端,

在写信号有效时,数据将被写入FIFO,由FIFO内部的写指针控制,并且在FIFO内部,写指针递增一个单元,同时FIFO的满信号(FIFO fulll Signal)将控制上游结点是否发送数据;

2.FIFO的下游节点是FIFO 的数据输出端,

当读信号有效时,FIFO中的数据将被读出,由FIFO内部的读指针控制,并且在FIFO内部读指针递增一个单元,同时FIFO空信号(FIFO empty Signal)将控制下游节点是否读出数据。

3.FIFO内部的空间已经被写满

如果FIFO内部的空间已经被写满,则实时生成满信号,以反压上游节点,上游节点停止写新的数据进来,否则就会把已经写好的数据冲掉。

4.FIFO内部的数据全部被读

如果FIFO内部的数据全部被读,则实时生成空信号,控制下游节点不再进行数据读操作。否则,下游节点就会将读过的数据重新再读一遍。

5.FIFO用途: FIFO主要用来调节上下游数据的吞吐量。

6.FIFO空满状态的判断

==空状态:==

当读写地址相等时,说明已经写入的数据,已经全部被读走,此时,FIFO还尚未有新的数据写入,说明FIFO为空。

空状态情况发生:

  • 复位操作时,
  • 当读地址读出FIFO中最后一个字后,追赶上了写地址。

判断条件:

​ 如果两个指针的MSB相同,则说明两个指针折回的次数相等。其余位相等,说明FIFO为空。

==满状态:==

此时,读地址已经读过的地址空间,再一次被写地址写入。而读地址到最高地址之间的数据,还尚未被读。说明此时FIFO处于满的状态。

判断条件:

​ 如果两个指针的最高有效位MSB不同,说明写指针比读指针多折回了一次;

​ 如r_addr=0000,而w_addr = 1000,为满。

==区分满状态、空状态方法:==

在地址位中添加一个额外的位(extra bit)。

​ 当写指针增加并越过最后一个FIFO地址时,就将写指针这个未用的MSB加1,其它位回零.

​ 对读指针也进行同样操作。

此时,对于深度为2^n的FIFO,需要的读/写指针位宽为(n + 1) 位

例子:深度为8的FIFO,需要采用4bit的计数器, 0000~1000、 1001~1111,MSB作为折回标志位,而低3位作为地址指针。

更多知识参考:(44条消息) 同步电路与跨时钟域电路设计2——多bit信号的跨时钟域传输(FIFO)_桐桐花的博客-CSDN博客

FIFO使用二进制读写指针的问题及解决(格雷码)

跨时钟域的问题:由于读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后仔进行比较
解决方法: 加两级寄存器同步 + 格雷码(目的都是消除亚稳态)

格雷码的特点:

  1. 格雷码相邻的2个数值之间只会有一位发生变化,其余各位都相同

  2. 格雷码是一种循环码,0和最大数(2的n次方减1)之间也只有一位不同

    用格雷码之后,相邻数值只有1位发生翻转,1位翻转所引起的亚稳态的概率远 远要小于几位同时翻转所引起的概率。因此,格雷码能很好的亚稳态出现的概率。

什么是亚稳态

亚稳态(Metastable):是指触发器无法在某个规定时间段内达到一个确定的状态。

image-20220622204804059

亚稳态产生的原因:在FPGA的系统中,如果数据传输不满足触发器的Tsu(建立时间)和Th(保持时间),或者复位过程中复位信号的释放不满足有效时钟沿的恢复时间(recovery time),就有可能产生亚稳态。

深入浅出理解亚稳态:数字信号中,只有1和0,所以2V-5V代表逻辑电平1,0V-0.8V代表逻辑电平0,中间的0.8V-2V没有定义,不妨称之为“模糊电平”。如果此时CLK突然产生了一个上升沿,“时钟门”相当于就被关闭了,A与B由于还没完成跳变,就处在了“模糊电平”,需要一定的时间才能恢复到明确定义的逻辑电平,此时就引起了亚稳态。

由于输入与clk的变化不同步而导致了亚稳态。所以从宏观角度来说,亚稳态的产生是由于输入的异步性。

亚稳态的经典解决方案

image-20220622210132857

知道了亚稳态产生的原因是由于输入的异步特性,所以解决的方法当然就是把输入同步化。经典解决方案就是在异步的输入后面接上两个同步的寄存器。

解决亚稳态的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module asy_to_syn(clk,rst_n,signal_in,signal_out);
input clk;
input rst_n;
input signal_in;
output signal_out;
reg state_tmp0,state_tmp1;

always@(posedge clk or negedge rst_n)
if(!rst_n) begin
state_tmp0 <= 1'b0;
state_tmp1 <= 1'b0;
end
else begin
state_tmp0 <= signal_in;
state_tmp1 <= state_tmp0;
end
assign signal_out = state_tmp1;
endmodule

设计FIFO深度

设计之前要清楚:分析清楚数据轻载和重载时数据的传输任务
如果FIFO能在重载的时候满足需求,轻载的时候,肯定可以胜任。

FIFO深度计算总结:

写时钟频率: WCLK 写时钟周期=1/WCLK
读时钟频率: RCLK 读时钟周期=1/RCLK
写时每B个时钟周期内会有 A个数据写入FIFO (写入效率A/B)
读时每Y个时钟周期内会有 X个数据读出FIFO(读出效率X/Y)

image-20220623084642447

连续传输数据的周期称为 burst长度:burst_length表示这段时间写入的数据量

burst的持续时间:burst长度*写时钟周期 (burst_length / WCLK)

读的实际速度:(RCLK*(X/Y)),

burst的持续时间这段时间读出的数据量:burst的持续时间*读的实际速度

​ (burst_length / WCLK )(RCLK(X/Y))

理论上的 FIFO的最小深度: 写入和读出两者之差为FIFO中残留的数据

​ burst_length - (burst_length / WCLK )(RCLK(X/Y))

————————————————
原文链接:https://blog.csdn.net/weixin_41788560/article/details/125339168

FIFO IP核的使用

image-20220622172052887

异步FIFO程序设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
module fifo_async#(
parameter data_width = 16,
parameter data_depth = 256,
parameter addr_width = 8
)
(
input rst,
input wr_clk,
input wr_en,
input [data_width-1:0] din,
input rd_clk,
input rd_en,
output reg valid,
output reg [data_width-1:0] dout,
output empty,
output full
);


reg [addr_width:0] wr_addr_ptr;//地址指针,比地址多一位,MSB用于检测在同一圈
reg [addr_width:0] rd_addr_ptr;
wire [addr_width-1:0] wr_addr;//RAM 地址
wire [addr_width-1:0] rd_addr;

wire [addr_width:0] wr_addr_gray;//地址指针对应的格雷码
reg [addr_width:0] wr_addr_gray_d1;
reg [addr_width:0] wr_addr_gray_d2;
wire [addr_width:0] rd_addr_gray;
reg [addr_width:0] rd_addr_gray_d1;
reg [addr_width:0] rd_addr_gray_d2;


reg [data_width-1:0] fifo_ram [data_depth-1:0];

//=========================================================write fifo
genvar i;
generate
for(i = 0; i < data_depth; i = i + 1 )
begin:fifo_init
always@(posedge wr_clk or posedge rst)
begin
if(rst)
fifo_ram[i] <= 'h0;//fifo复位后输出总线上是0,并非ram中真的复位。可无
else if(wr_en && (~full))
fifo_ram[wr_addr] <= din;
else
fifo_ram[wr_addr] <= fifo_ram[wr_addr];
end
end
endgenerate
//========================================================read_fifo
always@(posedge rd_clk or posedge rst)
begin
if(rst)
begin
dout <= 'h0;
valid <= 1'b0;
end
else if(rd_en && (~empty))
begin
dout <= fifo_ram[rd_addr];
valid <= 1'b1;
end
else
begin
dout <= 'h0;//fifo复位后输出总线上是0,并非ram中真的复位,只是让总线为0;
valid <= 1'b0;
end
end
assign wr_addr = wr_addr_ptr[addr_width-1-:addr_width];
assign rd_addr = rd_addr_ptr[addr_width-1-:addr_width];
//=============================================================格雷码同步化
always@(posedge wr_clk )
begin
rd_addr_gray_d1 <= rd_addr_gray;
rd_addr_gray_d2 <= rd_addr_gray_d1;
end
always@(posedge wr_clk or posedge rst)
begin
if(rst)
wr_addr_ptr <= 'h0;
else if(wr_en && (~full))
wr_addr_ptr <= wr_addr_ptr + 1;
else
wr_addr_ptr <= wr_addr_ptr;
end
//=========================================================rd_clk
always@(posedge rd_clk )
begin
wr_addr_gray_d1 <= wr_addr_gray;
wr_addr_gray_d2 <= wr_addr_gray_d1;
end
always@(posedge rd_clk or posedge rst)
begin
if(rst)
rd_addr_ptr <= 'h0;
else if(rd_en && (~empty))
rd_addr_ptr <= rd_addr_ptr + 1;
else
rd_addr_ptr <= rd_addr_ptr;
end

//========================================================== translation gary code
assign wr_addr_gray = (wr_addr_ptr >> 1) ^ wr_addr_ptr;
assign rd_addr_gray = (rd_addr_ptr >> 1) ^ rd_addr_ptr;

assign full = (wr_addr_gray == {~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高两位不同
assign empty = ( rd_addr_gray == wr_addr_gray_d2 );

endmodule

源代码:异步FIFO—Verilog实现_alangaixiaoxiao的博客-CSDN博客_异步fifo