FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

QEMU5.1.0でRISC-Vバイナリの動かし方調査

前回QEMUでとりあえずprintf()を扱うための簡単なサンプルは無いかと探していたのだが、どうやらfreedom-e-sdkがかなり使い物になっているようで、これを試してみることにした。

$ git clone https://github.com/sifive/freedom-e-sdk.git
$ cd freedom-e-sdk
$ git submodule update --init --recursive
# U54向け
$ make PROGRAM=hello TARGET=qemu-sifive-u54
# E31向け
$ make PROGRAM=hello TARGET=qemu-sifive-e31

ここではGCCとしてSiFive社が提供しているGCCバイナリを使用した。私は昔からの環境も取ってあるのでデフォルトのriscv64-unknown-elf-gccGCC-7.2.0になっているのだが、もう少し新しいものを使わないと駄目なようだ。

(自分用メモ:)module switch riscv/riscv64_sifevを適用する。

これで、それぞれ以下の場所にバイナリが生成される。

  • freedom-e-sdk/software/hello/debug/hello.elf

これをQEMUで実行するためにはどうするのか考えよう。まずは何も考えずにQEMUを実行してみる。

$ qemu-system-riscv64 --nographic --machine virt --bios none --kernel freedom-e-sdk/software/hello/debug/hello.elf

特に何も表示されないので、-d in_asmでどのような命令が実行されたのか確認してみる。

$ qemu-system-riscv64 -d in_asm --nographic --machine virt --bios none \
    --kernel freedom-e-sdk/software/hello/debug/hello.elf

実行直後のログ:

----------------
IN:
Priv: 3; Virt: 0
0x0000000000001000:  00000297          auipc           t0,0            # 0x1000
0x0000000000001004:  02828613          addi            a2,t0,40
0x0000000000001008:  f1402573          csrrs           a0,mhartid,zero

----------------
IN:
Priv: 3; Virt: 0
0x000000000000100c:  0202b583          ld              a1,32(t0)
0x0000000000001010:  0182b283          ld              t0,24(t0)
0x0000000000001014:  00028067          jr              t0

----------------
IN:
Priv: 3; Virt: 0
0x0000000080000000:  00007197          auipc           gp,28672        # 0x80007000
0x0000000080000004:  d3818193          addi            gp,gp,-712
0x0000000080000008:  00000297          auipc           t0,0            # 0x80000008
0x000000008000000c:  01828293          addi            t0,t0,24
0x0000000080000010:  30529073          csrrw           zero,mtvec,t0

----------------
IN:
Priv: 3; Virt: 0
0x0000000080000014:  00100293          addi            t0,zero,1
0x0000000080000018:  00028463          beqz            t0,8            # 0x80000020

----------------

0x8000_0000の付近はhello.elfのダンプ結果と合っているような気がする。

$ riscv64-unknown-elf-objdump -D freedom-e-sdk/software/hello/debug/hello.elf
0000000080000000 <_enter>:
    80000000:   00007197                auipc   gp,0x7
    80000004:   d3818193                addi    gp,gp,-712 # 80006d38 <__global_pointer$>
    80000008:   00000297                auipc   t0,0x0
    8000000c:   01828293                addi    t0,t0,24 # 80000020 <_enter+0x20>
    80000010:   30529073                csrw    mtvec,t0
    80000014:   00100293                li      t0,1
    80000018:   00028463                beqz    t0,80000020 <_enter+0x20>
    8000001c:   7c105073                csrwi   0x7c1,0
    80000020:   00000297                auipc   t0,0x0
    80000024:   12428293                addi    t0,t0,292 # 80000144 <early_trap_vector>
    80000028:   30529073                csrw    mtvec,t0
    8000002c:   80000117                auipc   sp,0x80000

そして、QEMUの実行はどこかで停止している。

----------------
IN: __metal_before_start
Priv: 3; Virt: 0
0x0000000080000144:  342022f3          csrrs           t0,mcause,zero

----------------
IN: __metal_before_start
Priv: 3; Virt: 0
0x0000000080000148:  34102373          csrrs           t1,mepc,zero

----------------
IN: __metal_before_start
Priv: 3; Virt: 0
0x000000008000014c:  343023f3          csrrs           t2,mtval,zero

----------------
IN: __metal_before_start
Priv: 3; Virt: 0
0x0000000080000150:  bfd5              j               -12             # 0x80000144

