FIFO即First In First Out,是一種先進先出數據存儲、緩沖器,我們知道一般的存儲器是用外部的讀寫地址來進行讀寫,而FIFO這種存儲器的結構並不需要外部的讀寫地址而是通過自動的加一操作來控制讀寫,這也就決定了FIFO只能順序的讀寫數據。下面我們就介紹一下同步FIFO和異步FIFO。
1、FIFO分類
同步FIFO,讀和寫應用同一個時鍾。它的作用一般是做交互數據的一個緩沖,也就是說它的主要作用就是一個buffer。
異步FIFO,讀寫應用不同的時鍾,它有兩個主要的作用,一個是實現數據在不同時鍾域進行傳遞,另一個作用就是實現不同數據寬度的數據接口。
2、FIFO的主要參數
同步FIFO和異步FIFO略有不同,下面的參數適用於兩者。
- 寬度,用參數FIFO_data_size表示,也就是FIFO存儲的數據寬度;
- 深度,用參數FIFO_addr_size表示,也就是地址的大小,也就是說能存儲多少個數據;
- 滿標志,full,當FIFO中的數據滿了以后將不再能進行數據的寫入;
- 空標志,empty,當FIFO為空的時候將不能進行數據的讀出;
- 寫地址,w_addr,由自動加一生成,將數據寫入該地址;
- 讀地址,r_addr,由自動加一生成,將該地址上的數據讀出;
同步FIFO和異步FIFO的最主要的不同就體現在空滿標志產生的方式上,由此引出兩者一些不同的參數。
同步FIFO
- 時鍾,clk,rst,讀寫應用同一個時鍾;
- 計數器,count,用計數器來進行空滿標志的判斷;
異步FIFO
- 時鍾,clk_w,rst_w,clk_r,rst_r,讀寫應用不同的時鍾;
- 指針,w_pointer_gray,r_pointer_gray,用指針來判斷空滿標識;
- 同步指針,w_pointer_gray_sync,r_pointer_gray_sync,指針的同步操作,用來做對比產生空滿標志符;
3、同步FIFO
FIFO主要的設計難點在於如何產生空滿標志,在同步FIFO中,我們定義一個計數器,當計數器的值為0時,產生空標志,當計數器的值為FIFO的深度時,產生滿標志。基於以上的思想,可以將同步FIFO划分為以下幾個模塊:write、read、count、RAM。
3.1 模塊划分
同步FIFO主要划分為四個模塊,RAM模塊是用來讀取和寫入數據;write模塊是用來產生寫地址;read模塊是用來產生讀地址;count模塊是用來產生空滿標志符,每寫入一位數,count加一,每讀出一位數,count減一。
下面是各個模塊的連接框圖:
3.2 同步FIFO代碼
源文件
module FIFO_sync(
clk,
rst,
w_en,
r_en,
data_in,
data_out,
count,
full,
empty
);
parameter FIFO_data_size=3,
FIFO_addr_size=2;
input clk,rst;
input w_en,r_en;
input[FIFO_data_size-1:0] data_in;
output[FIFO_data_size-1:0] data_out;
output full,empty;
output[FIFO_addr_size:0]count;
reg [FIFO_data_size-1:0] data_out;
reg [FIFO_addr_size:0]count;
reg [FIFO_addr_size-1:0]w_addr,r_addr;
reg [FIFO_data_size-1:0]mem[{FIFO_addr_size{1'b1}}:0];
integer i;
//memory的初始化以及寫操作
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
w_addr<=0;
for(i=0;i<={FIFO_addr_size{1'b1}};i=i+1)
mem[i]<={FIFO_data_size{1'b0}};
end
else if(w_en&(~full))
begin
mem[w_addr]<=data_in;
w_addr<=w_addr+1;
end
end
//讀操作
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
data_out<={(FIFO_data_size-1){1'b0}};
r_addr<=0;
end
else if(r_en&(~empty))
begin
data_out<=mem[r_addr];
r_addr<=r_addr+1;
end
end
//count產生空滿標志符
always@(posedge clk or negedge rst)
begin
if(!rst)
count<=0;
else if(((w_en)&(~full))&(~((r_en)&(~empty))))
count<=count+1;
else if(((r_en)&(~empty))&(~((w_en)&(~full))))
count<=count-1;
end
assign empty=(count==0);
assign full=(count=={FIFO_addr_size{1'b1}}+1);
endmodule
測試代碼
`timescale 1ns/1ns
module FIFO_sync_top;
reg clk,rst,w_en,r_en;
reg[2:0]data_in;
wire[2:0]count;
wire[2:0]dtat_out;
reg[2:0]i;
initial
begin
clk=0;
rst=1;
data_in=3'b000;
w_en=0;
r_en=0;
#25
rst=0;
#50
rst=1;
#25
w_en=1;
#100
r_en=1;
#100
w_en=0;
r_en=0;
#100
w_en=1;
#400
r_en=1;
end
initial
begin
for(i=0;i<=50;i=i+1)
#100 data_in=i;
end
always
#50 clk=~clk;
FIFO_sync #(.FIFO_data_size(3),.FIFO_addr_size(2)) ut(
.clk(clk),
.rst(rst),
.data_in(data_in),
.data_out(data_out),
.w_en(w_en),
.r_en(r_en),
.count(count),
.full(full),
.empty(empty)
);
endmodule
4、異步FIFO
異步FIFO的設計難點在於空滿標志符的產生,由於異步FIFO的讀寫是用不同的時鍾來控制的,所以不能采用計數器的方法來產生空滿標志符,就好像同一個變量不能再兩個always塊里賦值一樣,所以我們必須尋求新的方法來產生空滿標志符。
4.1 空滿標志
我們知道FIFO的狀態是滿還是空,他們的相同的判斷條件都是w_addr=r_addr,但到底是空還是滿我們還不能確定。在這里介紹一種方法來判斷空滿狀態。我們設定一個指針r_pointer_bin,w_pointer_bin,寬度為[FIFO_addr_size:0],也就是說比傳統的地址多一位,我們就用這多出來的一位做空滿判斷。
- 如果是滿狀態的話,也就是說w_pointer_bin比r_pointer_bin多走了一圈,反應在數值上就是w_pointer_bin和r_pointer_bin的最高位不相同。
- 如果是空狀態的話,也就是說w_pointer_bin和r_pointer_bin的路徑相同,反應在數值上就是w_pointer_bin和r_pointer_bin的每一位相等。
如下例子所示:
FIFO_addr_size=2;FIFO_data_size=3;
4.2 格雷碼
將一個時鍾域上的指針r_pointer_bin/w_pointer_bin同步到另一個時鍾域,如果數據用二進制的方式進行同步的話就會出現多位數據同時跳變的問題,比如3'b011到3'b100即3到4跳變會引起多位數據的改變,這樣會大大增加出錯的概率。Gray 碼就很好的解決了上述問題,gray碼相鄰數據只有一位跳變,這樣就大大降低了數據出錯的概率。下面 以一個例子介紹一下二進制碼向格雷碼的轉化的算法。
在不同時鍾域進行數據交換的時候我們一般采用格雷碼的數據形式進行數據傳遞,這樣能很大程度上降低出錯的概率。
引入格雷碼同時也引入一個問題,就是數據空滿標志的判斷不再是二進制時候的判斷標准。
- 如果是空狀態的話,無可厚非,仍然是要滿足r_pointer_gray和w_pointer_gray每一位都相等。
- 如果是滿狀態的話,我們以二進制為例,應該滿足r_pointer_bin=3'b111,w_pointer_bin=3'b011,相對應的格雷碼應該滿足r_pointer_gray=3'b100,w_pointer_gray=3'b010,通俗來講,滿狀態要滿足r_pointer_gray和w_pointer_gray的高位和次高位相反,其余各位相等。
同時由於格雷碼的引入,使得FIFO的深度只能是2的冪次方。
4.3 數據同步
我們知道滿狀態以后數據就不能進行寫入,空狀態以后數據就不能進行讀出。由此,我們在write模塊進行滿狀態的判斷,在read模塊進行空狀態的判斷。
- 在滿狀態的判斷時,我們要用到r_pointer_gray,為了避免亞穩態,選擇兩級D觸發器相串聯構成的同步模塊來傳送r_pointer_gray,最后用r_pointer_gray_sync和w_pointer_gray相比較產生full信號。
- 在空狀態的判斷時,同理我們要用w_pointer_gray_sync和r_pointer_gray相比較產生empty信號。
兩拍延時的數據同步對空滿標志產生的影響
由此信號r_pointer_gray經過兩級D觸發器,就會有兩拍的延時形成r_pointer_gray_sync信號,所以在進行比較的時候就不是實時的r_pointer_gray與w_pointer_gray進行比較,而是兩拍之前的r_pointer_gray即r_pointer_gray_sync與此刻的w_pointer_gray進行比較。那么問題就來了這與我們的本意其實是不相符的,其實是這樣的,這是一種最壞情況的考慮,將r_pointer_gray_sync與w_pointer_gray相比較是為了產生full信號,在用於數據同步的這兩拍里面有可能再進行讀操作,所以用於比較時的讀地址一定小於或等於當前的讀地址,就算此刻產生full信號,其實FIFO有可能還沒有滿。這也就為設計留了一些設計的余量。同理,就算有empty信號的產生,FIFO有可能還有數據。這種留余量的設計在實際的工程項目中是很常見的。
4.4 模塊的划分
異步FIFO將模塊划分為4個部分,RAM、write_full、read_empty、synchronization。RAM根據讀寫地址進行數據的寫入和讀出,write_full根據clk_w產生寫地址和full信號,read_empty根據clk_r產生讀地址和empty信號,synchronization用於同步w_pointer_gray到讀時鍾域或者同步r_pointer_gray到寫時鍾域。
下面是各個模塊的連接圖:
4.5 異步FIFO代碼
源文件
-
頂層文件
module FIFO_async( clk_w, rst_w, w_en, clk_r, rst_r, r_en, data_in, data_out, empty, full ); parameter FIFO_data_size=6; parameter FIFO_addr_size=5; input clk_w,rst_w,w_en; input clk_r,rst_r,r_en; input[FIFO_data_size-1:0]data_in; output[FIFO_data_size-1:0]data_out; output empty,full; wire[FIFO_addr_size:0]r_pointer_gray_sync,w_pointer_gray_sync; wire[FIFO_addr_size:0]r_pointer_gray,w_pointer_gray; wire[FIFO_addr_size-1:0]w_addr,r_addr; RAM #(FIFO_data_size,FIFO_addr_size) I0(.clk_w(clk_w), .rst_w(rst_w), .clk_r(clk_r), .rst_r(rst_r), .full(full), .empty(empty), .w_en(w_en), .r_en(r_en), .w_addr(w_addr), .r_addr(r_addr), .data_in(data_in), .data_out(data_out)); synchronization #(FIFO_addr_size) I1(.clk(clk_r), .rst(rst_r), .din(w_pointer_gray), .dout(w_pointer_gray_sync)); synchronization #(FIFO_addr_size) I2(.clk(clk_w), .rst(rst_w), .din(r_pointer_gray), .dout(r_pointer_gray_sync)); write_full #(FIFO_addr_size) I3(.clk_w(clk_w), .rst_w(rst_w), .w_en(w_en), .r_pointer_gray_sync(r_pointer_gray_sync), .w_pointer_gray(w_pointer_gray), .w_addr(w_addr), .full(full)); read_empty #(FIFO_addr_size) I4(.clk_r(clk_r), .rst_r(rst_r), .r_en(r_en), .w_pointer_gray_sync(w_pointer_gray_sync), .r_pointer_gray(r_pointer_gray), .r_addr(r_addr), .empty(empty)); endmodule
RAM
module RAM(
clk_w,
rst_w,
clk_r,
rst_r,
full,
empty,
w_en,
r_en,
r_addr,
w_addr,
data_in,
data_out
);
parameter FIFO_data_size=3,
FIFO_addr_size=2;
input clk_w,rst_w;
input clk_r,rst_r;
input w_en,r_en;
input full,empty;
input [FIFO_addr_size-1:0]w_addr,r_addr;
input [FIFO_data_size-1:0]data_in;
output[FIFO_data_size-1:0]data_out;
reg[FIFO_data_size-1:0]data_out;
reg[FIFO_data_size-1:0]mem[{FIFO_addr_size{1'b1}}:0];
integer i;
always@(posedge clk_w or negedge rst_w)
begin
if(!rst_w)
for(i=1;i<=FIFO_data_size;i=i+1)
mem[i]<={FIFO_data_size{1'b0}};
else if((w_en==1)&&(full==0))
mem[w_addr]<=data_in;
end
always@(posedge clk_r or negedge rst_r)
begin
if(!rst_r)
data_out<={(FIFO_data_size-1){1'b0}};
else if((r_en==1)&&(empty==0))
data_out<=mem[r_addr];
end
endmodule
write_full
module write_full(
clk_w,
rst_w,
w_en,
r_pointer_gray_sync,
//w_pointer_bin,
w_pointer_gray,
w_addr,
full
);
parameter FIFO_addr_size=2;
input clk_w,rst_w,w_en;
input [FIFO_addr_size:0]r_pointer_gray_sync;
output full;
output [FIFO_addr_size-1:0]w_addr;
output [FIFO_addr_size:0]w_pointer_gray;
reg [FIFO_addr_size:0]w_pointer_bin;
wire [FIFO_addr_size:0]w_pointer_gray;
wire [FIFO_addr_size-1:0]w_addr;
always@(posedge clk_w or negedge rst_w)
begin
if(!rst_w)
w_pointer_bin<={(FIFO_addr_size){1'b0}};
else if((w_en==1)&&(full==0))
w_pointer_bin<=w_pointer_bin+1;
end
assign w_pointer_gray=(w_pointer_bin>>1)^w_pointer_bin;
assign w_addr=w_pointer_bin[FIFO_addr_size-1:0];
assign full=w_pointer_gray=={~r_pointer_gray_sync[FIFO_addr_size:FIFO_addr_size-1],r_pointer_gray_sync[FIFO_addr_size-2:0]}? 1:0;
endmodule
read_empty
module read_empty(
clk_r,
rst_r,
r_en,
w_pointer_gray_sync,
// r_pointer_bin,
r_pointer_gray,
r_addr,
empty
);
parameter FIFO_addr_size=2;
input clk_r,rst_r,r_en;
input [FIFO_addr_size:0]w_pointer_gray_sync;
output empty;
output [FIFO_addr_size-1:0]r_addr;
output [FIFO_addr_size:0]r_pointer_gray;
reg [FIFO_addr_size:0]r_pointer_bin;
wire [FIFO_addr_size:0]r_pointer_gray;
wire [FIFO_addr_size-1:0]r_addr;
always@(posedge clk_r or negedge rst_r)
begin
if(!rst_r)
r_pointer_bin<={(FIFO_addr_size){1'b0}};
else if((r_en==1)&&(empty==0))
r_pointer_bin<=r_pointer_bin+1;
end
assign r_pointer_gray=(r_pointer_bin>>1)^r_pointer_bin;
assign r_addr=r_pointer_bin[FIFO_addr_size-1:0];
assign empty=r_pointer_gray==w_pointer_gray_sync?1:0;
endmodule
synchroization
module synchronization(
clk,
rst,
din,
dout
);
parameter FIFO_addr_size=2;
input clk,rst;
input[FIFO_addr_size:0] din;
output[FIFO_addr_size:0] dout;
reg[FIFO_addr_size:0] dout;
reg [FIFO_addr_size:0] dout1;
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
dout<={(FIFO_addr_size+1){1'b0}};
dout1<={(FIFO_addr_size+1){1'b0}};
end
else
begin
dout1<=din;
dout<=dout1;
end
end
endmodule
測試文件
`timescale 1ns/1ns
module FIFO_async_top;
parameter FIFO_data_size=3,
FIFO_addr_size=2;
reg clk_r,rst_r,w_en,r_en,clk_w,rst_w;
reg[FIFO_data_size-1:0]data_in;
wire[FIFO_addr_size-1:0]data_out;
wire empty,full;
reg[FIFO_data_size-1:0]i;
initial
begin
clk_w=0;
rst_w=1;
data_in={FIFO_data_size{1'b0}};
#15
rst_w=0;
#20
rst_w=1;
end
initial
begin
clk_r=0;
rst_r=1;
r_en=0;
#25
rst_r=0;
#50
rst_r=1;
end
initial
begin
w_en=0;
#450
w_en=1;
#400
w_en=0;
#750
w_en=1;
end
initial
begin
r_en=0;
#900
r_en=1;
#400
r_en=0;
#300
r_en=1;
end
initial
begin
for(i=0;i<=50;i=i+1)
#100 data_in=i;
end
always
#25 clk_w=~clk_w;
always
#50 clk_r=~clk_r;
FIFO_async #(.FIFO_data_size(FIFO_data_size),.FIFO_addr_size(FIFO_addr_size))
u1(.clk_w(clk_w),
.rst_w(rst_w),
.w_en(w_en),
.clk_r(clk_r),
.rst_r(rst_r),
.r_en(r_en),
.data_in(data_in),
.data_out(data_out),
.empty(empty),
.full(full)
);
endmodule
5、FIFO的深度計算
其實FIFO的深度可大可小,並沒有一個具體的公式能夠精確計算出FIFO深度的大小。在FIFO實際工作中,其數據的滿/空標志可以控制數據的繼續寫入或讀出。在一個具體的應用中也不可能由一些參數算數精確的所需FIFO深度為多少,這在寫速度大於讀速度的理想狀態下是可行的,但在實際中用到的FIFO深度往往要大於計算值。一般來說根據電路的具體情況,在兼顧系統性能和FIFO成本的情況下估算一個大概的寬度和深度就可以了。而對於寫速度慢於讀速度的應用,FIFO的深度要根據讀出的數據結構和讀出數據的由那些具體的要求來確定。下面我們以一道簡單的題目來估算一下FIFO的深度。
一個8bit寬的異步FIFO,輸入時鍾為100MHz,輸出時鍾為95MHz,設一個package為4Kbit,且兩個package之間的發送間距足夠大。問異步FIFO的深度。
解答:8bit位寬的異步FIFO,一個package的大小為4※1024/8=512Word,100MHz的輸入時鍾,傳送一個Word需要的時間為1/100MHz,則發送一個package需要的時間T=512/100MHz,95MHz的輸出時鍾,接受一個Word需要的時間為1/95MHz,發送一個package的時間所能接受的數據量為(512※95)/100word=486.4word,所以FIFO的深度至少為512-486.4=25.6=26。