FPGA学习记录(三)Zynq UltraScale+ MPSoC 基于 BRAM 的 PS 和 PL 的数据交互

本文详细描述了如何在Vivado2022.1和Vitis环境下,使用ZynqUltraScale+MPSoC开发板进行实验,通过PS将串口数据写入BRAM并从BRAM读取,同时PL进行数据验证。涉及自定义IP核的创建、连接和验证,以及Bitstream的生成和Vitis软件的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

前言

1.PL端

        2.PS端


前言

开发环境: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 GeneratorIP 核,其中一个端口连接到了 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文件;

经对比实验结果, PSPL两端数据完全一致。 

注:以上仅是个人见解,如果有误,烦请各位大佬指教。




 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值