目录
前言
开发环境:vivado2022.1 vitis windows10 开发板:xczu28dr
实验任务:PS 将串口接收到的数据写入 BRAM,然后从 BRAM 中读出数据,并通过串口打印 出来;与此同时,PL 从 BRAM 中同样读出数据,并通过 ILA 来观察读出的数据与串口打印的数据是否一致。
1.PL端
打开vivado2022.1并创建工程:Zynq UltraScale + MPSoC核配置:
接下来在 Block Design 中添加AXI BRAM核并配置:
接下来在 Block Design 中添加 Block Memory Generator核并配置:
在空白处鼠标右击,选择Regenerator Layout
箭头 1 指向的 AXI SmartConnect 是一种新型系统连接生成器,同样也是实现将一个 或多个内存映射主设备连接到一个或多个内存映射从设备,一般可以作为 AXI InterConnect 的替代品,且具 有更好的性能。箭头 2 指向的“Block Memory Generator”IP 核,其中一个端口连接到了 AXI BRAM 控制 器,另一个端口连接 PL 读 BRAM IP 核,这个 IP 核是我们自定义的 IP 核,接下来添加这个 IP 核。
然后通过菜单栏的 Tool→Setting→IP→Repository
在 Name 一栏的名称改为“pl_bram_rd”,IP 核的路径改为工程目录下的 ip_repo 文件夹,即删除路径 “/../”中间的一个“.”符号,其它的设置直接保持默认即可,点击“NEXT”,直到最后点击“Finish”按 钮完成自定义 IP 核的创建。
在“IP Catalog”界面下,依次展开 User Repository→AXI Peripheral→“pl_bram_rd_v1.0”,右击 “pl_bram_rd_v1.0”,选择“Edit in IP Packager”,如下图所示:
在弹出的页面中,点击“OK”,进入编辑自定义 IP 核的工程界面。打开 pl_bram_rd_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_rd_v1_0_S00_AXI 模块的位置,添加以下代码。
//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),
打开 pl_bram_rd_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_rd u_bram_rd(
.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 模块,其中 start_rd 信号是开始读 BRAM 的开始信号,start_addr 是设置 BRAM的读起始地址,rd_len 是设置读 BRAM 的个数,分别连接到 AXI4 总线的寄存器地址 0、地址 1 和地址 2 对 应的数据。
接下来右击“Design Sources”,选择“Add Sources…”向工程中添加“bram_rd.v”文件,位 于../ps_pl_bram/ip_repo/pl_bram_rd_1.0/hdl 路径下, bram_rd 模块的代码如下:
module bram_rd(
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;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
if(ram_addr - start_addr == rd_len - 4) begin //数据读完
ram_en <= 1'b0;
flow_cnt <= flow_cnt + 2'd1;
end
else
ram_addr <= ram_addr + 32'd4; //地址累加 4
end
2'd2 : begin
ram_addr <= 32'd0;
flow_cnt <= 2'd0;
end
endcase
end
end
endmodule
程序实现了根据输入的读开始信号(start_rd)、读起始地址(start_addr)和读数据长度(rd_len)从RAM 中读出数据。代码全部保存完成后,双击 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”图标,如下图所示:
接下来在弹出页面的搜索框中输入“BRAM”,选中 Advanced 一栏下的“bram_rtl”,即 BRAM 总线 接口。然后点击“OK”按钮,如下图所示:
将页面切换至“Port Mapping”选项页,左侧窗口为总线逻辑端口,右侧窗口为 IP 核定义的物理端口, 最下面的窗口是映射端口的总结页面,如下图所示:
点击左侧的“EN”端口,然后点击右侧的“ram_en”端口,此时页面上的“Map Ports”变成可以点击 的按钮,点击这个按钮,此时在映射端口总结页面可以看到这两个端口映射到了一起,如下图所示:
接下来将页面切换至“Parameters”选项页,展开 Auto-calculated,选中“MASTER_TYPE”,然后点 击单个向右的箭头,如下图所示:
此时,“MASTER_TYPE”参数会出现在右侧 Overridden 目录下,最后点击“OK”按钮完成 BRAM接口的封装,如下图所示:
接下来切换至“Review and Package”页面,点击上侧的“IP has been modified”来更新 IP,最后点击 “Re-Package IP”完成 IP 核的封装,此时 IP 核的封装界面会自动关闭。返回本次实验的工程界面,在 Diagram 界面中添加刚刚封装的 IP 核。在 Diagram 窗口空白位置右击, 然后选择“Add IP”。在弹出的 IP 目录中搜索“pl_bram”,最后双击搜索结果中的“pl_bram_rd_v10”将 其添加到设计中,如下图所示:
连接完成后,在 Diagram 窗口空白处右击,然后选择“Regenerate Layout”对设计进行重新布局,布局 后的界面如下图所示:
接下来切换至“Address Editor”页面,展开“zynq_ultra_ps_e_0”下的“Data”,将范围设置成“4K”, 如图 18.3.26 所示。由于 BRAM 的数据位宽是 32 位,因此 BRAM 的存储深度为 1K。
到这里我们的 Block Design 就设计完成了,在 Diagram 窗口空白处右击,然后选择“Validate Design” 验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按 快捷键“Ctrl + S”保存设计。
接下来在 Source 窗口中右键点击 Block Design 设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。
在左侧 Flow Navigator 导航栏中找到 SYNTHESIS,点击该选项中的“Run Synthesis”,如下图所示:
然后分别设置三个信号的 Clock Domain,设置方法为右击每个信号,选择 Select Clock Domain,在弹 出 窗 口 的 下 拉 栏 中 选 择 “ LOCAL_CLOCK ” , 此 时 可 以 看 到 下 面 显 示 栏 中 出 现 “design1_1_i/zynq_ultra_ps_e_0/inst/pl_clk0”,然后点击“OK”,设置完成后如下图所示:
点击 Next 按钮,接下来设置采样的深度,采样深度设置的值越大,采集到的数据越多。
点击 Next 按钮,最后点击 Finish 按钮完成信号的添加,按快捷键“Ctrl + S”保存 Synthesized Design, 在弹出的 Save Constraints 窗口中输入约束文件名,这里直接输入工程名“pl_test”,然后点击“OK”。
最后在左侧 Flow Navigator 导航栏中找到 PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成 Bitstream 文件。
2.PS端
在生成 Bitstream 之后,在菜单栏中选择 File > Export > Export hardware 导出硬件,并在弹出的对话框 中,勾选“Include bitstream”。将导出的“design_1_wrapper.xsa”文件放到 vitis 文件夹,然后在菜单栏选 择 Tools > Launch Vitis,启动 VITIS 软件。
请参考vitis使用教程【ARM软件操作】:ZYNQ创建内核使用vitis过程(vivado 2019.2) - 知乎
#include "xil_printf.h"
#include "stdio.h"
#include "pl_bram_rd.h"
#include "xbram.h"
#define PL_BRAM_BASE XPAR_PL_BRAM_RD_0_S00_AXI_BASEADDR //PL_RAM_RD 基地址
#define PL_BRAM_START PL_BRAM_RD_S00_AXI_SLV_REG0_OFFSET //RAM 读开始寄存器地址
#define PL_BRAM_START_ADDR PL_BRAM_RD_S00_AXI_SLV_REG1_OFFSET //RAM 起始寄存器地址
#define PL_BRAM_LEN PL_BRAM_RD_S00_AXI_SLV_REG2_OFFSET //PL 读 RAM 的深度
#define START_ADDR 0 //RAM 起始地址 范围:0~1023
#define BRAM_DATA_BYTE 4 //BRAM 数据字节个数
char ch_data[1024]; //写入 BRAM 的字符数组
int ch_data_len; //写入 BRAM 的字符个数
//函数声明
void str_wr_bram();
void str_rd_bram();
//main 函数
int main()
{
while(1)
{
printf("Please enter data to read and write BRAM\n") ;
scanf("%1024s", ch_data); //用户输入字符串
ch_data_len = strlen(ch_data); //计算字符串的长度
str_wr_bram(); //将用户输入的字符串写入 BRAM 中
str_rd_bram(); //从 BRAM 中读出数据
}
}
//将字符串写入 BRAM
void str_wr_bram()
{
int i=0,wr_cnt = 0;
//每次循环向 BRAM 中写入 1 个字符
for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + ch_data_len) ;
i += BRAM_DATA_BYTE){
XBram_WriteReg(XPAR_BRAM_0_BASEADDR,i,ch_data[wr_cnt]) ;
wr_cnt++;
}
//设置 BRAM 写入的字符串长度
PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_LEN , BRAM_DATA_BYTE*ch_data_len) ;
//设置 BRAM 的起始地址
PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START_ADDR, BRAM_DATA_BYTE*START_ADDR) ;
//拉高 BRAM 开始信号
PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 1) ;
//拉低 BRAM 开始信号
PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 0) ;
}
//从 BRAM 中读出数据
void str_rd_bram()
{
int read_data=0,i=0;
//循环从 BRAM 中读出数据
for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + ch_data_len) ;
i += BRAM_DATA_BYTE){
read_data = XBram_ReadReg(XPAR_BRAM_0_BASEADDR,i) ;
printf("BRAM address is %d\t,Read data is %c\n",i/BRAM_DATA_BYTE ,read_data) ;
}
}
以下内容请参考处理:
报错内容:
参考方法:
1.修改自定义IP核diver的status;
2.修改makefile文件;
makefile文件修改不建议!!!
再次编译:
我在vivado2022.1及vitis上做以上实验,以失败告终!!!!!!【个人觉得是环境问题!!!】
同样的步骤,在vivado2018.3及SDK做以上实验。
注:
1.vivado上面步骤完全一致;
2.SDK上面创建一个hello world工程,先尝试输出helloworld,正常输出;
3.再将上面的代码复制到hello_world.c中,编译运行。
vivado下载bit文件;
经对比实验结果, PS和PL两端数据完全一致。
注:以上仅是个人见解,如果有误,烦请各位大佬指教。