備戰秋招[五]-異步FIFO


歡迎關注個人公眾號摸魚范式

異步FIFO

參考資料為Clifford E. Cummings的論文。

1.0 摘要

異步FIFO是一種FIFO設計,數據從一個時鍾域進入到FIFO,在另一個時鍾域讀取數據,並且兩個時鍾域是異步。

異步FIFO的用於將數據從一個時鍾域安全准確地傳遞到另一個時鍾域。

設計異步FIFO的方法有很多,其中也有很多錯誤的設計方法。這些不正確的設計方法,在90%的時間內都能夠正確運行,而大多數正確的設計方法能夠保證在99%的時間中能夠正確運行。不幸的是,在99%以上的時間中都能正常工作的FIFO也具有設計缺陷,這些缺陷通常最難檢測和調試(如果你很幸運地在產品出廠前就注意到了此錯誤),或者診斷和召回的代價很高(直到產品由不滿意的客戶掌握之前才發現)。

本文討論了一種FIFO設計風格,以及進行異步FIFO設計時必須考慮的重要細節。

下文將“異步FIFO”簡稱為“ FIFO”

2.0 傳遞多個異步信號

將多個信號從一個時鍾域同步到另一個時鍾域,並確保所有的信號都同步到新時鍾域中的同一時鍾周期這是一個關鍵問題。 FIFO在設計中用於將多位數據從一個時鍾域安全地傳遞到另一個時鍾域。 通過一個時鍾域中的控制信號將數據存入FIFO緩存中,並通過來自第二時鍾域的控制信號將數據從同一FIFO緩存的另一個端口中讀取並刪除。 看上去,設計FIFO似乎很容易。

FIFO設計的難點在於生成FIFO指針以及FIFO上的滿和空狀態確定。

2.1 同步FIFO指針

對於同步FIFO設計(在同一時鍾域中執行FIFO讀寫操作),一種實現方式是對FIFO的寫入和讀取次數進行計數,遞增(以FIFO寫入但不讀取) ),遞減(在FIFO讀取但無寫入時)或保持(無寫入和讀或同時進行寫入和讀取操作)FIFO計數值。 當FIFO計數器達到預定的最大值時,FIFO為滿,而當FIFO計數器為零時,FIFO為空。

不幸的是,對於異步FIFO設計,不能使用增減FIFO填充計數器,因為將需要兩個不同的異步時鍾來控制計數器。 異步FIFO設計的滿狀態和空狀態,必須通過比較寫指針和讀指針確定。

2.2 異步FIFO指針

為了理解FIFO設計,需要了解FIFO指針是如何工作的。寫指針總是指向下一個要寫的字;因此,在復位時,兩個指針都設置為零,這也恰好是下一個要寫入的FIFO字位置。在FIFO寫入操作中,將數據寫入指針所指向的位置,然后將寫指針遞增以指向要寫入的下一個位置。同樣,讀取指針始終指向要讀取的當前FIFO字。再次復位時,兩個指針均復位為零,FIFO為空,而讀指針指向無效數據(因為FIFO為空且聲明了空標志)。一旦第一個數據被寫入FIFO,寫入指針就會遞增,清空標志,並且在尋址第一個FIFO存儲字內容的讀指針會立即將第一個有效數據驅動到FIFO數據輸出端口,由接收端讀取。讀指針始終指向要讀取的下一個FIFO字意味着,接收端不必使用兩個時鍾周期來讀取數據。如果接收端在讀取FIFO數據之前必須先遞增讀指針,則接收端必須先用一個周期等FIFO輸出數據字,再用一個周期將數據字捕獲到接收端,這回浪費一倍的時間。

當讀寫指針相等時,FIFO為空。當兩個指針在復位操作期間都復位為零時,或者在從FIFO讀取了最后一個字的情況下,讀指針趕上了寫指針,就會發生這種情況。

當指針再次相等時,也就是說,當寫指針跑了一圈並追上讀指針時,FIFO為滿。那么問題來了,當指針相等時,FIFO到底為空還是滿?

區分是滿還是空可以為每個指針添加一個額外的位。當寫指針增加到最終FIFO地址之后,寫指針將使未使用的MSB標志位遞增,同時將其余位置回零,如下圖所示(FIFO使用了帶MSB標志位的指針)。讀取指針也是如此。如果兩個指針的MSB不同,則意味着寫指針比讀指針多跑了一圈。如果兩個指針的MSB相同,則意味着兩個指針的圈數一致相同。

使用n位指針(其中n-1是訪問整個FIFO緩存所需的地址位數),當兩個指針(包括MSB)相等時,FIFO為空。當兩個指針(MSB除外)相等時,FIFO已滿。

