異步FIFO的verilog代碼實現(包含將滿和將空邏輯)
代碼參考來源:
Clifford E. Cummings, "Simulation and Synthesis Techniques for Asynchronous FIFO Design".
異步FIFO簡介
使用場景:在有大量的數據需要進行跨時鍾域傳輸, 並且對數據傳輸速度要求比較高的場合 。
一個異步 FIFO 一般由如下部分組成:
1. Memory, 作為數據的存儲器;
2. 寫邏輯部分,主要負責產生寫信號和地址;
3. 讀邏輯部分,主要負責產生讀信號和地址;
4. 地址比較部分,主要負責產生 FIFO 空、滿的標志。
跟普通的FIFO相比,異步FIFO實際上多了讀寫地址的跨時鍾域同步的邏輯,以及兩個時鍾域中讀寫信號的比較邏輯。
異步FIFO關鍵技術1 -- 讀寫信號跨時鍾域同步
首先,FIFO的關鍵是需要判斷讀空和寫滿,而這兩個信號的產生依賴讀地址和寫地址。在異步FIFO中,讀和寫是分在兩個時鍾域中的,在寫時鍾域,需要得到讀地址的信息進而判斷是否寫滿(寫指針是否追上讀指針),同理,在讀時鍾域,也需要寫地址的信息。我們知道跨時鍾域的單比特數據一般可以用雙寄存器法進行同步,但讀寫地址通常都是多比特的信號,此時如何進行同步呢?
當然,多比特的同步肯定可以通過增加握手信號來解決,但實際上對於數值上連續的信號,可以采用格雷碼進行多比特到單比特的傳輸。格雷碼再次不做介紹,具體原理可以參考:https://www.cnblogs.com/zhuruibi/p/8988044.html
有了格雷碼,就可以將讀寫地址同步到各自的時鍾域了。
異步FIFO關鍵技術2 -- 讀寫地址的比較
跟普通fifo一樣,異步fifo也是通過比較讀寫地址是否相同來判斷當前fifo是否空滿。區別在於,異步FIFO因為使用了格雷碼對地址進行編碼傳輸。比如讀信號通過格雷碼編碼后同步到寫時鍾域,此時需要只需要寫信號對應的格雷碼和讀信號格雷碼是否相同(有效位相同)。而在這個比較時會又一些問題。
下面這張圖詳細解釋了問題所在:

通常FIFO為了防止讀寫溢出,一般都會增加一個額外的MSB,例如上圖中,實際上深度為8的fifo只需要3位地址位表示,增加的額外的一個bit是為了區分讀寫是否經過了一次“回卷”。在二進制中表示的地址中,空滿信號實際上是先看MSB是否相同,再看低3比特是否相同。如果都相同,表示讀空,即讀地址趕上了寫地址。如果MSB不同,低3位相同,則表示寫滿。在二進制空間這個邏輯沒問題。
而到了格雷碼中,從上圖中可以發現,假設讀地址為0,寫地址為7。當寫地址再加1時,寫地址溢出,MSB變為1,實際上表示寫地址又回到了0的位置,MSB表示多了一圈。但是對應的格雷碼確實最高的兩位都發生了變化。從上圖可以發現,格雷碼中寫地址發生回卷后,也就是對應二進制的MSB發生變化后,其最高兩位恰好是不回卷時的反。因此只要將讀地址的格雷碼最高兩位進行翻轉后再跟寫地址比較即可得到正確的滿信號。
異步FIFO關鍵技術3 -- 將滿和將空的產生
將滿和將空信號實際上表示更加保守的滿和空信號。基本思路是,設定一個間隔值,當讀寫地址之間的間隔小於或等於該間隔就產生將空或將滿信號。
對於異步FIFO而言,由於同步過來的地址信號都是格雷碼表示的,我們不能直接用格雷碼去判斷上述的這個間隔,所以需要先對接受到的格雷碼進行解碼變為二進制,再和當前時鍾域下的另一個地址進行將滿和將空的生成。
對於將空的判斷和空一樣,只需要檢查寫地址與讀地址的差是否小於等於間隔。而對將滿的判斷則需要分兩種情況,一種是MSB不同,此時表示寫地址有一個回卷,直接將讀寫地址除去符號位的部分做差與間隔比較。而MSB相同時,需要在差值上再加上FIFO深度。
具體看代碼比較清晰。
FIFO邏輯圖

