1. 程式人生 > >EDK筆記——自定義IP核

EDK筆記——自定義IP核

fault 軟件 \n current 邏輯 直接 打開 不同 spa

這篇筆記是我之前在調試MicroBlaze時記錄下來的,當時在網上查了一些資料,發現都講的不是特別清楚,所以自己整理了一個筆記,如有差錯,希望大家指正。

在這次示例中,本文完成了一個改變流水燈的間隔時間以及按鍵檢測的間隔時間可變的一個MicroBlaze程序,修改參數後不用再經過布局布線,方便調試。

典型的嵌入式程序設計流程如下:

技術分享圖片

如上圖所示,在FPGA中開發嵌入式系統主要需要三個工具套件,分別為ISE,XPS和SDK。

ISE篇

先打開ISE,新建一個Project

技術分享圖片

我用的是黑金的AX309的板子

技術分享圖片

技術分享圖片

然後建一個Embedded Processor

技術分享圖片

XPS篇

建完後會自動打開XPS軟件,點擊Yes創建一個新的XPS Project

技術分享圖片

點擊OK,使用AXI總線

技術分享圖片

選擇Create a System for a Custom Board, Reference Clock Frequecy是板上的時鐘頻率,也可以是你用PLL生成的時鐘然後輸到MIcroBlaze系統的時鐘頻率,Optimization Strategy選擇Area和Throughput都可以,在這裏我選的是Area。點擊Next

技術分享圖片

處理器頻率選擇100Mhz,Local Memory Size選擇32KB,用於緩存你寫的應用程序,盡量選大一些。不用添加其它外設,點擊Finish即可

技術分享圖片

完了之後會生成一個基本的MicroBlaze系統,裏面包含了MIcroBlaze系統所需的基本模塊,如clock generator,axi4liite等等。

技術分享圖片

在這裏,我們需要自己新建一個IP,在建IP之前,我們要先想好這個IP的功能和端口是什麽,包括輸入和輸出端口,我目前要實現的功能是,通過檢測按鍵KEY1,來開啟和關閉流水燈,同時可以通過修改MIcroBlaze中的程序來改變流水燈的間隔時間以及按鍵檢測的間隔時間,所以在這個自定義IP中,端口有兩個,一個是控制4個LED燈亮的輸出端口LED[3:0],一個是按鍵的輸入端口dip。

首先,點擊Hardware>>Create or Import Peripheral

技術分享圖片

點擊next

技術分享圖片

選擇Create templates for a new perphera,點擊next

技術分享圖片

在這裏有兩個選擇,上面那個是將你創建的外設安裝到EDK的庫中,這樣下次再創建工程就可以直接利用你創建的外設,可重復利用,另外一個選項就是只用於當前XPS Project,在這裏我們這個外設只在這個工程中使用,所以選擇To an XPS Project,點擊next。

技術分享圖片

為你創建的這個外設取名字,我這裏取的是blink,你可以在description中添加對這個外設功能的描述。點擊next

技術分享圖片

選擇外設總線相連的協議,在這裏選的是AXI4_Lite,點擊next。

技術分享圖片

在這裏需要勾選User logic software registers,表示我們是通過軟件來更改寄存器的值,從而來更改我們所需要改變的參數的值。點擊next

技術分享圖片

在這裏我們選擇寄存器的個數,一般有幾個參數就選擇幾個寄存器,也可以把幾個參數合在一個寄存器裏,每個寄存器是32位的,總共可以選擇32個寄存器,在這裏我們需要修改兩個參數(流水燈亮的間隔時間,按鍵檢測的間隔時間),所以這裏選擇兩個寄存器。

技術分享圖片

在這裏不作修改,點擊next

技術分享圖片

這裏是選擇是否生成仿真平臺和仿真模型(Simulation Model),用於在ISim或Modelsim中進行仿真,在這裏我們不需要仿真,不勾選,點擊next

技術分享圖片

因為我們是用Verilog進行編程,所以需要勾選Generate stub“user logic” template in Verilog instead of VHDL,之後我們再對user logic模塊進行修改時就可以用Verilog了,第三個勾選的話可以生成相關驅動,這樣便可以利用驅動中的API函數了,不過不勾選也沒關系,因為我們需要的函數在MIcroBlaze核裏都有,在這裏我們不勾選,點擊next

技術分享圖片

技術分享圖片

點擊finish

技術分享圖片

這個時候可以看到,在IP Catalog中的USER有我們創建的外設BLINK

技術分享圖片

