本章节将演示 PS 访问 PL 端的 BRAM 资源,从而实现FPGA端和PS端的简单数据交互。
- 此章节内容适用于Smart ZYNQ SP SP2和 SL 版的板子 ( 不包含Smart ZYNQ 标准版 ),如是标准版或本站其他板子请看对应板子目录
- 本文在 vivado2018.3版本上演示
PS端与PL端在硬件上是相互独立的,之前介绍过通过寄存器的方式以及通过DDR的方式来实现数据交互,两者各有优缺点,寄存器的方式较为灵活但是受限于FPGA的逻辑资源数量,所以仅适用于小数据量通讯(几个或者几十个字节),而DDR支持大容量的数据存储,但数据必须是连贯的(对不连贯的数据,读取会比较慢),而BRAM就是一种折中的方式,可以实现少量(7020有4.9Mbit 的BRAM )不连续数据的读取。
Block Ram (Bram)原本是PL 端的资源, 曾经我们在实验 基于Smart ZYNQ (SP/SP2/SL 版) 的FPGA实验九 FPGA片内资源 BLOCK RAM IP核的使用 中介绍过如何在FPGA端使用 BRAM。 PS本身并不能直接读写 BRAM 的数据。但是我们可以通过AXI BRAM Controller IP 核来控制BRAM资源。
下面将进行具体的演示
一. 创建工程
1)打开Vivado 新建一个项目, 新建一个VIVADO 工程,打开软件 选中Create Project, 如下图所示
2)点击NEXT ,在出现的第二个对话框“Project name”中输入工程名;在“Project location”中选择保存路径;勾选“Create project subdirectory”(默认),最后点击“Next” 备注,所有的路径均不能出现中文名称
3)点击 RTL PROJECT 选项,点击NEXT
4) 第四步Add Sources 选项直接留空,NEXT
5)第五步Add Constraints 选项直接留空,NEXT
6)选择芯片型号 板子的芯片型号为 XC7Z020 封装是CLG484 所以型号我们选择 xc7z020clg484-1
7)确认所选信息 点击“Finish”,完成vivado的工程创建
之后 工程就新建好了, vivado 进入到开发界面
二、Vivado 部分设计
Vivado 部分工程的创建和之前已经介绍过很多次了,所以这里仅仅介绍重点:
创建一个BLOCK设计
1)IP INTEGRATOR→Create Block Design,在弹出的对话框中输入设计名,最后点击“OK”,如下图所示
2)在右侧的窗口里 ,点击加号,在选择框里搜索ZYNQ,并找到ZYNQ7 PROCESSING SYSTEM ,双击并打开
3)软件自动生成了一个 zynq的block 如下图所示,接下来要做一些相应的设置,双击下图中的ZYNQ核
4)依次在弹窗里找到DDR Configuration→DDR Controller Configuration→DDR3,在Memory Part下拉菜单中根据自己板子上的DDR来选择相应的DDR3,本实验所用到型号:MT41K256M16RE-125,数据位宽选择16bit 最后点击“OK”,如下图所示。
5)在PS的MIO配置选项增加UART部分,使能UART 0 并在IO选项里选择EMIO方式
设置完后点OK 完成ZYNQ的设置,然后引出UART 信号,因为我们增加了EMIO 的UART,所以这里需要将UART的引脚引出 ,右键选择ZYNQ模块的UART_0,然后选择Make External
6) 搜索并添加 AXI BRAM Controller IP 核 ,实验本身分两部分,第一部分是对其中一个bram进行读写,另一部分是PS写 BRAM 然后 PL读BRAM。所以这里需要添加三个AXI BRAM Controller IP 核
打开 AXI BRAM Controller 的参数设置页, 如下图所示调整 Number of BRM Interface 为1 (这样一个IP 负责读,一个IP负责写 可以更好的进行演示) 备注:三个IP都需要进行设置
7)搜索并添加 AXI BRAM Controller IP 核(这个就是PL端的BRAM 了)备注:这里需要添加两个Bram
类型选择 真双口 RAM: True Dual Port RAM ,这里的BRAM 不能调整容量 位宽,所以 Port A 和Port B 的地方保留默认就好。
在 Other Options 中去掉 Enable Safety Circuit 选项
8) 至此,我们有2个BRAM 和 3个 AXI BRAM Controller IP 核
9)分别 点 Run Block Automation 和Run Connection Automation
此后,系统将自动帮我们把需要的连线以及缺少的模块给补充好, 从下图可以看到BRAM 0的 A口和B口分别接到axi_bram_ctrl_0 和axi_bram_ctrl_1上了, 而另一个BRAM 1的A口 接在了axi_bram_ctrl_2上, B口暂时留空,后面将引出以接FPGA的逻辑使用。
10)右键选择BRAM模块的PORTB口,然后选择Make External,引出BRAM口
下图是本次工程完整的 BD 图
11) 在source→Design Source ,右键我们创建的BLOCK工程,点击create HDL wrapper,打包BLOCK文件并生成.v代码
在弹出的菜单里直接点选OK
PL部分代码设计
添加ILA模块
1) 为了方便演示观察结果,这里我们添加一个ILA 模块
2) 修改ILA的参数 观察的探针设置成2个 ,两个探针的位宽都设置成32
添加顶层模块
1) 回到vivado 主界面, 点选Add_Sources ,点选 add or create design souces 创建一个新的.v文件
2)点选Create File 在弹出的对话框里输入要添加的 .V文件的文件名 (这里是 TOP_MODULE)
3)在下一个窗口点OK 完成
双击打开 我们刚刚创建的TOP_MODULE 顶层模块,添加下列代码
`timescale 1ns / 1ps module TOP_MODULE( input CLK_50M, input UART_0_0_rxd, output UART_0_0_txd ); reg [31:0]BRAM_addr; wire [31:0]BRAM_dout; reg [31:0]BRAM_addr_Lag; always@(posedge CLK_50M)begin BRAM_addr_Lag<=BRAM_addr; if(BRAM_addr>=32'd19) BRAM_addr<=32'd0; else BRAM_addr<=BRAM_addr+1'b1; end ila_0 u_test( .clk(CLK_50M), .probe0(BRAM_addr_Lag), .probe1(BRAM_dout) ); ZYNQ_CORE_wrapper u_zynq( .BRAM_PORTB_0_addr(BRAM_addr), .BRAM_PORTB_0_clk(CLK_50M), .BRAM_PORTB_0_din(32'd0), .BRAM_PORTB_0_dout(BRAM_dout), .BRAM_PORTB_0_en(1'b1), .BRAM_PORTB_0_rst(1'b0), .BRAM_PORTB_0_we(4'd0), .UART_0_0_rxd(UART_0_0_rxd), .UART_0_0_txd(UART_0_0_txd) ); endmodule
代码中例化了 ZYNQ 核,以及我们刚刚创建的ILA模块, 并且定义了一个 从0-19自增的地址BRAM_addr。 备注,这里除了定义了BRAM_addr 之外,另外还定义了一个地址BRAM_addr_Lag, 这个地址较BRAM_addr 将滞后一拍,用来和BRAM输出的数据对齐用(因为BRAM输出的数据会滞后一个CLK)
4) 点击绿色箭头RUN 对代码进行编译
5) 增加UART管脚定义,点击RTL 中的SCHEMATIC , 并选择右边出现的 IO Ports 来增加UART 0 EMIO部分的管脚定义(这一步也可以在约束文件中定义, 可看之前的例子) 因为ZYNQ DDR之类的脚没有完全引出到TOP模块,所以可能会报警,无视就好了 将 UART 部分的 TX RX 分别设置成 L17,M17 电压属性设置成 LVCMOS33, 之后保存
6) 生成bit文件 :按下Generate Bitstream 完成综合以及生成bit文件,等待弹出综合完成的窗口
三、SDK部分设计
1)File→Export→Export hardware…在弹出的对话框中勾选“include bitstream”,点击“OK”确认,如下图所示。
2)File→Lauch SDK,在弹出的对话框中,保存默认,点击“OK”,如下图所示。
系统将自动打开SDK开发环境
3)新建一个工程 file→new→Application Project,来新建一个“Application Project”,如下图所示。
4)在新建工程名中输入自己的工程名称,点击NEXT
5)选择空工程,点击完成FINISH
6)右键工程的SRC目录,然后新建一个SOURCE FILE
取名 main.c
7) 在main .c中添加如下代码
#include <stdio.h> #include "xil_io.h" #include "xparameters.h" #include "xbram.h" #define BRAM_CTRL_0_BASE XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR #define BRAM_CTRL_1_BASE XPAR_AXI_BRAM_CTRL_1_S_AXI_BASEADDR #define BRAM_CTRL_2_BASE XPAR_AXI_BRAM_CTRL_2_S_AXI_BASEADDR char test_string[]="Hello FPGA"; int main(){ int num,str_len; int str_rev; xil_printf("test start\n\r"); str_len=strlen(test_string); for( num=0; num<str_len; num++ ){ XBram_WriteReg(BRAM_CTRL_0_BASE , num*4, test_string[num]); } for( num=0; num<str_len; num++ ){ str_rev = XBram_ReadReg( BRAM_CTRL_1_BASE, num*4); printf("The data for the address %x is %c\n\r",BRAM_CTRL_1_BASE+num*4,str_rev); } xil_printf("test over!\n\r"); xil_printf("\n\r"); for( num=0; num<5; num++ ){ XBram_WriteReg(BRAM_CTRL_2_BASE , num*4, num+1 ); } return 0; }
程序功能介绍: BRAM_CTRL_0_BASE,BRAM_CTRL_1_BASE,BRAM_CTRL_2_BASE 分别对应的三个BRAM控制器的基地址,其中BRAM_CTRL_0和BRAM_CTRL_1两个控制器分别接在同一个BRAM0的 A口和B口, 而BRAM_CTRL_2 则是接在另一个BRAM1上,这个BRAM的B口将与PL端的逻辑连接。
程序运行过程
- 通过 BRAM_CTRL_0 对 BRAM0 写入字符串 “Hello FPGA”
- 通过 BRAM_CTRL_1 从 BRAM0 中读出对应地址的数据,并通过串口进行打印,如果之前的内容写入成功,则这里读出的数据也将是 “Hello FPGA”
- 通过 BRAM_CTRL_2 对 BRAM1 写入顺序的0 ,1,2,3,4 (这里的验证将通过 PL端的 ILA来演示)
这里有个地方要注意 由于在ZYNQ中最小可寻址单元为字节,因此1个32位数据需占用4个地址,每次写入的地址都需要加4
四、下载程序到板子上验证
1) 先提前打开串口助手 波特率115200(也可用vivado SDK 自带的),本文演示用的是putty
2) 打开Run as -> Run Configurations
如果是第一次打开此页面,之前没有debug过,则双击system debugger选项
3 ) 在右侧 的窗口勾选 Reset entire system ,以及Program FPGA, 这样每次debug 的时候都会预先加载并配置FPGA。
4 ) 之后系统就会自动运行了(fpga也会被自动加载) 。 如果修改程序后 再次要debug,就可以直接点绿色箭头,或者选 Run As –>Launch on Hardware (system debugger即可)
5) 程序工作后, 结果如下图所示,可以看到串口显示下列信息,证明 从BRAM_CTRL_0写入的数据,从BRAM_CTRL_1顺利读出,证明存储成功。
6) 接下来我们看下PL端 ILA 读取到的信息
点选 Open Hardware Manager -> Auto Connect
执行完之前的下载操作后,系统如果运行没问题会自动弹出一个ila调试窗口, 入下图所示 点选箭头(Run trigger for this ILA core)开始采样
可以看到 ,从PL端读出来的数据和PS端写入的数据相同
- 本文的完整工程下载:15_PS_BRAM_TEST
- VIVADO的版本:2018.3
- 工程创建目录:E:\Smart_ZYNQ_SP_SL\SDK\15_PS_BRAM_TEST
- 工程适用主板: Smart ZYNQ (SP / SP2 / SL) (不适用于Smart ZYNQ 标准版 )