(轉)xilinx FIFO的使用及各信號的討論

FIFO的使用非常廣泛,一般用於不同時鐘域之間的數據傳輸,比如FIFO的一端是AD數據採集,另一端是計算機的PCI總線,假設其AD採集的速率爲16位100K SPS,那麼每秒的數據量爲100K×16bit=1.6Mbps,而PCI總線的速度爲33MHz,總線寬度32bit,其最大傳輸速率爲33*32=1056Mbps,在兩個不同的時鐘域間就可以採用FIFO來作爲數據緩衝。另外對於不同寬度的數據接口也可以用FIFO,例如單片機位8位數據輸出,而DSP可能是16位數據輸入,在單片機與DSP連接時就可以使用FIFO來達到數據匹配的目的。

  本文就講通過ISE軟件生成一個FIFO,並對其進行一些操作以求更加了解FIFO中各個信號的作用以及控制方法(有些簡單步驟將省略不提)。

  在這步中,由於現在做的不是soc工程,選擇Native.

Next後,需選擇時鐘和存儲器類型。1,時鐘,由FIFO的作用可知大部分都是讀寫不同步的,這裏我們也選擇異步模式,即讀寫的時鐘不同。2,存儲器類型,這裏主要是block RAM和distribute RAM之間的區別。簡而言之,block RAM是FPGA中定製的ram資源,而distribute RAM則是由LUT構成的RAM資源。由此區別表明,當FIFO較大時應選擇block RAM,當FIFO較小時,選擇distribute RAM.另外一個很重要的就是block RAM支持讀寫不同寬度,而distribute不支持。在這裏爲了更全面的瞭解FIFO,選擇block RAM以擁有非對稱方向速率的特性。

 

  讀模式有兩種選擇,一般選擇標準模式,至於First-Word Fall-Fhrough的含義請查看FIFO手冊。寫數據寬度定義爲8位,寫深度定義爲256.讀寬度定義爲4位,而讀深度將根據以上幾個參數自動計算。但我們需要注意的是,在data port parameters處,有actual write depth和actual read depth,他們都比我們設置的要小,其意義以及原因將在例程中說明。

 

  之後便是添加信號,信號越多越難操作,但同時也能讓我們更加準確的控制FIFO,這裏爲了更好的瞭解FIFO,把所有能選的信號都選上。

 

  接下來是復位信號以及可編程信號的配置。雖然在block RAM和distribute RAM中,復位信號不是必需的,但根據習慣,還是啓用rst端口,且配置成同步復位,即整個FIFO共用一個復位信號,而不是讀寫不同的復位。至於編程信號,看選項就很容易理解了。配置如圖,FIFO中數據達到200時,programmable full有效,數據爲10時,programmable empty有效。

  之後是寫計數和讀計數,都使之有效,由於寫深度是256,讀深度是512.因此寫計數器的寬度定義爲8,讀計數器的寬度定義爲9.其實不一定計數器一定要比深度大,當計數器計數最大值小於數據深度時,例如數據深度爲512,而計數器大小爲256,則每兩個數據計數器計數一次。

  最後可以看到我們配置的FIFO信息如下(注意幾個關鍵信息),最後生成IP就好了。

 

  通過點擊View HDL Instantiation Template,我們可以看到所有需要例化的信號,以及格式。

  在該FIFO例程中,首先是將1-255寫入FIFO中,此時不讀取,觀察各個信號,然後再從FIFO中讀出FIFO中存儲的數據,此時不再寫入,觀察各信號。

代碼如下:

module FIFO_top(

input clk,rst,

output  wire [3:0] dout

    );

wire clk_50M_wire;

wire [7:0] din_wire;

wire valid,wr_ack;

wire overflow,underflow;

wire almost_empty,almost_full;

wire [8:0] rd_data_count;

wire [7:0] wr_data_count;

wire prog_full,prog_empty;

wire wr_en,rd_en;

wire full,empty;

///////////////////////////////////////////////////

//二分頻電路

