一、什么是FIFO

FIFO 是 First In First Out 的簡稱。是指在FPGA內(nèi)部用邏輯資源實現(xiàn)的能對數(shù)據(jù)的存儲具有先進(jìn)先出特性的一種緩存器。

 

 

 

FIFO 與 RAM 和 ROM 的區(qū)別是 FIFO 沒有外部讀寫地址線,采取順序?qū)懭霐?shù)據(jù),順序讀出數(shù)據(jù)的方式,其數(shù)據(jù)地址由內(nèi)部讀寫指針自動加1完成。FIFO 使用起來簡單方便,由此帶來的缺點是不能像 RAM 和 ROM 那樣可以由地址線決定讀取或?qū)懭肽硞€指定的地址。

二、為什么要用FIFO

用Verilog實現(xiàn)的電路最終映射到FPGA內(nèi)是由一個個獨立的功能模塊組成的,各個模塊又通過相關(guān)信號關(guān)聯(lián)在一起,當(dāng)存在模塊間處理數(shù)據(jù)速度不同(有快有慢)時,處理得快的模塊就需要等一等處理得慢的模塊,這個等待其實就是緩存的實現(xiàn)。我們可以采用FIFO來解決數(shù)據(jù)的緩存。

打個比方,就像水龍頭放水慢(輸入慢),但我們?nèi)颂崴臅r候是一次處理一桶水(輸出快),所以需要一個水桶作為緩存,等存滿一桶水,再一次被人提走。

又或者像我們?nèi)撕人淮谓右槐?輸入快), 渴的時候喝兩口(輸出慢)。這里,杯子作為緩存。

在現(xiàn)代集成電路芯片中,隨著設(shè)計規(guī)模的不斷擴(kuò)大,一個系統(tǒng)中往往含有數(shù)個時鐘,此時,異步時鐘之間的接口電路的設(shè)計將成為關(guān)鍵。而使用異步FIFO可以在兩個不同時鐘系統(tǒng)之間快速而方便地傳輸實時數(shù)據(jù)。

 

 

FIFO的應(yīng)用場景一般有數(shù)據(jù)緩存、協(xié)議處理、串并轉(zhuǎn)換、跨時鐘域數(shù)據(jù)處理。

三、FIFO分類

FIFO根據(jù)讀寫時鐘是否為同一時鐘分為同步FIFO和異步FIFO。

 

同步FIFO是指讀時鐘和寫時鐘為同一個時鐘,在時鐘沿來臨時可同時發(fā)生讀寫操作。

異步FIFO是指讀寫時鐘不一致,讀寫時鐘是互相獨立的2個時鐘。

同步FIFO在實際應(yīng)用中比較少見,常用的是異步FIFO,但基于學(xué)習(xí)的目的,下文對兩種FIFO都進(jìn)行講解。

四、同步FIFO

1. 同步FIFO電路框圖

 截圖來自https://blog.csdn.net/HengZo/article/details/49683707

  簡單來說,同步FIFO其實就是一個雙口RAM加上兩個讀寫控制模塊。FIFO的常見參數(shù)和信號如下:

  • FIFO的寬度:即FIFO一次讀寫操作的數(shù)據(jù)位;
  • FIFO的深度:指的是FIFO可以存儲多少個N位的數(shù)據(jù)(如果寬度為N)。
  • 滿標(biāo)志:FIFO已滿或?qū)⒁獫M時由FIFO的狀態(tài)電路送出的一個信號,以阻止FIFO的寫操作繼續(xù)向FIFO中寫數(shù)據(jù)而造成溢出(overflow)。
  • 空標(biāo)志:FIFO已空或?qū)⒁諘r由FIFO的狀態(tài)電路送出的一個信號,以阻止FIFO的讀操作繼續(xù)從FIFO中讀出數(shù)據(jù)而造成無效數(shù)據(jù)的讀出(underflow)。
  • 讀時鐘:讀操作所遵循的時鐘,在每個時鐘沿來臨時讀數(shù)據(jù)。(同步FIFO 讀寫只有一個時鐘)
  • 寫時鐘:寫操作所遵循的時鐘,在每個時鐘沿來臨時寫數(shù)據(jù)。(異步FIFO讀寫時鐘分開)
  • 讀指針:總是指向下一個將要被寫入的單元,寫完后自動加1,復(fù)位時,指向第1個單元(編號為0)
  • 寫指針:總是指向下一個將要被讀出的單元,讀完后自動加1,復(fù)位時,指向第1個單元(編號為0)

 其實可以把FIFO比作一個單向行駛的隧道,隧道兩端都有一個門進(jìn)行控制,F(xiàn)IFO寬度就是這個隧道單向有幾個車道,F(xiàn)IFO的深度就是一個車道能容納多少輛車,當(dāng)隧道內(nèi)停滿車輛時,這就是FIFO的寫滿狀態(tài),當(dāng)隧道內(nèi)沒有一輛車時,這便是FIFO的讀空狀態(tài)。

 

2. 同步FIFO空滿判斷(計數(shù)器)

FIFO 的設(shè)計原則是任何時候都不能向滿FIFO中寫入數(shù)據(jù)(寫溢出),任何時候都不能從空FIFO中讀取數(shù)據(jù)(讀溢出)。FIFO 設(shè)計的核心是空滿判斷。FIFO設(shè)置讀、寫地址指針,F(xiàn)IFO初始化的時候讀指針和寫指針都指向地址為0的位置, 當(dāng)往FIFO里面每寫一個數(shù)據(jù),寫地址指針自動加1指向下一個要寫入的地址。當(dāng)從FIFO里面每讀一個數(shù)據(jù),讀地址指針自動加1指向下一個要讀出的地址,最后通過比較讀地址指針和寫地址指針的大小來確定空滿狀態(tài)。

 

當(dāng)讀地址指針追上寫地址指針,寫地址指針跟讀地址指針相等,此時FIFO是讀空狀態(tài)。

 

 

 

 當(dāng)寫地址指針追上讀地址指針,寫指針跟讀地址指針再次相等的時候,此時FIFO是寫滿狀態(tài)。

 

 

FIFO的空滿判斷一般有兩種方式,一種是計數(shù)器,另一種是拓展最高位。本文將在同步FIFO里面講解計數(shù)器的方法。在異步FIFO里面講解拓展最高位的方法。

3. 同步FIFO設(shè)計代碼

 同步FIFO基本接口:


 

信號 描述
clk 系統(tǒng)時鐘
rstn 系統(tǒng)復(fù)位信號
wr_en 寫使能端
wr_data FIFO寫數(shù)據(jù)
fifo_full FIFO的滿標(biāo)志位
 rd_en 讀使能端 
rd_data FIFO讀數(shù)據(jù)
fifo_empty FIFO的空標(biāo)志位

 

同步FIFO當(dāng)中,當(dāng)寫使能有效的時候計數(shù)器加一;當(dāng)讀使能有效的時候,計數(shù)器減一,將計數(shù)器與FIFO的size進(jìn)行比較來判斷FIFO的空滿狀態(tài)。這種方法設(shè)計比較簡單,但是需要的額外的計數(shù)器,就會產(chǎn)生額外的資源,而且當(dāng)FIFO比較大時,會降低FIFO最終可以達(dá)到的速度。

