当前位置: 首页 > news >正文

ZYNQ PS读写PL BRAM

一、实验室任务

        本章的实验任务是 PS 将数据写入BRAM,然后从 BRAM 中读出数据,并通过串口打印出来;与此同时,PL 从通过自定义ip核从BRAM中同样读出数据,并通过ILA 来观察读出的数据与串口打印的数据是否一致。这里是通过PS写入进的只有数据信息,而无法读数据,因此通过ILA观察数据。

二、实验整体架构

三、自定义ip写进ram的控制ip核

        这个ip核呢是将我们通过AXI输入进BRAM的数据通过自定义的ip核转为合适的ram接口信息接到ramip核上,这样就可以通过ps端输入的读使能,读地址读数据长度信息转化为ram的读使能,读地址,保证时序正确。

四、设计流程

一、自定义ip核。

1、创建IP管理器

2、选择AXI接口的ip核,然后一直保持默认最后点击完成,

3、编辑这个ip,右键ip核,然后选择编辑选项,进入编辑页面,然后默认ok就行

4、添加一个控制文件(.v)如下

        为什么要写这个模块呢?为什么不能通过ps侧输入读使能,读数据直接连接到ram的ip核上呢?在我看来这是因为ps侧的逻辑无法精准控制时钟逻辑,也就是ps侧无法做到像pl侧的严格时序控制。pl侧的逻辑是在每个时钟内做自己的事情,每个时钟周期都有严格的要求,因此我们在PS侧无法做到如此严格的时序要求。所以通过一个自定义控制模块对输入的信号进行处理,使得时序要求满足我们pl侧的逻辑。

