實驗環境: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地址空間進行操作,在訪問速度上比使用循環指針操作要快很多。