ZYNQ学习之路(三):自定义IP实现PL处理PS写入BRAM的数据

目录

一、实验简介

二、vivado部分处理

三、SDK编程

四、实验测试

五、总结


一、实验简介

ZYNQ系列嵌入式FPGA可以使PS将数据写入PL部分BRAM,PL可以将数据读取后再重新写入BRAM,PS将数据读出后再传走,这样可以使PL部分专心处理数据,不用考虑相对缓慢的数据接收与发送部分,并且可以实现数据的缓存。这也是数据量较小时的PS与PL的数据交互方法,当数据量达到BRAM的存储最大值后,就需要使用DDR来进行交互了。

实验目的:通过自定义IP核,将PS写入BRAM的数据加一后重新写入BRAM,再与第一次写入的数据比较。

二、vivado部分处理

首先创建IP核:Tools->Creat and Package New IP,选择创建AXI4 Peripheral:

在 Name 一栏的名称改为“ pl_bram_plus1”, IP 核的路径改为工程目录下的 ip_repo 文件夹,即删除路径“ /../”中间的一个“ .”符号,其它的设置直接保持默认即可,点击“ NEXT”,直到最后点击“ Finish”按钮完成自定义 IP 核的创建。

到这里IP核的框架已经搭建好了,接下来要去进行细节定义。首先打开IP Catalog,依次展开 User Repository→AXI Peripheral→“ pl_bram_plus1_v1.0”,右键选择“ Edit in IP Packager”。

打开 pl_bram_plus1_v1_0.v 文件,在 Users to add ports here 和 User port ends 中间行添加如下代码,这些端口用于连接 BRAM 端口的 BRAM_PORTB。

output wire ram_clk ,                  //RAM 时钟
input wire [31:0] ram_rd_data,         //RAM 中读出的数据
output wire ram_en ,                   //RAM 使能信号
output wire [31:0] ram_addr ,          //RAM 地址
output wire [3:0] ram_we ,             //RAM 读写控制信号
output wire [31:0] ram_wr_data,        //RAM 写数据
output wire ram_rst ,                  //RAM 复位信号,高电平有

在实例化 pl_ram_plus1_v1_0_S00_AXI 模块的位置,添加以下代码。这里需要注意一下例化时的逗号问题,如果添加在最后,需要在之前的最后一个信号后添加逗号。
 

.ram_clk (ram_clk ),
.ram_rd_data (ram_rd_data),
.ram_en (ram_en ),
.ram_addr (ram_addr ),
.ram_we (ram_we ),
.ram_wr_data (ram_wr_data),
.ram_rst (ram_rst )

打开 pl_bram_plus1_v1_0_S00_AXI.v 文件,同样在 Users to add ports here 和 User port ends 中间行添加如下代码:
 

output wire ram_clk ,                //RAM 时钟
input wire [31:0] ram_rd_data,       //RAM 中读出的数据
output wire ram_en ,                 //RAM 使能信号
output wire [31:0] ram_addr ,        //RAM 地址
output wire [3:0] ram_we ,           //RAM 读写控制信号
output wire [31:0] ram_wr_data,      //RAM 写数据
output wire ram_rst ,                //RAM 复位信号,高电平有

 在程序最后 Add user logic here 和 User logic ends 的中间行,添加如下代码:

bram_ip_plus1 inst_bram_ip_plus1(
		.clk (S_AXI_ACLK),
		.rst_n (S_AXI_ARESETN),
		.start_rd (slv_reg0[0]),
		.start_addr (slv_reg1),
		.rd_len (slv_reg2),
		//RAM 端口
		.ram_clk (ram_clk ),
		.ram_rd_data (ram_rd_data),
		.ram_en (ram_en ),
		.ram_addr (ram_addr ),
		.ram_we (ram_we ),
		.ram_wr_data (ram_wr_data),
		.ram_rst (ram_rst )
	);

 接下来在工程中创建一个新的模块,命名为“ bram_rd”,位于../BRAMIP/ip_repo/pl_bram_plus1_1.0/hdl路径下, bram_ip_plus1 模块的代码如下:

module bram_ip_plus1(
	input clk , //时钟信号
	input rst_n , //复位信号
	input start_rd , //读开始信号
	input [31:0] start_addr , //读起始地址
	input [31:0] rd_len , //读数据的长度
	//RAM 端口
	output ram_clk , //RAM 时钟
	input [31:0] ram_rd_data, //RAM 中读出的数据
	output reg ram_en , //RAM 使能信号
	output reg [31:0] ram_addr , //RAM 地址
	output reg [3:0] ram_we , //RAM 读写控制信号
	output reg [31:0] ram_wr_data, //RAM 写数据
	output ram_rst //RAM 复位信号,高电平有效
	);
	
	 //reg define
	reg [1:0] flow_cnt;
	reg start_rd_d0;
	reg start_rd_d1;
	
	//wire define
	wire pos_start_rd;
	
	//*****************************************************
	//** main code
	//*****************************************************
	
	assign ram_rst = 1'b0;
	assign ram_clk = clk ;
	assign pos_start_rd = ~start_rd_d1 & start_rd_d0;
	
	//延时两拍,采 start_rd 信号的上升沿
	always @(posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		start_rd_d0 <= 1'b0;
		start_rd_d1 <= 1'b0;
	end
	else begin
		start_rd_d0 <= start_rd;
		start_rd_d1 <= start_rd_d0;
		end
	end
	
	//根据读开始信号,从 RAM 中读出数据
	always @(posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		flow_cnt <= 2'd0;
		ram_en <= 1'b0;
		ram_addr <= 32'd0;
		ram_we <= 4'd0;
		end
	 else begin
	 case(flow_cnt)
	2'd0 : begin
	if(pos_start_rd) begin
		ram_en <= 1'b1;
		ram_addr <= start_addr;
		flow_cnt <= flow_cnt + 2'd1;

	end
	end
	2'd1 : begin
	ram_wr_data <= ram_rd_data + 1;
	flow_cnt <= flow_cnt + 2'd1;
	ram_we <= 4'b1111;
	end 
	2'd2:begin
		if(ram_addr - start_addr == rd_len - 4) begin //数据读完
			flow_cnt <= flow_cnt + 2'd1;
		end
		else begin
			ram_addr <= ram_addr + 32'd4; //地址累加 4
			ram_we <= 4'b0000;
			flow_cnt <= flow_cnt - 2'd1;
		end		
	end
	 2'd3 : begin
	 ram_addr <= 32'd0;
	 flow_cnt <= 2'd0;
	 ram_en <= 1'b0;
	 end
	 endcase
	 end
	 end
	
endmodule

代码都添加完毕后,进行IP封装。

双击 IP-XACT 界面下的 component.xml 切换至 Packaging Steps 界面。点击“ File Groups”一栏,随后点击界面上的“ Merge changes from File Groups Wizard”;点击“ Customization Parameters”一栏,随后点击界面上的“ Merge changes from Customization Parameters Wizard”。点击“ Ports and Interfaces”一栏,可以看到该 IP 核顶层模块的端口,为了方便在 Diagram 界面中对自定义的 IP 核的 BRAM 端口进行连线,我们需要将 BRAM 相关的端口定义成总线接口的形式,方法如下。
点击“ Add Bus Interfac”图标,如下图所示:

 在弹出的页面中,在 Name 一栏,输入 BRAM_PORT,然后点击 Interface Definition(总线定义)右侧的“ …”图标,如下图所示:

接下来在弹出页面的搜索框中输入“ BRAM”,选中 Advanced 一栏下的“ bram_rtl”,即 BRAM 总线接口。然后点击“ OK”按钮,如下图所示:
 

将页面切换至“Port Mapping”选项页,左侧窗口为总线逻辑端口,右侧窗口为 IP 核定义的物理端口,最下面的窗口是映射端口的总结页面,如下图所示:
 

点击左侧的“EN”端口,然后点击右侧的“ ram_en”端口,此时页面上的“ Map Ports”变成可以点击的按钮,点击这个按钮,此时在映射端口总结页面可以看到这两个端口映射到了一起,如下图所示:
 

这个步骤是将顶层模块定义的“ ram_en”端口和 BRAM 的“ EN”端口映射到一起,我们接下来将其余接口分别一一映射,如下图所示:
 

接下来将页面切换至“ Parameters”选项页,展开 Auto-calculated,选中“ MASTER_TYPE”,然后点击单个向右的箭头,如下图所示:

此时,“ MASTER_TYPE”参数会出现在右侧 Overridden 目录下,最后点击“OK”按钮完成 BRAM 接口的封装,如下图所示:
 

 接下来切换至“Review and Package”页面,点击上侧的“ IP has been modified”来更新 IP,最后点击“ Re-Package IP”完成 IP 核的封装,此时 IP 核的封装界面会自动关闭。

如果不嫌麻烦,可以在进行最后的封装前,进行一下综合,检查电路是否有问题,是否有信号被优化没了。不综合大多数情况下没问题,偶尔会出现问题。我建议可以不要嫌麻烦,综合后再进行封装。

退出后点击右侧的IP Catalog,在user repository 下面的AXI处右键添加IP。添加之后就可以在BD中调用了。

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

接下来走流程,生成output和顶层,然后综合实现布局布线生成比特流,成功后export hardware,launch SDK。然后去SDK中编程。

三、SDK编程

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,我们读出来的数据如下图所示:

可以看到,实验结果完全正确。

五、总结

本实验通过PS部分将数据写入FPGA的BRAM里,通过自定义IP实现PL部分对BRAM里的数据进行读取后,加一再重新写入BRAM里,PS再重新读取,进行对比。从截图可看到,试验成功。

通过这样的流程,以后我们可以直接在定义的IP里实现更加复杂的功能。

如果绝的自定义IP操作繁琐,可以留言跟我要ip_repo。

相关推荐
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页