本文中的FIFO設計將n位指針用於具有2(n-1)個可寫位置的FIFO,以幫助處理滿和空條件。 后續章節中包含了有關滿邏輯和空邏輯的更多設計詳細信息。

2.3 二進制FIFO指針注意事項

將二進制計數值從一個時鍾域同步到另一個時鍾域存在一些問題,因為n位計數器的每個位都可以同時更改(例如,二進制數7-> 8的值為0111-> 1000,所有位都更改了)。解決該問題的一種方法是使用握手機制將周期二進制計數值采樣並保存在保持寄存器中,然后將同步請求信號傳遞到新的時鍾域。識別請求信號后,接收時鍾域將同步的應答信號發送回發送時鍾域。在從接收時鍾域接收到應答信號之前,不得更改采樣指針。使用此技術,可以將具有多個更改位的計數值安全地傳輸到新的時鍾域。收到應答信號后,發送時鍾域有權清除就緒信號並重新采樣二進制計數值。

使用此技術,將定期采樣二進制計數器值,並且並非所有二進制計數器值都可以傳遞到新的時鍾域。問題是,我們是否需要關注二進制計數器可能繼續遞增和溢出的情況?還是在采樣的計數器值之間使FIFO下溢?答案是否定的。

  • 當寫指針追上同步和采樣的讀指針時,FIFO滿。同步和采樣的讀取指針可能無法反映實際讀取指針的當前值,但寫入指針將不會嘗試計數超過同步讀取指針值的值。因此不會發生溢出。
  • 當讀指針趕上同步采樣的寫指針時,FIFO空。同步和采樣的寫指針也可能無法反映實際寫指針的當前值,但讀指針將不會嘗試計數超過同步寫指針值的值。因此不會發生下溢[8]。

在討論了同步格雷碼指針之后,將在7.0節中詳細介紹有關對具有同步握手信號對的二進制指針進行采樣的技術。

FIFO計數器指針的另一種常見方法是使用格雷碼計數器。格雷碼在每次時鍾轉換時只允許改變一位,從而消除了與試圖在同一時鍾沿同步多個變化信號相關的問題。

2.4 FIFO測試的問題

完備的FIFO測試幾乎是不可能的,這與功能仿真不同,涉及到時序信息。問題根源在於就算RTL仿真中的FIFO指針表現理想,在實際設計中使用它們也會導致災難性的故障。

在RTL仿真中,如果設計中包含二進制計數FIFO指針,則所有FIFO指針位將同時變化,沒有機會觀察到同步和比較問題。在沒有反標延遲的門級仿真中,如果門上升沿和下降沿信號的門延遲不同,觀察到問題的機會將會很小,即使能夠觀察到,也必須需要具有正確的時序能夠觸發這種情況。數據在時鍾上升沿之前和之后變化,對於更高速度的設計,上升沿信號和下降沿信號之間的延遲差減小,發現問題的可能性也會減小。找到實際的FIFO設計問題對於帶有反標延遲的門級設計來說是更加容易一些,但是即使進行這種類型的仿真,也很難做到發現問題,而且隨着信號傳播延遲的減少,觀察到設計問題的幾率也會降低。

如果要解決這一問題,就必須認識到存在潛在的FIFO設計問題,並且從一開始就進行正確地設計。

我有時用於測試FIFO設計的行為模型是FIFO模型,它易於編碼,對於行為測試來說是准確的,但是如果用作RTL綜合模型,則很難調試。只建議將此FIFO模型用於FIFO測試平台。該模型可准確地確定何時應設置FIFO滿和空狀態位,並可用於確定應存儲在工作FIFO中的數據值。再重復一遍,此FIFO模型不能安全地進行綜合!

module beh_fifo (rdata, wfull, rempty, wdata, winc, wclk, wrst_n, rinc, rclk, rrst_n);
    parameter DSIZE = 8;
    parameter ASIZE = 4;
    output [DSIZE-1:0] rdata;
    output             wfull ;
    output             rempty; 
    input  [DSIZE-1:0] wdata;
    input              winc,wclk,wrst_ n;
    input              rinc,rc1k,rrst_ n;
    
    reg    [ASIZE: 0]  wptr,wrptr1,wrptr2,wrptr3;
    reg    [ASIZE: 0]  rptr,rwptr1,rwptr2,rwptr3;
    
    parameter MEMDEPTH = 1<<ASIZE;
    
    reg [DSIZE-1:0] ex_ mem [0 : MEMDEPTH-1];
    
    always @ (posedge wclk or negedge wrst_ n)
        if      (!wrst_ n) wptr <= 0
        else if (winc && !wfu11) begin
            ex_ mem [wptr [ASIZE-1:0]] <= wdata;
            wptr                       <= wptr+1;
        end
    
    always @ (posedge wc1k or negedge wrst_ n)
        if (!wrst_ n) {wrptr3,wrptr2,wrptr1} <= 0;
        else          {wrptr3,wrptr2,wrptr1} <= {wrptr2,wrptr1,rptr};
    
    always @ (posedge rc1k or negedge rrst_ n)
        if      (!rrst_ n)       rptr <= 0;
        else if (rinc & !rempty) rptr <= rptr+1;
    
    always @ (posedge rclk or negedge rrst_ n)
        if (!rrst_ n) {rwptr3,rwptr2,rwptr1} <= 0;
        else          {rwptr3,rwptr2,rwptr1} <= {rwptr2,rwptr1,wptr};
    
    assign rdata  = ex_ mem [rptr [ASIZE-1:0]] ;
    assign rempty = (rptr == rwptr3) ;
    assign wfu11  = ((wptr [ASIZE-1:0] == wrptr3 [ASIZE-1:0]) && 
                     (wptr [ASIZE]!= wrptr3 [ASIZE]));
