【正点原子FPGA连载】 第十八章基于BRAM的PS和PL的数据交互 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

news2024/11/24 1:59:20

1)实验平台:正点原子MPSoC开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html

第十八章基于BRAM的PS和PL的数据交互

在MPSOC开发过程中,PL和PS之间经常需要做数据交互。对于传输速度要求较高、数据量大、地址连续的场合,可以通过AXI DMA来完成。而对于数据量较少、地址不连续、长度不规则的情况,此时AXI DMA便不再适用了。针对这种情况,可以通过BRAM来进行数据的交互。本章我们来学习下基于BRAM的PS和PL的数据交互。
本章包括以下几个部分:
1818.1简介
18.2实验任务
18.3硬件设计
18.4软件设计
18.5下载验证

18.1简介

BRAM(Block RAM)是PL部分的存储器阵列,PS和PL通过对BRAM进行读写操作,来实现数据的交互。在PL中,通过输出时钟、地址、读写控制等信号来对BRAM进行读写操作(关于BRAM的操作时序,请参考“RAM IP核实验”);而在PS中,处理器并不需要直接驱动BRAM的端口,而是通过AXI BRAM控制器来对BRAM进行读写操作。AXI BRAM控制器是集成在Vivado设计软件中的软核,可以配置成AXI4-lite接口模式或者AXI4接口模式。AXI4-Lite 接口模式的框图如图 18.1.1所示。
在这里插入图片描述

图 18.1.1 AXI4-Lite BRAM控制器框图
AXI4接口模式的BRAM控制器支持的数据位宽为32位、64位、128位、512位和1024位,而AXI4-Lite接口仅支持32位数据位宽。由图 18.1.1可知,PS通过AXI4-Lite接口访问BRAM,当使能ECC选项时,ECC允许AXI主接口检测和纠正BRAM块中的单位和双位错误。AXI BRAM控制器作为AXI总线的从接口,和AXI主接口实现互联,来对BRAM进行读写操作。针对不同的应用场合,该IP核支持单次传输和突发传输两种方式。

18.2实验任务

本章的实验任务是PS将串口接收到的数据写入BRAM,然后从BRAM中读出数据,并通过串口打印出来;与此同时,PL从BRAM中同样读出数据,并通过ILA来观察读出的数据与串口打印的数据是否一致。

18.3硬件设计

根据实验任务我们可以画出本次实验的系统框图,如下图所示:
在这里插入图片描述

图 18.3.1 系统框图
在图 18.3.1中,PS端的M_AXI_HPM作为主端口,与PL端的AXI BRAM控制器IP核和PL读BRAM IP核(pl_bram_rd)通过AXI4总线进行连接。其中,AXI互联IP(AXI Interconnect)用于连接AXI存储器映射(memory-mapped)的主器件和从器件;AXI BRAM控制器作为PS端读写BRAM的IP核;PL读BRAM IP核是我们自定义的IP核,实现了PL端从BRAM中读出数据的功能,除此之外,PS端通过AXI总线来配置该IP核读取BRAM的起始地址和个数等。
由框图可知,本次实验创建的BRAM为双端口的RAM,其中一个端口连接AXI BRAM控制器,另一个连接PL读BRAM IP核。
首先创建Vivado工程,工程名为“ps_pl_bram”,然后创建Block Design设计(design_1.bd)并添加Zynq UltraScale+ MPSOC模块。接下来按照《“Hello World”实验》中的步骤2-7、2-8分别配置PS的UART和DDR控制器。需要特别注意的是,我们在《“Hello World”实验》的步骤2-10中,移除了PS中与PL端交互的接口信号,这些接口在我们本次实验中需要予以保留。
最后点击右下角的“OK”,本次实验Zynq UltraScale+ MPSOC就配置完成了。
Zynq UltraScale+ MPSOC配置完成后其接口如图 18.3.2所示:
在这里插入图片描述

