实验环境:window 7 64 bit, vivado 2017.1, ZTURN board.
参考手册:Xilinx Distributed Memory Generator
在ZYNQ开发中,经常需要PS与PL进行数据交互。当数据量比较大时往往需要先缓存一部分然后批量传输到Linux系统,否则中断响应时间无法满足要求,使用双端口RAM或许是一种不错的方法。本文详细描述PS端读写PL端片的双端口RAM数据的实验过程。本次实验使用FPGA内部的Block Memory,PS端通过Master GP0端口向RAM写数据,通过Master GP1端口读出数据,本次实验涉及到AXI BRAM Controller和Block Memory Generator等IP核。
一. SRAM介绍
1.1 双端口RAM结构
Xilinx的ZYNQ7Z010内部FPGA是virtex7系列,内部有32KB的Block Memory,可以用它作为ROM,Single-Port RAM, Dual-Port RAM或Simple Dual-Port RAM, RAM的区别在于读写数据线与地址总线的数量的区别,根据自己的需求进行选择。
RAM类型 |
功能 |
数据总线数量 |
地址总线数量 |
Single-Port |
通过一个端口进行数据的读写操作 |
1 |
1 |
Dual-Port |
通过两个端口读写数据 |
2 |
2 |
Simple Dual-Port |
一个端口进行写操作,另一个端口进行读操作 |
1 |
2 |
双端口RAM示意图如下:
双端口RAM相比其他几种RAM更复杂,所以本文只介绍双端口RAM的使用。
1.2 双端口RAM控制信号
上文中的示意图中只列出了基本且常用的信号,其他特殊信号在次不做介绍。常用控制信号功能定义如下:
信号 |
方向 |
描述 |
din[31:0] |
Input |
数据总线输入,32位宽 |
addr[31:0] |
Input |
地址总线输入,32位宽,地址从0开始,有效至容量大小 |
clk |
Input |
时钟信号输入,1位,写同步时钟 |
en |
Input |
使能读,写,复位选项 |
rst |
Input |
复位或置位读出锁存寄存器,如果要读出需要置置 |
wea[3:0] |
Input |
写使能 |
dout[31:0] |
Output |
数据输出,32位宽 |
双端口RAM的工作模式有三种,写优先、读优先和无变化模式。
写优先模式:数据同时写入到内存并输出到数据端口,时序如图1-2所示:
读优先模式:当要写入内存数据时,先前存储的数据在写地址有效前存储到数据输出总线上。时序图如图1-3所示:
无变化模式: 输出锁存器在写入内存期间不改变值。时序图如图1-4所示:
1.3 字节写操作
在使用32为数据总线时,数据按32位同时变化,如果只写入8,16,24位的数据或者间隔一个字节写入两个不连续的字节,这是可以使用WE信号,首先看看图1-5时序图。
图中第一个上升沿到来之时,数据输入0xFFEEDD,WEA=0x011,写入内容变为0x00FFDD,只有后面两个字节写入成功,可以发现WEA的某个bit位置1时对应数据输入的字节将被写入,其他字节屏蔽写操作,32为数据总线时WEA为4位,从低到高依次对应数据总线低到高的四个字节的掩码,置位可写,复位屏蔽,也就是说写32位数据时WEA[3:0]=b1111.
二. PL端硬件设计
2.1 新建一个vivado工程,选择开发板所对应的芯片型号。
2.2 Add IP, 添加ZYNQ,双击设置属性,保留uart1,选择M AXI GP0 interface和M AXI GP1 interface
2.3 Add IP, 添加一个Block memory Generator,选择true dual Port RAM类型。
2.4 Add IP,添加两个AXI BRAM Controller,把number of BRAM interfaces改成1。
2.5 点击run connection automation,把axi_bram_ctl_0的Master选择为GP0, axi_bram_ctl_1的Master选择为GP1,interconnect ip都选择为 new AXI Interconnect。
2.6 点击重新布局,最后得到如下结构。
2.7 点击Address Editor,可以看到GP0和GP1的地址已经自动分配好。
2.8 在sources下选择block design右击,点击Create HDL wrapper,生成系统的顶层模块,在system_wrapper上右击,选择generate output products...。
2.9 编译综合工程,最后generate bitstream,导出硬件(包含bitstream),launch SDK。
三. PS端软件的编写与测试
3.1 新建一个以helloworld为模板的工程,SDK自动创建了硬件的bsp,设置终端串口为uart1。
3.2 在main函数中编写代码如下:
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include <sleep.h>
#include "xil_io.h"
#include "xparameters.h"
int main()
{
int a[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
int b[16] = {0};
init_platform();
print("------The test is start...------\n\r");
xil_printf( "Write data:\n\r");
memcpy((void *)0x40000000, a, 16*4);
for(int i = 0;i<16;i++)
{
xil_printf( "%x ",a[i]);
}
xil_printf( "\n\r");
sleep(1);
xil_printf( "Read data:\n\r");
memcpy(b, (void*)0x80000000, 4*16);
for(int i = 0;i<16;i++)
{
xil_printf( "%x ",b[i]);
}
xil_printf( "\n\r");
xil_printf("------The test is end!------\n\r");
cleanup_platform();
return 0;
}
程序开始向地址0x40000000处复制数组a的内容,并打印数组a,然后将地址0x80000000开始的64字节(数组a大小)内容复制到数组b中,然后打印数组b。如果不复制到数组b,显然数组b是全零数组,上电启动初始状态内存中全部为为0。0x40000000和0x80000000分别是双端口RAM的两个端口的起始地址,如果写入与读取的内容一致,则说明双端口RAM的两套读写地址操作的是同一个存储空间,说明实验是成功的。
3.3 连接好仿真器和串口终端,先下载FPGA程序,然后运行PS的软件,在终端显示如下:
可以看出读取的与写入的数据完全一样,然而读取的地址不一样,说明实验是成功的。
说明:由于BRAM是连接在GP0和GP1接口上通过AXI总线进行读写操作,在地址空间上寻址可以直接使用指针操作,因此memcpy这样的C基本库函数可以直接对GP0和GP1地址空间进行操作,在访问速度上比使用循环指针操作要快很多。