概述
1. 应用场景
异步FIFO用来在两个异步时钟域间传输数据。如图所示是两个系统,分别为“system X”和“system Y”,从“system X”向“system Y”传输数据,两个系统工作在不同时钟域。
“system X”使用“xclk”将数据写入FIFO,“system Y”使用“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相较于同步FIFO,主要有两点不同:
Fifo memory工作在两个时钟域;
Wr_ctrl和rd_ctrl工作在不同时钟域;
读、写指针经过两级同步器会存在延迟。
异步FIFO不能再使用“计数器”产生fifo空、满信号,原因在于计数器不能被两个时钟同时驱动,此时只能通过比较读、写指针来产生空、满信号,将读指针同步到写时钟域,与写指针比较产生满信号;将写指针同步到读时钟域,与读指针比较产生空信号。
读写指针的同步都需要跨时钟域传输,若使用握手信号的方式同步指针,效率很低;假如指针仍使用“二进制编码”,使用两级同步器同步指针,可能会出现亚稳态导致用于比较的指针出错。
如指针从“111”→“000”,经过两级同步器,可能的结果有8种,每一个比特都可能存在采样错误,错误的指针会导致fifo空、满信号异常。
读指针同步错误,FIFO满信号未正常拉高,继续写FIFO会覆盖原数据,导致数据传输错误;
写指针同步错误,FIFO空信号为正常拉高,继续读FIFO会读出垃圾数据,导致数据传输错误。
异步FIFO的指针同步应该避免使用二进制编码。
3.2 格雷码
实现异步FIFO指针同步的一种方式是使用格雷码,格雷码是“单位间距码”,即相邻值之间只有1bit不同。格雷码与二进制码的对应关系如图所示。格雷码有以下几个特点:
单位间距码:相邻值之间只有1bit不同
中心对称:除MSB外,其余bit中心对称
指针采用格雷码计数,使用两级同步器同步指针很少会出现亚稳态,此外取样后的值最多只有1bit出现错误,但该错误不会影响空、满信号的正确产生。考虑格雷码读、写指针采样错误的情况:
读指针采样错误:相邻格雷码只有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都转换为二进制码),最高位不同其余位相同,此时会拉高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(指针均以转换为二进制码),所有比特位均相等,此时会拉高fifo_empty信号。FIFO的“假空”虽然在实际有数据的情况下拉高了FIFO的空信号,阻止了数据的读出,但是对数据的准确性是没有影响的,直到读时钟域看到的写指针变化才会拉低FIFO的空信号。只有在FIFO为空的时候没有阻止读操作才会产生问题,即underflow问题。
3.4 格雷码与二进制码转换
读、写指针有二进制码转为格雷码后经两级同步器同步到写、读时钟域,在读、写时钟域有时还需要将格雷码转为二进制码进行指针的比较以产生FIFO的空、满信号,下面将给出格雷码与二进制码相互转换的方法。
3.4.1 二进制码转格雷码
二进制码转格雷码的公式如下所示,其中n表示位宽。
3.4.2 格雷码转二进制码
格雷码转二进制码的公式如下所示,其中n表示位宽。
3.5 读、写指针产生
产生读、写指针有两种方法,一种是直接使用格雷码计数器产生读写指针,一种是使用二进制码计数器产生读写指针,再将读写指针转换为格雷码用于跨时钟域的同步,下面将分析这两种设计方法。
3.5.1 格雷码计数器
格雷码计数器的结构如图所示,由格雷码转二进制码、二进制加法器和二进制码转格雷码组成。
从面积和工作频率两个方面分析该设计。
面积:格雷码转二进制、二进制转格雷码的组合逻辑,格雷码指针寄存器;
工作频率:寄存器输入信号的组合逻辑较为复杂,该电路工作在较高频率可能存在时序违例的情况。
3.5.2 二进制码计数器
二进制码计数器的结构如图所示,由二进制转格雷码、二进制码寄存器和格雷码寄存器组成。
从面积和工作频率两个方面分析该设计。
面积:二进制转格雷码、两个寄存器;
工作频率:寄存器输入只有加法器或二进制转格雷码,组合逻辑延迟相对较小,该电路可以工作在较高频率。
3.6 空、满信号产生
FIFO的空、满信号通过比较读、写指针可以得到,下文将讨论两种计数器设计下FIFO的空、满信号产生。
3.6.1 格雷码计数器
3.6.1.1 二进制码产生空、满信号
N位指针可以覆盖FIFO中的2^N个地址。在两个指针相等时,因为FIFO可能处于空状态也可能处于满状态,所以需要使用额外的位对这两种情况进行区分。
FIFO满:二进制码指针的最高有效位不同,其余比特位相同;
FIFO空:二进制码指针所有比特位相等。
格雷码计数器设计下,使用二进制码产生空、满信号的结构如图所示。由于读写指针的值都是以格雷码保存,而所有比较和递增是以二进制码形式进行,实现和纠错比较简单。
读指针经过两级同步后得到rd_ptr_sync,将格雷码转为二进制码rd_ptr_sync_b,在写时钟域还需要将格雷码的wr_ptr转换为二进制码的wr_ptr_b,将rd_ptr_sync_b和wr_ptr_b进行比较,得到FIFO的满信号。
写指针经过两级同步后得到wr_ptr_sync,将格雷码的wr_ptr_sync转换为二进制码的wr_ptr_sync_b,在读时钟域还需要将格雷码的rd_ptr转为rd_ptr_b,最后将wr_ptr_sync_b和rd_ptr_b进行比较,得到FIFO的空信号。
该设计共使用了4个“格雷码转二进制码”,组合逻辑路径较长,较高频率下工作可能会存在时序违例的情况。如果直接使用格雷码指针产生FIFO的空、满信号,就不需要这些转换器,下文将分析使用格雷码指针产生FIFO的空、满信号。
3.6.1.2 格雷码产生空、满信号
使用格雷码产生FIFO的空满信号要求使用两个格雷码计数器,分别是N位和N-1位。由于格雷码除最高位镜像对称的特性,可以使用N位格雷码计数器产生N-1位格雷码计数器。N-1位格雷码计数器的产生如图所示,将N位格雷码计数器的2个MSB异或得到的值作为N-1位格雷码计数器的最高位,其余N-2位与N位格雷码的N-2位完全一致。
格雷码计数器产生空信号使用N位计数器与二进制码相同,当读写指针完全相同时FIFO为空。
格雷码计数器产生满信号与二进制码不同。如图所示,当rd_ptr和wr_ptr相等时FIFO为空。wr_ptr加一后,wr_ptr和rd_ptr的最高为不同、其余位相同,按照二进制码的判断标准此时FIFO为满,但此时FIFO并没有满,所以二进制码的FIFO满判断方法不再适用于格雷码。
双格雷码计数器可以很好地解决该问题。N位计数器用于区分写指针比读指针多回绕一次,N-1位计数器用于确定写指针的真实位置。FIFO的满需要满足三个条件:
N位格雷码指针最高有效位不同;
N-1位格雷码指针最高有效位相同;(该条件等效于N位格雷码指针的次高位不同)
N位格雷码指针剩余N-2位全部相等。
3.6.2 二进制码计数器
格雷码计数器的两种实现方式都略显复杂,二进制码计数器提供了一种简单的实现方法。如图所示,二进制码计数器使用两个寄存器分别缓存二进制指针和格雷码指针。二进制码计数器产生空、满信号如图所示。
仅需将同步后的指针转为二进制码,然后与二进制码指针比较,按照二进制码FIFO空、满方法产生FIFO空、满信号。
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
// data : 2022/2/27
// author : souther meditating
//=================================================================================
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
// data : 2022/2/27
// author : souther meditating
//=================================================================================
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
// data : 2022/2/27
// author : souther meditating
//=================================================================================
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 if() 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
// data : 2022/2/27
// author : souther meditating
//=================================================================================
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 = wbin + (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
6. 参考资料
[1] 《硬件架构的艺术》
[2] Simulation and Synthesis Techniques for Asynchronous FIFO Design
[3] 同步FIFO设计
来源:https://blog.csdn.net/shiwq1127/article/details/123102904
作者:南风在冥想 版权归作者所有
最后
以上就是自信乌龟为你收集整理的异步FIFO设计的全部内容,希望文章能够帮你解决异步FIFO设计所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复