endmodule

在上面的行為模型中,使用二進制計數指針,一個Verilog數組來表示FIFO緩存,同一模塊中的多個異步時鍾以及未注冊的輸出。 該模型不適合綜合!(希望在本節中使用了足夠的警示,以阻止任何人嘗試綜合此模型!)

模塊中的兩個Always模塊(帶有連接的Always模塊),以行為模型的方式表示實際RTL FIFO設計中所需的同步。 它們對於通過FIFO進行的數據傳輸測試並不重要,但對於在FIFO模型中正確生成的滿標志和空標志的測試至關重要。 行為模型中所需的同步階段的確切數量取決於FIFO設計。 該模型可用於幫助測試本文所述的FIFO設計。

3.0格雷碼計數器(風格1)

格雷碼以1953年最初為該代碼申請專利的人的名字命名,弗蘭克·格雷。 格雷碼計數器有很多種設計方法。 本節詳細介紹了一種簡單而直接的設計方法。 本文描述的技術僅使用一組觸發器實現格雷碼計數器。 下一節將詳細介紹使用兩組觸發器實現更高速度的第二種方法。

3.1 格雷碼模式

出於后面的原因,我們希望同時設計n位格雷碼計數器和(n-1)位格雷碼計數器。 分別設計兩個計數器很容易,但是設計一個通用的n位格雷碼計數器然后修改第二個MSB以形成一個共享LSB的(n-1)位格雷碼計數器也很容易且有效。在本文中,這將被稱為“對偶n-bit位格雷碼計數器”。

為了更好地理解將n位格雷碼轉換為(n-1)位格雷碼的問題,請考慮設計像圖2中的雙4位和3位格雷碼計數器。

如上圖所示,最常見的格雷碼是一種反射碼,其中除MSB以外的任何列中的位均關於序列中點對稱[6]。這意味着4位格雷碼的后半部分是MSB反轉的前半部分的鏡像。

要將4位轉換為3位格雷碼,我們不希望4位序列的后半部分的LSB是前半部分的LSB的鏡像,而是希望將4位序列的后半部分的LSB鏡像。下半部分重復上半部分的4位LSB序列。

經過仔細檢查,很明顯將4位格雷碼后半部分的第二個MSB取反將在4位序列的三個LSB中產生所需的3位格雷碼序列。唯一的另一個問題是帶有額外MSB的3位格雷碼不再是真正的格雷碼,因為當序列從7(格雷碼0100)變為8(〜格雷碼1000),然后又從15(〜格雷碼1100)變為0(格雷碼0000),兩位在變化,而不是一位。真正的格雷碼只會在計數之間改變一位。

3.2格雷碼計數器基礎

關於格雷碼,任何兩個相鄰數之間的編碼距離僅為1(從一個格雷計數到下一個格雷計數,只能改變一位)。其次,最有用的格雷碼計數器的深度必須是2的冪。可以使格雷碼計數器計數偶數個序列,但是這些序列之間的轉換通常不像標准格雷碼那樣簡單。另外請注意,由於沒有奇數長度的格雷碼序列,因此無法制作23深度的格雷碼。這意味着本文描述的技術用於制作2^n深度的FIFO。

圖3是風格1的對偶n-bit位格雷碼計數器的框圖。風格1的格雷碼計數器假定寄存器位的輸出是格雷碼值本身(ptr,wptr或rptr)。然后,將格雷碼輸出傳遞到格雷碼-二進制轉換程序(bin),將其傳遞給條件二進制值增量器,以生成下一個二進制計數值(bnext),並將其傳遞給二進制-格雷碼轉換器,生成下一個格雷碼計數值(gnext),該值傳遞到寄存器輸入。圖3方框圖的上半部分顯示了所描述的邏輯流程,而下半部分則表示了與第二格雷碼計數器相關的邏輯,如下一節所述。

