ポータブル Mandelbrot集合 with ZYBO (Zynq)

みんな大好きマンデルブロ集合をZynqで描画します。せっかくZynqを使うので、スマートフォンから操作して、どこでもマンデルブロ集合を見れるようにしました (IoT?)。マンデルブロ集合を計算するIPはVivadoHLSで高位合成で作成しました。OpenAMPを使用して、ハードウェア制御+JPEGエンコードを行うベアメタルファームウェア(FreeRTOS)と、サーバを動かすLinuxのハイブリッド環境を構築しました。サーバにはPython+bottleフレームワークを使用しました。

_今年に入ってからずっとZynqのお勉強をしていましたが、その集大成です！！_
今は色々と環境や情報が整っているので、勉強を始めて3か月でもこれくらいのものは作れます。(でもVerilogは一行も書いてない)
デモ動画: https://youtu.be/kXt1YUwyFD0

![00_pic.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/978fa6e7-f3e4-a954-6837-7b29eff6de26.jpeg)
_(右側がタブレットのブラウザ画面。左上がHDMIディスプレイ。HDMIディスプレイの色がおかしいのは配線を間違えているためと思われる。また、解像度が足りていないので、右と下で折り返した発生している。縦筋が入っているのは、単に壊れているため)_

# 作ったもの
## 仕様
![specs.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/e2f9ec48-6814-46f6-c97f-27ba3ae12b70.jpeg)

- 動作仕様
	- スマートフォン等のブラウザから、WebAPI経由でコントロール（座標、倍率指定, 色変更）できる
	- ZYBOは、コントロール情報を基にマンデルブロ集合を計算し、描画する (デバッグ用にHDMIにも出力する)
	- ZYBOは、計算したマンデルブロ集合イメージをJPEGにエンコードし、JPEGファイルとして保存する
	- スマートフォン等のブラウザは定期的に、JPEGファイルを取得して、ブラウザに表示する
- 性能
	- 解像度: 640 x 480
	- 最大反復回数: 128
	- ビット精度: 固定小数点 96-bit (整数部 = 6-bit, 小数部 = 90-bit)
	- フレームレート: 1.0 ~ 48.1 [fps]

## 環境
- 開発用PC: Windows 10 64-bit 
	- Vivado 2017.4 WebPACKライセンス
	- Vivado HLS 2017.4
	- Xilinx SDK 2017.4
	- Visual Studio Code
- 開発用PC (Linux): Ubuntu 16.04 本家 (日本語版じゃない) (on VirtualBox 5.2.4)
	- PetaLinux 2017.4
- ターゲットボード: ZYBO (Z7-20) 


## ソースコード
- Vivadoプロジェクト
	- https://github.com/take-iwiw/ZYBO_Portable_Mandelbrot/tree/master/project_1
- Vivado HLSプロジェクト
	- https://github.com/take-iwiw/ZYBO_Portable_Mandelbrot/tree/master/project_1/HLS/Mandelbrot
- Xilinx SDKプロジェクト
	- https://github.com/take-iwiw/ZYBO_Portable_Mandelbrot/tree/master/project_1/project_1.sdk
- PetaLinux プロジェクト
	- https://github.com/take-iwiw/ZYBO_Portable_Mandelbrot/tree/master/PjPetaLinuxMandelbrot
- サーバースクリプト
	- https://github.com/take-iwiw/ZYBO_Portable_Mandelbrot/tree/master/PjPetaLinuxMandelbrot/project-spec/meta-user/recipes-apps/AppMandelbrot/files/MandelbrotServer


# 設計
## システム全体像
![01_overview.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/cd3aee80-4dbd-f165-2d3c-5f44b9cb57f7.jpeg)

## ハードウェア (PL)
マンデルブロ集合の計算、HDMI出力はProgrammable Logic(PL)で行います。JPEGエンコードもハードウェアでやりたかったのですが、出来なかったので今回はソフトウェアでやることにしました。

下記IPを配置したハードウェアを、VivadoのIP Integratorで作成します。

- Mandelbrot
	- 自作のIPです。Vivado HLSでCによる高位合成で作成しました
	- 入力として、座標、倍率、カラーマップ、出力先アドレス、開始指示、をレジスタ設定で受け取ります
	- 開始指示を受けたら、マンデルブロ集合を計算し、結果をRGB画像としてDDRに出力します
- VDMA, VOut, VTC, rgb2dvi
	- RGB画像を入力し、HDMIに出力します
	- 標準IP、サードパーティIPの組み合わせです
	- 参照: https://qiita.com/take-iwiw/items/b323e129f96426031f9f