//100M爲讀時鐘,50M爲寫時鐘

reg clk_50M;

assign clk_50M_wire = clk_50M ;

 

always @(posedge clk or posedge rst) begin

if (rst) clk_50M<=0;

else clk_50M<=~clk_50M;

end

///////////////////////////////////////////////

 

////////////////////////////////////////////////

reg [2:0] cnt;

 

assign wr_en =(full==0 && rd_en==0 && cnt==5)?1:0 ;//非滿時寫,且滿後就不再寫了,即便之後數據被讀取導致非滿

assign rd_en = (empty==0 && wr_en==0)?1:0 ;//寫時不讀取,寫完再讀取

 

reg [7:0] din;

assign din_wire = din ;

 

always @(posedge clk_50M or posedge rst) begin

if (rst) begin

din<=1;

end

else begin

if(wr_en) din<=din+1;

else din<=din;

end

end

 

always @ (posedge clk_50M or posedge rst)

if(rst) cnt<=0;

else begin

if(cnt==3'd5) cnt<=cnt;

else cnt<=cnt+1;

end

 

FIFO FIFO (

  .rst(rst), // input rst

  .wr_clk(clk_50M_wire), // input wr_clk 50M 

  .rd_clk(clk), // input rd_cFIFOlk 100M 

  .din(din_wire), // input [7 : 0] din

  .wr_en(wr_en), // input wr_en

  .rd_en(rd_en), // input rd_en

  .dout(dout), // output [3 : 0] dout

  .full(full), // output full

  .almost_full(almost_full), // output almost_full

  .wr_ack(wr_ack), // output wr_ack

  .overflow(overflow), // output overflow

  .empty(empty), // output empty

  .almost_empty(almost_empty), // output almost_empty

  .valid(valid), // output valid

  .underflow(underflow), // output underflow

  .rd_data_count(rd_data_count), // output [8 : 0] rd_data_count

  .wr_data_count(wr_data_count), // output [7 : 0] wr_data_count

  .prog_full(prog_full), // output prog_full 200

  .prog_empty(prog_empty) // output prog_empty 10

);

endmodule

1,復位信號rst: 由結果可知,其爲高電平有效,且復位後其他信號的初始值是可以在產生FIFO中配置的,之前配置爲0;很重要的一點是,復位後的幾個寫週期內(2,3個週期)是無法進行寫操作的,所以在本例程中,復位一段時間後再拉高wr_en以確保首先寫入的是1.

2,寫使能信號wr_en與寫響應信號wr_ack:關於該信號,很重要的一點是,寫入的值是wr_en拉高時的值;還是說當wr_en拉高後,下一週期才能進行寫操作?如果是前者,由波形所示,寫入的第一個值應該是1;如果是後者,寫入的應該是2。這就需要根據讀取的第一個值來判斷了。而經查看,讀取的第一個值爲1,也就是說,只要wr_en拉高,立馬就進行寫操作。

從下圖還能明白wr_ack的工作模式,即寫入成功時,wr_ack將在下一週期拉高。也就是說,wr_en反映的是上一週期的寫操作。

3,讀使能信號rd_en與讀響應信號valid:在讀操作中,第一個讀取的數據應該是0,第二個是1(原因之後解釋)。由波形可知,當rd_en有效的那個上升沿,並沒有進行讀操作,而是在下一個週期才真正讀取了數據,同時valid被拉高,這是與寫操作所不同的地方。

4,寫計數wr_data_count和rd_data_count:因爲寫數據會有256個,讀數據會有512個,一旦count的大小不夠,count從一開始就會失效,成爲高阻態,所以應該給wr_data_count設置成8位,rd_data_count設置成9位。當把wr_data_count設置成7位,rd_data_count設置成8位時,結果見圖。

正常設置時,即wr_data_count設置成8位,rd_data_count設置成9位。

在寫的過程中,可以看到,wr_data_count正常計數,每次加一,但是其值滯後2個週期。而由於讀操作是每次4位,寫操作是每次8位,即每次寫操作都意味着需要讀兩次才能讀出數據,所以每次寫操作,rd_data_count都是加2。

在讀過程中,rd_data_count是每次減1。同理,wr_data_count則是每2次讀操作才減1。

對於這兩個信號,也有不太正常的地方。如下圖,當進行了讀操作的時候,wr_data_count依舊保持在255不變,rd_data_count則在505和504之間切換,且其最大值不是預期的510.

可能的原因在於wr_data_count是屬於寫時鐘域的,讀操作進行後需要一段時間才能反映到寫時鐘域的各個參數,這在之後的empty等信號也可以得出類似結論。

至於rd_data_count應該是受讀操作以及full或者wr_data_count等信號的共同影響,導致其在505和504之間不停變換。

官方文檔也提到說,wr_data_count以及rd_data_count是大概的,不是非常準確。

5,prog_full;almost_full;full:Prog_full在之前的設置中是200,該值是根據wr_data_count判定的,即當wr_data_count爲200時,prog_full置一。但是由於wr_data_count滯後2個週期,所以真正寫入到FIFO中的值應該有202個了。

full以及almost_full由圖可知,在數據數滿足要求後的下一個週期被拉高。而且當讀操作進行後,這兩個信號並不是立刻被拉低,和之前所提到的一樣,這兩個信號屬於寫時鐘域,讀操作反映到這兩個信號上需要一定的時間

6,prog_empty;almost_empty;empty:

Prog_empty之前設置的是10,由波形圖知,其信號還是比較準確的。

Almost_empty以及empty則提早了一個週期被拉高

也可以看到,當寫操作進行時,empty等信號也不是立刻變低的,其原因也應該是屬於不同時鐘域。

7,underflow;overflow: 改動程序,使寫信號一直有效,可以看到當full變高後的下一週期因爲繼續進行寫操作,使得overflow也被拉高。

再改動程序,寫操作一段時間後,讀信號一直有效。可以看到當empty有效後,繼續讀操作,underflow將在下一週期被拉高。

8,關於FIFO實際讀寫深度的問題:在之前的設置中可以看到,我們原本設置的寫深度是256,讀深度是512;但是邊上顯示的實際讀寫深度分別是255,510.從結果中我們也可以看到:當寫到255時(數據是1~255),full被拉高。讀數據以及讀數據深度是根據寫數據和寫深度來的,自然也就是510了。也就是說實際寫深度會比設置的小1,這是在工程中需要注意的地方。

9,關於讀寫不對稱的問題:所謂的讀寫不對稱,即讀寫的位大小以及讀寫速率不一樣。在本例程中,寫入的數據是8位的,而讀出的數據是4位的。那麼讀操作的時候是怎麼樣一個讀出法呢?在寫操作中,我們是順序寫入1~255的。通過觀察讀數據可知,讀出的數據爲:0,1,0,2,0,3……這就說明當讀寫非對稱時,是先讀取數據的高位的。

10,關於FIFO的深度計算問題:在很多筆試面試中,都會問的FIFO的深度計算問題。因此,瞭解這方面也是很有必要的。

網上有很多關於這方面的公式,在此就不做討論了。其實很簡單,只要先計算單位時間內讀寫數據量的差值,然後乘以持續時間,就是FIFO的最小深度了。但是這裏還需要注意幾點:

1,背靠背問題,假設寫數據100個wr_clk內寫入80個數據,這時就需要考慮最壞的情況,就是前20個時鐘不寫入,接着80個時鐘寫入,再後來的80個時鐘繼續寫入,最後的20個時鐘不寫入。這樣寫入數據最集中的情況就有160個時鐘寫入160個數據了。

2,需要留有深度裕量,即實際的深度會比設置的小,因此應該多設置點FIFO深度,以避免丟失數據。

--------------------- 本文來自 xuexiaokkk 的CSDN 博客 ,全文地址請點擊:https://blog.csdn.net/xuexiaokkk/article/details/47753459?utm_source=copy

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章