异步FIFO(Asynchronous First In First Out)
1. 前言
由于EDA工具的广泛支持,采用同步电路设计的方法是目前Soc设计的基本要求,但在一个功能复杂的Soc系统中,难免会有异步信号与同步电路交互的问题。特别是在由许多核组成的Soc中,通常采用全局异步局部同步(GALS)的设计。而跨时钟域传输数据时很容易造成亚稳态的现象,本文主要讲解Asyn_FIFO来解决跨时钟域传输多bit信号的问题。
2. Asyn_FIFO的结构与原理
Asyn_FIFO: 写数据wdata在写时钟域下wclk写入到FIFO中的存储器(双端口RAM)中,读数据rdata在读时钟域rclk下从FIFO中的存储器读出wdata写入的值。综合下,Asynchronous FIFO可以安全地把数据从一个时钟域传输到另外一个时钟域。
2.1 Asynchronous FIFO的结构
- 总体架构图如下
- 可以看出由5个模块组成:FIFO Memory, wptr_full, rptr_empty, sync_r2w, sync_w_r
- FIFO Memory: 这部分就是存储器。
- wptr_full: 在写信号有效的情况下, 生成二进制的写地址指针waddr、Gray码的写地址指针wptr和判满信号wfull。
- rptr_empty: 在读信号有效的情况下, 生成二进制的读地址指针raddr、Gray码的读地址指针rptr和判空信号rempty。
- sync_r2w: rclk domain 到 wclk domain 的同步器,Gray码的读地址指针rptr在wclk domain下的寄存器寄存两拍然后传输给wptr_full模块。
- sync_w2r: wclk domain 到 rclk domain 的同步器,Gray码的写地址指针wptr在rclk domain下的寄存器寄存两拍然后传输给rptr_empty模块。
2.2 Asynchronous FIFO的原理
1) FIFO Memory
- Asyn_FIFO的存储器部分采用RAM,而RAM分类有单端口和双端口,同步和异步。因为Syn_FIFO是需要在两个时钟域下工作的模块(分开读分开写)。所以采用双端口的RAM。而同步RAM和异步RAM的区别是在读端口引入一级DFF在时钟上升沿读取还是raddr改变时直接读取,也即同步读、异步读,理想情况下,同步读数据比异步读数据晚一个读时钟周期。通常这两种都可以,看具体要求再设计。
2) wptr_full and rptr_empty
- 这两个模块实际上是对称的,设计思路都是一样的。具体思路如下:
- 写时钟域:
- 根据写使能信号winc和写满标志位wfull来产生二进制写指针wbin;
- 根据二进制写指针wbin产生双端口RAM的写地址waddr;
- 把二进制写指针wbin转换成Gray码写指针wptr;
- 从同步器sync_r2w得到同步到写时钟域下的Gray码读指针;
- 判断 wptrnext == wq2_rptr 得写满标志位wfull;
- 读时钟域:
- 根据读使能信号rinc和读空标志rempty来产生二进制读指针rbin;
- 根据二进制读指针rbin产生双端口RAM得读地址raddr;
- 把二进制读指针raddr转换成Gray码写指针rptr;
- 从同步器sync_w2r得到同步到读时钟域下得Gray码写指针;
- 判断 rptrnext == rq2_wptr 得读空标志位rempty;
3) sync_w2r and sync_r2w
- 两级同步器
###2.3 信号描述
1) FIFO_Memory
2) wptr_full
Name | Source | To | Description |
---|---|---|---|
winc | module of wclk | fifo_memory and wptr_full | 写使能信号 |
wclk | clock source | 写时钟域下的module | 上升沿触发 |
wrst_n | reset controller | 写时钟域下的module | 复位信号,低电平有效 |
wq2_rptr[ADDRSIZE:0] | rptr_rempty module | wptr_full module | 经由两级同步器synr_r2w同步到写时钟域的读Gray码指针 |
waddr[ADDRSIZE-1:0] | wptr_full module | fifo_memory | 二进制写地址 |
wptr[ADDRSIZE:0] | wptr_full module | syn_w2r module | Gary码读指针,输出给syn_w2r |
wfull | wptr_full module | wptr_full module and module of wclk | 写满标志,wptr_full模块自身又作为输入,同时传递给在wclk下的的数据发送端 |
3) rptr_empty
Name | Source | To | Description |
---|---|---|---|
rinc | module of rclk | fifo_memory and rptr_empty | 写使能信号 |
rclk | clk source | 读时钟域下的module | 上升沿触发 |
rrst_n | reset controller | 读时钟域下的module | 复位信号,低电平有效 |
rq2_wptr[ADDRSIZE:0] | wptr_full module | rptr_empty module | 经由两级同步器synr_w2r同步到读时钟域的写Gray码指针 |
raddr[ADDRSIZE-1:0] | rptr_full module | fifo_memory module | 二进制读指针 |
rptr[ADDRSIZE:0] | rptr_empty module | syn_r2w | Gray码读指针 |
rempty | rptr_empty | rptr_empty module and module of rclk | 读空标志,rptr_empty模块自身又作为输入,同时传递给在rclk下的数据发送端 |
3 RTL code
1) FIFO Top-Level module
module asyn_fifo #(
parameter DATASIZE = 8,
parameter ADDRSIZE = 4
) (
input winc, wclk, wrst_n,
input rinc, rclk, rrst_n,
input [DATASIZE-1:0] wdata,
output [DATASIZE-1:0] rdata,
output wfull,
output rempty
);
wire [ADDRSIZE-1:0] waddr, raddr;
wire [ADDRSIZE:0] wptr, rptr, rq2_wptr, wq2_rptr;
fifomem #(
.DATASIZE ( 8 ),
.ADDRSIZE ( 4 ))
u_fifomem(
//ports
.wclken ( winc ),
.wfull ( wfull ),
.wclk ( wclk ),
.waddr ( waddr ),
.raddr ( raddr ),
.wdata ( wdata ),
.rdata ( rdata )
);
rptr_empty #(
.ADDRSIZE ( 4 ))
u_rptr_empty(
//ports
.rinc ( rinc ),
.rclk ( rclk ),
.rrst_n ( rrst_n ),
.rq2_wptr ( rq2_wptr ),
.rptr ( rptr ),
.raddr ( raddr ),
.rempty ( rempty )
);
wptr_full #(
.ADDRSIZE ( 4 ))
u_wptr_full(
//ports
.winc ( winc ),
.wclk ( wclk ),
.wrst_n ( wrst_n ),
.wq2_rptr ( wq2_rptr ),
.waddr ( waddr ),
.wptr ( wptr ),
.wfull ( wfull )
);
syn_r2w #(
.ADDRSIZE ( 4 ))
u_syn_r2w(
//ports
.wclk ( wclk ),
.wrst_n ( wrst_n ),
.rptr ( rptr ),
.wq2_rptr ( wq2_rptr )
);
syn_w2r #(
.ADDRSIZE ( 4 ))
u_syn_w2r(
//ports
.rclk ( rclk ),
.rrst_n ( rrst_n ),
.wptr ( wptr ),
.rq2_wptr ( rq2_wptr )
);
endmodule
2) FIFO Memory
module fifomem #(
parameter DATASIZE = 8,
parameter ADDRSIZE = 4
) (
input wclken, wfull, wclk,
input [ADDRSIZE-1:0] waddr,raddr,
input [DATASIZE-1:0] wdata,
output [DATASIZE-1:0] rdata
);
localparam DEPTH = 1<<ADDRSIZE;
reg [DATASIZE-1:0] mem [0:DEPTH-1];
assign rdata = mem[raddr];
always @(posedge wclk) begin
if(wclken && !wfull)
mem[waddr] <= wdata;
end
endmodule
3) wptr_full
module wptr_full #(
parameter ADDRSIZE = 4
)(
input winc, wclk, wrst_n,
input [ADDRSIZE :0] wq2_rptr,
output [ADDRSIZE-1:0] waddr,
output reg [ADDRSIZE :0] wptr,
output reg wfull
);
// internal port
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wbinnext, wptrnext;
wire wfull_val;
//----------------------------------------------
// Memory write-address pointer and Gray pointer
//----------------------------------------------
always @(posedge wclk or negedge wrst_n)begin
if(!wrst_n)
{wbin, wptr} <= 0;
else
{wbin, wptr} <= {wbinnext, wptrnext};
end
assign waddr = wbin[ADDRSIZE-1:0];
assign wbinnext = wbin + (winc && !wfull);
assign wptrnext = (wbinnext>>1) ^ wbinnext;
//--------------------------------------------------
// FIFO full when the next wptr == synchronized rptr
//--------------------------------------------------
always @(posedge wclk or negedge wrst_n)begin
if(!wrst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;
end
assign wfull_val = (wptrnext == {~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]});
endmodule //wptr_full
4) rptr_empty
module rptr_empty #(
parameter ADDRSIZE = 4
)(
input rinc, rclk, rrst_n,
input [ADDRSIZE :0] rq2_wptr,
output [ADDRSIZE-1:0] raddr,
output reg [ADDRSIZE :0] rptr,
output reg rempty
);
// internal signals
wire [ADDRSIZE:0] rbin, rbinnext, rptrnext;
wire rempty_val;
//---------------------------------------------
// Memory read_adderss pointer and Gray pointer
//----------------------------------------------
always @(posedge rclk or negedge rrst_n) begin
if(!rrst_n)
{rbin, rptr} <= 0;
else
{rbin, rptr} <= {rbinnext, rptrnext};
end
assign raddr = rbin[ADDRSIZE-1:0];
assign rbinnext = rbin + (rbin && !rempty);
assign rptrnext = (rbinnext>>1) ^ rbinnext;
//---------------------------------------------------------------
// FIFO empty when the next rptr == synchronized wptr or on reset
//---------------------------------------------------------------
always @(posedge rclk or negedge rrst_n) begin
if(!rrst_n)
rempty <= 1'b1;
else
rempty <= rempty_val;
end
assign rempty_val = (rptrnext == rq2_wptr);
endmodule //rptr_empty
5) sync_r2w
module sync_r2w #(
parameter ADDRSIZE = 4
)(
input wclk, wrst_n,
input [ADDRSIZE:0] rptr,
output reg [ADDRSIZE:0] wq2_rptr
);
reg [ADDRSIZE:0] wq1_rptr;
always @(posedge wclk or negedge wrst_n) begin
if(!wrst_n)
{wq2_rptr, wq1_rptr} <= 0;
else
{wq2_rptr, wq1_rptr} <= {wq1_rptr, rptr};
end
endmodule //sync_r2w
6) sync_w2r
moduel sync_w2r #(
parameter ADDRSIZE = 4
)(
input rclk, rrst_n,
intput [ADDRSIZE:0] wptr,
output [ADDRSIZE:0] rq2_wptr
);
reg rq1_wptr;
always @(posedge rclk or negedge rrst_n) begin
if(!rrst_n)
{rq2_wptr, rq1_wptr} <= 0;
else
{rq2_wptr, rq1_wptr} <= {rq1_wptr, wptr};
end
endmodule //sync_w2r
4. Discussions
4.1 Different clock speeds
4.2 Pessimistic full & empty
【参考文献】
[1] 郭炜, 魏继增, 郭筝. Soc设计方法与实现(第3版). 电子工业出版社. 2017年
[2] Clifford E. Cummings, “Synthesis and Scripting Techniques for Designing Multi-Asynchronous Clock Designs,” SNUG 2001(Synopsys Users Group Conference, San Jose, CA, 2001) User Papers, March 2001, Section MC1, 3rdpaper. Also available at www.sunburst-design.com/papers