简析

输入:winc, rcin, wdata
输出:rdata, wfull, rempty
读写使能:rcin\wcin,读写数据:rdata\wdata,空满信号:wfull\rempty。

异步FIFO是各大公司面试笔试的重点。难点仍然是空满信号。异步FIFO的与同步FIFO的核心区别是它的读时钟和写时钟是不同步的。所以用对比读写地址的方法产生空满信号时,要进行跨时钟域处理。为了降低亚稳态可能性,异步FIFO还引入了格雷码。同时,格雷码也更方便产生空满信号。

上图是本异步FIFO的结构示意图。蓝色区域是读时钟域,黄色部分是写时钟域。异步FIFO主要包含四部分:读写地址发生器、格雷码的产生与打拍、空满信号发生器以及RAM。本题已经给出了RAM部分。

读写地址发生器

产生FIFO的自然二进制读写地址。当读使能rinc==1且FIFO非空rempty==0时,读地址在读时钟rclk下自增;当写使能winc==1且FIFO非满wfull==0时,写地址在写时钟wclk下自增。这两种地址可以直接传入RAM模块。

wire                     wenc, renc;
wire [$clog2(DEPTH)-1:0] waddr, raddr;

assign wenc = winc&!wfull;
assign renc = rinc&!rempty;

always@(posedge wclk or negedge wrstn) begin
  	if(~wrstn)
    	waddr_bin <= 0;
  	else
    	waddr_bin <= wenc? waddr_bin+1: waddr_bin;
end
    
always@(posedge rclk or negedge rrstn) begin
  	if(~rrstn)
    	raddr_bin <= 0;
  	else
    	raddr_bin <= renc? raddr_bin+1: raddr_bin;
end

格雷码打拍

为了产生空满信号,需要比较读写地址的大小。但两个地址是由不同的时钟控制的,需要先做跨时钟域处理才能比较。所以使用了打两拍。又因为FIFO的读写地址是连续变化的,采用格雷码可以有效减少相邻地址的bit变化,进一步降低打拍过程产生亚稳态的可能性。本题的参考答案在格雷码产生后在本地时钟域打了一拍才进行跨时钟域传输。但其实并不用打这一拍。最后,自然二进制地址和格雷码地址都是$clog2(DEPTH)+1bit,比waddrraddr多了1bit。该bit是用来辅助产生满信号的。
格雷码与自然二进制码的转换参考:格雷码计数器

reg  [$clog2(DEPTH):0] waddr_bin, raddr_bin;
wire [$clog2(DEPTH):0] waddr_gray, raddr_gray;
reg  [$clog2(DEPTH):0] waddr_gray1, raddr_gray1;
reg  [$clog2(DEPTH):0] waddr_gray2, raddr_gray2;
reg  [$clog2(DEPTH):0] waddr_gray3, raddr_gray3;

assign waddr_gray = waddr_bin^(waddr_bin>>1);
assign raddr_gray = raddr_bin^(raddr_bin>>1);
assign waddr      = waddr_bin[$clog2(DEPTH)-1:0];
assign raddr      = raddr_bin[$clog2(DEPTH)-1:0];

always@(posedge rclk or negedge rrstn) begin
    if(~rrstn)
      	raddr_gray1 <= 0;
    else
      	raddr_gray1 <= raddr_gray;
end

always@(posedge rclk or negedge rrstn) begin
    if(~rrstn) begin
      	waddr_gray2 <= 0;
      	waddr_gray3 <= 0;
    end
    else begin
      	waddr_gray2 <= waddr_gray1;
      	waddr_gray3 <= waddr_gray2;
    end
end

always@(posedge wclk or negedge wrstn) begin
    if(~wrstn) begin
      	raddr_gray2 <= 0;
      	raddr_gray3 <= 0;
    end
    else begin
      	raddr_gray2 <= raddr_gray1;
      	raddr_gray3 <= raddr_gray2;
    end
end

空满信号发生器

当读写地址的格雷码仅有最高的2bit不同时,FIFO满;当读写地址完全相同时,FIFO空。

assign wfull  = (waddr_gray1=={~raddr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_gray3[$clog2(DEPTH)-2:0]});
assign rempty = (raddr_gray1==waddr_gray3);

向FIFO写数据时,可能会出现写地址超过读地址一圈的情况,所以写满信号wfull的判断复杂一些。

