ZYNQ 系列 SoC 中 PS 和 PL 的交互方式有很多种,通过 BRAM 实现 PS 和 PL 的数据交互是其中的一种方式,这篇博文介绍一下如何通过 BRAM 实现 PS 和 PL 的数据交互。
应用平台
- Vivado 2019.1
- ZYNQ 7z020
一、Vivado 搭建硬件描述
新建 Block Design,依次添加 ZYNQ7 处理器(需要添加 uart),Reset,AXI SmartConnect,AXI BRAM Controller 和 BRAM Generator,然后点击自动连线,接下来添加自定义 IP pl_bram_ctrl,再次点击自动连线,最终的框图如下:
框图中涉及到的在 Vivado 中创建、封装及移植自定义 IP 核、配置 BRAM、以及AXI Bram Ctrl IP 核读写功能仿真分析等知识点,可参考之前的博文,本篇文章不再赘述。
在完成框图之后,接下来走流程,生成 output 和顶层,然后综合实现布局布线生成 bitstream,成功后 export hardware,launch SDK。然后去 SDK 中编程。
二、SDK 编程
SDK 程序的整体流程如下:
- CPU 通过 axi_bram_controller 向 BRAM 中写入原始数据;
- CPU 通过 AXI 总线向自定义 IP pl_bram_ctrl 配置起始地址、数据长度和开始读写信号;
- PL 内部向 BRAM 相同地址写入(读出数据+1)(此部分通过自定义 IP pl_bram_ctrl实现);
- CPU 再从 BRAM 中读出数据。
具体操作如下:
SDK 里先建立工程,我们建立一个空的或者 HELLO WORLD! 工程都可以,我选择建立空工程,然后在 src 里新建 main.c,main.c 的内容如下:
#include "xil_printf.h" #include "stdio.h" #include "pl_bram_plus1.h" #include "xbram.h" #include "xparameters.h" #define PL_BRAM_BASE XPAR_PL_BRAM_PLUS1_0_S00_AXI_BASEADDR //PL_RAM_RD 基地址 #define PL_BRAM_START PL_BRAM_PLUS1_S00_AXI_SLV_REG0_OFFSET //RAM 读开始寄存器地址 #define PL_BRAM_START_ADDR PL_BRAM_PLUS1_S00_AXI_SLV_REG1_OFFSET //RAM 起始寄存器地址 #define PL_BRAM_LEN PL_BRAM_PLUS1_S00_AXI_SLV_REG2_OFFSET //PL 读 RAM 的深度 #define START_ADDR 0 //RAM 起始地址 范围:0~1023 #define BRAM_DATA_BYTE 4 //BRAM 数据字节个数 int data[1024]; //写入 BRAM 的字符数组 int data_len; //写入 BRAM 的字符个数 //main 函数 int main() { int i=0,wr_cnt = 0; int read_data=0; for(int k=0;k<1024;k++) { data[k] = k; } data_len = 1024; //将用户输入的字符串写入 BRAM 中 //每次循环向 BRAM 中写入 1 个字符 for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + data_len) ;i += BRAM_DATA_BYTE) { XBram_WriteReg(XPAR_BRAM_0_BASEADDR,i,data[wr_cnt]) ; wr_cnt++; } //设置 BRAM 写入的字符串长度 PL_BRAM_PLUS1_mWriteReg(PL_BRAM_BASE, PL_BRAM_LEN , BRAM_DATA_BYTE*data_len) ; //设置 BRAM 的起始地址 PL_BRAM_PLUS1_mWriteReg(PL_BRAM_BASE, PL_BRAM_START_ADDR, BRAM_DATA_BYTE*START_ADDR) ; //拉高 BRAM 开始信号 PL_BRAM_PLUS1_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 1) ; //拉低 BRAM 开始信号 PL_BRAM_PLUS1_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 0) ; //从 BRAM 中读出数据 i = 0; //循环从 BRAM 中读出数据 for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + data_len) ;i += BRAM_DATA_BYTE) { read_data = XBram_ReadReg(XPAR_BRAM_0_BASEADDR,i) ; printf("BRAM address is %d\t,Read data is %d\n",i/BRAM_DATA_BYTE ,read_data) ; } }
设置 run configurations,连接 uart 后,下载到开发板。
从 SDK 程序可以看到,我们写入的是 data 数组的内容,就是 0-1023,我们读出来的数据如下图所示:
可以看出,串口打印出来的数据为 PL 将 PS 事先写入的数据做了加 1 处理。
以上是比较简单的 PS 和 PL 通过 BRAM 交互数据的示例,当然,也可以根据需要变更 PL 侧的逻辑,例如增加写完中断。
三、增加中断功能
更改后的框图:
这部分主要涉及到自定义 IP 核的核心代码的修改,以及 SDK 中的中断处理。
自定义 IP 的核心代码:
module ram_read_write ( input clk, input rst_n, //bram port input [31:0] din, output reg [31:0] dout, output reg en, output reg [3:0] we, output rst, output reg [31:0] addr, //control signal input start, //start to read and write bram input [31:0] init_data, //initial data defined by software output reg start_clr, //clear start register input [31:0] len, //data count input [31:0] start_addr, //start bram address //Interrupt input intr_clr, //clear interrupt output reg intr //interrupt ); assign rst = 1'b0 ; localparam IDLE = 3'd0 ; localparam READ_RAM = 3'd1 ; localparam READ_END = 3'd2 ; localparam WRITE_RAM = 3'd3 ; localparam WRITE_END = 3'd4 ; reg [2:0] state ; reg [31:0] len_tmp ; reg [31:0] start_addr_tmp ; //Main statement always @(posedge clk or negedge rst_n) begin if (~rst_n) begin state <= IDLE ; dout <= 32'd0 ; en <= 1'b0 ; we <= 4'd0 ; addr <= 32'd0 ; intr <= 1'b0 ; start_clr <= 1'b0 ; len_tmp <= 32'd0 ; start_addr_tmp <= 32'd0 ; end else begin case(state) IDLE : begin if (start) begin state <= READ_RAM ; addr <= start_addr ; start_addr_tmp <= start_addr ; len_tmp <= len ; dout <= init_data ; en <= 1'b1 ; start_clr <= 1'b1 ; end if (intr_clr) intr <= 1'b0 ; end READ_RAM : begin if ((addr - start_addr_tmp) == len_tmp - 4) //read completed begin state <= READ_END ; en <= 1'b0 ; end else begin addr <= addr + 32'd4 ; //address is byte based, for 32bit data width, adding 4 end start_clr <= 1'b0 ; end READ_END : begin addr <= start_addr_tmp ; en <= 1'b1 ; we <= 4'hf ; state <= WRITE_RAM ; end WRITE_RAM : begin if ((addr - start_addr_tmp) == len_tmp - 4) //write completed begin state <= WRITE_END ; dout <= 32'd0 ; en <= 1'b0 ; we <= 4'd0 ; end else begin addr <= addr + 32'd4 ; dout <= dout + 32'd1 ; end end WRITE_END : begin addr <= 32'd0 ; intr <= 1'b1 ; state <= IDLE ; end default : state <= IDLE ; endcase end end endmodule
SDK 中 main 函数核心代码处理流程为:
- 在 PS 端输入起始地址和长度
- CPU 通过 axi bram controller 写入 BRAM 数据
- 通知 pl_bram_ctrl 读取数据
- PL 内部读完后向相同的位置写入数据,初始数据由 CPU 告知
- 写完后使能 write_end 信号,出发 GPIO 中断
- 中断读取 BRAM 数据,打印显示
下面是 main 函数的主要代码:
/* ------------------------------------------------------------ */ /* Include File Definitions */ /* ------------------------------------------------------------ */ #include "xil_printf.h" #include "xbram.h" #include <stdio.h> #include "pl_bram_ctrl.h" #include "xscugic.h" #define BRAM_CTRL_BASE XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR #define BRAM_CTRL_HIGH XPAR_AXI_BRAM_CTRL_0_S_AXI_HIGHADDR #define PL_RAM_BASE XPAR_PL_BRAM_CTRL_0_S00_AXI_BASEADDR #define PL_RAM_CTRL PL_BRAM_CTRL_S00_AXI_SLV_REG0_OFFSET #define PL_RAM_INIT_DATA PL_BRAM_CTRL_S00_AXI_SLV_REG1_OFFSET #define PL_RAM_LEN PL_BRAM_CTRL_S00_AXI_SLV_REG2_OFFSET #define PL_RAM_ST_ADDR PL_BRAM_CTRL_S00_AXI_SLV_REG3_OFFSET #define START_MASK 0x00000001 #define INTRCLR_MASK 0x00000002 #define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID #define INTR_ID XPAR_FABRIC_PL_BRAM_CTRL_0_INTR_INTR #define TEST_START_VAL 0xC /* * BRAM bytes number */ #define BRAM_BYTENUM 4 XScuGic INTCInst; int Len ; int Start_Addr ; int Intr_flag ; /* * Function declaration */ int bram_read_write() ; int IntrInitFuntion(u16 DeviceId); void IntrHandler(void *InstancePtr); int main() { int Status; Intr_flag = 1 ; IntrInitFuntion(INTC_DEVICE_ID) ; while(1) { if (Intr_flag) { Intr_flag = 0 ; printf("Please provide start address\t\n") ; scanf("%d", &Start_Addr) ; printf("Start address is %d\t\n", Start_Addr) ; printf("Please provide length\t\n") ; scanf("%d", &Len) ; printf("Length is %d\t\n", Len) ; Status = bram_read_write() ; if (Status != XST_SUCCESS) { xil_printf("Bram Test Failed!\r\n") ; xil_printf("******************************************\r\n"); Intr_flag = 1 ; } } } } // 对 BRAM 的读写操作 int bram_read_write() { u32 Write_Data = TEST_START_VAL ; // 要写入的数据 int i ; /* * if exceed BRAM address range, assert error */ if ((Start_Addr + Len) > (BRAM_CTRL_HIGH - BRAM_CTRL_BASE + 1)/4) { xil_printf("******************************************\r\n"); xil_printf("Error! Exceed Bram Control Address Range!\r\n"); return XST_FAILURE ; } /* * Write data to BRAM */ for(i = BRAM_BYTENUM*Start_Addr ; i < BRAM_BYTENUM*(Start_Addr + Len) ; i += BRAM_BYTENUM) { XBram_WriteReg(XPAR_BRAM_0_BASEADDR, i , Write_Data) ; Write_Data += 1 ; } //Set ram read and write length PL_BRAM_CTRL_mWriteReg(PL_RAM_BASE, PL_RAM_LEN , BRAM_BYTENUM*Len) ; //Set ram start address PL_BRAM_CTRL_mWriteReg(PL_RAM_BASE, PL_RAM_ST_ADDR , BRAM_BYTENUM*Start_Addr) ; //Set pl initial data PL_BRAM_CTRL_mWriteReg(PL_RAM_BASE, PL_RAM_INIT_DATA , (Start_Addr+1)) ; //Set ram start signal PL_BRAM_CTRL_mWriteReg(PL_RAM_BASE, PL_RAM_CTRL , START_MASK) ; return XST_SUCCESS ; } int IntrInitFuntion(u16 DeviceId) { XScuGic_Config *IntcConfig; int Status ; //check device id IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); //intialization Status = XScuGic_CfgInitialize(&INTCInst, IntcConfig, IntcConfig->CpuBaseAddress) ; if (Status != XST_SUCCESS) return XST_FAILURE ; XScuGic_SetPriorityTriggerType(&INTCInst, INTR_ID, 0xA0, 0x3); Status = XScuGic_Connect(&INTCInst, INTR_ID, (Xil_ExceptionHandler)IntrHandler, (void *)NULL) ; if (Status != XST_SUCCESS) return XST_FAILURE ; XScuGic_Enable(&INTCInst, INTR_ID) ; Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, &INTCInst); Xil_ExceptionEnable(); return XST_SUCCESS ; } void IntrHandler(void *CallbackRef) { int Read_Data ; int i ; printf("Enter interrupt\t\n"); //clear interrupt status PL_BRAM_CTRL_mWriteReg(PL_RAM_BASE, PL_RAM_CTRL , INTRCLR_MASK) ; for(i = BRAM_BYTENUM*Start_Addr ; i < BRAM_BYTENUM*(Start_Addr + Len) ; i += BRAM_BYTENUM) { Read_Data = XBram_ReadReg(XPAR_BRAM_0_BASEADDR , i) ; printf("Address is %d\t Read data is %d\t\n", i/BRAM_BYTENUM ,Read_Data) ; } Intr_flag = 1 ; }
显示结果:
扫码关注尚为网微信公众号
原创文章,作者:sunev,如若转载,请注明出处:https://www.sunev.cn/embedded/1230.html