NiosIIカスタム命令のつくりかた

# はじめに

こんにちは。
J-7SYSTEM WORKSの長船です。
普段は小規模～中規模あたりのFPGAがメインターゲットの業務をしております。

今回アドベントカレンダーということで、あんまり説明されないNiosIIプロセッサのカスタム命令について説明しようと思います。

少なくともNiosIIを触った経験があることが前提になりますから、本記事は一通りのツール操作ができる中級者向けの内容となります。
ある程度のワークフローは説明するものの、QaurtusPrimeやPlatform Designer、NiosII Software Build Tools（本文中ではSBTと表記、NiosII EDSと書かれているものもある）の入門的な操作手順については省略しています。
スキルレベルは「QuartusPrimeは普通に使うことができ、Platform DesignerやNiosIIも触ったことがある」らへんを想定しています。

また今回の記事内容は、以下の環境で作成しています。
文献やGiuHubリポジトリなどは記事の最後に紹介していますので、そちらも参照していただればと思います。

* QuartusPrime 20.1 LiteEdition
* NiosII Software Build Tools 20.1 (QuartusPrime 20.1LEに付属）
* OS : Windows 10 Pro 64bit 1909
* ターゲットボード : Terasic DE0-Nano


# 1. NiosIIのカスタム命令について

NiosIIそのものについては[Intelのサイト](https://www.intel.co.jp/content/www/jp/ja/products/programmable/processor/nios-ii.html)を参照していただくとして、ここでは合成可能なソフトマクロプロセッサであるNiosIIの最大の特徴でもあるカスタム命令について紹介しましょう。

カスタム命令とはその名の通り、ユーザー側が自由に追加することのできる命令です。
一般的にプロセッサはメーカーが設計して製造し、エンドユーザーにはシリコンチップとして提供されますから、通常は命令セットをユーザーがどうこうすることはできません。
命令セット全部をいじれなくとも、こんな演算ができる命令があればいいのに、と思ったところでエンドユーザーの立場では用意された命令セットを組み合わせて演算を行わせるしかないわけです。

命令セットに思いを馳せる時点でだいぶ深みにはまっているというのはとりあえず横に置いておきます。

そんなプロセッサの命令セットですが、対象がFPGA用のソフトマクロプロセッサ（論理合成で機能を構成するプロセッサコアのことをこのように呼びます）となると状況が少し変わります。
なにせ論理回路を手元で合成できるFPGA向けですから、命令セットをいじることも可能なわけです。
とはいえ何から何まで丸ごといじってしまうと開発ツール側が対応できませんから、実際にはベースとなる命令セットにカスタマイズできる部分をくっつけたスタイルが多くなります。

で、NiosIIではこの「カスタマイズできる部分」の中に、ユーザーが自由に追加できるカスタム命令があるわけです。
![nios2-hardware-acceleration.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/23feb703-dee0-7aa7-9a52-4f6d170d0228.jpeg)
(※画像はIntelのサイトより引用)

NiosIIのカスタム命令の特徴をざっと挙げると

* NiosII /eコアおよび/fコアに対応（無償版でも使える）
* 一つのNiosIIコアに対して最大256個のカスタム命令を追加できる
* 普通の３オペランド命令の他、内部状態を持つ命令や独自のレジスタセットを持つ命令も可能
* カスタム命令はPlatform Designerでコンポーネントとして扱うことができ、再利用が簡単
* NiosII SBTでC/C++用のマクロを自動生成

このようなカスタム命令をユーザーがどのように作ればいいかというと、具体的には演算部分をHDLで記述することになります。
カスタムとはいえ命令フィールドは普通の３オペランド命令ですから、インターフェースはほぼ決まっています。

今回は実例として擬似乱数を得るカスタム命令を作ってみることにしましょう。


# 2. カスタム命令のHDLとQuartusプロジェクト

### 2.1 Xorshiftとは
XorshiftはGeorge Marsaglia氏が2003年に提案した、疑似乱数列生成法の一つです。
演算がXOR(排他的論理和)とビットシフトのみで構成されていて高速でいて、なおかつ線形合同法に比べて非常に品質の良い乱数を得られるという特徴があります。

とはいえ擬似乱数ですから、シード値が一定ならば同じ数列を返してしまう欠点はそのまま残ります。
そこでソフトウェアとは無関係に内部状態を持つ（フリーランで走る）ハードウェアの出番となります。
Xorshiftはプロセッサでの実行速度も高速ですが、XORとビットシフトのみで構成されているこのアルゴリズムはHDLと親和性が高くとてもカスタム命令向きといえるでしょう。

以下のソース（`ci_rand_xorshift128.v`）は周期`2^128-1`のXorshiftをカスタム命令にしたものです。数理的な解説やHDLそのものの説明などはここでは省きます。

```verilog:ci_rand_xorshift128.v
module ci_rand_xorshift128 (
	input wire			clk,
	input wire			clk_en,
	input wire			reset,
	output wire [31:0]	result
);

	reg  [31:0]		x_reg, y_reg, z_reg, w_reg;
	wire [31:0]		t_sig, r_sig;

	assign t_sig = x_reg ^ {x_reg[20:0], {11{1'b0}}};
	assign r_sig = (w_reg ^ {{19{1'b0}}, w_reg[31:19]}) ^ (t_sig ^ {{8{1'b0}}, t_sig[31:8]});

	always @(posedge clk or posedge reset) begin
		if (reset) begin
			x_reg <= 32'd123456789;
			y_reg <= 32'd362436069;
			z_reg <= 32'd521288629;
			w_reg <= 32'd88675123;
		end
		else begin
			if (clk_en) begin
				x_reg <= y_reg;
				y_reg <= z_reg;
				z_reg <= w_reg;
				w_reg <= r_sig;
			end
		end
	end

	assign result = w_reg;
endmodule
```

ここでのポイントとしては

* **内部状態を持つためクロックを使う**（この場合`clk`,`clk_en`,`reset`の入力ポートが必須になる）
* **値を出力するだけなので入力オペランドが無い**（`dataa`,`datab`入力ポートがなく、`result`の出力ポートのみ）
* **エンティティ名やファイル名はPlatform Designerで生成されるモジュール内でユニークでなければならない**

の３つです。

３つめの条件は少しわかり難いと思いますので補足をすると、Platform DesignerではHDLモジュールの同一性の識別を単純にファイル名で行っています。
生成する際にコンポーネント構成ファイルをすべて一つのIPコアとしてまとめてしまうので、別々のコンポーネントに同じ名前のファイルがあったときにそれを解決できません。
HDLのエンティティ名（モジュール名）も同様で、そもそもHDLではローカルの名前空間を持てないのでこのような場合は人力でユニーク名を付けなければなりません。

これらの名前が衝突していた場合、Platform Designerの生成でエラーが出たり、Quartusでのコンパイル途中でエラーが出たりと、かなり後になって問題が発覚することになります。
ツールのエラーメッセージや挙動からは原因が推測しにくいために、仕組みを知らないと問題解決に時間がかかってしまうので注意しましょう。

### 2.2 Quartusプロジェクト作成
次ににQuartusプロジェクトを作成します。Quartusの基本的な操作については今回の記事の範囲ではないので割愛します。

さて今回NiosIIカスタム命令の紹介ということで、FPGAの外部I/Oなどは特に使用しません。
プログラムのダウンロードも実行結果もJTAG経由で行いますから、実行結果はSBT上のコンソールに出てきます。
QuartusPrime 20.1LEがサポートしているデバイスであればお手元にあるIntel FPGAボードでそのまま利用できるでしょう。
私はターゲットボードとして[Terasic DE0-Nano](https://www.terasic.com.tw/cgi-bin/page/archive.pl?Language=English&CategoryNo=139&No=593&PartNo=1)を使いました。たまたま手元に積んであったからです。
![de0nano_thumb.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/2e79c384-e2c1-8165-689b-279ed57ac005.jpeg)
(※画像はTerasicのサイトより引用)

今回は`de0nano_top`というトップエンティティのプロジェクトを作成しました。
DE0-Nano付属のCD-ROM（またはTerasicサイトからダウンロード）の中には、`DE0_Nano_GOLDEN_TOP`というスケルトンのプロジェクトがあるので、ピンアサインはここから流用しています。

続いてプロジェクトフォルダの直下に`ip`フォルダを作り、以下のようにコンポーネント用のフォルダを作成して先ほどのHDLファイルを格納します。

```:フォルダ構成
<プロジェクトフォルダ>
  ├─ de0nano_top.qpf
  ├─ de0nano_top.qsf
  ├─ de0nano_top.qws
  ├─ <ip>  ← プロジェクトローカルのIPコアを格納するフォルダ
  │   └─ <ci_rand_xorshift128>  ← カスタム命令コンポーネントのトップフォルダ
  │       └─ <hdl>
  │           └─ ci_rand_xorshift128.v
  :
  :
```
`ip`フォルダはプロジェクトローカルのIPコアやPlatform Designerコンポーネントを配置するフォルダで、プロジェクトフォルダ直下にこの名前のフォルダがある場合は自動的に参照します。

しかしこの段階では単にHDLが置かれているだけで、まだPlatform Designerコンポーネントしては識別されません。
カスタム命令やAvalonペリフェラルなどPlatform Designerで扱えるコンポーネントにするにはさらに設定用のtclファイルが必要です。これらの作り方は後で説明していくことにしましょう。


# 3. Platform DesignerでNiosIIプロセッサのSoCモジュールを作る

### 3.1 NiosIIモジュールの作成
プロジェクトができたらNiosIIのモジュールを作成していきます。
QuartusのツールバーアイコンからPlatform Designerを立ち上げます。

初期状態では下のような画面がでてきます。最初に1つだけClock Sourceコンポーネントが配置された状態です。
デフォルトで50MHzのクロック入力で設定されているので、今回はそのまま使います。
![pd_001_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/4c96a2f8-5c5d-ab4f-5bd4-8b9742a3ebb0.png)

次に左上のIP Calalogから`Nios II Processor`を選択して`Add`をクリックします。
コンポーネントを探しづらい場合は検索入力欄に`nios`と入力すると一番下にでてきます。
![pd_002_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/08ec45bd-ec27-276d-b8aa-e882176fe4df.png)

System Contentsリストに追加されるまえにプロセッサの設定ウィンドウが開きます。
今回は無償版のNiosII /eを使うので、`Main`タブの`Nios II Core`で`Nios II/e`を選択して`Finish`をクリックします。
いくつかエラーが出ていますが、これらはあとで修正するので今は無視します。
![pd_003_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/f6731fcd-c94c-bc88-cde8-4a6157f0c3d3.png)

続いてNiosIIの実行に必要なペリフェラルを追加していきます。
今回使うペリフェラルは以下の４つです。

* On-Chip Memory（プログラムの格納と実行時メモリ。32kバイトのRAMを割り当て）
* System ID（NiosII SBTでターゲットボードの確認に使用）
* Timer（HALでシステムタイムティックに使用する。ただし今回は未使用）
* JTAG-UART（HALの標準入出力ストリームに使用する）

![pd_004_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/96b9e609-f822-cf89-d65e-c47d91af225e.png)
これらのコンポーネントとメモリマップ、IRQ割り当ては以下のようにしました。

|コンポーネント|インスタンス名|ポート|メモリアドレス(命令バス)|メモリアドレス(データバス)|IRQ|
|---|---|---|---|---|---|
|NiosII|nios2_gen2_0|debug_mem_slave|0x0fff_0800|0x0fff_0800||
|On-Chip Memory|onchip_memory2_0|s1|0x0000_0000|0x0000_0000||
|System ID|sysid_qsys_0|control_slave||0x1000_0000||
|Timer|timer_0|s1||0x1000_0020|0|
|JTAG-UART|jtag_uart_0|avalon_jtag_slave||0x1000_0040|1|

NiosIIシステムのメモリマップ割り当てはいくつか流儀があります。ここでは私のやり方で、命令バス（instruction_master）に接続されるメモリペリフェラルは`0x0000_0000`から、データバス（data_master）に接続されるペリフェラルのうち周辺I/Oは`0x1000_0000`から、データ専用メモリは`0x4000_0000`から配置するようにしています。

なぜこのような配置にしているか？
これは主にNiosIIの命令セットの都合とPlatform Designerのアドレス割り当ての都合に起因しています。
NiosIIは32bitプロセッサですからアドレス空間は4Gバイト分あります。そのうちデータバスは通常2Gバイト（bit31をキャッシュスルーフラグに割り当てる場合）のアドレス空間を使用できるのに対して、命令バスは256Mバイトのアドレス空間しかありません。

それに加えてPlatform Designerではペリフェラルの開始アドレスは、そのペリフェラルが使用するアドレス幅の単位でしか配置できません。
例えば32Mバイトのメモリペリフェラルは32Mバイトごとのアドレス境界にしか配置できないわけです。
また当然ですがペリフェラル同士でアドレスがオーバーラップするような配置もできません。

これにより必然的にプログラムコードを格納するメモリは前方に配置することになります。
さらにデバッガ用にdebug_mem_slaveを接続する必要があり、ペリフェラルアドレスのオーバーラップができない制限から、コード格納用のメモリサイズは128Mバイト（1Gbit）が上限になってしまいます。

閑話休題。

メモリペリフェラルが接続されたので、NiosIIのエラー箇所を直していきます。
System ContentsウィンドウのNiosIIコンポーネント（`nios2_gen2_0`）を右クリック→`Edit`を選択して設定画面を開きます。
`Vectors`タブに移動して、`Reset vector memory`と`Exception vector memory`の両方とも`onchip_memory2_0.s1`を選択します。
これでエラーが消えるので、`Finish`をクリックして閉じます。
![pd_005_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/f06bcb63-4e3a-887d-c3fd-d65692af25c8.png)
ここまでできたら`File`→`Save`でいったんデザインファイルを保存しておきます。
保存するファイル名がそのままモジュール名になるので、今回は`nios2_ci_core`にしましょう。

### 3.2 カスタム命令コンポーネントの作成
ようやく本題のカスタム命令コンポーネント作成までたどり着きました。
この時点ではカスタム命令の動作をするHDLはあるものの、コンポーネントしてはまだ成立していません。
ここでは新しくコンポーネントを作成する手順を紹介していきます。

IP Catalogの一番上にある`New Component...`をダブルクリック（または選択して`Add`ボタンをクリック）して、Component Editorを開きます。
![pd_006_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/2a2cb3af-1193-6c52-a7f5-e965fb933aab.png)

Component Editorの`Component Type`タブの`Name`と`Display name`に`ci_rand_component`を記入します。
`Name`はインスタンス時に識別されるコンポーネント名。`Display name`はIP Catalogリストに表示される名前です。
これらはの名前は有効なIPコア（IP Catalogリストに表示されるもの）でユニークな名前にしなければなりません。
ほどほどに長い名前をつけましょう。
![pd_007_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/b03dc7d1-8032-a86a-f90d-fa6fdf596b41.png)

次に`Files`タブに移動して、`Add File...`で予め用意してあるHDLファイルを追加します。
その後`Analyze Synthesis Files`をクリックすると追加したHDLファイルを解析して、コンポーネントのインターフェースや信号名を推測して仮設定を行います。
HDL側のポート名の命名規則である程度はインターフェースを指示することができますが、それなりに失敗も多いのであとで手作業で修正するのが普通です。
![pd_008_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/f0bf7ae6-6b68-126a-26ad-eff3dc1a7131.png)

`Signals & Interface`タブに移動して確認すると、カスタム命令信号が推測されていないので新しくインターフェースを追加します。
`Name`ウィンドウの下にある_`《add interface》`_を右クリック→`Custom Instruction Slave`を選択します。
![pd_009_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/201afa05-210d-54fb-1131-1157eacf4787.png)

次に`clk_en`,`result`,`clk`,`reset`の信号を今し方作った`nios_custom_instruction_slave`の下にドラッグ＆ドロップして移動させます。
![pd_010_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/08c0368f-275c-293c-5e29-7dd9f2c3da79.png)

空になったインターフェースは右クリック→`Remove`を選択して削除します。
![pd_011_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/158924e2-df61-474e-914f-b6c12a154ebd.png)

カスタム命令コンポーネントでは、HDLのポート名とインターフェースの`Signal Type`は同じ名前になります。もし異なっているポートがあれば選択して修正を行います。
ここでは`clk_en`と`result`が違うインターフェースの信号タイプに設定されていた（信号タイプが赤く表示されている）ので、`Name`リストで選択して`Signal Type`を直しました。
![pd_012_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/c4b11447-4c48-9d28-02c6-9c992a30b11a.png)

`Signal Type`の修正が終わったら`Name`ウィンドウの`nios_custom_instruction_slave`を選択して、カスタム命令のパラメータを設定します。
今回のカスタム命令ではオペランドを取らない（出力のみ）なので、`Operands`に`0`を設定します。
最後に`Finish`ボタンをクリックすればPlatform Designer用のコンポーネント設定ファイル（～_hw.tcl）が生成されます。
![pd_013_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/1ff1cf36-50c6-9206-a196-d2d618064293.png)

これでひとまずPlatform Designerからはコンポーネントとして認識されるようになったわけです。ただ、実はこのままでは本当にプロジェクトローカルなコンポーネントでしかなく使い回しがとてもやりにくくなっています。
どうしてかというと、Platform DesignerのComponent Editorで生成される定義ファイル（～_hw.tcl）は**プロジェクトフォルダ直下に置かれる**ので、そのままではパッケージ化ができません。`ip`フォルダにcloneすればOKというわけにはいかないのです。

```:フォルダ構成
<プロジェクトフォルダ>
  ├─ de0nano_top.qpf
  ├─ de0nano_top.qsf
  ├─ de0nano_top.qws
  ├─ nios2_ci_core.qsys ← Platform Desginerのモジュール
  ├─ ci_rand_component_hw.tcl ← いまはコンポーネント定義ファイルがここにある
  ├─ <ip>
  │   └─ <ci_rand_xorshift128>  ← このフォルダの下に完結させたい
  │       └─ <hdl>
  │           └─ ci_rand_xorshift128.v
  :
  :
```

はい。そこで他のコンポーネントのようにパッケージ化してみましょう。コンポーネント定義ファイルを操作するので、**Platform Desginerはいったん終了**させておきます。

パッケージ化とはいってもそれほど難しいことはありません。
コンポーネント定義ファイルは`ip`フォルダ以下のフォルダに格納しておけば自動で検索してくれますので配置場所にそれほど制約はありません。
場所の問題はコンポーネントが使用するファイル（HDLファイルやSDCファイル）の場所を指定する部分です。
ファイルパスについてはコンポーネント定義ファイルの場所からの相対パスで指定できるため、パスを少し修正すれば済みます。

具体的な箇所は、`ci_rand_component_hw.tcl`の`add_fileset_file`コマンドの後ろ、`PATH`オプションの部分です。
Component Editorで生成した状態ではプロジェクトフォルダからの相対パスになっています。これを所望のフォルダの相対に修正して、定義ファイルの場所を移してやれば完了です。

```
add_fileset_file ci_rand_xorshift128.v VERILOG PATH ip/ci_rand_xorshift128/hdl/ci_rand_xorshift128.v TOP_LEVEL_FILE
  ↓
add_fileset_file ci_rand_xorshift128.v VERILOG PATH hdl/ci_rand_xorshift128.v TOP_LEVEL_FILE
```
![pd_013_b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/b5f82ac2-7078-e318-1ae2-2b0682918cfb.png)

これで今後他のプロジェクトで再利用したい場合でも、`ci_rand_xorshift128`フォルダをそのプロジェクトローカルの`ip`フォルダにコピーするだけでPlatform Desginerコンポーネントが使い回せるようになりました。
いちいちコピーするのが面倒くさいという場合は、ライブラリフォルダをどこかに用意してPlatform DesginerのIPフォルダ検索にパスを追加して参照させるような使い方もできます。
こういうものは使い回してナンボですから、どんどん作ってジャンジャン再利用して手間を省いていきましょう。

```:フォルダ構成
<プロジェクトフォルダ>
  ├─ de0nano_top.qpf
  ├─ de0nano_top.qsf
  ├─ de0nano_top.qws
  ├─ nios2_ci_core.qsys
  ├─ <ip>
  │   └─ <ci_rand_xorshift128>  ← このフォルダ以下で全て完結 °˖✧◝(⁰▿⁰)◜✧˖°
  │       ├─ ci_rand_component_hw.tcl
  │       └─ <hdl>
  │           └─ ci_rand_xorshift128.v
  :
  :
```

### 3.3 カスタム命令を組み込む
ようやくカスタム命令コンポーネントを作ることができました。ここまでできればあとは簡単です。
Platform DesginerのIP Catalogに`ci_rand_component`が新しく追加されているので、これを選択して`Add`します。
![pd_014_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/e327992d-3db7-b242-aef8-930904b45b7a.png)

コンポーネントをNiosIIの下まで移動させて、`custom_instruction_master`と接続します。
最後に`Generate HDL`ボタンをクリックしてモジュールを生成しましょう。
![pd_015_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/2b2156d0-75d2-878f-4cc0-b22d3b36b908.png)

`Finish`をクリックすると最後にqipファイルをプロジェクトに追加するか尋ねられますので`OK`を選択しておきます。
qipファイルはPlatform Desginerが生成したモジュールのパッケージ定義ファイルで、これをプロジェクトに追加しておかないとQuartusでコンパイルできません。不用意に外さないように注意しましょう。

### 3.4 プロジェクトの合成
今回のデザインでは入出力ともにJTAGで行うので、モジュールのポートはクロックとリセットだけしかありません。トップモジュールはDE0-Nanoの50MHz入力とプッシュスイッチをそのまま配線するだけのシンプルなものです。
ピンアサインファイルやSDCファイルはDE0-Nanoのスケルトン（`DE0_Nano_GOLDEN_TOP`）からそのまま流用しました。

```verilog:de0nano_top.v
module de0nano_top (
    input wire        CLOCK_50,
    input wire [1:0]  KEY
);

    nios2_ci_core u_core (
        .clk_clk       (CLOCK_50),
        .reset_reset_n (KEY[0])
    );

endmodule
```

あとはQuartus側の作業ですが、こちらは詳細説明は省きます。
コンパイルレポートを確認してみると、カスタム命令は135LEで実装されているようですね。かなりコンパクトです。
![pd_016_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/073928c4-761e-4ddc-3de0-8224c118f5b7.png)


# 4. NiosII SBTでアプリケーションとBSPを生成する

### 4.1 まずはHello World
ここからはNiosIIのソフトウェアの作業になります。
NiosII SBTを立ち上げて、まずはHello Worldからやっていきましょう。

ソフトウェア側でもプロジェクトから作成する必要があります。Quartusと同じとも言えますし、Eclipseの作法でもあります。
NiosII SBTではリンカやMakefileのツールチェーン、HALの各種設定も一緒になっているために、プロジェクト作成には専用のツールが用意されています。たとえ空のプロジェクトであっても生成ツールを使ってプロジェクトを作成する必要があります。
今回はまずHello Worldのテンプレートを生成させて動作を確認した後、それに手を加えていくことにしましょう。

Project Explorerの空欄を右クリックして、`New`→`NiosII Application and BSP from Template`を選択します。
![sbt_001_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/a1190bf8-11c9-b560-5ceb-2094f8eb4609.png)

続いて、`SOPC Information File name:`にPlatform Desginerが生成したsopcinfoファイルを指定します。
sopcinfoファイルはモジュールを生成したときに一緒に作成されるソフトウェア用の定義ファイルで、qsysファイルと同じ場所（普通はプロジェクトフォルダ直下）に同名で生成されています。
今回の場合はプロジェクトフォルダ直下に`nios2_ci_core.sopcinfo`というファイルがあるのでそれを指定します。

続いて`Project name`にプロジェクト名を設定します。この名前でプロジェクトフォルダが作成されるため、既に同名のフォルダがあると入力時にエラーがでます。
`Templates`からは`Hello World Small`を選択します。
Small版はHALのライブラリやPOSIXのドライバを削除してフットプリントを削った設定のもので、今回はコード領域もワーク領域も内蔵メモリにマッピングするためメモリ量が限られていてフルサイズのHALを実装できないのでこちらを選択します。

`Finish`ボタンをクリックするとテンプレートのアプリケーションとBSPが生成されます。
![sbt_002_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/da71a19a-cd71-1a47-1922-e686d16cec39.png)

### 4.2 NiosII SBT 20.1の不具合回避
18.1までのSBTではこれでいいのですが、19.1以降のSBTでは記事執筆時点でビルドツールチェーンに問題を抱えており、そのままではビルドエラーでコンパイルが完了しません。
~~どうも内部で呼び出している`wslpath`がMakefileの想定と異なった挙動をしているのが原因のようです。今のところアップデートの様子がなく、少々場当たり的ながらMakefileを直接修正して対処します。~~ 
**この不具合は最新の QuartusPrime 20.1.1 で修正されました**。既にそちら使われている場合はこの項の操作は不要です。4.3に進んでください。

まずアプリケーション側（今回ではci_testフォルダ）のMakefileを開き、以下の２点を修正します。
一つは132行付近の`BUILD_PRE_PROCESS`の定義に`touch $(ELF).srec`を追加します。

```
BUILD_PRE_PROCESS :=
　↓
BUILD_PRE_PROCESS := touch $(ELF).srec
```
![sbt_003_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/6a930ccf-9d74-86cd-24ad-759941320829.png)

二つ目は326行付近の`APP_LDFLAGS`の定義を`-msys-lib=$(SYS_LIB)`に修正します。

```
APP_LDFLAGS += -msys-lib=$(call adjust-path-mixed,$(SYS_LIB))
　↓
APP_LDFLAGS += -msys-lib=$(SYS_LIB)
```
![sbt_004_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/dee1b3c2-f10f-c89a-9e4e-10586d57f5ff.png)
この修正でひとまずビルドとダウンロードが通るようになります。

### 4.3 BSPの生成
NiosII SBTではRTOSやHAL、ペリフェラルドライバといったハードウェア部分についてはアプリケーション本体とは別にBSPプロジェクトとして管理されます。
BSPはPlatform Designerで設定したコンポーネントのインスタンス名やメモリマップ、設定値などのハードウェア情報をもとに、RTOSまたはベアメタルのHAL、ペリフェラルに紐付くドライバーがひとまとめにパッケージされています。
基本的には初回に１回BSPをビルドしておけばよく、アプリケーション側からはリンカで結合されます。

BSPの生成は、まずBSPの各種値を設定するところから始めます。今回のようにシンプルな構成の場合はデフォルトのままで問題ありません。とはいえ、デフォルト値に過度な期待をせずBSP生成前に設定値を確認しておくほうがよいでしょう。

Project Explorerの該当のBSPプロジェクト（今回の場合は`ci_test_bsp`）を右クリックし、`NiosII`→`BSP Editor...`を選択します。
![sbt_005_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/bab0f1c3-ec07-6517-1a11-2b67e03a954a.png)

BSP Editorが開き、各種タブで設定ができます。
今回はテンプレートで既に最小フットプリント構成になっているので、`Linker Script`タブで全てのセクション配置が内蔵メモリ（`onchip_memory2_0`）になっていることを確認しておけば十分です。GenerateボタンをクリックしてBSPを生成します。
![sbt_006_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/936e24b7-8ee3-aaba-dcc5-d40b136f2963.png)

BSPを生成したらEditorを閉じて、BSPプロジェクトのビルドを行います。
18.1までのSBTではBSPのビルドはアプリケーションビルド時に連動して行われるのですが、前述のとおり現時点ではツールチェーンに不具合があり、そのままビルドをするとリンカエラーでコンパイルが終わってしまいます。

リンカエラー回避のためにはBSPプロジェクト側だけ先にビルドする必要があります。しかしBSP側のMakefileの都合からか、どうやらBSPプロジェクトは一度ビルドしないと`ci_test_bsp`を右クリックしても`Build Project`が選択できない状態になってしまうことがあります。

わりとデッドロック状態ですが、解決方法としては構わずアプリケーション側（`ci_test`）から`Build Project`を実行してしまいます。
そのままでは当然エラーになります。ただし、この時点でBSP側のビルドは完了しているのでBSPビルドとしてはこれでOKです。
![sbt_007_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/93dca69a-7ea6-0698-3d25-2d0a6893560c.png)
以上でBSPのビルドは完了です。

BSPのビルドはアプリケーション側のコーディングをしている範疇では最初に１回行うだけです。BSP側を変更する場合、例えばHALの設定変更やペリフェラルドライバの差し替えなどを行った際はBSPプロジェクトをCleanしてBSP Editor操作からやり直すことになります。
しかしPlatform Designerでハードウェアレベルでの変更をした場合はCleanだけでは不十分なことが多く、BSPプロジェクトそのものを再生成、あるいはテンプレート生成からやり直すのが無難でしょう。


# 5. NiosIIで実行する

### 5.1 Hello Worldのビルド
それではNiosIIでプログラムを実行させていきましょう。

まずはアプリケーションのビルドを行います。
と言っても特別なことをする必要はなく、アプリケーション側（`ci_test`）のビルド操作をもう一回行うだけです。
BSP側のビルドが完了していればアプリケーション側のビルドも正しく完了します。
![sbt_008_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/01b72ebe-c37b-5277-67d3-97d1249371a3.png)

### 5.2 NiosIIへダウンロード
アプリケーションのビルドが終わったらNiosIIへプログラムのダウンロードを行います。
DE0-Nanoを接続してQuartusProgrammerでsofファイルを書き込んだら、`ci_test`を右クリック→`Run As`→`3 NiosII Hardware`を選択します。
![sbt_009_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/248c03b3-4a41-ba45-fa4a-5ceb24b128e3.png)

そのままターゲットのNiosIIに接続できる場合もあるものの、初回はだいたい`Target Connection`がエラーになります。
Run Configurationsが開くので、`Target Connection`タブに移動して`Refresh Connections`をクリックします。
![sbt_010_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/73f01b83-9982-e5e4-9aab-e2a30952c83f.png)

Run Configurationsで接続がエラーになる原因はいくつかあり、代表的なものをあげてみます。

* FPGAがコンフィギュレーションされてない、または電源が入ってない
* 別のデザインがコンフィギュレーションされている
* クロックが間違っている、またはリセットがかかりっぱなし
* 指定するJTAGダウンロードケーブルが間違っている
* 他にFTDIのドライバを利用するものが居る（FTDI互換IC等を使っている場合）
* JTAGラインの信号品質の問題（互換書き込みケーブルや独自ボードなどでは起こりやすい）

気を付ければ済むものから回避の難しいものまであり、どれが原因かはケースバイケースではあります。ただ、自作ボードでJTAGラインの信号品質で悩まされるのは意外とよく出くわす問題かつ、後からはなかなか修正の難しい原因ですので基板アートワークの段階で十分なレビューをすることをおすすめしておきます。

DE0-NanoのようなJTAGダウンロードケーブル機能が内蔵された評価ボードのいいところは、そういう検証が済んでいるので動かない場合でも原因の特定が容易なことです。
ターゲットのFPGAがきちんとコンフィギュレーションされていて、JTAGダウンロードケーブル（USB-Blaster）が接続されていれば`Run`ボタンがアクティブになります。ボタンをクリックして実行させましょう。
![sbt_011_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/58a002dd-d1d7-b394-f4f9-83c7dc8b5c99.png)

ターゲットのNiosIIにプログラムがダウンロードされ、自動的に`NiosII Console`タブ（JTAGコンソール）が開いてメッセージが表示されました。おめでとうございます！
一度分かれば大した手間でもないんですけど、最初はここまでたどり着くのはかなり大変なのではないでしょうか。あとは反復あるのみです。

さて、ほうっておくとずっとこのままなので、右の■ボタンをクリックしてJTAG接続を切断して終了させましょう。
![sbt_012_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/8cb543ef-d577-42b7-5a49-7be46a54391f.png)

### 5.3 カスタム命令を使ってみる
やっと本題です。

ここまででとりあえずNiosIIを実装して、その上で何らかのプログラムを実行させることができました。ですが記事の目的であるカスタム命令を動かすのはまだできていません。
ハードウェア的には既に実装されているカスタム命令を、ソフトウェアからはどのように使えばいいのでしょうか。

と言っても難しいことは何もありません。仕組みはすでにBSPプロジェクトの中に用意されています。具体的には`system.h`の中で、`ALT_CI_<インスタンス名>`というマクロで定義されています。
![sbt_013_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/6b52a21f-19ba-0d93-79e4-b587894ecc96.png)

今回の場合では`ALT_CI_CI_RAND_COMPONENT_0`というマクロが宣言されています。オペランドを持たない命令なので、引数のないマクロになっています。
ソースコード側では`system.h`をインクルードして`ALT_CI_CI_RAND_COMPONENT_0`を読み出す記述をすれば、カスタム命令を使ったバイナリになるわけです。

サンプルとして、ソフトウェアで実装したXorshiftとカスタム命令と比較するコードを作ってみました。

```cpp
#include <stdint.h>
#include "sys/alt_stdio.h"
#include "system.h"

uint32_t xorshift128()
{ 
  static uint32_t x = 123456789;
  static uint32_t y = 362436069;
  static uint32_t z = 521288629;
  static uint32_t w = 88675123; 
  uint32_t t;

  t = x ^ (x << 11);
  x = y; y = z; z = w;
  return w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)); 
}

int main()
{ 
  alt_putstr("Hello from Nios II!\n");

  for (int i=0 ; i<10 ; i++) {
    uint32_t r1 = ALT_CI_CI_RAND_COMPONENT_0;   // カスタム命令によるxorshift
    uint32_t r2 = xorshift128();                // 関数によるxorshift

    alt_printf("ci_rand=0x%x, func_rand=0x%x\n", r1, r2);
  }

  while (1);
  return 0;
}
```

実行した結果が下の図です。`xorshift128()`とカスタム命令をそれぞれ10回呼び出して値を表示しています。単純なものですが擬似乱数の挙動がよく分かります。
![sbt_014_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/cf20f6a6-a69d-9e9a-04c2-5dcd29ef7b85.png)

一つの結果だけを見るとソフトウェアで実装したXorshiftはランダムな値を返しているように見えます。ところが何回か結果を比較すると、実行ごとに同じ数列になっているのがわかります。一方でカスタム命令で実装したXorshiftでは毎回違う数列を返しています。
同じアルゴリズムの擬似乱数ですから両者はおなじ数列を生成しているはずです。しかしカスタム命令の中のHDLはソフトウェアやプロセッサの動作とは無関係に動くため、結果的にソフトウェアからは予測できない値に見えるわけです。
事実、カスタム命令のHDLをシミュレーションで見てみると、生成される数列はソフトウェアのXorshift関数が返す数列と同じ値になっています。
![sim_001_a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/93a389fc-270a-a2a8-49a8-698f8a1815ea.png)

もちろんリセットの解除から命令を実行するタイミングまで、クロック単位で厳密に一致していればカスタム命令であっても同じ数列を返すことになります。そういう意味ではいくらランダム性が高くても自然乱数ではなく擬似乱数の範疇ではあります。とはいえ、現実で狙って再現するのはなかなかの難易度でしょう。

またXorshiftがいかに軽量なアルゴリズムとはいえ、バレルシフタを持たない超軽量のNiosII /eコアでは演算に300クロック以上費やしています。高性能な/fコアにしても10クロックを下回らないでしょう。カスタム命令ではこれを１命令で行うわけですから、性能へのインパクトもなかなかです。

このように、ユーザーでハードウェアで実装する命令を自由に追加できるカスタム命令は、少しの手間でソフトウェアの性能を大きく広げることができるオプションです。
無論ピッタリ嵌まる場合とそうでない場合とありますが、そのさじ加減も含めてうまく付き合っていけば力強い手段となってくれることうけあいです。

それでは、皆さま良いカスタム命令ライフを！


# 6. 参考

**NiosII プロセッサー・サポート**
[https://www.intel.co.jp/content/www/jp/ja/programmable/products/processors/support.html](https://www.intel.co.jp/content/www/jp/ja/programmable/products/processors/support.html)

**NiosII Custom Instruction User Guide**
[https://www.intel.com/content/www/us/en/programmable/documentation/cru1439932898327.html](https://www.intel.com/content/www/us/en/programmable/documentation/cru1439932898327.html)

**Intel Quartus Prime Standard Edition User Guide : Platform Designer**
[https://www.intel.com/content/www/us/en/programmable/documentation/jrw1529444674987.html](https://www.intel.com/content/www/us/en/programmable/documentation/jrw1529444674987.html)

**Terasic DE0-Nano**
[https://www.terasic.com.tw/cgi-bin/page/archive.pl?Language=English&CategoryNo=139&No=593&PartNo=1](https://www.terasic.com.tw/cgi-bin/page/archive.pl?Language=English&CategoryNo=139&No=593&PartNo=1)

**Xorshift RNGs**
[https://www.jstatsoft.org/article/view/v008i14](https://www.jstatsoft.org/article/view/v008i14)

**この記事で作成したプロジェクト**
[https://github.com/osafune/nios2_ci_sample](https://github.com/osafune/nios2_ci_sample)

---