搜索
bottom↓
回复: 0

【正点原子FPGA连载】 第十四章基于BRAM的PS和PL的数据交互--摘自【正点原子】领航者 ZYNQ 之嵌入式开发指南

[复制链接]

出0入234汤圆

发表于 2020-7-30 11:22:31 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-10-24 10:28 编辑

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900
QQ群头像.png
5)关注正点原子公众号,获取最新资料


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

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



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



14.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 接口模式的框图如图 14.1.1所示。
阿莫论坛发帖领航者专用1520.png

图 14.1.1 AXI4-Lite BRAM控制器框图

AXI4接口模式的BRAM控制器支持的数据位宽为32位、64位、128位、512位和1024位,而AXI4-Lite接口仅支持32位数据位宽。由图 14.1.1可知,PS通过AXI4-Lite接口访问BRAM,当使能ECC选项时,ECC允许AXI主接口检测和纠正BRAM块中的单位和双位错误。AXI BRAM控制器作为AXI总线的从接口,和AXI主接口实现互联,来对BRAM进行读写操作。针对不同的应用场合,该IP核支持单次传输和突发传输两种方式。
14.2实验任务
本章的实验任务是PS将串口接收到的数据写入BRAM,然后从BRAM中读出数据,并通过串口打印出来;与此同时,PL从BRAM中同样读出数据,并通过ILA来观察读出的数据与串口打印的数据是否一致。
14.3硬件设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示:
阿莫论坛发帖领航者专用1979.png

图 14.3.1 系统框图

在图 5.3.1中,PS端的M_AXI_GP0作为主端口,与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设计(system.bd)并添加ZYNQ7 Processing System模块。接下来按照《“Hello World”实验》中的步骤2-7、2-8分别配置PS的UART和DDR控制器。需要特别注意的是,我们在《“Hello World”实验》的步骤2-10中,移除了PS中与PL端交互的接口信号,这些接口在我们本次实验中需要予以保留。
最后点击右下角的“OK”,本次实验ZYNQ处理系统就配置完成了。
ZYNQ7 PS配置完成后其接口如图 5.3.4所示:
阿莫论坛发帖领航者专用11699.png

图 14.3.2 ZYNQ7模块接口

接下来我们要在Block Design中添加AXI BRAM Controller IP核,在Diagram窗口空白位置右击,然后选择“Add IP”。在弹出的IP目录中搜索“AXI BRAM”,最后双击搜索结果中的“AXI BRAM Controller”将其添加到设计中,如下图所示:
阿莫论坛发帖领航者专用11908.png

图 14.3.3 添加AXI GPIO IP核

添加完成后,双击AXI BRAM Controller IP核,打开其配置界面如下图所示:
阿莫论坛发帖领航者专用12687.png

图 14.3.4 BRAM控制器配置页面

在图 14.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”将其添加到设计中,如下图所示:
阿莫论坛发帖领航者专用13036.png

图 14.3.5 添加Block Memory Generator IP核

添加完成后,双击Block Memory Generator IP核,打开其配置界面如下图所示:
阿莫论坛发帖领航者专用13279.png

图 14.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”的选项,如下图所示:
阿莫论坛发帖领航者专用13528.png

图 14.3.7 Block Memory Generator IP核“Other Options”配置页面

BRAM IP核内置了一个安全电路以降低BRAM数据出现错误的概率,如果勾选使能安全电路,BRAM端口会增加rsta_busy端口和rstb_busy端口,用于表示何时可以访问BRAM。这里直接取消使能安全电路,最后点击“OK”按钮完成设置。
添加完成后Diagram窗口如下图所示:
阿莫论坛发帖领航者专用13755.png

图 14.3.8 Diagram窗口

接下来点击图 14.3.8中箭头1所指示的位置,在弹出的对话框左侧确认勾选processing_system7_0,然后点击“OK”。这一步骤会将ZYNQ7 PS模块的DDR和FIXED_IO端口引出。
然后点击图 14.3.8中箭头2所指示的位置,弹出对话框如下图所示:
1439.png

图 14.3.9 自动连接

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

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

14311.png

图 14.3.11 ip_repo文件

点击菜单栏的“Tools”,选择“Creat and Package New IP…”,如下图所示:
14312.png

图 14.3.12 创建一个新的自定义IP核

此时弹出创建和封装新IP核的页面,点击“NEXT”,选择“Create a new AXI4 Peripheral”,再次点击“NEXT”,进入下图所示页面。
阿莫论坛发帖领航者专用14545.png

