SiFive Freedom E SDK の重箱を少しつついてみる

(更新

Slightly digging into SiFive Freedom E SDK.

今日は SiFive の Freedom Studio を試してみようと思ったのですが、もう少し SiFive Freedom E SDK の動きを調べてからにしようと思います。少し重箱つつきな感じです。

make software 編

まず最初に、make コマンドで make software するときの動作を少し追ってみます。つまり、コンパイル & リンクですね。あ、最後のほうで objcopy で .hexファイルなども作ってますが。

余談ですが、どうしてこれは make software なんでしょうね。make build のほうが、多くの人には直感的だと思うんですけど。

閑話休題。まず最初に少し環境変数を整備しましょう。これをしておくと、make のときにターゲット名やビルドするプログラムの名前を省略できます。

$ export PROGRAM=sifive-welcome
$ export TARGET=sifive-hifive1

あるいは、make standalone というコマンドを使うと、スタンドアローン環境でビルドするためのプロジェクトディレクトリを生成できます。

さて。さっそく Makefile が実行している GCC のコマンドラインを覗いてみましょう。

riscv64-unknown-elf-gcc \
  -march=rv32imac \      # これは、アーキテクチャとして RV32IMAC を指定してます。下記 (1) 参照
  -mabi=ilp32 \          # これは ABI として ilp32 を指定してます。下記 (2) 参照
  -mcmodel=medlow \      # メモリモデルです。GCC のマニュアル参照
  -ffunction-sections \  # 以下 2行は、関数やデータを個別のメモリセクションに配置するものです
  -fdata-sections \
  -I/home/yokoyama/freedom-e-sdk/bsp/sifive-hifive1/install/include \
  --specs=nano.specs \   # GCC の specs ファイルの指定です。最初に読み込まれる引数群ですね
  -O0 \
  -g \
  -Wl,--gc-sections \    # リンカにオプションを渡し、使われていないセクションを削除させます
  -Wl,-Map,sifive-welcome.map \  # マップファイルの生成ですね
  -nostartfiles \
  -nostdlib \
  -L/home/yokoyama/freedom-e-sdk/bsp/sifive-hifive1/install/lib/debug/ \
  -T/home/yokoyama/freedom-e-sdk/bsp/sifive-hifive1/metal.default.lds \  # リンカ指示ファイル
  sifive-welcome.c \
  -Wl,--start-group \    # リンカに渡す引数群です。具体的にはリンクするライブラリを指定してます
  -lc \
  -lgcc \
  -lm \
  -lmetal \
  -lmetal-gloss \
  -Wl,--end-group \
  -o \
  sifive-welcome

(1) RV32I が標準アーキテクチャで、これに M(乗算器/除算器あり)、A(マルチプロセシング用のアトミック命令あり)、C(16ビットの圧縮命令あり)が付いています。ちなみにマイコンで A オプションは過剰だと思うのですが、どうせシングルコアなので、余計なロジック回路追加はほとんどないので、含めてしまっているのでしょう。どうも、組込系のマイコン用途では、RV32IMAC というのが一つの標準みたいです。名前も覚えやすいですしね。

(2) ABI というのは、C 言語でプログラムを書くときの、関数間で引数を渡すやり方やレジスタの標準的な使い方を決める約束事(レジスタコンベンションとも言います)です。ilp32 というのは、int 型、long 型、ポインタ型を全て 32ビットで表現する、RISC-V の標準的な ABI の一つです。

さて。この make software では、オブジェクトファイルを作らずに一気にリンク済みの ELF ファイルを生成しているようですね。(それにしても、GCC のオプションって本当に分かりづらいですね…)

次にリンカ指示ファイルを少し覗いてみましょう。

OUTPUT_ARCH("riscv")

ENTRY(_enter)

MEMORY
{
        flash (rxai!w) : ORIGIN = 0x20400000, LENGTH = 0x1fc00000
        ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 0x4000
}
(略)
SECTIONS
{

        __stack_size = DEFINED(__stack_size) ? __stack_size : 0x400;
        PROVIDE(__stack_size = __stack_size);
        __heap_size = DEFINED(__heap_size) ? __heap_size : 0x800;
(略)

これを見ると、プログラムや初期化済みのデータは flash(外部 SPI メモリ)に配置され、それ以外は ram(内部 RAM)に配置されることが分かります。また、スタックサイズは 1Kバイト、ヒープは 2Kバイト定義されています。

参考までに、HiFive 1 の SoC FE310-G000 の諸元を少し調べてみましょう。

  • ISA: RV32IMAC(これは前述した通りです)
  • Instruction Cache: 16 KiB 2-way instruction cache(命令用に 16Kバイトのキャッシュを持ってます)
  • Data Tightly Integrated Memory: 16 KiB DTIM(内部 RAM のようです)
  • Modes: The E31 supports the following modes “Machine”(つまり、特権モードやユーザーモードなどを分離しない、Machine モードだけを利用できます)

次にメモリマップです。

外部 ROM は 0x2000_0000 から 512Mバイト用意されています。ただし、HiFive 1 は 16Mバイトだけメモリを搭載しています。面白いのは、プログラムを外部 Flash SPI ROM 上からフェッチして実行する点ですね。(最近はそういうプロセッサが増えてますが。)命令キャッシュ(2-way)を持っているので、まあ、十分な速度で動くと期待してのことでしょう。

内部 RAM は、0x8000_0000 から 16Kバイトですね。

次にマップファイルを覗いてみましょう。

Memory Configuration

Name             Origin             Length             Attributes
flash            0x0000000020400000 0x000000001fc00000 axrl !w
ram              0x0000000080000000 0x0000000000004000 axw !rl
*default*        0x0000000000000000 0xffffffffffffffff

(略)

.init           0x0000000020400000      0x194
 *(.text.metal.init.enter)
 .text.metal.init.enter
                0x0000000020400000       0x60 /home/yokoyama/freedom-e-sdk/bsp/sifive-hifive1/install/lib/debug//libmetal.a(libriscv__mmachine__sifive-hifive1_a-entry.o)

Memory Configuration は、リンカ指示ファイルで指定している通りですね。また、セクションも正しくメモリ配置されているようです。当たり前ですね。あとは皆さんでもいろいろ調べてみてください。

最後に、objcopy の引数を調べておしまいにしましょう。なお、ここからはパス名を少し省略して引用します。(長くて冗長なので。)

riscv64-unknown-elf-objcopy \
  -O ihex sifive-welcome.elf sifive-welcome.hex

make upload 編

次に make upload を調べてみましょう。実行してみると、次のようなスクリプト呼び出しになっていることが分かります。

scripts/upload \
  --elf sifive-welcome.elf \
  --openocd .../bin/openocd \
  --gdb .../bin/riscv64-unknown-elf-gdb \
  --openocd-config bsp/sifive-hifive1/openocd.cfg

さらに scripts/upload を覗いてみると、バックグラウンドで openocd(つまり gdbserver)を動かしつつ gdb からコマンドを送っていることが分かります。スクリプトの中身は割愛して、同じことをコマンドラインから実行してみましょう。バックグラウンドでの openocd 実行が分かりやすいように、ターミナルを 2つ開いて実行します。

まずは openocd 側です。

$ .../bin/openocd -f bsp/sifive-hifive1/openocd.cfg
Open On-Chip Debugger 0.10.0+dev (SiFive OpenOCD 0.10.0-2019.08.2)
Licensed under GNU GPL v2
For bug reports:
        https://github.com/sifive/freedom-tools/issues
adapter speed: 10000 kHz
Info : auto-selecting first available session transport "jtag". To override use 'transport select '.
Info : ftdi: if you experience problems at higher adapter clocks, try the command "ftdi_tdo_sample_edge falling"
Info : clock speed 10000 kHz
Info : JTAG tap: riscv.cpu tap/device found: 0x10e31913 (mfg: 0x489 (SiFive Inc), part: 0x0e31, ver: 0x1)
Info : [0] Found 2 triggers
halted at 0x200001aa due to debug interrupt
Info : Examined RISCV core; XLEN=32, misa=0x40001105
Info : Listening on port 3333 for gdb connections
Info : Found flash device 'issi is25lp128d' (ID 0x0018609d)
cleared protection for sectors 64 through 255 on flash bank 0
Ready for Remote Connections
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections

TCP ポート 3333 で gdb 接続待ちになりました。そこで、もう一つのターミナルから GDB を実行してみます。

$ .../bin/riscv64-unknown-elf-gdb software/sifive-welcome/debug/sifive-welcome.elf 
GNU gdb (SiFive GDB 8.3.0-2019.08.0) 8.3
(略)
Reading symbols from software/sifive-welcome/debug/sifive-welcome.elf...
(gdb) set remotetimeout 240  # gdbserver 応答タイムアウトの設定
(gdb) target extended-remote localhost:3333  # リモートターゲットの設定
Remote debugging using localhost:3333
0x200001aa in ?? ()
(gdb) monitor reset halt  # OpenOCD に reset halt を指示
JTAG tap: riscv.cpu tap/device found: 0x10e31913 (mfg: 0x489 (SiFive Inc), part: 0x0e31, ver: 0x1)
halted at 0x200001aa due to debug interrupt
(gdb) monitor flash protect 0 64 last off  # Flash メモリプロテクトを off に
cleared protection for sectors 64 through 255 on flash bank 0
(gdb) load  # プログラム(ELF)のロード
Loading section .init, size 0x194 lma 0x20400000
Loading section .text, size 0x5b38 lma 0x20400200
Loading section .rodata, size 0x10d8 lma 0x20405d38
Loading section .init_array, size 0xc lma 0x20406e10
Loading section .data, size 0x7e0 lma 0x20406e20
Start address 0x20400000, load size 30096
Transfer rate: 35 KB/sec, 5016 bytes/write.
(gdb) monitor resume  # OpenOCD にプログラム再開(実行)を指示
(gdb) monitor shutdown  # OpenOCD に終了を指示
shutdown command invoked
(gdb) quit  # GDB を抜けます
A debugging session is active.

        Inferior 1 [Remote target] will be detached.

Quit anyway? (y or n) EOF [assumed Y]
Detaching from program: /home/yokoyama/freedom-e-sdk/software/sifive-welcome/debug/sifive-welcome.elf, Remote target
Remote connection closed

裏で実行していることが良く分かりますね。

make debug 編

最後に、make debug についても重箱の隅をつついて、今日はおしまいにしましょう。

こちらは、今度はスクリプト scripts/debug を呼んでいます。

scripts/debug \
  --elf sifive-welcome.elf \
  --openocd .../bin/openocd \
  --gdb .../bin/riscv64-unknown-elf-gdb \
  --openocd-config bsp/sifive-hifive1/openocd.cfg

やはり OpenOCD と GDB を使っているので、make upload に似ています。

まず最初に、OpenOCD の実行は make upload とまったく同じなので省略します。次に GDB 側です。

.../bin/riscv64-unknown-elf-gdb software/sifive-welcome/debug/sifive-welcome.elf 
GNU gdb (SiFive GDB 8.3.0-2019.08.0) 8.3
(略)
Reading symbols from software/sifive-welcome/debug/sifive-welcome.elf...
(gdb) set remotetimeout 240
(gdb) target extended-remote localhost:3333
Remote debugging using localhost:3333
0x2040039c in wait_for_timer (which_led=0x800007b4 <__metal_dt_led_0blue>)
    at sifive-welcome.c:75
75          while (timer_isr_flag == 0){};
(gdb) 

なんのことはない、make upload の前半を実行しているだけでした。それが分かれば、もう少しいろいろできます。

make debug を実行すると、プログラムの現在実行箇所で停止しますが、プログラムを main() 関数の最初から実行したい場合もあるはずです。私は OpenOCD の reset halt コマンドで SRST がかかるかと思ったのですが、かからないので(よく分かっていない)とりあえず別の方法を考えました。

今回は make debug コマンドを使います。

$ make debug
(略)
Remote debugging using localhost:3333
metal_led_get_rgb (label=0x204060c8 "LD0", color=0x204060c4 "red")
    at /home/yokoyama/freedom-e-sdk/freedom-metal/src/led.c:14
14          if ((__METAL_DT_MAX_LEDS == 0) ||
(gdb) tbreak main  # main() 関数にテンポラリブレークポイントを設定します
Temporary breakpoint 1 at 0x204003c2: file sifive-welcome.c, line 89.
(gdb) jump *0x20400000  # 0x2040_0000 のエントリポイントにジャンプ
Line 24 is not in `metal_led_get_rgb'.  Jump anyway? (y or n) y
Continuing at 0x20400000.
Note: automatically using hardware breakpoints for read-only addresses.
halted at 0x20400004 due to step

Temporary breakpoint 1, main () at sifive-welcome.c:89
89          led0_red = metal_led_get_rgb("LD0", "red");
(gdb) info reg pc  # プログラムカウンタを見てみる
pc             0x204003c2       0x204003c2 <main+8>
(gdb) next  # 1行ステップ実行
halted at 0x204003c6 due to step
halted at 0x204003ca due to step
halted at 0x204003ce due to step
halted at 0x204003d2 due to step
halted at 0x204013fe due to step
halted at 0x204003da due to step
90          led0_green = metal_led_get_rgb("LD0", "green");
(gdb) info reg pc  # もう一度確認
pc             0x204003da       0x204003da <main+32>
(gdb) p/x $pc  # レジスタはこうやっても見られます
$1 = 0x204003da
(gdb) p/x $mstatus  # CSR も見られます
$2 = 0x1808
(gdb)

ちなみに CSR レジスタの一覧を見るには、info all-registers コマンドを使います。

これは余談ですが、HiFive 1 の SoC FE310-G000 にはハードウェアブレークポイントが 2つしかありません。ですので、3つ以上のブレークポイントを設定するとエラーになります。正確に言うと、break コマンドで設定する際にはエラーにならないのですが、c コマンドで動作続行させるときにエラーになります。御注意を。

今日もいろんなことを勉強しました。知恵熱が出そうですので、今日はここまで。

お問い合わせはお気軽に!

お問い合わせを頂いた後、継続して営業活動をしたり、ニュースレター等をお送りしたりすることはございません。
御返答は 24時間以内(営業時間中)とさせて頂いております。必ず返信致しますが、時々アドレス誤りと思われる返信エラーがございます。返答が届かない場合、大変お手数ではございますが別のメールアドレスで督促頂けますと幸いです。