本章节将演示 PS 访问 PL 端的 BRAM 资源,从而实现FPGA端和PS端的简单数据交互。
PS端与PL端在硬件上是相互独立的,之前介绍过通过寄存器的方式以及通过DDR的方式来实现数据交互,两者各有优缺点,寄存器的方式较为灵活但是受限于FPGA的逻辑资源数量,所以仅适用于小数据量通讯(几个或者几十个字节),而DDR支持大容量的数据存储,但数据必须是连贯的(对不连贯的数据,读取会比较慢),而BRAM就是一种折中的方式,可以实现少量(7020有4.9Mbit 的BRAM )不连续数据的读取。
Block Ram (Bram)原本是PL 端的资源, 曾经我们在实验EBAZ4205 第十九个工程 基于PL端的 BLOCK RAM IP核的使用 中介绍过如何在FPGA端使用 BRAM。 PS本身并不能直接读写 BRAM 的数据。但是我们可以通过AXI BRAM Controller IP 核来控制BRAM资源。
本文在 vivado2018.3版本上 演示, 其他版本请自行研究
(备注 此章节内容适用于 EBAZ4205与转接板的组合)
下面将进行具体的演示
一. 创建工程
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 封装是CLG400 所以型号我们选择 XC7Z010CLG400-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,本实验所用到型号:MT41K128M16JT 125,数据位宽选择16bit 最后点击“OK”,如下图所示。
5)因为EBAZ4205 大部分主板都是没有焊接PL晶振的, 而我们本次实验需要用到PL的逻辑去调度PL端的BRAM ,所以这里我们在PS端输出一个50M的CLK时钟 (为了和AXI 使用的FCLK_CLK0的50M作区分,所以这里我们额外使能了FCLK_CLK1 时钟同样设置成50M)
6)在PS的MIO配置选项增加UART部分,使能UART 0 并在IO选项里选择EMIO方式(转接板引出)
7) 设置完后点OK 完成ZYNQ的设置,然后引出UART 信号,以及FLCK_CLK1信号(按住CTRL键 用鼠标左右分别点选UART_0和 FCLK_CLK1,然后右键选Make External)
8) 搜索并添加 AXI BRAM Controller IP 核 ,实验本身分两部分,第一部分是对其中一个bram进行读写,另一部分是PS写 BRAM 然后 PL读BRAM。所以这里需要添加三个AXI BRAM Controller IP 核
打开 AXI BRAM Controller 的参数设置页, 如下图所示调整 Number of BRM Interface 为1 (这样一个IP 负责读,一个IP负责写 可以更好的进行演示) 备注:三个IP都需要进行设置
9)搜索并添加 AXI BRAM Controller IP 核(这个就是PL端的BRAM 了)备注:这里需要添加两个Bram
类型选择 真双口 RAM: True Dual Port RAM ,这里的BRAM 不能调整容量 位宽,所以 Port A 和Port B 的地方保留默认就好。
在 Other Options 中去掉 Enable Safety Circuit 选项
10) 至此,我们有2个BRAM 和 3个 AXI BRAM Controller IP 核
11)分别 点 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的逻辑使用。
12)右键选择BRAM模块的PORTB口,然后选择Make External,引出BRAM口
下图是本次工程完整的 BD 图
13) 在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 UART_0_0_rxd, output UART_0_0_txd ); wire CLK_50M; 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), .FCLK_CLK1_0(CLK_50M), .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 分别设置成 H17,H16 电压属性设置成 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端写入的数据相同
下面是本次实验的完整工程,大家自行研究:
请教一下,CLK_50M为什么不做约束啊?不是没有时钟信号吗
引出FLCK_CLK1,就是自动给了pl端去使用吗?