AIエッジコンテスト(実装コンテスト)のチュートリアル【7: 高位合成（失敗編）】

タイトルからしてネタバレですが、とりあえず失敗してみましょう。ソフトウェア技術者がなぜハードウェア設計につまずくかがわかると思います。
（この連載、いつになったら終わるんだろう。。。）

### 準備
1 環境

+ OS: Ubuntu 16.04 LTS (or 18.04 LTS), win10版はできますがサポート外です
+ Vivado 2019.1 を /tools/Xilinx/Vivado/2019.1 にインストール
(他のバージョン、特に<font color="red">Vivado 2019.2はサポート外</font>です！！)

2 Board Definition File (BDF) を設定
搭載されているFPGAに合わせて予め色々設定がされているので、初心者の方は設定しておくことをお勧めします。[Ultra96のメーカ(AvNet)からBoard Definition File](https://github.com/Avnet/bdf)をクローンして所定のディレクトリに置きます。

```
$ git clone git://github.com/Avnet/bdf
$ sudo cp -R bdf/* /tools/Xilinx/Vivado/2019.1/data/boards/board_files/
```

3 今回のソースコードを準備
コンテストチュートリアルのリポジトリに置いています。Vivado HLS(高位合成ツール)でプロジェクト作成後にコピーするので、予めcloneしておいてください。

https://github.com/HirokiNakahara/FPGA_AI_Edge_Contest_2019/tree/master/Inference_HLS_1

テストベンチ、パラメータは前回と同じものを使います。
+ testbench_input.txt
+ testbench_output.txt
+ weight_l0.txt
+ bias_l0.txt

以降、管理者権限で実行します。

### Vivado HLSの起動と初期設定
Vivado HLSのパスを通してから起動します。`.bashrc`に書いておけばパスは最初から通ります。

```
# source /tools/Xilinx/Vivado/2019.1/settings64.sh
# Vivado_hls &
```

Vivado HLSが起動するので`New Project`を選択→New Project ウインドウで

+ Project name: HW_conv_l0_1 (好みの名前でよいですが。。)
+ Location: (home directory)/xilinx/HLS_proj/

として`Next`, 次の`Add/Remove Files C-based source files`,`C-based testbench files`は共に`Next`をクリック。
Solution Configurationで Part Selection ->「...」をクリックし

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589480/91aff6dc-9ac8-5154-0c90-791ff78ba5c3.png)

`Boards`で`Ultra96v2 Evaluation Platform`を選択しOK -> Finish.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589480/45401755-187f-7801-1fb2-a0d8fe27a206.png)

Ultra96V2 Evaluation Platformが表示されない場合はBDFが適切な場所に置かれてないと思います。設定を確認してください。

プロジェクト設定が終わるとGUIが起動してコードが書ける状態になります。次に、ソース一式をプロジェクトに読み込みます。HLS設計の鉄則は<font color="red">予め合成対象コードをソフトウェアで確実に動作するように検証しておくこと</font>です。つまり、Vivado HLSで一からソフトコードを書くことはないはずです！

まずクローンしたサンプルコードをプロジェクトのディレクトリ直下に作った`src`に置きましょう。

```
# cd (home directory)/xilinx/HLS_proj/HW_conv_l0_1
# mkdir src
# cp (コンテストのリポジトリをクローンした場所)/Inference_HLS_1/* ./src
```

+ HW_kernel.cpp
+ HW_main.cpp

に加えて、テストベンチ・パラメータ

+ testbench_input.txt
+ testbench_output.txt
+ weight_l0.txt
+ bias_l0.txt

一式をコピーします。

メイン画面で Source -> 右クリック -> Add Files... を選択しソースファイルを置いた`src`からtestbench_input.txt, testbench_output.txt, weight_l0.txt, bias_l0.txt, HW_kernel.cpp, HW_main.cpp を読み込みます。

<img width="167" alt="スクリーンショット 2020-03-08 8.58.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589480/06ed1f2b-662f-b4ed-6c1d-e18d903b31c3.png">

こうなります↓

<img width="205" alt="スクリーンショット 2020-03-08 8.58.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589480/6bd3eaa9-a03b-4871-33dd-9701801922c9.png">

Project -> Project Settings ... をクリックして Simulation -> Add Files ... をクリックし、`HW_main.cpp`を読み込み、 Options -> Optimizing Compile を選択
Synthesis -> Top Function -> Browse をクリックして kernel (`HW_kernel.cpp`) を選択しOKをクリック
SimulationとSynthesisを設定したらOKをクリックしてProject Settingsを閉じる.