图 14.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”,如下图所示:
阿莫论坛发帖领航者专用14856.png

图 14.3.14 编辑IP核

在弹出的页面中,点击“OK”,进入编辑自定义IP核的工程界面。
打开pl_bram_rd_v1_0.v文件,在Users to add ports here和User port ends中间行添加如下代码,这些端口用于连接BRAM端口的BRAM_PORTB。
  1. 17      // Users to add ports here
  2. 18      //RAM端口
  3. 19      output wire          ram_clk    , //RAM时钟
  4. 20      input  wire  [31:0]  ram_rd_data, //RAM中读出的数据
  5. 21      output wire          ram_en     , //RAM使能信号
  6. 22      output wire  [31:0]  ram_addr   , //RAM地址
  7. 23      output wire  [3:0]   ram_we     , //RAM读写控制信号
  8. 24      output wire  [31:0]  ram_wr_data, //RAM写数据
  9. 25      output wire          ram_rst    , //RAM复位信号,高电平有效
  10. 26      // User ports ends
  11. 在实例化pl_ram_rd_v1_0_S00_AXI模块的位置,添加以下代码。
  12. 57      //RAM端口   
  13. 58      .ram_clk      (ram_clk    ),
  14. 59      .ram_rd_data  (ram_rd_data),
  15. 60      .ram_en       (ram_en     ),
  16. 61      .ram_addr     (ram_addr   ),
  17. 62      .ram_we       (ram_we     ),
  18. 63      .ram_wr_data  (ram_wr_data),
  19. 64      .ram_rst      (ram_rst    ),        
  20. 打开pl_bram_rd_v1_0_S00_AXI.v文件,同样在Users to add ports here和User port ends中间行添加如下代码:
  21. 17      // Users to add ports here
  22. 18      //RAM端口
  23. 19      output wire          ram_clk    , //RAM时钟
  24. 20      input  wire  [31:0]  ram_rd_data, //RAM中读出的数据
  25. 21      output wire          ram_en     , //RAM使能信号
  26. 22      output wire  [31:0]  ram_addr   , //RAM地址
  27. 23      output wire  [3:0]   ram_we     , //RAM读写控制信号
  28. 24      output wire  [31:0]  ram_wr_data, //RAM写数据
  29. 25      output wire          ram_rst    , //RAM复位信号,高电平有效
  30. 26      // User ports ends
  31. 在程序最后Add user logic here和User logic ends的中间行,添加如下代码:
  32. 407     // Add user logic here
  33. 408     bram_rd u_bram_rd(
  34. 409     .clk          (S_AXI_ACLK),
  35. 410     .rst_n        (S_AXI_ARESETN),
  36. 411     .start_rd     (slv_reg0[0]),
  37. 412     .start_addr   (slv_reg1),
  38. 413     .rd_len       (slv_reg2),
  39. 414     //RAM端口   
  40. 415     .ram_clk      (ram_clk    ),
  41. 416     .ram_rd_data  (ram_rd_data),
  42. 417     .ram_en       (ram_en     ),
  43. 418     .ram_addr     (ram_addr   ),
  44. 419     .ram_we       (ram_we     ),
  45. 420     .ram_wr_data  (ram_wr_data),
  46. 421     .ram_rst      (ram_rst    )
  47. 422     );
  48. 423     // User logic ends
复制代码

