异步FIFO问题
- 异步FIFO怎么设计?
结构框图
如图所示,共有2个同步模块,分别是同步读指针到写时钟域,同步写指针到读时钟域。
在写时钟域进行满判断产生模块,在读时钟域进行空判断产生模块。
双端口存储RAM模块;
异步FIFO的设计主要有5部分组成。
(1)双口 RAM 存储数据。
(2)同步读数据指针到写时钟域。
(3)同步写数据指针到读时钟域。
(4)处理写指针和满信号的逻辑。
(5)处理读指针和空信号的逻辑。
参数:数据深度,数据宽度,读时钟,写时钟,读使能,写使能,读空,写满,读数据,写数据,读地址,写地址。
采用数据在4,5部分进行跨时钟域传递时,采用格雷码。
地址实际宽度比所需宽度多一位,用于区别空、满。 分为五个部分:
- 双端口RAM寄存器。功能是在写时钟域下写使能有效时进行写入;在读时钟域下读使能有效时进行读取。
- 2.写地址部分。1)判断是否写满,读地址打两拍在写时钟域判断,格雷码最高两位不同,其余位相同,写满。2)未写满且写使能有效,二进制地址加1。
- 3.读地址部分。1)判断是否读空,写地址打两拍在读时钟域判断,格雷码最高两位不同,其余位相同,读空。2)未读空且读使能有效,二进制地址加1。
- 4.读地址传递给写地址。读二进制地址转为格雷码地址。Gn=Bn^0;Gn-1=Bn^Bn+1,在写时钟域下打两拍,被写模块接收。
- 5.写地址传递给读地址。同上。
对于异步FIFO,主要是实现不同时钟域之间的数据交互。与同步FIFO有着明显的区别,同步FIFO是使用一个时钟,读写在同一个时钟域内。而异步FIFO使用两个时钟,读/写在不同时钟域内,这个过程就涉及了跨时钟域处理的过程,跨时钟域又会产生亚稳态问题,所以这是异步FIFO设计的一个重点,与同步FIFO一样,通过空满标志衡量存储器的使用情况,那么在异步FIFO中,空满标志产生的条件和方式是什么呢,这也是设计的重点。
为什么异步fifo可以进行跨时钟域处理
1.异步fifo中使用了存储器RAM能将两个时钟域的时序路径隔开。
2.在异步fifo的读写控制中,引入了格雷码同步。由于格雷码相邻两个码之间只有一位发生变化,因此在指针跨时钟域传递时如果发生亚稳态,指针要么是变化后的地址,要么是与同步前的指针保持一致。因此这不会引起fifo的功能紊乱,只是影响了其读写效率。
FIFO会不会存在假空假满的情况呢?
会,异步FIFO会存在虚空虚满的情况。判空信号是在读时钟域中产生的,需要将写时钟的 写指针转换成格雷码并同步到读时钟域来,从而导致,被同步过来的写地址是慢于或者等于真实 的写地址的值,此时读地址的格雷码的值是真实的值,所以,此时判断出来的空状态是一个假空 状态,同理,可得到写时钟域判断出来的满状态是一个虚满状态,这不影响异步FIFO的功能使用, 只是会较低其工作效率。
介绍一下异步FIFO的原理以及内部如何实现跨时钟域
答案:异步FIFO的原理是将要跨时钟域传输的数据按照地址顺序存在RAM中,将读写时钟的指针进行跨时钟域处理,进行控制设计,为了减少亚稳态的概率,将读写指针先转成格雷码,保证连续地址的读写只有一bit发生变化,写指针在读时钟域的时写下经过两级触发器被同步到读时钟域去判空,读指针在写时钟域的时钟下经过两级触发器被同步到写时钟域去判满。异步FIFO满了不能再写入,空了不能在读取。
异步FIFO的深度和什么有关?如果不考虑读写时钟频率的关系,异步FIFO的深度的设定一般有什么限制嘛?
异步FIFO的深度与读写频率有关以及突发传输的数据量有关。在宏观上,不考虑读写速率,那么异步fifo的深度在本质上与在一段突发数据传输的时间,还没有读出数据的数量有关,需要保证在能够存储这部分没有被读出的数据防止溢出。
单口RAM实现同步FIFO的设计方法
答案:单口RAM不能同时读写,读写只有一根地址线,可以在写使能下,地址线是写地址的地址,否则就是读地址,所以必须将读写使能信号分时处理,且读信号要比写信号滞后,读地址也要比写地址慢,且写地址不能在该地址的数据没有被读的情况下被覆盖。
同步、异步FIFO的区别,在设计中有哪些不同?
答案:区别:1.同步FIFO的时钟有且只有一个,读写数据均在同一个时钟有效沿下驱动,异步FIFO的 时钟通常有两个,读写数据在不同的时种下驱动。 2.同步FIFO常作为缓冲器使用,异步FIFO常作为跨时钟域处理使用。 在设计时,同步FIFO的空满状态可由一个计数信号产生,异步FIFO的空满状态需要将读写指针 先转成格雷码,判空(满)是在读(写)时钟域进行的,将写(读)指针的格雷码在读(写) 时钟域下同步,判空是读写指针的格雷码完全相同,判满是读写指针的格雷码高两位不同,其余位相同。
会不会出现FIFO永远不够深的情况,为什么?
会,当不限制读写的空闲周期,每次传输,写比读快,都有额外的数据要缓存,就会出现FIFO 深度不够的情况。 例如:FIFO的写时钟频率为200MHZ,读时钟频率为100MHZ,读写都无空闲周期,这样,在每次写数据的时间内,读数据永远都读不完写的数据,导致一直有额外的数据要缓存,那么FIFO一定永远不够用,深度就只能是无限大。
- 异步FIFO的作用:
1、为什么需要异步FIFO?
用于在不同的时钟域(clock domain)之间安全地传输数据。而同步FIFO主要是解决数据传输速率匹配问题。
2、同步器(synchronizer)
对于跨时钟域之间的信号传输,需要进行同步(synchronize)处理;一般来讲,我们可以采用同步器(由2~3级FF组成)对单bit的信号进行同步操作。注意,这里的打拍子是针对单bit信号而已的。
那么为什么不能用简单的同步器(synchronizer)对数据总线 ( 大于1bit)进行同步呢?下面分析一下。
-
异步FIFO的读/写指针
写指针(write pointer)
▷ 始终指向下一次将要写入的数据的地址;
▷ 系统复位后(FIFO为空),写指针指向0地址;
▷ 每写入一笔数据,写指针地址加1;
读指针(read pointer)
▷ 始终指向当前要读出的数据的地址;
▷ 系统复位后(FIFO为空),读指针指向0地址;
▷ 此时的数据是无效的,因为还没有数据写入,空标志有效;
- 异步FIFO空/满标志
空标志(empty)
▷ 情形一,复位时,两指针都为0;
▷ 情形二,当读指针和写指针相等时,空标志=1;
满标志(full)
▷ 当写指针和读指针最高位不同,其他相等时,满标志=1;
▷ 例如,写入的速度快,写指针转了一圈(wrap around),又追上了读指针;
RTL代码
//-- modified by xlinxdu, 2022/05/17
module async_fifo
#(
parameter DATA_WIDTH = 16 ,
parameter FIFO_DEPTH = 8 ,
parameter PTR_WIDTH = 4 ,
parameter ADDR_DEPTH = $clog2(FIFO_DEPTH)
)
(
//reset signal
input wire wr_rst_n_i,
input wire rd_rst_n_i,
//write interface
input wire wr_clk_i ,
input wire wr_en_i ,
input wire [DATA_WIDTH-1:0] wr_data_i,
//read interface
input wire rd_clk_i ,
input wire rd_en_i ,
output reg [DATA_WIDTH-1:0] rd_data_o,
//flag
output reg full_o ,
output reg empty_o
);
//-- memery
reg [DATA_WIDTH-1:0] regs_array [FIFO_DEPTH-1:0] ;
//-- memery addr
wire [ADDR_DEPTH-1:0] wr_addr ;
wire [ADDR_DEPTH-1:0] rd_addr ;
//-- write poiter,write poiter of gray and sync
reg [PTR_WIDTH -1:0] wr_ptr ;
wire [PTR_WIDTH -1:0] gray_wr_ptr ;
reg [PTR_WIDTH -1:0] gray_wr_ptr_d1 ;
reg [PTR_WIDTH -1:0] gray_wr_ptr_d2 ;
//-- read poiter,read poiter of gray and sync
reg [PTR_WIDTH -1:0] rd_ptr ;
wire [PTR_WIDTH -1:0] gray_rd_ptr ;
reg [PTR_WIDTH -1:0] gray_rd_ptr_d1 ;
reg [PTR_WIDTH -1:0] gray_rd_ptr_d2 ;
/*-----------------------------------------------\
-- write poiter and bin->gray --
\-----------------------------------------------*/
always @ (posedge wr_clk_i or negedge wr_rst_n_i) begin
if (!wr_rst_n_i) begin
wr_ptr <= {(PTR_WIDTH){1'b0}};
end
else if (wr_en_i && !full_o) begin
wr_ptr <= wr_ptr + 1'b1;
end
end
assign gray_wr_ptr = wr_ptr ^ (wr_ptr >> 1'b1);
/*-----------------------------------------------\
-- gray_wr_prt sync --
\-----------------------------------------------*/
always @ (posedge wr_clk_i or negedge wr_rst_n_i) begin
if (!wr_rst_n_i) begin
gray_wr_ptr_d1 <= {(PTR_WIDTH){1'b0}};
gray_wr_ptr_d2 <= {(PTR_WIDTH){1'b0}};
end
else begin
gray_wr_ptr_d1 <= gray_wr_ptr ;
gray_wr_ptr_d2 <= gray_wr_ptr_d1;
end
end
/*-----------------------------------------------\
-- read poiter and bin->gray --
\-----------------------------------------------*/
always @ (posedge rd_clk_i or negedge rd_rst_n_i) begin
if (!rd_rst_n_i) begin
rd_ptr <= {(PTR_WIDTH){1'b0}};
end
else if (rd_en_i && !empty_o) begin
rd_ptr <= rd_ptr + 1'b1;
end
end
assign gray_rd_ptr = rd_ptr ^ (rd_ptr >> 1'b1);
/*-----------------------------------------------\
-- gray_rd_ptr sync --
\-----------------------------------------------*/
always @ (posedge rd_clk_i or negedge rd_rst_n_i) begin
if (!rd_rst_n_i) begin
gray_rd_ptr_d1 <= {(PTR_WIDTH){1'b0}};
gray_rd_ptr_d2 <= {(PTR_WIDTH){1'b0}};
end
else begin
gray_rd_ptr_d1 <= gray_rd_ptr ;
gray_rd_ptr_d2 <= gray_rd_ptr_d1;
end
end
/*-----------------------------------------------\
-- full flag and empty flag --
\-----------------------------------------------*/
assign full_o = (gray_wr_ptr == {~gray_rd_ptr_d2[PTR_WIDTH-1],gray_rd_ptr_d2[PTR_WIDTH-2:0]})? 1'b1 : 1'b0;
assign empty_o = (gray_rd_ptr == gray_wr_ptr_d2)? 1'b1 : 1'b0;
/*-----------------------------------------------\
-- write addr and read addr --
\-----------------------------------------------*/
assign wr_addr = wr_ptr[PTR_WIDTH-2:0];
assign rd_addr = rd_ptr[PTR_WIDTH-2:0];
/*-----------------------------------------------\
-- write operation --
\-----------------------------------------------*/
integer [PTR_WIDTH-1:0] i;
always @ (posedge wr_clk_i or negedge wr_rst_n_i) begin
if (!wr_rst_n_i) begin
for(i=0;i<FIFO_DEPTH;i=i+1)begin
regs_array[i] <= {(DATA_WIDTH){1'b0}};
end
end
else if (wr_en_i && !full_o) begin
regs_array[wr_addr] <= wr_data_i;
end
end
/*-----------------------------------------------\
-- read operation --
\-----------------------------------------------*/
always @ (posedge rd_clk_i or negedge rd_rst_n_i) begin
if (!rd_rst_n_i) begin
rd_data_o <= {(DATA_WIDTH){1'b0}};
end
else if (rd_en_i && !empty_o) begin
rd_data_o <= regs_array[rd_addr];
end
end
endmodule
TB
//-- modified by xlinxdu, 2022/05/17
module tb_async_fifo;
reg rst_n_i ;
reg wr_clk_i ;
reg wr_en_i ;
reg [15:0] wr_data_i;
reg rd_clk_i ;
reg rd_en_i ;
wire [15:0] rd_data_o;
reg full_o ;
reg empty_o ;
initial begin
rst_n_i = 1;
wr_clk_i = 0;
wr_en_i = 0;
wr_data_i= 16'b0;
rd_clk_i = 0;
rd_en_i = 0;
# 1 rst_n_i = 0;
# 2 rst_n_i = 1;
end
initial begin
#20 wr_en_i = 1;
rd_en_i = 0;
#40 wr_en_i = 0;
rd_en_i = 1;
#30 wr_en_i = 1 ;
rd_en_i = 0 ;
#13 rd_en_i = 1 ;
#10
repeat(100)
begin
#5 wr_en_i = {$random}%2 ;
rd_en_i = {$random}%2 ;
end
end
always #1.5 wr_clk_i = ~wr_clk_i ;
always #1 rd_clk_i = ~rd_clk_i ;
always #3 wr_data_i = {$random}%16'hFF;
async_fifo u_async_fifo(
.wr_rst_n_i(rst_n_i ),
.wr_clk_i (wr_clk_i ),
.wr_en_i (wr_en_i ),
.wr_data_i (wr_data_i),
.rd_rst_n_i(rst_n_i ),
.rd_clk_i (rd_clk_i ),
.rd_en_i (rd_en_i ),
.rd_data_o (rd_data_o),
.full_o (full_o ),
.empty_o (empty_o )
);
initial begin
#1000 $finish ;
$fsdbDumpfile("async.fsdb");
$fsdbDumpvars ;
$fsdbDumpMDA ;
end
endmodule