图 18.3.2 Zynq UltraScale+ MPSOC
接下来我们要在Block Design中添加AXI BRAM Controller IP核,在Diagram窗口空白位置右击,然后选择“Add IP”。在弹出的IP目录中搜索“AXI BRAM”,最后双击搜索结果中的“AXI BRAM Controller”将其添加到设计中,如下图所示:
在这里插入图片描述

图 18.3.3 添加AXI BRAM Controller IP核
添加完成后,双击AXI BRAM Controller IP核,打开其配置界面如下图所示:
在这里插入图片描述

图 18.3.4 BRAM控制器配置页面
在图 18.3.4中,AXI Protocol(AXI 协议)选择的是AXI4,对于本次实验来说,选择AXI4或者AXI4-Lite没有影响;Data Width(数据位宽)选择32位,由于AXI4总线为字节寻址,因此在映射到BRAM地址时,需要按4字节寻址。本次实验的BRAM控制器只需要读写BRAM的一个端口,因此将BRAM的总线个数设置为1;ECC选项用于数据错误纠正与检查,这里不使能。
需要说明的是,Memory Depth(存储深度)在这里不可以设置,寻址BRAM的存储深度是在Address Editor里设置的,最后点击“OK”按钮完成设置。
接下来在Block Design中添加BRAM IP核,即Block Memory Generator IP核。在Diagram窗口空白位置右击,然后选择“Add IP”。在弹出的IP目录中搜索“Block Memory”,最后双击搜索结果中的“Block Memory Generator”将其添加到设计中,如下图所示:
在这里插入图片描述

图 18.3.5 添加Block Memory Generator IP核
添加完成后,双击Block Memory Generator IP核,打开其配置界面如下图所示:
在这里插入图片描述

图 18.3.6 Block Memory Generator IP核配置页面
BRAM IP核支持两种模式,一种是独立模式(Stand Alone),在此模式下,可以自由配置RAM的数据深度和宽度;另一种是BRAM控制器模式(BRAM Controller),在此模式下,地址和数据默认为32位,由于本次实验添加了BRAM控制器IP核,因此BRAM模式选择BRAM控制器模式。
Memory Type(存储类型)设置为“True Dual Port RAM”,即真双口RAM。一端连接PL读BRAM IP核,另一端连接BRAM控制器。
接下来将页面切换至“Other Options”的选项,如下图所示:
在这里插入图片描述

图 18.3.7 Block Memory Generator IP核“Other Options”配置页面
BRAM IP核内置了一个安全电路以降低BRAM数据出现错误的概率,如果勾选使能安全电路,BRAM端口会增加rsta_busy端口和rstb_busy端口,用于表示何时可以访问BRAM。这里直接取消使能安全电路,最后点击“OK”按钮完成设置。
添加完成后Diagram窗口如下图所示:
在这里插入图片描述

图 18.3.8 Diagram窗口
接下来点击图 18.3.8中箭头所指示的位置,弹出对话框如下图所示:
在这里插入图片描述

图 18.3.9 自动连接
在左侧勾选“All Automation”,下面列出了会自动连接的模块及其接口。点击“OK”,工具会自动连接AXI BRAM Controller IP核的BRAM_PORTA接口和S_AXI接口。
连接完成后,在Diagram窗口空白处右击,然后选择“Regenerate Layout”对设计进行重新布局,布局后的界面如下图所示:
在这里插入图片描述

图 18.3.10 重新布局后的设计界面
在图 18.3.10中,箭头1指向的AXI SmartConnect是一种新型系统连接生成器,同样也是实现将一个或多个内存映射主设备连接到一个或多个内存映射从设备,一般可以作为AXI InterConnect的替代品,且具有更好的性能。箭头2指向的“Block Memory Generator”IP核,其中一个端口连接到了AXI BRAM控制器,另一个端口连接PL读BRAM IP核,这个IP核是我们自定义的IP核,接下来添加这个IP核。
自定义的IP核在例程工程目录下的ip_repo文件夹下,如图 18.3.11所示。大家可以直接拷贝这个文件夹至工程目录下,然后通过菜单栏的Tool→Setting→IP→Repository,点击“ADD”图标来添加自定义的IP核。由于本次实验的自定义IP核的部分设置和“自定义IP核-呼吸灯实验”有些区别,因此我们接下来在本次实验工程的基础上,来向大家演示创建一个新的自定义IP核。
在这里插入图片描述