上图中,`waddr_gray`和`raddr_gray`分别是写地址格雷码和读地址格雷码(未考虑打拍)。上面提到过,格雷码地址比`waddr`和`raddr`多出1bit用于判断是否满。当读地址和写地址在不同圈时,格雷码的最高位也会不同。再根据格雷码的对称性,此时如果次高位不同而其他位相同,说明读写地址的自然二进制码是相通的。这体现出了格雷码的另一个优势,就是方便判断是否套圈和满信号。

代码

`timescale 1ns/1ns

/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
					   parameter WIDTH = 8)(
	 input wclk
	,input wenc
	,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
	,input [WIDTH-1:0] wdata      	//数据写入
	,input rclk
	,input renc
	,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
	,output reg [WIDTH-1:0] rdata 		//数据输出
);

reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

always @(posedge wclk) begin
	if(wenc)
		RAM_MEM[waddr] <= wdata;
end 

always @(posedge rclk) begin
	if(renc)
		rdata <= RAM_MEM[raddr];
end 

endmodule  

/***************************************AFIFO*****************************************/
module asyn_fifo#(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					wclk	, 
	input 					rclk	,   
	input 					wrstn	,
	input					rrstn	,
	input 					winc	,
	input 			 		rinc	,
	input 		[WIDTH-1:0]	wdata	,

	output wire				wfull	,
	output wire				rempty	,
	output wire [WIDTH-1:0]	rdata
);
    wire [$clog2(DEPTH)-1:0] waddr, raddr;
    reg  [$clog2(DEPTH)  :0] waddr_bin, raddr_bin;
    
    wire [$clog2(DEPTH)  :0] waddr_gray, raddr_gray;
    reg  [$clog2(DEPTH)  :0] waddr_gray1, raddr_gray1;
    reg  [$clog2(DEPTH)  :0] waddr_gray2, raddr_gray2;
    reg  [$clog2(DEPTH)  :0] waddr_gray3, raddr_gray3;
    wire                     wenc, renc;
    
    always@(posedge wclk or negedge wrstn) begin
        if(~wrstn)
            waddr_bin <= 0;
        else
            waddr_bin <= wenc? waddr_bin+1: waddr_bin;
    end
    
    always@(posedge rclk or negedge rrstn) begin
        if(~rrstn)
            raddr_bin <= 0;
        else
            raddr_bin <= renc? raddr_bin+1: raddr_bin;
    end
    
    always@(posedge wclk or negedge wrstn) begin
        if(~wrstn)
            waddr_gray1 <= 0;
        else
            waddr_gray1 <= waddr_gray;
    end
    
    always@(posedge rclk or negedge rrstn) begin
        if(~rrstn)
            raddr_gray1 <= 0;
        else
            raddr_gray1 <= raddr_gray;
    end
    
    always@(posedge rclk or negedge rrstn) begin
        if(~rrstn) begin
            waddr_gray2 <= 0;
            waddr_gray3 <= 0;
        end
        else begin
            waddr_gray2 <= waddr_gray1;
            waddr_gray3 <= waddr_gray2;
        end 
    end
    
    always@(posedge wclk or negedge wrstn) begin
        if(~wrstn) begin
            raddr_gray2 <= 0;
            raddr_gray3 <= 0;
        end
        else begin
            raddr_gray2 <= raddr_gray1;
            raddr_gray3 <= raddr_gray2;
        end 
    end
    
    assign waddr_gray = waddr_bin^(waddr_bin>>1);
    assign raddr_gray = raddr_bin^(raddr_bin>>1);
    assign waddr      = waddr_bin[$clog2(DEPTH)-1:0];
    assign raddr      = raddr_bin[$clog2(DEPTH)-1:0];
    assign wenc       = winc&!wfull;
    assign renc       = rinc&!rempty;
    
    assign wfull      = (waddr_gray1=={~raddr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_gray3[$clog2(DEPTH)-2:0]});
    assign rempty     = (raddr_gray1==waddr_gray3);
    
    dual_port_RAM #(
        .DEPTH(DEPTH),
        .WIDTH(WIDTH)
    )
    myRAM(
        .wclk (wclk ),
        .wenc (wenc ),
        .waddr(waddr),
        .wdata(wdata),
        .rclk (rclk ),
        .renc (renc ),
        .raddr(raddr),
        .rdata(rdata)
    );
endmodule