“在數字IC設計中異步FIFO常用來解決多比特數據跨時鐘域的數據傳輸與同步問題,就像一個蓄水池,用于調節上下游水量”
01
異步FIFO簡介
在大規模ASIC設計中,多時鐘系統通常是不可避免的,這會導致不同時鐘域中的數據傳輸問題。其中一個好的解決方案是使用 異步FIFO來緩沖不同時鐘域中的數據 ,并改善它們之間的傳輸效率。數據從一個時鐘域寫入FIFO緩沖區,并從另一個時鐘域中讀取,該緩沖區彼此異步。異步FIFO允許數據從一個時鐘域安全地傳輸到另一個時鐘域。
如果沒有采取適當的預防措施,那么我們最終可能會遇到這樣的情況:寫入FIFO尚未完成,我們正在嘗試讀取它,反之亦然。這種情況通常會導致數據丟失和不穩定性問題。
為了避免這種情況,讀取和寫入是通過同步器完成的。同步器確保讀寫指針計算一致,并且 FIFO 中的數據不會被意外覆蓋或讀取兩次。但是,對于跨時鐘域,我們需要確保FIFO滿和空條件都考慮到跨時鐘周期。換句話說,需要添加悲觀的滿空條件。
異步FIFO的操作:
寫入操作:
此操作涉及將數據寫入或存儲到FIFO中,直到它出現不再寫入的任何標志條件。
要執行寫入操作,輸入端給出要寫入的數據,并且要將寫使能設置為高電平,然后在寫入時鐘的下一個上升沿寫入數據。
讀取操作:
當我們必須從FIFO存儲器中獲取數據時,執行讀取操作,直到它通知沒有更多的數據要從存儲器中讀取。
要執行讀取操作,我們需要將讀取使能設置為高電平,然后在讀取時鐘的下一個上升沿,輸出讀取的數據
控制操作的指針:
寫指針:此指針控制 FIFO 的寫入操作。它指向要寫入下一個數據的內存位置。
讀取指針:讀取操作由讀取指針控制。它指向要從中讀取下一個數據的位置。
FIFO中的標志:
異步FIFO為我們提供了以下兩個標志,用于確定狀態并中斷FIFO的操作。
- 空標志: 此標志可用于避免在 FIFO 已為空時出現讀取操作請求無效的情況。
- 滿標志: 此標志可用于避免在 FIFO 已滿時出現無效的寫入操作請求的情況。
將二進制計數值從一個時鐘域同步到另一個時鐘域將出現亞穩態問題,因為n位計數器的每個位都可以同時更改(例如,二進制數7-> 8的值為0111-> 1000,并且所有位都已更改)。 而使用格雷碼相鄰數據只有一位變化 ,因此在兩個時鐘域間同步多個位不會產生問題。所 以需要一個二進制到gray碼的轉換電路 , 將地址值轉換為相應的gray碼 , 然后將該gray碼同步到另一個時鐘域進行對比 ,作為空滿狀態的檢測。二進制轉為格雷碼代碼如下:
使用gray碼解決了一個問題,但同時也帶來另一個問題, 即使用格雷碼如何判斷空滿狀態 。判斷讀空狀態時: 需要在讀時鐘域的格雷碼gray_code和被同步到讀時鐘域的寫指針每一位完全相同;判斷寫滿時:需要寫時鐘域的格雷碼和被同步到寫時鐘域的讀指針高兩位不相同,其余各位完全相同;
由于讀指針是屬于讀時鐘域的,寫指針是屬于寫時鐘域的,而異步FIFO的讀寫時鐘域不同,是異步的,要是將讀時鐘域的讀指針與寫時鐘域的寫指針不做任何處理直接比較肯定是錯誤的,因此我們需要進行同步處理以后再進行比較,常用的方法采用兩級寄存器進行同步(簡單點就是在另外一個時鐘域打2拍)。
綜上,編寫異步FIFO主要有以下幾點:
- 將二進制地址轉換為格雷碼;
- 將寫時鐘域轉換后格雷碼的寫地址同步到讀時鐘域,進行讀空判斷;
- 將讀時鐘域轉換后格雷碼的讀地址同步到寫時鐘域,進行寫滿判斷;
02
Verilog代碼
module fifo_async#(
parameter data_width = 16,
parameter data_depth = 256,
parameter addr_width = 8
)
(
input rst,
input wr_clk,
input wr_en,
input [data_width-1:0] din,
input rd_clk,
input rd_en,
output reg valid,
output reg [data_width-1:0] dout,
output empty,
output full
);
reg [addr_width:0] wr_addr_ptr;//地址指針,比地址多一位,MSB用于檢測在同一圈
reg [addr_width:0] rd_addr_ptr;
wire [addr_width-1:0] wr_addr;//RAM 地址
wire [addr_width-1:0] rd_addr;
wire [addr_width:0] wr_addr_gray;//地址指針對應的格雷碼
reg [addr_width:0] wr_addr_gray_d1;
reg [addr_width:0] wr_addr_gray_d2;
wire [addr_width:0] rd_addr_gray;
reg [addr_width:0] rd_addr_gray_d1;
reg [addr_width:0] rd_addr_gray_d2;
reg [data_width-1:0] fifo_ram [data_depth-1:0];
//數據寫入 fifo
genvar i;
generate
for(i = 0; i < data_depth; i = i + 1 )
begin:fifo_init
always@(posedge wr_clk or posedge rst)
begin
if(rst)
fifo_ram[i] <= 'h0;
else if(wr_en && (~full))
fifo_ram[wr_addr] <= din;
else
fifo_ram[wr_addr] <= fifo_ram[wr_addr];
end
end
endgenerate
//========================================================read_fifo
always@(posedge rd_clk or posedge rst)
begin
if(rst)
begin
dout <= 'h0;
valid <= 1'b0;
end
else if(rd_en && (~empty))
begin
dout <= fifo_ram[rd_addr];
valid <= 1'b1;
end
else
begin
dout <= dout;
valid <= 1'b0;
end
end
//FIFO實際讀寫地址
assign wr_addr = wr_addr_ptr[addr_width-1-:0];
assign rd_addr = rd_addr_ptr[addr_width-1-:0];
//讀地址格雷碼同步到寫時鐘域:打2拍
always@(posedge wr_clk )
begin
rd_addr_gray_d1 <= rd_addr_gray;
rd_addr_gray_d2 <= rd_addr_gray_d1;
end
always@(posedge wr_clk or posedge rst)
begin
if(rst)
wr_addr_ptr <= 'h0;
else if(wr_en && (~full))
wr_addr_ptr <= wr_addr_ptr + 1;
else
wr_addr_ptr <= wr_addr_ptr;
end
//寫地址格雷碼同步到讀時鐘域:打2拍
always@(posedge rd_clk )
begin
wr_addr_gray_d1 <= wr_addr_gray;
wr_addr_gray_d2 <= wr_addr_gray_d1;
end
always@(posedge rd_clk or posedge rst)
begin
if(rst)
rd_addr_ptr <= 'h0;
else if(rd_en && (~empty))
rd_addr_ptr <= rd_addr_ptr + 1;
else
rd_addr_ptr <= rd_addr_ptr;
end
//二進制地址轉換為格雷碼
assign wr_addr_gray = (wr_addr_ptr > > 1) ^ wr_addr_ptr;
assign rd_addr_gray = (rd_addr_ptr > > 1) ^ rd_addr_ptr;
assign full = (wr_addr_gray == {~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高兩位不同
assign empty = ( rd_addr_gray == wr_addr_gray_d2 );
endmodule
評論
查看更多