1、異步FIFO簡介及其原理
FIFO是英文First In First Out 的縮寫,是一種先進先出的數(shù)據(jù)緩存器,它與普通存儲器的區(qū)別是沒有外部讀寫地址線,這樣使用起來非常簡單,但缺點就是只能順序?qū)懭霐?shù)據(jù)。
異步FIFO 是指讀寫時鐘不一致,讀寫時鐘是互相獨立的。
1.1 用途
用途1:
跨時鐘域:異步FIFO讀寫分別采用相互異步的不同時鐘。在現(xiàn)代集成電路芯片中,隨著設(shè)計規(guī)模的不斷擴大,一個系統(tǒng)中往往含有數(shù)個時鐘,多時鐘域帶來的一個問題就是,如何設(shè)計異步時鐘之間的接口電路。異步FIFO是這個問題的一種簡便、快捷的解決方案,使用異步FIFO可以在兩個不同時鐘系統(tǒng)之間快速而方便地傳輸實時數(shù)據(jù)。
用途2:
位寬變換:對于不同寬度的數(shù)據(jù)接口也可以用FIFO,例如單片機位8位數(shù)據(jù)輸出,而DSP可能是16位數(shù)據(jù)輸入,在單片機與DSP連接時就可以使用FIFO來達到數(shù)據(jù)匹配的目的。
1.2 結(jié)構(gòu)
由圖可見,異步FIFO的核心部件就是一個 Simple Dual Port RAM ;左右兩邊的長條矩形是地址控制器,負責控制地址自增、將二進制地址轉(zhuǎn)為格雷碼以及解格雷碼;下面的兩對D觸發(fā)器 sync_r2w 和 sync_w2r 是同步器,負責將寫地址同步至讀時鐘域、將讀地址同步至寫時鐘域。
FIFO的常見參數(shù)
FIFO的寬度:即FIFO一次讀寫操作的數(shù)據(jù)位;
FIFO的深度:指的是FIFO可以存儲多少個N位的數(shù)據(jù)(如果寬度為N)。
滿標志:FIFO已滿或?qū)⒁獫M時由FIFO的狀態(tài)電路送出的一個信號,以阻止FIFO的寫操作繼續(xù)向FIFO中寫數(shù)據(jù)而造成溢出(overflow)。
空標志:FIFO已空或?qū)⒁諘r由FIFO的狀態(tài)電路送出的一個信號,以阻止FIFO的讀操作繼續(xù)從FIFO中讀出數(shù)據(jù)而造成無效數(shù)據(jù)的讀出(underflow)。
讀時鐘:讀操作所遵循的時鐘,在每個時鐘沿來臨時讀數(shù)據(jù)。
寫時鐘:寫操作所遵循的時鐘,在每個時鐘沿來臨時寫數(shù)據(jù)。
2、 FIFO的“空”/“滿”檢測
FIFO設(shè)計的關(guān)鍵:產(chǎn)生可靠的FIFO讀寫指針和生成FIFO“空”/“滿”狀態(tài)標志。
此處切記判斷讀空和寫滿時指針的比較方式時不同的,大華面試時就問到了這個問題,一下把我干糊涂了,直接面試涼了。
如上圖所示的同步模塊synchronize to write clk,其作用是把讀時鐘域的讀指針rd_ptr采集到寫時鐘(wr_clk)域,然后和寫指針wr_ptr進行比較從而產(chǎn)生或撤消寫滿標志位wr_full;
同步模塊synchronize to read clk的作用是把寫時鐘域的寫指針wr_ptr采集到讀時鐘域,然后和讀指針rd_ptr進行比較從而產(chǎn)生或撤消讀空標志位rd_empty。
2.1、讀寫指針工作原理
讀指針:總是指向下一個將要被寫入的單元,復位時,指向第一個單元(編號為0)。
寫指針:總是指向當前要被讀出的數(shù)據(jù),復位時,指向第1個單元(編號為0)
FIFO空標志位:
(1)系統(tǒng)復位,讀寫指針全部清零,讀寫指針相等時;
(2)數(shù)據(jù)讀出速率大于寫入速率,讀指針趕上了寫指針,F(xiàn)IFO為空,如圖所示:
FIFO 寫滿標志位的產(chǎn)生
(1)讀寫指針指向了同一地址,但寫指針超前整整一圈,F(xiàn)IFO被寫滿;
2.2、 空滿區(qū)分
為了區(qū)分到底是滿狀態(tài)還是空狀態(tài),可以采用以下方法:
在指針中添加一個額外的位,當寫指針增加并越過最后一個FIFO地址時,就將未用的最高位(MSB)加1,其他位回0.對讀指針也進行同樣的操作。
此時,對于深度為2^n的FIFO,需要的讀寫指針位寬為(n+1)位。如對于深度為8的FIFO,需要采用4bit的計數(shù)器,0000~1000、1001~1111,MSB作為折回標志位,而低3位作為地址指針。
如果兩個指針的MSB不同,其他位相同,說明寫指針比讀指針多折回了一次;如r_addr=0000,而w_addr = 1000,為滿。如果兩個指針的MSB相同,其余位相等,則說明兩個指針折回的次數(shù)相等,說明FIFO為空;
2.3、 二進制FIFO指針的考慮
將一個二進制的計數(shù)值從一個時鐘域同步到另一個時鐘域的時候很容易出現(xiàn)問題,因為采用二進制計數(shù)器時所有位都可能同時變化,在同一個時鐘沿同步多個信號的變化會產(chǎn)生亞穩(wěn)態(tài)問題。而使用格雷碼只有一位變化,因此在兩個時鐘域間同步多個位不會產(chǎn)生問題。所以需要一個二進制到gray碼的轉(zhuǎn)換電路,將地址值轉(zhuǎn)換為相應(yīng)的gray碼,然后將該gray碼同步到另一個時鐘域進行對比,作為空滿狀態(tài)的檢測。
(1)二進制如何轉(zhuǎn)化為格雷碼
二進制數(shù)的最高位保持不變,?后續(xù)位依次與前一位進行異或運算
?
?
assign gray_code = (bin_code>>1) ^ bin_code;
?
?
1
(2) 使用gray碼進行對比,如何判斷“空”與“滿”
使用gray碼解決了一個問題,但同時也帶來另一個問題,即在格雷碼域如何判斷空與滿。
對于“空”的判斷依然依據(jù)二者完全相等(包括MSB);
而對于“滿”的判斷,如下圖,由于gray碼除了MSB外,具有鏡像對稱的特點,當讀指針指向7,寫指針指向8時,除了MSB,其余位皆相同,不能說它為滿。因此不能單純的只檢測最高位了,在gray碼上判斷為滿必須同時滿足以下3條:
(1)wptr和同步過來的rptr的MSB不相等,因為wptr必須比rptr多折回一次。
(2)wptr與rptr的次高位不相等,如上圖位置7和位置15,轉(zhuǎn)化為二進制對應(yīng)的是0111和1111,MSB不同說明多折回一次,111相同代表同一位置。
(3)剩下的其余位完全相等。
這里直接給出結(jié)論:
判斷讀空時?:將寫時鐘域的寫指針同步到讀時鐘,然后與讀時鐘域的讀指針進行比較,每一位都完全相同才判斷為讀空;
判斷寫滿時:需要 寫時鐘域的格雷碼wgray_next 和 被同步到寫時鐘域的讀指針wr2_rp 高兩位不相同,其余各位完全相同;
?
?
1 assign full = (wr_addr_gray == {~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高兩位不同 2 assign empty = ( rd_addr_gray == wr_addr_gray_d2 );
?
?
3、代碼:
代碼思想
二進制指針轉(zhuǎn)換為格雷碼,跨時鐘后進行空滿判斷。讀空是寫時鐘域下的寫指針同步到讀時鐘域下與讀指針進行比較,完全一樣即為讀空。寫滿是讀時鐘域的讀指針同步到寫時鐘域與寫指針比較,高兩位不同低位相同即為寫滿。
?
?
//異步FIFO代碼,關(guān)鍵是判斷空滿信號 module asyn_fifo #( parameter data_width = 16, parameter data_depth = 8, parameter ram_depth = 256 ) ( input rst_n, //寫的四個接口 input wr_clk, input wr_en, input [data_width-1:0]data_in, outputfull, //讀的四個接口 input rd_clk, input rd_en, outputreg [data_width-1:0]data_out, output empty ); reg [data_depth-1:0]wr_adr; reg [data_depth-1:0]rd_adr; reg [data_depth:0]wr_adr_ptr; reg [data_depth:0]rd_adr_ptr; wire [data_depth:0]wr_adr_gray; reg [data_depth:0]wr_adr_gray1; reg [data_depth:0]wr_adr_gray2; wire [data_depth:0]rd_adr_gray; reg [data_depth:0]rd_adr_gray1; reg [data_depth:0]rd_adr_gray2; //dual port ram - write and read,定義讀寫地址 assign wr_adr = wr_adr_ptr[data_depth-1:0]; assign rd_adr = rd_adr_ptr[data_depth-1:0]; //定義存儲空間FIFO integer i; reg [data_width-1:0] ram_fifo [ram_depth-1:0]; always@(posedge wr_clk or negedge rst_n)begin if(!rst_n)begin for(i=0;i> 1) ^ wr_adr_ptr; assign rd_adr_gray = (rd_adr_ptr >> 1) ^ rd_adr_ptr; //gray cdc compare 格雷碼跨時鐘比較指針-------------------------------重點 always@(posedge wr_clk or negedge rst_n)begin if(!rst_n)begin rd_adr_gray1 <= 'd0; rd_adr_gray2 <= 'd0; end else begin rd_adr_gray1 <= rd_adr_gray; rd_adr_gray2 <= rd_adr_gray1; end end always@(posedge rd_clk or negedge rst_n)begin if(!rst_n)begin wr_adr_gray1 <= 'd0; wr_adr_gray2 <= 'd0; end else begin wr_adr_gray1 <= wr_adr_gray; wr_adr_gray2 <= wr_adr_gray1; end end assign empty = (rd_adr_gray == wr_adr_gray2)?1'b1:1'b0; assign full = (wr_adr_gray[data_depth:data_depth-1] = (~(rd_adr_gray2[data_depth:data_depth-1]))) && (wr_adr_gray[data_depth-2:0] == rd_adr_gray2[data_depth-2:0]); endmodule
?
?
仿真測試代碼
?
?
`timescale 1ns / 1ps module asyn_fifo_tb; reg rst_n; reg wr_clk; reg wr_en; reg [15:0] data_in; wire full; reg rd_clk; reg rd_en; wire [15:0] data_out; wire empty; asyn_fifo asyn_fifo_inst ( .rst_n (rst_n), .wr_clk (wr_clk), .wr_en (wr_en), .data_in (data_in), .full (full), .rd_clk (rd_clk), .rd_en (rd_en), .data_out (data_out), .empty (empty) ); initial wr_clk = 0; always#10 wr_clk = ~wr_clk; initial rd_clk = 0; always#30 rd_clk = ~rd_clk; always@(posedge wr_clk or negedge rst_n)begin if(!rst_n) data_in <= 'd0; else if(wr_en) data_in <= data_in + 1'b1; else data_in <= data_in; end initial begin rst_n = 0; wr_en = 0; rd_en = 0; #200; rst_n = 1; wr_en = 1; #20000; wr_en = 0; rd_en = 1; #20000; rd_en = 0; $stop; end endmodule
?
?
可見讀數(shù)據(jù)對寫數(shù)據(jù)實現(xiàn)了多比特跨時鐘域處理,這是異步FIFO的一個常用用途。
4、重要補充
關(guān)于異步FIFO的關(guān)鍵技術(shù),有兩個,一個是格雷碼減小亞穩(wěn)態(tài),另一個是指針信號跨異步時鐘域的傳遞。
我在自己寫異步FIFO的時候也很疑惑,地址指針在同步化的時候,肯定會產(chǎn)生至少兩個周期的延遲,如果是從快時鐘域到慢時鐘域,快時域的地址指針并不能都被慢時域的時鐘捕獲,同步后的指針比起實際的指針延遲會更大。如果以此來產(chǎn)生fifo_empty和fifo_full 信號會非常不準器。
查找資料和仿真后發(fā)現(xiàn),數(shù)字電路的世界真的很神奇,還有很多的東西需要去學習。非常巧妙,F(xiàn)IFO中的一個潛在的條件是write_ptr總是大于或者等于read_ptr;分為兩種情況,寫快讀慢和寫慢讀快。
1.在寫時鐘大于讀時鐘時,產(chǎn)生fifo_empty信號,需要將write_ptr同步到讀時鐘域,寫指針會有延時,可能比實際的寫地址要小,如果不滿足fifo_empty的產(chǎn)生條件,沒問題。如果滿足fifo_empty的觸發(fā)條件,說明此時同步后的write_ptr = read_ptr,即實際的write_ptr >= read_ptr,最壞的情況就是write_ptr > read_ptr,像這種FIFO非空而產(chǎn)生空標志信號的情況稱為“虛空”,但是也并不影響FIFO的功能。
2.在寫時鐘大于讀時鐘時,產(chǎn)生fifo_full信號,需要將read_ptr同步到寫時鐘域,讀指針會有延時,可能比實際的讀地址要小,如果不滿足fifo_full的產(chǎn)生條件,沒問題。如果滿足fifo_full的觸發(fā)條件,說明此時同步后的read_ptr == write_ptr - fifo_depth,即實際的read_ptr >= read_ptr - fifo_depth,最壞的情況就是read_ptr > read_ptr - fifo_depth,像這種FIFO非滿而產(chǎn)生滿標志信號的情況稱為“虛滿”,但是也并不影響FIFO的功能。
寫慢讀快的情況也同上,并沒有大的差異,不再分析。
關(guān)于格雷碼減小亞穩(wěn)態(tài),如果讀寫時鐘差距過大,從快時鐘域同步到慢時鐘域的信號,時鐘捕獲的相鄰兩個數(shù)據(jù)變化并不是只有一個bit位的改變,可能導致格雷碼失去原來的意義。
編輯:黃飛
?
評論
查看更多