うーん、__metal_before_startの内部で止まってしまっている?0x800001500x80000144でループを繰り返しているようだ。

だいたいのダンプ結果を示してみると:

...
IN: _write
IN: _write
IN: _write
IN: _write
IN: metal_tty_putc
IN: metal_uart_putc
IN: __metal_driver_sifive_uart0_putc
IN: __metal_driver_sifive_uart0_control_base
IN: __metal_driver_sifive_uart0_control_base
IN: __metal_driver_sifive_uart0_control_base
IN: __metal_driver_sifive_uart0_putc
IN: __metal_driver_sifive_uart0_txready
IN: __metal_driver_sifive_uart0_txready
IN: __metal_before_start
IN: __metal_before_start
...

なるほど、おそらくputc()あたりで待ち状態になっているのかな?txready()あたりのソースコードを探してみる。

  • freedom-e-sdk/freedom-metal/src/drivers/sifive_uart0.c
int __metal_driver_sifive_uart0_putc(struct metal_uart *uart, int c) {
    long control_base = __metal_driver_sifive_uart0_control_base(uart);

    while (__metal_driver_sifive_uart0_txready(uart) != 0) {
        /* wait */
    }
    UART_REGW(METAL_SIFIVE_UART0_TXDATA) = c;
    return 0;
}

なるほど。METAL_SIFIVE_UART0_TXDATAへの書き込みが行われているようだ。これで一応putc()は出来ているはずだけどなあ?どうも動いていないらしい。

その後いろいろ調べて、まさかと思いQEMUのバージョンをriscv-qemuに変えてみた。

git clone git@github.com:sifive/riscv-qemu.git -b riscv-qemu-3.1
cd riscv-qemu
mkdir build
cd build
../configure --target-list=riscv64-softmmu
make -j4
./riscv64-softmmu/qemu-system-riscv64 -d in_asm --nographic --machine sifive_u --bios none \
    --kernel /home/msyksphinz/work/riscv/freedom-e-sdk/software/hello/debug/hello.elf

これだと動作した。つまり最新バージョンのQEMUにおけるsifive_uは仕様が変わっている、ということか...

まずは、QEMU-5.1.0とriscv-qemu(QEMU-3.1.0)のデバイスツリーを比較する。

  • QEMU-3.1.0の場合
$ ./riscv64-softmmu/qemu-system-riscv64 --nographic --machine sifive_u,dumpdtb=riscv64-sifive_u.dtb --bios none \
    --kernel /home/msyksphinz/work/riscv/freedom-e-sdk/software/hello/debug/hello.elf
