Vivado HLSで、世界のナベアツ (3の倍数と3のつく数字の時だけアホになるIP) を作る

# この記事について
Vivado HLSを使用して、C言語だけで、3の倍数と3のつく数字の時だけアホになるIPを高位合成して作ります。

タイトルはネタですが、ハンズオンっぽく、Vivado HLSの使い方を習得できるような記事にしたつもりです。

## 全体の仕様
- CPU (Cアプリケーション)からナベアツIPのレジスタに数字を書く。
- ナベアツIPは、アホになるときにLEDを点灯する。また、アホかどうかをレジスタに書く。CPUはその値を確認できる。

## ナベアツIPの仕様
- 名前: nabeatsu
- 入力ポート: `ap_uint<32> number`
	- 入力となる数字
	- インターフェイス = AXI Lite スレーブ
- 出力ポート: `ap_uint<1> *ahoOut`
	- アホになるとき1を出力
	- インターフェイス = ap_none (+ register)
- 出力ポート: `ap_uint<1> 戻り値`
	- アホになるとき1を出力
	- インターフェイス = AXI Lite スレーブ	

出力ポートが2つあるのは、CPUがAXI経由でレジスタからREADする用、と、LEDに直接出力する用です。

## 環境と事前知識
## 環境
- 開発用PC: Windows 10 64-bit 
	- Vivado 2017.4 WebPACKライセンス
	- Vivado HLS 2017.4
	- Xilinx SDK 2017.4
- ターゲットボード: ZYBO (Z7-20) 

## 事前知識
VivadoとXilinx SDKの簡単な使い方は分かっているものとします。下記記事を参考にしてください。