这段代码例化了bram_rd模块,其中start_rd信号是开始读BRAM的开始信号,start_addr是设置BRAM的读起始地址,rd_len是设置读BRAM的个数,分别连接到AXI4-Lite总线的寄存器地址0、地址1和地址2对应的数据。
接下来在工程中创建一个新的模块,命名为“bram_rd”,位于../ps_pl_bram/ip_repo/pl_bram_rd_1.0/hdl路径下,bram_rd模块的代码如下:
  1. 1  module bram_rd(
  2. 2      input                clk        , //时钟信号
  3. 3      input                rst_n      , //复位信号
  4. 4      input                start_rd   , //读开始信号
  5. 5      input        [31:0]  start_addr , //读起始地址  
  6. 6      input        [31:0]  rd_len     , //读数据的长度
  7. 7      //RAM端口
  8. 8      output               ram_clk    , //RAM时钟
  9. 9      input        [31:0]  ram_rd_data, //RAM中读出的数据
  10. 10     output  reg          ram_en     , //RAM使能信号
  11. 11     output  reg  [31:0]  ram_addr   , //RAM地址
  12. 12     output  reg  [3:0]   ram_we     , //RAM读写控制信号
  13. 13     output  reg  [31:0]  ram_wr_data, //RAM写数据
  14. 14     output               ram_rst      //RAM复位信号,高电平有效
  15. 15 );
  16. 16
  17. 17 //reg define
  18. 18 reg  [1:0]   flow_cnt;
  19. 19 reg          start_rd_d0;
  20. 20 reg          start_rd_d1;
  21. 21
  22. 22 //wire define
  23. 23 wire         pos_start_rd;
  24. 24
  25. 25 //*****************************************************
  26. 26 //**                  main code
  27. 27 //*****************************************************
  28. 28
  29. 29 assign  ram_rst = 1'b0;
  30. 30 assign  ram_clk = clk ;
  31. 31 assign pos_start_rd = ~start_rd_d1 & start_rd_d0;
  32. 32
  33. 33 //延时两拍,采start_rd信号的上升沿
  34. 34 always @(posedge clk or negedge rst_n) begin
  35. 35     if(!rst_n) begin
  36. 36         start_rd_d0 <= 1'b0;   
  37. 37         start_rd_d1 <= 1'b0;
  38. 38     end
  39. 39     else begin
  40. 40         start_rd_d0 <= start_rd;   
  41. 41         start_rd_d1 <= start_rd_d0;     
  42. 42     end
  43. 43 end
  44. 44
  45. 45 //根据读开始信号,从RAM中读出数据
  46. 46 always @(posedge clk or negedge rst_n) begin
  47. 47     if(!rst_n) begin
  48. 48         flow_cnt <= 2'd0;
  49. 49         ram_en <= 1'b0;
  50. 50         ram_addr <= 32'd0;
  51. 51         ram_we <= 4'd0;
  52. 52     end
  53. 53     else begin
  54. 54         case(flow_cnt)
  55. 55             2'd0 : begin
  56. 56                 if(pos_start_rd) begin
  57. 57                     ram_en <= 1'b1;
  58. 58                     ram_addr <= start_addr;
  59. 59                     flow_cnt <= flow_cnt + 2'd1;
  60. 60                 end
  61. 61             end
  62. 62             2'd1 : begin
  63. 63                 if(ram_addr - start_addr == rd_len - 4) begin  //数据读完
  64. 64                     ram_en <= 1'b0;
  65. 65                     flow_cnt <= flow_cnt + 2'd1;
  66. 66                 end
  67. 67                 else
  68. 68                     ram_addr <= ram_addr + 32'd4;              //地址累加4
  69. 69             end
  70. 70             2'd2 : begin
  71. 71                 ram_addr <= 32'd0;
  72. 72                 flow_cnt <= 2'd0;
  73. 73             end
  74. 74         endcase   
  75. 75     end
  76. 76 end
  77. 77
  78. 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”图标,如下图所示:
阿莫论坛发帖领航者专用19993.png

图 14.3.15 添加总线接口

在弹出的页面中,在Name一栏,输入BRAM_PORT,然后点击Interface Definition(总线定义)右侧的“…”图标,如下图所示:
阿莫论坛发帖领航者专用110129.png

图 14.3.16 选择一个总线接口

接下来在弹出页面的搜索框中输入“BRAM”,选中Advanced一栏下的“bram_rtl”,即BRAM总线接口。然后点击“OK”按钮,如下图所示:
阿莫论坛发帖领航者专用110268.png

图 14.3.17 选择“bram_ctrl”

将页面切换至“Port Mapping”选项页,左侧窗口为总线逻辑端口,右侧窗口为IP核定义的物理端口,最下面的窗口是映射端口的总结页面,如下图所示:
阿莫论坛发帖领航者专用110413.png

图 14.3.18 端口映射页面

点击左侧的“EN”端口,然后点击右侧的“ram_en”端口,此时页面上的“Map Ports”变成可以点击的按钮,点击这个按钮,此时在映射端口总结页面可以看到这两个端口映射到了一起,如下图所示:
阿莫论坛发帖领航者专用110573.png

图 14.3.19 映射“EN”端口和“ram_en”端口


这个步骤是将顶层模块定义的“ram_en”端口和BRAM的“EN”端口映射到一起,我们接下来将其余接口分别一一映射,如图 14.3.20和图 14.3.21所示:
阿莫论坛发帖领航者专用110844.png

图 14.3.20 映射端口总结页面1

阿莫论坛发帖领航者专用110779.png

图 14.3.21 映射端口总结页面2

接下来将页面切换至“Parameters”选项页,展开Auto-calculated,选中“MASTER_TYPE”,然后点击单个向右的箭头,如下图所示:
阿莫论坛发帖领航者专用110987.png