3.3 對偶n-bit格雷碼計數器

對偶n-bit格雷碼計數器是一個格雷碼計數器,它生成n位格雷碼序列和一個(n-1)位格雷碼序列。

通過對n位格雷碼的兩個MSB進行異或運算,即可生成(n-1)位格雷碼的MSB,從而簡單地生成(n-1)位格雷碼。 它與n位格雷碼計數器的(n-2)個LSB組合在一起,形成(n-1)位格雷碼計數器。

3.4 其他格雷碼計數器注意事項

  1. 如圖3所示,應該對二進制值增量器進行“不滿”或“不空”檢查,以確保FIFO指針在FIFO滿或FIFO空的情況下不會遞增,最終導致FIFO緩存上溢或下溢。
  2. 如果能夠保證在FIFO滿狀態時不向FIFO發送數據,則可以通過從FIFO寫指針中刪除滿狀態檢查邏輯(full-testing logic)來簡化FIFO設計。
  3. FIFO指針本身並不能保護FIFO緩存不被覆蓋,但是可以將其他條件邏輯添加到FIFO緩存中,以確保在FIFO滿狀態下不能激活write_enable信號。
  4. 可以將額外的“粘性”狀態位ovf(上溢)或unf(下溢)添加到指針設計中,以指示在滿狀態時發生了另外的FIFO寫操作,或者在空狀態時發生了另外的FIFO讀操作,而標志位錯誤情況只能在重置期間清除。

一個安全的通用FIFO設計需要包括上述保護措施,但代價是實現起來消耗會稍大一些,甚至可能導致速度變慢。但是這是一個好的設計,因為將來的同事不需要考慮那么多細節就能夠直接復用模塊。

4.0 格雷碼計數器(風格2)

從本文的1.2版開始,FIFO設計將使用格雷碼計數器風格2,這種風格使用兩組寄存器來消除將格雷指針值轉換為二進制值的需求。 第二組寄存器(二進制寄存器)也可用於直接尋址FIFO存儲器,而無需將存儲器地址轉換為格雷碼。 但是仍然需要n位格雷碼指針將指針同步到相反的時鍾域,但是n-1位二進制指針可用於直接尋址內存。 如果需要,二進制指針還使運行計算更容易以生成“接近空”和“接近空”的位(本文未顯示)。

FIFO(風格1)

下圖是第一種風格的FIFO

為了進行風格1FIFO設計的靜態時序分析,該設計已划分為以下六個具有以下功能和時鍾域的Verilog模塊:

  • fifo1.v-(請參見6.1節中的示例2)-此為頂層的包裝器模塊,包括所有時鍾域。頂部模塊僅用作包裝,以實例化設計中使用的所有其他FIFO模塊。如果將此FIFO用作較大的ASIC或FPGA設計的一部分,則可能會舍棄此頂級包裝程序,以允許將其他FIFO模塊分組到各自的時鍾域中,以改進綜合和靜態時序分析。
  • fifomem.v-(請參見第6.2節中的示例3)-這是FIFO緩存,可通過寫入和讀取時鍾域進行訪問。該緩沖區很可能是實例化的同步雙端口RAM。可以修改其他存儲器樣式以用作FIFO緩沖區。
  • sync_r2w.v-(請參閱6.3節中的示例4)-這是一個同步器模塊,用於將讀指針同步到寫時鍾域中。wptr_full模塊將使用同步的讀指針來生成FIFO滿狀態。該模塊僅包含與寫時鍾同步的觸發器。此模塊中沒有其他邏輯。
  • sync_w2r.v-(請參閱6.4節中的示例5)-這是一個同步器模塊,用於將寫指針同步到讀時鍾域中。rptr_empty模塊將使用同步的寫指針來生成FIFO空狀態。該模塊僅包含與讀取時鍾同步的觸發器。此模塊中沒有其他邏輯。
  • rptr_empty.v-(請參見6.5節中的示例6)-此模塊與讀取時鍾域完全同步,並包含FIFO讀取指針和空標志邏輯。
  • wptr_full.v-(請參見6.6節中的示例7)-此模塊與寫時鍾域完全同步,並且包含FIFO寫指針和全標志邏輯。

為了使用此FIFO風格執行FIFO滿和FIFO空測試,必須將讀取和寫入指針傳遞到相反的時鍾域以進行指針比較。

與其他FIFO設計一樣,由於兩個指針是從兩個不同的時鍾域生成的,因此需要將這些指針“安全地”傳遞到相對的時鍾域。本文使用的技術是同步格雷碼指針,以確保一次只能更改一個指針位。

5.0 處理空和滿標志位

