【ZYNQ Ultrascale+ MPSOC FPGA教程】第八章FPGA片內FIFO讀寫測試實驗

原創聲明:

本原創教程由芯驛電子科技(上海)有限公司(ALINX)創做,版權歸本公司全部,如需轉載,需受權並註明出處。node

適用於板卡型號:

AXU2CGA/AXU2CGB/AXU3EG/AXU4EV-E/AXU4EV-P/AXU5EV-E/AXU5EV-P /AXU9EG/AXU15EG緩存

實驗Vivado工程爲「fifo_test」。異步

FIFO是FPGA應用當中很是重要的模塊,普遍用於數據的緩存,跨時鐘域數據處理等。學好FIFO是FPGA的關鍵,靈活運用好FIFO是一個FPGA工程師必備的技能。本章主要介紹利用XILINX提供的FIFO IP進行讀寫測試。測試

1.實驗原理ui

FIFO: First in, First out表明先進的數據先出,後進的數據後出。Xilinx在VIVADO裏爲咱們已經提供了FIFO的IP核, 咱們只需經過IP核例化一個FIFO,根據FIFO的讀寫時序來寫入和讀取FIFO中存儲的數據。spa

其實FIFO是也是在RAM的基礎上增長了許多功能,FIFO的典型結構以下,主要分爲讀和寫兩部分,另外就是狀態信號,空和滿信號,同時還有數據的數量狀態信號,與RAM最大的不一樣是FIFO沒有地址線,不能進行隨機地址讀取數據,什麼是隨機讀取數據呢,也就是能夠任意讀取某個地址的數據。而FIFO則不一樣,不能進行隨機讀取,這樣的好處是不用頻繁地控制地址線。設計

雖然用戶看不到地址線,可是在FIFO內部仍是有地址的操做的,用來控制RAM的讀寫接口。其地址在讀寫操做時以下圖所示,其中深度值也就是一個FIFO裏最大能夠存放多少個數據。初始狀態下,讀寫地址都爲0,在向FIFO中寫入一個數據後,寫地址加1,從FIFO中讀出一個數據後,讀地址加1。此時FIFO的狀態即爲空,由於寫了一個數據,又讀出了一個數據。3d

能夠把FIFO想象成一個水池,寫通道即爲加水,讀通道即爲放水,假如不間斷的加水和放水,若是加水速度比放水速度快,那麼FIFO就會有滿的時候,若是滿了還繼續加水就會溢出overflow,若是放水速度比加水速度快,那麼FIFO就會有空的時候,因此把握好加水與放水的時機和速度,保證水池一直有水是一項很艱鉅的任務。也就是判斷空與滿的狀態,擇機寫數據或讀數據。code

根據讀寫時鐘,能夠分爲同步FIFO(讀寫時鐘相同)和異步FIFO(讀寫時鐘不一樣)。同步FIFO控制比較簡單,再也不介紹,本節實驗主要介紹異步FIFO的控制,其中讀時鐘爲75MHz,寫時鐘爲100MHz。實驗中會經過VIVADO集成的在想邏輯分析儀ila,咱們能夠觀察FIFO的讀寫時序和從FIFO中讀取的數據。orm

2. 建立Vivado工程

2.1 添加FIFO IP核

在添加FIFO IP以前先新建一個fifo_test的工程, 而後在工程中添加FIFO IP,方法以下:

2.1.1點擊下圖中IP Catalog,在右側彈出的界面中搜索fifo,找到FIFO Generator,雙擊打開。

2.1.2 彈出的配置頁面中,這裏能夠選擇讀寫時鐘分開仍是用同一個,通常來說咱們使用FIFO爲了緩存數據,一般兩邊的時鐘速度是不同的。因此獨立時鐘是最經常使用的,咱們這裏選擇「Independent Clocks Block RAM」,而後點擊「Next」到下一個配置頁面。

 

2.1.3 切換到Native Ports欄目下,選擇數據位寬16;FIFO深選擇512,實際使用你們根據須要自行設置就能夠。Read Mode有兩種方式,一個Standard FIFO,也就是平時常見的FIFO,數據滯後於讀信號一個週期,還有一種方式爲First Word Fall Through,數據預取模式,簡稱FWFT模式。也就是FIFO會預先取出一個數據,當讀信號有效時,相應的數據也有效。咱們首先作標準FIFO的實驗。

2.1.4 切換到Data Counts欄目下,使能Write Data Count(已經FIFO寫入多少數據)和Read Data Count(FIFO中有多少數據能夠讀),這樣咱們能夠經過這兩個值來看FIFO內部的數據多少。點擊OK,Generate生成FIFO IP。

2.2 FIFO的端口定義與時序