图 14.3.22 添加“MASTER_TPYE”参数

此时,“MASTER_TYPE”参数会出现在右侧Overridden目录下,最后点击“OK”按钮完成BRAM接口的封装,如下图所示:
阿莫论坛发帖领航者专用111127.png

图 14.3.23 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”将其添加到设计中,如下图所示:
阿莫论坛发帖领航者专用111429.png

图 14.3.24 添加“pl_bram_rd”IP核

添加完成后,点击界面上方的“Run Connection Automation”,在弹出页面的左侧勾选“All Automation”,下面列出了会自动连接的模块及其接口。点击“OK”,工具会自动连接PL读BRAM IP核的BRAM_PORT接口和BRAM IP核的BRAM_PORTB接口。
连接完成后,在Diagram窗口空白处右击,然后选择“Regenerate Layout”对设计进行重新布局,布局后的界面如下图所示:
阿莫论坛发帖领航者专用111717.png

图 14.3.25 重新布局后的设计界面

接下来切换至“Address Editor”页面,展开“processing_system7_0”下的“Data”,将范围设置成“4K”,如图 14.3.26所示。由于BRAM的数据位宽是32位,因此BRAM的存储深度为1K。
阿莫论坛发帖领航者专用111921.png

图 14.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”,如下图所示:
阿莫论坛发帖领航者专用112286.png

图 14.3.27 点击“Run Synthesis”

生成综合文件后,点击“Set Up Debug”来添加信号至ILA界面,如下图所示:
阿莫论坛发帖领航者专用112402.png

图 14.3.28 点击“Set Up Debug”

依次添加blk_mem_gen_0模块实例化U0模块下的addrb、doutb和enb信号,这三个信号分别对应BRAM PORTB端口的地址、读出的数据和RAM使能信号,如下图所示:
阿莫论坛发帖领航者专用112566.png

图 14.3.29 添加待观察信号

点击Next按钮,接下来设置采样的深度,采样深度设置的值越大,采集到的数据越多。
阿莫论坛发帖领航者专用112671.png

图 14.3.30 ILA设置选项

点击Next按钮,最后点击Finish按钮完成信号的添加,按快捷键“Ctrl + S”保存Synthesized Design。
最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。
在生成Bitstream之后,在菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。然后在菜单栏选择File > Launch SDK,启动SDK软件。
14.4软件设计
在SDK软件中新建一个BSP工程和一个空的应用工程,应用工程名为“ps_pl_bram”。然后为应用工程新建一个源文件“main.c”,我们在新建的main.c文件中输入本次实验的代码。代码的主体部分如下所示:
  1. 1  #include "xil_printf.h"
  2. 2  #include "stdio.h"
  3. 3  #include "pl_bram_rd.h"
  4. 4  #include "xbram.h"
  5. 5  
  6. 6  #define PL_BRAM_BASE        XPAR_PL_BRAM_RD_0_S00_AXI_BASEADDR   //PL_RAM_RD基地址
  7. 7  #define PL_BRAM_START       PL_BRAM_RD_S00_AXI_SLV_REG0_OFFSET   //RAM读开始寄存器地址
  8. 8  #define PL_BRAM_START_ADDR  PL_BRAM_RD_S00_AXI_SLV_REG1_OFFSET   //RAM起始寄存器地址
  9. 9  #define PL_BRAM_LEN         PL_BRAM_RD_S00_AXI_SLV_REG2_OFFSET   //PL读RAM的深度
  10. 10
  11. 11 #define START_ADDR          0  //RAM起始地址 范围:0~1023
  12. 12 #define BRAM_DATA_BYTE      4  //BRAM数据字节个数
  13. 13
  14. 14 char ch_data[1024];            //写入BRAM的字符数组
  15. 15 int ch_data_len;               //写入BRAM的字符个数
  16. 16
  17. 17 //函数声明
  18. 18 void str_wr_bram();
  19. 19 void str_rd_bram();
  20. 20
  21. 21 //main函数
  22. 22 int main()
  23. 23 {
  24. 24     while(1)
  25. 25     {
  26. 26         printf("Please enter data to read and write BRAM\n") ;
  27. 27         scanf("%1024s", ch_data);        //用户输入字符串
  28. 28         ch_data_len = strlen(ch_data);   //计算字符串的长度
  29. 29
  30. 30         str_wr_bram();                   //将用户输入的字符串写入BRAM中
  31. 31         str_rd_bram();                   //从BRAM中读出数据
  32. 32     }
  33. 33 }
  34. 34
  35. 35 //将字符串写入BRAM
  36. 36 void str_wr_bram()
  37. 37 {
  38. 38     int i=0,wr_cnt = 0;
  39. 39     //每次循环向BRAM中写入1个字符
  40. 40     for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + ch_data_len) ;
  41. 41             i += BRAM_DATA_BYTE){
  42. 42         XBram_WriteReg(XPAR_BRAM_0_BASEADDR,i,ch_data[wr_cnt]) ;
  43. 43         wr_cnt++;
  44. 44     }
  45. 45     //设置BRAM写入的字符串长度
  46. 46     PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_LEN , BRAM_DATA_BYTE*ch_data_len) ;
  47. 47     //设置BRAM的起始地址
  48. 48     PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START_ADDR, BRAM_DATA_BYTE*START_ADDR) ;
  49. 49     //拉高BRAM开始信号
  50. 50     PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 1) ;
  51. 51     //拉低BRAM开始信号
  52. 52     PL_BRAM_RD_mWriteReg(PL_BRAM_BASE, PL_BRAM_START , 0) ;
  53. 53 }
  54. 54
  55. 55 //从BRAM中读出数据
  56. 56 void str_rd_bram()
  57. 57 {
  58. 58     int read_data=0,i=0;
  59. 59     //循环从BRAM中读出数据
  60. 60     for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + ch_data_len) ;
  61. 61             i += BRAM_DATA_BYTE){
  62. 62         read_data = XBram_ReadReg(XPAR_BRAM_0_BASEADDR,i) ;
  63. 63         printf("BRAM address is %d\t,Read data is %c\n",i/BRAM_DATA_BYTE ,read_data) ;
  64. 64     }
  65. 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中读出数据,并通过串口打印出来。
