みつきんのメモ

組み込みエンジニアです。Interface誌で「My オリジナルLinuxの作り方」連載中

RISC-Vのベアメタル入門(自分用)

はじめに

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からメモリマップの部分を抜粋する。

f:id:mickey_happygolucky:20190303164944p:plain

ブートシーケンス

SiFive HiFive1 Getting Started Guide 1.0.2HiFive1 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言語のエントリポイント。 0x800010000x800020000x80003000に値を書き込んでいる。

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

-SGDBコマンドまでプログラムを実行状態にしない。

-s-gdb tcp::1234と同じ。

gdbgui

$ gdbgui -g riscv32-unknown-elf-gdb notmain.elf

デバッグコマンド

target remote localhost:1234

ここからgdbguiでデバッグが可能。CPUの先頭で止まっているのでデバッグし放題となる。

まとめ

写経だいじ。

参考資料

自分の備忘録も兼ねてお世話になったところをまとめておく。

SiFive HiFive1 Getting Started Guide

SiFive FE310-G000 Manual v2p3

RISC-Vのアセンブリ記述

RISC-V Assembly Programmer's Manual

QEMUにGDBを繋げてhariboteOSをデバッグする方法