1. 程式人生 > >米聯客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM進行PS與PL間資料互動

米聯客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM進行PS與PL間資料互動

軟體版本:VIVADO2017.4

作業系統:WIN10 64bit

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

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

19.1 概述

      本課介紹一種基於PL端BRAM的方式,進行PS和PS之間的資料互動。適用於在PS和PL之間傳輸少量,地址不連續,且長度不規則的資料,比如,配置引數,變數,控制資訊等。

19.2 基本原理

       在本例程中,在PL端設計了1個4KB(位寬32,深度1024)的BRAM。首先,PS通過M_AXI_GP口向BRAM中1024個地址依次存入1024個32位的資料。PS每向BRAM完成寫入1個32位資料後通過AXI GPIO輸出1個上升沿訊號,PL捕獲上升沿後立即將PS寫入的32位資料讀出,然後加2,再存入原地址中。儲存完成後,PL通過AXI GPIO向PS輸入1個翻轉訊號,每翻轉1次,AXI GPIO便向PS觸發1次中斷。PS觸發中斷後,再從BRAM中讀出該資料,判斷是否被加了2,若不一致,則報錯。

       以上過程重複1024次,便將BRAM的所有地址都進行了遍歷。之後,則不斷重複這個過程。

19.3 FPGA BD工程

        VIVADO 2017.4開始可以使用axi_smc IP這個IP和AXI_interconnect IP功能類似,但是沒有AXI_interconnect IP強大,在一些高頻寬的要求下,還是要用AXI_interconnect IP,VIVADO2017.4自動連線會優先使用axi_smc IP。

      以下是VIVADO 2016.4版本以前的自動聯絡,只會採用AXI_interconnect IP。所以從VIVADO2017.4開始我們多了axi_smc IP進行AXI4匯流排介面的互聯。

19.3.2 PS配置

      PS的配置如下圖所示。使能M_AXI_GP0口,將FCLK_CLK0設為100MHz,使能PL至PS的中斷。

19.3.3 AXI BRAM Controller

      AXI BRAM Controller是本例程中的關鍵,該IP核連線PS的M_AXI_GP0口和BRAM,完成AXI介面至BRAM介面的轉換。設定如下圖所示。

19.3.4 Block Memory Generator

       新增BRAM,將BRAM設定為雙口RAM,將PORTA與AXI BRAM Controller連線,PS通過PORTA讀寫BRAM,另外,將PORTB引出至外部模組,PL通過PORTB讀寫BRAM。如下圖所示。

      由於要與AXI BRAM Controller進行連線,BRAM介面的位寬固定為32位。BRAM的深度無法在該IP中進行設定,需要在所有模組連線完成後,在Address Editor裡對AXI BRAM Controller的地址範圍進行設定,本例程中設定為4KB。該地址範圍即對應BRAM的深度,如下圖所示。

例如,4KB=32bit×1024,因此BRAM的深度為1024。如下圖所示。

去掉Enable Safety Circuit

19.3.5 AXI GPIO

       新增AXI GPIO,位寬設為2,使能中斷,將中斷輸出與PS的中斷介面連線,設定如下圖所示。其中GPIO0作為PS向PL輸出的PS BRAM寫入完成訊號,對於PL而言,上升沿有效。GPIO1作為PL向PS輸入的BRAM寫入完成訊號,該訊號為翻轉訊號,每次翻轉向PS產生1次中斷。

19.4邏輯設計

       PL部分邏輯設計主要包括以下幾個過程:

       檢測AXI GPIO輸出的GPIO0的上升沿;

       若檢測到GPIO0的上升沿,則從BRAM的某1個地址中讀出1個PS寫入32位資料,然後加2,存入原地址中;

      1個32資料儲存完畢後,將AXI GPIO的輸入訊號GPIO1進行翻轉,告知PS一次資料讀寫完成。

      不斷迴圈上述過程,依次遍歷BRAM中0~4092的地址範圍。

