はじめに
「FPGA+SoC+Linuxのブートシーケンス(ZYNQ+Vivado編)」では Vivado SDKで作ったFSBL(First Stage Boot Loader)をステージ1ブートローダーとして Linux をブートする方法を説明しましたが、ここではそれとは別に、ステージ1ブートローダーとしてU-Boot-spl(U-Boot Second Program Loader)を使った方法を説明します。
U-Boot-splを使って Linux をブートする方法は、以下の資料を参考にしました。いずれもすばらしい資料なので合わせてご覧ください。
- 「FPGA マガジンNo.12」第4章, CQ出版社
- 「Debian Linux on Zynq Setup Flow Version March 2016 for VIvado 2015.4」
ブートシーケンス
下図にZYNQで Linuxを動作させる時のブートシーケンスを示します。
図1 ZYNQ の Linux の起動までのブートシーケンス
・ステージ 0 (BootROM)
ZYNQのステージ0ブートローダーはSDCardの第一パーティションにFATファイルシステムがある場合、そこからBOOT.binを探します。もし見つかったらBOOT.binを作る際に[bootloader]オプションを付けたブロック(ここにはU-Boot-splが入っている)を内部RAMに転送して制御を移します。
・ステージ 1 (U-Boot-spl)
ステージ1ブートローダー(U-Boot-spl)はU-Boot のSPL(Secondary Program Loader)を使います。U-Boot-splはPSの各種設定やSDRAMの設定をした後、SDCardの第一パーティションにある u-boot.img をSDRAMに転送して制御を移します。FSBLを使った場合と異なり、ここではPLのプログラムは行いません。
・ステージ 2 (U-Boot)
ステージ2ブートローダー(U-Boot)はスクリプトや環境変数などが使えるので、その設定にしたがって処理を実行できます。一般的には次の処理をスクリプトにします。
- FPGAビットストリームファイルをSDRAMにロード
- SDRAMにロードしたFPGAビットストリームをPLにロード
- Linuxのカーネルイメージ、Device Tree、場合によってはルートファイルシステムをSDRAMにロード
- ロードしたLinuxカーネルイメージに制御を移す
U-Bootが読み込めるDevice Tree は1つだけなので、あらかじめPS側のDevice Tree とPL側のDevice Tree とをマージしておく必要があります。
デザインフロー
下図にZYNQでLinuxを動作させる時のデザインフローを示します。
図2 ZYNQでLinuxを動作させるためのデザインフロー
1. VivadoでFPGAビットストリームファイル(.bit)を生成する
VHDLやVerilog-HDLなどで記述された回路をVivadoを使って論理合成と配置配線を行いFPGAビットストリームファイルを生成します。ここでは詳細な説明は省略します。
2. Vivadoでハードウェア情報ファイル(.hdf)をexportする
1.のFPGAビットストリームファイルを生成したプロジェクトから、ハードウェア情報ファイル(.hdf)をexportします。exportの手順に関しては「Vivadoでハードウェア情報をエクスポートするTclスクリプト」を参照してください。
3. ハードウェア情報ファイル(.hdf)からps_init_gpl.{c,h}を取り出す
ハードウェア情報ファイル(.hdf)にはPSの各種設定およびPL I/FやPLクロックの設定を行うためのCのプログラム(ps_init_gpl.{c,h})が入っています。このファイルを取り出します。
「FPGA マガジンNo.12」や「Debian Linux on Zynq Setup Flow Version March 2016 for VIvado 2015.4」では、Vivado SDK の hsi コマンドを使う方法が載っていますが、実はこのファイルは単なるzipで圧縮されたファイルなので、普通に解凍して取り出すことが出来ます。
また、u-boot-splで行う PL周りの設定を、 U-Boot がすでに定義済みのものをそのまま使うのであれば、この工程は必要ありません。
4. Vivado SDKでハードウェア情報ファイル(.hdf)からDevice Tree を作る
Vivado SDKを使って、ハードウェア情報ファイル(.hdf)からDevice Tree を作ります。ただし、ここで作られた Device Tree はあくまでも参考程度にしかなりません。このDevice Tree を使ってLinuxを起動しようとすると失敗することが多いです。
ここで作ったDevice Tree から、FPGAに実装したモジュールのレジスタアドレスの記述と割り込み関連の記述だけをメモしておきます。
また、モジュールのレジスタアドレスと割り込み番号が判るのであれば、ここでDevice Treeを作る必要はありません。
5. U-Bootのソースからu-boot.img と BOOT.binを作る
U-Bootの最新のリポジトリからU-Bootをビルドします。ただし、U-Bootに若干の修正を加える必要があります。詳細は「ZYBO用U-Bootを、ブート時にuEnv.txtを読むようにして、かつBOOT.binから起動できるようにする」を参照してください。
最新の U-Boot では自動的にu-boot-splから BOOT.bin を作ります。
「FPGA マガジンNo.12」や「Debian Linux on Zynq Setup Flow Version March 2016 for VIvado 2015.4」では、Zybo用のu-boot-spl を作るのに、ps7_init_gpl.{c,h}を作る方法が記載されていますが、最新のu-boot にはすでにZybo用の環境が入っているので、この工程は必要無いようです。
U-Bootのビルドには次の2つのケースがあります。
・PL周りの設定をU-Boot がすでに定義済みのものを使う場合
u-boot-spl で行うPL周りの設定を、 U-Boot がすでに定義済みのものをそのまま使うのであれば、なにもせずにboot.binは一回ビルドするだけで済みます。この場合は3.の工程は必要ありません。ビルドする場合は次のようにボードを指定してコンフィギュレーションしてください。
shell% make zynq_zybo_defconfig
shell% make
・PL周りの設定をPLのデザイン時に設定した値を使う場合
u-boot-splで行うPL周りの設定を、 PLのデザイン時に設定した値を使う場合は、3.で取り出したps7_init_gpl.{c,h}をboard/xilinx/zynq/custom_hw_platformにコピーしてビルドします。
shell% cp $(hdf)/ps7_init_gpl.* board/xilinx/zynq/custom_hw_platform
shell% make zynq_zybo_defconfig
shell% make menuconfig
shell% make
make zynq_xxxx_defconfig で一度ボードの型指定してconfigurationした後、meke menuconfig で ARM architechture ---> Use custom ps7_init provided by Xilinx tool にチェックをいれます。これによりboard/xilinx/zynq/custom_hw_platform にコピーされたps7_init_gpl.{c,h}を使って BOOT.bin がビルドされます。
PLのデザインを変更する度に BOOt.binをビルドしなければならないかもしれません。PLのデザインが異なっていてもPL側の設定が変わらないのであれば、その限りではありません。
7. Linux Kernel Source から zImageとDevice Tree Blob(.dtb)を作る
Linux Kernelのソースコードを入手してビルドします。最近のLinux はZYNQ用のカーネルもビルドできるようになっています。
ビルドすると、Linux Kernelのイメージと同時に Linux Kernel を起動するための Device Tree Blob(.dtb)も生成されます。
8. Linux Kernel Sourceから作ったDevice Tree とVivado SDKで作ったDevice Treeをマージする
7.で出来たDevice Tree Blob(.dtb) には、FPGAに実装したモジュールに関する情報が含まれていません。FPGAに実装したモジュールがLinuxとは無関係に動作するのであれば特にDevice Tree Blob に手を入れる必要はありませんが、FPGAに実装したモジュールをLinuxから制御する場合はレジスタのアドレスや割り込み番号などを Device Tree Blob に追加する必要があります。
残念ながら、今のところ、この部分を自動化することは出来ません。エディタなどを使ってマージする必要があります。
まず、7.で生成されたDevice Tree Blobを Device Tree Compiler(dtc)を使ってDevice Tree のソースコード(.dts)に戻します。
shell% dtc -I dtb -O dts -o devicetree.dts $(LINUX_SRC)/arch/arm/boot/dts/zynq-zybo.dtb
その後修正を加えたDevice TreeのソースコードをDevice Tree Compier(dtc)を使ってDevice Tree Blob(devicetree.dtb)に変換します。
shell% dtc -I dts -O dtb -o devicetree.dtb devicetree.dts
9. uEnv.txtを用意する
u-bootがブート時に実行するスクリプトを記述したファイルを用意します。ここではuEnv.txtを使いますが、u-bootには他にも専用のスクリプトファイルがあるので、そちらを使ってもかまいません。
ここではSDカードからPLのロードとLinuxをブートする場合の uEnv.txt の例を示します。SDカードの第一パーティションをFAT(VFAT)でファイルシステムを作り、1で作ったFPGAビットストリームファイル、5.で作ったBOOT.binとu-boot.img、7.で作ったzImage(Linuxカーネルイメージ)、8.で作ったdevicetree.dtb(デバイスツリーファイル)と、このuEnv.txtを置きます。SDカードの第二パーティションにはLinuxのルートディレクトリがあるものとします。
uenvcmd=fatload mmc 0 0x03000000 design.bit && fpga loadb 0 0x03000000 $filesize && fatload mmc 0 0x03000000 zImage && fatload mmc 0 0x02A00000 devicetree.dtb && bootz 0x03000000 - 0x02A00000
- fatload mmc 0 0x03000000 design.bit
FPGAビットストリームファイル(ここではdesign.bit)をメモリの0x03000000にロード - fpga loadb 0 0x03000000 $filesize
メモリの0x03000000にロードしたFPGAビットストリームをFPGAにロード、$filesize変数にはロードしたファイルのバイト数が入っている - fatload mmc 0 0x03000000 zImage
Linux Kernel のイメージファイル(zImage)をメモリの0x03000000にロード - fatload mmc 0 0x02A00000 devicetree.dtb
Device Tree Blob(ここではdevicetree.dtb)をメモリの0x02A00000にロード - bootz 0x03000000 - 0x02A00000
メモリの0x03000000にロードしたLinux Kernelと0x02A00000にロードしたDevice Tree Blob を使ってブート
10. SD Card の第1パーティション(FAT File System)にFPGAビットストリームファイル、BOOT.bin、u-boot.img、zImage、Device Tree Blob(.dtb)、uEnv.txtを置く
SD Card の第1パーティションにFATで File System を作って以下のファイルを置きます。
- 1.で作ったFPGAビットストリームファイル
- 5.で作ったBOOT.bin
- 7.で作ったLinux Kernelのイメージ(zImage)
- 8.で作ったDevice Tree Blob(.dtb)
- 9.で作ったuEnv.txt
留意点
PL周りの設定値(特にクロック)に注意
U-Boot には PS周りの設定とPL周りの設定がすでに定義されています。PS周りの設定はそのままU-Bootのものを使ってもかまいませんが、PL周りの設定に関しては注意が必要です。
・PL周りの設定をPLのデザイン時に設定した値を使う場合
PL周りの設定を PLのデザイン時に設定した値を使う場合、PLデザイン時に生成したハードウェア情報ファイル(.hdf)から設定用プログラム(ps_init_gpl.{c,h})を取り出して、u-bootのリポジトリにコピーしてBOOT.binをビルドする必要があります。
また、PL周りの設定値によっては PLのデザイン毎にBOOT.binをビルドし直す必要があります。同じ設定値で問題なければビルドし直す必要はありません。
・PL周りの設定をU-Boot がすでに定義済みのものを使う場合
PL周りの設定をU-Bootですでに定義済みのものを使う場合は、デザインフローが簡略されます。
また、PLのデザインに関わらず常に同じ BOOT.binを使うので管理が楽です。
PLクロックの設定に注意する必要があります。U-Bootではクロックの設定は次のように周波数が決め打ちされています。
クロックピン | 周波数 |
---|---|
PL_FCLK0 | 100MHz |
PL_FCLK1 | 175MHz |
PL_FCLK2 | 12.26MHz |
PL_FCLK3 | 100MHz |
PLのデザインがこれ以外のクロック設定を前提としている時は、何らの方法でクロックの設定を変更する必要があります。
PLのロードのタイミングの違い
PL(Programmable Logic)にFPGAビットストリームをロードするタイミングがFSBL版では、ステージ1ブートローダー(FSBL)が行うのに対し、U-boot-spl版 ではステージ2ブートローダー(U-Boot)が行います。従ってPLが動作可能になるのがu-boot-spl版では遅くなります。もしFPGA外部に回路があってそれをFPGAで制御する場合、起動が遅いと問題がおこるかもしれません。