SDSoCチュートリアル with ZYBO (Zynq) (1)standaloneプラットフォーム

# この記事について
XilinxのSDSoC環境を使った開発の流れをステップバイステップで紹介していきます。
(僕自身、この記事を書いている時点でSDSoCを初めて使ってから1日も経っていないので、間違えがあるかもしれません。その際は教えてください)

## SDSoC環境とは
SDSoC(Software-Defined Development Environment for System-on-Chip)環境とは、Zynq等の開発においてソフトウェアとハードウェアの協調開発をやりやすくするためのEclipseベースの環境(IDE)です。勘違いしていたのですが、ロジックやアルゴリズムを「普通の」Cで書いたらツールが自動的にハードウェア化してくれるといった魔法の道具ではありません。もちろんXilinxや高位合成界隈の人が目指しているのはそこかもしれませんが、現状はVivado HLS同等の独自のpragmaを使ってハードウェアを意識したCを書く必要があります。まだ本格的にコードは書いていないのですが、CUDAのコードを書くのと同じくらいの感覚で開発出来そうです。

### SDSoC環境によって何が嬉しいのか?
SDSoC環境を使うことによって、開発フローがガラッと変わります。

#### 今までの開発フロー
1. Vivado HLSまたはVeilogでIPの作成 (e.g. 画像処理IPなど)
2. Vivadoでハードウェア全体(hdf, ビットストリーム)を作成
3. (必要なら) PetaLinuxによってLinuxイメージの作成
4. XSDKによってアプリケーション(ソフトウェア)の作成

上記が、典型的な従来の開発フローだと思います。
この時の問題は、1.のIPを変更したいとき、ハードウェアやLinuxイメージの作成が再度必要になり非常に手間がかかります。そしてこの変更は実際に結構な頻度で発生します。(仕様変更だけでなく、開発初期段階のお試し実装など。また、最初は完全ソフトウェアで実装していたけど、後から高速化のためにハードウェアに持っていくということもあります)。また、今の時代、1. IPの作成はソフト屋さんだったりアルゴリズム屋さんがやることが多いです。そうなると、途中のVivadoを使ったハードウェアの作成といった手間はソフト屋さんには荷が重いです。

#### SDSoC環境による開発フロー
1. Vivadoでハードウェア全体(hdf, DSA)を作成
	- PS、クロック、リセット、IO関係のハードウェアだけを定義
	- ここではビットストリームはまだ生成しない。代わりに、DSA(Device Support Archive)という、ファイルを出力
2. (必要なら) PetaLinuxによってLinuxイメージの作成
3. SDxによってSDSoCプラットフォームを作る
	- ハードウェアとソフトのシステム周り(FSBL, リンカスクリプト、OSなど)をパッケージにしたもの
4. SDxによってアプリケーションの作成
	- ソフトウェアとハードウェア(HLS)を同時に開発
	- 自分でHLS用コードを書かないでもアクセラレータ関数なども使えるらしい

SDSoC環境による開発フローでは、SDSoCプラットフォームが出来てしまえば、後はCでコードを書くだけで、ソフトウェアとハードウェアIPが出来てしまいます。また、アルゴリズム改良のためハードウェアIPを変更する場合も、手戻りが不要になります。

#### その他の嬉しいこと
従来、VivadoHLSで作ったIPをソフトから制御するためには、やはりハードウェアということを意識して、レジスタにアクセスしたり自動生成されるドライバ経由でアクセスする必要がありました。SDSoCではそこらへんも意識しないで済むようになり、ただの関数callで実現できます。

## SDxとは
SDSoC環境による開発を行うためのIDEそのものの名前です。
(いつも通り、Xilinx社の命名は分かりづらいです。。。)