同步FIFO實現(xiàn)代碼如下:

 1 module sync_fifo#(parameter BUF_SIZE=8, BUF_WIDTH=8) (
 2     //FIFO的數(shù)據(jù)位寬默認(rèn)為8bit
 3     //FIFO深度默認(rèn)為8
 4    
 5 
 6     input                     i_clk,//輸入時鐘
 7     input                      i_rst,//復(fù)位信號
 8     input                      i_w_en,//寫使能信號
 9     input                      i_r_en,//讀使能信號
10    input      [BUF_WIDTH-1:0] i_data,//寫入數(shù)據(jù)
11   
12    output reg [BUF_WIDTH-1:0] o_data,//讀出數(shù)據(jù)
13     output                     o_buf_empty,//FIFO空標(biāo)志
14    output                     o_buf_full );//FIFO滿標(biāo)志
15 
16    reg [3:0] fifo_cnt;  //記錄FIFO數(shù)據(jù)個數(shù)
17     reg [$clog2(BUF_SIZE)-1:0] r_ptr,w_ptr;  //數(shù)據(jù)指針為3位寬度,0-7索引,8個數(shù)據(jù)深度,循環(huán)指針0-7-0-7
18    reg [BUF_WIDTH-1:0] buf_mem[0:BUF_SIZE-1]; //定義FIFO大小
19 
20     
21 //判斷空滿
22     assign o_buf_empty=(fifo_cnt==4'd0)?1'b1:1'b0;
23     assign o_buf_full=(fifo_cnt==4'd8)?1'b1:1'b0;
24 
25     
26     always@(posedge i_clk or posedge i_rst) //用于修改計數(shù)器
27         begin
28             if(i_rst)
29                 fifo_cnt<=4'd0;
30             else if((!o_buf_full&&i_w_en)&&(!o_buf_empty&&i_r_en)) //同時讀寫,計數(shù)器不變
31                 fifo_cnt<=fifo_cnt;
32             else if(!o_buf_full&&i_w_en) //寫數(shù)據(jù),計數(shù)器加1
33                 fifo_cnt<=fifo_cnt+1;
34             else if(!o_buf_empty&&i_r_en) //讀數(shù)據(jù),計數(shù)器減1
35                 fifo_cnt<=fifo_cnt-1;
36             else 
37                 fifo_cnt <= fifo_cnt; //其他情況,計數(shù)器不變
38         end
39 
40     always@(posedge i_clk or posedge i_rst) //讀數(shù)據(jù)
41         begin 
42             if(i_rst)
43                 o_data<=8'd0;
44             else if(!o_buf_empty&&i_r_en)
45                 o_data<=buf_mem[r_ptr];
46         end
47 
48     always@(posedge i_clk)  //寫數(shù)據(jù)
49         begin
50             if(!o_buf_full&&i_w_en)
51                 buf_mem[w_ptr]<=i_data;
52         end
53 
54     always@(posedge i_clk or posedge i_rst) //讀寫地址指針變化
55         begin
56             if(i_rst) begin
57                 w_ptr <= 0;
58                 r_ptr <= 0;
59             end
60             else begin
61                 if(!o_buf_full&&i_w_en) // 寫數(shù)據(jù),地址加1,溢出后自動回到0開始
62                         w_ptr <= w_ptr + 1;
63                 if(!o_buf_empty&&i_r_en) // 讀數(shù)據(jù),地址加1,溢出后自動回到0開始
64                         r_ptr <= r_ptr + 1;
65             end
66         end
67 
68 endmodule

 

4. 同步FIFO仿真結(jié)果

 同步FIFO仿真測試文件

 

  1 `timescale 1ns/1ns
  2 
  3 module sync_fifo_tb;
  4     reg i_clk,i_rst;
  5     reg i_w_en,i_r_en;
  6     reg [7:0] i_data;
  7     wire [7:0] o_data;
  8     wire o_buf_empty,o_buf_full;
  9 
 10     sync_fifo dut(
 11         .i_clk(i_clk),
 12         .i_rst(i_rst),
 13         .i_data(i_data),
 14         .i_w_en(i_w_en),
 15         .i_r_en(i_r_en),
 16         .o_buf_empty(o_buf_empty),
 17         .o_buf_full(o_buf_full),
 18         .o_data(o_data)
 19     );
 20 
 21     initial begin
 22         #30;
 23         forever #10 i_clk = ~i_clk; //時鐘
 24     end
 25     reg [7:0] r_data=8'd0;
 26     initial begin
 27           i_clk=1'b0;
 28            i_rst=1'b0;
 29            i_w_en=1'b0;
 30            i_r_en=1'b0;
 31             i_data=8'd0;
 32            #5 i_rst=1'b1;
 33         #10 i_rst=1'b0;
 34     
 35            push(1);
 36         fork  //同時執(zhí)行push和pop
 37               push(2);
 38                pop(r_data);
 39         join 
 40            push(3);
 41         push(4);
 42         push(5);
 43         push(6);
 44         push(7);
 45         push(8);
 46         push(9);
 47         push(10);
 48         push(11);
 49         push(12);
 50         push(13);
 51         push(14);
 52         push(15);
 53         push(16);
 54         push(17);
 55         pop(r_data);
 56         push(18);
 57         pop(r_data);
 58         pop(r_data);
 59         pop(r_data);
 60         pop(r_data);
 61         push(19);
 62         pop(r_data);
 63         push(20);
 64         pop(r_data);
 65         pop(r_data);
 66         pop(r_data);
 67         pop(r_data);
 68         pop(r_data);
 69         pop(r_data);
 70         pop(r_data);
 71         pop(r_data);
 72         pop(r_data);
 73         pop(r_data);
 74         pop(r_data);
 75         push(21);
 76         pop(r_data);
 77         pop(r_data);
 78          pop(r_data);
 79          pop(r_data);
 80         #100 $stop;
 81     end
 82     
 83     task push (input [7:0] data);
 84         if(o_buf_full)
 85                $display("Cannot push %d: Buffer Full",data);
 86         else begin
 87             $display("Push",,data);
 88             i_data=data;
 89             i_w_en=1;
 90             @(posedge i_clk) #4 i_w_en= 0; //時鐘上升沿后4ns,寫使能清零
 91         end
 92     endtask
 93     
 94     task pop(output[7:0] data);
 95         if(o_buf_empty)
 96             $display("Cannot Pop: Buffer Empty");
 97         else begin
 98             i_r_en=1;
 99             @(posedge i_clk) #4 i_r_en= 0; //時鐘上升沿4ns后,讀使能清零
100             data = o_data;
101             $display("Pop:",,data);
102         end        
103     endtask
104 endmodule

 

采用Modelsim仿真得到如下波形:

 可以在Modelsim的View——Transcript窗口看到有如下打印信息:

# run -all
# Push   1
# Push   2
# Pop:   1
# Push   3
# Push   4
# Push   5
# Push   6
# Push   7
# Push   8
# Push   9
# Cannot push  10: Buffer Full
# Cannot push  11: Buffer Full
# Cannot push  12: Buffer Full
# Cannot push  13: Buffer Full
# Cannot push  14: Buffer Full
# Cannot push  15: Buffer Full
# Cannot push  16: Buffer Full
# Cannot push  17: Buffer Full
# Pop:   2
# Push  18
# Pop:   3
# Pop:   4
# Pop:   5
# Pop:   6
# Push  19
# Pop:   7
# Push  20
# Pop:   8
# Pop:   9
# Pop:  18
# Pop:  19
# Pop:  20
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Push  21
# Pop:  21
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty

 

五、異步FIFO

雖然各FPGA大廠商都有自己的現(xiàn)成 FIFO IP 可供調(diào)用, 而且用戶自己設(shè)計異步FIFO是比較復(fù)雜的。但是我們?nèi)匀恍枰獙W(xué)習(xí)FIFO的設(shè)計原理,這樣我們在設(shè)計或移植的過程中查找問題起來將有據(jù)可循。所以掌握異步FIFO設(shè)計原理是一名合格FPGA工程師的基本功。

1、異步FIFO的電路框圖

異步FIFO有兩個時鐘信號,讀和寫接口分別采用不同時鐘,這兩個時鐘可能時鐘頻率不同,也可能時鐘相位不同,可能是同源時鐘,也可能是不同源時鐘。

由 Clifford E. Cummings(Sunburst Design 聯(lián)合創(chuàng)始人、Verilog/SystemVerilog 領(lǐng)域權(quán)威專家)提出的  異步 FIFO(Asynchronous FIFO) 的實現(xiàn)方法成為硬件設(shè)計中處理跨時鐘域數(shù)據(jù)通信的標(biāo)桿方案(論文《Simulation and Synthesis Techniques for Asynchronous FIFO Design》)。

下面來看該方案里異步FIFO的系統(tǒng)框圖:

 

 

 可以看到異步FIFO實質(zhì)上也是基于中間的雙口RAM,外加一些讀寫控制電路組成的。因為這里讀寫用的是兩個不同的時鐘,這將涉及到跨時鐘域(CDC, Clock Domain Crossing)問題。跨時鐘域的電路會帶來亞穩(wěn)態(tài)。

2 、亞穩(wěn)態(tài)

在了解亞穩(wěn)態(tài)之前先了解建立時間(setup time,Tsu)和保持時間(hold time,Th)的概念。如下是一個觸發(fā)器:

 建立時間是指在觸發(fā)器的時鐘信號有效沿(如上升沿)到來之前,輸入數(shù)據(jù)必須保持穩(wěn)定不變的最短時間。只有滿足建立時間,時鐘才能可靠地對數(shù)據(jù)進(jìn)行采樣,若不滿足,該時鐘沿?zé)o法正確將數(shù)據(jù)打入觸發(fā)器。   

保持時間是指在觸發(fā)器的時鐘信號有效沿(如上升沿)到來之后,輸入數(shù)據(jù)仍需保持穩(wěn)定不變的最短時間。這確保時鐘觸發(fā)后,數(shù)據(jù)有足夠時間被正確讀取和轉(zhuǎn)換。若保持時間不足,數(shù)據(jù)可能無法被有效處理。

建立時間和保持時間的時序圖如下(Tco 代表數(shù)據(jù)經(jīng)過觸發(fā)器的時延):

在同步系統(tǒng)中,數(shù)據(jù)相對于時鐘總有固定的關(guān)系。當(dāng)這種關(guān)系滿足器件的建立和保持時間的要求時,輸出端會在特定的傳輸延遲時間內(nèi)輸出一個有效狀態(tài)。因為在同步系統(tǒng)中輸入信號總是滿足觸發(fā)器的時序要求,所以不會發(fā)生亞穩(wěn)態(tài)。

其實換句話說就是,同步系統(tǒng)中時鐘可以做到與數(shù)據(jù)同步:

如上圖,假設(shè)同步系統(tǒng)中數(shù)據(jù)從0跳變到1,一般數(shù)據(jù)的跳變不是立馬跳變,而是有一個斜坡。第一個時鐘周期采集到的是0, 第二個周期電平已經(jīng)穩(wěn)定到1,所以同步時鐘系統(tǒng)不會出現(xiàn)亞穩(wěn)態(tài)。

但是,在異步系統(tǒng)中,由于數(shù)據(jù)和時鐘的關(guān)系不是固定的,因此有時會出現(xiàn)違反建立和保持時間的現(xiàn)象。當(dāng)違反建立和保持時間時,就會輸出介于兩個有效狀態(tài)之間的中間級電平且無法確定停留在中間狀態(tài)的時間,這就是出現(xiàn)了亞穩(wěn)態(tài)(Metastability)。

當(dāng)觸發(fā)器處在亞穩(wěn)態(tài)時,輸出會在高低電平之間波動,這會導(dǎo)致延遲輸出轉(zhuǎn)換過程,并超出所規(guī)定的時鐘到輸出的延遲值( tco)亞穩(wěn)態(tài)輸出恢復(fù)到穩(wěn)定狀態(tài)所需的超出tco的額外時間部分稱為穩(wěn)定時間 ( tMET)。并非所有不滿足建立和保持時間的輸入變化都會導(dǎo)致亞穩(wěn)態(tài)輸出。觸發(fā)器是否進(jìn)入亞穩(wěn)態(tài)和返回穩(wěn)態(tài)所需時間取決于生產(chǎn)器件的工藝技術(shù)與外界環(huán)境。一般來說,觸發(fā)器都會在一個或者兩個時鐘周期內(nèi)返回穩(wěn)態(tài)。(參考Mohit Arora的《The Art of Hardware Architecture》)

 

 

 亞穩(wěn)態(tài)的過程可類比小球處于坡頂,處于不穩(wěn)定狀態(tài),可能向左或轉(zhuǎn)向右滾下山坡,具體是穩(wěn)定到0還是1是隨機(jī)的,與輸入沒有必然的關(guān)系。

 

 

 

拿異步FIFO讀寫時鐘不同步來舉例,當(dāng)判斷異步FIFO空滿狀態(tài)時,將讀時鐘域的讀地址指針傳輸?shù)綄憰r鐘域然后與寫地址指針進(jìn)行比較判斷FIFO是否為滿,將寫時鐘域的寫地址指針傳輸?shù)阶x時鐘域然后與讀地址指針進(jìn)行比較判斷FIFO是否為空。這種跨時鐘域的處理就很可能會產(chǎn)生亞穩(wěn)態(tài):

假設(shè)數(shù)據(jù)跟clk1是同步的,第2個時鐘比第1個時鐘滯后一點點,那么第2個時鐘在采集數(shù)據(jù)的時候有可能時鐘上升沿正好對應(yīng)在數(shù)據(jù)跳變的階段,那此時讀到的數(shù)據(jù)可能是0, 可能是1, 也可能進(jìn)入振蕩狀態(tài)。這種不確定的電平輸出會沿著信號通道上的電路繼續(xù)傳遞下去,對電路造成很大危害,極有可能讓整個系統(tǒng)掛死。

亞穩(wěn)態(tài)不可完全避免, 只能通過一些處理機(jī)制如 引入同步機(jī)制(打2拍) 、 格雷碼以及乒乓操作等來降低亞穩(wěn)態(tài)出現(xiàn)的機(jī)率。

3、打兩拍

 異步FIFO的跨時鐘域處理所帶來的亞穩(wěn)態(tài)可以通過同步機(jī)制(打兩拍)來降低亞穩(wěn)態(tài)發(fā)生的概率。

如下圖,A時鐘域的數(shù)據(jù)Q1傳遞給B時鐘域, 當(dāng)B時鐘上升沿來時,可能恰好數(shù)據(jù)Q1從0跳變到1,這樣Q2極有可能出現(xiàn)亞穩(wěn)態(tài)。如果我們將Q2的值直接拿來用,將會導(dǎo)致亞穩(wěn)態(tài)傳播下去。所以后面再設(shè)置一個D觸發(fā)器繼續(xù)對Q2進(jìn)行采樣得到Q3。

 

 

 可能Q2會產(chǎn)生亞穩(wěn)態(tài),但等到Q3時候電平就會穩(wěn)定到0或者1(也有可能繼續(xù)是亞穩(wěn)態(tài),但一個電路出現(xiàn)亞穩(wěn)態(tài)概率非常低, 然后連續(xù)兩次出現(xiàn)亞穩(wěn)態(tài)的概率更低, 低到我們可以忽略, 因此我們可以假設(shè)打兩拍以后Q3 不存在亞穩(wěn)態(tài)了,因此打兩拍可以解決亞穩(wěn)態(tài)傳播的問題)。

 Q1經(jīng)過B時鐘打兩拍同步以后的數(shù)據(jù)Q3才能在B時鐘域被使用。

當(dāng)然,可能有人會問,如果Q1當(dāng)時跳變?yōu)?時卻被識別為0 ,對電路就沒有影響嗎? 答案是,如果只是一個地方判斷錯誤不會有太大影響。怕就怕亞穩(wěn)態(tài)一直被傳播下去。

4、格雷碼

格雷碼是一種相鄰數(shù)據(jù)只有1bit變化的碼制。

十進(jìn)制數(shù) 自然二進(jìn)制碼 格雷碼
0 0000 0000
1 0001 0001
2 0010 0011
3 0011 0010
4 0100 0110
5 0101 0111
6 0110 0101
7 0111 0100
8 1000 1100
9 1001 1101
10 1010 1111
11 1011 1110
12 1100 1010
13 1101 1011
14 1110 1001
15 1111 1000

如果地址采用二進(jìn)制碼,地址從3(0011)跳變到4(0100),有3個bit發(fā)生了變化, 每個bit 都有可能發(fā)生亞穩(wěn)態(tài),那么此時亞穩(wěn)態(tài)出現(xiàn)的幾率是1bit 跳變的3倍。

 

因為格雷碼每次跳變只有一個bit,所以采用格雷碼將大大降低了亞穩(wěn)態(tài)發(fā)生的概率。

格雷碼是二進(jìn)制碼右移1位再與原碼相異或的結(jié)果。

 

 

二進(jìn)制碼轉(zhuǎn)格雷碼的Verilog代碼實現(xiàn)如下:

graycode = (bincode>>1) ^ bincode;   

 

如果二進(jìn)制變化沒有任何規(guī)律,那么采用格雷碼也可能發(fā)生多 bit 的跳變,而 FIFO 設(shè)計中的讀寫地址都是連續(xù)變化的,因此格雷碼適用于 FIFO 的地址處理。

5、 FIFO的乒乓操作

FIFO的乒乓操作是一種數(shù)據(jù)處理技術(shù),它利用兩個或多個FIFO交替進(jìn)行讀寫。例如,在一個典型的乒乓操作中,當(dāng)數(shù)據(jù)被寫入到第一個FIFO時,第二個FIFO可以被讀取;當(dāng)?shù)谝粋€FIFO寫完且第二個FIFO也讀完時切換到第二個FIFO進(jìn)行寫入,同時讀取第一個FIFO。

 這樣就確保讀寫操作不會同時訪問同一緩沖區(qū),起到了讀和寫的隔離,從而減少了亞穩(wěn)態(tài)的發(fā)生。

當(dāng)一個緩沖區(qū)完成寫入后,讀取端可以立即處理其數(shù)據(jù),而寫入端已開始填充另一個緩沖區(qū)。這種“填一個,讀一個”的流水線模式,一定時間內(nèi)確保數(shù)據(jù)流無中斷。例如,在圖像處理過程中,DMT(Digital Multiplier Timing)時序要求在行同步信號之后,必須沒有時間間隔地輸出一整行像素數(shù)據(jù)。如果使用帶有反壓機(jī)制的FIFO,可能會出現(xiàn)輸出像素不完整的情況,因為FIFO的反壓機(jī)制(為了防止數(shù)據(jù)流量過大導(dǎo)致fifo溢出,會在fifo前面加一個ready反壓前級的數(shù)據(jù),ready與fifo的full相關(guān)聯(lián),fifo滿則ready為0讓前級不會再寫進(jìn)去,這種稱為反壓機(jī)制)可能會導(dǎo)致數(shù)據(jù)的流動受到限制,從而影響每一行像素的連續(xù)輸出。相反,采用乒乓操作可以有效解決這一問題。通過使用雙緩沖區(qū)機(jī)制,乒乓操作允許系統(tǒng)在一個緩沖區(qū)中存儲完整的一行像素數(shù)據(jù),同時另一個緩沖區(qū)可以用來進(jìn)行數(shù)據(jù)輸出或進(jìn)一步處理。這種方式確保了在行同步信號到達(dá)時,可以先完全存儲好一整行的像素數(shù)據(jù),然后再進(jìn)行連續(xù)輸出,避免了因數(shù)據(jù)流動受限而導(dǎo)致的像素輸出不完整的問題。(參考文獻(xiàn)《Proposed VESA and Industry Standards and Guidelines for Computer Display Monitor Timing (DMT)》)

總的來說,選擇乒乓FIFO的場景有:高速數(shù)據(jù)流處理、對數(shù)據(jù)連續(xù)性要求較高、異步FIFO跨時鐘域等。

6、異步FIFO的空滿判斷(拓展最高位)

FIFO只能順序?qū)懭霐?shù)據(jù),順序的讀出數(shù)據(jù),其數(shù)據(jù)地址由內(nèi)部讀寫指針自動加1完成。在指針中添加一個額外的位(extra bit),當(dāng)寫指針增加并越過最后一個FIFO地址時,就將寫指針這個未用的MSB1,其它位回零。對讀指針也進(jìn)行同樣的操作。此時,對于深度為2nFIFO,需要的讀/寫指針位寬為(n+1)位,如對于深度為8FIFO,需要采用4bit的計數(shù)器,0000100010011111MSB作為折回標(biāo)志位,而低3位作為地址指針。

 (1)空判斷

如果兩個指針相同,則說明兩個指針折回的次數(shù)相等。其余位相等,說明FIFO為空;

(2)滿判斷

如果兩個指針的MSB不同,其余位相同,說明寫指針比讀指針多折回了一次;如r_addr=0000,w_addr = 1000,為滿。

使用gray碼判斷“空”與“滿”

 使用gray碼降低亞穩(wěn)態(tài)概率,但同時也帶來另一個問題,即在格雷碼域如何判斷空與滿。

對于“空”的判斷依然依據(jù)二者完全相等(包括MSB)

而對于“滿”的判斷,如下圖,由于gray碼除了MSB外,具有鏡像對稱的特點,當(dāng)讀指針指向7,寫指針指向8時,除了MSB,其余位皆相同,不能說它為滿。因此不能單純的只檢測最高位了,在gray碼上判斷為滿必須同時滿足以下3條:

  • wptr和同步過來的rptrMSB不相等,因為wptr必須比rptr多折回一次。
  • wptrrptr的次高位不相等,如上圖位置7和位置15,轉(zhuǎn)化為二進(jìn)制對應(yīng)的是01111111MSB不同說明多折回一次,111相同代表同一位置。
  • 剩下的其余位完全相等。截圖參考 http://www.sunburst-design.com/papers/CummingsSNUG2002SJ_FIFO1.pdf

      

(3)虛空、虛滿

當(dāng)前計數(shù)器值和正在比較的計數(shù)器值不同。

當(dāng)寫地址同步到讀時鐘域時 這個地址需要在讀時鐘域打兩拍,而這兩拍的過程中寫控制端還可以繼續(xù)向FIFO里面寫 數(shù)據(jù),如果此時判斷FIFO為空的話,這個空屬于虛空。

 

 當(dāng)讀地址同步到寫時鐘域時 這個地址需要在寫時鐘域打兩拍,而這兩拍的過程中讀控制端還可以繼續(xù)從FIFO里面讀取 數(shù)據(jù),如果此時判斷FIFO為滿的話,這個滿屬于虛滿。

 虛空虛滿不會產(chǎn)生錯誤, 只是影響FIFO 效率。 理解這些原理后,分析問題就知道去哪里分析。

7、如何選擇FIFO深度

設(shè)計FIFO的時候其深度的計算至關(guān)重要,深度設(shè)置小了有丟數(shù)據(jù)的風(fēng)險,太大了又浪費資源。

其實FIFO的深度主要取決于傳輸時的極端情況。那么我們先了解跟極端情況相關(guān)的一個概念:突發(fā)傳輸。

突發(fā)傳輸(burst)

突發(fā)(burst)傳輸是這樣定義的:In telecommunication, a burst transmission or data burst is the broadcast of a relatively high-bandwidth transmission over a short period。某個短時間內(nèi)相對高帶寬的數(shù)據(jù)傳輸。

 其實突發(fā)傳輸也就是一個又一個的數(shù)據(jù)包,每一個數(shù)據(jù)包之間是有時間間隔的。假如模塊A不間斷地往FIFO中寫數(shù)據(jù),模塊B同樣不間斷地從FIFO中讀數(shù)據(jù),不同的是模塊A寫數(shù)據(jù)的時鐘頻率要大于模塊B讀數(shù)據(jù)的時鐘頻率,那么在一段時間內(nèi)總是有一些數(shù)據(jù)沒來得及被讀走,如果系統(tǒng)一直在工作,那么那些沒有被讀走的數(shù)據(jù)會越累積越多,那么FIFO的深度需要是無窮大的,因此只有在突發(fā)數(shù)據(jù)傳輸過程中討論FIFO深度才是有意義的。

突發(fā)長度

一次傳遞一包數(shù)據(jù)完成后再去傳遞下一包數(shù)據(jù),一段時間內(nèi)傳遞的數(shù)據(jù)個數(shù)稱為burst length。比如每100個寫周期內(nèi)寫入80個數(shù)據(jù),如果輸入數(shù)據(jù)的模式是固定的,那么這里burst length就是80個數(shù)據(jù)。如果傳輸模式不是固定的,那就要考了極端情況,也就是所謂的“背靠背”。

背靠背

假設(shè)一個異步FIFO,每100個cycle可以寫入80個數(shù)據(jù),輸入數(shù)據(jù)模式不固定,那么這就意味著這80個數(shù)據(jù)可以在100個cycle的周期的任意時間寫進(jìn)FIFO里面。極端情況下可以是連續(xù)的200個cycle中,兩個80個數(shù)據(jù)是“背靠背”接著輸入的(Case 5):

 

 那么這種場景下的burst length就是160個數(shù)據(jù)了。

FIFO深度計算公式

FIFO的最小深度與突發(fā)長度(burst length), 讀出率(rd_rate),寫入率(wr_rate),讀寫時鐘(rd_clk和wr_clk)等因素有關(guān)。

當(dāng)寫比讀快,F(xiàn)IFO深度計算公式如下

舉例:寫數(shù)據(jù)時鐘wr_clk=80MHz,讀數(shù)據(jù)時鐘rd_clk=50MHz,每100個寫時鐘周期就有80個數(shù)據(jù)寫入FIFO,每10個讀時鐘周期可以有6個數(shù)據(jù)讀出FIFO。

分析:這里可能會出現(xiàn)極端情況,即背靠背時burst length為160,rd_rate就是6/10也就是0.6。根據(jù)上面的公式計算可得出FIFO深度:

160-160*(50/80)*(6/10)=100

因此FIFO的最小深度為100。由于FIFO深度只能取2的整數(shù)次冪,因此最小深度為:2^7 = 128。

在讀寫時鐘相同或者讀比寫快時,理論上FIFO的最小深度是1即可,但在實際應(yīng)用中,還需要考慮時鐘相位差異、數(shù)據(jù)位寬、傳輸效率和模式等因素,并在計算出的最小深度基礎(chǔ)上增加適當(dāng)?shù)挠嗔俊?/p>

當(dāng)讀比寫快,F(xiàn)IFO深度計算公式如下

舉例:寫數(shù)據(jù)時鐘wr_clk=80MHz,讀數(shù)據(jù)時鐘rd_clk=100MHz,數(shù)據(jù)位寬1byte。寫側(cè)連續(xù)寫入均為8192B長度的數(shù)據(jù)包。問:為保證輸出的數(shù)據(jù)包是連續(xù)不間斷,存夠多少數(shù)據(jù)后才能開始發(fā)送?

分析:此場景為寫入一定數(shù)據(jù)后開始讀取FIFO,那么最惡劣的時刻當(dāng)然是剛開始讀取的時間點。想要保證輸出連續(xù),則必須滿足:讀取8192B數(shù)據(jù)期間寫入的數(shù)據(jù)+已緩存數(shù)據(jù) ≥8192B,即burst length為8192。在讀完t = 8192*(1/rd_clk)這段時間里, 寫入數(shù)據(jù)個數(shù)為:t/(1/wr_clk),8192 - 8192*(1/rd_clk)/(1/wr_clk) = 1638.4。同樣,將參數(shù)帶入上述公式:

8192?8192?(wr_clk/rd_clk)*wr_rate=1638.4

(因為是連續(xù)寫入,則寫入率wr_rate為1)

即FIFO深度最小應(yīng)是1638.4,在FIFO寫滿1638.4個數(shù)據(jù)后啟動讀,保證了讀側(cè)不出現(xiàn)”拉斷”現(xiàn)象。如此,深度既滿足了數(shù)據(jù)不丟失,也最大化節(jié)省了資源。由于FIFO深度只能取2的整數(shù)次冪,因此最小深度為:2^11 = 2048。

8、異步FIFO的設(shè)計代碼

 (1)頂層模塊

 1 module async_fifo#(parameter BUF_SIZE=8, BUF_WIDTH=8)
 2 //FIFO深度默認(rèn)為8
 3 //FIFO的數(shù)據(jù)位寬默認(rèn)為8bit
 4  
 5  (
 6     input  [BUF_WIDTH-1:0] i_wdata,
 7     input              i_w_en, i_wclk, i_wrst_n,  //寫請求信號,寫時鐘,寫復(fù)位
 8     input              i_r_en, i_rclk, i_rrst_n,  //讀請求信號,讀時鐘,讀復(fù)位
 9     output [BUF_WIDTH-1:0] o_rdata,
10     output             o_buf_full,
11     output             o_buf_empty
12  );
13 wire [$clog2(BUF_SIZE)-1:0] waddr, raddr;
14 wire [$clog2(BUF_SIZE):0]   wptr, rptr, wq2_rptr, rq2_wptr;        
15 
16 
17 /*在檢測“滿”或“空”狀態(tài)之前,需要將指針同步到其它時鐘域時,使用格雷碼,可以降低同步過程中亞穩(wěn)態(tài)出現(xiàn)的概率*/
18 
19 sync_r2w I1_sync_r2w(
20     .wq2_rptr(wq2_rptr), 
21     .rptr(rptr),
22     .wclk(i_wclk), 
23     .wrst_n(i_wrst_n));
24 sync_w2r I2_sync_w2r (
25     .rq2_wptr(rq2_wptr), 
26     .wptr(wptr),
27     .rclk(i_rclk), 
28     .rrst_n(i_rrst_n));
29 
30      /* DualRAM */
31      
32 dualram #(BUF_WIDTH, BUF_SIZE) I3_DualRAM(
33     .rdata(o_rdata), 
34     .wdata(i_wdata),
35     .waddr(waddr), 
36     .raddr(raddr),
37     .wclken(i_w_en), 
38     .wclk(i_wclk));
39     
40 
41      /*空、滿比較邏輯*/
42 
43 rptr_empty #(BUF_SIZE) I4_rptr_empty(
44     .rempty(o_buf_empty),
45     .raddr(raddr),
46     .rptr(rptr), 
47     .rq2_wptr(rq2_wptr),
48     .rinc(i_r_en), 
49     .rclk(i_rclk),
50     .rrst_n(i_rrst_n));
51 wptr_full #(BUF_SIZE) I5_wptr_full(
52     .wfull(o_buf_full), 
53     .waddr(waddr),
54     .wptr(wptr), 
55     .wq2_rptr(wq2_rptr),
56     .winc(i_w_en), 
57     .wclk(i_wclk),
58     .wrst_n(i_wrst_n));
59 endmodule

(2)雙端口RAM模塊

雙端口RAM模塊用于存儲數(shù)據(jù)。

 

 

 1 module dualram
 2 #(
 3     parameter BUF_WIDTH = 8,   // 數(shù)據(jù)位寬
 4     parameter BUF_SIZE = 8   // FIFO深度
 5 )
 6 (
 7     input                       wclken,wclk,
 8     input      [$clog2(BUF_SIZE)-1:0]  raddr,     //RAM 讀地址
 9     input      [$clog2(BUF_SIZE)-1:0]  waddr,     //RAM 寫地址
10     input      [BUF_WIDTH-1:0]  wdata,    //寫數(shù)據(jù)
11     output     [BUF_WIDTH-1:0]  rdata      //讀數(shù)據(jù)
12 );    
13 
14     reg [BUF_WIDTH-1:0] Mem[BUF_SIZE-1:0];
15           
16           
17     always@(posedge wclk)
18         begin
19             if(wclken)
20                 Mem[waddr] <= wdata;
21         end
22 
23     assign rdata =  Mem[raddr];
24 
25 
26 endmodule

(3)同步模塊1

sync_r2w 模塊用于讀地址同步到寫控制端。
 1 module sync_r2w 
 2 #(parameter BUF_SIZE = 8)
 3 (
 4     output reg [$clog2(BUF_SIZE):0] wq2_rptr,
 5     input      [$clog2(BUF_SIZE):0] rptr,
 6     input                       wclk, wrst_n
 7 );
 8 reg [$clog2(BUF_SIZE):0] wq1_rptr;
 9 
10 always @(posedge wclk or negedge wrst_n) begin
11     if (!wrst_n) 
12         {wq2_rptr,wq1_rptr} <= 0;
13     else 
14         {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};// 將寫時鐘域傳過來的地址打兩拍
15 
16 end
17 
18 endmodule

 

 

 

 (4)同步模塊2

sync_w2r模塊用于寫地址同步到讀控制端。
 1 module sync_w2r 
 2 #(parameter BUF_SIZE = 8)
 3 (
 4     output reg  [$clog2(BUF_SIZE)+1:0] rq2_wptr,
 5     input         [$clog2(BUF_SIZE)+1:0] wptr,
 6     input         rclk, rrst_n
 7 );        reg [$clog2(BUF_SIZE)+1:0] rq1_wptr;
 8 
 9 always @(posedge rclk or negedge rrst_n) begin
10     if (!rrst_n)
11         {rq2_wptr,rq1_wptr} <= 0;
12     else 
13         {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
14 end
15 endmodule

 

 

 

(5)空判斷模塊

空判斷模塊用于判斷是否可以讀取數(shù)據(jù)。

讀操作時,讀使能rinc有效且FIFO未空。

 1 module rptr_empty 
 2 #(parameter BUF_SIZE = 8)
 3 (
 4     output reg rempty,                          //輸出空信號
 5     output     [$clog2(BUF_SIZE)-1:0] raddr,   //輸出讀數(shù)據(jù)地址
 6     output reg [$clog2(BUF_SIZE):0]  rptr,  //讀數(shù)據(jù)指針
 7     input       [$clog2(BUF_SIZE):0] rq2_wptr, //寫數(shù)據(jù)指針的格雷碼經(jīng)過打兩拍后輸入
 8     input       rinc, rclk, rrst_n);
 9 reg  [$clog2(BUF_SIZE):0] rbin;
10 wire [$clog2(BUF_SIZE):0] rgraynext, rbinnext;
11 wire  rempty_val;
12 
13 always @(posedge rclk or negedge rrst_n)
14     if (!rrst_n) 
15         begin 
16             rbin <= 0;
17             rptr <= 0;
18         end
19     else
20         begin
21             rbin <= rbinnext ; 
22             rptr <= rgraynext;
23         end
24 // gray碼計數(shù)邏輯
25 assign rbinnext = !rempty ? (rbin + rinc) : rbin; //如果為空,則指針不變,如果不為空,指針+1
26 assign rgraynext = (rbinnext>>1) ^ rbinnext;      //二進(jìn)制到gray碼的轉(zhuǎn)換
27         assign raddr = rbin[$clog2(BUF_SIZE)-1:0];
28 
29 
30 /*讀指針是一個n位的gray碼計數(shù)器,比FIFO尋址所需的位寬大一位
31 當(dāng)系統(tǒng)復(fù)位或者讀指針和同步過來的寫指針完全相等時(包括MSB),說明二者折回次數(shù)一致,FIFO為空*/
32 assign rempty_val = (rgraynext == rq2_wptr);
33  always @(posedge rclk or negedge rrst_n)
34 if (!rrst_n) 
35     rempty <= 1'b1;
36 else 
37     rempty <= rempty_val;
38 endmodule

 

(6)滿判斷模塊

滿判斷模塊用于判斷是否可以寫入數(shù)據(jù)。

寫操作時,寫使能winc有效且FIFO未滿。

 1 module wptr_full 
 2 #(
 3     parameter BUF_SIZE = 8
 4 )
 5 (
 6     output reg                wfull,                     //輸出滿信號
 7     output     [$clog2(BUF_SIZE)-1:0] waddr, //輸出寫地址
 8     output reg [$clog2(BUF_SIZE):0]  wptr,   //輸出寫指針
 9     input      [$clog2(BUF_SIZE):0]  wq2_rptr, //讀指針的格雷碼打兩拍后輸入
10     input                     winc, wclk, wrst_n);       
11      reg  [$clog2(BUF_SIZE):0] wbin;
12 wire [$clog2(BUF_SIZE):0] wgraynext, wbinnext;
13 wire wfull_val;
14 // GRAYSTYLE2 pointer
15 always @(posedge wclk or negedge wrst_n)
16     if (!wrst_n) 
17     begin
18         wbin <= 0;
19         wptr <= 0;
20     end
21     else 
22     begin
23         wbin <= wbinnext;
24          wptr <= wgraynext;
25     end
26 //gray 碼計數(shù)邏輯    
27 assign wbinnext  = !wfull ? (wbin + winc) : wbin;
28 assign wgraynext = (wbinnext>>1) ^ wbinnext;
29         assign waddr = wbin[$clog2(BUF_SIZE)-1:0];
30         /*由于滿標(biāo)志在寫時鐘域產(chǎn)生,因此比較安全的做法是將讀指針同步到寫時鐘域*/
31 
32 assign wfull_val = (wgraynext=={~wq2_rptr[$clog2(BUF_SIZE):$clog2(BUF_SIZE)-1],
33                     wq2_rptr[$clog2(BUF_SIZE)-2:0]});
34 always @(posedge wclk or negedge wrst_n)
35 if (!wrst_n) 
36     wfull <= 1'b0;
37 else 
38     wfull <= wfull_val;
39 endmodule

異步FIFO設(shè)計的整體RTL Viewer如下圖所示:

9、 異步FIFO仿真

(1)異步FIFO仿真文件

  1 `timescale 1 ps/ 1 ps
  2 module async_fifo_vlg_tst();
  3     reg i_r_en;
  4     reg i_rclk;
  5     reg i_rrst_n;
  6     reg i_w_en;
  7     reg i_wclk;
  8     reg i_wrst_n;
  9     reg [7:0] i_wdata;
 10 
 11     wire o_buf_empty;
 12     wire o_buf_full;
 13     wire [7:0]  o_rdata;
 14 
 15 async_fifo i1 (
 16 
 17     .i_r_en(i_r_en),
 18     .i_rclk(i_rclk),
 19     .i_rrst_n(i_rrst_n),
 20     .i_w_en(i_w_en),
 21     .i_wclk(i_wclk),
 22     .i_wdata(i_wdata),
 23     .i_wrst_n(i_wrst_n),
 24     .o_buf_empty(o_buf_empty),
 25     .o_buf_full(o_buf_full),
 26     .o_rdata(o_rdata)
 27 );
 28 
 29 
 30 
 31     always #10 i_wclk = ~i_wclk;
 32     always #5 i_rclk = ~i_rclk;
 33 
 34 
 35     reg [7:0] r_data=8'd0;
 36 
 37     initial begin
 38           i_wclk=1'b0;
 39         i_rclk=1'b0;
 40        i_wrst_n=1'b1;
 41         i_rrst_n=1'b1;
 42        i_w_en=1'b0;
 43        i_r_en=1'b0;
 44             i_wdata=8'd0;
 45        #1 i_wrst_n=1'b0;
 46             i_rrst_n=1'b0;
 47 
 48         #1 i_wrst_n=1'b1;
 49             i_rrst_n=1'b1;
 50 
 51         #20 push(1);
 52 
 53 
 54         push(2);
 55         //pop(r_data);
 56        push(3);
 57        push(4);
 58         push(5);
 59         push(6);
 60         push(7);
 61         push(8);
 62         push(9);
 63         pop(r_data);
 64         push(10);
 65         push(11);
 66         push(12);
 67         push(13);
 68         push(14);
 69         push(15);
 70         push(16);
 71         pop(r_data);
 72         push(17);
 73         pop(r_data);
 74         push(18);
 75         pop(r_data);
 76         pop(r_data);
 77         pop(r_data);
 78         pop(r_data);
 79         push(19);
 80         pop(r_data);
 81         push(20);
 82         pop(r_data);
 83         pop(r_data);
 84         pop(r_data);
 85         pop(r_data);
 86         pop(r_data);
 87         pop(r_data);
 88         pop(r_data);
 89         pop(r_data);
 90         pop(r_data);
 91         pop(r_data);
 92         pop(r_data);
 93         push(21);
 94         pop(r_data);
 95         pop(r_data);
 96          pop(r_data);
 97          pop(r_data);
 98         #100 $stop;
 99     end
100 
101     task push (input [7:0] data);
102         if(o_buf_full)
103                $display("Cannot push %d: Buffer Full",data);
104         else begin
105             $display("Push",,data);
106             i_wdata=data;
107             i_w_en=1;
108             @(posedge i_wclk) #4 i_w_en= 0; //時鐘上升沿后4ns,寫使能清零
109         end
110     endtask
111 
112     task pop(output[7:0] data);
113         if(o_buf_empty)
114             $display("Cannot Pop: Buffer Empty");
115         else begin
116             data = o_rdata;
117             $display("Pop:",,data);
118             i_r_en=1;
119             @(posedge i_rclk) #4 i_r_en= 0; //時鐘上升沿4ns后,讀使能清零
120         end
121     endtask
122 
123 
124             endmodule

 

這里選擇的是讀寫頻率相同,但讀是在時鐘下降沿, 寫在時鐘的上升沿。

(2)異步FIFO仿真結(jié)果

打開Quartus 的 菜單欄的  Tools——Run Simulation Tool——RTL Simulation看到波形如下:

 

當(dāng)復(fù)位撤銷(復(fù)位信號低有效)之后,在寫使能 i_w_en 拉高有效之后,寫數(shù)據(jù)也開始變化:

 

empty 空標(biāo)記也開始在幾拍之后變?yōu)榉强眨ㄓ幸粋€寫到讀側(cè)的異步轉(zhuǎn)換,打了兩拍):

 當(dāng)讀使能i_ r_en 拉高有效之后,讀數(shù)據(jù)在下一拍也開始變化: 

 

 可以在Modelsim的View——Transcript窗口看到有如下打印信息:

 1 # run -all
 2 # Push   1
 3 # Push   2
 4 # Push   3
 5 # Push   4
 6 # Push   5
 7 # Push   6
 8 # Push   7
 9 # Push   8