在這裏我們需要修改幾個文件,首先右鍵BLINK>>view MPD,在## Ports處添加外設的端口LED[3:0]和dip

技術分享圖片

接下來是VHDL文件和Verilog文件,這兩個文件在 工程路徑\\pcores\blink_v1_00_a\hdl文件夾中,我的路徑是H:\project\EDK_test\system_test\pcores\blink_v1_00_a\hdl, 先修改VHDL文件,點擊VHDL文件夾,打開blink,vhd。有三個地方修改,一個是entity這裏需要添加端口 技術分享圖片

一個是user logic這裏需要添加端口

技術分享圖片

最後一個就是generic map這裏需要添加映射

技術分享圖片

這樣VHDL文件便修改完了,然後再修改verilog文件 Verilog文件主要就是你要實現的功能的代碼實現,有關AXI4相關的通信方面的連線及端口已經寫好了,我們要做的是寫自己的邏輯。 同樣,我們user logic中也要先添加自己的端口LED和dip 技術分享圖片

技術分享圖片

通過軟件控制的寄存器便是這兩個寄存器

技術分享圖片

上圖是有關寫寄存器的操作,這裏不需要更改,軟件賦予寄存器什麽值,相應的寄存器就會是什麽值。 下圖是有關寄存器讀的一些操作,在這裏我們可以修改,這樣軟件便可以讀到我們想要的信號的值,比如下圖中寄存器2寫操作寫的是設定按鍵檢測的時間間隔,讀操作的時候讀的是dip也就是開關的狀態 技術分享圖片

接下來就是自己為實現功能而寫的一些模塊,以下是為了實現功能而編寫的Verilog文件

技術分享圖片
//User Logic
//reg0    流水燈時間間隔
//reg1    按鍵檢測時間間隔,用於防抖
//reg1    讀入的時候讀的是dip的狀態
reg [31:0]  timer;
reg timer_enable;
always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1b0) begin
    timer <= 32b0;
  end else if (timer == 4*slv_reg0) begin
    timer <= 32b0;
  end else if (timer_enable) begin
    timer <= timer + 1b1;
  end else begin
    timer <= 32b0;
  end
end


//按鍵間隔時間計數
reg [31:0] counter;
always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1b0) begin
    counter <=32b0;
  end else if (counter == slv_reg1) begin
    counter <= 32b0;
  end else begin
    counter <= counter + 1b1;
  end
end


//檢測按鍵的下降沿
reg dip_temp1;
reg dip_temp2;
wire dip_neg;
always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1b0) begin
    dip_temp1 <= 1b1;
  end else if (counter == slv_reg1) begin
    dip_temp1 <= dip;
  end else begin
    dip_temp1 <= dip_temp1;
  end
end

//鎖定檢測dip的值
always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1b0) begin
    dip_temp2 <= 1b1;
  end else begin
    dip_temp2 <= dip_temp1;
  end
end

assign dip_neg = dip_temp2 & (~dip_temp1);


//流水燈的主要狀態機
reg [3:0] current_state;
reg [3:0] next_state;

parameter State_idle = 4d0;
parameter First_LED = 4d1;
parameter Second_LED = 4d2;
parameter Third_LED = 4d3;
parameter Last_LED = 4d4;

always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1b0) begin
    current_state <= State_idle;
  end else begin
    current_state <= next_state;
  end
end

always @ (current_state,dip_neg,timer)
begin
  next_state = 4dx;
  case (current_state)
    State_idle:
      begin
        if (dip_neg)     next_state = First_LED;
        else             next_state = State_idle;
      end
    First_LED:
      begin
        if (dip_neg)     next_state = State_idle;
        else if (timer == slv_reg0 - 1)   next_state = Second_LED;
        else                              next_state = First_LED;
      end
    Second_LED:
      begin
        if (dip_neg)     next_state = State_idle;
        else if (timer == 2*slv_reg0 - 1)   next_state = Third_LED;
        else                              next_state = Second_LED;
      end
    Third_LED:
      begin
        if (dip_neg)     next_state = State_idle;
        else if (timer == 3*slv_reg0 - 1)   next_state = Last_LED;
        else                              next_state = Third_LED;
      end
    Last_LED:
      begin
        if (dip_neg)     next_state = State_idle;
        else if (timer == 4*slv_reg0 - 1)   next_state = First_LED;
        else                              next_state = Last_LED;
      end
     default:
        next_state = State_idle;
    endcase
end


