1. 程式人生 > >ZYNQ學習之路3. 定製AXI IP核

ZYNQ學習之路3. 定製AXI IP核

       ZYNQ最大的優點就是硬核A9處理器FPGA的結合,處理器可以擴展出任何使用者想要的外設(數字邏輯外設),FPGA與處理器通過AXI高速匯流排進行連線,提供了處理器到FPGA的高速頻寬(ZYNQ7000最高8Gbps)。AXI匯流排協議相當複雜,好在Xilinx提供了AXI的打包工具,開發人員只需要在指定的位置新增自己的邏輯功能程式碼即可,極大的簡化了開發過程。ZedBoard推出的官方例子中已經介紹瞭如何將Xilinx做好的AXI匯流排IPAXI_Timer、AXI_GPIO等)新增到工程中,而下面就讓我們一起來自己編寫一個簡單的AXI匯流排裝置——讀取板上的4個Swtich狀態,並控制3個

LED的外設。

一. 建立LED和開關的AXI IP核

首先檢視開發板的原理圖,確定LED和開關的引腳:


        根據原理圖,確定引腳為:

表1. LED和開關引腳
元件LED_RLED_GLED_BSW1SW2SW3SW4
引腳R14Y16Y17R19T19G14J14

1.1 vivado開發環境裡新建一個LED_AXI的工程,並生成一個名為systemBlock Diagram檔案

  再新增ZYNQ7 Processing System核心系統到這個原理圖中,建立好的vivado工程及Bloack Diagram如下:


  雙擊ZYNQ7 Processing System,配置ZYNQ

DDRMT41K256M16 RE-125

  配置MIO48,MIO49為uart1的引腳


