NiosIIで自作周辺回路の割り込みを実装したときのハマり事

# この記事の概要

Intel FPGAにおいて、NiosIIへ自作の周辺回路を追加する際、割り込み信号はどのように設定すればよいか、試行錯誤したことを書き残します。

* 使用したもの
 * 開発環境：Windows10Professional
 * 開発ツール：Quartus Prime 18.1 Lite Edition
 * ターゲットボード：DE0-Nano Development Board
 * 参考書：小林優『改定2版 FPGAボードで学ぶ組込みシステム開発入門[Intel FPGA編]』技術評論社

## 結論

* 自作周辺回路のRTL設計時に注意すること：
 * 自作周辺回路からNiosIIへ入力する割り込み要求は、レベル割り込みにすること。
 * 自作周辺回路は、Avalonバス経由でソフトウェアから割り込み要求をクリアできるようにすること。

* 自作周辺回路をNiosIIプラットフォームへ追加するときに注意すること：
 * IP Catalogにて「New Component...」でファイル追加後、「Signals & Interfaces」タブでinterrupt senderを追加して、割り込み要求をそこへぶら下げること。
 * interrupt senderの設定内の「Associated clock」「Associated reset」を、正しいクロックとリセットにすること。
 * interrupt senderの設定内の「Parameters」で、「Associated addressable interface」を「avalon_slave_0」、「Bridged receiver offset」を「0」にすること。

* アプリケーションソフトの設計時に注意すること：
 * 割り込みコールバック関数内で、割り込み要求をクリアすること。

## 詳細内容

### 自作周辺回路のRTL設計時に注意すること

まずはじめに、適当なテキストエディタでRTL設計とコーディングをします。

* 自作周辺回路からNiosIIへ入力する割り込み要求は、レベル割り込みにすること。
* 自作周辺回路は、Avalonバス経由でソフトウェアから割り込み要求をクリアできるようにすること。

と述べましたが、そのままです。具体的なコードで説明した方が早いと思います。

```verilog:mypio.v（入出力部）
module mypio
(
	// clock & reset
	input        clk,
	input        reset,

	// Avalon Bus signals
	input [ 7:0] address,
	input        byteenable,
	input        read,
	output[ 7:0] readdata,
	input        write,
	input [ 7:0] writedata,
	output       waitrequest,
	// interrupt request
	output       irq,

	// conduit
	input [ 3:0] SW,
	output[ 7:0] nHEX
);
```

こんな入出力ポートを持つ自作周辺回路を作りました。基板上に4bitのDipSwitchがあり、それをこのSWというポートで受けとります。そして8bit信号をnHEXというポートで基板上のLEDへ負論理で出力します。

途中のコードは省略して次のポイントです。

```verilog:mypio.v（割り込み生成、クリア部）
	// IRQ信号生成
	wire irq_occur = (poll_sw==1'b1)&(sw_chg!=4'h0);
	reg  clr_irq;
	reg  irq_hold;

	always @(posedge clk) begin
		if ( reset==1'b1 ) begin
			irq_hold <= 1'b0;
		end
		else if ( irq_occur==1'b1 ) begin
			irq_hold <= 1'b1;
		end
		else if ( clr_irq==1'b1 ) begin
			irq_hold <= 1'b0;
		end
	end////////////////////////////////////////////////////////////////////////

	// 外部出力
	assign irq = irq_hold;
```

SWポートに入る信号は非同期かつ物理的な端子のチャタリングがあるため、メタステーブル沈めをし、さらに10ms毎にポーリングした信号をロジック信号として使います。
poll_swは10ms毎のポーリングタイミングを示すパルス信号、sw_chgはポーリング過去2回分の信号で変化があったことを示す信号です。
clr_irqは、後述しますがAvalonバス経由で割り込みクリアの書き込みがあったときに立つパルス信号です。割り込み発生（irq_occur信号）と割り込みクリア(clr_irq信号)が同時に起こったときは、割り込み発生のほうを優先します。

```verilog:mypio.v（Avalonバス書き込み部）
	always @(posedge clk) begin
		if ( reset==1'b1 ) begin
			clr_irq <= 1'b0;
		end
		else if ( regwr_irqc==1'b1 ) begin
			clr_irq <= 1'b1;
		end
		else begin
			clr_irq <= 1'b0;
		end
    end////////////////////////////////////////////////////////////////////////
```

