ZYNQ数据交互:通过BRAM实现PS和PL的数据交互

ZYNQ 系列 SoC 中 PS 和 PL 的交互方式有很多种,通过 BRAM 实现 PS 和 PL 的数据交互是其中的一种方式,这篇博文介绍一下如何通过 BRAM 实现 PS 和 PL 的数据交互。

ZYNQ数据交互:通过BRAM实现PS和PL的数据交互

应用平台

  1. Vivado 2019.1
  2. ZYNQ 7z020

一、Vivado 搭建硬件描述

新建 Block Design,依次添加 ZYNQ7 处理器(需要添加 uart),Reset,AXI SmartConnect,AXI BRAM Controller 和 BRAM Generator,然后点击自动连线,接下来添加自定义 IP pl_bram_ctrl,再次点击自动连线,最终的框图如下:

ZYNQ数据交互:通过BRAM实现PS和PL的数据交互

框图中涉及到的在 Vivado 中创建、封装及移植自定义 IP 核配置 BRAM、以及AXI Bram Ctrl IP 核读写功能仿真分析等知识点,可参考之前的博文,本篇文章不再赘述。

ZYNQ IP核:AXI_Bram_Ctrl IP核的读写功能仿真分析

ZYNQ IP核:AXI_Bram_Ctrl IP核的读写功能仿真分析

AXI Bram Ctrl IP 核是 ZYNQ 系列 SoC 用于 PS 和 PL 数据交互的方式之一,该 IP 核的功能是将 AXI4 或者 AXI4-lite 接口与 BRA…

ZYNQ IP核:BRAM IP核的配置及PL读写BRAM仿真分析

ZYNQ IP核:BRAM IP核的配置及PL读写BRAM仿真分析

 BRAM 就是 Block RAM,是 ZYNQ PL 端的存储单元。可以利用 BRAM,在 PS 和 PL 之间进行数据交换。ZYNQ 中还有一类存储器,称为分布式随机访问存储…

在Vivado中创建及封装AXI4接口的自定义IP核

在Vivado中创建及封装AXI4接口的自定义IP核

在 Vivado 中开发 ZYNQ 系列 SoC 时,AXI4 是使用频次较高的总线,有时候为了使 FPGA 功能模块化,还会将一些特定的模块封装成 AXI4 总线接口的自定义 I…

在完成框图之后,接下来走流程,生成 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,我们读出来的数据如下图所示:

ZYNQ数据交互:通过BRAM实现PS和PL的数据交互

可以看出,串口打印出来的数据为 PL 将 PS 事先写入的数据做了加 1 处理。

以上是比较简单的 PS 和 PL 通过 BRAM 交互数据的示例,当然,也可以根据需要变更 PL 侧的逻辑,例如增加写完中断。

三、增加中断功能

更改后的框图:

ZYNQ数据交互:通过BRAM实现PS和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 函数核心代码处理流程为:

  1. 在 PS 端输入起始地址和长度
  2. CPU 通过 axi bram controller 写入 BRAM 数据
  3. 通知 pl_bram_ctrl 读取数据
  4. PL 内部读完后向相同的位置写入数据,初始数据由 CPU 告知
  5. 写完后使能 write_end 信号,出发 GPIO 中断
  6. 中断读取 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 ;
}

显示结果:

ZYNQ数据交互:通过BRAM实现PS和PL的数据交互

扫码关注尚为网微信公众号

尚为网微信公众号
每天学习电路设计嵌入式系统的专业知识,关注一波,没准就用上了。

原创文章,作者:sunev,如若转载,请注明出处:https://www.sunev.cn/embedded/1230.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年6月21日
下一篇 2022年6月29日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注