## ソフトウェア (ベアメタル: CPU1)
ソフトウェア (PS)は、OpenAMPを使用してLinuxとベアメタル(RTOS)のハイブリッド環境にします。(参照: https://qiita.com/take-iwiw/items/ff46792eb107d978cd0b )
ベアメタル(RTOS)側ではハードウェア制御とJPEGエンコードを行います。FreeRTOS上で動きます。各モジュールはタスクを持っており、メッセージ通信によってデータをやり取りします。これによって、図のようなデータフローを実現します。

- ServerIF
	- OpenAMP関係の管理を行い、Linuxとの通信をします
	- Linux側のサーバーアプリ(以後、サーバー)からコントロール情報を受信します。
	- コントロール情報(座標、倍率、色情報)を、MandelbrotCtrlへ通知します
	- サーバーへ、JPEGストリームのアドレスを通知します
- MandelbrotCtrl
	- Mandelbrotハードウェアを制御します
	- 通知されたコントロール情報をレジスタに設定して、キック → 完了待ち、を繰り返します
	- 1回の計算が完了したら、マンデルブロ集合が描画されたRGBバッファのアドレスを、VideoCtrlとJpegCtrlに通知します
- VideoCtrl
	- 通知されたRGBバッファを出力するように、VDMAハードウェアに設定します
	- 通知があってもなくても、毎フレームHDMI出力するようにハードウェアをキックします
- JpegCtrl
	- 通知されたRGBバッファから、JPEGストリームを出力します。(libjpegを使用して、JPEGエンコードします)
	- JPEGストリームのアドレスをServerIFに通知します
	- libjpegのポーティングはこちらを参照: https://qiita.com/take-iwiw/items/da9a7f1e3552b55aa219
- BufferMgr
	- RGBイメージバッファと、JPEGストリームバッファの管理を行います
	- 参照: https://qiita.com/take-iwiw/items/333f8094668bcf28c35b
- DebugMonitor
	- OpenAMPではなく、Standaloneでデバッグする時に、ServerIFの代わりをします
	- キーボード入力からコマンドを受け付けます

## ソフトウェア (Linux: CPU0)
### サーバー
ユーザがブラウザ経由で操作できるように、WebAPIを提供するサーバを立てます。このサーバは、RPMsg(/dev/rpmsg)経由で、ベアメタルファームウェアと通信します。

- main.py
	- bottleフレームワークを使用して、WebAPIを提供します
	- 呼び出されたWebAPIに応じて、コントロール用コマンドをベアメタル側に通知します
- view/index.html
	- ブラウザ上で表示されるビュー
	- コントロール用ボタンと、マンデルブロ集合表示用のimgを持ちます
- static/js/main.js
	- ブラウザ上のユーザ操作に応じて、WebAPIを呼び出します
	- 定期的にマンデルブロ集合イメージのJPEGを取得,更新します

### init.dスクリプト
OpenAMP環境の開始、ベアメタルファームウェアのロード、サーバーの開始を、起動時にデーモンとして自動実行します。
https://github.com/take-iwiw/ZYBO_Portable_Mandelbrot/tree/master/PjPetaLinuxMandelbrot/project-spec/meta-user/recipes-apps/AppMandelbrot/files

### その他
- 上記ファイル達のインストール用レシピ
	- https://github.com/take-iwiw/ZYBO_Portable_Mandelbrot/blob/master/PjPetaLinuxMandelbrot/project-spec/meta-user/recipes-apps/AppMandelbrot/AppMandelbrot.bb
- デバイスツリー
	- https://github.com/take-iwiw/ZYBO_Portable_Mandelbrot/blob/master/PjPetaLinuxMandelbrot/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi

## メモリマップ
OpenAMPでは、まずLinuxがCPU0で起動して、その後ベアメタルファームウェアを所定の場所にロードします。また、本プロジェクトではRGBイメージとJPEG用のバッファが必要になります。JPEGバッファはベアメタルとLinuxの両方からアクセスされます。このバッファ領域は、reserved領域としてデバイスツリーで確保します。（まず、デバイスツリー設定をして、確保した領域を使うように、ベアメタル側のリンカスクリプトや実装を修正します。）

今回は、下記のようになるようにメモリマップを設計しました。

![memmap.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/69ad6cb9-99ba-9d9e-7f87-89fa30206ee6.jpeg)


# 実行方法や自分でビルドする方法
GitHubのREADMEに記載しました。
https://github.com/take-iwiw/ZYBO_Portable_Mandelbrot

お手持ちのZYBO上で動かすだけなら、Prebuiltバイナリを書き込んだSDカードで起動するだけです。(LANケーブルは接続しておいてください。IPアドレスは自動で取得します。HDMIはお好みで)

ビルドする場合は、結構大変です。本プロジェクトは、ハードウェア、ソフトウェア混合であり、さらにソフトウェアもベアメタルやLinux側スクリプト、さらにはLinuxイメージそのものまで、多くのプロジェクトがあります。そのため、何を編集するかによって、ビルド手順が変わります。


# プロジェクト作成時の手順メモ
Zynqの開発はただでさえ、ハードウェアとソフトウェアをいったりきたりして大変です。本プロジェクトの場合はさらにベアメタルとLinuxの開発も絡んできます。本プロジェクトを作成した時の手順を記載します。

1. VivadoHLSで、マンデルブロ集合を計算するIPを作る
2. Vivadoで、ハードウェア全体(hdf)を作る
3. Xilinx SDK(XSDK)で、ベアメタルアプリケーションを作る
	- まずは、非OpenAMPプロジェクトとして開発をする
	- 後々OpenAMP化することを考慮して、Language = C、CPU = CPU1にしておく
	- OpenAMPに絡むところを除いて、全ての実装、テストを終えておく。コマンドの発信元をDebugMonitorからServerIFに変えるだけで済むくらいまでの完成度にする
		- これは、OpenAMP化した後ではデバッグがやりずらいため
4. PetaLinuxで、Linuxイメージを作る
	- まずは、OpenAMPを有効にしただけのイメージを作る。
	- Pythonもインストールしておく
5. XSDKで、ベアメタルアプリケーションを、OpenAMP対応する
	- OpenAMPを管理するServerIFを作る
6. 作成したLinuxイメージを起動し、手動でOpenAMP環境の開始、ベアメタルアプリケーションのロードを行う
	- 簡単なLinux Cアプリケーション、またはPythonスクリプトでOpenAMPによる通信をテストする
7. 本格的にサーバー用Pythonスクリプトやhtmlを作成する
8. 手動起動によって、すべての処理がうまくいくことを確認する
9. PetaLinuxで、バイナリやサーバースクリプト、init.dスクリプトをインストールするように、レシピを編集する
10. Linuxイメージを再作成して完成

出来るだけ手戻りを小さくするために、`3. Xilinx SDK(XSDK)で、ベアメタルアプリケーションを作る`でしっかりと、ハードウェアとベアメタルソフトウェアのデバッグを完了させておくことが重要です。


# 残課題
## たまにJPEGイメージが壊れる
JPEGストリームは、ベアメタルアプリケーションがDDRに書き込み、Linux側が読み込んでファイルとして保存します。そのため、コヒーレンシを保つために、書き込み後のキャッシュフラッシュや、ノンキャッシュとして読み出すといったことが必要になります。

Linuxでの読出しにはちゃんとO_SYNCとMAP_SHAREDを指定しているにもかかわらず、たまにデータが壊れてしまいました。読出し前に、無理やりキャッシュを汚すことで、だいぶマシになりましたが、それでもまだたまに画乱れが発生してします。なんでだろう。。。デバイスツリーでのreserved領域の属性設定のせいかもしれない。が、原因不明。

## マンデルブロ集合の計算が遅い
Zynqを使っている割には計算が遅いです。ぶっちゃけCUDAの方が速い。下手したらCPU(OpenMP)の方が速いかも。。。

FPGAでマンデルブロ集合を描画するというのは、実は素晴らしい先駆者がおられます。(http://www.chiaki.cc/Pyxis2010/)。
この方の場合、480x256、反復回数=256回で、1フレームあたり26.2msec (=38 fps)という速度を達成しています。僕の場合はワーストで1fpsなので、38倍の差があります。

HLSとRTL (HDL)の違いがあるとはいえ、あまりにもひどい結果となっています。
(とはいえ、ほぼ素のアルゴリズムをCで書くだけで、ハードウェアが作れてしまうのは、高位合成凄いと思いました。実装だけなら数時間で終わりました。)

パイプラインディレクティブを付けるなど、数日Vivado HLSと格闘したのですが、リソース使用量や遅延などを考えると、何のディレクティブもつけない状態がベストという残念な結果になりました。
どなたか改良してくれると嬉しいです。

現時点での、合成結果レポート (@100MHz)

![report.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/7366f62b-1cc0-20c6-6bd2-9e3e40988094.jpeg) ![report2.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/88e02a4b-e0bf-04e2-2d8d-5038b82fa565.jpeg)


## スマホでの操作がしょぼい
スマートフォンから座標移動などの操作ができるのですが、ボタンのタップによる操作になります。ドラッグ(パン?)や、ピンチイン/アウト対応したかったけど、力尽きた。

# 色々と貼り付ける
## スクリーンショット
<table>
<tr>
<td><img src = "https://qiita-image-store.s3.amazonaws.com/0/214268/8752b4ff-1a5c-b8c0-74a8-ad3e4b390b2f.png"></td>
<td><img src = "https://qiita-image-store.s3.amazonaws.com/0/214268/a9dfea39-ac01-c8a3-4bd0-05a47facb6ab.png">
</td>
</tr>
</table>

## ハードウェアデザインとImplementation結果
![hw_diagram.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/36203286-95f8-538d-f9e1-b4172236af72.jpeg)
![floor_design.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/868c50e7-589c-e73a-a3df-a661805a23c7.jpeg)
![all_report.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/c1db1aba-19af-bda5-ff9b-a1a29d6fd7af.jpeg)