Asynchronous FIFO Design
总结来自Clifford E. Cummings论文 《Simulation and Synthesis Techniques for Asynchronous FIFO Design》
一、设计难点
- 使用格雷码计数时空和满的判断。
- 同步FIFO读写时钟相同,而异步FIFO读写来自不同两个读写时钟,需要考虑跨时钟域设计。
二、设计思路
把多bit信号跨时钟域是有问题的。设计一般通过FIFO对多bit信号进行安全的跨时钟域传递。
2.1 空满判断
1、二进制空满判断
如图1,在二进制计数nbit地址时,判断full和empty的条件都是waddr==raddr,为了区分,增加1bit,这样地址位宽范围变为[n:0]。此时,当 waddr[n:0]==raddr[n:0]
时,为empty。而full时,waddr会绕raddr一圈,因此判定为
{~waddr[n],waddr[n-1:0]}==raddr[n:0]
。
2、格雷码空满判断
但采用格雷码计数时,虽然MSB依然是前半部分是0,后半部分是1,但[n-1:0]却不是循环,而是对称,如下图所示。
但是也能看出来,当最高位变成1后,除了最高的前两位,其余位是符合最高位是0时候的顺序规律。因此可通过
{~waddr[n],~waddr[n-1:0],waddr[n-2:0]}==raddr[n:0]
判断full。当 waddr[n:0]==raddr[n:0]
时,为empty。
3、保守空满
思考一下,当判断空或满的时候,总有一个指针是通过两级触发器同步过来的,也就意味着有延迟,那这样的判断真的准确吗?下面分别从空和满的角度去分析:
- 假空
判断空的时候,需要用读指针和经过读时钟同步过来的写指针来比较。如果在同步写指针时,继续写操作,同步过来的仍然是之前落后的写指针,此时即使读指针和经过读时钟同步过来的写指针满足空的条件,FIFO仍有新写入的数据,即假空。
- 假满
判断满的时候,需要写指针和经过写时钟同步过来的读指针进行比较。如果在同步读指针时,继续读操作,同步过来的仍然是之前落后的读指针,此时即使写指针和经过写时钟同步过来的读指针满足满的条件,FIFO仍然有数据已经被读出,即假满。
综上所述,假满和假空虽然存在,但最多会降低运行吞吐率,性能降低,功能仍正确,而不会出现上溢出和下溢出。
但是想一想为什么要在写侧判断满,而在读测判断空?在读测判断满行不行?
答案是不可以。如果在读侧判断满,当读指针和读时钟同步过来的写指针满足满的条件式,已经有了延迟,如果写操作仍在进行,就会覆盖数据,造成功能不正确
2.2 Multi-bit 跨时钟域同步
1、快时钟频率是慢时钟的两倍甚至更快,那么快域地址变化两次,慢时钟域采样一次,前后采样值变化了两次,会产生多位同步的问题吗?
不会,只有多个位同时变化时才会有问题。因为在慢时钟域下只能看到最近的一次跳变,格雷码相邻跳变只有一位不同。
2、Multi-bit 异步复位
异步复位通常会导致指针位多bit产生变化,这会有问题吗?
因为复位后fifo里的数据已经无效,因此不会有问题,另外,虽然读写各有单独的复位信号,但单侧复位时必须通知另一侧,否则会出错。
2.3 格雷码计数器设计
文章给出两种结构,下面依次介绍
1、 第一种
第一种寄存器存储的均是格雷码。
2、第二种
第二种计数器存储的是二进制,直接用二进制对存储器寻址。二进制到格雷码转换的逻辑转移到下面的寄存器。即格雷码只用于跨时钟域的同步。这样做减少延迟,提高频率。
三、RTL结构与设计
1、FIFO结构
结构如下。
根据不同的功能和时钟域,分为以下六个模块:
名字 | 功能 | 时钟域 |
---|---|---|
fifo1.v | 顶层 | 所有 |
fifomem.v | 存储,一般用伪双端口ram | 所有 |
sync_r2w.v | 写时钟同步读指针 | 写 |
sync_w2r.v | 读时钟同步写指针 | 读 |
rptr_empty.v | 空判断 | 读 |
wptr_full.v | 满判断 | 写 |
2、Verilog代码
- fifo.v
module fifo1 #( parameter DSIZE = 8,parameter ASIZE = 4)(output [DSIZE-1:0] rdata,output wfull,output rempty,input [DSIZE-1:0] wdata,input winc, wclk, wrst_n,input rinc, rclk, rrst_n
);wire [ASIZE-1:0] waddr, raddr;wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;sync_r2w sync_r2w (.wq2_rptr(wq2_rptr), .rptr(rptr),.wclk(wclk), .wrst_n(wrst_n));sync_w2r sync_w2r (.rq2_wptr(rq2_wptr), .wptr(wptr),.rclk(rclk), .rrst_n(rrst_n));fifomem #(DSIZE, ASIZE) fifomem(.rdata(rdata), .wdata(wdata),.waddr(waddr), .raddr(raddr),.wclken(winc), .wfull(wfull),.wclk(wclk));rptr_empty #(ASIZE) rptr_empty(.rempty(rempty),.raddr(raddr),.rptr(rptr), .rq2_wptr(rq2_wptr),.rinc(rinc), .rclk(rclk),.rrst_n(rrst_n));wptr_full #(ASIZE) wptr_full(.wfull(wfull), .waddr(waddr),.wptr(wptr), .wq2_rptr(wq2_rptr),.winc(winc), .wclk(wclk),.wrst_n(wrst_n));
endmodule
- fifomem.v
module fifomem #( parameter DATASIZE = 8, // Memory data word widthparameter ADDRSIZE = 4)( // Number of mem address bitsoutput [DATASIZE-1:0] rdata,input [DATASIZE-1:0] wdata,input [ADDRSIZE-1:0] waddr, raddr,input wclken, wfull, wclk);
`ifdef VENDORRAM// instantiation of a vendor's dual-port RAMvendor_ram mem (.dout(rdata), .din(wdata),.waddr(waddr), .raddr(raddr),.wclken(wclken),.wclken_n(wfull), .clk(wclk));
`else// RTL Verilog memory modellocalparam DEPTH = 1<<ADDRSIZE;reg [DATASIZE-1:0] mem [0:DEPTH-1];assign rdata = mem[raddr];always @(posedge wclk)if (wclken && !wfull) mem[waddr] <= wdata;
`endif
endmodule
- sync_r2w.v
module sync_r2w #(parameter ADDRSIZE = 4)(output reg [ADDRSIZE:0] wq2_rptr,input [ADDRSIZE:0] rptr,input wclk, wrst_n
);
reg [ADDRSIZE:0] wq1_rptr;
always @(posedge wclk, negedge wrst_n) beginif(!wrst_n){wq2_rptr,wq1_rptr} <= 'b0;else {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
end
endmodule
- sync_w2r.v
module sync_w2r #(parameter ADDRSIZE = 4)(output reg [ADDRSIZE:0] rq2_wptr,input [ADDRSIZE:0] wptr,input rclk, rrst_n
);
reg [ADDRSIZE:0] rq1_wptr;
always @(posedge rclk, negedge rrst_n) beginif(!rrst_n){rq2_wptr,rq1_wptr} <= 'b0;else {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
end
endmodule
- rptr_empty.v
module rptr_empty #(parameter ADDRSIZE = 4) (output reg rempty, output [ADDRSIZE-1:0] raddr, output reg [ADDRSIZE :0] rptr, input [ADDRSIZE :0] rq2_wptr, input rinc, rclk, rrst_n
);
reg [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] bnext,gnext;
assign raddr = rbin[ADDRSIZE-1:0];
// bin counter
assign bnext = rbin + (rinc & ~rempty);
always @(posedge rclk or negedge rrst_n) beginif(!rrst_n)rbin <= {(ADDRSIZE+1){1'b0}};elserbin <= bnext;
end
// gray counter
assign gnext = (bnext>>1) ^ bnext;
always @(posedge rclk or negedge rrst_n) beginif(!rrst_n)rptr <= {(ADDRSIZE+1){1'b0}};elserptr <= gnext;
end
// empty flag
always @(posedge rclk or negedge rrst_n) beginif(!rrst_n)rempty <= 1'b1;elserempty <= rq2_wptr == gnext;
end
endmodule
- wptr_full.v
module wptr_full #(parameter ADDRSIZE = 4)(output reg wfull,output [ADDRSIZE-1:0] waddr,output reg [ADDRSIZE :0] wptr,input [ADDRSIZE :0] wq2_rptr,input winc, wclk, wrst_n
);
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] bnext,gnext;assign waddr = wbin[ADDRSIZE-1:0];
// bin counter
assign bnext = wbin + (winc & ~wfull);
always @(posedge wclk or negedge wrst_n) beginif(!wrst_n)wbin <= {(ADDRSIZE+1){1'b0}};elsewbin <= bnext;
end
// gray counter
assign gnext = (bnext>>1) ^ bnext;
always @(posedge wclk or negedge wrst_n) beginif(!wrst_n)wptr <= {(ADDRSIZE+1){1'b0}};elsewptr <= gnext;
end
// full flag
always @(posedge wclk or negedge wrst_n) beginif(!wrst_n)wfull <= 1'b0;elsewfull <= gnext == {~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]};
end
endmodule