FIFO根據輸入輸出時鐘是否一致,分爲同步FIFO與異步FIFO。同步FIFO中,讀寫控制信號以及數據均處於同一時鐘域,滿足STA分析時一般不會出現亞穩態等不穩定情形;而對於異步FIFO,讀寫相關信號處於不同時鐘域,信號的不同步可能會導致亞穩態,導致FIFO工作異常,設計較爲複雜;在之前的記錄中,我們對同步FIFO的設計進行了分析:
此處我們不再對同步FIFO進行介紹而直接以異步FIFO與同步FIFO的異同爲線索,逐步對異步FIFO進行分析,介紹異步FIFO相比於同步FIFO的額外處理,並進一步實現異步FIFO。
目錄
一、異步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中always@()語句雙邊沿觸發(語法與綜合的差異)