1 異步FIFO結構
在上篇文章中我們給出了FIFO的基本接口圖
并且指出,該圖適用于所有的FIFO,這次我們先看看異步FIFO內(nèi)部的大體框圖
異步FIFO主要由五部分組成:寫控制端、讀控制端、FIFO Memory和兩個時鐘同步端
寫控制端用于判斷是否可以寫入數(shù)據(jù)
讀控制端用于判斷是否可以讀取數(shù)據(jù)
FIFO Memory用于存儲數(shù)據(jù)
兩個時鐘同步端用于將讀寫時鐘進行同步處理
介紹完內(nèi)部結構,我們在和基本接口圖做個聯(lián)動
剛才說過,讀/寫控制端用于判斷能否寫入/讀取數(shù)據(jù),判斷能否寫入/讀取數(shù)據(jù)關鍵在于:
- 寫操作時,寫使能有效且FIFO未滿
- 讀操作時,讀使能有效且FIFO未空
因此兩個使能信號和空滿判斷信號都連接到控制端上
最后我們再加上時鐘信號和復位信號
這便是完整的異步FIFO簡化框圖
2 空滿判斷
在同步FIFO篇中,我們給出了兩個判斷空滿狀態(tài)的圖
并且也有指出,讀空狀態(tài)可以理解為 讀地址指針追上寫地址指針 ,寫滿狀態(tài)可以理解為寫地址指針再次追上讀地址指針
在同步FIFO中,因為讀寫都是在同一個時鐘信號下進行的,因此兩個地址指針可以直接進行比較
但在異步FIFO中,讀寫是在不同的時鐘信號下進行的,因此在進行比較之前,應當先進行跨時鐘與同步
在時鐘同步之前,我們應當先將二進制地址轉(zhuǎn)換為格雷碼,因為格雷碼相鄰的兩個狀態(tài)之間,只有1 bit數(shù)據(jù)發(fā)生翻轉(zhuǎn)
下面給出二進制與格雷碼的對照圖
上面也有說到,讀指針追上寫指針是讀空,寫指針再次追上讀指針是寫滿,為了便于理解,我們做一個環(huán)形圖
假設內(nèi)圈為讀,外圈為寫,讀空時
是讀寫指針應當指向同一個地址,就像這樣
此時,讀地址應當和寫地址完全相同,就以0010為例,0010的格雷碼為0011,可以看出對于讀空狀態(tài),無論是二進制還是格雷碼均是所有位都相同
寫滿
和讀空略有不同,應當是下面這樣
細心的小伙伴應該可以發(fā)現(xiàn),上面在提到寫滿時,說的是寫指針再次追上讀指針,也就是說,寫滿時,寫指針比讀指針多走一圈,為了便于區(qū)分,將地址位寬從3 bit拓寬到4 bit,因此此時的寫指針地址可以認為是1010
1010的格雷碼是1111, 0010的格雷碼是0011,對比兩個格雷碼是不是可以發(fā)現(xiàn),此時 高兩位相反,低兩位相同 ,這便是格雷碼下
寫滿的判斷條件
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中,常用的同步方法是兩級同步打拍延遲,同步地址指針的大致過程如下:
寫操作時,先將寫地址指針轉(zhuǎn)換成格雷碼,然后通過兩級同步(兩級同步在讀時鐘下進行),將寫地址指針同步到讀時鐘域下;讀操作類似
根據(jù)這個過程圖,也可以看出空滿判斷的方式:
- 寫滿在寫時鐘下判斷,將寫地址指針的格雷碼與同步過來的讀地址指針格雷碼進行比較,符合寫滿條件,即FIFO
虛滿
- 讀空在讀時鐘下判斷,將讀地址指針的格雷碼與同步過來的寫地址指針格雷碼進行比較,符合讀空條件,即FIFO
虛空
留意下,這里我說的是虛空/滿
,并不是輸入錯誤喲,具體解釋我放在文章最后,愛思考的朋友現(xiàn)在可以思考一下原因
下面給出時鐘同步的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];
//定義讀數(shù)據(jù)
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;
//讀寫指針轉(zhuǎn)換成格雷碼
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 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
//聲明空滿信號數(shù)據(jù)類型
reg fifo_full;
reg fifo_empty;
//寫滿判斷
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
endmodule
下面是tb
module asy_fifo_tb;
parameter width = 8;
parameter depth = 8;
reg wr_clk, wr_en, wr_rstn;
reg rd_clk, rd_en, rd_rstn;
reg [width - 1 : 0] wr_data;
wire fifo_full, fifo_empty;
wire [width - 1 : 0] rd_data;
//實例化
asy_fifo myfifo (
.wr_clk(wr_clk),
.rd_clk(rd_clk),
.wr_rstn(wr_rstn),
.rd_rstn(rd_rstn),
.wr_en(wr_en),
.rd_en(rd_en),
.wr_data(wr_data),
.rd_data(rd_data),
.fifo_empty(fifo_empty),
.fifo_full(fifo_full)
);
//時鐘
initial begin
rd_clk = 0;
forever #25 rd_clk = ~rd_clk;
end
initial begin
wr_clk = 0;
forever #30 wr_clk = ~wr_clk;
end
//波形顯示
initial begin
$fsdbDumpfile("wave.fsdb");
$fsdbDumpvars(0, myfifo);
$fsdbDumpon();
end
//賦值
initial begin
wr_en = 0;
rd_en = 0;
wr_rstn = 1;
rd_rstn = 1;
#10;
wr_rstn = 0;
rd_rstn = 0;
#20;
wr_rstn = 1;
rd_rstn = 1;
@(negedge wr_clk)
wr_data = {$random}%30;
wr_en = 1;
repeat(7) begin
@(negedge wr_clk)
wr_data = {$random}%30;
end
@(negedge wr_clk)
wr_en = 0;
@(negedge rd_clk)
rd_en = 1;
repeat(7) begin
@(negedge rd_clk);
end
@(negedge rd_clk)
rd_en = 0;
#150;
@(negedge wr_clk)
wr_en = 1;
wr_data = {$random}%30;
repeat(15) begin
@(negedge wr_clk)
wr_data = {$random}%30;
end
@(negedge wr_clk)
wr_en = 0;
#50;
$finish;
end
endmodule
下面貼上運行結果
這里有一點需要說明
藍色框的位置,已經(jīng)開始寫入數(shù)據(jù),但fifo_empty
信號并沒有被拉低,而是在第三個rd_clk
上升沿被拉低,這是因為在判斷FIFO是否讀空時,是在讀時鐘下判斷,并且,進行判斷時,寫地址格雷碼需要在讀時鐘域進行兩次同步,最后進行比較
為了更清晰的解釋,可以將所有的地址指針也加入到波形圖中
注意剛才提到的位置
在位置1,有數(shù)據(jù)寫入,此時wr_ptr
和wr_ptr_g
都發(fā)生了變化,wr_ptr_gr
和wr_ptr_grr
保持不變
在位置2,wr_ptr_gr
變化,wr_ptr_grr
保持不變
在位置3,wr_ptr_grr
才開始發(fā)生變化
在位置4,wr_ptr_grr
和rd_ptr_g
進行比較,判定此時FIFO非空
還有一個有意思的地方
注意wr_ptr_gr
和wr_ptr_grr
,這兩個保持了兩個rd_clk時鐘周期,返回查看完整波形圖的話,這種情況只出現(xiàn)在寫指針的格雷碼
這tb文件中,我們設定的是,rd_clk比wr_clk快,打兩拍同步的方式,慢時鐘域同步到快時鐘域和快時鐘域同步到慢時鐘域處理方式是不同的,后面有時間再做介紹。
有興趣的小伙伴,可以試下如果rd_clk比wr_clk慢,這種情況會出現(xiàn)在rd_ptr_gr
和rd_ptr_grr
5 一個我在面試中被問到的問題
我在面試的時候被問到,如果跨時鐘域同步時,同步過來的是一個亞穩(wěn)態(tài)的數(shù)據(jù),那么FIFO還能否正常工作?
在空滿判斷部分說過,讀空或?qū)憹M可以理解為:
- 讀空時:可以理解為是讀指針在追寫指針
- 寫滿時:可以理解為是寫指針在追讀指針
為了方便理解,我們將其抽象成一個追逐運動,以讀空為例
先確定讀空的判斷, 讀空判斷是在rd_clk下,讀指針的格雷碼與寫指針同步過來的格雷碼進行比較 ,實際上,讀指針追的是wr_ptr_grr
而wr_ptr_grr
又是寫指針留在原地的殘影(寫指針經(jīng)過兩個讀時鐘周期后得來的),在兩個讀時鐘周期的這段時間,寫指針可能會原地不動,也可能繼續(xù)前行,這便是虛空的概念。
評論
查看更多