10 # Cannot push   9: Buffer Full
11 # Pop:   1
12 # Cannot push  10: Buffer Full
13 # Cannot push  11: Buffer Full
14 # Cannot push  12: Buffer Full
15 # Cannot push  13: Buffer Full
16 # Cannot push  14: Buffer Full
17 # Cannot push  15: Buffer Full
18 # Cannot push  16: Buffer Full
19 # Pop:   2
20 # Cannot push  17: Buffer Full
21 # Pop:   3
22 # Cannot push  18: Buffer Full
23 # Pop:   4
24 # Pop:   5
25 # Pop:   6
26 # Pop:   7
27 # Push  19
28 # Pop:   8
29 # Push  20
30 # Cannot Pop: Buffer Empty
31 # Cannot Pop: Buffer Empty
32 # Cannot Pop: Buffer Empty
33 # Cannot Pop: Buffer Empty
34 # Cannot Pop: Buffer Empty
35 # Cannot Pop: Buffer Empty
36 # Cannot Pop: Buffer Empty
37 # Cannot Pop: Buffer Empty
38 # Cannot Pop: Buffer Empty
39 # Cannot Pop: Buffer Empty
40 # Cannot Pop: Buffer Empty
41 # Push  21
42 # Cannot Pop: Buffer Empty
43 # Cannot Pop: Buffer Empty
44 # Cannot Pop: Buffer Empty
45 # Cannot Pop: Buffer Empty
46 # ** Note: $stop    : 