程序设计完成后,按快捷键Ctrl+S保存main.c文件,工具会自动进行编译。编译完成后控制台(Console)中会出现提示信息“Build Finished”,同时在应用工程的Binaries目录下可以看到生成的elf文件。
14.5下载验证
首先我们将下载器与领航者底板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用Mini USB连接线将USB UART接口与电脑连接,用于串口通信。最后连接开发板的电源,并打开电源开关。
在SDK软件下方的SDK Terminal窗口中点击右上角的加号设置并连接串口。然后下载本次实验硬件设计过程中所生成的BIT文件,来对PL进行配置。最后下载软件程序,下载完成后,在下方的SDK Terminal中可以看到应用程序打印的信息“Please enter data to read and write BRAM”。实验结果如下图所示:
阿莫论坛发帖领航者专用116219.png

图 14.5.1 下载验证

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

此时会打开ILA观察波形的界面。我们接下来设置触发条件,点击“Trigger Setup”界面下的“+”图标,选择enb信号,将Value的值设置为“R”,如下图所示:
阿莫论坛发帖领航者专用116491.png

图 14.5.2 添加触发条件

点击工具栏的Run Trigger for this ILA core,此时界面上出现了等待触发的状态,如下图所示:
阿莫论坛发帖领航者专用116611.png

图 14.5.3 等待触发

接下来打开SDK的软件界面,在SDK Terminal窗口中输入www.openedv.com,并点击“Sned”按钮。此时可以看到串口中打印的信息,如图 14.5.4和图 14.5.5所示:
阿莫论坛发帖领航者专用116815.png

图 14.5.4 发送数据界面

阿莫论坛发帖领航者专用116876.png

图 14.5.5 窗口打印信息界面

由上图可知,PS写入BRAM的数据和从BRAM中读出的数据一致。接下来我们在ILA界面观察PL从BRAM中读出的数据是否正确。
返回到Vivado界面,可以看到ILA逻辑分析仪的界面已经显示触发信号的波形。对波形进行放大,将doutb的显示格式改成ASCII,如图 14.5.6和图 14.5.7所示:
阿莫论坛发帖领航者专用117139.png

图 14.5.6 切换显示的进制

阿莫论坛发帖领航者专用117201.png

图 14.5.7 ILA波形显示界面

由上图可知,在enb拉高期间,doutb输出的数据分别是“www.openedv.com”,和串口助手发送的数据和打印出的数据一致,说明本次实验验证成功。
在这里需要注意的是,如果大家使用其它串口调试助手的时候,需要勾选“发送新行”,否则PS的串口接收程序会一直等待接收串口的数据,串口调试助手的界面设置如下:
阿莫论坛发帖领航者专用117421.png

图 14.5.8 串口助手设置界面

阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 01:06

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表