Vivado HLSツールのお約束として、検証(C-Simulation)のメイン関数(今回は`HW_main.cpp`)を別に読み込む必要があったので、設定しています。
 
<img width="329" alt="スクリーンショット 2020-03-08 8.59.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589480/0aa31ad8-b07d-98ec-fe7c-ea9eb6fa2c82.png">

### シミュレーションの実行
ソフトウェア検証と同じことをしていますが、一応、Vivado HLSでもやってみましょう。Run C SimulationをクリックしてOKをクリックください。シミュレーションが実行されてUbuntuで実行した場合と同じ結果になるはずです。

<img width="462" alt="スクリーンショット 2020-03-08 9.00.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589480/4cdbcb27-027e-5ae1-7db0-caaf09875529.png">
↑こんな感じにログが出力される。

今回はお試しですが、回路設計時にはこれを繰り返してソフトの検証を行ってください。

### ハードウェア合成
まずは`HW_main.cpp`と`HW_kernel.cpp`のプリプロセッサの定義をアンコメントします

```cpp:HW_main.cppとHW_kernel.cppの該当箇所
//#define HLS 
```

↓

```cpp:HW_main.cppとHW_kernel.cppの該当箇所
 #define HLS
```

これで高位合成記述を対象に合成が行われます。
いよいよ高位合成です！メニューの`C Synthesis`をクリックして高位合成を行いましょう。



しばらくするとVivado HLS Console にFinished C Cynthesisが表示されて合成が終わります。レポートを見て合成結果を見てみましょう。どれどれ、Latencyってのがデータを送って処理が終わるまでの時間だから‥

<img width="362" alt="スクリーンショット 2020-03-08 9.00.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589480/31d47a55-ff98-bf2e-3aac-7ef724db7fe6.png">**？？？？？**

<img width="472" alt="スクリーンショット 2020-03-08 9.01.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589480/f1be0d91-4945-a25e-a7e8-e780dea29002.png">
ちょっとびっくりしましたが、`Detail`をクリックすると詳細が表示されます。どうやらLoop1〜Loop3が`？(わかりませーん)`だから性能が見積もれない、ということです。
これはデータの読み出しに<font color="red">while()文を使っている→ループ回数が不明</font>だからですね。データ出力転送は固定回数のfor()文で記述しましたから665859サイクル（クロック）なので、もし100MHz(10nsクロック周期)の場合は6658590ns、つまり6.8ms間隔で処理が終わるということです。

肝心の畳み込みは、、
え、2227967424サイクル？？てことは100MHz(10ns)で22,279,674,240nsですか、、つまり**約22秒？？**遅い。。。

<img width="552" alt="スクリーンショット 2020-03-08 9.04.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589480/7f093182-c8a5-7ac4-3d47-752629388e2c.png">
次にハードウェアの消費量を見ましょう。えーと、Utilization Estimatesを見ると、利用可能なFPGAのリソース量(Utilization(%))に対する今回の高位合成による見積もりが見れます。BRAM18K（FPGAの内部メモリブロック数）が**496%！！！！**ひーっ、実装できないじゃないですか。。
原因は`Detail -> Memory`をクリックすると分かります。BRAM18K数はメモリ量を18Kb(単位はビット)で割れば算出できるので

（入力バッファ`src_img`）= 416 x 416 x 3 x 32bit(float) / (18 x 1024) 〜 920 (端数は繰り上がる)
（出力バッファ`dst_img`）= 102 x 102 x 64 x 32bit / (18/1024) 〜 1181 (端数は繰り上がる)

入出力バッファをなんとか削ればよいということですね。その割には他のハードウェア資源(DSPブロック,LUT)の利用率は1%程度です。。この割合がアンバランスということはFPGAメーカーが意図している回路が設計できていないということですね。

### つまりどういうことだ
ソフトウェアでよく書いている作法をそのまま高位合成しても

+ とりあえず合成**は**できる
+ 性能が全く出ない。そもそも性能が見積もれないことが多い。。
+ ハードウェア量が爆発してしまう。。特に内臓メモリ(BRAM)数。

ハードウェアを意識したコードの修正（リファクタリング）が必要です。次回はリファクタリングしてみましょう。