1.FIFO简介
-
按指针顺序读写数据。
FIFO是“First In First Out的简称,是一种根据“先写入的数据则先读出来”的规则进行数据吞吐的数据缓存器。与其它的数据存储器不同,FIFO没有数据地址线,所以数据只能顺序写入和顺序写出。
-
读/写时钟域可以不一样。
异步FIFO的数据写入和数据读出是两个独立的操作,分别由两个不同的时钟进行同步操作:将数据写入FIFO是一个时钟域,将数据从FIFO读出时需要另外一个时钟域。
虽然两个时钟域是独立的,但是对于FIFO的数据读写是可以同时发生的。因为这个特性的存在,使得异步FIFO通常作为2个不同时钟域模块之间的数据缓冲。
-
空/满信号。
对于异步FIFO来说,空满标志的产生尤其重要:FIFO要在装满数据的时候及时拉高
fifo_full
信号,否则之前的数据会被覆盖;FIFO要在内部没有数据的时候及时拉高fifo_empty
信号,否则将读出错误的数据。 -
空/满信号的产生。
当写指针追上读指针的时候,意味着FIFO已经写满了,此时
fifo_full
应立即拉高;而当读指针追上了写指针的时候,意味着FIFO已经读空了,此时fifo_empty
应立即拉高。由于FIFO是异步的,所以空满信号的产生具有一定的要求:满信号
fifo_full
需要在写时钟域产生;空信号fifo_empty
需要在读时钟域产生。满信号
fifo_full
需要在写时钟域产生,且判断该信号的时候需要参考读时钟域的读指针。若直接将写时钟域的写指针wr_ptr
和读时钟域的读指针rd_ptr
进行比较,由于时钟域的不同,很容易发生数据覆盖和数据错误读取的情况发生。例如,当写时钟
wr_clk
频率很快的时候,若直接将写指针和读指针进行比较来判断fifo_full
信号,则可能已经有多个数据写进了FIFO后fifo_full
信号才拉高。解决这个问题的方法在于将读指针
rd_ptr
从读时钟域同步至写时钟域,然后再与写指针wr_ptr
进行比较,以判断fifo_full
是否需要拉高。这样的话,写指针能够在写时钟的下一个周期立马得到读指针的数值,能够在FIFO写满的时候立马将满信号fifo_full
置高。同理,空信号
fifo_empty
亦是如此。 -
亚稳态问题和格雷码。
在跨时钟域同步的过程中,若直接将二进制的指针进行传递,那么在指针发生变化的时候,会有多个比特的数据发生变化,例如
0111->1000
,这样很容易产生亚稳态。亚稳态是指触发器在其建立时间和保持时间阶段输出不能够保持稳定,而当其从亚稳态中脱离后,输出值可能早已发生了错误。亚稳态是可以传递给下一级触发器的,所以这种错误也会一直传递下去。
-
使用格雷码避免亚稳态
可以使用格雷码解决亚稳态的问题:相邻格雷码之间只有1位不同,这也就意味着格雷码在自增的时候,只有1位数据会发生变化,这将极大地避免亚稳态的产生。
对于一个普通二进制数,可以通过异或操作很方便地得到其格雷码值,例如:
十进制表示 普通二进制表示 移位操作结果 格雷码表示 1 0001 0000 0001 2 0010 0001 0011 3 0011 0001 0010 4 0100 0010 0110 用VerilogHDL代码获取写指针
wr_ptr
的格雷码可以写为:assign wr_ptr_gray = wr_ptr^(wr_ptr>>1);//由于wr_ptr是一个无符号整数,所以右移操作后最高位补的是0
将格雷码应用于异步FIFO中的方法:先将二进制的指针通过组合逻辑电路转化为格雷码的形式,随后再进行跨时钟域的同步,同步的时候可以用寄存器打一拍(或者两拍)以保持数据的稳定,例如:
//get Gray code of rd_ptr always @(*) begin rd_ptr_gray = rd_ptr^(rd_ptr>>1); end //sync rd_ptr from rd_clk to wr_clk always @(posedge wr_clk or negedge rst_n) begin if(rst_n) begin sync_rd_ptr_gray_dff <= 'b0; sync_rd_ptr_gray <= 'b0; end else begin sync_rd_ptr_gray_dff <= rd_ptr_gray; sync_rd_ptr_gray <= sync_rd_ptr_gray_dff; end end
2.代码实现
- 基于SystemVerilog的异步FIFO设计实现:
module sv_test #(
parameter AWIDTH = 4,
parameter DWIDTH = 8,
parameter DEPTH = 1 << AWIDTH
)(
input logic rst_n,
input logic wr_clk,
input logic rd_clk,
input logic wr_en,
input logic rd_en,
input logic [DWIDTH-1:0] din,
output logic fifo_full,
output logic fifo_empty,
output logic dout_valid,
output logic [DWIDTH-1:0] dout
);
logic is_in, is_out;
logic [AWIDTH:0] wptr_bin, wptr_gray, next_wptr_bin, next_wptr_gray, sync_wptr_bin, sync_wptr_gray, sync_wptr_gray_dff;
logic [AWIDTH:0] rptr_bin, rptr_gray, next_rptr_bin, next_rptr_gray, sync_rptr_bin, sync_rptr_gray, sync_rptr_gray_dff;
logic [AWIDTH-1:0] raddr, waddr;
logic [DWIDTH-1:0] ram [DEPTH];
assign is_in = wr_en & (~fifo_full);//judge if data should be written in async_fifo
assign is_out = rd_en & (~fifo_empty);//judge if data should be read from async_fifo
always @(posedge wr_clk or negedge rst_n) begin
if(!rst_n) begin
wptr_bin <= 'b0;
end
else if(is_in) begin
wptr_bin <= wptr_bin + 1'b1;
end
end
always @(posedge rd_clk or negedge rst_n) begin
if(!rst_n) begin
rptr_bin <= 'b0;
end
else if(is_out) begin
rptr_bin <= rptr_bin + 1'b1;
end
end
assign wptr_gray = wptr_bin ^ (wptr_bin>>1);
assign rptr_gray = rptr_bin ^ (rptr_bin>>1);
assign next_wptr_bin = wptr_bin + 1'b1;
assign next_rptr_bin = rptr_bin + 1'b1;
assign next_wptr_gray = next_wptr_bin ^ (next_wptr_bin>>1);
assign next_rptr_gray = next_rptr_bin ^ (next_rptr_bin>>1);
//wptr_gray should be stable in rd_clk
always @(posedge rd_clk or negedge rst_n) begin
if(!rst_n) begin
sync_wptr_gray_dff <= 'b0;
sync_wptr_gray <= 'b0;
end
else begin
sync_wptr_gray_dff <= wptr_gray;
sync_wptr_gray <= sync_wptr_gray_dff;
end
end
//rptr_gray should be stable in wr_clk
always @(posedge wr_clk or negedge rst_n) begin
if(!rst_n) begin
sync_rptr_gray_dff <= 'b0;
sync_rptr_gray <= 'b0;
end
else begin
sync_rptr_gray_dff <= rptr_gray;
sync_rptr_gray <= sync_rptr_gray_dff;
end
end
//judge if fifo_full signal should be pull up
assign fifo_full = ((sync_rptr_gray[AWIDTH] != wptr_gray[AWIDTH])
&& ((sync_rptr_gray[AWIDTH]^sync_rptr_gray[AWIDTH-1]) == (wptr_gray[AWIDTH]^wptr_gray[AWIDTH-1]))
&& (sync_rptr_gray[AWIDTH-2:0] == (wptr_gray[AWIDTH-2:0])));
//judge if fifo_empty signal should be pull up
assign fifo_empty = (sync_wptr_gray == rptr_gray);
always @(posedge wr_clk or negedge rst_n) begin
if(!rst_n) begin
waddr <= 'b0;
end
else if(is_in) begin
waddr <= next_wptr_bin[AWIDTH-1:0];
end
end
always @(posedge rd_clk or negedge rst_n) begin
if(!rst_n) begin
raddr <= 'b0;
end
else if(is_out) begin
raddr <= next_rptr_bin[AWIDTH-1:0];
end
end
always @(posedge wr_clk or negedge rst_n) begin
if(is_in) begin
ram[waddr] <= din;
end
end
always @(posedge rd_clk or negedge rst_n) begin
if(!rst_n) begin
dout_valid <= 'b0;
end
else if(is_out) begin
dout_valid <= 'b1;
dout <= ram[raddr];
end
else begin
dout_valid <= 'b0;
end
end
endmodule
- 一个可供参考的测试代码:
module tb_sv_test #(
parameter AWIDTH = 4,
parameter DWIDTH = 8,
parameter DEPTH = 1 << AWIDTH
)();
logic rst_n;
logic wr_clk;
logic rd_clk;
logic wr_en;
logic rd_en;
logic [DWIDTH-1:0] din;
logic fifo_full;
logic fifo_empty;
logic dout_valid;
logic [DWIDTH-1:0] dout;
sv_test my_top( .rst_n(rst_n),
.wr_clk(wr_clk),
.rd_clk(rd_clk),
.wr_en(wr_en),
.rd_en(rd_en),
.din(din),
.fifo_full(fifo_full),
.fifo_empty(fifo_empty),
.dout_valid(dout_valid),
.dout(dout)
);
initial begin
wr_clk = 0;
rd_clk = 0;
fork
forever begin
#5 wr_clk = ~wr_clk;
end
forever begin
#20 rd_clk = ~rd_clk;
end
join_none
end
initial begin
wr_en = 0;
rd_en = 0;
rst_n = 0;
#25;
rst_n = 1;
#10;
fork
forever begin
din = $urandom_range(0,255);
#2;
#0;
wr_en = 1;
#10;
#0;
wr_en = 0;
end
join_none
#500;
fork
forever begin
#0;
rd_en = 1;
#40;
rd_en = 0;
#2;
end
join_none
end
initial begin
#3000;
$finish;
end
// dump fsdb
`ifdef DUMP
initial begin//do_not_remove
$fsdbAutoSwitchDumpfile(1000, "./fsdb/test_fsdb.fsdb", 10);//do_not_remove
$fsdbDumpvars(0, my_top);//do_not_remove
$fsdbDumpMDA(1000, my_top);//do_not_remove
// $fsdbDumpflush();//do_not_remove
//$fsdbDumpvars("+all");//do_not_remove
end//do_not_remove
`endif
endmodule