はじめに
「FPGA+SoC+Linuxのブートシーケンス(ZYNQ+U-Boot-SPL編)」で、U-Boot-spl(U-Boot Second Program Loader)をステージ1ブートローダーとして Linux をブートする方法を説明しました。
その際、ちょっと問題になったのが PL 周りの設定です。
PL周りの設定をPLのデザイン時に設定した値を使う場合、PL側のデザインを変更する度に boot.bin(U-Boot-SPL) を再ビルドする必要があります(PL周りの設定が変わらない場合は除く)。
また、PL周りの設定を U-Boot がすでに定義済みのものをそのまま使う場合は、boot.bin(U-Boot-SPL)を再ビルドする必要はありませんが、PL周りの設定を変更することができません。
そこで、U-Boot の mw (memory write) コマンドを使って直接 Zynq のレジスタに書き込むことによって PL周りの設定を変える方法を説明します。
なお、Zynq のレジスタを直接操作するので、けっこう危険です。ハードウェア や PLL に詳しい人向けです。
レジスタ等の詳細は「Zynq-7000 Technical Reference Manual」を参照してください。これは Xilinx 社から UG585(User Guide 585)として公開されています。
PL周りの設定レジスタ
Zynq で PL 周りの設定が必要なレジスタは、おそらく、クロックの設定とリセット出力制御だと思います。
これらを設定するレジスタは System Level Control Registers (slcr) のグループに属しています。このうち FPGA のクロックの設定とリセット出力の制御を行うレジスタは次の通りです。
Register Name | Address | Width | Type | Description |
---|---|---|---|---|
FPGA0_CLK_CTRL | 0xF8000170 | 32 | rw | PL Clock 0 Output control |
FPGA1_CLK_CTRL | 0xF8000180 | 32 | rw | PL Clock 1 Output control |
FPGA2_CLK_CTRL | 0xF8000190 | 32 | rw | PL Clock 2 Output control |
FPGA3_CLK_CTRL | 0xF80001A0 | 32 | rw | PL Clock 3 Output control |
FPGA_RST_CTRL | 0xF8000240 | 32 | rw | FPGA Software Reset Control |
FPGA[0-3]_CLK_CTRL
- Name : FPGA[0-3]_CLK_CTRL
- Address : 0xF8000170, 0xF8000180, 0xF8000190, 0xF80001A0
- Width : 32
- Access Type : rw(Read and Write)
- Description : PL Clock [0-3] Output control
Field Name | Bits | Type | Description |
---|---|---|---|
DIVISOR1 | 25:20 | rw | Provides the divisor used to divide the source clock to generate the required generated clock frequency. Second cascade divide |
DIVISOR0 | 13:08 | rw | Provides the divisor used to divide the source clock to generate the required generated clock frequency. First cascade divide |
SRCSEL | 05:04 | rw | Select the source used to generate the clock: 0x: Source for generated clock is IO PLL. 10: Source for generated clock is ARM PLL. 11: Source for generated clock is DDR PLL. |
FPGA_RST_CTRL
- Name : FPGA_RST_CTRL
- Address : 0xF8000240
- Width : 32
- Access Type : rw(Read and Write)
- Description : FPGA Software Reset Control
Field Name | Bits | Type | Description |
---|---|---|---|
FPGA3_OUT_RST | 3 | rw | PL Reset 3 (FCLKRESETN3 output signal). Refer to the PS7 wrapper in EDK for possible signal inversion. Logic level on the FCLKRESETN3 signal: 0: De-assert reset (High logic level). 1: Assert Reset (Low logic state) |
FPGA2_OUT_RST | 2 | rw | PL Reset 2 (FCLKRESETN2 output signal). Refer to the PS7 wrapper in EDK for possible signal inversion. Logic level on the FCLKRESETN2 signal: 0: De-assert reset (High logic level). 1: Assert Reset (Low logic state) |
FPGA1_OUT_RST | 1 | rw | PL Reset 1 (FCLKRESETN1 output signal). Refer to the PS7 wrapper in EDK for possible signal inversion. Logic level on the FCLKRESETN1 signal: 0: De-assert reset (High logic level). 1: Assert Reset (Low logic state) |
FPGA0_OUT_RST | 0 | rw | PL Reset 0 (FCLKRESETN0 output signal). Refer to the PS7 wrapper in EDK for possible signal inversion. Logic level on the FCLKRESETN0 signal: 0: De-assert reset (High logic level). 1: Assert Reset (Low logic state) |
どのような値を設定すれば良いか
U-Boot が定義済みの値(ZYBOの場合)
最新の U-Boot を Zynq 用にビルドした場合、設定されているクロックの周波数を U-Boot の clk dump コマンドで見ることができます。
Zynq> clk dump
clk frequency
armpll 1300000000
ddrpll 1050000000
iopll 1000000000
cpu_6or4x 650000000
cpu_3or2x 325000000
cpu_2x 216666666
cpu_1x 108333333
ddr_2x 350000000
ddr_3x 525000000
dci 10096154
lqspi 200000000
smc 21666667
pcap 200000000
gem0 125000000
gem1 16666667
fclk0 100000000
fclk1 175000000
fclk2 12264151
fclk3 100000000
sdio0 50000000
sdio1 50000000
uart0 50000000
uart1 50000000
spi0 15873016
spi1 15873016
usb0_aper 108333333
usb1_aper 108333333
gem0_aper 108333333
gem1_aper 108333333
sdio0_aper 108333333
sdio1_aper 108333333
spi0_aper 108333333
spi1_aper 108333333
can0_aper 108333333
can1_aper 108333333
i2c0_aper 108333333
i2c1_aper 108333333
uart0_aper 108333333
uart1_aper 108333333
gpio_aper 108333333
lqspi_aper 108333333
smc_aper 108333333
dbg_trc 66666667
dbg_apb 66666667
これによるとクロックの周波数は次の様になっているようです。
clk | frequency |
---|---|
ARMPLL | 1300MHz |
DDRPLL | 1050MHz |
IOPLL | 1000MHz |
FPGA0 | 100MHz |
FPGA1 | 175MHz |
FPGA2 | 123MHz |
FPGA3 | 100MHz |
次に U-Boot がレジスタにどのような値を設定しているか見ると、次の様に設定しています。
Register Name | Address | U-Boot Def Value | Description |
---|---|---|---|
FPGA0_CLK_CTRL | 0xF8000170 | 0x00100A00 | PL Clock 0 Output control |
FPGA1_CLK_CTRL | 0xF8000180 | 0x00100630 | PL Clock 1 Output control |
FPGA2_CLK_CTRL | 0xF8000190 | 0x00203520 | PL Clock 2 Output control |
FPGA3_CLK_CTRL | 0xF80001A0 | 0x00100A00 | PL Clock 3 Output control |
FPGA_RST_CTRL | 0xF8000240 | 0x00000000 | FPGA Software Reset Control |
FPGA0_CLK_CTRL では、ソースの PLL に IOPLL、DIVISOR0 に 10、DIVISOR1 に 1 を設定しています。
つまり、PL Clock 0 の周波数は (1GHz(=IOPLLの周波数)÷10(=DIVISOR0の値))÷(1(=DIVISOR1の値))で100MHzになります。
周波数に対応した設定値
例えば PL Clock 0 に 125MHz を設定したければ、FPGA0_CLK_CTRL に 0x00100800(SRCSEL=IOPLL、DIVISOR0=8、DIVISOR1=1)を書き込みます。
残念ながら、Zynq のこの PLL の仕様上、ソースの周波数の整数分の1の周波数しか設定できません。
例えば ZYBO の場合、PL CLock 0 に 133MHz を設定しようと思っても、無理です。一番近いのは、131.25MHz=((1050MHz(=DDRPLLの周波数)÷8)÷1)で、FPGA0_CLK_CTRL に 0x00100803(SRCSEL=DDRPLL、DIVISOR0=8、DIVISOR1=1)を書き込むことでしょう。
その他のボードの場合
ここでは ZYBO の例をあげましたが、ARMPLL、DDRPLL、IOPLLの周波数はボードによって異なります。
参考までに、判る範囲で調べて見たところ次の様になりました。
Board Name | Input Freq | ARMPLL Mult | ARMPLL Freq | DDRPLL Mult | DDRPLL Freq | IOPLL Mult | IOPLL Freq |
---|---|---|---|---|---|---|---|
ZC706 | 33.3333MHz | 40 | 1333.333MHz | 32 | 1066.667MHz | 30 | 1000MHz |
ZC702 | 33.3333MHz | 40 | 1333.333MHz | 32 | 1066.667MHz | 30 | 1000MHz |
ZedBoard | 33.3333MHz | 40 | 1333.333MHz | 32 | 1066.667MHz | 30 | 1000MHz |
ZYBO | 50MHz | 26 | 1300.000MHz | 21 | 1050.000MHz | 20 | 1000MHz |
レジスタへの書き込み
素直に mw(memory write) コマンドにアドレスとデータを指定して書き込みたいところですが、残念ながら Zynq の場合はそういうわけにはいきません。というのも、Zynq の System Level Control Registers(slcr) は、万が一プログラムが暴走してもレジスタに変な値が書き込まれないようにライトプロテクションがかけられているからです。
ライトプロテクションを制御するレジスタは次の通りです。
Register Name | Address | Width | Type | Description |
---|---|---|---|---|
SLCR_LOCK | 0xF8000004 | 32 | wo | SLCR Write Protection Lock |
SLCR_UNLOCK | 0xF8000008 | 32 | wo | SLCR Write Protection UnLock |
SLCR_LOCK
- Name : SLCR_LOCK
- Address : 0xF8000004
- Width : 32
- Access Type : wo(Write Only)
- Description : SLCR Write Protection Lock
Field Name | Bits | Type | Description |
---|---|---|---|
LOCK_KEY | 15:0 | wo | Write the lock key, 0x767B, to write protect the slcr registers: all slcr registers, 0xF800_0000 to 0xF800_0B74, are write protected until the unlock key is written to the SLCR_UNLOCK register. A read of this register returns zero. |
SLCR_UNLOCK
- Name : SLCR_UNLOCK
- Address : 0xF8000008
- Width : 32
- Access Type : wo(Write Only)
- Description : SLCR Write Protection Unlock
Field Name | Bits | Type | Description |
---|---|---|---|
UNLOCK_KEY | 15:0 | wo | Write the unlock key, 0xDF0D, to enable writes to the slcr registers. All slcr registers, 0xF800_0000 to 0xF800_0B74, are writeable until locked using the SLCR_LOCK register. A read of this register returns zero. |
書き込みの手順
つまり、FPGA0_CLK_CTRL や FPGA_RST_CTRL に値を書き込む前に SLCR_UNLOCK(0xF8000008) に 0xDF0D を書いてライトプロテクションを無効にする必要があります。またレジスタへの書き込みが終わったら SLCR_LOCK(0xF8000004) に 0x767B を書いてライトプロテクションを有効にします。
uEnv.txt の例
U-Boot がブート時に実行するスクリプトを記述したファイルを用意します。ここでは uEnv.txt を使いますが、U-Boot には他にも専用のスクリプトファイルがあるので、そちらを使ってもかまいません。
slcr_unlock_cmd=mw.l 0xF8000008 0xDF0D
slcr_lock_cmd=mw.l 0xF8000004 0x767B
fpga_set_cmd=run slcr_unlock_cmd && mw.l 0xF8000170 0x00101400 && run slcr_lock_cmd
fpga_load_cmd=fatload mmc 0 0x03000000 design.bit && fpga loadb 0 0x03000000 $filesize
linux_load_cmd=fatload mmc 0 0x03000000 zImage && fatload mmc 0 0x02A00000 devicetree.dtb
linux_boot_cmd=bootz 0x03000000 - 0x02A00000
uenvcmd=run fpga_load_cmd && run fpga_set_cmd && run linux_load_cmd && run linux_boot_cmd
- slcr_unlock_cmd=mw.l 0xF8000008 0xDF0D
System Levec Control Registers のライトプロテクションを解除するコマンドを定義。 - slcr_lock_cmd=mw.l 0xF8000004 0x767B
System Levec Control Registers のライトプロテクションをセットするコマンドを定義。 - fpga_set_cmd=run slcr_unlock_cmd && mw.l 0xF8000170 0x00101400 && run slcr_lock_cmd
PL Clock をセットするコマンドを定義。
ここでは FPGA0_CLK_CTRL に 50MHz=(SRCSEL=IOPLL(1GHz),DIVISOR0=20,DIVISOR1=1)をセットしています。 - fpga_load_cmd=fatload mmc 0 0x03000000 design.bit && fpga loadb 0 0x03000000 $filesize
FPGAビットストリームファイル(ここではdesign.bit)を FPGA にロードするコマンドを定義。 - linux_load_cmd=fatload mmc 0 0x03000000 zImage && fatload mmc 0 0x02A00000 devicetree.dtb
Linux Kernelのイメージファイル(zImage)とDevice Tree Blob(devicetree.dtb)をメモリにロードするコマンドを定義。 - linux_boot_cmd=bootz 0x03000000 - 0x02A00000
メモリにロードした Linux Kernel と Device Tree Blob を使ってブートするコマンドを定義。 - uenvcmd=run fpga_load_cmd && run fpga_set_cmd && run linux_load_cmd && run linux_boot_cmd
FPGAのロード、FPGAの設定、Linux のロードとブートを行うコマンドを定義。
ブートの際、uEnv.txt に uenvcmd があると自動的にこのコマンドが実行されます。
なお、uenvcmd がある程度の文字数(256?)以上だとブートに失敗することがあります。