异步FIFO
本文代码参考Simulation and Synthesis Techniques for Asynchronous FIFO Design Clifford E. Cummings, Sunburst Design, Inc FIFO style #1
异步FIFO是读写时钟频率不同的先入先出数据缓存器。一个系统往往有多个时钟,跨时钟域数据传输由异步FIFO实现。异步FIFO实现时必须考虑跨时钟域问题。跨时钟域问题,是指输入触发器的信号没有满足触发器的建立和保持时间,导致触发器输出t
时间的中间电平,最终稳定到未知电平(高电平或低电平)的现象。其中t
时间的中间电平可以沿着触发器传播。
读和写
FIFO通过读指针和写指针,来确定RAM内部数据位置。读端需要知道写端指针位置 来发出读空信号。写端需要知道读端指针位置 来发出写满信号。FIFO通过这两个信号限制写入和读出,防止指针乱套。
假设有一个深度为 8 的RAM,地址 3 bit,将地址在最高位扩展一位,用来表示写指针超过RAM最大地址(该 4bit 地址记为wrptr,wrptr的低3bit接到RAM记作wraddr,读也一样)。需要记住 写入数据后再将地址加1,该过程由一个时钟上升沿完成,读出也是一样。
FIFO工作流程如下:
- rdptr和wrptr指向RAM地址 0。由于wrptr==rdptr,置高 read empty
- 写使能 clk上升沿开始写数据,同时wrptr变为 1
- 继续写直到wrptr==7(0111)
- 再写一个后wrptr==8(1000),此时rdptr==0(0000) 由于低3bit相同 高位不同,置高 write full
读端也是一样,这里不讨论,增加1bit就可以区分 wraddr==rdaddr时 是满还是空。如果不考虑亚稳态,使用上面的方法就能构建正确的FIFO了。但是由于时钟频率不同,读端rd_clk 去采 写端rdptr可能采到错误数据。造成empty 信号不准确,但是empty和full不准确都是可以接受的
使用格雷码传输指针
在使用二进制码时,空满如下产生。
assign wr_full = (wrptr == {~rdptr[3],rdptr[2:0]});
assign rd_empty = (rdptr == wrptr);
在使用格雷码时,空满信号产生方式不一样。格雷码次高位 0 ~ 7和8 ~ 15之间是对称的。
例如:wrptr=8,rdptr=0,在二进制码里 wrptr高位与rdptr高位相反说明写超过一轮,低3bit相同说明指向同一RAM地址,此时说明FIFO写满。在格雷码里需要将次高位取反再与低0 ~ 7内的rdptr做比较。
然后读取8个数据,rdptr变为8,wrptr==rdptr转成格雷码也相等,此时FIFO读空。
然后写入8个数据,wrptr变为0,rdptr=8,wrptr高位与rdptr高位相反说明写超过一轮。
assign wr_full = (wrptr_gray == {~rdptr_gray_r1[3:2],rdptr_gray_r1[1:0]});
assign rd_empty = (rdptr_gray == wrptr_gray_r1);
结构
Clifford描述的异步FIFO结构图:
其中的sync_r2w、sync_w2r用于缓解亚稳态的传播,并将指针同步到各自时钟域以进行比较,其中传输的是格雷码。
讨论极限情况:
写时钟100MHz,读时钟1MHz。以下说明以上图中的信号举例(格雷码),并且FIFO深度8
1. 写7个数据,下一上升沿写入8,wptr变为1100,并输出wq2_rptr,假设采集的wq2_rptr是错误值(这是因为rptr从0000变为0001,wclk恰好采到了过度态)。由于写时钟很快,wq2_rptr之前的值是0000,新的值(0001)只改变了一位,这一位又采错了变为0000,那么wfull会提早给出,指示满了但还有一个可写。
2. 上升沿读第4个,rptr变为0101,并输出rq2_wptr,上一次rq2_wptr=0110,由于读时钟很慢,这次采样的rq2_wptr=1001,中间跳过了很多格雷码,4比特发生改变(0110=>1001),假设rq2_wptr采样的4bit全错,变成了0101,居然和rptr和一样,那么rempty会意外置高。还有一种情况是 rd端发现读空,置高rempty,但写速度很快已经写入多个数据,这时就是虚空。wfull和rempty是保守估计,不影响使用。
写慢读快的情况类似。
附代码:
/************************************************************************************
* Name :async_fifo.v
* Description :
* Origin :210430
* Author :helrori2011@gmail.com
* Reference :
************************************************************************************/
module async_fifo //(First_Word_Fall_Through FIFO,数据提前输出,可能不利于时序收敛)
#(
parameter integer DATA_WIDTH = 16 ,
parameter integer ADDR_WIDTH = 8 //深度只能是2**ADDR_WIDTH
)
(
input wire wr_rst ,
input wire wr_clk ,
input wire wr_ena ,
input wire[DATA_WIDTH-1:0] wr_dat ,
output wire wr_full ,
input wire rd_rst ,
input wire rd_clk ,
input wire rd_ena ,
output wire[DATA_WIDTH-1:0] rd_dat ,
output wire rd_empty
);
reg [DATA_WIDTH-1:0]mem[0:2**ADDR_WIDTH-1];
reg [ADDR_WIDTH :0]wrptr;
reg [ADDR_WIDTH :0]rdptr;
wire [ADDR_WIDTH-1:0]wraddr = wrptr[ADDR_WIDTH-1:0];
wire [ADDR_WIDTH-1:0]rdaddr = rdptr[ADDR_WIDTH-1:0];
wire [ADDR_WIDTH :0]wrptr_gray = (wrptr>>1)^wrptr;
wire [ADDR_WIDTH :0]rdptr_gray = (rdptr>>1)^rdptr;
reg [ADDR_WIDTH :0]wrptr_gray_r0;
reg [ADDR_WIDTH :0]wrptr_gray_r1;
reg [ADDR_WIDTH :0]rdptr_gray_r0;
reg [ADDR_WIDTH :0]rdptr_gray_r1;
assign wr_full = (wrptr_gray == {~rdptr_gray_r1[ADDR_WIDTH:ADDR_WIDTH-1],rdptr_gray_r1[ADDR_WIDTH-2:0]});
assign rd_empty = (rdptr_gray == wrptr_gray_r1);//在有些设计中,full和empty提早一个时钟出现,并加了一级寄存,这有助于收敛
always @(posedge wr_clk or posedge wr_rst) begin
if (wr_rst) begin
rdptr_gray_r0 <= 'd0;
rdptr_gray_r1 <= 'd0;
end else begin
rdptr_gray_r0 <= rdptr_gray;
rdptr_gray_r1 <= rdptr_gray_r0;
end
end
always @(posedge rd_clk or posedge rd_rst) begin
if (rd_rst) begin
wrptr_gray_r0 <= 'd0;
wrptr_gray_r1 <= 'd0;
end else begin
wrptr_gray_r0 <= wrptr_gray;
wrptr_gray_r1 <= wrptr_gray_r0;
end
end
always @(posedge wr_clk or posedge wr_rst) begin
if (wr_rst) begin
mem[0] <= 'd0;
wrptr <= 'd0;
end else if (wr_ena&&(!wr_full)) begin
mem[wraddr] <= wr_dat;
wrptr <= wrptr + 1'd1;
end else begin
mem[wraddr] <= mem[wraddr];
wrptr <= wrptr;
end
end
always @(posedge rd_clk or posedge rd_rst) begin
if (rd_rst) begin
rdptr <= 'd0;
end else if (rd_ena&&(!rd_empty)) begin
rdptr <= rdptr + 1'd1;
end else begin
rdptr <= rdptr;
end
end
assign rd_dat = mem[rdaddr];
endmodule
testbench
//~ `New testbench
`timescale 1ns / 1ps
module tb_async_fifo;
// async_fifo Parameters
parameter PERIOD_WR = 1000 ;
parameter PERIOD_RD = 20 ;
parameter DATA_WIDTH = 16 ;
parameter ADDR_WIDTH = 8 ;
parameter DATA_DEPTH = 2**ADDR_WIDTH;
// async_fifo Inputs
reg wr_clk = 0 ;
reg wr_ena = 0 ;
reg [DATA_WIDTH-1:0] wr_dat = 0 ;
reg rd_clk = 0 ;
reg rd_ena = 0 ;
// async_fifo Outputs
wire wr_full ;
wire [DATA_WIDTH-1:0] rd_dat ;
wire rd_empty ;
reg rst=1;
always #(PERIOD_WR/2) wr_clk=~wr_clk;
always #(PERIOD_RD/2) rd_clk=~rd_clk;
async_fifo #(
.DATA_WIDTH ( DATA_WIDTH ),
.ADDR_WIDTH ( ADDR_WIDTH ))
u_async_fifo (
.wr_rst (rst ),
.wr_clk (wr_clk ),
.wr_ena (wr_ena ),
.wr_dat (wr_dat ),
.wr_full (wr_full ),
.rd_rst (rst ),
.rd_clk (rd_clk ),
.rd_ena (rd_ena ),
.rd_dat (rd_dat ),
.rd_empty(rd_empty)
);
task automatic wr;
input integer cnt;
begin:w
integer i;
@(posedge wr_clk)
#0 wr_ena=1;
wr_dat=0;
for ( i=0 ;i< cnt;i=i+1 ) begin
@(posedge wr_clk)
if (i==cnt-1) begin
#0 wr_ena=0;
end else begin
#0 wr_dat=wr_dat+1;
end
end
end
endtask
task automatic rd;
input integer cnt;
begin:r
integer j;
@(posedge rd_clk)
#0 rd_ena=1;
for ( j=0 ;j< cnt;j=j+1 ) begin
@(posedge rd_clk)
if(rd_dat!=j)begin
$display("error");
$stop;
end
if (j==cnt-1) begin
#0 rd_ena=0;
end
end
end
endtask
task automatic massive_test;
input real times;
begin:massive_test
real k;
integer rands,wrn;
real pct;
for(k=0;k<times;k=k+1)begin
pct= k/times;
$display("%.0f%%",pct*100);
rands = {$random}%(DATA_DEPTH+DATA_DEPTH/2);
rands = rands+1;
wrn = rands>DATA_DEPTH?DATA_DEPTH:rands;
wr(rands);//writing side do not care about wr_full
wait(!rd_empty) @(posedge rd_clk);// reading side will check rd_empty before read
rd(wrn);
end
end
endtask
initial
begin
//$dumpfile("wave.vcd");
//$dumpvars(0,tb_async_fifo);
#(PERIOD_WR*20) rst = 0;
#(PERIOD_WR*20)
massive_test(1000);
$display("Test success");
#(PERIOD_WR*20)
$finish;
end
endmodule
full和empty提前输出并设置一级寄存的版本:
除了时序好一点,其他一样
module async_fifo //(First_Word_Fall_Through FIFO,数据提前输出)
#(
parameter integer DATA_WIDTH = 16 ,
parameter integer ADDR_WIDTH = 8
)
(
input wire wr_rst ,
input wire wr_clk ,
input wire wr_ena ,
input wire[DATA_WIDTH-1:0] wr_dat ,
output reg wr_full ,
input wire rd_rst ,
input wire rd_clk ,
input wire rd_ena ,
output wire[DATA_WIDTH-1:0] rd_dat ,
output reg rd_empty
);
reg [DATA_WIDTH-1:0]mem[0:2**ADDR_WIDTH-1];
reg [ADDR_WIDTH :0]wrptr;
reg [ADDR_WIDTH :0]rdptr;
wire [ADDR_WIDTH :0]wrptr_nxt = wrptr + (wr_ena&(~wr_full ));
wire [ADDR_WIDTH :0]rdptr_nxt = rdptr + (rd_ena&(~rd_empty));
wire [ADDR_WIDTH-1:0]wraddr = wrptr[ADDR_WIDTH-1:0];
wire [ADDR_WIDTH-1:0]rdaddr = rdptr[ADDR_WIDTH-1:0];
wire [ADDR_WIDTH :0]wrptr_nxt_gray = (wrptr_nxt>>1)^wrptr_nxt;
wire [ADDR_WIDTH :0]rdptr_nxt_gray = (rdptr_nxt>>1)^rdptr_nxt;
reg [ADDR_WIDTH :0]wrptr_gray;
reg [ADDR_WIDTH :0]rdptr_gray;
reg [ADDR_WIDTH :0]wrptr_gray_r0;
reg [ADDR_WIDTH :0]wrptr_gray_r1;
reg [ADDR_WIDTH :0]rdptr_gray_r0;
reg [ADDR_WIDTH :0]rdptr_gray_r1;
assign wr_full_nxt = (wrptr_nxt_gray == {~rdptr_gray_r1[ADDR_WIDTH:ADDR_WIDTH-1],rdptr_gray_r1[ADDR_WIDTH-2:0]});
assign rd_empty_nxt = (rdptr_nxt_gray == wrptr_gray_r1);
always @(posedge rd_clk or posedge rd_rst) begin
if (rd_rst) begin
rdptr_gray <= 'd0;
end else begin
rdptr_gray <= rdptr_nxt_gray;
end
end
always @(posedge wr_clk or posedge wr_rst) begin
if (wr_rst) begin
rdptr_gray_r0 <= 'd0;
rdptr_gray_r1 <= 'd0;
end else begin
rdptr_gray_r0 <= rdptr_gray;
rdptr_gray_r1 <= rdptr_gray_r0;
end
end
always @(posedge wr_clk or posedge wr_rst) begin
if (wr_rst) begin
wrptr_gray <= 'd0;
end else begin
wrptr_gray <= wrptr_nxt_gray;
end
end
always @(posedge rd_clk or posedge rd_rst) begin
if (rd_rst) begin
wrptr_gray_r0 <= 'd0;
wrptr_gray_r1 <= 'd0;
end else begin
wrptr_gray_r0 <= wrptr_gray;
wrptr_gray_r1 <= wrptr_gray_r0;
end
end
always @(posedge wr_clk or posedge wr_rst) begin
if (wr_rst) begin
mem[0] <= 'd0;
wrptr <= 'd0;
end else if (wr_ena&&(!wr_full)) begin
mem[wraddr] <= wr_dat;
wrptr <= wrptr_nxt;
end else begin
mem[wraddr] <= mem[wraddr];
wrptr <= wrptr;
end
if (wr_rst) begin
wr_full <= 'd0;
end begin
wr_full <= wr_full_nxt;
end
end
always @(posedge rd_clk or posedge rd_rst) begin
if (rd_rst) begin
rdptr <= 'd0;
rd_empty <= 'd0;
end else begin
rdptr <= rdptr_nxt;
rd_empty <= rd_empty_nxt;
end
end
assign rd_dat = mem[rdaddr];
endmodule
- 波形中的未定值,是由于mem未初始化和RAM提前输出造成的,这只在rd_empty时出现,不影响使用。
- 行为仿真不能说明所有问题
以上。
还是不太理解 使用格雷码可以避免下面情况出现
wrptr==rdptr时empty没有置高
wrptr、rdptr低3bit相同高位不同时full没有置高