DeviceTree Overlay でのFPGAコンフィグ(DE10nano)

# はじめに
ARM CPUと一体化されたFPGAでLinuxを動作させているときの話です。
デバイスツリー オーバーレイを使い、LinuxからFPGAをコンフィグレーションする方法について、[Terasic DE10nano](https://www.terasic.com.tw/cgi-bin/page/archive.pl?Language=English&No=1046)というボードを使ってやってみた手順について書かせていただきます（Intel系FPGAを使った場合の記事もあまり見当たらなかったので）。

なお、この仕組みは、「デバイスツリー」、「デバイスツリーオーバーレイ」、「FPGA Subsystem」を使ったものになります。この記事を書くにあたり、それぞれについて調べた内容も書かせていただきました。よろしければこちらもご参照下さい(下記３つの内容はFPGAメーカ非依存の内容になるかと思います）。 
[資料１：デバイスツリーについて調べてみた](https://qiita.com/eofz/items/20cfa99601756fc98905) 、
[資料２：デバイスツリー Overlay について調べてみた](https://qiita.com/eofz/items/fbc72b39c6a176995444)
[資料３：FPGA Subsystem(FPGA-region)でFPGAをコンフィグする](https://qiita.com/eofz/items/9de2a9739712f31afa56)

#ベースとなるSD card の準備
ボードは、[Terasic DE10nano](https://www.terasic.com.tw/cgi-bin/page/archive.pl?Language=English&No=1046)というIntelのCyclone V SoC FPGAが載ったものを使っています。（Intel系のFPGAであれば、異なるボードやFPGA FamilyでもLinuxがbootできるSD cardさえあれば同様の手順でイケると思います。）まずは、ベースとなるLinuxが起動するSD cardを準備します。ボードベンダーが提供してくれていると思います。ここでは、Terasicサイトの [DE10-Nano Kit Resources](https://www.terasic.com.tw/cgi-bin/page/archive.pl?Language=English&CategoryNo=167&No=1046&PartNo=4) ページのリストから、"Linux LXDE Desktop (kernel 4.5)" 使います。このimageは、RootFile SystemがUbuntu16.04ベースなので、必要なものはaptで取ってこれたりして何かと便利です(Raspiでできることは大抵できる）。以降でKernelを入れ替えていますが、RootFile System(Ubuntuのコマンド系)の方はそのまま使えます。
SD cardへの書き込みができたら、このSD cardでのDE10nanoの起動を確認して下さい。HDMIに接続したモニタにLXDEデスクトップ画面が表示されればOKです。

#Linux Kernel とBaseデバイスツリーのコンパイル
上で用意したSD card imageのKernelを自分でビルドしたものに変更します。一般的に、提供されているイメージ中のKernelは古いバージョンの場合が多いのと、driver やデバイスツリーの中身を見たり修正したりするには、Kernel Sourceが必要なので、自分でKernelをビルドして、動かすというのが良いと思います。
基本は、Intel SoC FPGA 用Linuxのポータルサイト [RocketBoards.org](https://rocketboards.org/) のドキュメントに従い、最近のLinux Kernelをビルドし、zImage と DE10nano用のデバイスツリー(.dtb)を作成します。
ただし、ここではオーバーレイによりframe bufferが動作することを確認したいので、zImageにframe buffer driverを追加しておきます。手順はこちらの記事[DE10nano 向けLinux Kernel Build](https://qiita.com/eofz/items/3413cbdf804154bc150e)の「おまけ FrameBuffer driver 追加」になります。Kernelパラメータに、`Device Drivers-> Graphics support -> Frame buffer Devices -> Altera VIP Frame Reader framebuffer support`を追加してzImageを作成してください。なお、.dtb に関しては、修正の必要はなく、デフォルトのsocfpga_cyclone5_de0_sockit.dtbを使って下さい（デバイスツリーへの frame buffer 追加はオーバーレイで行います）。

出来上がったsocfpga_cyclone5_de0_sockit.dtbは、soc_system.dtbとリネームし、zImageとsoc_system.dtbをSD cardのFATパーティションにコピーします(同名ファイルを上書き）。

**Base デバイスツリー内容の確認**

オーバレイでコンフィグレーションを行うには、最初に読み込むデバイスツリー(Base tree)にFPGA region nodeがそれなりの内容で定義されている必要があります（[資料３](https://qiita.com/drafts/9de2a9739712f31afa56) ）。これについて確認しておきましょう。
今回使う.dtbファイルのソースは、socfpga_cyclone5_de0_sockit.dtsですが、このファイルが最終的に#includeしているsocfpga.dtsiに下記記述があります。


```text:socfpga.dtsi
/ {
	 ...
	soc {
　　　　　　　　　...
		base_fpga_region: base-fpga-region {
			compatible = "fpga-region";
			fpga-mgr = <&fpgamgr0>;

			#address-cells = <0x1>;
			#size-cells = <0x1>;
		};
                 ...
        };
}
```

/soc/base-fpga-regionというpath(ラベル名 base_fpga_region)で FPGA region nodeがあり、その中にfpga-mgr propertyも設定されています。この.dtbを使ってデバイスツリーオバーレイによるコンフィグレーションが行えることがわかります。

ちなみに、`socfpga.dtsi` は、DE10nanoに限らず、CycloneV, ArriraV, Arria10 用のボードの全.dts に共通して #include されているファイルです。Kernelソースに入っているデフォルトのデバイスツリーでオーバーレイによるコンフィグレーションready になっているということになります。



# オーバレイファイルの作成とコンパイル

実際のオバーレイファイルの例です。`soc_system.rbf` というビットストリームファイルでコンフィグレーションした後、Frame Bufferを有効にする、というオーバレイファイルです。


```text:my_overlay1.dtso
/dts-v1/;
/plugin/;
/ {
    fragment@0 {
        target = <&base_fpga_region>;
        #address-cells = <0x1>;
        #size-cells = <0x1>;
        __overlay__ {
            firmware-name = "soc_system.rbf"; 

            #address-cells = <0x1>;
            #size-cells = <0x1>;
            ranges =  <0x00000000 0xff200000 0x00200000>;

            alt_vip_vfr_hdmi: vip@31000 {
                compatible = "ALTR,vip-frame-reader-14.0", "ALTR,vip-frame-reader-9.1";
                reg = <0x00031000 0x00000080>;
                max-width = <1024>; max-height = <768>;
                bits-per-color = <8>; colors-per-beat = <4>;
                beats-per-pixel = <1>; mem-word-width = <128>;
            };
        };
    };
};
```

- `target = <&base_fpga_region>;` 
読み込まれているデバイスツリー中で、`base_fpga_region`のラベル[^1]を持つnodeに対してオーバーレイを行います。
`target-path = "/soc/base-fpga-region"` と書いても等価ですね。

- `firmware-name = "soc_system.rbf";` 
/lib/firmware/`soc_system.rbf`ファイルでコンフィグレーションします。このrbfには、Frame Buffer IPが含まれているものとします。

- `   alt_vip_vfr_hdmi: vip@31000 { ... };`
Frame Buffer用のnode記述です。`{ ... }` 内に書かれている内容は、Frame Bufferのドキュメントに従っています（devicetree の書き方のお作法）。
この様に、新たにコンフィグレーションしたことにより追加されたハードウェアを用のnodeを `__overlay__` ノードの中で定義します。他にも追加したいドライバがあれば、続けて追記します。
`#address-cells`, `#size-cells`, `ranges` は、Frame Buffer nodeのメモリマップを指定するのに必要なpropertyです（devicetree の書き方のお作法)。

## DeviceTree Overlay fileのコンパイル
Kernelをビルドした環境に含まれるdtcコマンドを使います。（FPGA の開発ツールに含まれるdtcコマンドでも構わないとは思います。）
先程の例のmy_overlay1.dtsoをコンパイルしてmy_overlay1.dtboにする場合：

```
<Kernel Source path>/script/dtc/dtc -o my_overlay1.dtbo my_overlay1.dtso 
```

バイナリのオーバレイファイル my_overlay1.dtso が生成されます。

# テスト用SD card作成
オーバレイによるコンフィグレーションの動作テスト用のSD cardを以下の様に作ります。動作テストの全容は以下となります。
**電源投入後は、
　FPGA: Frame Bufferを含まないデザイン
　zImage: Frame Buffer ドライバを含む
　DeviceTree: FPGA regionを含む、Frame Bufferは含まない
という状態で起動します。
その後、オーバーレイにより、
　FPGA: Frame Bufferを含むデザインに再コンフィグ
　DeviceTree: Frame Buffer node 追加
とします。**


前節で作成したSD cardのファイルを以下のファイルで置き換えorコピーします。

- 「Linux Kernel とBaseデバイスツリーのコンパイル」で作った zImage -> `FAT partitionの zImage` 

- 「Linux Kernel とBaseデバイスツリーのコンパイル」で作った socfpga_cyclone5_de0_sockit.dtb -> `FAT partitionの soc_system.dtb`

- DE10nano付属のSystemCDの Demonstrations/SoC_FPGA/ControlPanel/Quartus フォルダ 
Demonstrations/SoC_FPGA/DE10_NANO_SoC_GHRD/output_files/soc_system.rbf -> `FAT partitionの soc_system.rbf`

- DE10nano付属のSystemCDの Demonstrations/SoC_FPGA/ControlPanel/Quartus/output_files/soc_system.rbf -> `ext3 partitionの /lib/firmware/soc_system.rbf`
（もともとのSD imageのFAT partitionのrbfは、このControlPanelのrbfを使っています。ですので、最初にFAT partitionのrbfを /lib/firmware にコピーするということでもOKです。）


# オーバーレイ(=コンフィグレーション) 実行
テスト用SD cardでDE10nanoを起動し、root でログインします(以下すべてuart terminal からの作業）。この状態では、HDMIに接続したモニタには画像出力がされていません。また、デバイスファイルにFrame Bufferが存在しないことを確認してみて下さい。

```
root@DE10_NANO:~# ls /dev/fb?
ls: cannot access '/dev/fb?': No such file or directory
root@DE10_NANO:~#
```


作成した my_overlay1.dtbo をscpなどでコピーしてきます。
ConfigFsを使った方法([資料２](https://qiita.com/drafts/fbc72b39c6a176995444) )でオーバーレイを実行します。オーバーレイのディレクトリ名は test_fb とした例です。

```
mkdir /sys/kernel/config/device-tree/overlays/test_fb
cat my_overlay1.dtbo > /sys/kernel/config/device-tree/overlays/test_fb/dtbo 
```

正常に実行されれば、`cat ... > .../test_fb/dtbo`を実行した瞬間にLEDが一瞬消灯し、再度点灯するはずです。また、デバイスファイルを確認すると以下の様にFrame Bufferデバイス /dev/fb0 が生成されています。

```
root@DE10_NANO:~# ls /dev/fb? -l
crw-rw---- 1 root video 29, 0 May 21 00:27 /dev/fb0
root@DE10_NANO:~#
```

Frame Bufferデバイスが有効になったことにより、HDMIに接続されたモニタには、カーソルの点滅が見えてきているはずです。さらにもう一つ、このFrame Bufferを使ってLXDEデスクトップを起動してみましょう。

```
systemctl start lightdm
```
これで、最初にTerasicサイトからダウンロードしてきたSD card image("Linux LXDE Desktop" )と同様の状態になりました。
また、Kernelのログにも fpga manager や、frame buffer のログが記録されていますので、`dmesg` で確認してみて下さい。

# さらに別のコンフィグレーションを行う場合
このオーバーレイを使って何度もFPGAのコンフィグレーションが可能となります。が、FPGAの再コンフィグレーションする際は、FPGA内のハードウェアをアクセスしているARMソフトウェアプロセスを停止してやることが必要ですので、この点は注意してください。この例ですと、FPGAのハードウェアは、Frame Bufferだけですが、これをアクセスしているプロセスとしては、LXDEデスクトップとfbcon(カーソル点滅)[^2]がありますので、それらを停止してから、オーバーレイの削除、新たなオーバーレイの実施、をするべきでしょう。具体的には、


```text:この例でのFPGA-HWをアクセスするプロセス停止
systemctl stop lightdm
echo 0 > /sys/class/graphics/fbcon/cursor_blink
```

の後、

```
rmdir /sys/kernel/config/device-tree/overlays/test_fb
```

を行った後、次のFPGAコンフィグを行うオーバレイを実施します。
 
```
mkdir /sys/kernel/config/device-tree/overlays/test_fb2
cat my_overlay2.dtbo > /sys/kernel/config/device-tree/overlays/test_fb2/dtbo 
```




----------

# 注意点
いくらFPGAが丸ごと書き換えられると言っても、動作の仕組みから考えると、書き換えられるFPGAデザインには制約があると思われますので、その点についてちょっと。SoC FPGAの最初の最初のbootには、preloaderというコードが走りますが、このpreloaderは、FPGA のコンパイル時に生成されるhandoff_file情報も加味してbuildするので、今の起動に使われたpreloaderでの設定を保った範囲のFPGAデザインに書き換える必要があるはずです。具体的には、handoff_file情報は、FPGAのデザインで使うPlatform Designer(Qsys)での HPS IP(ARM CPUが入ったモジュール)の設定情報なので、**HPS IPの設定が同一のFPGAデザインなら、Linuxを起動したままでも書き換えて動作させることができる**ということになるかと思います。 
HPSとFPGA間のbridgeの設定（ビット幅やバス本数）はHPS IPでの設定する内容となりますが、これらバスブリッジの設定に関しては、動作中にコンフィグレーションし直して変更することはできない可能性大です。実際にFPGA2SDRAMの設定に関しては、後から追加したbusは、Linux からのコンフィグでは開通しませんでした(preloaderの変更&再起動が必要）。




# 参考資料・リンク


[FPGA+SoC+LinuxでDevice Tree Overlayを試してみた -Qiita](https://qiita.com/ikwzm/items/ec514e955c16076327ce)
[Linux から FPGA コンフィグレーションする方法 by Macnica](https://macnicago.zendesk.com/hc/ja/articles/360025351651-Linux-%E3%81%8B%E3%82%89-FPGA-%E3%82%B3%E3%83%B3%E3%83%95%E3%82%A3%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95)

[^1]: LinuxのMakefileで作られたbase treeにはシンボル情報も含まれている(-@付きでコンパイルされている）のでオーバーレイ側でbase treeのラベルがオーバーレイファイル中でも使えます。
[^2]: なぜカーソル点滅が勝手に実行されているかですが、Kernelのコンフィグレーションにて、`Device Drivers-> Graphics support -> Console display driver support -> Framebuffer Console support` がenableになっているためです。Kernelを作る際にこれをdisableにしておけばfbconのケアは必要なくなります。