异步FIFO设计
0、异步fifo基本概念
异步fifo简单来说就是实现多bit数据在两个时钟域之间进行传输的方式;
是跨时钟域的方法之一。
1、异步fifo设计的主要两个问题
- 空满的判断
- 读写指针的表示
先说明下空满的判断:
指针总是指向要写或者要读的数据,如果两个指针相等,则读空或者写满,那怎么去判断空满呢?
答:这里就需要添加MSB bit位,来指示说明空满的情况,此时指针位数要比地址的位数多1位。读写指针的地址位都相等时,就看MSB位;如果读写指针的MSB bit不相同,说明写指针已经追上读指针,表示写满;如果MSB位相同,说明读指针追上写指针,表示读空。
补充虚空和虚满概念:
虚空就是写指针同步到读时钟域时需要几个时钟周期,在这期间写时钟域继续写,此时恰好在读时钟域判断为空,那就是虚空;
同理,虚满就是读指针同步到写时钟需要几T时钟,在这期间读继续读,此时恰好在写时钟域判断满,那就是虚满。
2、二进制计数器编码的问题
其实已经有很多资料已经说明了为何选择格雷码计数器而不是二进制计数器:
二进制编码的主要问题是计数器值变化是多bit的,这会采到计数值中间变化值的可能,导致计数错误;所以推荐使用格雷码计数器的方式,因为格雷码每次变化只有1bit。
3、 格雷码计数器
格雷码计数器有两个设计思路
方法一:双n-bit格雷码计数器
十进制数 | 二进制 | 格雷码 |
---|---|---|
0 | 0000 | 0000 |
1 | 0001 | 0001 |
2 | 0010 | 0011 |
3 | 0011 | 0010 |
4 | 0100 | 0110 |
5 | 0101 | 0111 |
6 | 0110 | 0101 |
7 | 0111 | 0100 |
8 | 1000 | 1100 |
9 | 1001 | 1101 |
10 | 1010 | 1111 |
11 | 1011 | 1110 |
12 | 1100 | 1010 |
13 | 1101 | 1011 |
14 | 1110 | 1011 |
15 | 1111 | 1000 |
上图分析:4bit格雷码计数器,总共16个值(注意fifo深度是8),可以理解为计数到7时,最高位翻转为1,然而低三位是镜像0-7的低三位;如果想要MSB翻转后低三位重复0-7的顺序,可以通过实现3bit格雷码计数器来实现;这里就存在1个问题,从7-8和15-0,是两bit翻转。这里就可以理解3bit的格雷码计数器,当计数到8时,就翻转最高位,然后从头开始计数。(思考:这都是2^n个值,如果是奇数个值,怎么转变呢?)
tip:上图格雷码计数器使用了n+1个寄存器
方法二:常用的格雷码计数器方法
适合高速设计模块
tip:上述使用了2n-1个寄存器
上图的分析:
- mem的深度为8,所以地址为3,扩展1bit来指示空满;
- 将这4bit的二进制转换成格雷码,作为判断空满的指针;
- 4bit的二进制的低三位作为索引memory的地址;
注意不同于第一种格雷码计数方式,这里不需要将8-15的格雷码转成n-1bit的格雷码,而是直接使用这n-bit格雷码判断空满。这种直接使用也存在一个问题,如格雷码8,低三位表示的是7,与格雷码7正好是相反的,按照空满判断条件是满的,但实际上是写1个,这样就会出现空满判断错误。如何解决呢?
这就引入了空满判断条件:
1、写指针和同步到写时钟域的读指针的MSB不相等;
2、它们的次高位也不相等;
3、剩余位必须相等。
有个问题:如果地址累加的时候,或者说bin 累加的时候,格雷码计数器出现问题,会影响判空判满吗?
答:不会。这个格雷码翻转只有一位变化,意味着只有两种可能的结果,即要么正常翻转,要么不变,(因为可以理解的,格雷码只有1位翻转,二进制是正常翻转的情况下,通过异或,如果因为组合逻辑的延迟,导致某些bit位没变,结果就是没变的bit位还是前一个格雷码bit位,变的话,那就是需要变的bit位,所以从结果来看,要么格雷码没翻转,要么就正常翻转)但不影响判空判满的条件,因为如不变,但确实写进数据,在同步到读时钟域的时候,就会出现,如果判空,但实际上不空;同理,指针不变,意味可能判满,但实际上没有满,这都不影响FIFO的功能。
通过上面的分析,剩下的模块就是同步模块的设计和RAM的设计,从而整个异步FIFO的模块的设计图如下:
4、设计代码
代码是描述电路的,所以一定要有电路设计图,弄清楚输入输出有哪些,然后就是用语言去描述这个电路的过程。
module async_fifo
#(parameter DATA_WIDTH = 8,
parameter PTR_WIDTH = 4,
parameter ADDR_WIDTH = PTR_WIDTH-1,
parameter DEPTH = 2 ** ADDR_WIDTH)
(
input wclk,
input rclk,
input wrst_n,
input rrst_n,
input winc,
input rinc,
input [DATA_WIDTH-1:0] wdata,
output [DATA_WIDTH-1:0] rdata,
output wfull,
output rempty);
logic [PTR_WIDTH-1:0] wptr;
logic [PTR_WIDTH-1:0] rptr;
logic [ADDR_WIDTH-1:0] waddr;
logic [ADDR_WIDTH-1:0] raddr;
logic [DATA_WIDTH-1:0] mem [DEPTH-1:0];
//sync wptr
logic [PTR_WIDTH-1:0] wptr_sync_s0,wptr_sync_s1;
always_ff @(posedge rclk or negedge rrst_n)begin
if(!rrst_n)begin
{wptr_sync_s1,wptr_sync_s0} <= 'h0;
end else begin
{wptr_sync_s1,wptr_sync_s0} <= {wptr_sync_s0,wptr};
end
end
//sync_rptr
logic [PTR_WIDTH-1:0] rptr_sync_s0,rptr_sync_s1;
always_ff @(posedge wclk or negedge wrst_n)begin
if(!wrst_n)begin
{rptr_sync_s1,rptr_sync_s0} <= 'h0;
end else begin
{rptr_sync_s1,rptr_sync_s0} <= {rptr_sync_s0,rptr};
end
end
//wfull
logic [PTR_WIDTH-1:0] wbin,wbnext;
logic [PTR_WIDTH-1:0] wgnext;
logic wfull_tmp;
always_ff @(posedge wclk or negedge wrst_n)begin
if(!wrst_n)begin
{wbin,wptr} <= 'h0;
end else begin
{wbin,wptr} <= {wbnext,wgnext};
end
end
assign wbnext = wbin + (winc && !wfull);
assign wgnext = (wbnext >> 1) ^ wbnext;
assign wfull_tmp = (wgnext == ({~rptr_sync_s1[PTR_WIDTH-1:PTR_WIDTH-2],rptr_sync_s1[PTR_WIDTH-3:0]}));
always_ff @(posedge wclk or negedge wrst_n)begin
if(!wrst_n)begin
wfull <= 'h0;
end else
wfull <= wfull_tmp;
end
//rempty
logic [PTR_WIDTH-1:0] rbin,rbnext;
logic [PTR_WIDTH-1:0] rgnext;
logic rempty_tmp;
always_ff @(posedge rclk or negedge rrst_n)begin
if(!rrst_n)begin
{rbin,rptr} <= 'h0;
end else begin
{rbin,rptr} <= {rbnext,rgnext};
end
end
assign rbnext = rbin + (rinc && !rempty);
assign rgnext = (rbnext >> 1) ^ rbnext;
assign rempty_tmp = (rgnext == wptr_sync_s1);
always_ff @(posedge rclk or negedge rrst_n)begin
if(!rst_n)begin
rempty <= 'h1;
end else
rempty <= rempty_tmp;
end
//RAM
assign waddr = wbin[PTR_WIDTH-2:0];
assign raddr = wbin[PTR_WIDTH-2:0];
always_ff @(posedge wclk)begin
if(winc && !wfull)
mem[waddr] <= wdata;
end
assign rdata = mem[raddr];
endmoduel // async_fifo
5、波形图分析
上述波形是写慢读快的情形。
上图是写快读慢的波形图分析。
其实可以发现,整个FIFO设计中,读数据是立即读出先写进去的数据的,这样的设计称为露头fifo,这在一些需要仲裁逻辑电路中是很有帮助的。
参考资料
[1] Simulation and Synthesis Techniques for Asynchronous FIFO Design.SNUG 2002 (San Jose)