1. 程式人生 > >(轉)xilinx FIFO的使用及各訊號的討論

(轉)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