到底如何實現FIFO滿標志和FIFO空標志取決於設計方法。

本文中的FIFO設計假定將在讀時鍾域中生成空標志,以確保當FIFO緩沖區為空時(即,讀指針趕上寫操作的瞬間)能夠立即檢測到空標志指針(包括指針的MSB)。

本文中的FIFO設計假定將在寫時鍾域中生成滿標志,以確保當FIFO緩沖區已滿時(即,寫指針趕上讀取的瞬間)能夠立即檢測到滿標志指針(不同的指針的MSB除外)。

5.1 生成空標志位

如2.2中所述,當讀指針和同步寫指針相等時,FIFO為空。

空標志位很容易生成。使用指針比尋址FIFO緩存所需的指針大一位。如果兩個指針的額外位(指針的MSB)相等,則指針的換行次數相同,並且如果其余讀取指針等於寫指針,則FIFO為空。

格雷碼寫指針必須通過sync_w2r模塊中的一對同步器寄存器同步到讀時鍾域中。由於使用格雷碼指針一次只更改一位,因此在時鍾域之間同步多位轉換沒有問題。

為了有效地輸出空標志位,實際上會將寫指針與rgraynext(rptr的下一個格雷碼值)進行比較。下面是空標志位檢查的代碼,來自示例6中的rptr_empty.v:

assign rempty_val = (rgraynext == rq2_wptr);
always @(posedge rclk or negedge rrst_n)
    if (!rrst_n) rempty <= 1'b1;
    else         rempty <= rempty_val;

5.2 生成滿標志位

由於需要通過在寫時鍾域中比較寫指針和讀指針來生成滿標志,因此要在進行指針比較之前將讀指針同步到寫時鍾域中。

生成滿標志位不像空標志位那么簡單。 比尋址FIFO緩存所需的地址大一點的指針仍用於比較,但是僅使用帶有額外位的格雷碼計數器進行比較並不准確。 問題在於,格雷碼除MSB之外是對稱碼。

考慮上圖所示的深度為8的FIFO。在這個例子中,使用3位格雷碼指針尋址存儲器,並添加了一個額外的位(4位格雷碼的MSB)來判斷滿和空狀態。如果允許FIFO填充前七個位置(字0-6),然后如果通過回讀相同的七個字來清空FIFO,則兩個指針將相等並指向地址Gray-7(FIFO為空) )。在下一次寫操作中,寫指針將使4位格雷碼指針遞增(請記住,只有3個LSB用於地址存儲器),使MSB在和讀指針不一樣,但其余部分和讀指針一致,判定FIFO滿狀態。這是錯誤的!不僅FIFO未滿,而且3個LSB均未更改,這意味着尋址的緩存位置會覆蓋最后寫入的FIFO緩存。這也是錯的!

這就是為什么使用第4.0節的對偶n-bit位格雷碼計數器的原因之一。

通過將rptr同步到wclk域中,進行比較,然后需要滿足以下三個條件才能判定為滿狀態:

(1)wptr和rptr的MSB不相等(因為wptr必須比rptr多跑一圈)。

(2)wptr和rptr的次MSB不相等(參見上面的圖)。

(3)所有其他wptr和同步rptr位必須相等。

為了有效地判定滿標志,實際上需要把同步讀指針與wgnext(wptr的下一個格雷碼)進行比較。如下所示,這是從示例7的wptr_full.v代碼中提取的代碼塊

assign wfull_val = ((wgnext[ADDRSIZE]    !=wq2_rptr[ADDRSIZE] ) &&
                    (wgnext[ADDRSIZE-1]  !=wq2_rptr[ADDRSIZE-1]) && 
                    (wgnext[ADDRSIZE-2:0]==wq2_rptr[ADDRSIZE-2:0]));
always @(posedge wclk or negedge wrst_n)
    if (!wrst_n) wfull <= 1'b0; 
    else         wfull <= wfull_val;

上述代碼中的assign可以進一步簡化為

assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1], wq2_rptr[ADDRSIZE-2:0]});

5.3 不同的時鍾速度

由於異步FIFO是從兩個不同的時鍾域提供時鍾,因此顯然時鍾運行在不同的速度。將較快的時鍾同步到較慢的時鍾域時,由於較快的時鍾將在較慢的時鍾沿之間半周期地遞增兩次,因此會跳過一些計數值。這引發了對以下兩個問題的討論:

第一個問題。快時鍾是慢時鍾的兩倍,那么快域地址變化兩次,慢時鍾域采樣一次,前后采樣值變化了兩次,會產生多位同步的問題嗎?

不會。快時鍾域第一次改變一位,比如從A到B,慢時鍾域沒有采樣,當快時鍾域改變第二次B到C之后,慢時鍾域才采樣,雖然這期間快時鍾域的地址從A到C變了兩次,但是慢時鍾域只看到第二次B到C,只跳變了一位,因此不會產生多位同步問題。