10.  異步復(fù)位同步釋放

同步復(fù)位是指復(fù)位信號只有在時鐘上升沿到來時,才能有效。否則,無法完成對系統(tǒng)的復(fù)位工作。參考設(shè)計如下:

always @(posedge clk) begin
    if (!rst_n) begin // 低電平復(fù)位有效
        q <= 1'b0;
    end else begin
        q <= d;
    end
end

異步復(fù)位是指無論時鐘沿是否到來,只要復(fù)位信號有效,就對系統(tǒng)進(jìn)行復(fù)位。

always @(posedge clk or negedge rst_n) begin // 時鐘或復(fù)位邊沿觸發(fā)
    if (!rst_n) begin // 低電平復(fù)位有效
        q <= 1'b0;
    end else begin
        q <= d;
    end
end

在異步FIFO中,需為寫時鐘域和讀時鐘域分別設(shè)計獨立的異步復(fù)位同步釋放電路。

  1. 異步復(fù)位的必要性

    • 異步復(fù)位(Async Reset)能立即響應(yīng)復(fù)位信號,無論時鐘是否有效,確保系統(tǒng)在異常情況下快速進(jìn)入確定狀態(tài)。

    • 在異步FIFO中,寫時鐘域和讀時鐘域可能獨立運(yùn)行,使用異步復(fù)位則可同時復(fù)位兩個時鐘域的電路。

  2. 同步釋放的必要性

    • 若復(fù)位信號直接異步釋放(撤銷),不同時鐘域的觸發(fā)器可能在不同時鐘邊沿退出復(fù)位狀態(tài),導(dǎo)致以下問題:

      • 亞穩(wěn)態(tài):復(fù)位釋放時,若釋放信號與時鐘邊沿太接近,可能違反觸發(fā)器的時序要求(恢復(fù)時間/移除時間)。

      • 狀態(tài)不一致:寫指針和讀指針可能在復(fù)位釋放后不同步,導(dǎo)致FIFO的空滿判斷錯誤(如誤判為空或滿)。

