1. 程式人生 > >米聯客 ZYNQ/SOC 精品教程 S03-CH01基於FDMA記憶體讀寫測試

米聯客 ZYNQ/SOC 精品教程 S03-CH01基於FDMA記憶體讀寫測試

軟體版本:VIVADO2017.4

作業系統:WIN10 64bit

硬體平臺:適用米聯客 ZYNQ系列開發板

米聯客(MSXBO)論壇:www.osrc.cn答疑解惑專欄開通,歡迎大家給我提問!!

1.1概述

      FDMA是MSXBO(米聯客的)基於AXI4匯流排協議定製的一個DMA控制器。有了這個IP我們可以統一實現用FPGA程式碼直接讀寫PL的DDR或者ZYNQ PS的DDR。

      如果用過ZYNQ的都知道,要直接操作PS的DDR 通常是DMA 或者VDMA,然而用過XILINX 的DMA IP 和VDMA IP,總有一種遺憾,那就是不夠靈活,還需要對暫存器配置,真是麻煩。對於我們搞FPGA的人來說,最喜歡直接了當,直接用FPGA程式碼搞定。現在XILINX 的匯流排介面是AXI4匯流排,那麼熟練自定義AXI4 IP掛到總線上就非常方便了。基於這個目的,本小編定義了一個基於AXI4 FULL MASTER的IP,暫且取名為MSXBO_FDMA。

     通過這個IP我們可以方便地進行AXI4 FULL MASTER的操作,比如我們經常要讀寫DDR,那麼只要掛到AXI4總線上就可以利用這個IP實現。

     以下是小編展示了一種在ZYNQ上的讀寫PS DDR測試方案。

1.2基於FDMA搭建的BD工程

     首先看下基於FDMA搭建的BD工程,這裡使用的是ZYNQ IP搭建。

1.3 ZYNQ PS IP配置

      和使用PL DDR MIG 不同,MZ7X是ZYNQ晶片只有PS有DDR 所以訪問DDR需要配置ZYNQ IP這樣配置好後就可以通過AXI匯流排範圍PS的DDR了。

      配置輸入時鐘

     配置DDR 

   僅配置HP介面

  地址空間分配

1.3編寫FDMA測試程式碼

首先右擊BD 並且單擊

其次繼續右擊BD檔案,選擇Create HDL Wrapper

最後修改Wrapper並且新增測試程式碼,測試程式碼如下

