ZynqMP 向け Debian/LinuxでFPGA クロックの周波数を変更するとリブートする件







# はじめに


筆者は以下の記事で示すように Zynq/ZynqMP で Linux を動かし、その上で Device Tree Overlay を使って Linux 上から FPGA のロードやクロックの設定などを行っています。



* [「Linux Kernel 4.10 でのFPGAのサポート事情」@Qiita](https://qiita.com/ikwzm/items/2ff7d5429da8ace7c0bd)



この記事では、Linux から FPGA クロックの周波数を変更した際に出会ったトラブルの内容とその原因と対処方法を示します。ただし、原因に関してはまだ状況証拠があるだけでまだ推測の段階です。こう対処すれば出なくなったというだけであることに注意してください。




# 症状



## トラブルが起こった環境


* ハードウェア: Ultra96-V2
* ソフトウェア: [ZynqMP-FPGA-Linux v2019.1.0](https://github.com/ikwzm/ZynqMP-FPGA-Linux)

  *  OS: Linux Kernel 4.19.0 (linux-xlnx v2019.1)
  *  Root File System: Debian10 (buster)

* アプリケーション: [QCONV-STRIP-Ultra96](https://github.com/ikwzm/QCONV-STRIP-Ultra96)


## トラブルの内容


ZynqMP-FPGA-Linux v2019.1.0 では、ブート時に サンプルの FPGA プログラムがPLにロードされて、FPGA クロックを周波数100MHz に設定されて出力されています。このようにすでにFPGAが動作している状態で、次のようなデバイスツリーをオーバーレイして FPGA をロードして、その後 FPGA クロックの周波数を変更します。

この例では Xilinx が提供している PL クロック用ドライバ `xlnx,fclk` を使っています。


```devicetree:test1.dts
/dts-v1/; /plugin/;
/ {
        fragment@0 {
                target-path = "/fpga-full";
                __overlay__ {
                        firmware-name = "qconv_strip_axi.bin";
                };
        };
        fragment@1 {
                target-path = "/amba_pl@0";
                #address-cells = <2>;
                #size-cells = <2>;
                __overlay__ {
                        #address-cells = <2>;
                        #size-cells = <2>;
                        afi0 {
                                compatible    = "xlnx,afi-fpga";
                                config-afi    = <0 0>, <1 0>, <14 0x200>;
                        };
                        clocking0: clocking0 {
                                #clock-cells = <0>;
                                assigned-clock-rates = <250000000>;
                                assigned-clocks = <&zynqmp_clk 71>;
                                clock-output-names = "fabric_clk";
                                clocks = <&zynqmp_clk 71>;
                                compatible = "xlnx,fclk";
                        };
                };
        } ;
} ;
```




上記のデバイスツリーをオーバーレイすると、次のようにシステムがリブートしてしまいます。


```Shell
root@debian-fpga:/home/fpga/# mkdir /config/device-tree/overlays/test1
root@debian-fpga:/home/fpga/# dtc -I dts -O dtb -o test1.dtb test1.dts
test1.dtb: Warning (avoid_unnecessary_addr_size): /fragment@1: unnecessary #address-cells/#size-cells without "ranges" or child "reg" property
test1.dtb: Warning (avoid_unnecessary_addr_size): /fragment@1/__overlay__: unnecessary #address-cells/#size-cells without "ranges" or child "reg" property
root@debian-fpga/home/fpga/# cp test1.dtb /config/device-tree/overlays/test1/dtbo
[  283.411110] fpga_manager fpga0: writing qconv_strip_axi.bin to Xilinx ZynqMP FPGA Manager
NOTICE:  ATF running on XCZU3EG/silicon v4/RTL5.1 at 0xfffea000
NOTICE:  BL31: Secure code at 0x0
NOTICE:  BL31: Non secure code at 0x8000000
NOTICE:  BL31: v2.0(release):xilinx-v2019.1-ultra96-1
NOTICE:  BL31: Built : 14:43:47, Jul 22 2019
PMUFW:  v1.1
U-Boot 2019.01 (Jul 21 2019 - 00:29:10 +0900)
Model: Avnet Ultra96 Rev1
Board: Xilinx ZynqMP
DRAM:  2 GiB
EL Level:       EL2
Chip ID:        zu3eg
MMC:   mmc@ff160000: 0, mmc@ff170000: 1
In:    serial@ff010000
Out:   serial@ff010000
Err:   serial@ff010000
Bootmode: SD_MODE
Reset reason:   EXTERNAL
Net:   No ethernet found.
626 bytes read in 8 ms (76.2 KiB/s)
Loaded environment from uEnv.txt
Importing environment from SD ...
```





# 原因



## ZynqMP のクロック生成回路の構造


システムがリブートする原因は、まだ状況証拠の段階ですが、FPGA が動作している時に FPGA クロックの周波数を変更する時になんらかの問題が発生するためのようです。



ZynqMP のPLクロック生成回路は次のようになっています。


![Fig.1 ZynqMPのクロック生成回路の構造](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/621cae65-4830-6c23-2ac7-3d77ad146980.jpeg "Fig.1 ZynqMPのクロック生成回路の構造")

Fig.1 ZynqMPのクロック生成回路の構造

<br />




## デバイスツリーオーバーレイによるFPGA のロードとFPGAクロック周波数変更シーケンス


前節のデバイスツリーをオーバーレイしてFPGAのロードと FPGA クロック周波数の変更を行ったときの各モジュールのシーケンスを示します。




![Fig.2 デバイスツリーオーバーレイによるFPGAのロードと FPGA クロック周波数の変更シーケンス](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/987d1e22-da3a-3194-a36e-cbcafcf043e4.jpeg "Fig.2 デバイスツリーオーバーレイによるFPGAのロードと FPGA クロック周波数の変更シーケンス")

Fig.2 デバイスツリーオーバーレイによるFPGAのロードと FPGA クロック周波数の変更シーケンス

<br />





* Device Tree Overlay により fragment@0 に指定された fpga-regionの of_fpga_region_notiry_pre_apply() が呼ばれます。
* fpga-region は、指定された firmware ファイル(FPGA のビットストリームファイル)をバッファに読み込みます。
* fpga-region はfpga-mgr に対して load オペレーションを呼び出します。
* fpga-mgr は、まずFPGA の  GSR(Global Set Reset)をオンにします。これによりFPGAには非同期リセットがかかります。
* fpga-mgr は、ICAP(Internal Configuration Access Port) を通して FPGA のビットストリームをFPGA にロードします。
* fpga-mrg は FPGA のロードが完了したのを確認してからGSRをオフにします。これによりFPGA はリセットが解除され、ロードされたプログラムが動作を開始します。
* fpga-mgr はFPGAのロードを確認して fpga-region に制御を返します。
* fpga-region は fpga-mgr の正常終了を確認して処理を終了します。
* Device Tree Overlay は fragment@1 に指定されたノードのオーバーレイを行います。具体的には xlnx,fclk デバイスドライバを probe します。
* xlnx,fclk デバイスドライバは probe された際に devm_clock_get()を呼び出してクロックモジュールを生成します。
* devm_clock_get() はクロックモジュールを生成する際、of_clk_set_defaults() を呼び出します。
* of_clk_set_defaults() はデバイスツリーのノードに assigned-clock-ratesプロパティがあれば、そこに指定された周波数でクロックの周波数を変更します。
* xlnx,fclk デバイスドライバは devm_clk_get() でクロックモジュールを生成したあと、clk_prepare_enable()を呼び出してクロックを出力します。




## 問題と思われる点


このシーケンスで問題と思われるのは、次図の赤丸で示したところです。


![Fig.3 問題と思われる点](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/174d1d9d-bc4d-ccc9-36c2-46777b640620.jpeg "Fig.3 問題と思われる点")

Fig.3 問題と思われる点

<br />

Linux Kernel のクロックモジュールは、単純に clk_set_rate() を呼び出して周波数を変更した場合、特に何もせず周波数を変更しようとします。そして、ZynqMP の場合は、次図で示すように場合によっては意図しない周波数のクロックを出力する可能性があります。

例えば Primary PLL に1500MHz、pl0_division0 に15、Pl0_division1 に 1 が設定されているとします。この場合、出力クロックの周波数は100MHz(=(1500MHz÷15)÷1) です。ここで周波数を250MHz に変更するとします。もし、次の図で示すように、pl0_division0 に１を設定してから pl0_division1 に 6 を設定したとしたら、pl0_divison0 の設定から pl1_division1 の設定までの間、1500MHz のクロックが出力されてしまいます。そして、そのクロックで動いている回路が誤動作する可能性があります。

![Fig.4 ZynqMP で意図しない周波数のクロックが出力される例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/4d6210f1-ff5f-856a-d16c-4e3c532e9f3a.jpeg "Fig.4 ZynqMP で意図しない周波数のクロックが出力される例")

Fig.4 ZynqMP で意図しない周波数のクロックが出力される例

<br />





クロックの周波数を変更する際、すでにクロックが出力状態にあった時、この誤ったクロックの出力がそのまま FPGA に出力されてしまいます。この誤ったクロック出力がなんらかの誤動作を招き、システムをリブートするものと思われます。



ただし、この現象を正確に捉えたわけではないので、状況証拠でしかありません。試しに、別のクロックドライバを使って、クロックの周波数を変更する前にクロック出力をディセーブルして(pl0_clk_act=0)、クロックの周波数を変更した後にクロックの出力をイネーブル(pl0_clk_act=1)にしたところ、システムはリブートしませんでした。




![Fig.5 試しにクロックをディセーブルしてから周波数を変更してみる](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/cc581c56-4e80-71ba-3b1a-152e0cec3098.jpeg "Fig.5 試しにクロックをディセーブルしてから周波数を変更してみる")

Fig.5 試しにクロックをディセーブルしてから周波数を変更してみる

<br />




# 対処方法



## 暫定的な対処方法


もっとも簡単に対処するには、fpga-region が FPGA のロードを開始する前に、FPGA クロックを止めてしまうことです。この方法がベストというわけではありませんが(詳細は後述)、現状、もっとも現実的です。


![Fig.6 FPGA ロード前に FPGA クロックを止めて対処](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/606280fe-427e-91c7-0d4f-df28eeb7aef5.jpeg "Fig.6 FPGA ロード前に FPGA クロックを止めて対処")

Fig.6 FPGA ロード前に FPGA クロックを止めて対処

<br />



残念ながら、xlnx,fclk デバイスドライバはクロックを止める機能はありません。クロックを止めるには fclkcfg (筆者が公開している FPGA Clock デバイスドライバ）などを使う必要があります。

例えば次のようなデバイスツリーを用意しておき、このデバイスツリーをオーバーレイして一旦クロックを止めたあと、デバイスツリーをオーバーレイから削除します。


```devicetree:fclk0-off.dts
/dts-v1/; /plugin/;
/ {
         fragment@0 {
                target-path = "/amba_pl@0";
                #address-cells = <2>;
                #size-cells = <2>;
                __overlay__ {
                        #address-cells = <2>;
                        #size-cells = <2>;
                        fclk0: fclk0 {
                                compatible    = "ikwzm,fclkcfg-0.10.a";
                                clocks        = <&zynqmp_clk 0x47 &zynqmp_clk 0>;
                                insert-enable = <0>;
                        };
                };
        } ;
} ;
```





## おまけ１　ダメな対処方法


当初はデバイスドライバに手を入れて、クロックの周波数を変更する前にクロックを一旦強制的にディセーブルにすることを考えました。しかしこの方法は致命的な欠点があります。この方法では、Fig.6 の赤で囲んだ部分で示すように、新しく FPGA にロードする回路が、ロードする前のクロックの周波数で動作する期間ができてしまいます。


![Fig.7 ダメな対処方法](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/f01f3ec4-cbef-a987-e506-383ffeaf4b1f.jpeg "Fig.7 ダメな対処方法")

Fig.7 ダメな対処方法

<br />

もし、FPGAをロードする前のクロックが200MHz だったとき、新しくロードする回路が100MHz で動くように設計されたものだったりしたら、この期間だけ100MHz でしか動かない回路に200MHz のクロックを入れることになります。どのような誤動作を引き起こすかわかりません。

この対処方法には、もうひとつ問題があります。Linux Kernel のクロックモジュールに修正が必要なことです。xlnx,fclk デバイスドライバは単純にクロックモジュールの基本的な関数である devm_clock_get() を呼び出しています。devm_clock_get()はクロックモジュールの生成とクロック周波数の変更を一気に行っています。クロックの周波数を変更する前にクロックをディセーブルするには、このクロックモジュールの基本的なところに手を入れなければなりません。他への影響を考えると躊躇します。




## おまけ２　理想的だが困難な対処方法


理想はFPGAのロードをしている間にクロックの周波数の変更もしてしまうことです。FPGA のロード中は FPGA 全体にリセットがかかっているので、この間にクロックの周波数を変更しても誤動作しません。そしてFPGAのロードが終わってリセットを解除した時点ですぐに動作を開始することができます。


![Fig.8 理想的だが困難な対処方法](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/7d12d68c-1143-1a82-5962-e1cab9c53a13.jpeg "Fig.8 理想的だが困難な対処方法")

Fig.8 理想的だが困難な対処方法

<br />



この対処方法が困難な点は、fpga-mgr とクロックドライバを並列に動作させなければならないことです。さらにFPGA のロードの開始とクロックのディセーブルとクロック周波数の変更を開始するタイミング、およびFPGAの動作を開始するタイミングとクロックのイネーブルのタイミングをちゃんと同期をとる必要があります。現時点の Linux Kernel の fpga-region や fpga-mgr にはこの機能がありません。現在 Linux Kernel が提供している FPGA フレームワークを抜本的に見直す必要があります










# 参考


* [「UltraZed/Ultra96/Ultra96-V2 向け Debian GNU/Linux (v2019.1版) ブートイメージの提供」@Qiita](https://qiita.com/ikwzm/items/0c6f110aa19e368af03d)
* [「FPGA+SoC+Linux+Device Tree Overlay+FPGA Manager(ブートイメージの提供)」@Qiita](https://qiita.com/ikwzm/items/cdeb3c9c352776eac683)
* [「Linux でユーザー空間から Zynq/ZynqMP の PLクロック信号を制御するデバイスドライバ」 @Qiita](https://qiita.com/ikwzm/items/aae3dab578e28c13bc65)