增加了read和write地址的同步信號,以及二進制轉格雷碼的邏輯。
示例代碼
module dual_clk_fifo
#(parameter DATESIZE = 8,
parameter ADDRSIZE = 4,
parameter ALMOST_GAP = 3
)
(
input [DATESIZE-1:0] wdata,
input winc, wclk, wrst_n,
input rinc, rclk, rrst_n,
output wire [DATESIZE-1:0] rdata,
output reg wfull,
output reg rempty,
output reg almost_full,
output reg almost_empty
);
wire [ADDRSIZE-1:0] waddr, raddr;
reg [ADDRSIZE:0] wptr, rptr;
wire rempty_val,wfull_val;
//--------------------------------
// RTL Verilog memory model
//--------------------------------
localparam DEPTH = 1<<ADDRSIZE;
reg [DATESIZE-1:0] mem [0:DEPTH-1];
assign rdata = mem[raddr];
always @(posedge wclk)
if (winc && !wfull) mem[waddr] <= wdata;
//--------------------------------
// read-domain to write-domain synchronizer
//--------------------------------
reg [ADDRSIZE:0] wq1_rptr,wq2_rptr;
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) {wq2_rptr,wq1_rptr} <= 0;
else {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
//--------------------------------
// Write-domain to read-domain synchronizer
//--------------------------------
reg [ADDRSIZE:0] rq1_wptr,rq2_wptr;
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) {rq2_wptr,rq1_wptr} <= 0;
else {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
//--------------------------------
//Read pointer & empty generation logic
//--------------------------------
reg [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] rgraynext, rbinnext;
// GRAYSTYLE2 pointer
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) {rbin, rptr} <= 0;
else {rbin, rptr} <= {rbinnext, rgraynext};
// Memory read-address pointer (okay to use binary to address memory)
assign raddr = rbin[ADDRSIZE-1:0];
assign rbinnext = rbin + (rinc & ~rempty);
assign rgraynext = (rbinnext>>1) ^ rbinnext;
// FIFO empty when the next rptr == synchronized wptr or on reset
assign rempty_val = (rgraynext == rq2_wptr);
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) rempty <= 1'b1;
else rempty <= rempty_val ;
//--------------------------------
// Write pointer & full generation logic
//--------------------------------
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wgraynext, wbinnext;
// GRAYSTYLE2 pointer
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) {wbin, wptr} <= 0;
else {wbin, wptr} <= {wbinnext, wgraynext};
// Memory write-address pointer (okay to use binary to address memory)
assign waddr = wbin[ADDRSIZE-1:0];
assign wbinnext = wbin + (winc & ~ wfull);
assign wgraynext = (wbinnext>>1) ^ wbinnext;
//------------------------------------------------------------------
// Simplified version of the three necessary full-tests:
// assign wfull_val=((wgnext[ADDRSIZE] !=wq2_rptr[ADDRSIZE] ) &&
// (wgnext[ADDRSIZE-1] !=wq2_rptr[ADDRSIZE-1]) &&
// (wgnext[ADDRSIZE-2:0]==wq2_rptr[ADDRSIZE-2:0]));
//------------------------------------------------------------------
wire [ADDRSIZE:0] full_flag;
assign full_flag = {~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]};
assign wfull_val = (wgraynext==full_flag);
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) wfull <= 1'b0;
else wfull <= wfull_val;
//--------------------------------
// almost full and empty logic
//--------------------------------
//Gray encoded read and write address decode to bin.
wire [ADDRSIZE:0]rq2_wptr_bin,wq2_rptr_bin;
wire almost_empty_val,almost_full_val;
assign rq2_wptr_bin[ADDRSIZE] = rq2_wptr[ADDRSIZE];
assign wq2_rptr_bin[ADDRSIZE] = wq2_rptr[ADDRSIZE];
genvar i;
generate
for(i=ADDRSIZE-1;i>=0;i=i-1) begin:wpgray2bin
assign rq2_wptr_bin[i] = rq2_wptr_bin[i+1]^rq2_wptr[i];
assign wq2_rptr_bin[i] = wq2_rptr_bin[i+1]^wq2_rptr[i];
end
endgenerate
//--------------------------------
// read almost empty
//--------------------------------
wire [ADDRSIZE:0] rgap_reg;
assign rgap_reg = rq2_wptr_bin - rbin;
assign almost_empty_val = (rgap_reg <= ALMOST_GAP);
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) almost_empty <= 1'b1;
else almost_empty <= almost_empty_val;
//--------------------------------
//write almost full
//--------------------------------
wire [ADDRSIZE:0] wgap_reg;
assign wgap_reg = (wbin[ADDRSIZE] ^ wq2_rptr_bin[ADDRSIZE])? wq2_rptr_bin[ADDRSIZE-1:0] - wbin[ADDRSIZE-1:0]:DEPTH + wq2_rptr_bin - wbin;
assign almost_full_val = (wgap_reg <= ALMOST_GAP);
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) almost_full <= 1'b0;
else almost_full <= almost_full_val;
endmodule
簡單的testbench:
`timescale 1 ns / 1 ps
module dual_clk_fifo_tb;
parameter DATESIZE = 8;
parameter ADDRSIZE = 3;
parameter ALMOST_GAP = 1;
reg [DATESIZE-1:0]wdata;
reg wrst_n;
reg winc;
reg rinc;
reg wclk;
reg rclk;
reg rrst_n;
wire [DATESIZE-1:0]rdata;
wire wfull;
wire rempty;
wire almost_empty;
wire almost_full;
reg [3:0]a;
reg [3:0]b;
reg [4:0]c;
reg x;
initial begin
$dumpfile("dual_clk_fifo_tb.vcd");
$dumpvars;
wdata = 0;
wrst_n = 0;
rinc = 0;
rclk = 0;
rrst_n = 0;
wclk = 0;
winc = 0;
#2;wrst_n = 0; rrst_n = 0;
#4;wrst_n = 1; rrst_n = 1;
#100;
$finish();
end
always @(posedge wclk or wrst_n)begin
if( wrst_n == 1'b0 )begin
winc = 1'b0;
end
else if( wfull )
winc = 1'b0;
else
winc = 1'b1 ;
end
// rinc generate
always @(posedge rclk or rrst_n)begin
if( rrst_n == 1'b0 )begin
rinc = 1'b0 ;
end
else if( rempty )
rinc = 1'b0;
else
rinc = 1'b1 ;
end
// wdata
always @(posedge wclk or negedge wrst_n)begin
if( wrst_n == 1'b0 )begin
wdata = 0 ;
end
else if( winc )begin
wdata = wdata + 1'b1;
end
end
always #0.5 wclk = ~wclk;
always #2 rclk = ~rclk;
dual_clk_fifo #(
.DATESIZE ( DATESIZE ),
.ADDRSIZE ( ADDRSIZE ),
.ALMOST_GAP ( ALMOST_GAP )
U_DUAL_CLK_FIFO_0(
.wdata ( wdata ),
.winc ( winc ),
.wclk ( wclk ),
.wrst_n ( wrst_n),
.rinc ( rinc ),
.rclk ( rclk ),
.rrst_n ( rrst_n),
.rdata ( rdata ),
.wfull ( wfull ),
.rempty ( rempty),
.almost_empty (almost_empty),
.almost_full (almost_full)
);
endmodule
仿真波形:

從波形上看,當almost_gap設定為2時,almost_full信號比full信號提前了一個數據時間。
由於讀地址同步到寫端需要打兩拍,而且empty信號復位也需要一拍,所以圖中經過三個rclk周期后empty才被置為0.