`timescale 1ns / 1ps

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

// Company:CZ123 MSXBO www.osrc.cn

// Engineer: tjy

// Create Date: 2019/04/02 12:39:25

// Design Name:

// Module Name: fdma_top

// Project Name: AXI_FDMA

// Target Devices:

// Tool Versions: VIVADO

// Description: test DDR

// Dependencies:

// Revision:

// Revision 0.01 - File Created

// Additional Comments:

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

module fdma_top(

inout [14:0]DDR_addr,

inout [2:0]DDR_ba,

inout DDR_cas_n,

inout DDR_ck_n,

inout DDR_ck_p,

inout DDR_cke,

inout DDR_cs_n,

inout [3:0]DDR_dm,

inout [31:0]DDR_dq,

inout [3:0]DDR_dqs_n,

inout [3:0]DDR_dqs_p,

inout DDR_odt,

inout DDR_ras_n,

inout DDR_reset_n,

inout DDR_we_n,

inout FIXED_IO_ddr_vrn,

inout FIXED_IO_ddr_vrp,

inout [53:0]FIXED_IO_mio,

inout FIXED_IO_ps_clk,

inout FIXED_IO_ps_porb,

inout FIXED_IO_ps_srstb

);

 

  wire [0:0]ui_rstn;

  wire ui_clk;

  //-----------------fmda signals--------------------------------------

  wire [31:0] pkg_wr_addr;

 (*mark_debug = "true"*)  wire  [31:0] pkg_wr_data;

 (*mark_debug = "true"*) (* KEEP = "TRUE" *)  reg         pkg_wr_areq;

 (*mark_debug = "true"*)  wire        pkg_wr_en;

 (*mark_debug = "true"*)  wire        pkg_wr_last;

  wire [31:0] pkg_wr_size;

  wire [31:0] pkg_rd_addr;

 (*mark_debug = "true"*)  wire  [31:0] pkg_rd_data;

 (*mark_debug = "true"*) (* KEEP = "TRUE" *) reg         pkg_rd_areq;

 (*mark_debug = "true"*)  wire        pkg_rd_en;

 (*mark_debug = "true"*)  wire        pkg_rd_last;

  wire [31:0] pkg_rd_size;

 //---------------------------------------------------------------------

  reg [31:0]pkg_wr_cnt;

 (*mark_debug = "true"*) (* KEEP = "TRUE" *) reg [31:0]pkg_rd_cnt;

 (*mark_debug = "true"*) (* KEEP = "TRUE" *) reg [1:0] T_S;

 

  reg [31:0] pkg_addr;

 

  parameter WRITE1 = 0;

  parameter WRITE2 = 1;

  parameter READ1  = 2;

  parameter READ2  = 3;

  //-----------------  

  assign pkg_wr_size = 1024;

  assign pkg_rd_size = 1024;

  

  assign pkg_wr_data = pkg_wr_cnt;

  (*mark_debug = "true"*) wire test_error;

  assign test_error = (pkg_rd_en && (pkg_rd_cnt != pkg_rd_data));

  

  parameter DDR_BASE = (10*1024*1024);

  

  assign pkg_wr_addr = pkg_addr+ DDR_BASE;

  assign pkg_rd_addr = pkg_addr+ DDR_BASE;

  

  always @(posedge ui_clk)

  begin

    if(!ui_rstn)begin

        T_S <=0;

        pkg_wr_areq <= 1'b0;

        pkg_rd_areq <= 1'b0;         

        pkg_wr_cnt<=0;

        pkg_rd_cnt<=0;

        pkg_addr<=0;       

    end

    else begin

        case(T_S)

        WRITE1:begin

            if(pkg_addr>=32'd536870911) pkg_addr<=0;

            pkg_wr_areq  <= 1'b1;

            T_S <= WRITE2;

        end

        WRITE2:begin

            pkg_wr_areq  <= 1'b0;

            if(pkg_wr_last) begin

                 T_S <= READ1;

                 pkg_wr_cnt <= 32'd0;

            end

            else if(pkg_wr_en) begin

                pkg_wr_cnt <= pkg_wr_cnt + 1'b1;

            end

        end

        READ1:begin

            pkg_rd_areq <= 1'b1;

            T_S <= READ2;   

        end   

        READ2:begin

            pkg_rd_areq <= 1'b0;

            if(pkg_rd_last) begin

                T_S <= WRITE1;  

                pkg_addr <= pkg_addr + 4096;

                pkg_rd_cnt <= 32'd0;

            end

            else if(pkg_rd_en) begin

                pkg_rd_cnt <= pkg_rd_cnt + 1'b1;

            end

        end             

        endcase

    end

  end

  

system system_i

       (.DDR_addr(DDR_addr),

        .DDR_ba(DDR_ba),

        .DDR_cas_n(DDR_cas_n),

        .DDR_ck_n(DDR_ck_n),

        .DDR_ck_p(DDR_ck_p),

        .DDR_cke(DDR_cke),

        .DDR_cs_n(DDR_cs_n),

        .DDR_dm(DDR_dm),

        .DDR_dq(DDR_dq),

        .DDR_dqs_n(DDR_dqs_n),

        .DDR_dqs_p(DDR_dqs_p),

        .DDR_odt(DDR_odt),

        .DDR_ras_n(DDR_ras_n),

        .DDR_reset_n(DDR_reset_n),

        .DDR_we_n(DDR_we_n),

        .FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),

        .FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),

        .FIXED_IO_mio(FIXED_IO_mio),

        .FIXED_IO_ps_clk(FIXED_IO_ps_clk),

        .FIXED_IO_ps_porb(FIXED_IO_ps_porb),

        .FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),

 

        .pkg_wr_addr(pkg_wr_addr),

        .pkg_wr_data(pkg_wr_data),

        .pkg_wr_areq(pkg_wr_areq),

        .pkg_wr_en  (pkg_wr_en),

        .pkg_wr_last(pkg_wr_last),

        .pkg_wr_size(pkg_wr_size),

                

        .pkg_rd_addr(pkg_rd_addr),

        .pkg_rd_data(pkg_rd_data),

        .pkg_rd_areq(pkg_rd_areq),  

        .pkg_rd_en  (pkg_rd_en),   

        .pkg_rd_last(pkg_rd_last),

        .pkg_rd_size(pkg_rd_size),

 

        .ui_clk(ui_clk),

        .fdma_rstn(ui_rstn)    

        );

        

endmodule

以上程式碼中需要注意紅色部分程式碼,PS的DDR 地址需要偏移大約1MB ,這裡偏移10MB並且確保不和應用程式的DDR空間衝突。

1.5測試程式碼狀態機分析

  1. WRITE1狀態:為了測試整個DDR的儲存控制元件,所以先計算DDR大小,536870911正好是一片512MB DDR的大小。根據之前BD裡面FDMA的引數設定,一次AXI4 burst大小為256,那麼每次傳輸1024byte(256x32/8)。我們設定pkg_wr_size 和pkg_rd_size大小為1024,那麼這裡每次parket 傳輸大小為4096bytes(1024x32bit/8)。pkg_wr_size 和pkg_rd_size的大小是以32bit計算。pkg_wr_size 和pkg_rd_size大小的設定需要注意pkg_wr_size需要是FDMA裡面 AXI BURST LEN的整數倍。
  2. WRITE2狀態: WRITE1通過設定pkg_wr_areq為1持續1個時鐘週期,後進入WRITE2狀態機,並且啟動啟動一次FDMA的parket傳輸。當pkg_wr_last為1的時候表示一次parket傳輸傳輸結束。在WRITE2狀態機中使用了一個計數器,把計數器的值寫入到DDR中。
  3. READ1狀態:在READ1狀態設定pkg_rd_areq為1啟動一次FMDA的parket read 傳輸,之後進入READ2狀態。
  4. READ2狀態:當pkg_rd_las 為1代表一次read傳輸結束,並且地址空間地址增加。在READ2狀態,通過一個計數器計數並且對比和從DDR裡面讀出的資料,看是否有錯誤。

assign test_error = (pkg_rd_en && (pkg_rd_cnt != pkg_rd_data));

1.6測試結果

      匯入硬體工程到SDK 並且新建一個helloworld程式,之後下載程式測試,如果讀者這個地方不清楚,可以先學習ZYNQ基礎方面的教程內容,掌握如何除錯ZYNQ。

      可以看到上圖中error 訊號一直為0代表資料讀寫對比沒有錯誤。再看pkg_wr_en和pkg_rd_en訊號,可以看到他們是每次burst了4次每次的長度是256。證明我們的FDMA工作正常,測試程式工作正常。下面放大資料部分。

       上圖是寫操作往記憶體裡面寫資料。

      上圖是讀操作放大後看到的資料分別是計數器和從記憶體裡