第二個問題。快時鍾域是否會引起full+1的情況——寫溢出,或者empty+1——讀溢出?

在本文的設計下,答案是不。首先考慮FIFO已滿。當寫指針追上同步的讀指針並且在寫時鍾域中檢測到FIFO滿狀態時,FIFO充滿。如果wclk域快於rclk域,則寫指針最終將追上同步的讀指針,FIFO達到滿狀態,wfull置1,並且FIFO將停止寫操作,直到同步的讀指針前進才會繼續。也就是說寫指針不能超過wclk域中的同步讀指針。

對空標志的檢查也是類似的,當讀取指針追上同步寫入指針並且在讀取時鍾域中檢測到FIFO空狀態時,FIFO變空。如果rclk域快於wclk域,則讀指針最終將追上同步寫指針,FIFO將為空,rempty位將置位,並且FIFO會暫停讀取,直到同步寫指針前進才會繼續。讀指針不能超過rclk域中的同步寫指針。

使用這種設計,FIFO的空滿狀態判定總是及時、准確地。對清楚空滿狀態是悲觀的。

5.4 悲觀的空與滿

本文使用的FIFO使用“悲觀”方法實現了滿清除和空清楚。也就是說,“full”和“empty”都能准確判斷,但是清除得很晚。

由於在寫時鍾生成FIFO-full狀態,且當寫指針趕上同步讀指針時發生成FIFO-full,因此滿檢測是“准確的”和即時的。刪除“滿”狀態是悲觀是因為“滿”比較是用同步讀指針完成的。當讀指針增加時,FIFO不再滿,但是直到兩個wclk邊沿將更新后的rptr同步到wclk域中,整個生成邏輯才會檢測到這種變化。這並不是一個問題,因為這意味着數據發送硬件被“阻止”了,或者FIFO標志位仍然是滿的,有兩個額外的wclk周期延遲。因此,能夠保證FIFO不會溢出。向數據發送方發出信號,讓它不要發送更多的數據,騰出兩個額外的wclk周期,給FIFO留出時間來接收更多的數據。

類似地,由於用讀時鍾生成FIFO-empty狀態,並且當讀指針趕上同步的寫指針時產生FIFO-empty,所以空檢測是准確的和及時的。清除“空”狀態是悲觀的,因為“空”比較是用同步寫指針完成的。當寫指針增加時,FIFO不再為空,但是直到兩個上升的rclk上升沿才能將新的wptr同步到rclk域中,空生成邏輯才會檢測到這種變化。這並不是問題,因為這意味着數據接收邏輯被“阻止”了,或者通知FIFO仍然是空的,因為有兩個額外的rclk邊沿。重要的是確保FIFO不會下溢。向數據接收器發出信號,讓它停止從FIFO中讀取數據,騰出兩個額外的rclk周期,這只是為FIFO填充更多的數據提供了時間。

5.4.1 精確的空和滿

注意,如果兩個指針同時遞增,則設置滿標志或空標志可能不太准確。 例如,如果寫指針趕上了同步的讀指針,拉高滿標志,但是如果讀指針與寫指針同時增加,FIFO過早的置為滿狀態。 由於讀操作與“寫到滿”操作同時發生,因此未真正寫滿,但讀指針尚未同步到寫時鍾域中。 因此滿標志的生成有點太早,有些悲觀。 但這不是設計問題。

5.5 多bit異步復位

我們已經非常確定FIFO指針一次僅改變一位。 問題是是否存在與異步復位相關的問題,是否會導致多個指針位同時更改?

答案是不。 復位表示FIFO也已復位,並且FIFO中沒有有效數據。 復位生效后,所有同步寄存器,wclk域邏輯(包括已生成的滿標志)和rclk域邏輯均同時異步復位。 生成的空標志也同時復位。 更重要的問題是如何有序去除復位信號。

請注意,本文包含的設計對wclk和rclk域使用了不同的復位信號。 FIFO指針的異步復位不是問題。

5.6 幾乎已滿,幾乎已空

許多設計要求通過生成“幾乎已滿”和“幾乎為空”狀態位來通知未決的已滿或已空狀態。有很多方法可以實現這兩個狀態位,每種實現都取決於指定的設計要求。

某些FIFO設計要求可編程的FIFO滿和FIFO空差值,以便當兩個指針之間的差小於編程差時,會聲明相應的幾乎滿或幾乎為空的位。有些設計會要求在固定差值的條件下,生成幾乎滿或空的內容。當FIFO指針的MSB關閉時,松散地產生幾乎滿和空的狀態,可以滿足某些FIFO的要求。而有些設計可能只需要知道FIFO何時滿或不足一半即可。