always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1b0) begin
    timer_enable <= 1b0;
    LED <= 4b0000;
  end else begin
    case (next_state)
      State_idle:
        begin
          timer_enable <= 1b0;
          LED <= 4b0000;
        end
      First_LED:
        begin
          timer_enable <= 1b1;
          LED <= 4b0001;
        end
      Second_LED:
        begin
          timer_enable <= 1b1;
          LED <= 4b0010;
        end
      Third_LED:
        begin
          timer_enable <= 1b1;
          LED <= 4b0100;
        end
      Last_LED:
        begin
          timer_enable <= 1b1;
          LED <= 4b1000;
        end
        default:
            begin
             timer_enable <= 1b0;
             LED <= 4b0000;
            end
    endcase
  end
end
View Code

可以看到,流水燈的時間間隔是受slv_reg0控制,按鍵檢測的時間間隔受slv_reg1控制。 修改完成後,記得一定要點擊Project->Rescan User Repositories,這樣修改才能生效 技術分享圖片

然後再添加自己創建的外設BLINK,添加進來後,點擊PORT欄,將LED和dip設為External Port,如下圖所示

技術分享圖片

完成上述修改後,先在XPS中點擊Hardware>>Generate Netlist,然後返回到ISE中,新建一個V文件,用作頂層文件,來例化這個MIcroBlaze核

技術分享圖片

然後,編寫UCF文件,將端口信號與管腳一一對應起來

技術分享圖片

最後,在ISE中點擊生成Bit文件 生成完畢後,可以在ISE中先選中MIcroBlaze核再點擊Export Hardware Design To SDK with Bitsream,從而來打開SDK,如下圖所示

技術分享圖片

也可以在XPS中點擊Hardware>>Export Hardware Design To SDK,來打開SDK,區別不同就在於前者打開SDK後,可以在SDK直接燒寫BIt文件到FPGA中,然後用SDK進行調試,後者是先需要在ISE中燒寫Bit文件,然後再打開SDK進行調試。

SDK篇 打開SDK,選擇一個路徑作為workspace後,然後新建一個Application Project,點擊File>>New>>Appication Project, 技術分享圖片

點擊Finish,在生成的helloworld.c文件中進行程序編寫

技術分享圖片

在這裏,我先額外建了一個文件夾blink_led,放置寫寄存器的操作

技術分享圖片
/*
 * blink_led.c
 *
 *  Created on: 2018-5-23

 */

#include "blink_led.h"
#include "xio.h"


//流水燈的間隔時間
void set_LED_interval(uint32_t  bassaddr_p, uint32_t num_ms, uint32_t num_us)
{
    //CLK 100MHZ
    XIo_Out32((bassaddr_p) + 0x00000000, num_ms*100000 + num_us*100);
}

//按鍵檢測的間隔時間
void set_dip_interval(uint32_t bassaddr_p, uint32_t num_ms)
{
    //CLK 100MHZ
    XIo_Out32((bassaddr_p + 0x00000004), num_ms*100000);
}
View Code

其中寄存器的讀寫是靠xio.h中包含的API函數來進行操作的,所以要添加include“xio.h”,另外,此程序需要在helloword.c引用,所以也需要編寫一個頭文件,在helloword.c中進行程序的編寫時需要包含進去

技術分享圖片
/*
 * blink_led.h
 *
 *  Created on: 2018-5-23
 */

#ifndef BLINK_LED_H_
#define BLINK_LED_H_

#include <stdint.h>

void set_LED_interval(uint32_t  bassaddr_p, uint32_t num_ms, uint32_t num_us);
void set_dip_interval(uint32_t bassaddr_p, uint32_t num_ms);

#endif /* BLINK_LED_H_ */
View Code

然後再在helloword.c中進行程序的編寫

技術分享圖片
 */

#include <stdio.h>
#include "platform.h"
#include "blink_led/blink_led.h"
#include "xil_printf.h"

#define blink_LED_ADDR         0x7C600000


int main()
{
    init_platform();

    set_LED_interval(blink_LED_ADDR, 5000, 0);
    print("set_LED_interval Completed\n");
    set_dip_interval(blink_LED_ADDR, 10);
    print("set_dip_interval Completed\n");

    return 0;
}
View Code

在Debug之前,先設置Debug Configuration,勾上Connect STDIO to Console,這樣,打印出來的信息就會顯示在Console欄中。

技術分享圖片

至此整個自定義IP過程完成,接下來就是上電,將程序燒寫進去進行調試就可以了。

EDK筆記——自定義IP核