異步復(fù)位同步釋放的核心是通過兩級觸發(fā)器對復(fù)位信號的釋放進(jìn)行同步,確保復(fù)位信號在目標(biāo)時鐘域中穩(wěn)定撤銷。以下是一個典型的異步復(fù)位同步釋放電路:

module async_reset_sync_release (
    input  wire clk,       // 目標(biāo)時鐘
    input  wire async_rst, // 異步復(fù)位輸入
    output wire sync_rst   // 同步釋放后的復(fù)位輸出
);
    reg rst_ff1, rst_ff2;

    always @(posedge clk or negedge async_rst) begin
        if (~async_rst) begin
            rst_ff1 <= 1'b0;
            rst_ff2 <= 1'b0;
        end 
     else begin rst_ff1 <= 1'b1; rst_ff2 <= rst_ff1; end end assign sync_rst = rst_ff2; endmodule
  • 異步復(fù)位階段:

  1. 當(dāng) async_rst 有效時,rst_ff1 和 rst_ff2 立即被置位,輸出 sync_rst 立即生效。
  • 同步釋放階段:

  1. 當(dāng) async_rst 撤銷時,rst_ff1 和 rst_ff2 需要等待 clk 的上升沿才能依次清零。
  2. 經(jīng)過兩個時鐘周期后,sync_rst 同步釋放,確保復(fù)位撤銷與時鐘邊沿對齊,避免亞穩(wěn)態(tài)。