module system_wrapper_BRAM(

   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 [31:0]BRAM_PORTB_addr;

  wire BRAM_PORTB_clk;

  wire [31:0]BRAM_PORTB_din;

  wire [31:0]BRAM_PORTB_dout;

  wire BRAM_PORTB_en;

  wire BRAM_PORTB_rst;

  wire [3:0]BRAM_PORTB_we;

  wire [0:0]GPIO_tri_i_0;

  wire [1:1]GPIO_tri_i_1;

  wire [0:0]GPIO_tri_o_0;

  wire [1:1]GPIO_tri_o_1;

  wire [0:0]aresetn;

  

   reg gpio_tri_o_0_reg;

   reg ps_bram_wr_done;

   reg pl_bram_wr_done;

   reg bram_en;

   reg [3:0]  bram_we;

   reg [31:0] bram_addr;

   reg [31:0] bram_rd_data;

   reg [31:0] bram_wr_data;

   reg [2:0] state;

   

   localparam  BRAM_ADDRESS_HIGH = 32'd4096 - 32'd4;

   

   always@(posedge FCLK_CLK0)

     begin

         if(!aresetn)

             gpio_tri_o_0_reg <= 1'b0;

         else

             gpio_tri_o_0_reg <= GPIO_tri_o_0;

     end

   

   always@(posedge FCLK_CLK0)

     begin

         if(!aresetn)

             ps_bram_wr_done <= 1'b0;

         else if({gpio_tri_o_0_reg, GPIO_tri_o_0} == 2'b01) //gpio0 rising edge

             ps_bram_wr_done <= 1'b1;

         else

             ps_bram_wr_done <= 1'b0;

     end

     

   always@(posedge FCLK_CLK0)

     begin

         if(!aresetn) begin

             bram_we <= 4'd0;

             bram_en <= 1'b0;

             bram_addr <= 32'd0;

             bram_rd_data <= 32'd0;

             bram_wr_data <= 32'd0;

             pl_bram_wr_done <= 1'b0;

             state <= 3'd0;

         end

         else begin

             case(state)

             0: begin

                   if(ps_bram_wr_done) begin

                       bram_en <= 1'b1;

                         bram_we <= 4'd0;

                       state <= 1;

                   end

                   else begin

                       state <= 0;

                       bram_en <= 1'b0;

                         bram_we <= 4'd0;

                       bram_addr <= bram_addr;

                   end

                end

             1: begin

                   bram_en <= 1'b0;

                   state <= 2;

                end

             2: begin

                   bram_rd_data <= BRAM_PORTB_dout;

                   state <= 3;

                end            

             3: begin

                   bram_en <= 1'b1;

                   bram_we <= 4'hf;

                   bram_wr_data <= bram_rd_data + 2;

                   pl_bram_wr_done <= ~pl_bram_wr_done;

                   state <= 4;

                end

             4: begin

                   state <= 0;

                   bram_en <= 1'b0;

                   if(bram_addr == BRAM_ADDRESS_HIGH)

                      bram_addr <= 32'd0;

                   else

                      bram_addr <= bram_addr + 32'd4;

                end

             default: state <= 0;

             endcase

         end

     end

     

   assign BRAM_PORTB_en = bram_en;

   assign BRAM_PORTB_we = bram_we;

   assign BRAM_PORTB_rst = ~aresetn;

   assign BRAM_PORTB_clk = FCLK_CLK0;

   assign BRAM_PORTB_addr = bram_addr;

   assign BRAM_PORTB_din = bram_wr_data;

   

   assign GPIO_tri_i_1 = pl_bram_wr_done;

  system system_i

       (.BRAM_PORTB_addr(BRAM_PORTB_addr),

        .BRAM_PORTB_clk(BRAM_PORTB_clk),

        .BRAM_PORTB_din(BRAM_PORTB_din),

        .BRAM_PORTB_dout(BRAM_PORTB_dout),

        .BRAM_PORTB_en(BRAM_PORTB_en),

        .BRAM_PORTB_rst(BRAM_PORTB_rst),

        .BRAM_PORTB_we(BRAM_PORTB_we),

        .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),

        .FCLK_CLK0(FCLK_CLK0),

        .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),

        .GPIO_tri_i({GPIO_tri_i_1,GPIO_tri_i_0}),

        .GPIO_tri_o({GPIO_tri_o_1,GPIO_tri_o_0}),

        .GPIO_tri_t(),

        .aresetn(aresetn));