信號名稱 方向 說明
rst in 復位信號,高有效
wr_clk in 寫時鐘輸入
rd_clk in 讀時鐘輸入
din in 寫數據
wr_en in 寫使能,高有效
rd_en in 讀使能,高有效
dout out 讀數據
full out 滿信號
empty out 空信號
rd_data_count out 可讀數據數量
wr_data_count out 已寫入的數據數量

FIFO的數據寫入和讀出都是按時鐘的上升沿操做的,當wr_en信號爲高時寫入FIFO數據,當almost_full信號有效時,表示FIFO只能再寫入一個數據,一旦寫入一個數據了,full信號就會拉高,若是在full的狀況下wr_en仍然有效,也就是繼續向FIFO寫數據,則FIFO的overflow就會有效,表示溢出。

標準FIFO寫時序

當rd_en信號爲高時讀FIFO數據,數據在下個週期有效。valid爲數據有效信號,almost_empty表示還有一個數據讀,當再讀一個數據,empty信號有效,若是繼續讀,則underflow有效,表示下溢,此時讀出的數據無效。

標準FIFO讀時序

而從FWFT模式讀數據時序圖能夠看出,rd_en信號有效時,有效數據D0已經在數據線上準備好有效了,不會再延後一個週期。這就是與標準FIFO的不一樣之處。

FWFT FIFO讀時序

關於FIFO的詳細內容可參考pg057文檔,可在xilinx官網下載。

3. FIFO測試程序編寫

咱們按照異步FIFO進行設計,用PLL產生出兩路時鐘,分別是100MHz和75MHz,用於寫時鐘和讀時鐘,也就是寫時鐘頻率高於讀時鐘頻率。

