异步FIFO设计可以说是数字IC设计工程师面试时必问的一个问题了,也是我们经常使用但是又往往被忽略的一个东西,今天就展开详细说一说不同深度(2^N或者非2^N)异步FIFO的设计思想;
一:2^N深度异步FIFO设计
1:应用场景
异步FIFO用来在两个异步时钟域之间传输数据,如下图中是两个系统,分别为“system A”和“systemY”,从X向Y传输数据,两个系统工作在不同的时钟域。
system X使用 xclk将数据写入FIFO,systemY使用yclk将数据读出,fifo full和fifo_empty分别负责监控上溢(overflow)和下溢(underflow)情况
fifo_full 指示上溢出情况,拉高时数据不应再写入FIFO,否则会将FIFO内的数据覆盖掉;
fifo_empty指示下溢出情况,拉高时不应该再读取FIFO,否则会读出垃圾数据
与握手信号不同,异步FIFO用于对性能要求较高的设计中,尤其是时钟延时比系统资源更为重要的环境中。
2:异步FIFO的结构
与同步FIFO设计原理相似,异步FIFO的结构也可以分为三个部分,如图中所示,fifo write control。fifo read control和fifo memory,其中fifo write control和fifo read control分别工作在wr_clk和rd_clk时钟域,fifo memory工作在wr_clk和rd_clk时钟域。
fifo write control:写指针产生,满信号产生,读指针同步器
fifo read control:读指针产生,空信号产生和写指针同步器
fifo memory:缓存数据
3:关键设计
3.1:异步fifo和同步fifo的差异,异步fifo特点:
1:fifo memory工作在两个时钟域
2:wr_ctrl和rd_ctrl工作在不同的时钟域
3:读/写指针经过两级同步器后会存在延迟
异步FIFO不能再使用“计数器”产生fifo空,满信号,原因在于计数器不能被两个时钟同时驱动,此时只能通过比较读,写指针来产生空,满信号,将读指针同步到写时钟域,与写指针比较产生满信号,将写指针同步到读时钟域,与读指针比较产生空信号。
读写指针的同步都需要跨时钟域传输,若使用握手信号的方式同步指针,效率很低;加入指针仍然使用“二进制编码”,使用两级同步器同步指针,可能会出现采样的亚稳态导致用于比较的指针出现错误。
如指针从“111-000”经过两级同步器,可能的结果有8种可能,每一个比特都可能存在采样错误,错误的指针会导致fifo空,满信号异常拉起。
# 读指针同步错误,FIFO满信号未正常拉高,继续写FIFO会覆盖原数据,导致数据传输错误;
# 写指针同步错误,FIFO空信号为正常拉高,继续读FIFO会读出垃圾数据,导致数据传输错误;
因此异步fifo的指针在跨时钟做同步时应该避免使用二进制编码!
3.2:格雷码
实现异步fifo指针同步的一种方式是使用gray code,格雷码是“单位间距码”,即相邻之间只有1bit不同,格雷码与二进制码的对应关系如下图中所示,格雷码有一下几个特点:
# 单位间距码:相邻值之间只有1bit不同;
# 中心对称:除MSB外,其余bit中心对称;
指针采用格雷码经过两级同步器同步很少会出现亚稳态,此外取样后的值最多只有1bit出现错误,但该错误不会影响空,满信号的产生,考虑格雷码读写指针采样错误的情况:
# 读指针若采样错误:相邻gray code只有1bit不同,若采样出现亚稳态会出现两种情况,一是采样值保持不变,同步的读指针小于当前的真实值,fifo full会提前拉高,此时不会出现overflow;二是采样值采样成功,同步的读指针等于当前的真实值,两种结果都不会影响fifo_full的正确产生;
# 写指针采样错误:写指针同理,一是同步的写指针小于当前的真实值,fifo empty会提前拉高,虽然还有数据未读出,但是这种情况也不会造成underflow;二是同步的写指针党羽当前的真实值,两种结果都不会影响fifo empty的正确产生。
3.3:同步器对指针进行同步操作带来的影响:
比较读写指针的目的是产生FIFO的空满信号,相比同步FIFO,异步FIFO在两个时钟域分别比较读写指针,读/写指针都需要经过两级同步器;
3.3.1:FIFO的 “假满”
由于读指针同步到写时钟域存在2*T_wclk的延迟,此时写时钟域采样到的rd_ptr_sync必然小于或者等于读时钟域的rd_ptr,rd_ptr_sync小于rd_ptr的情况如图所示,wr_ptr追赶上rd_ptr_sync (wr_ptr和rd_ptr_sync都转换为gray code) ,最高位不同其余未相同,此时fifo full信号拉高,但是实际上FIFO内还有两个地址空间可以写入数据,这种情况就是FIFO的假满,fifo的假满虽然阻止了数据的写入,但是对数据的准确性是没有影响的,只有在FIFO实际已经满了但是没有阻止数据写入才会出现overflow(原本有效的数据被覆盖)
3.3.2 :FIFO的 “假空”
由于写指针同步到读时钟域存在2*T_rclk的延迟,此时读时钟域采样到的wr_ptr_sync小于等于读时钟域的wr_ptr。wr_ptr_sync小于等于wr_ptr的情况如图所示,wr_ptr_sync等于rd_ptr(指针均以转换为gray code),所有比特位均相等,此时会拉高fifo_empty信号。FIFO的“假空”虽然在实际有数据的情况下拉高了FIFO的空信号,阻止了数据的读出,但是对数据的准确性是没有影响的,直到读时钟域看到的写指针变化才会拉低FIFO的空信号。只有在FIFO为空的时候没有阻止读操作才会产生问题,即underflow问题;
3.4 格雷码和二进制码的转换
读、写指针有二进制码转为格雷码后经两级同步器同步到写、读时钟域进行指针的比较以产生FIFO的空、满信号,下面将给出格雷码与二进制码相互转换的方法。
二进制转换gray code:
gray code转换二进制:
3.5 读,写指针的产生
读写指针的产生有两种方法,一种是直接使用gray code计数器产生读写指针,一种是使用二进制码计数器产生读写指针,再将读写指针转换为格雷码用于跨时钟域的同步,下面将分析这两种设计方法:
3.5.1格雷码计数器
格雷码计数器的结构如图所示,由格雷码转换二进制,二进制加法器和二进制码转格雷码组成;
从面积和工作频率两个方面分析该设计。
# 面积:格雷码转二进制、二进制转格雷码的组合逻辑,格雷码指针寄存器;
# 工作频率:寄存器输入信号的组合逻辑较为复杂,该电路工作在较高频率可能存在时序违例的情况。
3.5.2 二进制码计数器
二进制码计数器的结构如图所示,由二进制转格雷码,二进制码寄存器和格雷码寄存器组成;
从面积和工作频率两个方面分析该设计;
面积:二进制转格雷码,两个寄存器;
工作频率:寄存器输入只有加法器或者二进制转格雷码,组合逻辑延迟较小,该电路可以工作在较高的频率;
3.6:空,满信号的产生
FIFO的空,满信号通过比较读,写指针可以得到,下文直接给出最常用的设计下FIFO的空,满信号如何产生。
采用二进制计数,将二进制转换为gray code进行指针比较产生full或者empty信号;
2^N deep 的fifo 指针计数信号和转换后的gray code信号位宽需要N+1位。
按照格雷码的特性:
FIFO EMPTY:读写指针完全相同时;
FIFO FULL:对比二进制码的满判定原则,最高位不同,其余位相同即认为满,但是如下图所示的情况如果格雷码也采用这种方式判断就会出现问题;
当rd_ptr和wr_ptr相等时FIFO为空,wr_ptr加一后,wr_ptr和rd_ptr的最高位不同,其余位相同,按照二进制的判满原则此时FIFO为满,但此时FIFO并没有满,所以二进制码的FIFO判断满的方法不适用于格雷码。
那格雷码该怎么判满呢?很简单,双格雷码计数器可以很好的解决这个问题,计数器的最高位用于区分写指针比读指针多回绕一次,次高位用于确定写指针的真实位置,FIFO满需要满足三个条件:
1:同步后的读指针rd_ptr_sync的MSB应该与下一个写指针的格雷码wr_gtemp的MSB应该不同;
2:写时钟域两位MSB异或应与读指针一样;
3:剩下的LSB都应该一样;
总结上述三点其实就是:读写指针的最高位和次高位相反,其余位均相同;
4:大容量异步FIFO设计
大容量同步FIFO是使用SRAM作为FIFO memory实现的,大容量异步FIFO仍可以使用该方法,不过更为常用的方法是使用“大容量同步FIFO和小容量异步FIFO”级联来实现的。原因在于异步FIFO的功能只是跨时钟域传输数据,同步FIFO更适合缓存数据,结合这两种FIFO的特点将其级联,得到大容量异步FIFO;
5:代码实现
5.1:async_fifo_top
//=================================================================================
// module : async_fifo_gray_cnt.v
// description : asynchronous fifo , pointer generate by gray counter
//=================================================================================
module async_fifo(
wclk,
wrst_n,
wr_en,
wr_data,
rclk,
rrst_n,
rd_en,
rd_data,
fifo_full,
fifo_empty
);
//=================================================================================
// parameter & localparam
//=================================================================================
//=================================================================================
// parameter
parameter FIFO_DATA_WIDTH = 16;
parameter FIFO_DEPTH = 16;
//=================================================================================
// localparam
localparam FIFO_ADDR_WIDTH = clog2(FIFO_DEPTH);
localparam FIFO_PTR_WIDTH = FIFO_ADDR_WIDTH + 1;
//=================================================================================
// I/O
//=================================================================================
input wclk;
input wrst_n;
input wr_en;
input [FIFO_DATA_WIDTH-1:0] wdata;
input rclk;
input rrst_n;
input rd_en;
output [FIFO_DATA_WIDTH-1:0] rdata;
output fifo_full;
output fifo_empty;
//=================================================================================
// signal
//=================================================================================
// ---- fifo mem ----
wire [FIFO_ADDR_WIDTH-1:0] waddr;
wire [FIFO_ADDR_WIDTH-1:0] raddr;
// ---- wr_sync_cell ----
wire [FIFO_PTR_WIDTH-1:0] rptr_g;
wire [FIFO_PTR_WIDTH-1:0] rptr_g_sync;
// ---- rd_sync_cell ----
wire [FIFO_PTR_WIDTH-1:0] wptr_g;
wire [FIFO_PTR_WIDTH-1:0] wptr_g_sync;
// ---- wr_ptr_full ----
//=================================================================================
// main body
//=================================================================================
//=================================================================================
// 1. fifo_mem
// 2. wr_sync_cell
// 3. rd_sync_cell
// 4. wptr_full
// 5. rptr_empty
//=================================================================================
// fifo_mem
fifo_mem #(
.DSIZE (FIFO_DATA_WIDTH ),
.FIFO_DEPTH (FIFO_DEPTH )
)
u_fifo_mem(
.raddr (raddr ),
.rdata (rdata ),
.wclk (wclk ),
.wr_en (wr_en ),
.waddr (waddr ),
.wdata (wdata ),
.fifo_full (fifo_full )
);
//=================================================================================
// wr_sync_cell
sync_cell #(
.DSIZE (FIFO_DATA_WIDTH)
)
u_rd_ptr_sync(
.dat_i (rptr_g ),
.clk_o (wclk ),
.rst_n_o (wrst_n ),
.dat_o (rptr_g_sync )
);
//=================================================================================
// rd_sync_cell
sync_cell #(
.DSIZE (FIFO_DATA_WIDTH)
)
u_wr_ptr_sync(
.dat_i (wptr_g ),
.clk_o (rclk ),
.rst_n_o (rrst_n ),
.dat_o (wptr_g_sync )
);
//=================================================================================
// wptr_full
wptr_full #(
.FIFO_DEPTH (FIFO_DEPTH )
)
u_wptr_full (
.wclk (wclk ),
.wrst_n (wrst_n ),
.wr_en (wr_en ),
.waddr (waddr ),
.wptr_g (wptr_g ),
.rptr_g_sync (rptr_g_sync ),
.fifo_full (fifo_full )
);
//=================================================================================
// rptr_empty
rptr_empty #(
.FIFO_DEPTH (FIFO_DEPTH )
)
u_rptr_empty(
.rclk (rclk ),
.rrst_n (rrst_n ),
.rd_en (rd_en ),
.raddr (raddr ),
.wptr_g_sync (wptr_g_sync ),
.rptr_g (rptr_g ),
.fifo_empty (fifo_empty )
);
endmodule;
5.2 async_fifo_mem
//=================================================================================
// module : fifo_mem.v
// description : register dpram
//=================================================================================
module fifo_mem (
raddr,
rdata,
wclk,
wr_en,
fifo_full,
waddr,
wdata
);
// ==========================================================================
// parameter
// ==========================================================================
parameter DSIZE = 8;
parameter FIFO_DEPTH = 16;
// ==========================================================================
// localpara
// ==========================================================================
localparam FIFO_ADDR_WIDTH = $clog2(FIFO_DEPTH);
// ==========================================================================
// I/O
// ==========================================================================
input [FIFO_ADDR_WIDTH-1:0] raddr;
output [DSIZE-1:0] rdata;
input wclk;
input wr_en;
input [FIFO_ADDR_WIDTH-1:0] waddr;
input [DSIZE-1:0] wdata;
// ==========================================================================
// signal define
// ==========================================================================
reg [DSIZE-1:0] fifo_mem[FIFO_DEPTH-1:0];
// ==========================================================================
// main body
// ==========================================================================
// ==========================================================================
// write fifo_mem
always@(posedge wclk) begin
if((wr_en == 1'b1) && (fifo_full != 1'b1)) begin
fifo_mem[waddr] <= wdata;
end
end
// ==========================================================================
// read fifo_mem
assign rdata = fifo_mem[raddr];
endmodule
5.3 sync_cell
//=================================================================================
// module : sync_cell.v
// description : 2-stage synchronizer
//=================================================================================
module sync_cell(
dat_i,
clk_o,
rst_n_o,
dat_o
);
//=================================================================================
// parameter & localparam
//=================================================================================
parameter DSIZE = 16;
//=================================================================================
// I/O
//=================================================================================
input [DSIZE-1:0] dat_i;
input clk_o;
input rst_n_o;
output [DSIZE-1:0] dat_o;
//=================================================================================
// signal
//=================================================================================
// ---- temp flip-flop ----
reg [DSIZE-1:0] dat_i_ff1;
reg [DSIZE-1:0] dat_i_ff2;
//=================================================================================
// main body
//=================================================================================
always@(posedge clk_o or negedge rst_n_o) begin
if(rst_n_o == 1'b0) begin
dat_i_ff1 <= {DSIZE{1'b0}};
dat_i_ff2 <= {DSIZE{1'b0}};
end
else begin
dat_i_ff1 <= dat_i;
dat_i_ff2 <= dat_i_ff1;
end
end
assign dat_o = dat_i_ff2;
endmodule
5.4 wptr_full
//=================================================================================
// module : wptr_full.v
// description : async_fifo write ctrl
//=================================================================================
module wptr_full(
wclk,
wrst_n,
wr_en,
waddr,
wptr_g,
rptr_g_sync,
fifo_full
);
//=================================================================================
// parameter & localparam
//=================================================================================
parameter FIFO_DEPTH = 16;
localparam FIFO_ADDR_WIDTH = clog2(FIFO_DEPTH) ;
localparam FIFO_PTR_WIDTH = FIFO_ADDR_WIDTH + 1;
//=================================================================================
// I/O
//=================================================================================
input wclk;
input wrst_n;
input wr_en;
output [FIFO_ADDR_WIDTH-1:0] waddr;
output [FIFO_PTR_WIDTH-1:0] wptr_g;
input [FIFO_PTR_WIDTH-1:0] rptr_g_sync;
output fifo_full;
// ==========================================================================
// signal define
// ==========================================================================
reg [FIFO_PTR_WIDTH-1:0] wptr_b;
reg [FIFO_PTR_WIDTH-1:0] wptr_g;
reg fifo_full;
wire [FIFO_PTR_WIDTH-1:0] wptr_bnext;
wire [FIFO_PTR_WIDTH-1:0] wptr_gnext;
//=================================================================================
// main body
//=================================================================================
// ---- binary count ----
assign wptr_bnext = wptr_b + (wr_en & (~fifo_full));
assign waddr = wptr_b[FIFO_ADDR_WIDTH-1:0];
always @(posedge wclk or negedge wrst_n) begin
if(wrst_n == 1'b0)begin
wptr_b <= {FIFO_PTR_WIDTH{1'b0}};
end
else begin
wptr_b <= wptr_bnext;
end
end
// ---- binary to gray ----
assign wptr_gnext = (wptr_bnext >> 1) ^ (wptr_bnext);
always @(posedge wclk or negedge wrst_n) begin
if(wrst_n == 1'b0) begin
wptr_g <= {FIFO_PTR_WIDTH{1'b0}};
end
else begin
wptr_g <= wptr_gnext;
end
end
// ---- fifo full -----
// three necessary condition
assign fifo_full_val = (wptr_gnext == {~rptr_g_sync[FIFO_PTR_WIDTH-1:FIFO_PTR_WIDTH-2],
rptr_g_sync[FIFO_PTR_WIDTH-3:0]});
always@(posedge wclk or negedge wrst_n) begin
if(wrst_n == 1'b0) begin
fifo_full <= 1'b0;
end
else begin
fifo_full <= fifo_full_val;
end
end
endmodule
5.5 rptr_empty
//=================================================================================
// module : rptr_empty.v
// description : async_fifo read ctrl
// data : 2022/3/2
// author : souther meditating
//=================================================================================
module rptr_empty(
rclk,
rrst_n,
rd_en,
raddr,
wptr_g_sync,
rptr_g,
fifo_empty
);
//=================================================================================
// parameter & localparam
//=================================================================================
parameter FIFO_DEPTH = 16;
localparam FIFO_ADDR_WIDTH = clog2(FIFO_DEPTH) ;
localparam FIFO_PTR_WIDTH = FIFO_ADDR_WIDTH + 1;
//=================================================================================
// I/O
//=================================================================================
input rclk;
input rrst_n;
input rd_en;
output [FIFO_ADDR_WIDTH-1:0] raddr;
input [FIFO_PTR_WIDTH-1:0] wptr_g_sync;
output [FIFO_PTR_WIDTH-1:0] rptr_g;
output fifo_empty;
// ==========================================================================
// signal define
// ==========================================================================
reg [FIFO_PTR_WIDTH-1:0] rptr_b;
reg [FIFO_PTR_WIDTH-1:0] rptr_g;
wire [FIFO_PTR_WIDTH-1:0] rptr_bnext;
wire [FIFO_PTR_WIDTH-1:0] rptr_gnext;
wire fifo_empty_val;
reg fifo_empty;
//=================================================================================
// main body
//=================================================================================
// ---- binary count ----
assign rptr_bnext = rptr_b + (rd_en & (~fifo_empty));
assign raddr = rptr_b[FIFO_ADDR_WIDTH-1:0];
always @(posedge rclk or negedge rrst_n) begin
if(rrst_n == 1'b0) begin
rptr_b <= {FIFO_PTR_WIDTH{1'b0}};
end
else begin
rptr_b <= rptr_bnext;
end
end
// ---- binary to gray ----
assign rptr_gnext = (rptr_bnext >> 1) ^ rptr_bnext;
always@(posedge rclk or negedge rrst_n) begin
if(rrst_n == 1'b0) begin
rptr_g <= {FIFO_PTR_WIDTH{1'b0}};
end
else begin
rptr_g <= rptr_gnext;
end
end
// ---- fifo empty ----
assign fifo_empty_val = (rptr_gnext == wptr_g_sync);
always @(posedge rclk or posedge rrst_n) begin
if(rrst_n == 1'b0) begin
fifo_empty <= 1'b1;
end
else begin
fifo_empty <= fifo_empty_val;
end
end
endmodule
二:非2^N深度异步FIFO设计
三:参考文章
硬件架构的艺术:异步FIFO设计_fifo级联_南风在冥想的博客-CSDN博客
数字IC前端设计进阶 - 知乎 (zhihu.com)
【《硬件架构的艺术》读书笔记】03 处理多个时钟(3) - Magnolia666 - 博客园 (cnblogs.com)