19.4.1 BRAM讀時序

       BRAM讀時序如下圖所示。

19.4.2 BRAM寫時序

      BRAM寫時序如下圖所示。

19.5 PS程式設計

      將提供例程中SDK工程的原始檔複製,並貼上到新建SDK工程。

19.5.1 main函式

       main函式的完成的功能如下:

       配置AXI GPIO及其中斷;

       初始化中斷控制器及系統中斷;

       依次向BRAM所對應的地址寫入1024個32位整型資料,每寫入1個數據等待PL的讀寫完成中斷來臨後繼續寫入下一個;

       依次從BRAM所對應的地址讀出1024個32位整型資料,並判斷是否被加了2,若比對不一致則報錯。

19.5.2 GPIO輸入輸出

       在main函式呼叫Gpiopl_init()函式初始化AXI GPIO,設定2個GPIO的方向,其中GPIO[0]為輸出,GPIO[1]為輸入。並將GPIO[0]置為0。每個GPIO的巨集定義在gpiopl_intr.h中,如下所示。

#define PS_BRAM_MASK            0x00000001

#define PL_INTR_MASK             0x00000002

      通過Gpiopl_Setup_Intr_System()初始化並使能AXI GPIO的輸入中斷,GPIO[1]的輸入發生1次改變將觸發1次中斷,GpioplIntrHandler()為GPIO的中斷函式。

      GpioplIntrHandler()函式將PL讀寫BRAM完成中斷標誌位pl_bram_flag置1,該訊號將在PS寫入BRAM時使用。

19.5.3 BRAM資料寫入

      每一個迴圈PS向BRAM寫入1024個32bit整型資料,每次迴圈後將下一次寫入的1024個數據都加1。因此,第1次寫入BRAM的資料為0~1023,第2次寫入的為1~1024,第3次為2~1025,以此類推。BRAM寫入通過如下函式完成:

      Xil_Out32(XPAR_BRAM_0_BASEADDR + 4*i, write_data);

      由於在ZYNQ中最小可定址單元為位元組,因此1個32位資料需佔用4個地址,每次寫入的地址都需要加4。

      每次寫入完成後,拉高GPIO0,通過如下程式碼完成:

      XGpio_DiscreteWrite(&Gpio, 1, PS_BRAM_MASK);

      然後,PS等待PL完成BRAM中該地址32bit資料的讀寫,PL翻轉GPIO1使AXI GPIO產生中斷,程式碼如下所示:

      while(!pl_bram_flag);

      pl_bram_flag = 0;

      PL完成BRAM讀寫後,PS拉低GPIO0,通過如下程式碼完成:

      XGpio_DiscreteWrite(&Gpio, 1, ~PS_BRAM_MASK);

      迴圈1024次,完成1024個數據的寫入。

19.5.4 BRAM資料讀出

      當PS完成1024個數據的寫入,此時PL也完成了1024個數據的讀出、加2、寫入工作。因此,PS需要依次將1024個數據讀出進行比對,驗證PL給每個資料加2的正確性,因此,第1次讀出的1024個數據應該為2~1025,第2次為3~1026,第3次為4~1027,以此類推。若比對出現不一致,則通過串列埠進行報錯。程式碼如下:

for(i = 0; 4*i < (XPAR_BRAM_0_HIGHADDR - XPAR_BRAM_0_BASEADDR); i++)

{

read_data = Xil_In32(XPAR_BRAM_0_BASEADDR + 4*i);

//xil_printf("data at address %d is %d\r\n", 4*i, read_data);

/*compare data read form bram if they are add by 2*/

if(read_data != (i + j + 2))

xil_printf("error: data at address %d should be %d, but is %d\r\n", 4*i, (i + j + 2), read_data);

}

 

       其中,可通過xil_printf("data at address %d is %d\r\n", 4*i, read_data);檢視每一次從BRAM讀出的資料的具體值,若無需使用則可註釋掉。

19.6 程式測試

      程式測試串列埠列印資訊如下圖所示。

      第1次讀出的1024個數據。

     第2次讀出的1024個數據。

     第3次讀出的1024個數據。

      後面不作一一列舉。

      本課讀寫的速度相對較慢,讀者可以嘗試修改程式,實現批