Zybo割り込みについて

ブログを書くのは久しぶりである。今回も2月に掲げた目標とは別の内容を取り上げる。
その内容とはZyboの割り込み発生方法についてである。今回はPLから割り込み信号を発生させ、簡単なPSで割り込み処理を行う方法について記載する。

目次

使用環境

Zyboの割り込みについて

ZyboのFPGAには、汎用割り込みコントローラ(GIC)と呼ばれるものが組み込まれている。GICはPS/PL - CPU 間の割り込みを管理するものである。PS/PL - CPU 間の割り込みは、いくつかの種類が存在する。

今回やりたいことは、PLから割り込み信号を発生させて簡単なPSで割り込み処理を行うことである。その場合は共有ペリフェラル割り込み(SPI)を使用する。SPIはI/OペリフェラルやPLからの割り込み信号をGICへ送信するモジュールである。

下の図にブロック図を示す。ブロック図の赤枠がSPIの部分を示しており、PLからSPIへ送信する信号数は計16本である。

割り込み ブロック図

計16本あるPLからSPIへ送信する割り込み信号には、割り込み要求番号(IRQ ID#)が割り当てられている。割り込み信号とIRQ ID の関係を下の表にまとめた。今回は下記の表のうち、割り込み要求番号 #61を使用して割り込みの動作確認を行う。 ちなみに、PS部のI/OペリフェラルからSPIへ送信する信号数は計44本ある。


信号名 IRQ ID# トリガタイプ vivado上の信号名
PL[7:0] 68:61 立ち上がりエッジ/Highレベル IRQF2P[ 7:0]
PL[15:8] 91:84 立ち上がりエッジ/Highレベル IRQF2P[15:8]

動作確認用の構成

割り込み信号の発生と割り込み処理の動作確認するための構成について説明する。構成図を下に示す。割り込み信号はPL側に配置したプッシュボタンを押すことで発生させる。そして、割り込み処理は割り込み処理関数を作成し、コンソール画面上に文字を出力することで動作確認する。

構成図

これら手続きはxilinxが用意したAPIを使用することで、簡単に済ますことができる。今回の動作確認ではこのAPIを使用する。

動作確認

PL設定

FPGAの構成図を以下に示す。上記にて記載した通り、プッシュボタンを押すことで割り込み信号を発生させ、CPUに送信したい。割り込み信号をCPUに送信するには、Vivado IPインテグレータ上で下記の2つを実施する必要がある。

  • zynq IPコアの割り込み有効化
  • 割り込み信号の接続


FPGA構成図

zynq IPコアの割り込み有効化


zynq IPコアの割り込み有効化は、ZynqIPの設定画面から行う。ZynqIPの初期設定では、PL割り込みは無効となっている。ZynqIPをダブルクリックすると、Re-Customize IPというウインドウが表示させる。その画面の左側の項目から、Interruptsを選択する。Interruptsを選択すると、Interrupt Portの下にいくつかの設定項目が表示される。そのうち、IRQ_F2P[15:0]のチェックボックスを選択する。選択したときの画面を下に示す。

設定画面


画面右下のOKボタンを押すと、Zynq IPにIRQ_F2Pという入力ポートが表示される。これでzynq IPコアの割り込み有効化は完了である。

割り込み信号の接続

次にZynqIPとプッシュボタンの信号を接続する。Zyboの基板上にはプッシュボタンが4つある。プッシュボタンの信号名をBTN [3:0]とし、これらをConCat IPを経由してZynqIPのIRQ_F2Pポートに接続する。ConCat IPは信号名の異なるバスを一つのバスにまとめる役割を持つ。今回の例だとConCat IPを使用するメリットはないが、とりあえず使用した。

今回の場合、割り込み要求番号とプッシュボタンの関係は下記表のようになる。

信号名 IRQ ID#
BTN[0] 61
BTN[1] 62
BTN[2] 63
BTN[3] 64


上記2つが終わったら、bitファイルを生成する。bitファイルが生成後、Xilinx SDKを起動させる。PS設定に進む。

PS設定

CPUが割り込み処理を行うためには、GICの初期化やPL割り込みの有効化、割り込みハンドラの登録、使用する割り込みIDの有効化といくつかの手続きが必要となる。これら手続きを以下のフロー図に示す。書くほどの内容はないが・・・

構成図

この手続きはXilinxが提供しているAPIを使用することで簡単に済ますことができる。なお、Xilinx APIのドキュメントやサンプルコードは以下から入手できる。

サンプルコードは、下記のように構造体に変数をまとめていたり、インスタンス生成するなど、オブジェクト指向設計っぽい記述となっている。オブジェクト指向設計、というよりソフトについては全般的に詳しくないので違っているかもしれないが。。。

API

作成したコード全文を以下に示す。これを実行すると、プッシュボタンを押すたびにコンソール画面上にgenerate interrupt!!と表示される。

/******************************************************************************/
#include "xil_cache.h"
#include "xil_exception.h"
#include "xil_io.h"
#include "xil_printf.h"
#include "xil_types.h"
#include "xparameters.h"
#include "xscugic.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

/************************** Constant Definitions *****************************/

#define INTC_DEVICE_ID XPAR_SCUGIC_0_DEVICE_ID
#define PRIORITY 0xF

// zynq7000 SPI  fpga's interrupt IDs
static const uint32_t fpgaId[] = {61, 62, 63, 64, 65, 66, 67, 68, 84, 85, 86, 87, 88, 89, 90, 91};

typedef enum {
    LEVEL_HIGH  =0x1,
    EDGE_RISING =0x3
} TriggerType;


/************************** Variable Definitions *****************************/

// 構造体の宣言
XScuGic InterruptController; /* Instance of the Interrupt Controller */
// 構造体ポインタの宣言
static XScuGic_Config* GicConfig; /* The configuration parameters of the controller */


/******************************************************************************/
/**
 * @param  CallbackRef is passed back to the device driver's interrupt
 * @return None.
 * @note   None.
 *
 ****************************************************************************/
void IRQHandler(void* CallbackRef) {
    /*
     * Indicate the interrupt has been processed using a shared variable
     */
    printf("generate interrupt!! \n");
}

/******************************************************************************/
/**
 *
 * This function connects the interrupt handler of the interrupt controller to
 * the processor.  This function is seperate to allow it to be customized for
 * each application.  Each processor or RTOS may require unique processing to
 * connect the interrupt handler.
 *
 * @param  XScuGicInstancePtr is the instance of the interrupt controller
 *     that needs to be worked on.
 *
 * @return None.
 *
 * @note   None.
 *
 ****************************************************************************/
int SetUpInterruptSystem(XScuGic* XScuGicInstancePtr) {

    /*
     * Connect the interrupt controller interrupt handler to the hardware
     * interrupt handling logic in the ARM processor.
     */
    Xil_ExceptionRegisterHandler(
        XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, XScuGicInstancePtr);

    /*
     * Enable interrupts in the ARM
     */
    Xil_ExceptionEnable();

    return XST_SUCCESS;
}


/******************************************************************************/
/**
 * @param
 * @return None.
 * @note   None.
 *
 ****************************************************************************/
int SetupGIC(uint32_t usingID, uint32_t* fpgaId) {
    int32_t  status;
    uint32_t maxId = sizeof(fpgaId) / sizeof(fpgaId[0]);



    ///! GICの初期化のためにメモリ領域を確保する
    GicConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
    if (NULL == GicConfig) {
        xil_printf("ERROR : device does not exist   \n");
        return XST_FAILURE;
    }

    ///! GICを初期化する
    status = XScuGic_CfgInitialize(&InterruptController, GicConfig, GicConfig->CpuBaseAddress);
    if (status != XST_SUCCESS) {
        xil_printf("ERROR : inizialization failed   \n");
        return XST_FAILURE;
    }

    ///! 割り込みの種類と優先度を設定する
    for (uint32_t i = 0; i < maxId; i++) {
        XScuGic_SetPriorityTriggerType(&InterruptController, fpgaId[i], PRIORITY , EDGE_RISING);
    }


    ///! GICとハードウェアを接続する
    for (uint32_t i = 0; i < usingID; i++) {
        status = XScuGic_Connect(&InterruptController, fpgaId[i], (Xil_ExceptionHandler)IRQHandler,
                                 (void*)&InterruptController);
        if (status != XST_SUCCESS) {
            xil_printf("ERROR   \n");
            return XST_FAILURE;
        }
    }

    ///! GICを有効化する
    for (uint32_t i = 0; i < usingID; i++) {
        XScuGic_Enable(&InterruptController, fpgaId[i]);

        printf("setup interrupt \n");
    }

    ///! GICとARMコアを接続する
    SetUpInterruptSystem(&InterruptController);
}


/*****************************************************************************/
/**
 * This is the main function for the Interrupt Controller example.
 * @param  None.
 * @return XST_SUCCESS to indicate success, otherwise XST_FAILURE.
 * @note   None.
 *
 ****************************************************************************/


int main() {
    init_platform();

    // get fpga interrupt ID
    uint32_t idNumber = sizeof(fpgaId) / sizeof(fpgaId[0]);
    printf("ID Number %d \n", idNumber);
    SetupGIC(1, &fpgaId);

    printf("waiting interrupt  \n");
    printf("\n---------------------------------\n");

    while (1) {
    }

    cleanup_platform();
    return 0;
}


まとめ

割り込み処理の動作確認を行うことができた。次は、割り込みを使った別のプログラムを作成する。また、今回の記事は後半に駆け足になり、PS部の説明が薄い内容になってしまった。もう少し記事を各モチベーションを維持できるようにしたい。