1 FIFO简介
FIFO的本质是RAM
,具有先进先出的特性。
FIFO的基本使用原则:空时不能读,满时不能写
FIFO的两个重要参数:宽度和深度
FIFO的两种类型:
- 同步FIFO:读写时钟相同,通常用来做数据缓存
- 异步FIFO:读写时钟不同,通常用来做跨时钟域处理
2 异步FIFO设计要点
2.1 空满信号产生的原理
读空信号:
- 复位的时候(读指针和写指针相等),读空信号有效
- 读指针赶上写指针的时候,读空信号有效
写满信号:
- 写指针赶上读指针的时候
2.2 空满指针跨时钟域处理,为什么要使用Gray信号进行同步
空满信号是通过读写指针的比较来产生,但是读指针属于读时钟域,写指针属于写时钟域,读写指针的比较有一个跨时钟域的问题,因此需要对读写指针进行信号的同步:
- 将写指针同步到读时钟域,然后读指针再和同步后的写指针比较,产生读空信号
- 将读指针同步到写时钟域,然后写指针再和同步后的读指针比较,产生写满信号
使用格雷码Gray
表示的读写指针用两级寄存器同步:
同步过程的亚稳态问题是无法解决的,需要保证即使出现亚稳态也不影响设计的功能
- 如果使用二进制编码表示的读写指针进行同步,当出现亚稳态的时候,将会影响设计的功能。
例如现在将写指针同步到读时钟域,去判断读空信号。写指针0111–>1000,同时有4bit数据发生跳变,在亚稳态的影响下,同步后的写指针的数值可能是0000-1111之间的任何一个数值。本来应该判空的,但是现在没有判空,将会读取错误的数据,使得设计出现问题。 - 如果使用格雷码表示的读写指针进行同步,即使出现亚稳态,也不会影响设计的功能。
格雷码的特点是,相邻的编码之间每次只有1bit数据发生变化。那么这1bit数据如果发生亚稳态会影响设计的功能吗?答案是不会。例如现在还是将写指针同步到读时钟域,去判断读空信号。假设格雷码写指针从0000->0001,写指针同步出错,出错的结果只能是0000->0000,即写指针不变。这种结果最多就是让空信号标志在FIFO不是真正空的时候产生,而不会产生空读的情形。只会影响FIFO的效率,而不会影响到FIFO的功能。
所以Gray码保证的是同步后的读写指针即使在出错的情形下依然能够保证FIFO功能的正确性
2.3 设计的时候,读写指针的同步至少需要消耗两个时钟周期,肯定会使得空满信号的判断有延迟,会不会导致设计出错??
假设现在将写指针同步到读时钟域再和读指针比较进行FIFO空状态判断,因为在同步写指针时需要时间,而在这个同步的时间内有可能还会写入新的数据,因此同步后的写指针一定是小于或者等于当前实际的写指针,所以此时判断FIFO为空不一定是真空,这样更加保守,一共不会出现空读的情况,虽然会影响FIFO的性能,但是并不会出错。
同理将读指针同步到写时钟域再和写指针比较进行FIFO满状态判断,同步后的读指针一定是小于或者等于当前的读指针,所以此时判断FIFO为满不一定是真满,这样更保守,肯定不会出错。
总结来说异步逻辑转到同步逻辑不可避免需要额外的时钟开销,这会导致满空趋于保守,会稍微有性能损失,但是不会出错
2.4 读写时钟不同频,在同步的过程中,可能会有指针遗漏,是否会影响功能?
异步FIFO两端的时钟不是同频的,或者读快写慢,或者读慢写快,慢的时钟域同步到快的时钟域不会出现漏掉指针的情况,但是将指针从快的时钟域同步到慢的时钟域时可能会有指针遗漏。
举例:假设读慢写快
进行满标志判断的时候,需要将读指针同步到写时钟域,因为写时钟快,不会发生读指针同步遗漏的情况,但是由于同步需要消耗至少两个时钟周期,导致读指针同步延迟,读时钟域rd_ptr=3,但是写时钟域的sync_rd_ptr=2,这样可能会导致满标志信号提前产生,满并非真满,但是不影响FIFO功能。
进行空标志判断的时候,需要将写指针同步到读时钟域,因为读时钟慢,会发生写指针同步遗漏的情况,不用关心漏掉哪几个,而是担心漏掉的指针是否会对设计产生影响,比如写指针从0写到10,期间读时钟域只同步捕捉到了3、5、8这三个写指针而漏掉了其他指针。当同步到8这个写指针时,真实的写指针可能已经写到10 ,相当于在读时钟域还没来得及觉察的情况下,写时钟域可能偷偷写了数据到FIFO去,这样在判断它是不是空的时候会出现不是真正空的情况,漏掉的指针不会对FIFO的逻辑操作产生影响(即不会影响,FIFO在满的时候写数据,空的时候读数据,造成的功能错误),只会影响FIFO的工作效率。
总而言之,实际的读/写指针大于同步之后的读/写指针的数值,这种情况不会影响FIFO的功能,只会影响FIFO的效率。(2.3和2.4)均是这种情况
2.5 二进制转Gray码
原理
:
代码
代码编写思路
大概就是格雷码最高位与二进制码一致,其余各位为二进制码对应位与其相邻的高位异或,如格雷码的第二位为二进制的第二位与二进制码的第三位异或。因此实现代码则为二进制码本身与逻辑右移一位后的二进制码异或即可得。
2.6 Gray码转二进制
原理
代码
代码编写思路
二进制码的最高位与格雷码最高位保持一致,其余通过自生移位与异或得到,如b[2]为格雷码逻辑右移两位(逻辑右移,最高位补0)再自身四位异或,0与任何数异或均为其本身。
2.7 读空的条件是写指针等于读指针,写满的条件也是写指针等于读指针,到底如何区分呢?
二进制码判断空满
:指针的位宽多定义一位
假设要设计深度为 8 的异步FIFO,此时定义读写指针只需要 3 位(2^3=8)就够用了,
但是我们在设计时将指针的位宽设计成 4 位,最高位的作用就是区分是读空还是写满。
具体判断规则如下:
- 当最高位相同,其余位相同认为是读空
- 当最高位不同,其余位相同认为是写满
格雷码判断空满
:指针的位宽多定义一位
在设计中判断是读指针是否等于写指针的时候,用的是读写指针的格雷码形式
具体判断规则如下:
-
当最高位和次高位相同,其余位相同认为是读空
-
当最高位和次高位不同,其余位相同认为是写满
因为格雷码是一种镜像码, 例如读指针指向 8,写指针指向 7 ,我们可以知道此时此刻并不是读空状态也不是写满状态;
(7对应的格雷码0100;8对应得格雷码1100) 但是如果按照二进制码得判断规则,最高位不同,其余位相同,就会误判出现写满信号;因此格雷码判断空满的条件和二进制判断空满的规则不相同。
3 RTL
一个典型的FIFO模块结构
如下所示:
下面是参考上图进行的实现(略有不同):
module async_fifo#(
parameter DEPTH = 16,
parameter WIDTH = 8
)(
input wr_clk ,
input wr_rstn ,
input wr_en ,
input [WIDTH-1:0] wr_data ,
input rd_clk ,
input rd_rstn ,
input rd_en ,
output reg [WIDTH-1:0] rd_data ,
output rempty ,
output wfull
);
//------------------------------------------------------------------------------------
// internal vars
//------------------------------------------------------------------------------------
reg [WIDTH-1:0] fifo_ram [DEPTH-1:0];
reg [$clog2(DEPTH):0] wr_ptr;
reg [$clog2(DEPTH):0] rd_ptr;
wire [$clog2(DEPTH):0] wr_ptr_g;
wire [$clog2(DEPTH):0] rd_ptr_g;
reg [$clog2(DEPTH):0] wr_ptr_g_d1;
reg [$clog2(DEPTH):0] rd_ptr_g_d1;
reg [$clog2(DEPTH):0] wr_ptr_g_d2;
reg [$clog2(DEPTH):0] rd_ptr_g_d2;
wire [$clog2(DEPTH)-1:0] wr_addr;
wire [$clog2(DEPTH)-1:0] rd_addr;
//------------------------------------------------------------------------------------
// wr_ptr && rd_ptr
//------------------------------------------------------------------------------------
always@(posedge wr_clk or negedge wr_rstn)begin
if(!wr_rstn)
wr_ptr <= 0;
else if(wr_en && !wfull)
wr_ptr <= wr_ptr + 1'b1;
end
always@(posedge rd_clk or negedge rd_rstn)begin
if(!rd_rstn)
rd_ptr <= 0;
else if(rd_en && !rempty)
rd_ptr <= rd_ptr + 1'b1;
end
//------------------------------------------------------------------------------------
// wfull && rempty
//------------------------------------------------------------------------------------
assign wr_ptr_g = (wr_ptr >> 1) ^ wr_ptr;
assign rd_ptr_g = (rd_ptr >> 1) ^ rd_ptr;
always@(posedge rd_clk or negedge rd_rstn )begin//write_ptr_g sync to read domain
if(!rd_rstn)
{wr_ptr_g_d2,wr_ptr_g_d1} <= 0;
else
{wr_ptr_g_d2,wr_ptr_g_d1} <= {wr_ptr_g_d1,wr_ptr_g};
end
always@(posedge wr_clk or negedge wr_rstn)begin//read_ptr_g sync to write domain
if(!wr_rstn)
{rd_ptr_g_d2,rd_ptr_g_d1} <= 0;
else
{rd_ptr_g_d2,rd_ptr_g_d1} <= {rd_ptr_g_d1,rd_ptr_g};
end
assign rempty = (rd_ptr_g == wr_ptr_g_d2);
assign wfull = wr_ptr_g == {~rd_ptr_g_d2[$clog2(DEPTH):$clog2(DEPTH)-1],rd_ptr_g_d2[$clog2(DEPTH)-2:0]};
//------------------------------------------------------------------------------------
// write && read
//------------------------------------------------------------------------------------
assign wr_addr = wr_ptr[$clog2(DEPTH)-1:0];
assign rd_addr = rd_ptr[$clog2(DEPTH)-1:0];
always@(posedge wr_clk)
if(wr_en && !wfull)
fifo_ram[wr_addr] <= wr_data;
always@(posedge rd_clk)
if(rd_en && !rempty)
rd_data <= fifo_ram[rd_addr];
endmodule
4 Testbench
pattern只写不读、pattern只读不写、pattern写比读快
`timescale 1ns / 1ps
module tb_async_fifo();
//---------------------------------------------------
parameter WIDTH = 3;
parameter DEPTH = 32;
reg wr_clk ;
reg wr_rstn ;
reg wr_en ;
reg [WIDTH-1:0] wr_data ;
reg rd_clk ;
reg rd_rstn ;
reg rd_en ;
wire [WIDTH-1:0] rd_data ;
wire empty ;
wire full ;
//---------------------------------------------------
initial begin
wr_clk = 0;
forever #10 wr_clk = ~wr_clk;//50MHz
end
initial begin
rd_clk = 0;
forever #12.5 rd_clk = ~rd_clk;//40MHz
end
initial begin
rd_rstn = 0;
wr_rstn = 0;
#103 ;
rd_rstn = 1;
wr_rstn = 1;
end
//pattern1
initial begin
wr_en = 0;
wr_data = 0;
#203;
wr_en = 1;
repeat(32) @(posedge wr_clk)begin
#1;
wr_data = wr_data + 1;
end
#1;
wr_en = 0;
end
//pattern2
initial begin
rd_en = 0;
#3000;
rd_en = 1;
repeat(32) @(posedge rd_clk)begin
end
#1;
rd_en = 0;
end
//pattern3
initial begin
rd_en = 0;
wr_en = 0;
#6000;
wr_en = 1;
rd_en = 1;
repeat(300) @(posedge wr_clk)begin
#1;
wr_data = wr_data + 1;
end
#1;
wr_en = 0;
repeat(32) @(posedge rd_clk)begin
end
#1;
rd_en = 0;
end
//---------------------------------------------------
async_fifo#(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
) u_async_fifo(
.wr_clk (wr_clk ),
.wr_rstn (wr_rstn ),
.wr_en (wr_en ),
.wr_data (wr_data ),
.rd_clk (rd_clk ),
.rd_rstn (rd_rstn ),
.rd_en (rd_en ),
.rd_data (rd_data ),
.empty (empty ),
.full (full )
);
endmodule
pattern读比写快
`timescale 1ns / 1ps
module tb_async_fifo_w_slow();
//---------------------------------------------------
parameter WIDTH = 3;
parameter DEPTH = 32;
reg wr_clk ;
reg wr_rstn ;
reg wr_en ;
reg [WIDTH-1:0] wr_data ;
reg rd_clk ;
reg rd_rstn ;
reg rd_en ;
wire [WIDTH-1:0] rd_data ;
wire empty ;
wire full ;
//---------------------------------------------------
initial begin
wr_clk = 0;
forever #10 wr_clk = ~wr_clk;//50MHz
end
initial begin
rd_clk = 0;
forever #8 rd_clk = ~rd_clk;//62.5MHz
end
initial begin
rd_rstn = 0;
wr_rstn = 0;
#103 ;
rd_rstn = 1;
wr_rstn = 1;
end
//pattern1
initial begin
wr_en = 0;
wr_data = 0;
#203;
wr_en = 1;
repeat(32) @(posedge wr_clk)begin
#1;
wr_data = wr_data + 1;
end
#1;
wr_en = 0;
end
//pattern2
initial begin
rd_en = 0;
#3000;
rd_en = 1;
repeat(32) @(posedge rd_clk)begin
end
#1;
rd_en = 0;
end
//pattern3
initial begin
rd_en = 0;
wr_en = 0;
#6000;
wr_en = 1;
rd_en = 1;
repeat(300) @(posedge wr_clk)begin
#1;
wr_data = wr_data + 1;
end
#1;
wr_en = 0;
repeat(32) @(posedge rd_clk)begin
end
#1;
rd_en = 0;
end
//---------------------------------------------------
async_fifo#(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
) u_async_fifo(
.wr_clk (wr_clk ),
.wr_rstn (wr_rstn ),
.wr_en (wr_en ),
.wr_data (wr_data ),
.rd_clk (rd_clk ),
.rd_rstn (rd_rstn ),
.rd_en (rd_en ),
.rd_data (rd_data ),
.empty (empty ),
.full (full )
);
endmodule
pattern读写一样快
`timescale 1ns / 1ps
module tb_async_fifo_w_equal();
//---------------------------------------------------
parameter WIDTH = 3;
parameter DEPTH = 32;
reg wr_clk ;
reg wr_rstn ;
reg wr_en ;
reg [WIDTH-1:0] wr_data ;
reg rd_clk ;
reg rd_rstn ;
reg rd_en ;
wire [WIDTH-1:0] rd_data ;
wire empty ;
wire full ;
//---------------------------------------------------
initial begin
wr_clk = 0;
forever #10 wr_clk = ~wr_clk;//50MHz
end
initial begin
rd_clk = 0;
#8;
forever #10 rd_clk = ~rd_clk;//50MHz
end
initial begin
rd_rstn = 0;
wr_rstn = 0;
#103 ;
rd_rstn = 1;
wr_rstn = 1;
end
//pattern1
initial begin
wr_en = 0;
wr_data = 0;
#203;
wr_en = 1;
repeat(32) @(posedge wr_clk)begin
#1;
wr_data = wr_data + 1;
end
#1;
wr_en = 0;
end
//pattern2
initial begin
rd_en = 0;
#3000;
rd_en = 1;
repeat(32) @(posedge rd_clk)begin
end
#1;
rd_en = 0;
end
//pattern3
initial begin
rd_en = 0;
wr_en = 0;
#6000;
wr_en = 1;
rd_en = 1;
repeat(300) @(posedge wr_clk)begin
#1;
wr_data = wr_data + 1;
end
#1;
wr_en = 0;
repeat(32) @(posedge rd_clk)begin
end
#1;
rd_en = 0;
end
//---------------------------------------------------
async_fifo#(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
) u_async_fifo(
.wr_clk (wr_clk ),
.wr_rstn (wr_rstn ),
.wr_en (wr_en ),
.wr_data (wr_data ),
.rd_clk (rd_clk ),
.rd_rstn (rd_rstn ),
.rd_en (rd_en ),
.rd_data (rd_data ),
.empty (empty ),
.full (full )
);
endmodule
参考博客:
https://zhuanlan.zhihu.com/p/551351794
https://blog.csdn.net/ZZ2588/article/details/122347438
https://blog.csdn.net/spx1164376416/article/details/121086907
https://zhuanlan.zhihu.com/p/344170711
https://wuzhikai.blog.csdn.net/article/details/121152844
https://blog.csdn.net/JustinLee2015/article/details/106527301/
https://blog.csdn.net/wuzhikaidetb/article/details/123570799
https://blog.csdn.net/weixin_44348260/article/details/119612998