1.2 下面開始建立自定義的LED和開關IP核

        點選選單Tools->Create and Package IP...


        點選next, 選擇Create a new AXI4 peripheral項,上面三個是將當前工程打包。


        修改IP的名稱和存放位置:

        這裡顯示了AXI匯流排介面的名字,介面時Slave, 資料位寬為32位,IP暫存器是4個,點選finish完成設計。

        開啟IP Catalog介面,我們可以看到LED_IP_v1.0,此時這個IP不具備任何功能。


        右鍵選中LED_IP_v1.0然後選擇Edit in IP Packager項。點選OK,軟體會開啟另外一個vivado視窗對這個IP進行編輯

       雙擊頂層檔案LED_IP_V1_0.v開啟,在下圖的位置新增LED和開關的引腳埠定義:

 

        在頂層檔案LED_IP_V1_0.v的下面位置對LED和開關引腳例化:


        開啟LED_IP_v1_0_S00_AXI.v檔案,在以下位置新增LED和開關的引腳埠定義:


        遮蔽AXI匯流排對slv_reg0的操作。對於暫存器來說,輸入埠不能自行拉低或拉高,所以寫操作無效,必須遮蔽。



        在程式的最後註釋(//Add user logic here)位置新增程式碼,實現自己的邏輯功能,以下程式碼的意思是根據slv_reg0讀取開關的狀態,slv_reg1設定LED的狀態。如果看不懂建議再回顧以下verilog基礎吧!

// Add user logic here
always @( posedge S_AXI_ACLK )
begin
    if ( S_AXI_ARESETN == 1'b0 )
    begin
        LED  <= 0;
    end 
    else
    begin    
        LED <= slv_reg0[2:0]; 
    end
end    
always @( posedge S_AXI_ACLK )
begin
    slv_reg0[3:0] <= SWITCH;
end    
// User logic ends

        編譯LED_IP_v1_0這個專案,確保沒有錯誤。

        雙擊IP-XACT下的component.xml檔案,點選ports and interfaces項,點選merge changesfrom ports and Interface wizard。


        在視窗中多出了我們在程式中定義的埠:


        再對其他沒有打鉤的file groups點選Merge changes from file groups wizard來更新檔案和驅動。


        選擇Review and Package項,然後點選Re-Package IP按鈕技術IP核的設計。


        到此為止,自定義的IP和設計完成了,關閉IP核的vivado工程回到LED_AXI的工程中來。

1.3 回到LED_AXI工程中設計系統原理

        在Diagram視窗中搜索LED會出現剛才自定義的IP核:LED_IP_v1.0.


        雙擊LED_IP_v1.0新增,點選Run Connection Automation,選中All automation進行匯流排的自動連線。


        點選Run Block Automation後完成系統的設計,最後原理圖如下所示。


        此時LED和SWITCH的引腳並沒有自動產生為外部的port,這裡需要手動設定這些引腳為外部引腳,右鍵選中LED,選擇make external


        在Source視窗中選擇system.bd,右鍵點選並選擇Generate output Projects和Create HDL Wrapper選項進行操作,完成之後如下圖所示:


        編譯後配置引腳約束,然後綜合生成Bitstream檔案。

        匯出硬體:選擇選單File->Export->Export Hardware...匯出,勾選include bitstream。

        匯出硬體後,選擇選單File->Launch SDK, 啟動SDK開發環境,進行裸機驅動編寫驗證IP的正確性。

二. SDK軟體程式設計

2.1 SDK環境裡重新新建一個名為LED_test的工程,軟體會自動建立一個LED_test_bsp的工程,專案使用HelloWorld為模板,新建好的工程如下:

        同樣的,為了能使用串列埠輸出到終端,需要設定BSP工程的屬性,指定終端標準輸入輸出為串列埠1,而不是預設的串列埠0。

2.2 修改LED_test工程的helloworld.c, 程式碼如下:

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "LED_IP.h"
#include "xil_io.h"
#include <stdlib.h>

//look up in address editor
#define LED_BASEADDR	0x43C00000
int reg_led, reg_switch;

int main()
{
    int i = 0;
    init_platform();

    printf("======= AXI IP Test ======\n\r");
    printf("Read Switch register...\n");
    printf("Light on R G B LED...\n");

    while(1)
    {
    	 reg_switch = LED_IP_mReadReg(LED_BASEADDR, 0);//Switch status
    	 printf("switch=0x%0x i =%d\n",reg_switch, i);
    	 LED_IP_mWriteReg(LED_BASEADDR, 4, i);
    	 i++;
    	 if(i>=8) i = 0;
    	 sleep(1);
    }

    cleanup_platform();
    return 0;
}

        再這裡簡要介紹一下驅動程式碼,LED_BASEADDR的是LED AXI匯流排掛載在處理上的地址,這個地址在新增AXI外設時軟體會自動分配一個地址,預設是0x43c00000,也可以在軟體的Address editor中編輯自定義的地址,但必須是AXI外設地址空間範圍內。LED_IP_mReadReg函式是讀取自定義AXI外設的暫存器數值,相應的LED_IP_mWriteReg是寫AXI外設暫存器值,第一個引數是AXI外設的基地址,第二個引數是地址偏移,在設計verilog邏輯程式碼是,開關使用的是slv_reg0,所以讀取開關狀態是0偏移地址,而LED是slv_reg1,地址偏移是4。這裡的兩個讀寫函式都是由SDK軟體自動生成,當然也可以自己實現這兩個函式,那麼久需要用到系統庫函式讀寫暫存器。

2.3 下載除錯

        首先需要下載FPGA的程式,點選選單Xilinx Tools->Program FPGA:


        再右鍵選擇LED_test, 點選Run as->1 launch on hardware. 在串列埠終端中顯示如下:


        同時RGB三色燈呈現8種顏色迴圈閃爍。到處位置,FPGA中的LED和開關的IP核建立完成。

三. Linux下的IP核驅動及應用

3.1 在Eclipse中配置Linux驅動程式開發環境,配置方法參考前面的《Eclipse開發ZYNQ驅動程式》教程,編寫程式碼如下:

#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include <linux/ioctl.h>
#include<linux/types.h>
#include<linux/delay.h>
#include <linux/miscdevice.h>

#include <linux/gpio.h> //gpio 操作函式
#include <asm/io.h> //io讀取函式
#include <asm/uaccess.h>

#define DEVICE_NAME "LED"
#define LED_MAJOR 252
#define LED_MINOR 0

#define LED_BASEADDR	0x43C00000
unsigned int* LED_Address = 0;

int led_open(struct inode* inode,struct file* pfile);
int led_release(struct inode* inode,struct file* pfile);
int led_ioctl(struct file* pfile,unsigned int cmd,unsigned long arg);

static const struct file_operations led_fops =
{
    .owner = THIS_MODULE,
    .open  = led_open,
    .release = led_release,
    .unlocked_ioctl = led_ioctl
};

int led_open(struct inode* inode,struct file* pfile)
{
    printk("Open\n");
    LED_Address = ioremap(LED_BASEADDR + 4,4);
    return 0;
}

int led_release(struct inode* inode,struct file* pfile)
{
    iounmap((void*)(LED_BASEADDR + 4));
    printk("LED release\n");
    return 0;
}

int led_ioctl(struct file* pfile,unsigned int cmd,unsigned long arg)
{
    printk("ioctl\n");
    *LED_Address = arg;
    return 0;
}

static struct miscdevice LED_misc =
{
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVICE_NAME,
    .fops = &led_fops,
};
static int __init led_init(void)
{
    int ret;
    ret = misc_register(&LED_misc);
    if(ret)
    {
        printk("Error:misc_register failed!\n");
        return 0;
    }
    printk("LED module register successfully!\n");
    return 0;
}

static void __exit led_exit(void)
{
    misc_deregister(&LED_misc);
    printk("Exit module\n");
}

MODULE_AUTHOR("Xiong.guo");
MODULE_LICENSE("Dual BSD/GPL");

module_init(led_init);
module_exit(led_exit);

        此處驅動不做過多的解釋,因為這是最簡單的Linux驅動程式了。編譯驅動模組,得到ZYNQ_LED.ko檔案。

3.2 在Eclipse中新建C++工程,配置方法見其他教程,編寫LED的測試程式,程式碼如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include "stdlib.h"

int main(int argc, char *argv[]){
    int fd;

    int i = 0;
    printf("ZYNQ LED driver\n");
    fd = open("/dev/LED",0);
    if(fd < 0)
    {
    	printf("can't open LED\n");
    	return 0;
    }

    while(1)
    {
    	for(i=0;i<8;i++)
    	{
            ioctl(fd,i,i);
	    sleep(1);
	    ioctl(fd,i,i);
	    sleep(1);
    	}
    }

    close(fd);
    printf("exit led\n");

    return 0;
}

        編譯程式得到LED_test檔案。

3.3 執行程式測試,開發板與Ubuntu系統通過NFS檔案傳輸,將ZYNQ_LED.ko和LED_test兩個檔案放入NFS的根目錄下,在開發板的終端掛載nfs的目錄到/mnt目錄下,則接著可以載入模組並執行程式:

$ cd /mnt/
$ insmod ZYNQ_LED.ko
$ ./LED_test

        執行程式後,開發板上的三色LED迴圈閃爍,檢視/dev目錄,可以看到系統檔案中多了一個名為LED的檔案,這個便是剛才的驅動程式所建立的檔案節點。

四. 總結

        本文詳細介紹瞭如何在ZYNQ7000中一步一步的建立自定義的IP核,並在裸機環境使用模擬器進行驗證,在裸機執行無誤的情況下再在Linux系統中編寫驅動程式進行測試,以及Linux應用程式的測試。在以後的教程中大致上都是這樣一個除錯步驟,FPGA->邏輯模擬->裸機除錯->Linux驅動->Linux應用,至此,ZYNQ SOC的開發流程基本都已經熟悉吧!