## SDSoCプラットフォームとは
上述の開発フロー内でも述べたように、SDSoC環境においては、SDSoCプラットフォームをベースに開発を行います。
SDSoCプラットフォームは、ハードウェア(HDF)、ソフトのシステム周り(FSBL、リンカスクリプト、OSなど)をパッケージにしたものです。例えば、ZYBO用の○○というハードウェアを実装済みのLinuxベースのSDSoCプラットフォーム、といったように、構成はまちまちです。
SDSoC環境における開発は、このSDSoCプラットフォームを基に行います。
基本的これは、Xilinxやベンダから提供されます。　ZYBO用のプラットフォームも2018.2ではビルトインで入っているとwebには書いてありました。
![01.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/13bb371d-643f-8d7c-822c-cd1e09ca9ca9.jpeg)


書いてありました。。。が、実際にプロジェクトを作ろうとしたら見つかりませんでした。。。
SDSoCプラットフォームは自分で作ることもできます。ということで、本記事では、SDSoCプラットフォームを作り、それをベースにアプリケーション開発を行う、という流れで進めていきます。

![02.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/b3d2e611-620b-8990-e546-94aeb5cc2fa9.jpeg)

↑ZYBOがない。。。

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

## 事前知識
[ZYBO (Zynq) 初心者ガイド](https://qiita.com/take-iwiw/items/966f252f6ca954aff08b) シリーズの1, 2, 3, 4, 7, 8, 9, 10, 11の内容を理解してあると捗ると思います。

## 参考資料
- ug1027-sdsoc-user-guide.pdf
- ug1028-sdsoc-intro-tutorial.pdf
- ug1146-sdsoc-platform-development.pdf
- ug1236-sdsoc-platform-tutorial.pdf


## 今回やること
- OSなし(standalone)のSDSoCプラットフォームを作る
- 作成したSDSoCプラットフォーム上で、行列積の計算を高速化するアプリケーションを作る

(基本的にはug1236-sdsoc-platform-tutorial.pdfの内容と全く同じです)

# SDSoCプラットフォームを作る
## ハードウェアを作る (on Vivado)
### ブロックデザインを作る
まずVivadoを使ってハードウェアを作ります。中身としては以下を用意します。

- PS
- クロック (100MHz, 142MHz, 166MHz, 200MHz)
	- クロック同期用のリセット
- 割り込みをまとめるconcat

繰り返しになりますが、IPはここでは作りません。SDxで作ります。ですが、SDxで作るハードウェアIPはクロックを使う(かもしれない)し、割り込みを使う(かもしれない)必要があります。そのため、今の時点で必要でなくても、クロックと割り込みを用意しておきます。

Vivado -> New Projectから、project_1というRTLプロジェクトを作ります。ターゲットとしてはZybo Z7-20を指定します。
![03.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/8a51e80d-efe3-a7a9-b8cf-a79643e14cba.jpeg)
![04.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/70f22c99-aa67-bb3e-d152-1847753134be.jpeg)
![05.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/0558d585-9445-01f3-e82a-5154a3974d96.jpeg)


プロジェクトが出来たら、design_1という新しいブロックデザインを作ります。

#### PS
PSを配置します。
![06.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/fbd22cc2-9c77-4bca-17d5-c5d0eecdf1db.jpeg)

PSの設定を確認/変更します。
Ethernet0が有効になっていることを確認します。(過去のボードファイルだとバグがあったため)
![07.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/b6be66da-d236-2b47-bf6e-aa20816be81b.jpeg)

IRQ_F2P[15:0]を有効にします。
![08.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/9e69d5cd-0e15-8be8-62fb-e8a5f29bbafd.jpeg)


#### Concat
Concatを配置します。Number of Portsに1を設定します。
![09.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/a1dc097a-1185-f95e-97df-9748884dc2d7.jpeg)

#### Clocking Wizard
Clocking Wizardを配置します。Input Frequencyを50MHzに設定します。これは入力クロックとしてPSが出力するFCLK(50MHz)を使用するためです。
また、出力クロック(clk_out)を追加して、それぞれ100, 142, 166, 200MHzに設定します。今回、この数値はXilinxのチュートリアルの値に揃えました。SDxでIPを作るときに、ここで用意したクロックを使用することが出来ます。また、ResetTypeをActive Lowにしておきます。
![10.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/bf36833f-5a21-7b19-9782-699e7af55d37.jpeg)
![11.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/ce8e8c61-2d9d-3e9e-335f-1536ed382a39.jpeg)


#### Processor System Reset
各クロックに同期するProcessor System Resetを4つ配置します。

#### 接続する
以下のように接続します。
![12.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/da592f34-7deb-fe95-7729-ecdd86fe8f3f.jpeg)

### ハードウェアを生成する
旧来は、Vivadoの出力としてはビットストリーム付きのhdfを出力していました。今回の目的はプラットフォームを作ることです。そして、ハードウェアIPも後でSDxで作成/追加されるので、ビットストリームは不要です。代わりに、どういうハードウェアインターフェイスが使えるかという情報を出力してあげる必要があります。これらを含めたDSA(Device Support Archive)というファイルが最終的な生成物になります。

### プラットフォームプロパティの設定
tclスクリプトによって、プラットフォームプロパティを設定します。これによって、後でSDxでハードウェアIPを作る際に、どういうクロックを使えるのか? ということが分かるようになります。逆にこれがないと、SDxでハードウェアIPを作ろうとしてもエラーになります。(使えるクロック、ポートが分からないため)。

Vivadoの画面下部のtcl consoleに以下のコマンドを入力します。(VivaoプロジェクトがC:/asd/dev/zynq/vivado/project_1にあるとします。)

```tcl:プラットフォームプロパティの設定
cd C:/asd/dev/zynq/vivado/project_1

set_property PFM_NAME "takeiwiw:test:design_1:1.0" [get_files ./project_1.srcs/sources_1/bd/design_1/design_1.bd]

set_property PFM.AXI_PORT { \
	M_AXI_GP1 {memport "M_AXI_GP"} \
	S_AXI_HP1 {memport "S_AXI_HP" sptag "HP1" memory "ps7 HP1_DDR_LOWOCM"} \
	S_AXI_HP2 {memport "S_AXI_HP" sptag "HP2" memory "ps7 HP2_DDR_LOWOCM"} \
	S_AXI_HP3 {memport "S_AXI_HP" sptag "HP3" memory "ps7 HP3_DDR_LOWOCM"} \
	S_AXI_ACP {memport "S_AXI_ACP" sptag "ACP" memory "ps_ACP_DDR_LOWOCM"} \
} [get_bd_cells /processing_system7_0]

set_property PFM.CLOCK { \
	clk_out1 {id "0" is_default "true" proc_sys_reset "proc_sys_reset_0" } \
	clk_out2 {id "1" is_default "false" proc_sys_reset "proc_sys_reset_1" } \
	clk_out3 {id "2" is_default "false" proc_sys_reset "proc_sys_reset_2" } \
	clk_out4 {id "3" is_default "false" proc_sys_reset "proc_sys_reset_3" } \
} [get_bd_cells /clk_wiz_0]

set intVar []
for {set i 0} {$i < 16} {incr i} {
	lappend intVar In$i {}
}
set_property PFM.IRQ $intVar [get_bd_cells /xlconcat_0]

set_property dsa.name  design_1 [current_project]
```

上から順に、このプラットフォームの名前、使用可能なAXIポート、クロック、割り込みの設定になります。
AXIとしてGP0とHP0はここでは省略しました。理由は、GP0などは今後Vivado上でハードウェアを作る際に使用する可能性があるためです。例えば、AXI GPIOを使いたいとき、AXI GPIOとPSを接続するためにGP0を使います。逆に言うと、ここで設定したポートや割り込みはプラットフォームとしてSDxに対して自由に使っていいですよ、と言っているものなので、Vivado上で使うのはやめた方がいいです。

### ハードウェアを生成する
作成したdesign_1をGenerate Output Productsします。この時、Synthesis OptionsとしてGlobalを選ばないといけないようです。
![13.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/820c5808-9c6b-25ad-b01e-6fe3c62c9de4.jpeg)
![14.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/b22f5949-70e9-9231-2bce-d69f4b6c7cd1.jpeg)


その後、Create HDL Wrapperをします。
最後に、Export Hardwareをします。この時、Include bitstreamにチェックをする必要はありません。(あっても大丈夫です)
![15.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/826defdb-c47b-de92-b90a-c2d7aca7e678.jpeg)


最後に再びtcl console上でDSAファイルを出力して完了です。

```tcl:DSAファイルの出力
write_dsa -force  ./design_1.dsa
```
![16.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/0d87c74e-0f63-64ec-14e0-730b279a731e.jpeg)


### 生成物
- design_1.hdf (ビットストリームなし)
- design_1.dsa


## ソフトウェアコンポーネントを作る (on XSDK)
次に、Xilinx SDK (XDK) を使用して、プラットフォームに必要なブートイメージ、FSBL (First Stage Boot Loader)、リンカスクリプトを用意します。
VivadoからLaunch SDKを選び、XSDKを起動します。Vivadoはもう使わないので閉じてしまってOKです。

### FSBLの作成
XSDK上で、FSBL用の新規アプリケーションを作成します。OS Platformとしてはstandaloneを選び、TemplateとしてZynq FSBLを選びます。
![17.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/7fab6fbc-49cf-0137-9dcd-2e199278b7ff.jpeg)
![18.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/53031c48-ecc3-6974-9084-6b83926cb8fd.jpeg)
![19.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/3b6e6170-2818-f8ca-addd-91770b3fd4b2.jpeg)


プロジェクト作成後、Buildします。(デフォルトでオートビルドが走ります)。

その後、Xilinx -> Create Boot Imageを選びます。以下のように、適当な場所に、fsbl_standalone.bifとBOOT.binを出力するように設定します。また、Boot image partitionsにAddをしてビルドされたfsbl.elfを追加します(これは自動で追加されているかも)。
![20.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/ea704a99-7d1b-8897-992f-408e09dcd703.jpeg)
![21.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/585003cc-c041-288c-41f7-df9d8832e11c.jpeg)


最後にCreate Imageをクリックして完成です。

### リンカスクリプトの作成
リンカスクリプトは自分で書いてもいいのですが、適当なアプリケーションを作ってそれに使われているリンカスクリプトを流用します。

新たに、適当なアプリケーションを作ります(empty_applicaitionとします)。
![22.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/f4f0f7ad-93d4-c742-1c19-f10dd312f832.jpeg)
![23.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/61d7b72a-47bc-8492-32f7-f38cbe6b1d19.jpeg)


プロジェクトが出来たら、右クリックしてGenerate Linker Scriptを選びます。HeapSizeに768MB(805306368)、Stack Sizeに256KB(262144)を設定して、Generateします。
![24.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/2807893c-5ff4-e65b-a381-b338ac965871.jpeg)
![25.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/9644fbde-722c-33c0-6f42-b4aa0cbc44f0.jpeg)



### 生成物
以下の生成物を同じ場所にコピーしておきます。例えば、vivado/project_1/boot

- fsbl_standalone.bif
- BOOT.bin
- fsbl.elf
- lscript.ld

また、fsbl_standalone.bifを以下のように編集します。

```txt:fsbl_standalone.bif
//arch = zynq; split = false; format = BIN
the_ROM_image:
{
	[bootloader]<fsbl.elf>
	<bitstream>
	<elf>
}
```

![26.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/e7fc27ee-2c85-7ea6-8568-0aad06f2b175.jpeg)


以上で、必要なものは全てそろいました。XSDKはもう使用しないので閉じてしまってOKです。

## SDSoCプラットフォームを作る (on SDx)
いよいよSDxを使用してSDSoCプラットフォームを作ります。
SDxを起動して、New -> SDx Projectを選びます。Project TypeとしてPlatformを選びます。このとき、入力としてHardware Specification File (DSA/HDF)を選ぶ必要がありますので、Vivadoで生成したDSAを選びます。(HDFを選んではダメです)

![27.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/26997dbe-4fda-0599-9419-50aa61cc53ed.jpeg)
![28.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/257fc44a-34e2-bdb4-6b14-abd2f5e6ff2c.jpeg)
![29.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/bb624ca2-5a6b-a2e6-33bb-30e729e06800.jpeg)


プロジェクトが出来上がると、以下のようにdesign_1というSDSoCプラットフォームのひな形が出来ます。

![30.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/95ff031d-0944-b0a3-934e-8a0b7206feae.jpeg)


① Define System Configurationをクリックして、システム設定をします。今回作成するプラットフォームはOSなしのstandalone用なので、nameはstandaloneとします。ここで、生成済みのBoot用ファイルを設定します。
![31.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/eb2eee59-0abb-d703-6b6a-5b2e2e7ef91f.jpeg)

すると、② Add Processor Group/Domainをクリックできるようになります。ここで、リンカスクリプトを設定します。また、OSとしてstandaloneを設定しておきます。

![32.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/17c8cff4-1a0a-5ee5-df61-df3d3b2323f7.jpeg)
![33.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/ffd2862b-2f40-5f79-ae7d-63824167990e.jpeg)


最後に、③Generate Platformと④Add to Custom Repositoryをクリックして完成です。

# SDxアプリケーションの作成
作成したSDSoCプラットフォームを使って、SDxアプリケーションを作成します。
New -> SDx Projectを選びます。Project TypeとしてApplicationを選びます。
その後、Platformを選択する画面で、先ほど作成したdesign_1というプラットフォームが選べます。

![34.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/cb9cd97c-7753-fda6-6a38-d04b761d4fe5.jpeg)
![35.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/f1cd908c-5a29-30b4-7d81-5f61bf412250.jpeg)

standaloneとしてプロジェクトを作ります。(design_1にはstandalone用の設定しかないため、今はstandaloneしか選べません)
Templateとして、Array Partitioningを選びます。もしも、Empty Applicationしか表示されていなかったら、画面下部のSDx Examplesをクリックしてダウンロードしてください。
![36.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/1e154e60-5b9c-7522-76b3-cf2dd9e85fbe.jpeg)
![37.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/4705e8ae-e3e6-65f8-853d-6e7155be8e4b.jpeg)


## SDxアプリケーションのビルドと実行
作成したプロジェクトで右クリックして、Run As -> Launch on Hardwareを選びます。
![38.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/40858e20-1a2b-8b03-4404-17aa6343feef.jpeg)


待ちます。ひたすら待ちます。i7マシンで15分くらいかかりました。HLSによる高位合成と、ビットストリーム作成のための論理合成/配置配線をやっていると思えば妥当な時間ではあります。

しばらくするとターミナルに以下のような出力が出ます。行列積の計算が、ソフトに比べて169倍になったよ、と言っています。
![39.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/094890a1-2714-132a-f907-21c842118b9a.jpeg)


## (おまけ)ハードウェアの設定を変えてみる
SDxがハードウェアIPを生成するときの設定を少し変えてみます。Vivadoでハードウェアを作るときに、クロックを4つ作ったことを思い出してください。デフォルトでは100MHzが使用されていますが、せっかくなので、200MHzを使ってみます。Data motion network clock frequencyとHardware FunctionsのClock Frequencyを200にしてみます。(両方変える必要があるのかは不明)

結果としては、以下のようにタイミング制約を満たせないというエラーが出ました。

![40.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/4ac9613e-6f67-a84c-a1f6-eef015426584.jpeg)



# おわりに
SDSoCプラットフォームを作るのが少し手間ですが、一度作ってしまえばあとはSDx上だけの作業で済みそうなので、だいぶ楽そうです。
今まで、ZYBO用のSDSoCプラットフォームが用意されていないので敬遠していたのですが、思ったよりは簡単に自分でもSDSoCプラットフォームを作ることが出来ました。