當wptr趕上同步rptr時FIFO已滿,幾乎滿的情況可以描述為(wptr + 4)趕上同步rptr時的情況。

本文的代碼不包括almost_full和almost_empty標志位的生成。

6.0 風格1FIFO的RTL代碼

6.1 fifo1.v FIFO的頂層模塊

module fifo1 #(parameter DSIZE = 8,
parameter ASIZE = 4)
(output [DSIZE-1:0] rdata,
output wfull,
output rempty,
input [DSIZE-1:0] wdata,
input winc, wclk, wrst_n,
input rinc, rclk, rrst_n);
wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;
    
sync_r2w sync_r2w (.wq2_rptr(wq2_rptr), .rptr(rptr),
.wclk(wclk), .wrst_n(wrst_n));
    
sync_w2r sync_w2r (.rq2_wptr(rq2_wptr), .wptr(wptr),
.rclk(rclk), .rrst_n(rrst_n));
    
fifomem #(DSIZE, ASIZE) fifomem
(.rdata(rdata), .wdata(wdata),
.waddr(waddr), .raddr(raddr),
.wclken(winc), .wfull(wfull),
.wclk(wclk));
    
rptr_empty #(ASIZE) rptr_empty
(.rempty(rempty),
.raddr(raddr),
.rptr(rptr), .rq2_wptr(rq2_wptr),
.rinc(rinc), .rclk(rclk),
.rrst_n(rrst_n));
    
wptr_full #(ASIZE) wptr_full
(.wfull(wfull), .waddr(waddr),
.wptr(wptr), .wq2_rptr(wq2_rptr),
.winc(winc), .wclk(wclk),
.wrst_n(wrst_n));
    
endmodule

6.2 fifomem.v FIFO緩存

讀數據是直接從mem中讀,不需要時鍾。讀地址A指示的是下一次要讀的地址——此時FIFO不為空,讀A地址是有效的。當前rdata信號上保存的是下一次要讀的數據。如果外部讀時鍾域打算讀數據,那么給一個讀有效rinc,在rclk上升沿就可以直接把rdata取走,在rinc上升沿FIFO內部會根據A+1地址判斷是否empty。(可以參考3.2節)。

寫數據winc有效時,下一個寫時鍾沿wclk要寫入數據。滿標志表示如果在下一個時鍾沿寫數據,就寫到讀地址處(讀寫地址相同)。如果滿,則下一個時鍾沿不能寫。所以在下一個寫時鍾沿到來時要判斷滿標志,如果滿了,則不能寫。

同樣,讀地址表示下一個讀時鍾沿要讀的數據,空表示下一個時鍾沿是否可以讀。對於讀數據的設備,它需要在讀的時候判斷是否空了,至於FIFO的rdata輸出端則不需要進行empty判斷,FIFO將下一次要讀的數據放在rdata處,如果讀數據的設備要讀,就在rclk時鍾沿讀就行了。

module fifomem #(parameter DATASIZE = 8, // Memory data word width
parameter ADDRSIZE = 4) // Number of mem address bits
(output [DATASIZE-1:0] rdata,
input [DATASIZE-1:0] wdata,
input [ADDRSIZE-1:0] waddr, raddr,
input wclken, wfull, wclk);
`ifdef VENDORRAM
// instantiation of a vendor's dual-port RAM
    vendor_ram mem (.dout(rdata), .din(wdata),  //這一塊不用管
.waddr(waddr), .raddr(raddr),
.wclken(wclken),
.wclken_n(wfull), .clk(wclk));
`else
// RTL Verilog memory model
localparam DEPTH = 1<<ADDRSIZE; //將地址位數轉化成FIFO深度
reg [DATASIZE-1:0] mem [0:DEPTH-1];
assign rdata = mem[raddr]; //直接讀,empty標志位影響raddr的累加
always @(posedge wclk)
    if (wclken && !wfull) mem[waddr] <= wdata;//寫需要判斷full,並且在時鍾沿處寫
