一、什么是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
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;
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地址時,就將寫指針這個未用的MSB加1,其它位回零。對讀指針也進(jìn)行同樣的操作。此時,對于深度為2n的FIFO,需要的讀/寫指針位寬為(n+1)位,如對于深度為8的FIFO,需要采用4bit的計數(shù)器,0000~1000、1001~1111,MSB作為折回標(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和同步過來的rptr的MSB不相等,因為wptr必須比rptr多折回一次。
- wptr與rptr的次高位不相等,如上圖位置7和位置15,轉(zhuǎn)化為二進(jìn)制對應(yīng)的是0111和1111,MSB不同說明多折回一次,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)讀使能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ù)位同步釋放電路。
-
異步復(fù)位的必要性:
-
異步復(fù)位(Async Reset)能立即響應(yīng)復(fù)位信號,無論時鐘是否有效,確保系統(tǒng)在異常情況下快速進(jìn)入確定狀態(tài)。
-
在異步FIFO中,寫時鐘域和讀時鐘域可能獨立運(yùn)行,使用異步復(fù)位則可同時復(fù)位兩個時鐘域的電路。
-
-
同步釋放的必要性:
-
若復(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ù)位階段:
- 當(dāng) async_rst 有效時,rst_ff1 和 rst_ff2 立即被置位,輸出 sync_rst 立即生效。
-
同步釋放階段:
- 當(dāng) async_rst 撤銷時,rst_ff1 和 rst_ff2 需要等待 clk 的上升沿才能依次清零。
- 經(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è)計——總概述
浙公網(wǎng)安備 33010602011771號