图 18.3.11 ip_repo文件夹
点击菜单栏的“Tools”,选择“Creat and Package New IP…”,如下图所示:
在这里插入图片描述

图 18.3.12 创建一个新的自定义IP核
此时弹出创建和封装新IP核的页面,点击“NEXT”,选择“Create a new AXI4 Peripheral”,再次点击“NEXT”,进入下图所示页面。
在这里插入图片描述

图 18.3.13 自定义IP核设置页面
在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”,如下图所示:
在这里插入图片描述

图 18.3.14 编辑IP核
在弹出的页面中,点击“OK”,进入编辑自定义IP核的工程界面。
打开pl_bram_rd_v1_0.v文件,在Users to add ports here和User port ends中间行添加如下代码,这些端口用于连接BRAM端口的BRAM_PORTB。

17      // Users to add ports here
18      //RAM端口
19      output wire          ram_clk    , //RAM时钟
20      input  wire  [31:0]  ram_rd_data, //RAM中读出的数据
21      output wire          ram_en     , //RAM使能信号
22      output wire  [31:0]  ram_addr   , //RAM地址
23      output wire  [3:0]   ram_we     , //RAM读写控制信号
24      output wire  [31:0]  ram_wr_data, //RAM写数据
25      output wire          ram_rst    , //RAM复位信号,高电平有效
26      // User ports ends
在实例化pl_ram_rd_v1_0_S00_AXI模块的位置,添加以下代码。
57      //RAM端口   
58      .ram_clk      (ram_clk    ),
59      .ram_rd_data  (ram_rd_data),
60      .ram_en       (ram_en     ),
61      .ram_addr     (ram_addr   ),
62      .ram_we       (ram_we     ),
63      .ram_wr_data  (ram_wr_data),
64      .ram_rst      (ram_rst    ),        

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

17      // Users to add ports here
18      //RAM端口
19      output wire          ram_clk    , //RAM时钟
20      input  wire  [31:0]  ram_rd_data, //RAM中读出的数据
21      output wire          ram_en     , //RAM使能信号
22      output wire  [31:0]  ram_addr   , //RAM地址
23      output wire  [3:0]   ram_we     , //RAM读写控制信号
24      output wire  [31:0]  ram_wr_data, //RAM写数据
25      output wire          ram_rst    , //RAM复位信号,高电平有效
26      // User ports ends
在程序最后Add user logic here和User logic ends的中间行,添加如下代码:
407     // Add user logic here
408     bram_rd u_bram_rd(
409     .clk          (S_AXI_ACLK),
410     .rst_n        (S_AXI_ARESETN),
411     .start_rd     (slv_reg0[0]),
412     .start_addr   (slv_reg1),
413     .rd_len       (slv_reg2),
414     //RAM端口   
415     .ram_clk      (ram_clk    ),
416     .ram_rd_data  (ram_rd_data),
417     .ram_en       (ram_en     ),
418     .ram_addr     (ram_addr   ),
419     .ram_we       (ram_we     ),
420     .ram_wr_data  (ram_wr_data),
421     .ram_rst      (ram_rst    )
422     );
423     // User logic ends

这段代码例化了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模块的代码如下:

1  module bram_rd(
2      input                clk        , //时钟信号
3      input                rst_n      , //复位信号
4      input                start_rd   , //读开始信号
5      input        [31:0]  start_addr , //读起始地址  
6      input        [31:0]  rd_len     , //读数据的长度
7      //RAM端口
8      output               ram_clk    , //RAM时钟
9      input        [31:0]  ram_rd_data, //RAM中读出的数据
10     output  reg          ram_en     , //RAM使能信号
11     output  reg  [31:0]  ram_addr   , //RAM地址
12     output  reg  [3:0]   ram_we     , //RAM读写控制信号
13     output  reg  [31:0]  ram_wr_data, //RAM写数据
14     output               ram_rst      //RAM复位信号,高电平有效
15 );
16 
17 //reg define
18 reg  [1:0]   flow_cnt;
19 reg          start_rd_d0;
20 reg          start_rd_d1;
21 
22 //wire define
23 wire         pos_start_rd;
24 
25 //*****************************************************
26 //**                  main code
27 //*****************************************************
28 
29 assign  ram_rst = 1'b0;
30 assign  ram_clk = clk ;
31 assign pos_start_rd = ~start_rd_d1 & start_rd_d0;
32 
33 //延时两拍,采start_rd信号的上升沿
34 always @(posedge clk or negedge rst_n) begin
35     if(!rst_n) begin
36         start_rd_d0 <= 1'b0;   
37         start_rd_d1 <= 1'b0; 
38     end
39     else begin
40         start_rd_d0 <= start_rd;   
41         start_rd_d1 <= start_rd_d0;     
42     end
43 end
44 
45 //根据读开始信号,从RAM中读出数据
46 always @(posedge clk or negedge rst_n) begin
47     if(!rst_n) begin
48         flow_cnt <= 2'd0;
49         ram_en <= 1'b0;
50         ram_addr <= 32'd0;
51         ram_we <= 4'd0;
52     end
53     else begin
54         case(flow_cnt)
55             2'd0 : begin
56                 if(pos_start_rd) begin
57                     ram_en <= 1'b1;
58                     ram_addr <= start_addr;
59                     flow_cnt <= flow_cnt + 2'd1;
60                 end
61             end
62             2'd1 : begin
63                 if(ram_addr - start_addr == rd_len - 4) begin  //数据读完
64                     ram_en <= 1'b0;
65                     flow_cnt <= flow_cnt + 2'd1;
66                 end
67                 else
68                     ram_addr <= ram_addr + 32'd4;              //地址累加4
69             end
70             2'd2 : begin
71                 ram_addr <= 32'd0; 
72                 flow_cnt <= 2'd0;
73             end
74         endcase    
75     end
76 end
77 
78 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”图标,如下图所示:
在这里插入图片描述

图 18.3.15 添加总线接口
在弹出的页面中,在Name一栏,输入BRAM_PORT,然后点击Interface Definition(总线定义)右侧的“…”图标,如下图所示:
在这里插入图片描述

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

图 18.3.17 选择“bram_ctrl”
将页面切换至“Port Mapping”选项页,左侧窗口为总线逻辑端口,右侧窗口为IP核定义的物理端口,最下面的窗口是映射端口的总结页面,如下图所示:
在这里插入图片描述

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

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

图 18.3.20 映射端口总结页面1
在这里插入图片描述

图 18.3.21 映射端口总结页面2
接下来将页面切换至“Parameters”选项页,展开Auto-calculated,选中“MASTER_TYPE”,然后点击单个向右的箭头,如下图所示:
在这里插入图片描述

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

图 18.3.2
3 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”将其添加到设计中,如下图所示:
在这里插入图片描述

图 18.3.24 添加“pl_bram_rd”IP核
添加完成后,点击界面上方的“Run Connection Automation”,在弹出页面的左侧勾选“All Automation”,下面列出了会自动连接的模块及其接口。点击“OK”,工具会自动连接PL读BRAM IP核的BRAM_PORT接口和BRAM IP核的BRAM_PORTB接口。
连接完成后,在Diagram窗口空白处右击,然后选择“Regenerate Layout”对设计进行重新布局,布局后的界面如下图所示:
在这里插入图片描述

图 18.3.25 重新布局后的设计界面
接下来切换至“Address Editor”页面,展开“zynq_ultra_ps_e_0”下的“Data”,将范围设置成“4K”,如图 18.3.26所示。由于BRAM的数据位宽是32位,因此BRAM的存储深度为1K。
在这里插入图片描述

