Verilog實現FIFO專題5-異步FIFO設計(異步FIFO工作方式、異步FIFO介紹、異步FIFO介紹)

FIFO根據輸入輸出時鐘是否一致,分爲同步FIFO與異步FIFO。同步FIFO中,讀寫控制信號以及數據均處於同一時鐘域,滿足STA分析時一般不會出現亞穩態等不穩定情形;而對於異步FIFO,讀寫相關信號處於不同時鐘域,信號的不同步可能會導致亞穩態,導致FIFO工作異常,設計較爲複雜;在之前的記錄中,我們對同步FIFO的設計進行了分析:

Verilog實現FIFO專題(3-同步FIFO設計)

此處我們不再對同步FIFO進行介紹而直接以異步FIFO與同步FIFO的異同爲線索,逐步對異步FIFO進行分析,介紹異步FIFO相比於同步FIFO的額外處理,並進一步實現異步FIFO。

目錄

一、異步FIFO與同步FIFO工作流程比較

1、同步FIFO

2、異步FIFO

二、異步FIFO的空滿檢測

1、同步FIFO的空滿檢測

2、異步FIFO的空滿檢測

計數檢測空滿:

指針比較檢測空滿:

擴展指針比較檢測空滿:

格雷碼指針比較檢測空滿:

三、異步FIFO的同步處理

1、同步方式

2、延遲對FIFO設計的影響

結論:

FIFO滿檢測:

FIFO空檢測:

四、異步FIFO設計

1、端口設計

外部端口

內部信號

2、功能描述

3、實現代碼

4、仿真驗證

五、參考文獻


一、異步FIFO與同步FIFO工作流程比較

1、同步FIFO

同步FIFO的讀寫控制信號以及數據均處於同一時鐘域,即:FIFO在同一時鐘驅動下進行讀寫操作,讀控制信號有效且FIFO不爲空時,輸出讀指針對應地址的數據,隨後讀指針加1;寫控制信號有效且FIFO不爲滿時,將輸入數據存儲到寫指針對應地址處,隨後寫指針加1;

2、異步FIFO

異步FIFO的工作內容與同步FIFO類似:FIFO在時鐘驅動下進行讀寫操作,讀控制信號有效且FIFO不爲空時,輸出讀指針對應地址的數據,隨後讀指針加1;寫控制信號有效且FIFO不爲滿時,將輸入數據存儲到寫指針對應地址處,隨後寫指針加1;

但是異步FIFO的控制並不像同步FIFO那麼簡單,因爲異步FIFO工作在不同的時鐘域,這就帶來了一些問題:

(1)如何進行空滿檢測?還能像同步FIFO中通過計數值來判斷嗎?

(2)需要同步電路

二、異步FIFO的空滿檢測

1、同步FIFO的空滿檢測

同步FIFO的空滿檢測可以通過計數很簡單的實現:

讀寫邏輯是同一個時鐘,因此可以在每次時鐘來臨時進行判斷,如果不執行讀寫操作/同時讀寫,則計數值不變;只執行讀操作,計數值減1;只執行寫操作,計數值加1;

如果計數值爲0,則說明FIFO空,只能寫不能讀(直到寫入一次數據,計數值加1,FIFO不再爲空才能執行讀操作);

如果計數值爲FIFO深度,則說明FIFO滿,只能讀不能寫(直到讀出一次數據,計數值減1,FIFO不再爲滿才能執行寫操作);

2、異步FIFO的空滿檢測

計數檢測空滿:

異步FIFO不能採用同步FIFO這種計數方式來實現空滿檢測,因爲用兩個時鐘去控制同一個計數器的加剪很明顯是不可取的。

如果不明白爲什麼不能用兩個時鐘控制同一個計數器,可以查閱:Verilog中always@()語句雙邊沿觸發(語法與綜合的差異)

指針比較檢測空滿:

讀寫指針指向讀寫操作面向的FIFO地址空間,因此空滿檢測的另一個思路是比較讀寫指針。每次寫操作執行,寫指針加1;而每次讀操作執行,讀指針加1,因此:

FIFO空發生在:讀指針追上寫指針時;

FIFO滿發生在:寫指針追上讀指針時;

但是這種處理仍存在一個問題,就是讀寫指針相等時,難以區分FIFO是空還是滿。

擴展指針比較檢測空滿:

如上分析,直接比較讀寫指針時存在一個特例:讀寫指針相等時,難以區分FIFO是空還是滿。

因此,有人提出,將指針進行高位擴展。即指針加寬一位,當寫指針超出FIFO深度時,這額外的一位就會改變。FIFO的深度決定了指針擴展前(即除了最高位的其餘位)的寬度,而這擴展的一位與FIFO深度無關,是爲了標誌指針多轉了一圈,因此:

當讀寫指針完全相同時,FIFO空;