module ram_ip
(input 		 		 pl_rst_n		,input		 		 pl_clk			,input		 		 ps_read_en		,//开始读的使能input		 [31:0]	 ps_start_addar	,//开始读的起始地址input		 [31:0]	 ps_data_num	,//需要读取的数量//bram接口output               ram_clk    	,//RAM时钟input        [31:0]  ram_rd_data	,//RAM中读出的数据,这个接口是读出来的数据需要我们通过该控制器输出到ps端。output  reg          ram_en     	,//RAM使能信号,这个接口是读使能信号,需要根据我们ps侧输入的几个信息给出合适的读信号。output  reg  [31:0]  ram_addr   	,//RAM地址,这个也需要通过ps侧输入的起始地址和需要读取的数量来生成的。output  reg  [3 :0]  ram_we     	,//RAM读写控制信号output  reg  [31:0]  ram_wr_data	,//RAM写数据output               ram_rst     	 //RAM复位信号,高电平有效);assign ram_clk=pl_clk;
assign ram_rst=1'b0;always @(posedge pl_clk)
if(!pl_rst_n)ram_we<=4'd0;
else ram_we<=ram_we;always @(posedge pl_clk)
if(!pl_rst_n)ram_wr_data<=32'd0;
else ram_wr_data<=ram_wr_data;reg		ps_read_en_reg;
always @(posedge pl_clk)
if(!pl_rst_n)ps_read_en_reg<=1'b0;
else ps_read_en_reg<=ps_read_en;wire ps_read_en_pos=ps_read_en&~ps_read_en_reg;parameter 		A_EDA=3'd0,A_ADD=3'd1,A_DOW=3'd2;reg 	[2:0]	state;
always @(posedge pl_clk)if(!pl_rst_n)state<=A_EDA;else case(state)A_EDA:state<=(ps_read_en_pos)	?	A_ADD	:	A_EDA;A_ADD:state<=(ram_addr-ps_start_addar+2'd2==ps_data_num)	?	A_DOW	:	A_ADD;A_DOW:state<=A_EDA;default:state<=A_EDA;endcasealways @(posedge pl_clk)if(!pl_rst_n)ram_en<=1'b0;else case(state)A_EDA:if(ps_read_en_pos) ram_en<=1'b1;A_ADD:ram_en<=1'b1;default:ram_en<=1'b0; endcasealways @(posedge pl_clk)if(!pl_rst_n)ram_addr<=32'd0;else case(state)A_EDA:if(ps_read_en_pos) ram_addr<=ps_start_addar;A_ADD:ram_addr<=ram_addr+1'b1;default:ram_addr<=32'd0;endcaseendmodule

        上面是我们的需要添加进我们ip核的verilog代码。下面是systemverilog仿真代码和仿真图。由图可知在我们的pl侧受到来自ps的使能和读数据信息时候,会产生一个高脉冲,然后通过状态机完成对读数据信息的转化,转化成我们pl侧ram接口可以识别的信息,比如读使能,使能对应的读地址。由仿真图也可以看出:当我们的pl侧检测到来自ps侧的上升沿时就会立马进入ram接口时序生成状态,依据起始地址,读数量等生成我们需要的接口时序。

`timescale 1ns/1ns
module ram_ip_sim();reg 	 	 	   pl_rst_n			;
reg		  		   pl_clk			;
reg		  		   ps_read_en		;
reg		  [31:0]   ps_start_addar	;
reg		  [31:0]   ps_data_num		;//bram接口
wire               ram_clk    		;
reg        [31:0]  ram_rd_data		;
wire               ram_en     		;
wire       [31:0]  ram_addr   		;
wire       [3 :0]  ram_we     		;
wire       [31:0]  ram_wr_data		;
wire               ram_rst     	    ;initial beginpl_clk<=1'b0;forever #10 pl_clk=~pl_clk;
endinitial beginpl_rst_n=1'b0;repeat(3) @(posedge pl_clk);pl_rst_n=1'b1;
endinitial beginps_read_en		=1'b0 ;ps_start_addar	=32'd0;ps_data_num		=32'd0;ram_rd_data		=32'd0;
endtask data_send;repeat(3) @(posedge pl_clk);ps_read_en		=1'b1  ;ps_start_addar	=32'd0 ;ps_data_num		=32'd10;repeat(15) @(posedge pl_clk);ps_read_en		=1'b0 ;ps_start_addar	=32'd0;ps_data_num		=32'd0;endtask :	data_send;initial beginwait(pl_rst_n);fork beginrepeat(1) @(posedge pl_clk);data_send;endjoin
endram_ip  ram_ip_un
(.pl_rst_n		(pl_rst_n		),.pl_clk			(pl_clk			),.ps_read_en		(ps_read_en		),//开始读的使能.ps_start_addar	(ps_start_addar	),//开始读的起始地址.ps_data_num	(ps_data_num	),//需要读取的数量.ram_clk    	(ram_clk    	),//RAM时钟.ram_rd_data	(ram_rd_data	),//RAM中读出的数据,这个接口是读出来的数据需要我们通过该控制器输出到ps端。.ram_en     	(ram_en     	),//RAM使能信号,这个接口是读使能信号,需要根据我们ps侧输入的几个信息给出合适的读信号。.ram_addr   	(ram_addr   	),//RAM地址,这个也需要通过ps侧输入的起始地址和需要读取的数量来生成的。.ram_we     	(ram_we     	),//RAM读写控制信号.ram_wr_data	(ram_wr_data	),//RAM写数据.ram_rst     	(ram_rst     	) //RAM复位信号,高电平有效);endmodule

 5、需要修改的地方

在顶层模块添加输出端口

在顶层例化的模块添加例化模块修改的端口。

在AXI接口模块添加输出接口,然后例化我们的bram控制器

在例化我们添加的模块可知,我们例化模块的使能信号,我存在寄存器0的第一位,因此我们需要在ps侧C语言代码中操作该寄存器就可以给该bram控制模块输出想要的信息,同理我们用到了寄存器1和2分别接收来自ps侧的起始地址和读数据量,然后由该信息就可以让bram控制模块输出满足时序要求的读使能端口和读地址。

6、点击文件组,这里就是我们添加和修改文件后需要刷新

7、这里是常量刷新(如果你是替换的代码就先进代码随便打个空格然后保存就会刷新)

8、按照如下操作分别将信号接口从时钟和复位接口移出去ram_rst和ram_clk

9、这里有两个警告,这是因为我们顶层的输出端口没有和其他的ip核端口和形成映射关系,因此这里会有警告不知道对应的接口是哪个,因此我们添加输出端口映射。

10、添加输出端口映射。右键随便一个端口,然后点击add bus interface。

11、先修改我们要对接的端口,这里我们选择bram端口。然后添加该接口映射的总线名字,然后选择主端口,因为这个模块控制的是ram ip的读数据信息。

12、添加端口映射,点一下需要映射的端口然后点击映射即可,按照此流程将所有端口映射。这样做的目的是为了我们软件可以自动帮我们连线,不需要我们一个一个连。

13、将端口的属性添加到自适应列。这样我们连接端口时,端口的信号会自动和从机端口属性保持一致,这样不需要我们一个一个的设置。

14、点击提示信息

15、点击下面的生成ip核

二、IP组装

1、先修改我们的最小系统IP,因为在hello_word实验时将有些端口取消了,这里我们重新添加回来,如下:GP接口和时钟复位添加进来

2、添加AXI bram控制器,并按照如图修改

3、添加bram ip核,并按照如图修改两个地方

4、添加我们自己写的ip控制器,不需要修改

5、点击自动连接,然后全部选中,然后为这两个ip核的端口设置连接的ip核接口,这样这两个ip核的接口就会自动连接到我们的双端口bram上去,实现一个ip核写,一个ip核读。然后点击确定后点击重新布局。 

6、验证设计,然后验证无误点击ok即可。

7、修改ram容量大小,这里可以将两个都改为4k也可以不改。

8、生成输出文件,然后ok即可

9、生成顶层文件,这里需要记住的是在原来的hello的工程基础上的顶层没有改动,可以不生成,如果是重新建立的工程要生成顶层文件。以下是已经生成过的顶层文件重新生成,默认点击ok即可

三、添加ILA

1、点击综合

2、点击综合设计->点击step up debug

3、next后添加我们要探针的信号,如图,导航到ram ip核的U0下添加我们要探查的信号

4、为探查信号添加时钟,搜索选择ALL_CLOCK,然后选择下列时钟后点击🆗。重复刚才步骤为这三个信号都添加时钟。然后一直默认点击ok。

5、然后点击实现,然后点击生成bit流

6、导出设计文件,勾选bit流后点击🆗即可,一定要勾选bit流,因为这里用到了pl端的设计。

7、打开SDK

四、SDK设计

1、新建空项目。

2、建立一个源文件,命名main.c

3、复制如下代码

#include "stdio.h"
#include "xbram_hw.h"
#include "stdio.h"
#include "custom_pl_bram_ps.h"
#include "xparameters.h"
#include "sleep.h"#define PL_BRAM_START		CUSTOM_PL_BRAM_PS_S00_AXI_SLV_REG0_OFFSET//寄存器1输出使能信号
#define PL_BRAM_START_ADDR	CUSTOM_PL_BRAM_PS_S00_AXI_SLV_REG1_OFFSET//寄存器2输出起始地址
#define PL_BRAM_LEN			CUSTOM_PL_BRAM_PS_S00_AXI_SLV_REG2_OFFSET//寄存器3输出数据长度#define PL_BRAM_BASE		XPAR_CUSTOM_PL_BRAM_PS_0_S00_AXI_BASEADDR //自定义ip的地址#define START_ADDAR			0//
#define PL_BRAM_DATA_LEN	4//每个bram的深度为4个字节char input_data[1024]="www.com.lzs"	;
int  num_data			;int main()
{//通过调试发现如果没初值,那会将数据写入到系统分配的初始地址位置。int wr_cnt=START_ADDAR;printf("test start!");sleep(5);//while(1)//{//scanf("please input data: %s",input_data);num_data=strlen(input_data);for(int i=START_ADDAR*PL_BRAM_DATA_LEN;i<(START_ADDAR+num_data)*PL_BRAM_DATA_LEN;i+=PL_BRAM_DATA_LEN){//第一个参数为写入AXIip核的地址XBram_WriteReg(XPAR_BRAM_0_BASEADDR,i,input_data[wr_cnt]);printf("w_data:%c,odata : %d\n",input_data[wr_cnt],input_data[wr_cnt]);wr_cnt++;}//}//设备ip,ip映射(寄存器2),写入数据的bram起始地址。其实就是像我们自定义的ip核里面的AXI寄存器写入数据。//这个寄存器就是连接我们自己编写的bram读控制文件的输入端口。CUSTOM_PL_BRAM_PS_mWriteReg(PL_BRAM_BASE,PL_BRAM_START_ADDR,START_ADDAR*PL_BRAM_DATA_LEN);//配置长度CUSTOM_PL_BRAM_PS_mWriteReg(PL_BRAM_BASE,PL_BRAM_LEN,(START_ADDAR+num_data)*PL_BRAM_DATA_LEN);//配置使能,让我们的自定义ip采集上升沿CUSTOM_PL_BRAM_PS_mWriteReg(PL_BRAM_BASE,PL_BRAM_START,0x00000001);CUSTOM_PL_BRAM_PS_mWriteReg(PL_BRAM_BASE,PL_BRAM_START,0x00000000);printf("\naddares,data\n");int read_data;for(int i=START_ADDAR*PL_BRAM_DATA_LEN;i<(START_ADDAR+num_data)*PL_BRAM_DATA_LEN;i+=PL_BRAM_DATA_LEN){read_data=XBram_ReadReg(XPAR_BRAM_0_BASEADDR,i);printf("addares: %d, data: %c, odata : %d\n",i/PL_BRAM_DATA_LEN,read_data,read_data);}return 0;}

4、修改报错信息,这里是因为系统生成的库是按照我们自定义ip核名字生成的,如果你是按照我的步骤来的就复制最后修改好的代码。

5、找到对应的库文件,然后找到这几个参数复制改变定义

6、找到这几个库文件的函数修改为以下函数

7、修改完后保存,如果你的自定义ip核的命名也为pl_ps_bram就可以直接复制下面的

#include "stdio.h"
#include "xbram_hw.h"
#include "stdio.h"
#include "pl_ps_bram.h"
#include "xparameters.h"
#include "sleep.h"#define PL_BRAM_START		PL_PS_BRAM_S00_AXI_SLV_REG0_OFFSET//寄存器1输出使能信号
#define PL_BRAM_START_ADDR	PL_PS_BRAM_S00_AXI_SLV_REG1_OFFSET//寄存器2输出起始地址
#define PL_BRAM_LEN			PL_PS_BRAM_S00_AXI_SLV_REG2_OFFSET//寄存器3输出数据长度#define PL_BRAM_BASE		XPAR_PL_PS_BRAM_0_S00_AXI_BASEADDR //自定义ip的地址#define START_ADDAR			0//
#define PL_BRAM_DATA_LEN	4//每个bram的深度为4个字节char input_data[1024]="www.com.lzs"	;
int  num_data			;int main()
{//通过调试发现如果没初值,那会将数据写入到系统分配的初始地址位置。int wr_cnt=START_ADDAR;printf("test start!");sleep(5);//while(1)//{//scanf("please input data: %s",input_data);num_data=strlen(input_data);for(int i=START_ADDAR*PL_BRAM_DATA_LEN;i<(START_ADDAR+num_data)*PL_BRAM_DATA_LEN;i+=PL_BRAM_DATA_LEN){//第一个参数为写入AXIip核的地址XBram_WriteReg(XPAR_BRAM_0_BASEADDR,i,input_data[wr_cnt]);printf("w_data:%c,odata : %d\n",input_data[wr_cnt],input_data[wr_cnt]);wr_cnt++;}//}//设备ip,ip映射(寄存器2),写入数据的bram起始地址。其实就是像我们自定义的ip核里面的AXI寄存器写入数据。//这个寄存器就是连接我们自己编写的bram读控制文件的输入端口。PL_PS_BRAM_mWriteReg(PL_BRAM_BASE,PL_BRAM_START_ADDR,START_ADDAR*PL_BRAM_DATA_LEN);//配置长度PL_PS_BRAM_mWriteReg(PL_BRAM_BASE,PL_BRAM_LEN,(START_ADDAR+num_data)*PL_BRAM_DATA_LEN);//配置使能,让我们的自定义ip采集上升沿PL_PS_BRAM_mWriteReg(PL_BRAM_BASE,PL_BRAM_START,0x00000001);PL_PS_BRAM_mWriteReg(PL_BRAM_BASE,PL_BRAM_START,0x00000000);printf("\naddares,data\n");int read_data;for(int i=START_ADDAR*PL_BRAM_DATA_LEN;i<(START_ADDAR+num_data)*PL_BRAM_DATA_LEN;i+=PL_BRAM_DATA_LEN){read_data=XBram_ReadReg(XPAR_BRAM_0_BASEADDR,i);printf("addares: %d, data: %c, odata : %d\n",i/PL_BRAM_DATA_LEN,read_data,read_data);}return 0;}

五、SDK验证

1、打开板子供电、连接串口

2、右键项目,打开运行配置

3、双击GDB,打开烧录bit流和复位

4、点击运行

5、我们发现AXIbram控制器ip核读写一致

六、ILA验证

        这里我们需要注意,由于我的设计用不了scanf输入函数,因此我加了一个延迟然后马上回到ILA界面运行,等待enb拉高,触发逻辑分析仪。如果你的scanf能用就不用加延迟,就可以通过串口输入写入信息后,SDK程序会立马运行到拉高enb的信号函数。

还有一点就是如果你调不出ILA界面请点击以下链接,查看原因和解决方法

vivado 下载程序后没有ILA界面

1、点击设备管理器后点击自动连接,进入ila界面

2、设置触发条件,选择enb,选择上升沿触发。

3、回到SDK烧录界面,烧录后马上回到ILA界面点击运行。这里一定要马上回到这个界面点运行,然后等待触发。前面说过如果你的scanf能用就可以在SDK 终端输入后这里就会触发。而这里由于我的scanf用不了,我就加了个延迟,等几秒钟后会按顺序执行到SDK设置上升沿的使能程序。然后ILA界面就会触发,如下图

4、对比写入数据。将数据以ASCLL码的形式展现

5、对比发现我们逻辑分析仪捕捉到的自定义ip核读数据转为ASCLL码后和我们SDK PS侧写进PS侧RAM的数据是一致的。因此我们的设计正确。

 

 

 

 


文章转载自:

http://E6cPca5L.hntrf.cn
http://SmcPFJJE.hntrf.cn
http://XKRcjQfj.hntrf.cn
http://3kw07iZ5.hntrf.cn
http://HX7E39CB.hntrf.cn
http://ASF4Z79y.hntrf.cn
http://9fdPwmz4.hntrf.cn
http://7cuHzwtJ.hntrf.cn
http://tnY9EKtY.hntrf.cn
http://6bNzACAN.hntrf.cn
http://BXVbmaJw.hntrf.cn
http://Zjk3n4Vr.hntrf.cn
http://lqn0jRIf.hntrf.cn
http://YXNtrqCG.hntrf.cn
http://PdCtbF4I.hntrf.cn
http://YiW52Vd5.hntrf.cn
http://7eZ4UCOS.hntrf.cn
http://UiBnBit3.hntrf.cn
http://usPSQZ5V.hntrf.cn
http://Nbk4qq1Y.hntrf.cn
http://aj1WkAG5.hntrf.cn
http://QSr0YuCE.hntrf.cn
http://D7WdAdNM.hntrf.cn
http://ow6omJGp.hntrf.cn
http://QT3JjXHw.hntrf.cn
http://iqGsvuX3.hntrf.cn
http://PXuDAIQR.hntrf.cn
http://FiLxOrIf.hntrf.cn
http://vgdFRo7Z.hntrf.cn
http://ecIaS5ui.hntrf.cn
http://www.dtcms.com/a/382016.html

相关文章:

  • [数据结构] 队列 (Queue)
  • Git : 基本操作
  • Vue模板中传递对象或数组时,避免直接使用字面量[]和{}
  • 26考研——内存管理_虚拟内存管理(3)
  • FastAPI如何用契约测试确保API的「菜单」与「菜品」一致?
  • PDFgear:免费全能的PDF处理工具
  • 贪心算法应用:K-Means++初始化详解
  • Linux相关概念和易错知识点(43)(数据链路层、ARP、以太网、交换机)
  • 交换机数据管理
  • 【Redis#11】Redis 在 C++ 客户端下的安装使用流程(一条龙服务)
  • leetcode 315 计算右侧小于当前元素的个数
  • MYSQL端口号3306被占用
  • Python核心技术开发指南(062)——静态方法
  • [Windows] 整容脸比对系统
  • C语言:指针从入门到精通(上)
  • 【MySQL】--- 表的约束
  • SpringBoot 轻量级一站式日志可视化与JVM监控
  • Java零基础学习Day10——面向对象高级
  • JavaScript中ES模块语法详解与示例
  • 系统核心解析:深入操作系统内部机制——进程管理与控制指南(三)【进程优先级/切换/调度】
  • Roo Code:用自然语言编程的VS Code扩展
  • 第8.4节:awk的内置时间处理函数
  • leetcode算法刷题的第三十四天
  • 【技术博客分享】LLM推理过程中的不确定问题
  • Vue3基础知识-setup()、ref()和reactive()
  • 规则系统架构风格
  • 宋红康 JVM 笔记 Day17|垃圾回收器
  • vue表单弹窗最大化无法渲染复杂组件内容
  • 加餐加餐!烧烤斗破苍穹
  • SCSS 中的Mixins 和 Includes,%是什么意思