- ZYBO (Zynq) 初心者ガイド
	- [1回目: 開発環境の準備](https://qiita.com/take-iwiw/items/966f252f6ca954aff08b)
	- [2回目: Hello Worldプロジェクト](https://qiita.com/take-iwiw/items/24a8a94741fdbb80f62a)
	- [3回目: PSのGPIOでLチカ](https://qiita.com/take-iwiw/items/39d2ed00fa88a1204fce)
	- [4回目: PLのAXI GPIOでPSからLチカ](https://qiita.com/take-iwiw/items/a3b935a4e4dbd41593e1)

# 全体の流れ
まず、Vivado HLSでナベアツIPを作ります。__Vivado HLSはあくまでIPを作るツールです。__ そのため、Vivado HLSでIPを作った後に、Vivadoで全体のハードウェアを作ります。その中で先ほど作成したナベアツIPを使用します。Vivadoでハードウェア(hdf)を作り、エクスポートしたら、最後にXilinx SDKで制御プログラムを作成します。

## 事前準備
Vivado HLSでZYBOボードを参照できるようにします。`C:\Xilinx\Vivado\2017.4\common\config\VivadoHls_boards.xml`に、以下2行を追加します。

```txt:VivadoHls_boards.xml
    <board name="Zybo_Z7_10" display_name="Zybo_Z7_10" family="zynq" part="xc7z010clg400-1"  device="xc7z010" package="clg400" speedgrade="-1" vendor="digilentinc.com" />
    <board name="Zybo_Z7_20" display_name="Zybo_Z7_20" family="zynq" part="xc7z020clg400-1"  device="xc7z020" package="clg400" speedgrade="-1" vendor="digilentinc.com" />
```

# Vivado HLSでナベアツIPを作る
## プロジェクトを作る
Vivado HSLを起動して、Create New Projectします。プロジェクト名は`nabeatsu`とします。ソースコードの追加などはひとまず何もせずにNextします。
最後に、ソリューションの設定をします。その時に、使用するデバイス設定が必要になりますので、今回はBoardsからZybo Z7 20 (先ほどの事前準備で追加した)を指定します。

![01.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/cbb38bc6-0dcb-3614-5aed-8368b31e94ea.jpeg)

## C++ソースコードの実装と、Cシミュレーションの実行
作成するIPのソースコードを用意します。
メニューバー -> Project -> New Sourceと、New Test Benchで、ソースコードとテストベンチを作ります。今回はナベアツIPを作るので、それぞれ以下のようなファイルを作成します。

- Source: nabeatsu.cpp
- Test Bench: nabeatsu_tb.cpp

それぞれ、以下のように実装します。
アルゴリズムの説明は簡単なので省略しますが、以下がポイントになります。

- 型として、ビット幅が指定できる`ap_uint`を使用している
- 出力ポートは、戻り値でもポインタ引数でもOK
- テストベンチでちゃんとテストコードを書く。(なくても害はないが)

```cpp:nabeatsu.cpp
#include <ap_int.h>

ap_uint<1> nabeatsu(ap_uint<32> number, ap_uint<1> *ahoOut)
{
	if (number % 3 == 0) {
		*ahoOut = 1;
		return 1;
	}
	while (number > 0) {
		if (number % 10 == 3) {
			*ahoOut = 1;
			return 1;
		}
		number /= 10;
	}

	*ahoOut = 0;
	return 0;
}
```

```cpp:nabeatsu_tb.cpp
#include <ap_int.h>
#include <stdio.h>

extern ap_uint<1> nabeatsu(ap_uint<32> number, ap_uint<1> *ahoOut);

int main()
{
	printf("Hello \n");
	ap_uint<1> ahoOut;
	for (int i = 1; i < 100; i++) {
		printf("%d: ", i);
		if (nabeatsu(i, &ahoOut)) printf("aho");
		printf("\n");
	}
	return 0;
}
```

実装が完了したら、メニューバー -> Project -> Run C Simulationします。これによってテストベンチ内のmain関数が実行されて、Cレベルでの動作確認ができます。



## C 高位合成する
### とりあえず合成してみる
まず、メニューバー -> Project -> Project Settingsで、Synthesisを選んで、`Top Function`に`nabeatsu`関数を設定しておきます。
その後、メニューバー -> Solution -> Run C Synthesis -> Active Solutionで、高位合成を行います。
完了したら、高位合成結果のレポートが表示されます。ここで性能(レイテンシ等)や回路規模を確認します。

![02.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/1e4b8825-bd2b-75c6-a23c-2081ccb0f811.jpeg)

### インターフェースをAXIにする
レポート内の、Interfaceを見ていただきたいのですが、これを見ると、`number_V`や`ahoOut_V`の他にも`ap_start`などがあります。これらは、このIPをコントロールするためのインターフェイスになります。このIPの接続先が、PL内の他のIPだけであればこれでも良いのですが、今回はCPUから制御する予定です。そのため、CPUから制御しやすいようにAXIインターフェースに変更します。

`nabeatsu.cpp`を開いた状態で、右側のペインで`Directive`タブを選びます。関数名や引数をダブルクリックすることで、どのようなインターフェースにするかを選択できます。なお、戻り値のインターフェースは関数名をダブルクリックして設定できます。インターフェイス仕様はこの記事の冒頭で記したとおりです。一点注意点は、ahoOutは直接LEDピン(M14)に接続します。そのため、余計な制御ピンは不要なので、`ap_none`とします。しかし、それだけだとvalid以外のときに出力が不安定になってしまいます。そのため、オプションとして`register`を指定します。これによって、出力状態が保たれます。

![03.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/47a78680-069f-27d2-97d8-459ce46fe94e.jpeg)

### IP化する
Directive設定後、再度、Run C SynthesisしてRTLを出力します。その後、Export RTLをクリックして、IP化します。設定はデフォルトのままでOKです。

### メモ: 最適化 / Directiveについて
今回はやりませんが、Directiveでは、上述したインターフェイスの他にも、ループをパイプライン化するとか展開するといった指定も可能です。回路規模や性能のトレードオフとなるところです。色々な設定を試して、レポートやRTLシミュレーション結果を見て、プロジェクトに最適な設定を探します。

このように、ソースコードは同じだけど、Directiveを色々変えて試したい、という状況が発生します。そのため、Vivado HLSではソリューションという概念を導入しています。ソースコードを共通としながら、ソリューション毎に別々のDirectiveを指定することが出来ます。これによって、後で比較したりするのが楽になります。

これも今回はやりませんが、RTLシミュレーションをやるには、Run C SynthesisでRTLを出力した後に、Run C/RTL Cosimulationします。シミュレーション設定ウィンドウが開くので、Dump Traceにportかallを設定して、OKします。完了後、Open Wave Viewerをクリックすることで、新規に開かれるVivadoウィンドウ上で信号波形を確認することができます。

# Vivado (IP Integrator)で全体のハードウェア(hdf)を作る
## プロジェクトを作る
Vivadoで、Zybo Z7-20ボードを指定して、新しいプロジェクトを作ります。IP Integratorで新規Block Designを作成して、PSを配置しておきます。(https://qiita.com/take-iwiw/items/24a8a94741fdbb80f62a )を参考にしてください。

## ナベアツIPを組込む
Vivado HLSで作成したIPを取り込むために、IPレポジトリの追加を行う必要があります。
左側のFlow Navigator -> PROJECT MANAGER -> Settingsを開き、IP -> Repository から、先ほど作成IP化したナベアツIPの場所を指定します。IPのパスは、`HSLプロジェクトのパス/高位合成したソリューション名/impl/ip`になります。

![04.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/1655da50-c96b-01c4-bb59-0d490a845873.jpeg)

再度Block Designに戻ると、Add IPから`Nabeatsu`が指定できるようになっているはずです。これを追加して、Run Connection Automationで自動配線します。
配置された`nabeatsu_0`の`ahoOut_V[0:0]`で右クリック -> `Make External`をクリックして、外部ポートを作成しておきます。これは、後でLED(M14)に接続します。

![05.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/e90275ab-b766-3ec0-ceb1-24bbcfe871b0.jpeg)

IP間の接続が完了したら、Generate Output Products, Create HDL Wrapperします。

## アホ出力をLEDに接続する
左側のFlow Navigator -> RTL ANALYSIS -> Open Elaborated Designを開きます。
下部に表示されるI/O Portsタブで、`ahoOut_V_0[0]`にM14 (LVCMOS33)を割り当てて、適当な名前でXDCファイルとして保存します。

## ハードウェアを完成させる
Generate Bitstreamをクリックして、ビットストリームを作成します。その後、Export Hardwareでビットストリーム付きのhdfを作成します。

# Xilinx SDKで制御プログラムを書く
VivadoのメニューバーからLaunch SDKでXilinx SDKを起動します。
standaloneプラットフォームのアプリケーションプロジェクトを作成します。
プログラム全体は以下のようになります。ナベアツIPへのアクセスは、自動生成されたbspプロジェクト内の`xnabeatsu.h`を使用します。

```c:helloworld.c
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xnabeatsu.h"

int main()
{
	init_platform();
	print("Hello World\n\r");

	XNabeatsu instNabe;
	u32 aho;
	XNabeatsu_Initialize(&instNabe, XPAR_NABEATSU_0_DEVICE_ID);

	for (int i = 1; i < 100; i++) {
		printf("%d: ", i);
		XNabeatsu_Set_number_V(&instNabe, i);
		XNabeatsu_Start(&instNabe);
		while (XNabeatsu_IsDone(&instNabe) == 0);
		aho = XNabeatsu_Get_return(&instNabe);
		if (aho == 1) {
			printf("aho\n");
		} else {
			printf("\n");
		}
		getchar();
	}

	cleanup_platform();
	return 0;
}
```

## 実行する
Program FPGAでビットストリームファイルを書き込んだ後に、ビルドしたCアプリケーションをRunします。
すると、入力数字が3の倍数か3を含むときだけアホになっていることが分かると思います。また、アホになっているときはLEDが点灯します。

```txt:実行結果
Hello World
1:
2:
3: aho
4:
5:
6: aho
7:
8:
9: aho
10:
11:
12: aho
13: aho
14:
15: aho
16:
17:
18: aho
19:
20:
21: aho
22:
23: aho
```

## メモ: HLSで高位合成されたIPへのアクセス
自分でVerilogを書いた自作IPの場合は、レジスタの何番地が何用のパラメータ、といった情報を自分で分かっていると思います。HLSで高位合成されたIPでも、bsp内の`xnabeatsu_hw.h`に、アドレスと用途がまとまっています。しかし、処理開始/終了のためのstart/doneフラグなどを自分で管理するのは面倒です。

そのため、今回使用したように、自動生成されたライブラリ(xnabeatsu.c/h)を使用するのが楽です。

便利なことに、LinuxでUIOとしてアクセスするためのライブラリ(xnabeatsu_linux.c)も自動生成されます。しかもインターフェイス(xnabeatsu.h)は、共通で使用することが出来ます。そのため、IPをHLSで作った後に、まずはXSDKのstandaloneプラットフォームで軽く動作確認をして、その後、Linux側で本格的にアプリケーションで使用する、といったワークフローでも移行が簡単にできます。

# 参考資料
https://www.slideshare.net/marsee101/vivado-hls1
https://forums.xilinx.com/t5/Vivado-High-Level-Synthesis-HLS/Zybo-Board-Files-for-HLS/td-p/748198