FIFO即First In First Out,是一種先進(jìn)先出數(shù)據(jù)存儲、緩沖器峡眶,我們知道一般的存儲器是用外部的讀寫地址來進(jìn)行讀寫,而FIFO這種存儲器的結(jié)構(gòu)并不需要外部的讀寫地址而是通過自動的加一操作來控制讀寫批旺,這也就決定了FIFO只能順序的讀寫數(shù)據(jù)幌陕。下面我們就介紹一下同步FIFO和異步FIFO。
1汽煮、FIFO分類
同步FIFO搏熄,讀和寫應(yīng)用同一個時鐘。它的作用一般是做交互數(shù)據(jù)的一個緩沖暇赤,也就是說它的主要作用就是一個buffer心例。
異步FIFO,讀寫應(yīng)用不同的時鐘鞋囊,它有兩個主要的作用止后,一個是實現(xiàn)數(shù)據(jù)在不同時鐘域進(jìn)行傳遞,另一個作用就是實現(xiàn)不同數(shù)據(jù)寬度的數(shù)據(jù)接口溜腐。
2译株、FIFO的主要參數(shù)
同步FIFO和異步FIFO略有不同,下面的參數(shù)適用于兩者挺益。
- 寬度歉糜,用參數(shù)FIFO_data_size表示,也就是FIFO存儲的數(shù)據(jù)寬度望众;
- 深度匪补,用參數(shù)FIFO_addr_size表示伞辛,也就是地址的大小,也就是說能存儲多少個數(shù)據(jù)夯缺;
- 滿標(biāo)志蚤氏,full,當(dāng)FIFO中的數(shù)據(jù)滿了以后將不再能進(jìn)行數(shù)據(jù)的寫入;
- 空標(biāo)志踊兜,empty竿滨,當(dāng)FIFO為空的時候?qū)⒉荒苓M(jìn)行數(shù)據(jù)的讀出;
- 寫地址润文,w_addr姐呐,由自動加一生成,將數(shù)據(jù)寫入該地址典蝌;
- 讀地址曙砂,r_addr,由自動加一生成骏掀,將該地址上的數(shù)據(jù)讀出鸠澈;
同步FIFO和異步FIFO的最主要的不同就體現(xiàn)在空滿標(biāo)志產(chǎn)生的方式上,由此引出兩者一些不同的參數(shù)截驮。
同步FIFO
- 時鐘笑陈,clk,rst葵袭,讀寫應(yīng)用同一個時鐘涵妥;
- 計數(shù)器,count坡锡,用計數(shù)器來進(jìn)行空滿標(biāo)志的判斷蓬网;
異步FIFO
- 時鐘,clk_w鹉勒,rst_w帆锋,clk_r,rst_r禽额,讀寫應(yīng)用不同的時鐘锯厢;
- 指針,w_pointer_gray脯倒,r_pointer_gray实辑,用指針來判斷空滿標(biāo)識;
- 同步指針藻丢,w_pointer_gray_sync徙菠,r_pointer_gray_sync,指針的同步操作郁岩,用來做對比產(chǎn)生空滿標(biāo)志符婿奔;
3、同步FIFO
FIFO主要的設(shè)計難點在于如何產(chǎn)生空滿標(biāo)志问慎,在同步FIFO中萍摊,我們定義一個計數(shù)器,當(dāng)計數(shù)器的值為0時如叼,產(chǎn)生空標(biāo)志冰木,當(dāng)計數(shù)器的值為FIFO的深度時,產(chǎn)生滿標(biāo)志笼恰∮环校基于以上的思想,可以將同步FIFO劃分為以下幾個模塊:write社证、read逼龟、count、RAM追葡。
3.1 模塊劃分
同步FIFO主要劃分為四個模塊腺律,RAM模塊是用來讀取和寫入數(shù)據(jù);write模塊是用來產(chǎn)生寫地址宜肉;read模塊是用來產(chǎn)生讀地址匀钧;count模塊是用來產(chǎn)生空滿標(biāo)志符,每寫入一位數(shù)谬返,count加一之斯,每讀出一位數(shù),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產(chǎn)生空滿標(biāo)志符
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的設(shè)計難點在于空滿標(biāo)志符的產(chǎn)生,由于異步FIFO的讀寫是用不同的時鐘來控制的翰蠢,所以不能采用計數(shù)器的方法來產(chǎn)生空滿標(biāo)志符项乒,就好像同一個變量不能再兩個always塊里賦值一樣,所以我們必須尋求新的方法來產(chǎn)生空滿標(biāo)志符梁沧。
4.1 空滿標(biāo)志
我們知道FIFO的狀態(tài)是滿還是空檀何,他們的相同的判斷條件都是w_addr=r_addr,但到底是空還是滿我們還不能確定廷支。在這里介紹一種方法來判斷空滿狀態(tài)频鉴。我們設(shè)定一個指針r_pointer_bin,w_pointer_bin恋拍,寬度為[FIFO_addr_size:0]垛孔,也就是說比傳統(tǒng)的地址多一位,我們就用這多出來的一位做空滿判斷施敢。
- 如果是滿狀態(tài)的話周荐,也就是說w_pointer_bin比r_pointer_bin多走了一圈狭莱,反應(yīng)在數(shù)值上就是w_pointer_bin和r_pointer_bin的最高位不相同。
- 如果是空狀態(tài)的話概作,也就是說w_pointer_bin和r_pointer_bin的路徑相同腋妙,反應(yīng)在數(shù)值上就是w_pointer_bin和r_pointer_bin的每一位相等。
如下例子所示:
FIFO_addr_size=2;FIFO_data_size=3;
4.2 格雷碼
將一個時鐘域上的指針r_pointer_bin/w_pointer_bin同步到另一個時鐘域讯榕,如果數(shù)據(jù)用二進(jìn)制的方式進(jìn)行同步的話就會出現(xiàn)多位數(shù)據(jù)同時跳變的問題骤素,比如3'b011到3'b100即3到4跳變會引起多位數(shù)據(jù)的改變,這樣會大大增加出錯的概率愚屁。Gray 碼就很好的解決了上述問題济竹,gray碼相鄰數(shù)據(jù)只有一位跳變,這樣就大大降低了數(shù)據(jù)出錯的概率霎槐。下面 以一個例子介紹一下二進(jìn)制碼向格雷碼的轉(zhuǎn)化的算法送浊。
在不同時鐘域進(jìn)行數(shù)據(jù)交換的時候我們一般采用格雷碼的數(shù)據(jù)形式進(jìn)行數(shù)據(jù)傳遞,這樣能很大程度上降低出錯的概率栽燕。
引入格雷碼同時也引入一個問題罕袋,就是數(shù)據(jù)空滿標(biāo)志的判斷不再是二進(jìn)制時候的判斷標(biāo)準(zhǔn)。
- 如果是空狀態(tài)的話碍岔,無可厚非浴讯,仍然是要滿足r_pointer_gray和w_pointer_gray每一位都相等。
- 如果是滿狀態(tài)的話蔼啦,我們以二進(jìn)制為例榆纽,應(yīng)該滿足r_pointer_bin=3'b111,w_pointer_bin=3'b011捏肢,相對應(yīng)的格雷碼應(yīng)該滿足r_pointer_gray=3'b100奈籽,w_pointer_gray=3'b010,通俗來講鸵赫,滿狀態(tài)要滿足r_pointer_gray和w_pointer_gray的高位和次高位相反衣屏,其余各位相等。
同時由于格雷碼的引入辩棒,使得FIFO的深度只能是2的冪次方狼忱。
4.3 數(shù)據(jù)同步
我們知道滿狀態(tài)以后數(shù)據(jù)就不能進(jìn)行寫入,空狀態(tài)以后數(shù)據(jù)就不能進(jìn)行讀出一睁。由此钻弄,我們在write模塊進(jìn)行滿狀態(tài)的判斷,在read模塊進(jìn)行空狀態(tài)的判斷者吁。
- 在滿狀態(tài)的判斷時窘俺,我們要用到r_pointer_gray,為了避免亞穩(wěn)態(tài)复凳,選擇兩級D觸發(fā)器相串聯(lián)構(gòu)成的同步模塊來傳送r_pointer_gray瘤泪,最后用r_pointer_gray_sync和w_pointer_gray相比較產(chǎn)生full信號灶泵。
- 在空狀態(tài)的判斷時,同理我們要用w_pointer_gray_sync和r_pointer_gray相比較產(chǎn)生empty信號对途。
兩拍延時的數(shù)據(jù)同步對空滿標(biāo)志產(chǎn)生的影響
由此信號r_pointer_gray經(jīng)過兩級D觸發(fā)器丘逸,就會有兩拍的延時形成r_pointer_gray_sync信號,所以在進(jìn)行比較的時候就不是實時的r_pointer_gray與w_pointer_gray進(jìn)行比較掀宋,而是兩拍之前的r_pointer_gray即r_pointer_gray_sync與此刻的w_pointer_gray進(jìn)行比較。那么問題就來了這與我們的本意其實是不相符的仲锄,其實是這樣的劲妙,這是一種最壞情況的考慮,將r_pointer_gray_sync與w_pointer_gray相比較是為了產(chǎn)生full信號儒喊,在用于數(shù)據(jù)同步的這兩拍里面有可能再進(jìn)行讀操作镣奋,所以用于比較時的讀地址一定小于或等于當(dāng)前的讀地址,就算此刻產(chǎn)生full信號怀愧,其實FIFO有可能還沒有滿侨颈。這也就為設(shè)計留了一些設(shè)計的余量。同理芯义,就算有empty信號的產(chǎn)生哈垢,F(xiàn)IFO有可能還有數(shù)據(jù)。這種留余量的設(shè)計在實際的工程項目中是很常見的扛拨。
4.4 模塊的劃分
異步FIFO將模塊劃分為4個部分耘分,RAM、write_full绑警、read_empty求泰、synchronization。RAM根據(jù)讀寫地址進(jìn)行數(shù)據(jù)的寫入和讀出计盒,write_full根據(jù)clk_w產(chǎn)生寫地址和full信號渴频,read_empty根據(jù)clk_r產(chǎn)生讀地址和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實際工作中聊替,其數(shù)據(jù)的滿/空標(biāo)志可以控制數(shù)據(jù)的繼續(xù)寫入或讀出。在一個具體的應(yīng)用中也不可能由一些參數(shù)算數(shù)精確的所需FIFO深度為多少培廓,這在寫速度大于讀速度的理想狀態(tài)下是可行的惹悄,但在實際中用到的FIFO深度往往要大于計算值。一般來說根據(jù)電路的具體情況肩钠,在兼顧系統(tǒng)性能和FIFO成本的情況下估算一個大概的寬度和深度就可以了泣港。而對于寫速度慢于讀速度的應(yīng)用暂殖,F(xiàn)IFO的深度要根據(jù)讀出的數(shù)據(jù)結(jié)構(gòu)和讀出數(shù)據(jù)的由那些具體的要求來確定。下面我們以一道簡單的題目來估算一下FIFO的深度当纱。
一個8bit寬的異步FIFO呛每,輸入時鐘為100MHz,輸出時鐘為95MHz坡氯,設(shè)一個package為4Kbit晨横,且兩個package之間的發(fā)送間距足夠大。問異步FIFO的深度箫柳。
解答:8bit位寬的異步FIFO手形,一個package的大小為4※1024/8=512Word,100MHz的輸入時鐘悯恍,傳送一個Word需要的時間為1/100MHz库糠,則發(fā)送一個package需要的時間T=512/100MHz,95MHz的輸出時鐘涮毫,接受一個Word需要的時間為1/95MHz瞬欧,發(fā)送一個package的時間所能接受的數(shù)據(jù)量為(512※95)/100word=486.4word,所以FIFO的深度至少為512-486.4=25.6=26罢防。