图 18.3.26 分配存储空间
到这里我们的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”,如下图所示:
在这里插入图片描述

图 18.3.27 点击“Run Synthesis”
生成综合文件后,点击“Set Up Debug”来添加信号至ILA界面,如下图所示:
在这里插入图片描述

图 18.3.28 点击“Set Up Debug”
在弹出的Set Up Debug窗口中点击“Next”,接下来的窗口中点击“Find Nets to Add”依次搜索并添加blk_mem_gen_0模块实例化U0模块下的addrb、doutb和enb信号,这三个信号分别对应BRAM PORTB端口的地址、读出的数据和RAM使能信号,如下图所示:
在这里插入图片描述

图18.3.29 搜索添加信号至ILA
然后分别设置三个信号的Clock Domain,设置方法为右击每个信号,选择Select Clock Domain,在弹出窗口的下拉栏中选择“LOCAL_CLOCK”,此时可以看到下面显示栏中出现“design1_1_i/zynq_ultra_ps_e_0/inst/pl_clk0”,然后点击“OK”,设置完成后如下图所示:
在这里插入图片描述

图18.3.30 添加待观察信号
点击Next按钮,接下来设置采样的深度,采样深度设置的值越大,采集到的数据越多。
在这里插入图片描述

图18.3.31 ILA设置选项
点击Next按钮,最后点击Finish按钮完成信号的添加,按快捷键“Ctrl + S”保存Synthesized Design,在弹出的Save Constraints窗口中输入约束文件名,这里直接输入工程名“ps_pl_bram”,然后点击“OK”。
最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。
在生成Bitstream之后,在菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。将导出的“design_1_wrapper.xsa”文件放到vitis文件夹,然后在菜单栏选择Tools > Launch Vitis,启动VITIS软件。
18.4软件设计
在VITIS软件中新建一个空的应用工程,应用工程名为“pl_sysmon_vpvn”。然后为应用工程新建一个源文件“main.c”,我们在新建的main.c文件中输入本次实验的代码:

1  #include "xil_printf.h"
2  #include "stdio.h"
3  #include "pl_bram_rd.h"
4  #include "xbram.h"
5  
6  #define PL_BRAM_BASE        XPAR_PL_BRAM_RD_0_S00_AXI_BASEADDR   //PL_RAM_RD基地址
7  #define PL_BRAM_START       PL_BRAM_RD_S00_AXI_SLV_REG0_OFFSET   //RAM读开始寄存器地址
8  #define PL_BRAM_START_ADDR  PL_BRAM_RD_S00_AXI_SLV_REG1_OFFSET   //RAM起始寄存器地址
9  #define PL_BRAM_LEN         PL_BRAM_RD_S00_AXI_SLV_REG2_OFFSET   //PL读RAM的深度
10 
11 #define START_ADDR          0  //RAM起始地址 范围:0~1023
12 #define BRAM_DATA_BYTE      4  //BRAM数据字节个数
13 
14 char ch_data[1024];            //写入BRAM的字符数组
15 int ch_data_len;               //写入BRAM的字符个数
16 
17 //函数声明
18 void str_wr_bram();
19 void str_rd_bram();
20 
21 //main函数
22 int main()
23 {
24     while(1)
25     {
26         printf("Please enter data to read and write BRAM\n") ;
27         scanf("%1024s", ch_data);        //用户输入字符串
28         ch_data_len = strlen(ch_data);   //计算字符串的长度
29 
30         str_wr_bram();                   //将用户输入的字符串写入BRAM中
31         str_rd_bram();                   //从BRAM中读出数据
32     }
33 }
34 
35 //将字符串写入BRAM
36 void str_wr_bram()
37 {
38     int i=0,wr_cnt = 0;
39     //每次循环向BRAM中写入1个字符
40     for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + ch_data_len) ;
41             i += BRAM_DATA_BYTE){
42         XBram_WriteReg(XPAR_BRAM_0_BASEADDR,i,ch_data[wr_cnt]) ;
43         wr_cnt++;
44     }
45     //设置BRAM写入的字符串长度
46     PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_LEN , BRAM_DATA_BYTE*ch_data_len) ;
47     //设置BRAM的起始地址
48     PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START_ADDR, BRAM_DATA_BYTE*START_ADDR) ;
49     //拉高BRAM开始信号
50     PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 1) ;
51     //拉低BRAM开始信号
52     PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 0) ;
53 }
54 
55 //从BRAM中读出数据
56 void str_rd_bram()
57 {
58     int read_data=0,i=0;
59     //循环从BRAM中读出数据
60     for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + ch_data_len) ;
61             i += BRAM_DATA_BYTE){
62         read_data = XBram_ReadReg(XPAR_BRAM_0_BASEADDR,i) ;
63         printf("BRAM address is %d\t,Read data is %c\n",i/BRAM_DATA_BYTE ,read_data) ;
64     }
65 }

