Zephyr OS を Cyclone V SoC で走らせる

# はじめに
この11月の ["インテル® FPGA テクノロジー・デイ 2022"](https://webinar.intel.com/IFTD2022_JP_REG)  で 小・中規模のレンジをカバーする Intel® Agilex™ FPGA の新シリーズ(開発コード Sundance Mesa および D シリーズ) の紹介があり、それらに搭載される HPS には、Arm Cortex-A76 と Arm Cortex-A55 の Arm DynamIQ technology を使った CPU クラスタ が採用されるとの発表がありました[^1]。

[^1]: 新 HPS のブロック図はこちらのホワイト・ペーパーにあります。https://www.intel.co.jp/content/www/jp/ja/products/docs/programmable/agilex-d-series-fpga-whitepaper.html

久々の 小・中規模 Intel® SoC FPGA の発表にワクワクしていたら、Zephyr OS のサポートに今現役の Cyclone® V SoC FPGA が追加されたとの情報を耳にしました。リリースされている Cortex-A53 を搭載の Intel® Agilex™ F シリーズ SoC FPGA も 昨年 Zephyr OS でサポートされており、新 Intel® Agilex™ シリーズ SoC FPGA も Zephyr OS のサポートリストに加わることを期待しつつ、ここはひとつ Zephyr OS も触ってみるか、というモチベーションでこの記事を書いています。


（前置きが長くなりましたが）
Zephyr OS の サポートボード に[ Intel® Cyclone® V SoC Development Kit （Intel 製の開発キット）のページ](https://docs.zephyrproject.org/latest/boards/arm/cyclonev_socdk/doc/index.html "Intel® Cyclone® V SoC Development Kit") が追加されています。こちらを使って、ビルドの方法と実ボードでの動作確認を行いました。
動作確認のボードは、Terasic の DE10-Nano board を使いましたので、異なる Cyclone® V SoC FPGA ボードへのポーティング（コード修正）方法も書いています。出来上がったサンプルアプリケーションの実行方法としては、
\- RiscFree* IDE for Intel® FPGAs(以下 RiscFree) の debugger を使って GUI デバッグ実行する[^7]方法と、
\- Zepher 標準の west flash コマンドを使う方法、
\- OpenOCD+gdb のスクリプトで実行する方法
\- SD card から boot し実行する方法 
の4種類の方法を行ってみました。
また、最近のバージョンの bootloader (u-boot-spl) と一緒に使う際の注意点なども書いておきます。

[^7]: こちらの記事[ "RiscFree IDE(０円デバッガ) で SoC FPGAをデバッグ" ](https://qiita.com/eofz/items/92328e9f1dbfe8609582) では、bootloader を起動したのみでしたが、本記事で OS アプリケーションも走らせる 所まで行います。

# Zephyr OS とは
Linux Foundation でサポートされている、Apache 2.0 ライセンス(GPL よりは緩い縛りのライセンス)の Real Time OS となってます。[Zephyr OS ドキュメントサイト](https://docs.zephyrproject.org/latest/)
多くの CPU arch と、多くのボードがサポートされていて、サンプルも沢山あります。マルチコア(smp)もサポートしています。あと、Qemu を使って、実ボードで実行する前に、開発 PC 上で動作確認できる仕組み(qemu_x86 board) が提供されているというのもウレシイ点かと思います。
（さらに詳細は、Web サーチで色々出て来ますので、そちらをご覧いただければ思います。）

# 開発環境の準備
基本は、公式ドキュメントの ["Getting Started Guide"](https://docs.zephyrproject.org/latest/develop/getting_started/index.html) に従うのが王道かと思います（Linux, MacOS, Windows でのそれぞれの手順が書かれています）。ここでは、Zephyr 開発用の Docker image が公開されているので、こちらを利用します。ちなみに、Host PC は Linux Ubuntu 18.04 を使ってこの記事を書いています。

こちら [Zephyr Docker Images](https://github.com/zephyrproject-rtos/docker-image) のドキュメントに沿って作業していきます。このドキュメントでは、GitHub Container Registry (```ghcr.io```) もしくは、DockerHub (```docker.io```) にある image を使う方法と、[Zephyr Docker Images](https://github.com/zephyrproject-rtos/docker-image) に掲載されている Dockerfile.user を使って、Docker image を自分で作る方法が記載されています。以下は、DockerHub に置いてある image を使わせてもらう方法です。
まず、この Docker image を使い docker run コマンドで container を起動します。ここでは、Zephyr のソースコードを置く作業フォルダとして```$HOME/Zephyr_workdir/zephyrproject``` を Host 側で作っておき、container 側の /workdir フォルダにマッピングしています。
```bash
$ mkdir -p $HOME/Zephyr_workdir/zephyrproject
$ docker run -ti --rm -v $HOME/Zephyr_workdir/zephyrproject:/workdir \
           --entrypoint /bin/bash docker.io/zephyrprojectrtos/zephyr-build:latest
```

<details><summary>上記 docker run コマンドで 私が追加したオプションへのコメント</summary>

```
"--rm" : container での作業が終了し exit したときに、container が残り続けない様に付加してます。
"--entrypoint /bin/bash": デフォルトの entrypoint.sh では、framebuffer を使った zephyr アプリケーションの動作確認もできるよう xvfb/vnc を起動していますが、今回は不要なので単純に bash を entrypoint として指定しています。なおこちらは、ssh termimal から起動した場合に Xwindow 関連のエラーが表示されるのを防ぐ、という意味もあります。
```
</details>

その後、container 内で west コマンドにより Zephyr のソースコードをダウンロードします。
```bash:container terminal にて
user@28cfe9c1640b:/workdir$ west init .
user@28cfe9c1640b:/workdir$ west update
```
ネットワーク環境によっては、一回の west update のコマンド実行だけでは ```ERROR: update failed for project xxxx``` といったエラーが出る場合がありますが、何度か west update コマンドを繰り返すとすべての project がダウンロードでき、エラーが出なくなると思います。

# qemu_x86 用のビルドと実行
qemu_x86 ボードをターゲットボードとして、付属の hello_world サンプルをビルドし、実行を確認してみます。qemu_x86 ボードは、Qemu を使い、[X86 CPU が載ったボードをエミュレート](https://docs.zephyrproject.org/2.6.0/boards/x86/qemu_x86/doc/index.html "X86 Emulation (QEMU)")したものになっています。このボードをターゲットとして Zephyr アプリケーションをビルドすれば、ビルドした PC 上ですぐにコードの動作確認ができるという、便利な環境が提供されています。

hello_world サンプルコードは、/workdir/zephyr/samples/hello_world/ にあるものを使います（Host 環境の path では、$HOME/Zephyr_workdir/zephyrproject/zephyr/samples/hello_world/）。src/main.c は以下の通り、至ってシンプルな hello_world のコードとなっています（先頭のコメント部分は除いてます）。
```c:hello_world/src/main.c 抜粋
#include <zephyr/kernel.h>

void main(void)
{
        printk("Hello World! %s\n", CONFIG_BOARD);
}
```

zephyr フォルダに ```cd``` し、 ```west build -p -b qemu_x86 samples/hello_world``` [^5]コマンドを実行します。
[^5]: "-b qemu_x86" でターゲットとするボードを指定し、最後の "samples/hello_world" でビルドするアプリケーションのフォルダを指定しています。"-p" は、前回の build の内容を削除してから build する指示。

```:container terminal にて
user@28cfe9c1640b:/workdir$ cd zephyr
user@28cfe9c1640b:/workdir/zephyr$ west build -p -b qemu_x86 samples/hello_world
-- west build: generating a build system
Loading Zephyr default modules (Zephyr base).
....
....
[133/133] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
             RAM:       53280 B         3 MB      1.69%
        IDT_LIST:          0 GB         2 KB      0.00%
user@28cfe9c1640b:/workdir/zephyr$ 
```
この様に zephyr.elf が生成された旨の表示がされます（エラー表示なく west build コマンドが終了するはずです）。ビルドで生成されたファイルは、build/ フォルダ下に置かれていて、実行形式の ELF ファイルは、build/zepher/zephyr.elf です。

続いて実行してみます。```west build -t run```[^2]コマンドを使います。
[^2]: qemu を介して build/zepher/zephyr.elf が実行されます.
```:container terminal にて
user@28cfe9c1640b:/workdir/zephyr$ west build -t run
-- west build: running target run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: qemu32,+nx,+pae
SeaBIOS (version zephyr-v1.0.0-0-g31d4e0e-dirty-20200714_234759-fv-az50-zephyr)
Booting from ROM..
*** Booting Zephyr OS build zephyr-v3.2.0-2489-g87e63247c94e ***
Hello World! qemu_x86 
```
無事に "Hello World! <ボード名>" が表示され、動作確認ができました(CTRL+a, x で終了させます)。Docker Image を使うことで、簡単にビルド環境が入手でき、サンプルの実行までがあっさりと確認できました。

続いて、目的の Cyclone® V SoC FPGA ボード用の作業に移りましょう。

# DE10-Nano で動かす

まずは、デフォルトの Zephyr コードでサポートされている Cyclone® V SoC development kit（Intel 製の開発キット）向けのビルドを確認してみます。ダウンロードしてきたコードに何も手を加えず、[Zephyr の Cyclone® V SoC devkit page](https://docs.zephyrproject.org/latest/boards/arm/cyclonev_socdk/doc/index.html) に従いビルド行ってみます。ビルドコマンドは、-b オプションの引数を qemu_x86 から cyclonev_socdk に変えるだけです。```west build -p -b cyclonev_socdk samples/hello_world```
```:container terminal にて
user@28cfe9c1640b:/workdir/zephyr$ west build -p -b cyclonev_socdk samples/hello_world
-- west build: generating a build system
Loading Zephyr default modules (Zephyr base).
....
....
[140/140] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:          0 GB         0 GB
             RAM:     1432036 B   1048560 KB      0.13%
           OCRAM:          0 GB        64 KB      0.00%
        IDT_LIST:          0 GB         2 KB      0.00%
user@28cfe9c1640b:/workdir/zephyr$ 
```
上記のように、Cyclone® V SoC development kit (Intel 製の開発キット) 向けのビルドが、無事完了することが確認できるものと思います。
なお、Cyclone® V SoC development kit 用の設定ファイル（Zephyr のドキュメントでは board directory と呼んでいます）は、boards/arm/cyclonev_socdk/ に在ります。

## コード変更
必要なコードの変更を行って行きます。

### ボードの差分 (ポーティング)
目的のボード DE10-Nano で動かせるようにコードを変更します。本来ならば、新たに DE10-Nano 用の board directory を boards/arm/cyclonev_socdk/ に倣って生成し（例えば boards/arm/cyclonev_de10nano という名前で作成し）その下のファイルに修正を加えていくという作業となるのですが、ここでは「必要最小限の変更」ということで、boards/arm/cyclonev_socdk/ のファイルを変更する、ということ進めさせていただきます[^3]。 

[^3]: board directory を追加する作業の詳細は、[Board Porting Guide](https://docs.zephyrproject.org/latest/hardware/porting/board_porting.html) に書かれています。

boards/arm/cyclonev_socdk/ の構成は、以下のようになっています。

```
boards/arm/cyclonev_socdk
├── doc フォルダ
├── support フォルダ
├── board.cmake
├── cyclonev_socdk_defconfig
├── cyclonev_socdk.dts　　#デバイスツリー
├── cyclonev_socdk.yaml
├── Kconfig.board
└── Kconfig.defconfig
```

変更が必要な箇所は、主にボードの HW 構成の差分となりますが、Cyclone® V SoC development kit と DE10-Nano の差はそれほどなく、２つのファイルの修正
- ```cyconev_socdk.dts``` : Periperal 設定の修正（GPIO port の修正と搭載されていない peripheral の削除）
- ```Kconfig.defconfig``` : ボード名を "cyclonev_de10nano" に変更と、 OS タイマー周波数[^8]の設定を 200MHz に変更 

でOKでした。以下にそれぞれのファイルの修正例を置いておきます（変更点が分かりやすいように diff 出力としてます）。
[^8]: MP Core 内の Global Timer が使われています。CPU clock の 1/4 で動作。デフォルトは、soc/arm/intel_socfpga_std/cyclonev/Kconfig.defconfig で (925/4) MHz となっています。

<details><summary>cyconev_socdk.dts の修正例(diff)</summary>

```diff_tcl
--- a/boards/arm/cyclonev_socdk/cyclonev_socdk.dts
+++ b/boards/arm/cyclonev_socdk/cyclonev_socdk.dts
@@ -20,27 +20,13 @@
 	};
 
 	aliases {
-	/* Giving different names to the LEDs connected to the HPS side of the chip */
 		led0 = &hps0;
-		led1 = &hps1;
-		led2 = &hps2;
-		led3 = &hps3;
-		eeprom-0 = &eeprom;
 	};
 
 	leds {
 		compatible = "gpio-leds";
-		hps3: hps_led_3 {
-			gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
-		};
-		hps2: hps_led_2 {
-			gpios = <&gpio1 13 GPIO_ACTIVE_LOW>;
-		};
-		hps1: hps_led_1 {
-			gpios = <&gpio1 14 GPIO_ACTIVE_LOW>;
-		};
 		hps0: hps_led_0 {
-			gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
+			gpios = <&gpio1 24 GPIO_ACTIVE_HIGH>;
 		};
 	};
 
@@ -55,24 +41,7 @@
 			status = "okay";
 		};
 
-		i2c0: i2c@ffc04000 {
-			status = "okay";
-
-			eeprom: eeprom@51 {
-				compatible = "atmel,at24";
-				status = "okay";
-				reg = <0x51>;
-				size = <4096>;
-				address-width = <8>;
-				timeout = <25>;
-				pagesize = <32>;
-			};
 
-			ds3231: rtc@68 {
-				compatible = "maxim,ds3231";
-				reg = <0x68>;
-			};
-		};
 	};
 
 	/* Configuring Zephyr mandatory devices */
```
</details>

<details><summary>Kconfig.defconfig の修正例(diff)</summary>

```diff_tcl
--- a/boards/arm/cyclonev_socdk/Kconfig.defconfig
+++ b/boards/arm/cyclonev_socdk/Kconfig.defconfig
@@ -6,9 +6,13 @@
 if BOARD_CVSXDEVKIT
 
 config BOARD
-	default "cyclonev_socdk"
+	default "cyclonev_de10nano"
 depends on BOARD_CVSXDEVKIT
 
+config SYS_CLOCK_HW_CYCLES_PER_SEC
+	# (CPU Clock Freq.)/4 
+	default 200000000
+
 if I2C_DW
 config I2C_DW_CLOCK_SPEED
 	default 200
```
</details>

### 新 bootloader 対応
[Zephyr の Cyclone® V SoC devkit page](https://docs.zephyrproject.org/latest/boards/arm/cyclonev_socdk/doc/index.html) で動作確認に使っている bootloader は、バージョン 2013.01 の u-boot-spl を使っています。ですが、この u-boot-spl は一世代前のバージョンなので、ここでは、最近の u-boot-spl （2022.04バージョン[^4]）を使ってみたいと思います。これにより、この後の章で行う SD card からの起動においても新しいバージョンのU-Boot を使うことができます。

[^4]: このバージョンから、Github の U-Boot のソースと、Quarus Project のコンパイルで生成される hps_isw_handoff だけで bootloader のビルドができるようになりました(以前の SoCEDS というツールは必要なし)。 u-boot-spl の生成方法については、Rocketboards.org の [Building Bootloader for Cyclone V and Arria 10](https://www.rocketboards.org/foswiki/Documentation/BuildingBootloaderCycloneVAndArria10) を参照してださい。

以前の2013.01バージョンの u-boot-spl と、最近のバージョン（2020.xx 以降、デバイスツリーを使うバージョン）の u-boot-spl では Peripheral のリセット解除の動作が異なるので注意が必要です。

以前の u-boot-spl 2013.01 では、HW 設計の Platform designer の HPS_IP 設定で enable にされている Peripheral は全てリセット解除される仕様になっていました。
一方、最近の u-boot-spl では、u-boot-spl で使用する Peripheral (uart, sd/mmc, wdt, timer) 以外は全てリセットされたまま、という仕様になっています。各 Peripheral のリセット解除は、その Peripheral を使う OS/Application が行うという、より secure な考え方によるものと思います。U-Boot, Linux は、その様になっていますし、そして Zephyr もその思想に対応できるようになっています。

しかし、現在の Cyclone® V SoC FPGA への Zephyr ポーティングでは、以前の2013.01 u-boot-spl でのリセット解除に頼ったコードとなっており、SoC の初期化ルーチンでは、Peripheral のリセット解除を行うようになっていません。

ということで、使用する Peripheral のリセット解除をするコードを追加してやる必要があります。「Reset driver と Device-Tee で記述する.」というのが正式な方法だとは思いますが、ここでは、「とりあえずの動作確認として、適当な初期化ルーチンに書き加える」という手で行かせて頂きます[^6]。
[^6]: とはいえ、このままというのもなんなので、正式な方法を Zephyr repo に入れてもらえるようチャレンジしてみようかとも思ったりしてます。

Peripheral のリセット解除は、HPS Reset Manager の permodrst レジスタ（アドレス 0xffd05014 ）の該当ビットを "0" に設定してやる、です。この操作を追加する場所ですが、Cyclone® V SoC FPGA 用の初期化ルーチンを記述する、```soc/arm/intel_socfpga_std/cyclonev/soc.c``` の ```soc_intel_cyclonev_init()``` 関数内としました。 
リセット解除が必要な Peripheral ですが、今回、試す blinky サンプルデザインは、GPIO1 に接続された LED を点滅させるものとなっています。GPIO1 のリセット解除をしてやります[^9]。

また、bootloader の設定によっては、WatchDog Timer 0 (wdt0) が有効になったままになっている場合があるので、どんな場合でも Zepher アプリが動作するよう WatchDog Timer 0 の停止操作も行っておきます。WatchDog Timer 0 を停止させるには、permodrst レジスタの wdt0 bit (bit-6) を "1" にします。

これらの操作を追加したコード例を以下に置きます。

[^9]: リセット解除が必要な Peripheral ですが、基本は、デバイスツリーで ```status = "okay"``` となっているものをリセット解除するものとなります。

<details><summary>soc/arm/intel_socfpga_std/cyclonev/soc.c の修正例(diff)</summary>

```diff_cpp
--- a/soc/arm/intel_socfpga_std/cyclonev/soc.c
+++ b/soc/arm/intel_socfpga_std/cyclonev/soc.c
@@ -15,6 +15,7 @@
 #include <zephyr/arch/arm/aarch32/cortex_a_r/cmsis.h>
 #include <zephyr/arch/arm/aarch32/nmi.h>
 #include "soc.h"
+#include <zephyr/arch/cpu.h>
 
 void arch_reserved_pages_update(void)
 {
@@ -79,6 +80,11 @@ static int soc_intel_cyclonev_init(const struct device *arg)
 	sctlr &= ~SCTLR_A_Msk;
 	__set_SCTLR(sctlr);
 	__set_VBAR(0);
+	/* Manage peripheral resets with Permodrst Reg.(0xffd05014) */
+	int v = sys_read32(0xffd05014); /* Read the current value of Permodrst Reg. */
+	v |= (1<<6);   /* Assert Watch Dog timer 0 RESET*/
+	v &= ~(1<<26); /* Release GPIO1 RESET */
+	sys_write32(v , 0xffd05014); /* Write the new value to Permodrst Reg. */
 	return 0;
 }

```
</details>


## DE10-Nano 用のビルド
修正したコードで２種類のサンプルをビルドをしてみます。どちらのサンプルでもエラー無く zephyr.elf が生成されることを確認しましょう。
- hello_world サンプル
container terminal にて ```west build -p -b cyclonev_socdk samples/hello_world``` を実行します。エラー無くビルドが終了し、build/zephyr/zephyr.elf が新たに生成されることを確認します。

- blinky(LED 点滅) サンプル
container terminal にて ```west build -p -b cyclonev_socdk samples/basic/blinky``` を実行します（blinky サンプルは、samples/basic/blinky にあります）。同じく、build/zephyr/zephyr.elf が新たに生成されることを確認します。

※ ビルドするサンプルデザインが異なっても、生成されるサンプルアプリケーションの elf ファイルは常に zephyr.elf という名前です。

## サンプルアプリの実行
いよいよ、実機での実行確認です。以下の４種類で行ってみます。
- RiscFree debugger を使って .elf をダウンロードし、実行する方法
- Zepher 標準の west flash コマンドを使う方法
- Intel® Quartus® software 付属の OpenOCD + gdb を使い、スクリプトで実行する方法
- SD card から boot し、U-Boot を介して自動実行する方法

事前準備として、Rocketboards.org の [Building Bootloader for Cyclone V and Arria 10](https://www.rocketboards.org/foswiki/Documentation/BuildingBootloaderCycloneVAndArria10) を参照し、 u-boot-spl がビルドされているものとします。
また、必要に応じて Host PC と DE10-Nano ボードとの間で USB-Blaster 用とSerial terminal 用の USB cable 接続をしておきます。

### RiscFree から実行

RiscFree で bootloader (u-boot-spl) をダウンロード＆実行した後、zephyr.elf をダウンロード＆実行するという手順となります。
- u-boot-spl が実行できる設定の作成
RiscFree の設定は、まず [こちらの記事](https://qiita.com/eofz/items/92328e9f1dbfe8609582 "RiscFree IDE(０円デバッガ) で SoC FPGAをデバッグ") に沿って、u-boot-spl が実行できる設定を作成します。そして u-boot-spl の実行を行い、spl_boot_device 関数の先頭で実行が停止している状態になることを確認してください。u-boot-spl の実行ができる設定になっていることが確認できたら、debugger 接続は一旦切ります（debugger 切断後にボードの電源を Off/On してボードをリセットをしておくことをお勧めします。）
- zephyr.elf のコピー
ビルドした zephyr.elf を u-boot-spl と同じフォルダにコピーしておきます。ここでは、samples/hello_world をビルドした zephyr.elf を使った場合を書きます。
- gdb スクリプトファイル作成
- 以下のテキストファイルを my_gdbinit.gdb という名前で作成し、これも u-boot-spl ファイルと同じフォルダにセーブしてください。zephyr.elf をダウンロードし、main() の先頭で実行を停止する ```zephyr```コマンドを定義したスクリプトとなっています。
```tcl:my_gdbinit.gdb
define zephyr_load
  delete breakpoints
  restore zephyr.elf
  symbol-file -readnow zephyr.elf
end

define zephyr
  zephyr_load
  thbreak main
  jump z_arm_reset
end
```
- Debugger 設定
RiscFree IDE の Debugger Configurations の Startup タブを開き、下図のように、"Run/Restart Commands" に ```source my_gdbinit.gdb``` の一行を加え、起動時にこのスクリプトを読み込むようにします。Apply ボタンを押して設定を保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175992/b11bc1dd-3c39-c5d4-6a44-caa457f6e29a.png)

以上で一通りの設定は終わりました。Debug ボタンを押して、debugger を開始します。接続が無事に完了すると、再び spl_boot_device 関数の先頭で実行が停止している状態になります(DE10nano のシリアルターミナルでは、u-boot-spl からの起動メッセージが一行プリントされているはずです）。Debugger Console をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175992/60ced8a3-f3eb-1e28-0d67-b8e8b46abc16.png)

プロンプトは出ていませんが、Debugger Console ウインドウ内で ```zephyr``` [enter] と入力します。これにより、my_gdbinit.gdb で定義した zephyr コマンドを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175992/796d71ea-d974-a15f-6b48-fc26e16e92b3.png)
すると、zephyr.elf のダウンロードが行われ、zephyr サンプルコード hello_world の main 関数の先頭で実行が停止するはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175992/5a25921b-8d41-51a5-e9ec-0c58070a7be4.png)
この時、Serial Terminal では、Zephyr からの起動メッセージが一行表示されるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175992/1778cd73-9a4f-e8a2-af9b-6fba7f8e078d.png)

デバッガの Resume(F8) ボタンを押すと、hello_world が実行され、Serial Terminal に print 文によるメッセージが出るはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175992/61401aa3-5bba-1ff1-b2c4-3b09021d9601.png)
ボード名が、"cyclonev_**de10nano**" になっていることも確認してください。

### west flash コマンドで実行
Cyclon V SoC development kit の board directroy では、west flash コマンドで zephyr.elf の実行ができるようになっています（container からの実行も可能ですが、注意が必要[^11]です）。使用する u-boot-spl を DE10-Nano 用に変更すれば、DE10-Nano でも使えます。ただし、現状のコード(gdb 用のスクリプト)は u-boot-spl が バージョン 2013.01 の場合のみに動作します。
今回使った、最近のバージョン(dtb を含む U-Boot バージョン)の u-boot-spl を使った場合に west flash コマンドが使えるようにする方法は以下となります。
\- 生成した u-boot-spl だけでなく u-boot-spl-dtb.bin ファイルも、boards/arm/cyclonev_socdk/support/ フォルダにコピーする。
\- boards/arm/cyclonev_socdk/support/preloader_dl_cmd.txt を以下の様に修正する。

[^11]: まず、Host PC に最近 (v22.x 以降) の Intel® Quartus® Prime Edition もしくは Intel® Quartus® Prime Edition Programmer and Tools (Pro もしくは Std) がインストールしてあり、```jtagconfig``` で USB Blaster II が認識されている必要があります。その状態で、container を起動します。container からの USB device アクセスが必要なので ```docker run ...``` には --privileged を追加する必要があります。container 起動中に、ボードの電源 off/on をすると、container を起動し直さないと USB Blaster II が認識されませんので、CPU の reset が必要な時は、リセットボタンで行うのが良いと思います。

<details><summary>boards/arm/cyclonev_socdk/support/preloader_dl_cmd.txt の修正例(diff)</summary>

```diff_tcl
--- a/boards/arm/cyclonev_socdk/support/preloader_dl_cmd.txt
+++ b/boards/arm/cyclonev_socdk/support/preloader_dl_cmd.txt
@@ -5,5 +5,6 @@

 restore boards/arm/cyclonev_socdk/support/u-boot-spl
 symbol-file -readnow boards/arm/cyclonev_socdk/support/u-boot-spl
+restore boards/arm/cyclonev_socdk/support/u-boot-spl-dtb.bin binary 0xffff0000
 thbreak spl_boot_device
 jump _start
```
</details>


以上の修正により ```west flash``` の実行で 新 u-boot-spl と zephyr.elf のダウンロード＆実行が可能です。 
```:container terminal にて west flash の実行例
user@26299df982bd:/workdir/zephyr$ west flash
-- west flash: rebuilding
...
Info : dropped 'gdb' connection
shutdown command invoked
user@26299df982bd:/workdir/zephyr
```

### OpenOCD+gdb のスクリプトで実行
前記の "RiscFree から実行" は、RiscFree の GUI でデバッグ実行するものでしたが、[OpenOCD がやってきた](https://qiita.com/eofz/items/c5df493e5290a12e8e8b) で説明しているように、bootloader とアプリケーションをダウンロード&実行するだけならスクリプトだけで可能です。Intel® Quartus® software に含まれる OpenOCD と、RiscFree に含まれる gdb を使います。
以下 Windows での Nios V command shell 用のバッチスクリプトの例です。ここで使っている、cyclone5_client.cfg ファイルは、[OpenOCD がやってきた](https://qiita.com/eofz/items/c5df493e5290a12e8e8b) や [RiscFree IDE(０円デバッガ) で SoC FPGAをデバッグ](https://qiita.com/eofz/items/92328e9f1dbfe8609582) で紹介しているものと同一です。gdb 用のスクリプトは、Zephyr の boards/arm/cyclonev_socdk/support/ に入っているスクリプトとほぼ同一(参照するfile path 名を変えたもの) になっており、実際の所、前記の ```west flash``` コマンドで実行される内容と同一の流れになっています（OpenOCD の起動 -> arm gdb によるスクリプト実行 -> OpenOCD の終了）。

```bat:zephyr_download.bat  (Zephyr ダウンロード＆実行のバッチファイル)
start openocd -f cyclone5_client.cfg
%QUARTUS_ROOTDIR%/../riscfree/toolchain/Arm/arm-none-eabi/bin/arm-none-eabi-gdb -ex "target extended-remote localhost:3333"  -x preloader_dl.gdb -x zephyr_dl.gdb
taskkill /F /im openocd.exe
```

<details><summary>preloader_dl.gdb の内容</summary>

```tcl:preloader_dl.gdb
set confirm off
set pagination off

restore u-boot-spl
symbol-file -readnow u-boot-spl
restore u-boot-spl-dtb.bin binary 0xffff0000
thbreak spl_boot_device
jump _start
```
</details>

<details><summary>zephyr_dl.gdb.gdb の内容</summary>

```tcl:zephyr_dl.gdb.gdb
set confirm off
set pagination off

restore zephyr.elf
symbol-file -readnow zephyr.elf
thbreak main
jump z_arm_reset

# Execute OpenOCD "resume" command to keep the target running, then quit gdb.
mon resume
quit
```
</details>

zephyr_download.bat, cyclone5_client.cfg, preloader_dl.gdb, zephyr_dl.gdb, u-boot-spl, u-boot-spl-dtb.bin の各ファイルは同一のフォルダに入れおき、Nios V command shell でそのディレクトリから、zephyr_download.bat を実行します。

```:Nios V command shell にて バッチファイルの実行例
[niosv-shell] C:\Qiita\2022_advent\Zephyr\run_by_script> zephyr_download.bat
...
[niosv-shell] C:\Qiita\2022_advent\Zephyr\run_by_script> taskkill /F /im openocd.exe
SUCCESS: The process "openocd.exe" with PID 15180 has been terminated.

[niosv-shell] C:\Qiita\2022_advent\Zephyr\run_by_script>
```
Windows の bat ファイルの例を書きましたが、Linux でも同様のシェルスクリプトで実行可能です。container で```west flash``` を実行するより、こちらのスクリプト or バッチを Host PC で実行する方が、安定して実行が行えるものと思います。

### SD card boot から実行
（Host PC と DE10-Nano ボードとの間で Serial terminal 用の USB cable 接続が必要です。）

- SD card 作成
SD card に書き込みが必要なファイルは、u-boot-spl をビルドしたときに同時に生成される boot-with-spl.sfp (u-boot-spl と U-Boot を結合したファイル）と、zephyr.elf の 2つのファイルです。これらのファイル含んだ boot 用の SD card image を以下のコマンドで作ります。こちらも、Linux 環境での作業となります。zephyr.elf  は、samples/basic/blinky をビルドした zephyr.elf を使ってみてください。



```bash: boot 用の SD card image を作成するコマンド
# 以下では、
# $Uboot : u-boot-with-spl.sfp が存在するフォルダ
# $Zephyr : zephyr.elf が存在するフォルダ
# としています。

# boot image を生成する python script をダウンロードしておきます(初回のみ)
wget https://releases.rocketboards.org/2021.04/gsrd/tools/make_sdimage_p3.py

# python script を使って CVSoC boot 用の SD card img を作成
# 　10MByte 領域の "A2" partition を作り、u-boot-with-spl.sfp を書き込む
# 　32MByte 領域の fat32 partition を作り、zephyr.elf を書き込む
sudo python3 ./make_sdimage_p3.py -f \
  -P $Uboot/u-boot-with-spl.sfp,num=3,format=raw,size=10M,type=A2 \
  -P $Zephyr/zephyr.elf,num=1,format=fat32,size=32M \
  -s 48M -n sdcard_cv.img
```
上記により、sdcard_cv.img という約 48MByte の SoC boot 用 image ファイルが出来上がります。このファイルを SD card に書き込みます。Linux なら dd コマンドで、Windows なら Win32 Disk Imager や balenaEtcher などの disk image 書き込みツールで行えます。

sdcard_cv.img を書き込んだ SD card を DE10-Nano に挿して、ボードの電源を入れます。すると、u-boot-spl が起動し、次いで U-Boot が起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175992/b434b49d-d3c6-020a-7cb6-c3434462b3e9.png)

次に U-Boot から zephyr.elf を起動できるよう設定をしていきます。U-Boot のプロンプトから、
```:U-Boot プロンプトから入力するコマンド
setenv start_zephyr 'setenv autostart y;\
 fatload mmc 0 1000000 zephyr.elf; icache off;dcache off;\
 bootelf -p 1000000'
saveenv
```
を入力します。これにより、fat 領域から zephyr.elf を読み出し、その elf を起動するコマンド "start_zephyr" を作成し、SD card に記録することができました（次回電源投入時にも読み込まれる）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175992/14c35c13-4670-02f0-f0be-4a2a568b1427.png)
作成したコマンド ```run start_zephyr``` を入力し、zephyr.elf を実行してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175992/f6a19680-407f-2603-976f-327e2a966fc3.png)
Zephyr OS からの起動メッセージが表示され、User LED が１秒ごとにON/OFFをトグルする点滅動作を始めているはずです。blinky サンプルから生成した zephyr.elf が実行されたことが確認できました。

最後の仕上げとして、電源投入直後から zephyr.elf を起動するように設定してみます。U-Boot のプロンプトで、
```:zephyr.elf を自動起動するための設定
setenv bootdelay 3
setenv bootcmd 'run start_zephyr'
seveenv
```
と入力します。
これ以降、電源を再投入すれば、U-Boot の 3 秒のカウントダウン後[^10]、zephyr.elf が起動されるようになります。

[^10]: bootdelay の値を 3 としたので。この値を変えれば、カウントダウンの秒数を調整できます。

以上により、zephyr アプリケーションが起動する SD card の作成ができました!!
なお、この SD card の fat パーティションの zephyr.elf ファイルを書き変えることで、書き変えた zephyr.elf を自動実行させることができます。


以上です。



# おわりに
以上、Zephyr OS を Cyclone® V SoC FPGA で走らせることに挑戦してみました。

build 環境は、docker image を使うことでで、簡単に用意することができました。
Cyclone® V SoC FPGA を使った別ボード（今回は DE10-Nano ボードで試しました）へのポーティングも主にデバイスツリーの変更で対応可能という感触です。
実行についても、debugger による実行、スクリプトでのバッチ実行 および SD card からの自動起動 ができることを確認し、デバッグ作業と、組み込み用途の実用的な使い方も可能であることが確認できました。思った以上に実用的な RTOS だという印象を持ちました。

今回は試せませんでしたが、サンプルの数も多く、Cyclone® V SoC FPGA の Ethernet や USB の driver も用意されている様ですので、この先、試してみたいと思います。

# 参考資料
* [Zephyr OS ドキュメントサイト](https://docs.zephyrproject.org/latest/)
[　- Intel® Cyclone® V SoC Development Kit](https://docs.zephyrproject.org/latest/boards/arm/cyclonev_socdk/doc/index.html) 
[　- Getting Started Guide](https://docs.zephyrproject.org/latest/develop/getting_started/index.html)
[　- Board Porting Guide](https://docs.zephyrproject.org/latest/hardware/porting/board_porting.html) 
* [Github：Zephyr Docker Images](https://github.com/zephyrproject-rtos/docker-image)
* [Qiita: RiscFree IDE(０円デバッガ) で SoC FPGAをデバッグ](https://qiita.com/eofz/items/92328e9f1dbfe8609582) 
* [Qiita: OpenOCD がやってきた](https://qiita.com/eofz/items/c5df493e5290a12e8e8b)
* [Rockeboards.org: Building Bootloader for Cyclone V and Arria 10](https://www.rocketboards.org/foswiki/Documentation/BuildingBootloaderCycloneVAndArria10) 

# Notices & Disclaimers
Intel technologies may require enabled hardware, software or service activation. No product or component can be absolutely secure. Your costs and results may vary. 
© Intel Corporation.  Intel, the Intel logo, and other Intel marks are trademarks of Intel Corporation or its subsidiaries.  Other names and brands may be claimed as the property of others.