代碼對應(yīng)的 RTL Viewer 視圖:

 將異步復(fù)位同步釋放電路添加到前面的異步fifo工程當(dāng)中:

 

 對應(yīng)的RTL Viewer視圖如下:

 

六、FIFO應(yīng)用實例 (ADC)

接下來將異步fifo設(shè)計成IP核,并在實際工程當(dāng)中進(jìn)行調(diào)用。

參考:

手寫異步FIFO的FPGA 工程實例驗證(基于DE10-Standard)(LTC2308)

 

 自己設(shè)計FIFO的目的一般是為了學(xué)習(xí)一下FIFO的結(jié)構(gòu),設(shè)計思路等,如果是一般的項目設(shè)計 ,建議可以直接調(diào)用廠商提供的FIFO IP 進(jìn)行簡單配置會不容易出錯一點。

使用Quartus II軟件提供的免費FIFO IP核,Quartus II軟件為用戶提供了友好的圖形化界面方便用戶對FIFO的各種參數(shù)和結(jié)構(gòu)進(jìn)行配置,生成的FIFO IP核針對Altera不同系列的器件,還可以實現(xiàn)結(jié)構(gòu)上的優(yōu)化。

 Quartus 里面提供的FIFO可分為兩種結(jié)構(gòu):單時鐘FIFO(SCFIFO)和雙時鐘FIFO(DCFIFO), 這個實驗調(diào)用DCFIFO:

1-【友晶科技Terasic】基于FPGA實現(xiàn)LTC2308控制器的設(shè)計——總概述