1. 异步FIFO原理
2. 格雷码传递FIFO读写指针(回环特性)
通常情况下,设计的异步FIFO的深度是2的N次方,但事实上,选择这个2^N的原因也是因为格雷码这么取的时候,最大值+1回到最小值时,跳变还是只有1bit。
事实上格雷码可以取2N个进行环回,例如取格雷码个数为12,在2^4=16的基础上,掐头去尾,取中间的12个格雷码,这样从0011到最大值1011中间任意两个相邻数之间只有1bit位的跳变。
任意偶数个数格雷码,都可以实现相邻数字跳变仅有1bit发生变化。
3. 任意深度异步FIFO
异步FIFO的关键就在于读写指针跨时钟域传输的问题,保证每次跳变仅 有1bit变化,这样无论是变化前还是变化后的读写指针被同步到另一个时钟域,都不会发生“FIFO空时继续读”、“FIFO满时继续写”这样的逻辑错误。
3.1 偶数深度的异步FIFO
对于偶数深度的FIFO,如FIFO深度为4。那么无论FIFO深度是多少,我们都会对地址进行扩展1bit,来作为标志位,用于产生空满信号。因此FIFO深度为4时,我们的读写指针取值范围为:
{0_110,0_111,0_101,0_100,1_100,1_101,1_111,1_110}
可以看到,任意两个相邻的数字仅有1bit发生跳变,取格雷码回环就按照:在2^4=16的基础上,掐头去尾,取中间的8个格雷码。
3.2 奇数深度的异步FIFO
对于奇数深度的FIFO,如FIFO深度为3。那么无论FIFO深度是多少,我们都会对地址进行扩展1bit,来作为标志位,用于产生空满信号。因此FIFO深度为3时,我们的读写指针取值范围为:
{0_111,0_101,0_100,1_100,1_101,1_111}
可以看到,任意两个相邻的数字仅有1bit发生跳变,取格雷码回环就按照:在2^4=16的基础上,掐头去尾,取中间的6个格雷码。
也就是说,无论FIFO深度是奇数还是偶数,我们都会对读写指针扩1bit标志位,使得扩充后他们的深度一定是偶数。这样我们就可以用第二节提到的偶数格雷码环回特性:任意偶数个数格雷码,都可以实现相邻数字跳变仅有1bit发生变化。让读写指针跨时钟域传输时,相邻数字仅有1bit发生变化,就不会出现FIFO读写错误,所以FIFO深度可以是任意值。
4. Verilog 实现
4.1 设计代码
module async_fifo
#(
parameter DATA_WIDTH = 16, //FIFO 位宽(数据位宽)
FIFO_DEPTH = 8, //FIFO 深度
ADDR_DEPTH = $clog2(FIFO_DEPTH) //根据FIFO深度计算地址位宽,此处为3。
)
(
//reset signals
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 signals
output wire fifo_full_o ,
output wire fifo_empty_o
);
//memary
reg [DATA_WIDTH-1:0] fifo_buffer [FIFO_DEPTH-1:0];
//memary addr
wire [ADDR_DEPTH-1:0] wr_addr; //真实写地址,作为写ram地址
wire [ADDR_DEPTH-1:0] rd_addr; //真实读地址,作为读ram地址
//write poiter and write poiter of gray and sync
reg [ADDR_DEPTH:0] wr_ptr ; //写指针,二进制
wire [ADDR_DEPTH:0] gray_wr_ptr ; //写指针,格雷码
reg [ADDR_DEPTH:0] gray_wr_ptr_d0 ; //写指针在读时钟域下同步一拍
reg [ADDR_DEPTH:0] gray_wr_ptr_d1 ; //写指针在读时钟域下同步二拍
//read poiter and read poiter of gray and sync
reg [ADDR_DEPTH:0] rd_ptr ; //读指针,二进制
wire [ADDR_DEPTH:0] gray_rd_ptr ; //读指针,格雷码
reg [ADDR_DEPTH:0] gray_rd_ptr_d0 ; //读指针在写时钟域下同步一拍
reg [ADDR_DEPTH:0] gray_rd_ptr_d1 ; //读指针在写时钟域下同步二拍
//---write poiter and bin2gray---//
always@(posedge wr_clk_i or negedge wr_rst_n_i)begin
if(~wr_rst_n_i)begin
wr_ptr <= 'b0;
end else if(wr_en_i && !fifo_full_o)begin //写使能有效,且非满
wr_ptr <= wr_ptr + 1'b1;
end
end
assign gray_wr_ptr = wr_ptr ^ (wr_ptr>>1);
//---gray_wr_ptr sync to read clk domain---//
always@(posedge rd_clk_i or negedge rd_rst_n_i)begin
if(~rd_rst_n_i)begin
gray_wr_ptr_d0 <= 'b0; //寄存1拍
gray_wr_ptr_d1 <= 'b0; //寄存2拍
end else begin
gray_wr_ptr_d0 <= gray_wr_ptr ; //寄存1拍
gray_wr_ptr_d1 <= gray_wr_ptr_d0; //寄存2拍
end
end
//---read poiter and bin2gray---//
always@(posedge rd_clk_i or negedge rd_rst_n_i)begin
if(~rd_rst_n_i)begin
rd_ptr <= 'b0;
end else if(rd_en_i && !fifo_empty_o)begin //读使能有效,且非空
rd_ptr <= rd_ptr + 1'b1;
end
end
assign gray_rd_ptr = rd_ptr ^ (rd_ptr>>1);
//---gray_rd_ptr sync to write clk domain---//
always@(posedge wr_clk_i or negedge wr_rst_n_i)begin
if(~wr_rst_n_i)begin
gray_rd_ptr_d0 <= 'b0; //寄存1拍
gray_rd_ptr_d1 <= 'b0; //寄存2拍
end else begin
gray_rd_ptr_d0 <= gray_rd_ptr ; //寄存1拍
gray_rd_ptr_d1 <= gray_rd_ptr_d0; //寄存2拍
end
end
//---full flag and empty flag---//
//当高位相反且其他位相等时,写指针超过读指针一圈,FIFO被写满
//同步后的读指针格雷码高两位取反,再拼接上余下位
assign fifo_full_o = (gray_wr_ptr == {~gray_rd_ptr_d1[ADDR_DEPTH], ~gray_rd_ptr_d1[ADDR_DEPTH-1], gray_rd_ptr_d1[ADDR_DEPTH-2:0]})
? 1'b1 : 1'b0;
//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign fifo_empty_o= (gray_rd_ptr == gray_wr_ptr_d1) ? 1'b1 : 1'b0;
//---write addr and read addr---//
assign wr_addr = wr_ptr[ADDR_DEPTH-1:0]; //写RAM地址等于写指针的低 ADDR_DEPTH 位(去除最高位)
assign rd_addr = rd_ptr[ADDR_DEPTH-1:0]; //读RAM地址等于读指针的低 ADDR_DEPTH 位(去除最高位)
//---write operation---//
integer 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
fifo_buffer[i] <= 'd0;
end
end else if(wr_en_i && !fifo_full_o)begin //写使能有效,且非满
fifo_buffer[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 && !fifo_empty_o)begin //读使能有效,且非空
rd_data_o <= fifo_buffer[rd_addr];
end
end
endmodule
4.2 测试代码(testbench)
`timescale 1ns/1ns //时间单位/精度
//----------<模块及端口声明>-------//
module tb_async_fifo();
parameter DATA_WIDTH = 8 ; //FIFO位宽
parameter FIFO_DEPTH = 8 ; //FIFO深度
parameter TEST_TIME = 300; //随机测试最大次数
reg wr_rst_n_i ;
reg rd_rst_n_i ;
reg wr_clk_i ;
reg wr_en_i ;
reg [DATA_WIDTH-1:0] wr_data_i ;
reg rd_clk_i ;
reg rd_en_i ;
wire [DATA_WIDTH-1:0] rd_data_o ;
wire fifo_full_o ;
wire fifo_empty_o;
integer i;
//------------<设置时钟>-------------------//
always #10 rd_clk_i = ~rd_clk_i; //读时钟周期20ns
always #20 wr_clk_i = ~wr_clk_i; //写时钟周期40ns
//------------<设置初始测试条件>----------//
initial begin
rd_clk_i = 1'b0; //初始时钟为0
wr_clk_i = 1'b0; //初始时钟为0
wr_rst_n_i <= 1'b0; //初始复位
rd_rst_n_i <= 1'b0; //初始复位
wr_en_i <= 1'b0;
rd_en_i <= 1'b0;
wr_data_i <= 'd0;
#5
wr_rst_n_i <= 1'b1;
rd_rst_n_i <= 1'b1;
//-----------固定测试-------------//
//重复8次写操作,让FIFO写满
repeat(8) begin
@(negedge wr_clk_i)begin
wr_en_i <= 1'b1;
wr_data_i <= $random; //生成8位随机数
end
end
//拉低写使能
@(negedge wr_clk_i) wr_en_i <= 1'b0;
//重复8次读操作,让FIFO读空
repeat(8) begin
@(negedge rd_clk_i)
rd_en_i <= 1'd1;
end
//拉低读使能
@(negedge rd_clk_i)rd_en_i <= 1'd0;
//重复4次写操作,写入4个随机数据
repeat(4) begin
@(negedge wr_clk_i)begin
wr_en_i <= 1'b1;
wr_data_i <= $random; //生成8位随机数
end
end
//持续同时对FIFO读
@(negedge rd_clk_i)rd_en_i <= 1'b1;
//持续同时对FIFO写,写入数据为随机数据
repeat(100) begin
@(negedge wr_clk_i)begin
wr_en_i <= 1'b1;
wr_data_i <= $random; //生成8位随机数
end
end
// 拉低读写使能
@(negedge wr_clk_i)wr_en_i <= 1'b0;
@(negedge rd_clk_i)rd_en_i <= 1'b0;
//等待5个写时钟周期
repeat(5) @(posedge wr_clk_i);
//--------------随机测试---------------//
for(i=0; i<TEST_TIME; i=i+1)begin
if(i<100) begin
@(negedge wr_clk_i) ;
wr_en_i <= $random() % 2;
wr_data_i <= $random();
@(negedge rd_clk_i);
rd_en_i <= 1;
end
if(i<200) begin
@(negedge wr_clk_i) ;
wr_en_i <= 1;
wr_data_i <= $random();
@(negedge rd_clk_i);
rd_en_i <= $random() % 2;
end
else if(i<TEST_TIME)begin
@(negedge wr_clk_i) ;
wr_en_i <= $random() % 2;
wr_data_i <= $random();
@(negedge rd_clk_i);
rd_en_i <= $random() % 2;
end
end
repeat(5) @(posedge wr_clk_i);
$finish();
end
//------------<例化被测试模块>-----------//
async_fifo
#(
.DATA_WIDTH (DATA_WIDTH), //FIFO位宽
.FIFO_DEPTH (FIFO_DEPTH) //FIFO深度
)
async_fifo_inst(
.wr_rst_n_i (wr_rst_n_i ),
.rd_rst_n_i (rd_rst_n_i ),
.wr_clk_i (wr_clk_i ),
.wr_en_i (wr_en_i ),
.wr_data_i (wr_data_i ),
.rd_clk_i (rd_clk_i ),
.rd_en_i (rd_en_i ),
.rd_data_o (rd_data_o ),
.fifo_full_o (fifo_full_o ),
.fifo_empty_o (fifo_empty_o)
);
endmodule