主函数实现了通过串口接收用户数据,然后将用户数据写入BRAM,并从BRAM中读出数据的功能。主函数包含一个无限循环while语句,程序首先打印一串字符串来提示用户开始输入数据,然后通过scanf函数来接收串口的数据,存放在字符串数组ch_data中。这里的“%1024s”是指单次最大接收1024个字符,这是因为BRAM的深度为1024,防止写入BRAM的数据个数超出BRAM的深度。
然后计算接收到的数据个数,并调用自己编写的str_wr_bram函数将数据写入BRAM。数据写完后,调用自己编写的str_rd_bram()函数从BRAM中读出数据。
接下来在程序的第35至53行,将接收到的数据写入BRAM中,并配置PL端开始从BRAM中读取数据。在写BRAM的过程中,通过XBram_WriteReg()函数将接收到的数据按照起始地址,依次写入BRAM中。在数据写入完成后,通过AXI总线,配置PL端读取BRAM的数据个数和起始地址,并驱动PL读开始信号输出一个脉冲信号。
在程序第55至65行PS端读取BRAM中的数据。通过XBram_ReadReg()函数按照BRAM的起始地址,依次从BRAM中读出数据,并通过串口打印出来。
18.5下载验证
首先我们将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB_UART(开发板PS PORT)接口与电脑连接,用于串口通信。最后连接开发板的电源,给开发板上电。
打开Vitis Terminal终端,设置并连接串口。然后下载本次实验的程序,下载完成后,在下方的VITIS Terminal中可以看到应用程序打印的信息“Please enter data to read and write BRAM”。实验结果如下图所示:
在这里插入图片描述

图 18.5.1 下载验证
此时返回到Vivado软件,在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,展开“Open Hardware Manager”,点击该选项中的“Open Target”,选择“Auto Connect”,如下图所示:
在这里插入图片描述

图 18.5.2自动连接器件
此时会打开ILA观察波形的界面。我们接下来设置触发条件,点击“Trigger Setup”界面下的“+”图标,选择enb信号,将Value的值设置为“R”,如下图所示:
在这里插入图片描述

图 18.5.3 添加触发条件
点击工具栏的Run Trigger for this ILA core,此时界面上出现了等待触发的状态,如下图所示:
在这里插入图片描述

图 18.5.4 等待触发
接下来打开VITIS的软件界面,在VITIS Terminal中点击箭头处的按钮打开输入框,输入www.openedv.com,并按回车键发送。此时可以看到串口中打印的信息,如图 18.5.5和图 18.5.6所示:
在这里插入图片描述

图 18.5.5 发送数据界面
在这里插入图片描述

图 18.5.6 窗口打印信息界面
由上图可知,PS写入BRAM的数据和从BRAM中读出的数据一致。接下来我们在ILA界面观察PL从BRAM中读出的数据是否正确。
返回到Vivado界面,可以看到ILA逻辑分析仪的界面已经显示触发信号的波形。对波形进行放大,将doutb的显示格式改成ASCII,如图 18.5.7和图 18.5.8所示:
在这里插入图片描述

图 18.5.7 切换显示的进制
在这里插入图片描述

