米联客 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。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第1张图片

      以下是VIVADO 2016.4版本以前的自动联系,只会采用AXI_interconnect IP。所以从VIVADO2017.4开始我们多了axi_smc IP进行AXI4总线接口的互联。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第2张图片

19.3.2 PS配置

      PS的配置如下图所示。使能M_AXI_GP0口,将FCLK_CLK0设为100MHz,使能PL至PS的中断。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第3张图片

19.3.3 AXI BRAM Controller

      AXI BRAM Controller是本例程中的关键,该IP核连接PS的M_AXI_GP0口和BRAM,完成AXI接口至BRAM接口的转换。设置如下图所示。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第4张图片

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第5张图片

19.3.4 Block Memory Generator

       添加BRAM,将BRAM设置为双口RAM,将PORTA与AXI BRAM Controller连接,PS通过PORTA读写BRAM,另外,将PORTB引出至外部模块,PL通过PORTB读写BRAM。如下图所示。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第6张图片

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第7张图片

      由于要与AXI BRAM Controller进行连接,BRAM接口的位宽固定为32位。BRAM的深度无法在该IP中进行设置,需要在所有模块连接完成后,在Address Editor里对AXI BRAM Controller的地址范围进行设置,本例程中设置为4KB。该地址范围即对应BRAM的深度,如下图所示。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第8张图片

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

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第9张图片

去掉Enable Safety Circuit

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第10张图片

19.3.5 AXI GPIO

       添加AXI GPIO,位宽设为2,使能中断,将中断输出与PS的中断接口连接,设置如下图所示。其中GPIO0作为PS向PL输出的PS BRAM写入完成信号,对于PL而言,上升沿有效。GPIO1作为PL向PS输入的BRAM写入完成信号,该信号为翻转信号,每次翻转向PS产生1次中断。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第11张图片

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读时序如下图所示。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第12张图片

19.4.2 BRAM写时序

      BRAM写时序如下图所示。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第13张图片

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个数据。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第14张图片

     第2次读出的1024个数据。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第15张图片

     第3次读出的1024个数据。

米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互_第16张图片

      后面不作一一列举。

      本课读写的速度相对较慢,读者可以尝试修改程序,实现批量数据的读写。

转载于:https://my.oschina.net/msxbo/blog/3100642

你可能感兴趣的:(米联客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM进行PS与PL间数据交互)