异步FIFO的工作内容与同步FIFO类似,但是异步FIFO的控制并不像同步FIFO那么简单,因为异步FIFO工作在不同的时钟域,这将会带来一些问题,比如空满检测?是否还可以像同步FIFO通过计数器来判断?是否需要同步电路?这些问题将会在本章节一一讨论。
因为同步FIFO工作在一个时钟域,可以通过一个计数器来判断空满。
当同步FIFO写入一次数据,计数器加1.
当同步FIFO读出一次数据,计数器减1.
当同步FIFO同时进行一次读写操作,计数器的值不变。
这时就可以通过计数器的值来判断空满,当计数器的值为0,则表示同步FIFO中没有可读的数据,此时同步FIFO为空。
当计数器的值等于同步FIFO的深度时,则表示同步FIFO中已经写满了。
异步FIFO是否可以通过同步FIFO这种计数器的方式实现空满检测。假如可以,那么在异步FIFO中对计数器的描述
always@(posedge wclk or posedge rclk),这种代码综合过程中是没有实际的触发器与之对应的。
实际的触发器只有两种,一种是是对一个时钟边沿敏感—>always@(posedge clk)。另一种是除了对一个时钟边沿敏感,还有一个异步复位端—>always@(posedge clk or negedge rst_n)
可以通过比较写指针和读指针来判断空满,如下图所示,先对异步FIFO写入数据1,写两次,然后对异步FIFO读两次,把之前写进去的数据1都读出来,已经没有数据可读,所以此时异步FIFO处于空状态。注意此时写指针和读指针相等,都是指向地址为2的存储空间。
然后对异步FIFO写数据0,写6次,此时写指针重新指向地址0所对应的存储空间,此时的状态不是满状态,因为之前写入两次数据1已经被读出,此时地址0和地址1还是可以写入数据的,再对异步FIFO写数据0,写两次,此时异步FIFO中所有的存储空间都写入数据,并且没有一个数据是被读出过的,所以此时是满状态,此时也是写指针和读指针相等。
所以可以总结当读指针追上写指针时,异步FIFO处于空状态。
写指针追上读指针时,异步FIFO处于满状态。但是这种方法处理后,仍然有一个问题就是读写指针相等时,难以区分是空状态还是满状态。
针对上面指针比较空满出现的问题,有人提出,对指针最高位进行扩展,即指针加宽一位,当写指针超出FIFO深度时,这额外的一位就会发生改变。FIFO的深度决定了指针扩展前的宽度,而这扩展的一位与FIFO深度无关,是为了标志指针多转了一圈。
因此,当读写指针完全相同时,此时FIFO为空状态。
当读写指针最高位不同,其余位宽相同时,FIFO为满状态。
用扩展指针的方式分析上面的例子,如下图所示,空状态时,rptr=0010,wptr=0010,读写指针完全相同,为空状态。
满状态时,rptr=0010,wptr=1010,读写指针最高位不同,其余位宽相同,为满状态。
经过指针扩展,可以进行空满检测。但是异步FIFO读写时钟不同,而读写指针又是分属各自的时钟域,在进行空满检测的时候,是需要将读写指针进行跨时钟域处理的。
使用二进制的方式很容易出现错误,在跨时钟域的时候,相邻二进制地址变化时,不止一位发生变化。比如写指针从0111到1000跳变时,4位地址都可以发生改变,这样读时钟在进行写指针同步后得到的写指针可能是0000~1111中任意一个值,这些都是不可控的,出错的概率非常大。
直接扩展读写指针可能因为多位改变导致错误。因此采用格雷码的方式,利用格雷码每次只变化一位的特征,降低同步发生错误的概率。如下图所示,其中0~7为真实的FIFO地址,而8-15是指针多转一圈以后的地址,实际对应还是0-7对应的存储空间。此时应该注意,此时是按照格雷码方式编码,不能再用二进制的方式来判断空满。比如说位置6(0101)和位置9(1101),除最高位不同,其余位相同,应该是满状态,但是位置6(0101)对应的满状态应是14(1001)。
因此格雷码的检测标准为:
当最高位和次高位均相同,其余为相同,FIFO空
当最高位和次高位均相反,其余为相同,FIFO满
所以异步FIFO最终的空满检测方式为:将二进制指针转换为格雷码,用于另一时钟域接收,随后按照检测条件进行检测。
异步FIFO相比较同步FIFO外部端口引脚类似。
wclk为写时钟,rclk为读时钟。
wrst_n为写复位信号,rrst_n为读复位信号。
winc为写使能信号,rinc为读使能信号。
wdata[7:0]为输入数据。
rdata[7:0]为输出数据。
wfull为写满信号。
rempty为读空信号。
异步FIFO的内部电路主要分为5个部分,会在下面部分一一详细介绍。在这里主要介绍一下内部的信号。
wptr为写指针,写指针的位数比写地址多一位。
rptr为读指针,读指针的位数比读地址多一位。
waddr为写地址,为具体把数据写入FIFO_Memory的地址。
raddr为读地址,为具体读出FIFO_Memory中数据的地址。
wq2_rptr为将读指针同步到写时钟域的信号。
rq2_wptr为将写指针同步到读时钟域的信号。
第一部分就是异步FIFO的写指针、满信号和写地址产生的电路设计,详细电路图如下所示。
代码如下:
module wptr_wfull #(
parameter ADDRSIZE=4)
(
input winc,
input wclk,
input wrst_n,
input [ADDRSIZE:0] wq2_rptr,
output reg wfull,
output reg [ADDRSIZE:0] wptr,
output [ADDRSIZE-1:0] waddr
);
wire full_value;
wire [ADDRSIZE:0] wbinnext , wgraynext;
reg [ADDRSIZE:0] wbin;
//写地址产生
assign wbinnext=wbin+(winc&!wfull);
always@(posedge wclk or negedge wrst_n)
begin
if(wrst_n==1’b0)
wbin<=0;
else
wbin<=wbinnext;
end
assign waddr=wbin[ADDRSIZE-1:0];
//写指针产生
assign wgraynext=wbinnext^(wbinnext>>1);
always @(posedge wclk or negedge wrst_n)
begin
if(wrst_n==1’b0)
wptr <=0;
else
wptr<=wgraynext;
end
//满信号产生
assign full_value=wgraynext == ({~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]});
always@(posedge wclk or negedge wrst_n)
begin
if(wrst_n==1’b0)
wfull<=1’b0;
else
wfull<=full_value;
end
endmodule
第二部分就是异步FIFO的读指针、空信号和读地址产生的电路设计,电路图如第一部分类似,因此不再具体介绍。
代码如下:
module rptr_rempty #(
parameter ADDRSIZE=4)
(
input rinc,
input rclk,
input rrst_n,
input [ADDRSIZE:0] rq2_wptr,
output reg rempty,
output reg [ADDRSIZE:0] rptr,
output [ADDRSIZE-1:0] raddr
);
wire empty_value;
wire [ADDRSIZE:0] rbinnext , rgraynext;
reg [ADDRSIZE:0] rbin;
//读地址产生
assign rbinnext=rbin+(rinc&!rempty);
always@(posedge rclk or negedge rrst_n)
begin
if(rrst_n ==1’b0)
rbin <=0;
else
rbin <=rbinnext;
end
assign raddr=rbin[ADDRSIZE-1:0];
//读指针产生
assign rgraynext=rbinnext^(rbinnext>>1);
always@(posedge rclk or negedge rrst_n)
begin
if(rrst_n ==1’b0)
rptr <=0;
else
rptr<=rgraynext;
end
//空信号产生
assign empty_value=rgraynext == rq2_wptr;
always@(posedge rclk or negedge rrst_n)
begin
if(rrst_n==1’b0)
rempty<=1’b0;
else
rempty<=empty_value;
end
endmodule
第三部分是读指针的同步,将读指针打两拍同步到写时钟域去,具体电路如下图所示。
代码如下:
module sync_r2w #(
parameter ADDRSIZE=4)
(
input wclk,
input 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==1’b0)
{wq2_rptr, wq1_rptr}<=2’b0;
else
{wq2_rptr, wq1_rptr}<= {wq1_rptr, rptr} ;
end
endmodule
第四部分是写指针的同步,将写指针打两拍同步到读时钟域去,电路图如第三部分类似,不再具体介绍。
代码如下:
module sync_w2r #(
parameter ADDRSIZE=4)
(
input rclk,
input rrst_n,
input [ADDRSIZE:0] wptr,
output reg [ADDRSIZE:0] rq2_wptr
);
reg [ADDRSIZE:0] rq1_wptr;
always@(posedge rclk or negedge rrst_n)
begin
if(rrst_n==1’b0)
{rq2_wptr, rq1_wptr}<=2’b0;
else
{rq2_wptr, rq1_wptr}<= {rq1_wptr, wptr};
end
endmodule
第五部分是数据的读取,具体电路如2.2异步电路中标记为5的部分所示。
代码如下:
module fifomem #(
parameter ADDRSIZE=4,
parameter DATASIZE=8)
(
input winc,
input wclk,
input wfull,
input [ADDRSIZE-1:0] waddr , raddr,
input [DATASIZE-1:0] wdata ,
output [DATASIZE-1:0] rdata
);
localparam DEPTH=1<
将上述5个部分整合在一起就形成一个完整的异步FIFO。
module afifo #(
parameter ADDRSIZE=4,
parameter DATASIZE=8)
(
input wclk,
input rclk,
input wrst_n,
input rrst_n,
input winc,
input rinc,
input [DATASIZE-1:0] wdata,
output [DATASIZE-1:0] rdata,
output rempty,
output wfull
);
wire [ ADDRSIZE:0] wq2_rptr;
wire [ ADDRSIZE:0] wptr;
wire [ ADDRSIZE-1:0] waddr;
wire [ ADDRSIZE:0] rq2_wptr;
wire [ ADDRSIZE:0] rptr;
wire [ ADDRSIZE-1:0] raddr;
wptr_wfull u1(
.winc(winc),
.wclk(wclk),
.wrst_n(wrst_n),
.wq2_rptr(wq2_rptr),
.wfull(wfull),
.wptr(wptr),
.waddr(waddr)
);
rptr_rempty u2(
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n),
.rq2_wptr(rq2_wptr),
.rempty(rempty),
.rptr(rptr),
.raddr(raddr)
);
sync_r2w u3(
.wclk(wclk),
.wrst_n(wrst_n),
.rptr(rptr),
.wq2_rptr(wq2_rptr)
);
sync_w2r u4(
.rclk(rclk),
.rrst_n(rrst_n),
.wptr(wptr),
.rq2_wptr(rq2_wptr)
);
fifomem u5(
.winc(winc),
.wclk(wclk),
.wfull(wfull),
.waddr(waddr),
.raddr(raddr),
.wdata(wdata),
.rdata(rdata)
);
endmodule
module afifo_tb();
reg wclk;
reg rclk;
reg wrst_n;
reg rrst_n;
reg winc;
reg rinc;
reg [7:0] wdata;
wire [7:0] rdata;
wire rempty;
wire wfull;
afifo u6(
.wclk(wclk),
.rclk(rclk),
.wrst_n(wrst_n),
.rrst_n(rrst_n),
.winc(winc),
.rinc(rinc),
.wdata(wdata),
.rdata(rdata),
.rempty(rempty),
.wfull(wfull)
);
initial begin //设置写时钟,写周期是20ns,50Mhz
wclk=0;
forever #10 wclk=~wclk;
end
initial begin //设置读时钟,读周期是10ns,100Mhz
rclk=0;
forever #5 rclk=~rclk;
end
initial begin
wrst_n=1'b0; //写复位
rrst_n=1'b0; //读复位
winc =1'b0; //写无效
rinc =1'b0; //读无效
wdata=0; //初始写数据为0
#28 wrst_n=1'b1; //松开写复位
rrst_n=1'b1; //松开读复位
winc =1'b1; //写有效
wdata=1; //输入数据为1
@(posedge wclk);//写入数据
repeat(7) //接着写入2,3,4,5,6,7,8这些数据
begin
#18;
wdata=wdata+1'b1;
@(posedge wclk);
end
#18 wdata=wdata+1'b1; //此时异步FIFO已经写满了,在往同步FIFO中写数据8
//8这个数据不会被写进
@(posedge rclk);
#8 rinc=1’b1; //读使能,写无效
winc=1'b0;
@(posedge rclk); //第一个读出的数为1
repeat(7) //读取剩余的数
begin
@(posedge rclk);
end
#2;
rinc=1‘b0; //结束读操作
end
endmodule