软件版本: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个数据。
后面不作一一列举。
本课读写的速度相对较慢,读者可以尝试修改程序,实现批量数据的读写。