异步FIFO设计Verilog
介绍
Clifford E. Cummings的《Simulation and Synthesis Techniques for Asynchronous FIFO Design》这篇异步FIFO仿真分析写的真的厉害,使用了非常巧妙的方法解决的空满标志判断的问题还有跨时钟阈信号亚稳态的问题,我就写一下自己读了这个之后对异步FIFO的感悟吧。
两个设计要点
1:读写地址各增加一位得到地址指针使用格雷码判断数据空满状态
2:读写指针信号跨时钟阈处理
空满标志判断原理
空标志:
空标志相对好判断,只要读写指针在同一地址时即可,但是如何区分空满标志呢,所以采用增加一位地址得到地址指针,使其作为标志位,当完成一次计数时最高位变为一,其余为继续计数。为了降低多比特亚稳态所以采用格雷码计数。
满标志
虽然扩展一位最高标志位解决了空满信号判断的问题(写指针最高位与读指针相反其余位相同为满信号),虽然格雷码解决了多比特跨时钟阈问题,但是格雷码相邻两个数只有1bit数据变化所以不能满足写指针最高位与读指针相反其余位相同为满信号的要求。具体如下图所示
空满信号代码实现如下段所示
assign full = (wr_gray == {~(rd_gray_d2[data_addr- : 2]),rd_gray_d2[2:0]} ) ;
assign empty = (rd_gray == wr_gray_d2) ;
通过上图可以观察出,在使用格雷码编码规则时,判断满信号需要使读写指针最高位和次高位相反其余为相同。这种方法即解决的多比特信号亚稳态的问题,也解决了无法区分空满信号的问题。
读写指针信号同步化
因为异步FIFO读写时钟不同步所以,在判断空满信号时必然会采用打拍处理跨时钟阈问题。大部分资料都说的是第一级寄存器产生亚稳态后,第二级寄存器稳定输出概率为90%,第三极寄存器稳定输出的概率为99%,但是亚稳态也有可能跟随电路一直传递下去,如果电路容错能力强的话还好,要不然就需要后续进行容错处理。本设计在在转换为格雷码后进行打俩拍处理。代码如下段所示。
always @(posedge rd_clk )begin //打俩拍降低亚稳态
wr_gray_d1 <= wr_gray ;
wr_gray_d2 <= wr_gray_d1 ;
end
always @(posedge wr_clk )begin //打俩拍降低亚稳态
rd_gray_d1 <= rd_gray ;
rd_gray_d2 <= rd_gray_d1 ;
end
格雷码编码
编码解码规则:二进制最高位保留,其余位与这位的高一位进行异或运算。原理如下图所示。
具体实现可以通过将原码右移一位后与原码进行异或运算。具体代码如下段所示。
assign wr_gray = (wr_ptr >> 1'd1) ^ wr_ptr ; //格雷码转换
assign rd_gray = (rd_ptr >> 1'd1) ^ rd_ptr ; //格雷码转换
异步FIFO Verilog设计
异步FIFO代码实现如下
module FIFO_async#(
parameter data_wid = 4'd8 , //FIFO中数据位宽
parameter data_deep = 4'd16 , //FIFO中存储深度
parameter data_addr = 4'd4 //地址位宽
)
(
input rest ,
input wr_clk ,
input wr_en ,
input [data_wid : 1] data_in ,
input rd_clk ,
input rd_en ,
output reg [data_wid : 1] data_out ,
output full ,
output empty
);
//-----------------------------------------------------------------------------------------------
wire [data_addr - 1'd1 : 0] wr_addr ; //写数据地址
reg [data_addr : 0] wr_ptr ; //写指针
wire [data_addr : 0] wr_gray ;
reg [data_addr : 0] wr_gray_d1 ;
reg [data_addr : 0] wr_gray_d2 ;
wire [data_addr - 1'd1 : 0] rd_addr ; //读数据地址
reg [data_addr : 0] rd_ptr ; //读指针
wire [data_addr : 0] rd_gray ;
reg [data_addr : 0] rd_gray_d1 ;
reg [data_addr : 0] rd_gray_d2 ;
reg [data_wid - 1'd1 : 0] fifo_ram [data_deep - 1'd1 : 0] ;
//--------------------------------------------------------------------------- fifo地址产生
assign wr_addr = wr_ptr[data_addr - 1'd1 : 0] ;
assign rd_addr = rd_ptr[data_addr - 1'd1 : 0] ;
always @(posedge wr_clk or negedge rest )begin
if (!rest)
wr_ptr <= 5'd0 ;
else if ( (wr_en) && (!full) )
wr_ptr <= 5'd1 + wr_ptr;
else
wr_ptr <= wr_ptr ;
end
always @(posedge rd_clk or negedge rest )begin
if (!rest)
rd_ptr <= 5'd0 ;
else if ( (rd_en) && (!empty) )
rd_ptr <= 5'd1 + rd_ptr;
else
rd_ptr <= rd_ptr ;
end
//----------------------------------------------------------------------- 格雷码转换 打两拍
assign wr_gray = (wr_ptr >> 1'd1) ^ wr_ptr ;
assign rd_gray = (rd_ptr >> 1'd1) ^ rd_ptr ;
always @(posedge rd_clk )begin
wr_gray_d1 <= wr_gray ;
wr_gray_d2 <= wr_gray_d1 ;
end
always @(posedge wr_clk )begin
rd_gray_d1 <= rd_gray ;
rd_gray_d2 <= rd_gray_d1 ;
end
//-------------------------------------------------------------产生fifo读写数据 产生空满信号
assign full = (wr_gray == {~(rd_gray_d2[data_addr- : 2]),rd_gray_d2[2:0]} ) ;
assign empty = (rd_gray == wr_gray_d2) ;
integer i ;
always @(posedge wr_clk or negedge rest)begin
if (!rest)begin
for ( i=0 ; i<16 ; i=i+1 ) //当前地址下FIFO总线上数据为零,并非fifo_ram数据清零
fifo_ram [i] <= 8'd0 ;
end
else if ( (wr_en) && (!full) )
fifo_ram[wr_addr] <= data_in ;
else
fifo_ram[wr_addr] <= fifo_ram[wr_addr] ;
end
always @(posedge rd_clk or negedge rest)begin
if (!rest)
data_out <= 8'd0 ;
else if ( (rd_en) && (!empty) )
data_out <= fifo_ram[rd_addr] ;
else
data_out <= data_out ;
end
endmodule
仿真代码如下(写时钟50M读时钟100M)
`timescale 1ns/1ns
module tb_FIFO;
parameter DATA_W = 8 ; //数据位宽
parameter ADDR_W = 4 ; //地址位宽
parameter data_depth = 16 ;
reg wr_clk ;
reg rd_clk ;
reg rst_n ;
reg wr_en ;
reg [DATA_W-1:0] wr_data ;
reg rd_en ;
wire [DATA_W-1:0] rd_data ;
wire empty ;
wire full ;
FIFO_async
#(
.data_wid (DATA_W ),
.data_deep (data_depth ),
.data_addr (ADDR_W )
)
u_FIFO_async
(
.rest (rst_n ),
.wr_clk (wr_clk ),
.wr_en (wr_en ),
.data_in (wr_data ),
.rd_clk (rd_clk ),
.rd_en (rd_en ),
.data_out (rd_data ),
.empty (empty ),
.full (full )
);
always #10 wr_clk=~wr_clk;
always #5 rd_clk=~rd_clk;
initial begin
wr_clk = 1;
rd_clk = 0;
rst_n = 0;
wr_en = 0;
wr_data = 0;
rd_en = 0;
#101;
rst_n = 1;
#20;
gen_data;
repeat(16)@(posedge rd_clk);
rd_en=0;
end
initial begin
#221 rd_en = 1;
end
task gen_data;
integer i;
begin
for(i=0; i<16; i=i+1) begin
wr_en = 1;
wr_data = i;
#20;
end
wr_en = 0;
wr_data = 0;
end
endtask
endmodule
仿真波形如下所示
可以看出在空闲信号产生时 rd_gray == wr_gray_d2