はじめに
PlatformIOなどを使用すれば、なんちゃってベアメタルプログラミングは簡単に始められるが、 そこに至るまでの知識が全く足りていないため少しだけ掘り下げてみる。
今回のターゲットはSiFiveのHiFive1
(のエミュレータ)。
ツールチェイン
HiFive1に合わせてRV32IMAC
のツールチェインを作成する。
$ mkdir -p ~/bin/riscv $ cd ~/bin/riscv $ sudo apt-get install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc $ git clone --recursive https://github.com/riscv/riscv-gnu-toolchain $ cd riscv-gnu-toolchain $ ./configure --prefix=${HOME}/bin/riscv --with-arch=rv32imac $ make -j $(nproc)
手動でPATHを通すのでmake install
はしない。
.bashrc
に下記を追加
PATH="${HOME}/bin/riscv/bin:${PATH}"
メモリマップ
SiFive FE310-G000 Manual v2p3
からメモリマップの部分を抜粋する。
ブートシーケンス
SiFive HiFive1 Getting Started Guide 1.0.2
のHiFive1 Boot Sequence
によると、ざっくりと次のようになっている。
Board is shipped with boot loader at the beginning of SPI Flash(0x20000000) The core jumps to the main user portion of code at 0x20400000
HiFive1はブートローダが書き込み済みの状態で出荷され、それはは0x20000000〜0x203FFFFFの間に書き込まれているらしい事がわかる。
ユーザーがプログラムを書き込むのは0x20400000
からということらしい。
サンプルプログラム
ベアメタルの基本的な知識が圧倒的に足りないため何かを写経する。
dwelch67/sifive_samplesが良さそう。
blinker00(flash版)
blinkerとはなっているけど特に何かチカチカするプログラムではないと思う。
memmap(リンカスクリプト)
メモリをフラッシュとRAMに分けてセクションを切っている。
MEMORY { rom : ORIGIN = 0x20400000, LENGTH = 0x4000 ram : ORIGIN = 0x80000000, LENGTH = 0x4000 } SECTIONS { .text : {*(.text*)} > rom .rodata : {*(.rodata*)} > rom .bss : {*(.bss*)} > ram }
プログラムの書き込み先は0x20400000。
novectors.s
エントリとなる_start
ではスタックポインタの初期化しかしていない。
その後いきなりC言語の関数を呼び出している。
.globl _start _start: lui x2, 0x80004 /* set sp to 0x80004 */ jal notmain /* call notmain(jump and link) */ j . /* infinit loop */ .globl dummy dummy: ret .globl PUT32 PUT32: sw x11,(x10) /* *a0 = a1(store word) */ ret .globl GET32 GET32: lw x10,(x10) /* ret = *(a0) */ ret
ちなみにコメントは追加した。
notmain.c
C言語のエントリポイント。
0x80001000
、0x80002000
、0x80003000
に値を書き込んでいる。
void PUT32(unsigned int, unsigned int); unsigned int GET32(unsigned int); void dummy(unsigned int); int notmain(void) { unsigned int ra; for (ra = 0; ra < 20; ra+=4) PUT32(0x80001000+ra, ra); PUT32(0x80002000, 0x12345678); PUT32(0x80003000, GET32(0x80002000)); return 0; }
Makefile
QEMUで実行する場合、elfを使用するためbin形式への変換は不要。
RISCVGNU = riscv32-unknown-elf AOPS = -march=rv32imac -g COPS = -march=rv32imac -Wall -O2 -nostdlib -nostartfiles -ffreestanding -g all : notmain.bin clean : rm -f *.o rm -f *.elf rm -f *.bin rm -f *.list novectors.o : novectors.s $(RISCVGNU)-as $(AOPS) novectors.s -o novectors.o notmain.o : notmain.c $(RISCVGNU)-gcc $(COPS) -c notmain.c -o notmain.o notmain.bin : memmap novectors.o notmain.o $(RISCVGNU)-ld novectors.o notmain.o -T memmap -o notmain.elf $(RISCVGNU)-objdump -D notmain.elf > notmain.list $(RISCVGNU)-objcopy notmain.elf -O binary notmain.bin
QEMUで実行
HiFive1がないのでQEMUで実行する。
バイナリの実行
$ qemu-system-riscv32 -nographic -machine sifive_e -kernel ./notmain.elf
GDB
$ qemu-system-riscv32 -nographic -machine sifive_e -kernel ./notmain.elf -S -s
-S
はGDBコマンドまでプログラムを実行状態にしない。
-s
は-gdb tcp::1234
と同じ。
gdbgui
$ gdbgui -g riscv32-unknown-elf-gdb notmain.elf
デバッグコマンド
target remote localhost:1234
ここからgdbguiでデバッグが可能。CPUの先頭で止まっているのでデバッグし放題となる。
まとめ
写経だいじ。
参考資料
自分の備忘録も兼ねてお世話になったところをまとめておく。
SiFive HiFive1 Getting Started Guide