先述のclr_irq信号の生成部です。NiosIIからはAvalonバス上のIRQCレジスタとして見えます。ここに書き込みアクセス（値不問）を起こせば割り込み要求をクリアできるようにしています。なおregwr_irqcはAvalonバスからIRQCレジスタのアドレスへ書き込み信号があったことを示すパルス信号（Avalonバスのwrite信号とaddress信号をandして作った信号）です。
 
### 自作周辺回路をNiosIIプラットフォームへ追加するときに注意すること

次に、Platform Designerを起動します。

* IP Catalogにて「New Component...」でファイル追加後、「Signals & Interfaces」タブでinterrupt senderを追加して、割り込み要求をそこへぶら下げること。

「Files」タブにてファイル追加後、初めての状態ではirq信号がavalon_slave_0の中に紛れ込んでいます。薄字の\<\<add interface\>\>をクリックして、interrupt_senderを追加してから、irq信号をその下へドラッグ＆ドロップしてください。
ついでですが、SWやnHEXも同様にalavlon_slave_0に紛れ込んでいるので、\<\<add interface\>\>をクリックしてconduit_endを追加し、そこへ移動させましょう。

![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/ac63b29e-78ba-11ba-bf81-a9e927b02afa.png)

* interrupt senderの設定内の「Associated clock」「Associated reset」を、正しいクロックとリセットにすること。
* interrupt senderの設定内の「Parameters」で、「Associated addressable interface」を「avalon_slave_0」、「Bridged receiver offset」を「0」にすること。

あとは図のとおりです。
ついでなのですが、avalon_slave_0も「Associated reset」を「reset」へ、contuid_endも「Associated reset」を「reset」にしましょう。（デフォルトでresetを選択してくれていればいいのですが、なぜかデフォルトでエラーな状態になっています）

![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/6d211081-684a-4630-3f0c-f0d0d48f1862.png)

これら設定が終わったら、System Contentsでバス周りの接続を行います。こんな感じです。（参考書に従って作ったプラットフォームを再利用しているため、本件に無関係なtimer_0も入っています）

![図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/6c46074c-a689-f222-c1a1-3d1dbad4c128.png)

### アプリケーションソフトの設計時に注意すること

そして最後に、NiosII Software Build Tools for Eclipseを立ち上げ、ソフトウェアのコーディングです。
最初にbpsプロジェクトでGenerate BSPを実行してから、Build Projectを行います。

![図4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483682/ac60cd77-6695-c4e2-4452-8633e737e1b0.png)

* 割り込みコールバック関数内で、割り込み要求をクリアすること。

割り込みコールバック関数はmypio_callbackです。
この関数の最後で、割り込み要求をクリアするレジスタへライトアクセスをかけています。

```C
#include "system.h"
#include "io.h"
#include "sys/alt_irq.h"

//=============================================================================
// mypioによる割り込みコールバック関数
static void mypio_callback(void* context){

	// SWの状態を読み取る
	unsigned char sw = IORD_8DIRECT(MYPIO_0_BASE, 1);
	
	// (処理したい内容、色々と。)

	// SW変化による割り込みをクリアする
	IOWR_8DIRECT(MYPIO_0_BASE, 2, 0x00);
	
	return ;
}//////////////////////////////////////////////////////////////////////////////
```
そしてこちらがmain関数です。
割り込みコールバック関数は、alt_ic_isr_regiserというAPIで登録します。

```C
//=============================================================================
int main()
{ 
	unsigned char pout=0xFF; // ポートに出力するパターン
	
	// SW変化割り込みの設定
	alt_ic_isr_register(
		MYPIO_0_IRQ_INTERRUPT_CONTROLLER_ID,
		MYPIO_0_IRQ,
		mypio_callback,
		(void*)NULL,
		(void*)0
	);
	
	while(1){

		//（処理色々と：ポートに出力するパターンを pout へ入れる）

		// nHEXへ出力するためのレジスタへ書き込む
		IOWR_8DIRECT(MYPIO_0_BASE, 0, pout);
		
	}
	return 0;
}//////////////////////////////////////////////////////////////////////////////
```

## あとがき

私は初めてFPGAに触れてから20年経つロートルエンジニアなのですが、ここ5年くらい管理側の仕事がメインで実開発から離れていました。すっかり世の中の技術から取り残されていることに焦りを感じ始め、以前から興味のあったFPGA内蔵CPUを使ったシステムの開発を、参考書を片手に独習し始めました。
今までFPGAでシーケンシャルな処理を実現しようと思えば、ステートマシンを設計して実装することしか考えませんでしたが、これからはCPUを使ったソフトウェア処理も選択肢に加わりそうです。
でも、まだまだ序の口。もっと勉強しなくては。