`endif
endmodule

6.3 sync_r2w.v 讀時鍾域到寫時鍾域同步器

用寫時鍾打兩拍

module sync_r2w #(parameter ADDRSIZE = 4)
(output reg [ADDRSIZE:0] wq2_rptr,
input [ADDRSIZE:0] rptr,
input wclk, wrst_n);
reg [ADDRSIZE:0] wq1_rptr;
always @(posedge wclk or negedge wrst_n)
    if (!wrst_n) {wq2_rptr,wq1_rptr} <= 0;
    else {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
endmodule

6.4 sync_w2r.v 寫時鍾域到讀時鍾域同步器

用讀時鍾打兩拍

module sync_w2r #(parameter ADDRSIZE = 4)
(output reg [ADDRSIZE:0] rq2_wptr,
input [ADDRSIZE:0] wptr,
input rclk, rrst_n);
reg [ADDRSIZE:0] rq1_wptr;
always @(posedge rclk or negedge rrst_n)
    if (!rrst_n) {rq2_wptr,rq1_wptr} <= 0;
    else {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
endmodule

6.5 rptr_empty.v 讀指針和空標志位生成邏輯

產生空標志位、n-1位當前的二進制讀地址、n位格雷碼讀地址

module rptr_empty #(parameter ADDRSIZE = 4)
(output reg rempty,
output [ADDRSIZE-1:0] raddr,
output reg [ADDRSIZE :0] rptr,
input [ADDRSIZE :0] rq2_wptr,
input rinc, rclk, rrst_n);
reg [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] rgraynext, rbinnext;
wire rempty_val;    
//-------------------
// 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];//給到memory模塊
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;
endmodule

6.6 wptr_full.v 寫指針和滿標志生成邏輯

產生滿標志、n-1位當前寫地址、n位格雷碼寫地址

module wptr_full #(parameter ADDRSIZE = 4)
(output reg wfull,
output [ADDRSIZE-1:0] waddr,
output reg [ADDRSIZE :0] wptr,
input [ADDRSIZE :0] wq2_rptr,
input winc, wclk, wrst_n);
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wgraynext, wbinnext;
wire wfull_val;    
// 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]));
//------------------------------------------------------------------
assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]});
    // 空判斷是根據下一次要寫的地址判斷的。
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) wfull <= 1'b0;
else wfull <= wfull_val;
endmodule

7.0 對比格雷碼指針和二進制指針

如第2.3節所述,如果對指針進行了采樣並且在兩個時鍾域之間使用了握手控制信號來安全地傳遞采樣的二進制計數值,也可以可以使用二進制指針進行FIFO設計。與格雷碼指針相比,使用二進制指針的一些優點:

  • 將多位值采樣到保持寄存器中並使用同步握手控制信號將多位值傳遞到新的時鍾域中的色痕跡可用於跨時鍾域傳遞任何任意多位值。 這種方法可用於傳遞FIFO指針或任何多位值。

  • 每個同步格雷碼指針需要2n個觸發器(每個指針位2個)。采樣的多位寄存器需要2n + 4個觸發器(由於握手機制)。兩種指針樣式產生亞穩性的可能並沒有差異。

  • 采樣的多位二進制指針允許任意更改指針。格雷碼指針只能遞增和遞減。

  • 采樣的多位二進制指針允許任意FIFO深度;格雷碼指針需要2的冪的FIFO深度。如果設計需要至少132個字的FIFO深度,則使用標准格雷碼指針將采用256個字的FIFO深度。由於大多數實例化的雙端口RAM塊深度為2的冪,因此這並不是什么大問題。

  • 使用二進制指針使生成“幾乎為空”和“幾乎為滿”狀態位變得容易。

與格雷碼指針相比,使用二進制指針的一個小缺點是:

  • 采樣並持有二進制FIFO指針,然后在時鍾邊界上握手會有延遲。
  • 至少從接收時鍾域兩個時鍾沿捕獲新采樣,從發送時鍾域至少兩個時鍾沿捕獲新采樣。等待時間通常不是問題,但通常會在判別full和empty時會更加悲觀,並且可能需要額外的FIFO深度來補償所增加的悲觀度。由於大多數FIFO通常都是用較大的深度指定的,因此不太可能需要額外的寄存器或更大的雙端口FIFO緩存大小。

設計FIFO時,上述關鍵點值得考慮。

8.0 結論

異步FIFO設計需要仔細注意從指針生成到空滿生成的細節。對重要細節的無知通常會導致設計易於驗證但也是錯誤的。要發現FIFO設計錯誤,通常需要對FIFO設計進行門級仿真,並需要帶有實際延遲的反標和很多運氣!

使用格雷碼指針可以安全地將FIFO指針同步到另一個的時鍾域中。

產生FIFO已滿狀態可能是FIFO設計中最難的部分。對偶n位格雷碼計數器對於將n位指針同步到相反的時鍾域並使用(n-1)位指針進行滿標志生成比較非常有用。使用第7.0節中介紹的技術同步二進制FIFO指針是進行FIFO設計時要使用的另一項有價值的技術。

通過將n位讀指針與同步的n位寫指針進行比較,可以輕松實現生成FIFO空狀態。本文的技術對於速度差大和小的異步時鍾都是適用的。

沿着時鍾域邊界對FIFO模塊進行仔細的划分,並記錄所有輸出,對兩個異步時鍾域內的綜合和靜態時序分析有很大幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM