在系統設計期間,有幾個工作在不同頻率上的組件(器件),例如處理器,外圍設備等,它們有時可能具有自己的時鐘晶體。先進先出First-In-First-Out(FIFO)隊列在此類設備之間的數據交換中起著重要作用。FIFO是簡單的存儲器,用于數據排隊通過通信總線。因此,FIFO通常用于跨不同時鐘域的數據傳輸。我們首先描述了一個簡單的同步FIFO架構,其中讀寫是在同一時鐘上完成的。隨后將會詳細描述了異步FIFO的概念和設計,其中讀寫是在不同的時鐘頻率上完成的。

圖1-1 同步FIFO結構
1.1同步FIFO的結構
圖1-1展示了同步FIFO的一般結構,DPRAM(雙端口RAM)用作FIFO存儲器,具有獨立的讀取和寫入功能。
讀寫端口具有由兩個讀寫指針生成的單獨的讀寫地址。寫指針指向下一步將要寫入的位置,讀指針指向下一步將要讀取的位置。有效的寫使能會增加寫指針,而有效的讀使能會增加讀指針。
圖1-1展示一個“Status Block”,該狀態塊生成“fifo_empty”和“fifo_full”信號。如果斷言(斷言就是把信號變為有效)“fifo_full”,則意味著沒有更多空間可將更多數據寫入FIFO。同樣,“fifo_empty”表示FIFO中沒有可用的數據可供外部模塊讀取。可以用兩個指針的不同邏輯來表示某個時間點FIFO中數據空或者數據滿的狀態。
圖中所示的雙端口存儲器(DPRAM)可以具有同步讀取或異步讀取的功能。對于同步讀取,在FIFO輸出數據有效(valid)前,就應該提供明確的讀信號。對于異步讀取,DPRAM沒有寄存的數據輸出;有效數據一經寫入即可用(首先讀取數據,然后指針遞增)。
1-2 FIFO滿狀況
1.2同步FIFO的工作
復位時,讀和寫指針都初始化為零。在此期間,信號“ fifo_empty”被置為有效,“fifo_full”保持為“低”。當FIFO為空時,將阻止對FIFO的進一步讀取,因此只能進行寫操作。隨后對FIFO的寫操作會增加寫指針,并置低“fifo_empty”信號。當到達沒有空間容納更多數據且寫入指針等于RAM_SIZE -1的時候。此時,寫操作將讓寫指針再次回滾至零,從而使“fifo_full”信號變為高電平。那么當讀指針等于寫指針時,FIFO是已滿還是為空?因此有必要在這兩個條件之間進行區分。
1.2.1 FIFO空和滿的生成
圖1-2展示了4深度同步FIFO的FIFO完整生成。圖1-2所示的所有轉換都在隨后的時鐘中。如圖所示,當寫入讓兩個指針在下一個時鐘中變為相等時,FIFO變滿。這使“fifo_full”信號有效的條件如下:
fifo_ full =(read _ pointer ==(write _ pointer +1))AND "write"
下面還展示“fifo_full”邏輯的示例Verilog代碼:
always @ (posedge clk or nededge reset_n)
begin: fifo_full_gen
if (~reset_n)
fifo_full < = 1’b0;
else if (wr_fifo && rd_fifo)
;//do nothing
else if (rd_fifo)
fifo_full <= 1’b0;
else if (wr_fifo && (rd_ptr = wr_ptr + 1’bl))
fifo_full <= 1’bl;
end類似地,當讀取導致兩個指針在下一個時鐘中變為相等時,FIFO變為空。這使“fifo_empty”信號有效的條件如下:
fifo_ empty =(write _ pointer ==(read _ pointer +1))AND "read"
下面還展示“fifo_empty”邏輯的示例Verilog代碼:
always @ (posedge clk or negedge reset_n)
begin: full_gen
if (~reset_n)
fifo full < = 1’bl;
else if ( wr_fifo && rd_fifo)
; //do nothing
else if (wr_fifo)
fifo_empty <= 1’b0;
else if (rd_fifo && (rw_ptr = rd_ptr + 1’bl ))
fifo_empty <= 1’bl;
end1.2.2可選的方案
生成“fifo_full”和“fifo_empty”條件的另一種方法是使用一個計數器,該計數器不斷指示FIFO中剩余的空間數量。
計數器的寬度必須等于FIFO的深度,以便存儲最大值。計數器在復位時初始化為零值。任何后續的寫操作會使計數器加一,而隨后的讀操作會使計數器減一。
現在,當計數器值達到“零”時,可以輕松生成FIFO空狀態,而當計數器值等于FIFO的大小時,則可以輕松生成FIFO已滿狀態。
與本節中提到的方法相比,本節中提到的另一種方法雖然簡單,但效率不高。因為它需要額外的硬件(比較器)來生成FIFO空和FIFO滿的條件。隨著FIFO的深度增加,計數器的寬度也增加。因此需要更高階的比較器來產生FIFO空和FIFO滿狀態信號。最終,這會降低FIFO的最大工作頻率。
二、異步FIFO
異步FIFO用于在兩個異步時鐘域之間傳輸數據。圖2-1展示兩個系統“System X”和“System Y”,其中來自“SystemX”的數據被傳輸到“System Y”,這兩個系統都在不同的時鐘域工作。
2-1 異步FIFO的數據傳輸
“System X”將“xclk”時鐘上的數據寫入FIFO,并由“yclk”時鐘上的“系統Y”讀出。
“FIFO full”和“FIFO empty”信號負責下溢和上溢的條件。
上溢條件由“FIFO full”信號處理,即,如果斷言“FIFO Full”信號,則不會將數據寫入FIFO,否則數據將被覆蓋。
通過“FIFO empty”信號來注意下溢條件,即,如果斷言“FIFO empty”信號,則不會從FIFO讀取數據,否則將讀取無效數據。與握手信令不同,異步FIFO用于對性能至關重要的設計,在這些設計中,時鐘延遲是一個更加重要的因素,而不是系統資源。
正如在同步FIFO中提到的,可以使用雙端口RAM來實現簡單的同步FIFO,該雙端口RAM具有用于在同一時鐘下進行讀寫操作的單獨端口以進行讀寫操作。在設計異步FIFO時可以擴展相同的概念,并特別注意FIFO空和FIFO滿信號的生成,以避免出現亞穩態情況。
2.1避免將二進制計數器用于指針實現
以寫指針為例,每當有對FIFO的有效寫請求時,寫指針總是基于寫時鐘上遞增。同樣,只要有有效的讀取請求,讀取指針就會在讀取時鐘上遞增。為了產生FIFO Full信號,需要將寫指針與讀指針進行比較,并且由于兩個指針都與各自的時鐘同步,但是彼此異步,如果將二進制計數器用于比較,則會導致指針值的采樣錯誤以進行比較。指針實現。如下圖所示。
假設二進制計數器從FFF變為000,在這種情況下,所有位都將同時更改。(異步時鐘域簡單講為什么要格雷碼,計數器中的位數不應該同時改變,會增加錯誤概率,因為從FFF到000會有多種過渡態,這種過渡態可能會導致錯誤)可以通過使計數器同步來避免亞穩態,但是這仍然可能會獲得超出范圍的采樣值,因此,并非最終解決方案中要同步計數器。

從FFF到000可能的過度:

如果同步時鐘沿位于從FFF到000的過渡的中間,則有可能在新的時鐘域中對3位二進制值中的任何一個數值進行采樣和同步。
由于FIFO滿標志和FIFO空標志的生成取決于這些指針值,因此這些指針的值不正確將導致標志的錯誤觸發。可能存在以下情況:即使實際上FIFO已滿,也不會觸發FIFO滿標志,從而導致數據丟失,或者未觸發FIFO空標志,從而導致無效數據。
注意:考慮到以上情況,強烈建議避免將二進制計數器用于讀寫指針實現
2.2使用格雷碼取代二進制計數器

圖2-2 同步對FIFO滿邏輯的影響
格雷碼相對于純二進制數的優勢在于,格雷碼中的數字在從一個數字到下一個數字之間變化一位。要獲得不同的格雷碼,只要兩個數字不相同,就可以從任意位組合開始,然后通過以任意所需的方式僅將一位從0更改為1或從1更改為0來繼續獲取其它格雷碼,格雷碼也稱為反射碼。由于格雷碼是單位距離碼,因此每個下一個值不同于上一個值的位僅有一個,即,如果出錯將導致最多一位錯誤。舉例,如果計數器從“1010”變為“1011”,那么即使出錯,也只會讀到“1010”,正確的話就會讀出“1011”,相比較二進制的多種錯誤可能,這降低錯誤率和錯誤可能。
注意:同步格雷計數器很少會導致采樣的計數器值出現亞穩態,其次,采樣的值最多會有一位錯誤。
2.2.1指針同步的影響
如果FIFO已滿,應阻止對FIFO的進一步訪問。為了計算FIFO滿狀態,必須對在各自時鐘上遞增的讀和寫指針進行比較。讀指針(格雷編碼)需要與寫時鐘同步。讓我們舉個例子。如圖2-2所示,在FIFO為空的情況下,初始讀取指針和寫入指針在t0時為零。當隨后的寫操作在FIFO上進行時,寫指針將遞增。當寫指針等于讀指針并且FIFO變為FULL時,達到一個階段。如圖2-3所示,這發生在t5。現在,如果在t6發生讀取,由于典型的同步器電路至少由兩個觸發器組成,因此在寫入時鐘上同步讀取指針將導致在兩個寫入時鐘之后反映出更改的讀取指針。這導致在額外的周期中阻止對FIFO的其他寫操作,但這是無害的。如果在FIFO實際上已滿時沒有阻止寫操作,那將是一個問題。同樣,當FIFO為空時,應阻止對FIFO的進一步讀取訪問

2-3 FIFO滿時序

圖2-4 同步對FIFO空邏輯的影響
對于FIFO空計算,寫指針與讀時鐘同步,并與讀指針進行比較。因此,讀取端會看到延遲的寫入(兩個時鐘延遲的信號),即使實際上有一些數據,仍然會指示FIFO為空。這將導致讀取被阻塞,直到寫入對讀取端可見為止。
如圖2-4所示,在t0且FIFO為空的情況下,初始讀取和寫入指針為零。在FIFO上進行后續寫操作時,寫指針將遞增。當寫指針等于讀指針并且FIFO變為FULL時,達到一個階段。如圖2-5所示,這發生在t3。
隨后的讀取在t4開始,并且FIFO在t6再次變空。現在,由于典型的同步器電路至少由兩個觸發器組成,因此在t7和t8再次寫回FIFO,因此在讀時鐘上同步寫指針將導致在兩個讀時鐘后反映寫指針的變化。這樣會阻止對FIFO的其他讀取,并且是無害的。如果在FIFO實際上為空的情況下不阻止讀取,那將是一個問題。

圖2-5 FIFO空
注意:向寫方報告FIFO滿時,FIFO是滿的;向讀方報告FIFO為空時,則為FIFO。即使指針的同步值(寫期間的同步讀指針和讀期間的同步寫指針)在短時間內保持亞穩態,其效果也將是阻止寫操作。
2.3 FIFO指針的格雷碼實現
讀和寫指針值都需要正確采樣,以完美生成FIFO空和FIFO滿條件。在時鐘域之間傳遞指針的最佳方法是將格雷碼計數器用于指針實現,因為如果同步時鐘信號進入計數器轉換的中間,它們將消除大多數錯誤。設計一個格雷碼計數器似乎很復雜,但確實很簡單,應該做的所有事情如下:
步驟I:將Gray值轉換為Binary值。
步驟II:根據某些條件遞增二進制值。
步驟III:將Binary值轉換回Gray。
步驟IV:將計數器的最終Gray值存儲在寄存器中。
圖2-6 展示生成格雷碼計數器的步驟

圖2-6 使用二進制加法器的格雷碼計數器

圖2-7 計數器以格雷/二進制遞增
2.3.1格雷碼到二進制的轉換器
圖2-7列出了用格雷碼和二進制計數時的四位計數器值。當在時鐘上遞增時,特定列中的后續行將顯示計數器的轉換值。格雷碼到二進制轉換的方程式:


圖2-6 計數器的位編號
其中i <n -1,在n位計數器
圖2-5 展示計數器的位編號
讓我們舉一個簡單的例子,將格雷碼“ 1010”轉換成它的二進制等效值。取n – 1 = 3
將i = 3 代入上面3.3的等式中:

將i = 2 代入上面3.4的等式中:

將i = 1 代入上面3.4的等式中:

將i = 1 代入上面3.4的等式中:

因此,我們可以得到下面四個等式:

基于以上等式,格雷值“ 1010”的最終二進制等效值是“ 1100”。
因此,從上面的方程式中可以清楚地看出,可以通過將灰度值右移3來生成bin[3],通過將灰度值右移2來生成bin [2],通過將灰度值右移1來生成bin [1],然后將bin[0]生成 將格雷碼右移0。
下面是上述格雷碼到二進制轉換器的Verilog代碼:
module gray_to_bin (bin , gray);
parameter SIZE = 4;
input [SIZE] – 1:10] bin;
output [SIZE – 1:10] gray;
reg [SIZE – 1:10] bin;
integer i;
always @ (gray)
for ( i = 0; i <= SIZE; i = i + 1)
bin[i] = ^(gray >> i);
endmodule2.3.2二進制到格雷碼轉換器
下面是二進制轉換到格雷值的等式:

讓我們舉一個簡單的例子,把二進制“1100”轉回它等效的格雷值。
取n – 1 = 3
把i = 3 帶入上面的3.9等式中:

把i = 2 帶入上面的3.10等式中:

把i = 1 帶入上面的3.10等式中:

把i = 0 帶入上面的3.10等式中:

這給出了與給定二進制值“ 1100”等價的格雷值“ 1010”
基于以上的等式,我們可以得到下面四個等式:

從等式3.11–3.14推斷,可以通過在二進制值與其右移得到的值之間執行異或運算來獲得等效格雷值,如下所示:

下面是二進制轉格雷碼的Verilog代碼:
module bin_to_gray (bin, gray);
parameter SIZE = 4;
input [SIZE-1:0] bin;
output [SIZE-1:10] gray;
assign gray = (bin >> 1) ^ bin;
endmodule2.3.3格雷碼計數器的實現
它是圖2-6所示的所有四個步驟的組合(格雷至二進制轉換器,加法器,二進制至格雷轉換器以及最后一組用于存儲格雷碼的寄存器元素)。
以下是格雷碼計數器的Verilog代碼:
module gray_ counter (clk, gray, inr, reset_n)
parameter SIZE = 4;
input clk, inr, reset_n;t
output [SIZE -1 ] gray;
reg [SIZE] – 1 ] gray_temp, gray, bin_temp, bin;
integer i;
always @ (gray or inr)
begin:gray_bin_gray
for (i = 0; i<SIZE ; 1 = i +1)
bin[i] = ^(gray >> i); // gray to binary conversion
bin_temp = bin + inr; // addition in binary
gray_temp = bin_temp >> 1) ^ bin_temp; // binary to gray conversion
end
endmodule
圖2-8 格雷計數器邏輯
下面的Always塊顯示了轉換后的格雷值的register:
always @ (posedge clk or negedge reset_n)
begin:gray_registered
if (~reset_n)
gray <= {SIZE {1’b0}};
else
gray <= gray_temp;
end圖2-8展示了上述格雷計數器代碼的邏輯圖。
2.4 FIFO空標志和滿標志的生成
N位指針可以尋址FIFO中的2*N個位置。由于當兩個指針相等時FIFO可能為空或已滿,因此需要一個額外的位來區分這兩個條件。當指針的二進制的最高有效位不同且其余N位相等時,FIFO已滿。當指針的二進制在所有位位置完全相等時,FIFO為空。
例如:考慮一個8深度的FIFO。需要三個位來尋址其所有八個位置,另外需要一個位來區分FIFO已滿和FIFO空狀態。最初,rd_ptr_bin和wr_ptr_bin均為“0000”,并且FIFO為空。現在,在對FIFO進行了八次后續寫入之后,我們具有以下讀取和寫入指針值:


圖2-9 FIFO滿狀況

圖2-10 FIFO空狀況
現在,在隨后的八次讀取中,讀寫指針具有以下值:

如圖2-10所示,這是FIFO空狀態。圖2-11給出了顯示FIFO空和FIFO滿生成的框圖:
在這種情況下,最大操作頻率將取決于格雷碼計數器的工作速度,因為它需要XOR門鏈。
由于“讀/寫”指針的值存儲為格雷碼,并且所有比較,指針的遞增等均以二進制形式進行,因此實現和調試都非常簡單。如圖2-11所示,它需要四個格雷碼到二進制轉換器,如果直接用格雷碼完成FIFO空和FIFO滿生成的計算等比較,則可以避免。這有些復雜,并且需要一些其他邏輯。在下一部分中,讓我們看看這種替代方法的工作原理。
2.4.1 FIFO滿標志和FIFO空標志生成的另一種方法
這種方法需要創建兩個格雷碼計數器,一個為n位,另一個為n-1位。可以通過單個n位計數器創建兩個計數器,然后修改其第二個MSB以生成具有與n位計數器相同的LSB的(n-1)位格雷碼計數器。

圖2-11 FIFO滿和空信號生成圖

圖2-12 四位的格雷碼計數器
在開始主要邏輯之前,讓我們先了解有關格雷碼計數器的更多信息。圖2-12顯示了四位格雷碼計數器。如上圖所示,除MSB以外的任何列中的位均關于序列中點對稱。因此,四位格雷碼的后半部分是MSB反轉后的前半部分的鏡像。

圖2-13將四位格雷碼轉換為三位格雷碼
下表是將四位格雷碼轉換為三位格雷碼

現在,可以通過對n位格雷碼的兩個MSB進行XOR運算,輕松生成(n-1)位格雷碼,以生成(n-1)位格雷碼的MSB。其余(n-2)位可以簡單地使用n位計數器的(n-2)位。圖2-13顯示了將4位格雷碼轉換為3位格雷碼(上表)。此雙n位格雷碼計數器的用法FIFO空/滿生成邏輯的邏輯將在下一部分FIFO設計中介紹。
2.5雙時鐘FIFO設計
圖2-14顯示了使用雙端口存儲器作為存儲元件的FIFO的框圖。

2-14 雙時鐘FIFO設計
2.5.1 FIFO空條件生成
當FIFO變空(即讀指針與同步寫指針匹配)時,將立即在讀時鐘域中生成FIFO空標志。2.4節中所示的實現。如果在比較之前首先將指針轉換為它們的等效二進制數,則可以節省四個格雷到二進制的轉換器。與先前的實現類似,指針比尋址FIFO存儲器所需的指針大一位。將同步的寫指針(wr_ptr_sync)與rd_gtemp(將在rd_ptr中注冊的下一個格雷碼)進行比較。下面是上述邏輯的Verilog代碼:
always @ (posedge rclk or negedge reset_n)
begin: fifo_empty_gen
if (~reset_n)
fifo_empty <= 1’bl;
else
fifo_empty <= (rd_gtemp = = wr_ptr_sync);
end
2-15 FIFO空和滿狀況
2.5.2 FIFO滿條件生成
當FIFO變滿時,即寫指針與同步讀指針匹配時,將立即在寫時鐘中生成FIFO滿標志。請注意,寫入指針和同步讀取指針都直接用格雷碼進行比較。與先前的實現類似,指針比尋址FIFO存儲器所需的指針大一位。生成此條件的邏輯與以前的實現不同,因為指針比較直接以格雷碼而不是二進制完成。讓我們舉一個例子。圖2-15顯示了在八深度FIFO上執行的步驟。
步驟1:最初,FIFO為空,“ rd ptr” =“ wr ptr” = 0,如圖2-15所示。
步驟2:隨后的寫操作發生在FIFO上,直到FIFO變為“ rd ptr” = 0且“ wr ptr” = 7為止。如圖3.35所示,“ rd ptr” =“ wr ptr” = 7,并且FIFO變空(因為讀和寫指針的所有位都相等)。
步驟3:此時一次寫操作將導致“ rd ptr” = 7,“ write ptr” =8。如果使用與以前的實現(第2.4節)相同的邏輯,并且使用二進制比較,則FIFO將再次將其指示為“已滿”,即使不是(圖2-16)。
通過使用雙n位格雷碼計數器,可以輕松解決這種情況。通過將“ rd ptr”同步到寫時鐘域中,可以完成執行完全比較的正確方法。對MSB進行比較,并且在寫指針比同步讀指針多纏繞一圈的情況下,MSB應該有所不同。如果同步讀指針的MSB為高,則在與(n ? 1)位寫指針進行比較之前,將同步讀指針的第二個MSB(rd_ptr_sync)反轉。

圖2-16 FIFO滿狀況
因此,當以下三個條件全部成立時,將聲明FIFO滿標志:
1.同步讀取指針(rd_ptr_sync)的MSB應該不同于將在wr_ptr中注冊的寫入指針(wr_gtemp)的下一個格雷碼值的MSB。
2.寫時鐘域(wr_gtemp)中下一個格雷碼計數的第二個MSB應等于已同步到寫時鐘域(rd_ptr_sync)中的讀指針的第二個MSB。
3.兩個指針中所有遺漏的LSB應該匹配。
注意:上面(2)中的第二個MSB是通過對指針的前兩個MSB進行XOR運算而得出的。(如果兩個MSB為高電平,則對其進行異或運算會使第二個MSB反轉)。
下面是上面邏輯的Verilog代碼:
wire rd_2nd _msb = rd_ptr_sync [SIZE] ^ rd_ptr_sync [SIZE - 1];
wire wr_2nd_msb = wr_gtemp [SIZE] ^ wr_gtemp [SIZE-1];
always @ (posedge wclk or negedge reset_n)
begin: fifo_full_gen
if (~reset_n)
fifo_full <= 1’b0;
else
fifo_full <= ((wr_gtemp [SIZE] != rd_ptr_sync[SIZE])
&&(rd_2nd_msb = = wr_2nd_msb) &&(wr_gtemp[SIZE -2:0]
= = rd_ptr_sync[SIZE-2:0]));
end翻譯/摘自:硬件架構的藝術(英文原版),有部分加工,供參考學習。
譯者:亦安
轉自公眾號:AriesOpenFPGA
本文內容僅代表作者觀點,不代表平臺觀點。
如有任何異議,歡迎聯系我們。