图 18.5.8 ILA波形显示界面
由上图可知,在enb拉高期间,doutb输出的数据分别是“www.openedv.com”,和串口助手发送的数据和打印出的数据一致,说明本次实验验证成功。
在这里需要注意的是,如果大家使用其它串口调试助手的时候,需要勾选“发送新行”,否则PS的串口接收程序会一直等待接收串口的数据,串口调试助手的界面设置如下:
在这里插入图片描述

图 18.5.9 串口助手设置界面

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/374146.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在 Flutter 中使用 webview_flutter 4.0 | 基础用法与事件处理

大家好&#xff0c;我是 17。 Flutter WebView 一共写了四篇文章 在 Flutter 中使用 webview_flutter 4.0 | 基础用法与事件处理在 Flutter 中使用 webview_flutter 4.0 | js 交互Flutter WebView 性能优化&#xff0c;让 h5 像原生页面一样优秀&#xff0c;已入选 掘金一周 …

AI绘画进军三次元,有人用它打造赛博女友?(diffusion)

目录0 写在前面1 AI绘画技术飞跃2 效果展示3 环境配置3.1 下载基础模型3.2 更新.NET和模型3.3 下载绘画模型3.4 启动项目3.5 标签配置4 结语0 写在前面 机器学习强基计划聚焦深度和广度&#xff0c;加深对机器学习模型的理解与应用。“深”在详细推导算法模型背后的数学原理&a…

内存数据库-4-[redis]在ubuntu中离线安装

Ubuntu20.04(linux)离线安装redis 官网redis下载地址 下载安装包redis-6.0.9.tar.gz。 1 下载安装 (1)解压 sudo tar -xzvf redis-6.0.9.tar.gz -C /usr/local/ cd /usr/local/redis-6.0.9/(2)编译 sudo make(3)测试 sudo dpkg -i libtcl8.6_8.6.10dfsg-1_amd64.deb sudo d…

纯x86汇编实现的多线程操作系统实践 - 第七章 AP2的用户进程

AP2用户进程的代码为task2.asm。该用户进程将在界面上显示一个移动的弹球。一旦在界面上点击鼠标左键&#xff0c;弹球就会直接从鼠标点击处重新出现并继续移动。如何在界面上显示出一个持续移动的小球&#xff1f;计算小球将移动到的区域1->保留该区域中将被小球覆盖的点-&…

智慧物联网源码带手机端源码 物联网系统源码

在智慧工厂领域&#xff0c;智慧城市领域&#xff0c;都需要对设备进行监控。比如工厂需要对周围环境温度、湿度、气压、电压&#xff0c;灯的开关进行监控。这时候就需要物联网平台来进行管理。 推荐一个基于java开发的物联网平台&#xff0c;前端HTML带云组态、可接入视频监…