`timescale1ns/1ps ////////////////////////////////////////////////////////////////////////////////// module fifo_test ( input clk, //25MHz時鐘  input rst_n //復位信號,低電平有效  ); reg [15:0] w_data ; //FIFO寫數據 wire wr_en ; //FIFO寫使能 wire rd_en ; //FIFO讀使能 wire[15:0] r_data ; //FIFO讀數據 wire full ; //FIFO滿信號 wire empty ; //FIFO空信號 wire[8:0] rd_data_count ; //可讀數據數量 wire[8:0] wr_data_count ; //已寫入數據數量  wire clk_100M ; //PLL產生100MHz時鐘 wire clk_75M ; //PLL產生100MHz時鐘 wire locked ; //PLL lock信號,可做爲系統復位信號,高電平表示lock住 wire fifo_rst_n ; //fifo復位信號, 低電平有效  wire wr_clk ; //寫FIFO時鐘 wire rd_clk ; //讀FIFO時鐘 reg [7:0] wcnt ; //寫FIFO復位後等待計數器 reg [7:0] rcnt ; //讀FIFO復位後等待計數器  wire clkbuf ; BUFG BUFG_inst ( .O(clkbuf),// 1-bit output: Clock output. .I(clk)// 1-bit input: Clock input. ); //例化PLL,產生100MHz和75MHz時鐘 clk_wiz_0 fifo_pll ( // Clock out ports .clk_out1(clk_100M), // output clk_out1 .clk_out2(clk_75M), // output clk_out2 // Status and control signals .reset(~rst_n), // input reset .locked(locked), // output locked // Clock in ports .clk_in1(clkbuf) // input clk_in1 ); assign fifo_rst_n = locked ; //將PLL的LOCK信號賦值給fifo的復位信號 assign wr_clk = clk_100M ; //將100MHz時鐘賦值給寫時鐘 assign rd_clk = clk_75M ; //將75MHz時鐘賦值給讀時鐘  /* 寫FIFO狀態機 */ localparam W_IDLE =1 ; localparam W_FIFO =2 ; reg[2:0] write_state; reg[2:0] next_write_state; always@(posedge wr_clk ornegedge fifo_rst_n) begin if(!fifo_rst_n) write_state <= W_IDLE; else write_state <= next_write_state; end always@(*) begin case(write_state) W_IDLE: begin if(wcnt ==8'd79)//復位後等待必定時間,safety circuit模式下的最慢時鐘60個週期  next_write_state <= W_FIFO; else next_write_state <= W_IDLE; end W_FIFO: next_write_state <= W_FIFO; //一直在寫FIFO狀態  default: next_write_state <= W_IDLE; endcase end //在IDLE狀態下,也就是復位以後,計數器計數 always@(posedge wr_clk ornegedge fifo_rst_n) begin if(!fifo_rst_n) wcnt <=8'd0; elseif(write_state == W_IDLE) wcnt <= wcnt +1'b1; else wcnt <=8'd0; end //在寫FIFO狀態下,若是不滿就向FIFO中寫數據 assign wr_en =(write_state == W_FIFO)?~full :1'b0; //在寫使能有效狀況下,寫數據值加1 always@(posedge wr_clk ornegedge fifo_rst_n) begin if(!fifo_rst_n) w_data <=16'd1; elseif(wr_en) w_data <= w_data +1'b1; end /* 讀FIFO狀態機 */ localparam R_IDLE =1 ; localparam R_FIFO =2 ; reg[2:0] read_state; reg[2:0] next_read_state; ///產生FIFO讀的數據 always@(posedge rd_clk ornegedge fifo_rst_n) begin if(!fifo_rst_n) read_state <= R_IDLE; else read_state <= next_read_state; end always@(*) begin case(read_state) R_IDLE: begin if(rcnt ==8'd59) //復位後等待必定時間,safety circuit模式下的最慢時鐘60個週期  next_read_state <= R_FIFO; else next_read_state <= R_IDLE; end R_FIFO: next_read_state <= R_FIFO ; //一直在讀FIFO狀態  default: next_read_state <= R_IDLE; endcase end //在IDLE狀態下,也就是復位以後,計數器計數 always@(posedge rd_clk ornegedge fifo_rst_n) begin if(!fifo_rst_n) rcnt <=8'd0; elseif(write_state == W_IDLE) rcnt <= rcnt +1'b1; else rcnt <=8'd0; end //在讀FIFO狀態下,若是不空就從FIFO中讀數據 assign rd_en =(read_state == R_FIFO)?~empty :1'b0; //----------------------------------------------------------- //實例化FIFO fifo_ip fifo_ip_inst ( .rst (~fifo_rst_n ),// input rst .wr_clk (wr_clk ),// input wr_clk .rd_clk (rd_clk ),// input rd_clk .din (w_data ),// input [15 : 0] din .wr_en (wr_en ),// input wr_en .rd_en (rd_en ),// input rd_en .dout (r_data ),// output [15 : 0] dout .full (full ),// output full .empty (empty ),// output empty .rd_data_count (rd_data_count ),// output [8 : 0] rd_data_count .wr_data_count (wr_data_count )// output [8 : 0] wr_data_count ); //寫通道邏輯分析儀 ila_m0 ila_wfifo ( .clk (wr_clk ), .probe0 (w_data ), .probe1 (wr_en ), .probe2 (full ), .probe3 (wr_data_count ) ); //讀通道邏輯分析儀 ila_m0 ila_rfifo ( .clk (rd_clk ), .probe0 (r_data ), .probe1 (rd_en ), .probe2 (empty ), .probe3 (rd_data_count ) ); endmodule 

在程序中採用PLL的lock信號做爲fifo的復位,同時將100MHz時鐘賦值給寫時鐘,75MHz時鐘賦值給讀時鐘。

有一點須要注意的是,FIFO設置默認爲採用safety circuit,此功能是保證到達內部RAM的輸入信號是同步的,在這種狀況下,若是異步復位後,則須要等待60個最慢時鐘週期,在本實驗中也就是75MHz的60個週期,那麼100MHz時鐘大概須要(100/75)x60=80個週期。

所以在寫狀態機中,等待80個週期進入寫FIFO狀態

在讀狀態機中,等待60個週期進入讀狀態

若是FIFO不滿,就一直向FIFO寫數據

若是FIFO不空,就一直從FIFO讀數據

例化兩個邏輯分析儀,分別鏈接寫通道和讀通道的信號

4. 仿真

如下爲仿真結果,能夠看到寫使能wr_en有效後開始寫數據,初始值爲0001,從開始寫到empty不空,是須要必定週期的,由於內部還要作同步處理。在不空後,開始讀數據,讀出的數據相對於rd_en滯後一個週期。

在後面能夠看到若是FIFO滿了,根據程序的設計,滿了就不向FIFO寫數據了,wr_en也就拉低了。爲何會滿呢,就是由於寫時鐘比讀時鐘快。若是將寫時鐘與讀時鐘調換,也就是讀時鐘快,就會出現讀空的狀況,你們能夠試一下。

若是將FIFO的Read Mode改爲First Word Fall Through

仿真結果以下,能夠看到rd_en有效的時候數據也有效,沒有相差一個週期

5. 板上驗證

生成好bit文件,下載bit文件,會出現兩個ila,先來看寫通道的,能夠看到full信號爲高電平時,wr_en爲低電平,再也不向裏面寫數據。

而讀通道也與仿真一致

若是以rd_en上升沿做爲觸發條件,點擊運行,而後按下復位,也就是咱們綁定的PL KEY1,會出現下面的結果,與仿真一致,標準FIFO模式下,數據滯後rd_en一個週期。

相關文章
相關標籤/搜索