當讀寫指針高位不同,其餘位完全相同時,FIFO滿;

經過指針擴展,可以明確的進行空滿檢測。但是這種處理仍然不夠,因爲異步FIFO讀寫時鐘相互獨立,分屬不同時鐘域,相鄰二進制地址位變化時,不止一個地址位都要變化,這樣指針在進行同步過程中很容易出錯,比如寫指針在從0111到1000跳變時4位同時改變,這樣讀時鐘在進行寫指針同步後得到的寫指針可能是0000-1111的某個值,一共有2^4個可能的情況,而這些都是不可控制的,你並不能確定會出現哪個值,那出錯的概率非常大。

格雷碼指針比較檢測空滿:

如上分析,直接比較擴展讀寫指針時可能因多位改變導致錯誤。因此,進一步採用gray碼形式的指針,利用格雷碼每次只變化一位的特性,降低同步發生時錯誤的概率。如圖,爲一個深度爲8FIFO的格雷碼指針(綠色框中):

0-7爲真實的FIFO地址,而8-15是指針多轉一圈以後的地址(8-0,9-1...)。應注意,此時指針按照格雷碼方式進行編碼,不能再用二級制指針的比較方式來判斷空滿。比如:位置6(0101)和位置9(1101),除最高位外其餘位均相等。但是很明顯,位置9實際對應位置1處的存儲空間。

因此,格雷碼指針下的空滿檢測條件爲:

當最高位和次高位均相同,其餘位相同:FIFO空

當最高位和次高位均相反,其餘位相同:FIFO滿

因此,最終的空滿檢測方式爲:將二進制指針轉換爲格雷碼,用於另一時鐘域接收,隨後按照檢測條件進行檢測。

二進制指針轉換爲格雷碼的詳情見:Verilog實現二進制碼與格雷碼轉換  此處不再展開。

三、異步FIFO的同步處理

1、同步方式

判斷FIFO空滿狀態時,需要在讀FIFO時獲取寫時鐘域的寫指針,與讀指針比較來判斷FIFO是否爲空;需要在寫FIFO時獲取讀時鐘域的讀指針,與寫指針比較來判斷FIFO是否爲滿;

也就是說,判斷空滿狀態時牽扯到跨時鐘域問題,需要進行同步;

採用兩級寄存器打兩拍的方式進行同步,具體實現見:亞穩態專題

2、延遲對FIFO設計的影響

異步FIFO通過比較讀寫指針進行滿空判斷,但是讀寫指針屬於不同的時鐘域,所以在比較之前需要先將讀寫指針進行同步處理,將讀寫指針同步後再進行比較,判斷FIFO空滿狀態。但是因爲在同步指針時需要時間(如延遲兩拍同步),而在這個同步的時間內有可能還會寫入/讀出新的數據,因此同步後的指針一定是小於或者等於當前實際的讀/寫指針,那麼此時判斷FIFO滿空狀態時是否會出錯?是否會導致錯誤?

結論:

先說結論:異步邏輯進行同步時,不可避免需要額外的時鐘開銷,這會導致滿空趨於保守,但是保守並不等於錯誤,這麼寫會稍微有性能損失,但是不會出錯。

FIFO滿檢測:

FIFO滿檢測發生在寫時鐘域,將讀指針同步到寫時鐘域後再和寫指針比較,進行FIFO滿狀態判斷。因爲同步時間的存在,同步後的讀指針一定是小於或者等於當前真正的讀指針(同步時間內可能出現了讀操作,導致讀指針增加),所以此時判斷FIFO爲滿不一定是真滿,這樣更保守(即:寫指針=同步讀指針<=真實讀指針),這樣可以保證FIFO的特性:FIFO空之後不能繼續讀取。

FIFO空檢測:

FIFO空檢測發生在讀時鐘域,將寫指針同步到讀時鐘域後再和讀指針比較,進行FIFO空狀態判斷。因爲同步時間的存在,同步後的寫指針一定是小於或者等於當前真正的寫指針(同步時間內可能出現了寫操作,導致寫指針增加),所以此時判斷FIFO爲空不一定是真空,這樣更保守(即:讀指針=同步寫指針<=真實寫指針),這樣可以保證FIFO的特性:FIFO滿之後不能繼續寫入。

四、異步FIFO設計

1、端口設計

外部端口

1、讀時鐘信號clk_r,作爲異步FIFO的讀驅動信號

2、寫時鐘信號clk_w,作爲異步FIFO的寫驅動信號

3、異步復位信號rst_n

// 寫FIFO相關

4、數據輸入信號din[DW-1:0],作爲FIFO數據寫入端,DW數據爲位寬

5、寫使能we

// 讀FIFO相關

6、數據輸出信號dout[DW-1:0],作爲FIFO數據輸出端

7、讀使能re

// 標誌相關

8、FIFO滿標誌full,FIFO滿時不能再寫入數據

9、FIFO空標誌empty,FIFO空時不能再讀出數據

內部信號

1、讀指針wp,作爲讀地址(FIFO讀寫只能順序進行,不能外部設置,因此爲內部信號)

2、格雷碼讀指針wp_g,供寫時鐘域同步後判斷FIFO滿使用

3、寫指針rp,作爲寫地址

4、格雷碼寫指針wp_g,供讀時鐘域同步後判斷FIFO空使用

5、[DW-1:0]ram[0:Depth-1],數據存儲空間,Depth爲FIFO深度

2、功能描述

讀邏輯:

clk_r來臨,re有效,並且FIFO非空(empty=0)時,將讀指針rp對應地址處的數據讀出至dout;

// 讀操作
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        dout <= {DW{1'bz}};
    else if(!empty & re)
        dout <= ram[rp[AW-1:0]];
    else
        dout <= dout;
end

讀指針邏輯

clk_r來臨,re有效,並且FIFO非空(empty=0)時,進行一次讀操作,rp加1(即順序讀出),並進行格雷碼轉換,生成對應rp_g;

// 讀指針
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        rp <= {AW{1'b0}};
    else if(!empty & re)
        rp <= rp+1'b1;
    else
        rp <= rp;
end

寫邏輯:

clk_w來臨,we有效,並且FIFO不滿(full=0)時,將din寫入寫指針wp對應地址處;

//寫操作
always@(posedge clk_w)
begin
    if(!full & we)
        ram[wp[AW-1:0]] <= din;
    else
        ram[wp[AW-1:0]] <= ram[wp[AW-1:0]];
end

寫指針邏輯

clk_w來臨,we有效,並且FIFO不滿(full=0)時,進行一次寫操作,wp加1(即順序存儲),並進行格雷碼轉換,生成對應wp_g;

//寫指針
always@(posedge clk_w or negedge rst_n)
begin
    if(!rst_n)
        wp <= {AW{1'b0}};
    else if(!full & we)
        wp <= wp+1'b1;
    else
        wp <= wp;
end

格雷碼指針生成邏輯:

利用二進制與格雷碼的轉換關係,將二進制指針轉換爲格雷碼指針,用於另一個時鐘域的同步接收;

// 二進制指針轉換爲格雷指針
assign wp_g = (wp>>1) ^ wp;
assign rp_g = (rp>>1) ^ rp;

格雷碼指針同步邏輯:

讀時鐘域,同步寫地址,用於空邏輯判斷;寫時鐘域,同步讀地址,用於滿邏輯判斷;

// 讀時鐘域,寫地址同步
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        begin
            wp_m <= {AW{1'b0}};
            wp_s <= {AW{1'b0}};       
        end
    else
        begin
            wp_m <= wp_g;
            wp_s <= wp_m;    
        end       
end
// 寫時鐘域,讀地址同步
always@(posedge clk_w or negedge rst_n)
begin
    if(!rst_n)
        begin
            rp_m <= {AW{1'b0}};
            rp_s <= {AW{1'b0}};       
        end
    else
        begin
            rp_m <= rp_g;
            rp_s <= rp_m;    
        end       
end

空滿檢測邏輯:

根據格雷碼指針判斷邏輯,進行空滿檢測,應注意:

讀時鐘域,同步寫地址。讀格雷碼指針同步後寫格雷碼指針比較,用於空邏輯判斷;

寫時鐘域,同步讀地址,寫格雷碼指針同步後讀格雷碼指針比較,用於滿邏輯判斷;

// 空滿檢測,使用同步後的格雷指針?
assign empty = (wp_s == rp_g)?1'b1:1'b0;// 空檢測,使用同步後的寫格雷指針
assign full = ( {~wp_g[AW:AW-1] , wp_g[AW-2:0]} == {rp_s[AW:AW-1] , rp_s[AW-2:0]} )?1'b1:1'b0;  // 滿檢測,使用同步後的讀格雷指針

3、實現代碼

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: guoliang CLL
// 
// Create Date: 2020/03/24 20:51:06
// Design Name: 
// Module Name: afifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module afifo
#(parameter DW = 8,AW = 4)//默認數據寬度8,FIFO深度16
(
    input clk_r,
    input clk_w,
    input rst_n,
    input we,
    input re,
    input [DW-1:0]din,
    output reg [DW-1:0]dout,
    output empty,
    output full
    );
// internal signal
parameter Depth = 1 << AW;//depth of FIFO 
reg [DW-1:0]ram[0:Depth-1];
reg [AW:0]wp;  //point
reg [AW:0]rp;
wire [AW:0]wp_g;//Gray point
wire [AW:0]rp_g;
reg [AW:0]wp_m;//mid_point for syn
reg [AW:0]rp_m;
reg [AW:0]wp_s;//point after syn
reg [AW:0]rp_s;
// FIFO declaration
// 二進制指針轉換爲格雷指針
assign wp_g = (wp>>1) ^ wp;
assign rp_g = (rp>>1) ^ rp;
// 空滿檢測,使用同步後的格雷指針?
assign empty = (wp_s == rp_g)?1'b1:1'b0;// 空檢測,使用同步後的寫格雷指針
assign full = ( {~wp_g[AW:AW-1] , wp_g[AW-2:0]} == {rp_s[AW:AW-1] , rp_s[AW-2:0]} )?1'b1:1'b0;  // 滿檢測,使用同步後的讀格雷指針
// 讀指針
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        rp <= {AW{1'b0}};
    else if(!empty & re)
        rp <= rp+1'b1;
    else
        rp <= rp;
end
//寫指針
always@(posedge clk_w or negedge rst_n)
begin
    if(!rst_n)
        wp <= {AW{1'b0}};
    else if(!full & we)
        wp <= wp+1'b1;
    else
        wp <= wp;
end
// 讀操作
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        dout <= {DW{1'bz}};
    else if(!empty & re)
        dout <= ram[rp[AW-1:0]];
    else
        dout <= dout;
end
//寫操作
always@(posedge clk_w)
begin
    if(!full & we)
        ram[wp[AW-1:0]] <= din;
    else
        ram[wp[AW-1:0]] <= ram[wp[AW-1:0]];
end
// 讀時鐘域,寫地址同步
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        begin
            wp_m <= {AW{1'b0}};
            wp_s <= {AW{1'b0}};       
        end
    else
        begin
            wp_m <= wp_g;
            wp_s <= wp_m;    
        end       
end
// 寫時鐘域,讀地址同步
always@(posedge clk_w or negedge rst_n)
begin
    if(!rst_n)
        begin
            rp_m <= {AW{1'b0}};
            rp_s <= {AW{1'b0}};       
        end
    else
        begin
            rp_m <= rp_g;
            rp_s <= rp_m;    
        end       
end
endmodule

4、仿真驗證

測試文件如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: CLL guoliang
// 
// Create Date: 2020/03/24 21:22:45
// Design Name: 
// Module Name: afifo_tsb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module afifo_tsb(

    );
// port declaration
reg clk_r;
reg clk_w;
reg rst_n;
reg we;
reg re;
reg [7:0]din;
wire  [7:0]dout;
wire empty;
wire full;
//clk
initial
begin
    clk_r = 1'b0;
    forever #25 clk_r = ~clk_r;
end
initial
begin
    clk_w = 1'b0;
    forever #10 clk_w = ~clk_w;
end
// 
initial
begin
    rst_n = 1'b1;
    din = 1'b0;
    re = 1'b0;
    we = 1'b0;
    #50 rst_n = 1'b0;
    #50 rst_n = 1'b1;
    // only write
    we = 1'b1;
    repeat(20) #20 din = din+1'b1;
    // only read 
    we = 1'b0;
    re = 1'b1;
    repeat(20) #50; 
    // read and write
//    we = 1'b1;
//    re = 1'b1;
//    din = 1'b0;
//    repeat(20) #20 din = din+1'b1;     
end
// inst
afifo inst2(
    .clk_r(clk_r),
    .clk_w(clk_w),
    .rst_n(rst_n),
    .we(we),
    .re(re),
    .din(din),
    .dout(dout),
    .empty(empty),
    .full(full)
);
endmodule

仿真結果如下:

篇幅有限,仿真結果不再展開分析。提醒自己,應注意仿真測試是很必要的,通過功能仿真能暴露出設計上的不足、缺陷、以及實現過程中因粗心等導致的其餘問題;

因此,如何設計測試文件也具有重要意義。測試文件容易編寫,但是如何使得測試文件能全面的對設計進行檢測,高效準確的對設計進行測試,無疑是一門學問;

我只簡單記錄一下,我調試時關注的部分

1、寫邏輯

數據能否在寫時鐘驅動下,順序寫入FIFO中對應地址;FIFO滿時,是否停止寫入;

2、讀邏輯

能否在讀時鐘驅動下,順序讀出FIFO中對應數據;FIFO空時,是否停止讀出;

3、滿判斷

設計能否在寫時鐘驅動下,同步讀指針,並且在適當位置產生滿標誌;

3、空判斷

設計能否在讀時鐘驅動下,同步寫指針,並且在適當位置產生空標誌;

RTL電路如下:

五、參考文獻

Verilog實現FIFO專題(3-同步FIFO設計)

異步FIFO的設計

Verilog中always@()語句雙邊沿觸發(語法與綜合的差異)

Verilog實現二進制碼與格雷碼轉換

亞穩態專題



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章