$ dtc riscv64-sifive_u.dtb
...
        soc {
                #address-cells = < 0x02 >;
                #size-cells = < 0x02 >;
                compatible = "simple-bus";
                ranges;

                uart@ {
                        interrupts = < 0x01 >;
                        interrupt-parent = < 0x02 >;
                        reg = < 0x00 0x10013000 0x00 0x1000 >;
                        compatible = "sifive,uart0";
                };
...
  • QEMU-5.1.0の場合
...
        soc {
                #address-cells = < 0x02 >;
                #size-cells = < 0x02 >;
                compatible = "simple-bus";
                ranges;

                serial@10010000 {
                        interrupts = < 0x04 >;
                        interrupt-parent = < 0x06 >;
                        clocks = < 0x05 0x03 >;
                        reg = < 0x00 0x10010000 0x00 0x1000 >;
                        compatible = "sifive,uart0";
                };
...

あーこのメモリマップの関係が違うのか。では、hello側のメモリマップを変更してみよう。このためにはfreedom-e-sdk内のbspを変更する必要があった。

diff --git a/bsp/qemu-sifive-u54/metal-platform.h b/bsp/qemu-sifive-u54/metal-platform.h
index cd1e5e3..d241916 100644
--- a/bsp/qemu-sifive-u54/metal-platform.h
+++ b/bsp/qemu-sifive-u54/metal-platform.h
@@ -47,8 +47,8 @@
 #define METAL_SIFIVE_TEST0_FINISHER_OFFSET 0UL

 /* From uart@10013000 */
-#define METAL_SIFIVE_UART0_10013000_BASE_ADDRESS 268513280UL
-#define METAL_SIFIVE_UART0_0_BASE_ADDRESS 268513280UL
+#define METAL_SIFIVE_UART0_10013000_BASE_ADDRESS 0x10010000UL
+#define METAL_SIFIVE_UART0_0_BASE_ADDRESS 0x10010000UL
 #define METAL_SIFIVE_UART0_10013000_SIZE 4096UL
 #define METAL_SIFIVE_UART0_0_SIZE 4096UL

これでhelloを再ビルドしQEMUで実行すると、とりあえずprintf()は動くようになった。ただテストパタンが終了しないのでこれは問題。QEMU-3.1.0では終了するので解析していきたいと思う。

$ ./qemu/build/riscv64-softmmu/qemu-system-riscv64 --nographic --machine sifive_u --bios none \
    --kernel freedom-e-sdk/software/hello/debug/hello.elf
Hello, World!
QEMU: Terminated

もう一つの確認事項は、テストパタンの終了手順だ。riscv-qemuで確認すると最終的に__metal_driver_sifive_test0_exitで終了しているようだった。

$ ./build/riscv64-softmmu/qemu-system-riscv64 -d in_asm --nographic --machine sifive_u --bios none --kernel /home/msyksphinz/work/riscv/freedom-e-sdk/software/hello/debug/hello.elf
----------------
IN: __metal_driver_sifive_test0_exit
0x0000000080004b56:  6795              lui             a5,20480
0x0000000080004b58:  55578793          addi            a5,a5,1365
0x0000000080004b5c:  a021              j               8               # 0x80004b64

----------------
IN: __metal_driver_sifive_test0_exit
0x0000000080004b64:  9fb9              addw            a5,a5,a4
0x0000000080004b66:  2781              sext.w          a5,a5
0x0000000080004b68:  fef42223          sw              a5,-28(s0)
0x0000000080004b6c:  fe843783          ld              a5,-24(s0)
0x0000000080004b70:  fe442703          lw              a4,-28(s0)
0x0000000080004b74:  c398              sw              a4,0(a5)
0x0000000080004b76:  bfdd              j               -10             # 0x80004b6c

確認してみると、riscv-qemuには以下のテスト用のデバイスがマップされていた。

                test@100000 {
                        reg = < 0x00 0x100000 0x00 0x1000 >;
                        compatible = "sifive,test0";
                };

しかし、QEMU-5.1.0ではmachine=sifive_uではこのようなデバイスは定義していなかった。どうもmachine=virtでは定義されているようだ。

        uart@10000000 {
                interrupts = < 0x0a >;
                interrupt-parent = < 0x03 >;
                clock-frequency = < 0x384000 >;
                reg = < 0x00 0x10000000 0x00 0x100 >;
                compatible = "ns16550a";
        };
...
        test@100000 {
                phandle = < 0x04 >;
                reg = < 0x00 0x100000 0x00 0x1000 >;
                compatible = "sifive,test1\0sifive,test0\0syscon";
        };

この0x100000というのはfreedom-e-sdkのデフォルト値と合っているようだ。ということはuartの位置だけを0x10000000に切り替えるとhelloは動作してくれるだろう。

diff --git a/bsp/qemu-sifive-u54/metal-platform.h b/bsp/qemu-sifive-u54/metal-platform.h
index cd1e5e3..2f5f89a 100644
--- a/bsp/qemu-sifive-u54/metal-platform.h
+++ b/bsp/qemu-sifive-u54/metal-platform.h
@@ -47,8 +47,8 @@
 #define METAL_SIFIVE_TEST0_FINISHER_OFFSET 0UL

 /* From uart@10013000 */
-#define METAL_SIFIVE_UART0_10013000_BASE_ADDRESS 268513280UL
-#define METAL_SIFIVE_UART0_0_BASE_ADDRESS 268513280UL
+#define METAL_SIFIVE_UART0_10013000_BASE_ADDRESS 0x10000000UL
+#define METAL_SIFIVE_UART0_0_BASE_ADDRESS 0x10010000UL
 #define METAL_SIFIVE_UART0_10013000_SIZE 4096UL
 #define METAL_SIFIVE_UART0_0_SIZE 4096UL
./qemu-5.1.0/build/riscv64-softmmu/qemu-system-riscv64 --nographic --machine virt --bios none --kernel /home/msyksphinz/work/riscv/freedom-e-sdk/software/hello/debug/hello.elf                                                          
Hello, World!                                 

なるほど。ちゃんと動作して終了もしてくれた。