1. 程式人生 > >Xilinx ZYNQ 7000+Vivado2015.2系列(六)建立一個基於AXI匯流排的GPIO IP並使用

Xilinx ZYNQ 7000+Vivado2015.2系列(六)建立一個基於AXI匯流排的GPIO IP並使用

前言:

FPGA+ARM是ZYNQ的特點,那麼PL部分怎麼和ARM通訊呢,依靠的就是AXI匯流排。這個實驗是建立一個基於AXI匯流排的GPIO IP,利用PL的資源來擴充GPIO資源。通過這個實驗迅速入門開發基於匯流排的系統。

使用的板子是zc702。

AXI匯流排初識:

AXI (Advanced eXtensible Interface),由ARM公司提出的一種匯流排協議。匯流排是一組傳輸通道, 是各種邏輯器件構成的傳輸資料的通道, 一般由資料線、地址線、 控制線構成。 Xilinx從6系列的 FPGA 開始對 AXI 匯流排提供支援, 此時 AXI 已經發展到了 AXI4 這個版本, Vivado裡都是基於AIX4的 IP。


ZYNQ支援三種AXI匯流排,擁有三種AXI介面,用的都是AXI協議:
AXI4:(For high-performance memory-mapped requirements)主要面向高效能地址對映通訊的需求,是面向地址對映的介面,允許最大256輪的資料突發傳輸。
AXI4-Lite:(For simple, low-throughput memory-mapped communication)是一個輕量級的地址對映單次傳輸介面, 佔用很少的邏輯單元。
AXI4-Stream:(For high-speed streaming data)面向高速流資料傳輸,去掉了地址項,允許無限制的資料突發傳輸。

資料在總線上是遵守協議定的規則來傳輸的,AXI訊號傳輸先是傳地址,然後檢測READY+VALID,都為高電平時開始傳資料,當主機發送最後一個數據時LAST訊號拉高,通知從機傳輸結束。


在介紹讀寫如何進行前先介紹握手協議:

READY,VALID握手通訊機制,主機產生 VLAID 訊號來指明何時資料或控制資訊有效。從機產生 READY 訊號來指明已經準備好接受資料或控制資訊。傳輸發生在 VALID和 READY 訊號同時為高的時候。(還有一個LAST訊號表示什麼時候傳到最後一個數據了)


讀時序:地址線上發來地址,地址準備和地址有效都高時,開始傳送要讀的資料,讀準備和讀有效都高時資料被讀取到,發最後一個數據時讀LAST訊號拉高。

寫時序:地址線上發來地址,地址準備和地址有效都高時,開始傳送要寫的資料,寫準備和寫有效都高時資料寫入,發最後一個數據時寫LAST訊號拉高。寫資料多了一個反饋訊號,反饋給主機,主機接收到這個訊號,就知道寫成功了。


這個協議可以暫時不去理清,知道大致訊號關係,後面會通過觀察波形進一步加深印象,這次實驗的重點是學習通過程式設計操作暫存器完成讀寫!

第一步,建立AXI匯流排IP

新建一個工程,Tools-->Create and Pacakge IP-->選擇Create AXI4 Peripheral


建立完以後(起個易理解的名字,放到能找到的路徑下),有三項需要設定:介面型別,資料型別和暫存器數量


我們按預設這是就好,記住這裡的設定:選擇AXI_Lite匯流排,資料位寬是32位,也就是4位元組,暫存器4個,實際我們用到的只有一個,但這裡最低要求4個,沒關係,多出的不用就是,待會我們就要通過操作暫存器完成對資料的讀寫。

然後選擇Edit IP,

開啟ip的工程後,先開啟這個檔案:


這個就是基於AXI_Lite匯流排協議的模組,可以看到我們設定的資料位寬和暫存器數量:



AXI匯流排向暫存器寫資料:


AXI匯流排下讀暫存器的資料:

接下來我們新增一個訊號,將暫存器繫結到使用者輸出,用這個輸出控制LED燈,這樣可以通過觀察LED的亮滅看有沒有寫入成功。



然後開啟頂層檔案:

將新增的訊號加上去:



儲存,Tools-->Create and Package IP:


overwrite原來的檔案。

在IP自己建立的工程資料夾裡,打包好的IP就是這個資料夾,可以將其拷貝放到任意地方:


至此,基於AXI_Lite匯流排的IP就完成了。可以將這個資料夾拷到你之前建的工程目錄下,我是放在myip資料夾下。

第二步,使用基於AXI匯流排的IP

將我們自定義的IP新增到庫裡:


Create Block Design,命名為GPIO_AXI_LED,

