1 異步FIFO結構
文章轉自:
https://baijiahao.baidu.com/s?id=1724030588865450475 感謝老鐵!
在上篇文章中我們給出了FIFO的基本接口圖
並且指出,該圖適用於所有的FIFO,這次我們先看看異步FIFO內部的大體框圖
異步FIFO主要由五部分組成:寫控制端、讀控制端、FIFO Memory和兩個時鍾同步端
寫控制端用於判斷是否可以寫入數據
讀控制端用於判斷是否可以讀取數據
FIFO Memory用於存儲數據
兩個時鍾同步端用於將讀寫時鍾進行同步處理
介紹完內部結構,我們在和基本接口圖做個聯動
剛才說過,讀/寫控制端用於判斷能否寫入/讀取數據,判斷能否寫入/讀取數據關鍵在於:
- 寫操作時,寫使能有效且FIFO未滿
- 讀操作時,讀使能有效且FIFO未空
因此兩個使能信號和空滿判斷信號都連接到控制端上
最后我們再加上時鍾信號和復位信號
這便是完整的異步FIFO簡化框圖
2 空滿判斷
在同步FIFO篇中,我們給出了兩個判斷空滿狀態的圖
並且也有指出,讀空狀態可以理解為讀地址指針追上寫地址指針,寫滿狀態可以理解為寫地址指針再次追上讀地址指針
在同步FIFO中,因為讀寫都是在同一個時鍾信號下進行的,因此兩個地址指針可以直接進行比較
但在異步FIFO中,讀寫是在不同的時鍾信號下進行的,因此在進行比較之前,應當先進行跨時鍾與同步
在時鍾同步之前,我們應當先將二進制地址轉換為格雷碼,因為格雷碼相鄰的兩個狀態之間,只有1 bit數據發生翻轉
下面給出二進制數與格雷碼的對照圖
上面也有說到,讀指針追上寫指針是讀空,寫指針再次追上讀指針是寫滿,為了便於理解,我們做一個環形圖
假設內圈為讀,外圈為寫,讀空時
是讀寫指針應當指向同一個地址,就像這樣
此時,讀地址應當和寫地址完全相同,就以0010為例,0010的格雷碼為0011,可以看出對於讀空狀態,無論是二進制還是格雷碼均是所有位都相同
寫滿
和讀空略有不同,應當是下面這樣
細心的小伙伴應該可以發現,上面在提到寫滿時,說的是寫指針再次追上讀指針,也就是說,寫滿時,寫指針比讀指針多走一圈,為了便於區分,將地址位寬從3 bit拓寬到4 bit,因此此時的寫指針地址可以認為是1010
1010的格雷碼是1111, 0010的格雷碼是0011,對比兩個格雷碼是不是可以發現,此時高兩位相反,低兩位相同,這便是格雷碼下
寫滿的判斷條件
Verilog中表示為
//寫滿判斷
always @ (posedge wr_clk or negedge wr_rstn) begin
if(!wr_rstn)
fifo_full <= 0;
else if((wr_ptr_g[$clog2(DEPTH)] != rd_ptr_grr[$clog2(DEPTH)]) && (wr_ptr_g[$clog2(DEPTH) - 1] != rd_ptr_grr[$clog2(DEPTH) - 1]) && (wr_ptr_g[$clog2(DEPTH) - 2 : 0] == rd_ptr_grr[$clog2(DEPTH) - 2 : 0]))
fifo_full <= 1;
else
fifo_full <= 0;
end
//讀空判斷
always @ (posedge rd_clk or negedge rd_rstn) begin
if(!rd_rstn)
fifo_empty <= 0;
else if(wr_ptr_grr[$clog2(DEPTH) : 0] == rd_ptr_g[$clog2(DEPTH) : 0])
fifo_empty <= 1;
else
fifo_empty <= 0;
end
3 時鍾同步
在同步FIFO設計中,因為讀寫指針在同一個時鍾下,因此可以直接進行比較
但在異步FIFO中,由於讀寫指針在不同的時鍾下,因此需要將兩個地址指針進行時鍾同步操作
在異步FIFO中,常用的同步方法是兩級同步打拍延遲,同步地址指針的大致過程如下:
寫操作時,先將寫地址指針轉換成格雷碼,然后通過兩級同步(兩級同步在讀時鍾下進行),將寫地址指針同步到讀時鍾域下;讀操作類似
根據這個過程圖,也可以看出空滿判斷的方式:
- 寫滿在寫時鍾下判斷,將寫地址指針的格雷碼與同步過來的讀地址指針格雷碼進行比較,符合寫滿條件,即FIFO
虛滿
- 讀空在讀時鍾下判斷,將讀地址指針的格雷碼與同步過來的寫地址指針格雷碼進行比較,符合讀空條件,即FIFO
虛空
留意下,這里我說的是虛空/滿
,並不是輸入錯誤喲,具體解釋我放在文章最后,愛思考的朋友現在可以思考一下原因
下面給出時鍾同步的Verilog代碼
assign wr_ptr_g = wr_ptr ^ (wr_ptr >> 1); //B2G
assign rd_ptr_g = rd_ptr ^ (rd_ptr >> 1);
//寫指針同步到讀時鍾域
always @ (posedge rd_clk or negedge rd_rstn) begin
if(!rd_rstn) begin
wr_ptr_gr <= 0;
wr_ptr_grr <= 0;
end
else begin
wr_ptr_gr <= wr_ptr_g;
wr_ptr_grr <= wr_ptr_gr;
end
end
//讀指針同步到寫時鍾域
always @ (posedge wr_clk or negedge wr_rstn) begin
if(!wr_rstn) begin
rd_ptr_gr <= 0;
rd_ptr_grr <= 0;
end
else begin
rd_ptr_gr <= rd_ptr_g;
rd_ptr_grr <= rd_ptr_gr;
end
end
4 異步FIFO設計
下面給出整體Verilog代碼
module asy_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 8
)(
input [WIDTH - 1 : 0] wr_data,
input wr_clk,
input wr_rstn,
input wr_en,
input rd_clk,
input rd_rstn,
input rd_en,
output fifo_full,
output fifo_empty,
output [WIDTH - 1 : 0] rd_data
);
//定義讀寫指針
reg [$clog2(DEPTH) : 0] wr_ptr, rd_ptr;
//定義一個寬度為WIDTH,深度為DEPTH的fifo
reg [WIDTH - 1 : 0] fifo [DEPTH - 1 : 0];
//定義讀數據
reg [WIDTH - 1 : 0] rd_data;
//寫操作
always @ (posedge wr_clk or negedge wr_rstn) begin
if(!wr_rstn)
wr_ptr <= 0;
else if(wr_en && !fifo_full) begin
fifo[wr_ptr] <= wr_data;
wr_ptr <= wr_ptr + 1;
end
else
wr_ptr <= wr_ptr;
end
//讀操作
always @ (posedge rd_clk or negedge rd_rstn) begin
if(!rd_rstn) begin
rd_ptr <= 0;
rd_data <= 0;
end
else if(rd_en && !fifo_empty) begin
rd_data <= fifo[rd_ptr];
rd_ptr <= rd_ptr + 1;
end
else
rd_ptr <= rd_ptr;
end
//定義讀寫指針格雷碼
wire [$clog2(DEPTH) : 0] wr_ptr_g;
wire [$clog2(DEPTH) : 0] rd_ptr_g;
//讀寫指針轉換成格雷碼
assign wr_ptr_g = wr_ptr ^ (wr_ptr >>> 1);
assign rd_ptr_g = rd_ptr ^ (rd_ptr >>> 1);
//定義打拍延遲格雷碼
reg [$clog2(DEPTH) : 0] wr_ptr_gr, wr_ptr_grr;
reg [$clog2(DEPTH) : 0] rd_ptr_gr, rd_ptr_grr;
//寫指針同步到讀時鍾域
always @ (posedge rd_clk or negedge rd_rstn) begin
if(!rd_rstn) begin
wr_ptr_gr <= 0;
wr_ptr_grr <= 0;
end
else begin
wr_ptr_gr <= wr_ptr_g;
wr_ptr_grr <= wr_ptr_gr;
end
end
//讀指針同步到寫時鍾域
always @ (posedge wr_clk or