【华为OD机试模拟题】用 C++ 实现 - 网上商城优惠活动(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明网上商城优惠活动题目输入输出备注示例一输入输出说明输入说明输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才…

Android zygote进程启动流程

zygote启动过程中涉及到以下模块&#xff1a; app_processzygote USAPsocketFileDescriptor (FD) AndroidRuntimeAppRuntime &#xff08;定义于app_process模块&#xff0c;继承自AndroidRuntime。&#xff09; init进程启动zygote进程&#xff1a; #init.zygote32_64.rc s…

【华为OD机试模拟题】用 C++ 实现 - 统计匹配的二元组个数(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明统计匹配的二元组个数题目输入输出描述示例一输入输出说明示例二输入输出说明备注Code使用说明 参加华为od机试,一定要注意不要完全背诵代码&

python读写hdfs文件的实用解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…

Python学习笔记之环境搭建

Python学习笔记之环境搭建1. 下载Python2. Windows 安装最新Python3. Linux 安装最新PythonPython是一种编程语言&#xff0c;可以让您更快地工作并更有效地集成系统。 您可以学习使用Python&#xff0c;并立即看到生产力的提高和维护成本的降低。 Python是荷兰程序员吉多范罗苏…

使用 OpenCV 进行面部和眼睛检测

OpenCV是构建计算机视觉应用程序的强大工具。计算机视觉中最常见的任务之一是人脸检测&#xff0c;它涉及识别图像或视频中人脸的存在、位置和面部特征。在本文中&#xff0c;我们将学习如何使用 Haar 级联分类器检测图像中的人脸。先决条件在开始之前&#xff0c;你需要在计算…

Android打造万能的BannerView无限轮播图

效果图&#xff1a;工程目录图&#xff1a;BannerAdapter&#xff1a;banner轮播图的适配器&#xff0c;因为服务器返回的列表图片的url&#xff0c;显示的时候需要转成IamgeViw&#xff1b; BannerScroller&#xff1a;设置切换页面的持续时间&#xff1b; BannerView&…

怎样划分MES系统实施阶段,三分钟告诉你整体实施思路

MES系统的实施阶段划分的思路是&#xff1a;在集成的前提下实现可视化&#xff0c;在可视化的基础上实现精细化&#xff0c;在精细化的前提下实现均衡化。生产过程透明的目的就是要实现生产过程的可视化&#xff0c;实现精细化生产。首先要做的就是收集生产信息&#xff0c;这是…

Linux->进程地址空间

目录 前言&#xff1a; 1. 程序地址空间回顾 2. 进程空间是什么 3. 进程地址空间与内存 4. 进程地址空间和内存的关联 5. 为什么要有进程地址空间 前言&#xff1a; 我们在平时学习的过程当中总是听到栈、堆、代码段等等储存空间&#xff0c;但是这些东西到底是什么&…

CSS3新特性

CSS3新特性 1.1、字体图标 何为字体图标&#xff1f; 字体图标展示的是图标&#xff0c;本质是字体。用于处理简单的、颜色单一的图片 字体图标的优点&#xff1a; 灵活性&#xff1a;灵活地修改样式&#xff0c;例如&#xff1a;尺寸、颜色等轻量级&#xff1a;体积小、渲…

Java并发编程(1)—— 操作系统、Linux、Java中进程与线程的区别

一、操作系统中什么是线程和进程 线程和进程都是操作系统中定义的结构&#xff0c;进程是系统中一个独立的活动程序&#xff0c;比如像QQ、网易云音乐&#xff0c;进程是操作系统进行资源分配的基本单位&#xff0c;一个进程中的所有线程共享进程内的资源&#xff0c;而线程则…

【Python学习笔记】第十八节 Python 内置函数

Python 内置函数内置函数就是Python给你提供的, 拿来直接用的函数&#xff0c;比如print&#xff0c;input等Python 内置函数一览表内置函数abs()divmod()input()open()staticmethod()all()enumerate()int()ord()str()any()eval()isinstance()pow()sum()basestring()execfile()…

ARMv8 同步和信号量(读写一致性问题):Load-Exclusive/Store-Exclusive指令详解

目录 一&#xff0c;Local Monitor 与 Global Monitor 1&#xff0c;Local Monitor 2&#xff0c;Global Monitor 二&#xff0c;Exclusive 指令的简单使用 三&#xff0c;Exclusive 示例程序 1&#xff0c;原子自加1程序 2&#xff0c;原子锁程序 四&#xff0c; 多处理…

算法设计与智能计算 || 专题一: 算法基础

专题一: 算法基础 文章目录专题一: 算法基础1. 算法的定义及特点1.1 算法的基本特征1.2 算法的基本要素1.3 算法的评定2 算法常见执行方法2.1 判断语句2.2 循环语句2.3 综合运用3. 计算复杂度4. 代码的重用5. 类函数的定义与使用5.1 定义类5.2 调用类函数1. 算法的定义及特点 …

_hand-2

实现一个迷你版的vue 入口 // js/vue.js class Vue {constructor (options) {// 1. 通过属性保存选项的数据this.$options options || {}this.$data options.data || {}this.$el typeof options.el string ? document.querySelector(options.el) : options.el// 2. 把da…