新增zynq核,雙擊修改ddr訊號,其他預設設定:


新增我們自己建立的IP,然後點選自動連線:

會自動出現互聯模組和復位模組,互聯模組主要是起管理主從裝置的作用:


本來我們還應該新增邏輯分析儀觀察AXI匯流排的各訊號波形,但是為了先上手體驗怎麼開發基於AXI的系統,我們先略過,放在下一個實驗中。

再點選Run Block Automatiom:


將LED訊號也輸出出來,右擊GPIO_LED,Make External。

右擊空白處,選擇Regenerate layout,美化一下排版:


這樣我們的系統就搭建成功了,下面就是一些例行操作:

檢驗一下我們的設計:


儲存一下我們的設計:

右鍵bd檔案,復位一下系統,Reset Output Products:


右鍵bd檔案,Geberate Output Products,

右鍵bd檔案,Create HDL Wrapper。

然後就是新增管腳約束,把GPIO_LED訊號連線到LED燈上:

zc702的管教約束如下:

#GPIO PMOD1
set_property PACKAGE_PIN E15 [get_ports {GPIO_LED[7]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[7]}]
set_property PACKAGE_PIN D15 [get_ports {GPIO_LED[6]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[6]}]
set_property PACKAGE_PIN W17 [get_ports {GPIO_LED[5]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[5]}]
set_property PACKAGE_PIN W5 [get_ports {GPIO_LED[4]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[4]}]
#GPIO PMOD2
set_property PACKAGE_PIN V7 [get_ports {GPIO_LED[3]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[3]}]
set_property PACKAGE_PIN W10 [get_ports {GPIO_LED[2]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[2]}]
set_property PACKAGE_PIN P18 [get_ports {GPIO_LED[1]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[1]}]
set_property PACKAGE_PIN P17 [get_ports {GPIO_LED[0]}]
set_property IOSTANDARD LVCMOS25 [get_ports {GPIO_LED[0]}]

新增完先綜合一下,看看連線有沒有錯誤。綜合完成生成位元流檔案。

至此,大功告成,下面就到了本實現的重點,進入SDK寫程式碼來讀寫暫存器!

將硬體系統資訊和bit檔案匯入SDK:


然後Lanch SDK,新建一個空的工程:


在src檔案下建一個c檔案:


c大家都知道,用到什麼函式要將這個函式所在的檔案新增到標頭檔案,這類先把標頭檔案新增進去:

#include <stdio.h>
#include "xparameters.h"
#include "xil_io.h"
#include "sleep.h"
#include "xil_types.h"

Xinlin提供的讀函式是Xil_Out32((BaseAddr) + (u32)(RegOffset)),寫函式是Xil_Out32((BaseAddr) + (u32)(RegOffset), (u32)(Data)),讀寫都是相對於Master而言的,讀當然是In,寫當然是Out了。

前面我們提到了,讀寫是對我們定義的暫存器操作,我們這裡8個led燈,只要用到暫存器0的低8位就可以了。既然要操作暫存器,肯定要知道暫存器的地址,所有裝置的地址都放在bsp檔案下的include檔案裡的xparameters.h檔案裡,並且以巨集定義,方便呼叫:


例如我們的自定義IP在這裡,GPIO_Zhu,第一個是基地址,第二個是最高地址,:


暫存器0所在地址就是基地址,偏移量為0,因為我們定義的位寬是32位,4個位元組,暫存器1所在地址就是基地址+4,依次類推。

這裡我們讓8個Led燈依次閃爍,1秒移動一次,並讀取暫存器的資料列印到串列埠:

#include <stdio.h>
#include "xparameters.h"
#include "xil_io.h"
#include "sleep.h"
#include "xil_types.h"

int main(){
	u8 i=0;
	u8 ledValue=0;
	Xil_Out32(XPAR_GPIO_ZHU_V1_0_0_BASEADDR+0*4,0X00);
	while(1){
		for(i=0;i<=7;i++){
			Xil_Out32(XPAR_GPIO_ZHU_V1_0_0_BASEADDR+0*4,1<<i); //寫入1,8個Led,依次左移
			ledValue=Xil_In32(XPAR_GPIO_ZHU_V1_0_0_BASEADDR+0*4);
			xil_printf("ledValue=%x\r\n",ledValue); //列印到串列埠
			sleep(1); //1s移動一次
		}
		i=0;
	}
}


板子上電,連線好,以Debug方式執行:


下載好後,開啟串列埠:



點選開始執行:


Led開始依次閃爍了!,並且在串列埠看到打印出的資料:

至此,實驗成功,開啟了我們ARM+FPGA開發之路!以後可以嘗試開發更復雜的系統。