Spike を RV32 のプログラム向けにビルドする
Spike (riscv-isa-sim) は RISC-V の命令セットシミュレータで、(比較的) 手軽に RISC-V のプログラムを試す上でとても便利なツール。
Spike はデフォルトで RV64 のプログラム向けにビルドされるが、今回は Spike を RV32 のプログラム向けにビルドする。また、合わせて必要となる RISC-V のコンパイラ (riscv-gnu-toolchain) とプロキシカーネル (riscv-pk) をビルドし、Hello world プログラムを動かす。
RISC-V の開発環境は基本的に Linux だが、macOS や Windows の人向けに Docker を利用したビルド方法についても触れる。
- Spike RISC-V ISA Simulator - GitHub
- RISC-V GNU Compiler Toolchain - GitHub
- RISC-V Proxy Kernel and Boot Loader - GitHub
インストール
Spike と RISC-V GNU Compiler Toolchain、RISC-V Proxy Kernel をそれぞれインストールする。
基本的には GitHub のインストール方法に書かれていることと同じだが、configure のオプションが異なるので注意。
特に、Spike の configure で --with-target=riscv32-unknown-elf を指定し忘れると Spike が pk を見つけられなくなり、毎回 pk のフルパスを記述するか、pk のバイナリをカレントディレクトリにコピーしなければならなくなるので注意。
↓ 忘れるとこんな感じのエラーが出力される。
$ spike pk hello terminate called after throwing an instance of 'std::runtime_error' what(): could not open pk; searched paths: . (current directory) /opt/riscv/riscv64-unknown-elf/bin/ (based on configured --prefix and --with-target)
以下は Linux (Ubuntu) の人向けのインストール方法。WORK と RISCV のディレクトリはどこでもいいので適宜置き換える。
$ sudo apt update && sudo apt upgrade -y $ export WORK="$HOME/src" $ export RISCV="/opt/riscv" $ export PATH="$RISCV/bin:$PATH" $ mkdir -p $WORK # install riscv-gnu-toolchain $ sudo apt install -y autoconf automake autotools-dev curl python3 python3-pip libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev ninja-build git cmake libglib2.0-dev libslirp-dev $ cd $WORK $ git clone https://github.com/riscv/riscv-gnu-toolchain $ cd riscv-gnu-toolchain $ ./configure --prefix=$RISCV --with-arch=rv32gc --with-abi=ilp32 $ make -j $(nproc) # install riscv-pk $ cd $WORK $ git clone https://github.com/riscv-software-src/riscv-pk.git $ cd riscv-pk $ mkdir build $ cd build $ ../configure --prefix=$RISCV --host=riscv32-unknown-elf $ make -j $(nproc) $ sudo make install # install riscv-isa-sim (spike) $ sudo apt install -y device-tree-compiler $ cd $WORK $ git clone https://github.com/riscv-software-src/riscv-isa-sim.git $ cd riscv-isa-sim $ mkdir build $ cd build $ ../configure --prefix=$RISCV --with-target=riscv32-unknown-elf $ make -j $(nproc) $ sudo make install
macOS や Windows、Linux の人で Docker を使う場合の Dockerfile は以下の通り。
FROM ubuntu:latest ENV HOME="/root" ENV WORK="$HOME/src" ENV RISCV="/opt/riscv" ENV PATH="$RISCV/bin:$PATH" RUN apt-get update && apt-get install -y \ autoconf \ automake \ autotools-dev \ bc \ bison \ build-essential \ cmake \ curl \ device-tree-compiler \ flex \ gawk \ git \ gperf \ libexpat-dev \ libglib2.0-dev \ libgmp-dev \ libmpc-dev \ libmpfr-dev \ libtool \ ninja-build \ patchutils \ python3 \ python3-pip \ texinfo \ zlib1g-dev \ && rm -rf /var/lib/apt/lists/* # install riscv-gnu-toolchain WORKDIR $WORK RUN git clone https://github.com/riscv/riscv-gnu-toolchain WORKDIR $WORK/riscv-gnu-toolchain RUN ./configure --prefix=$RISCV --with-arch=rv32gc --with-abi=ilp32 RUN make -j $(nproc) RUN make distclean # install riscv-pk WORKDIR $WORK RUN git clone https://github.com/riscv-software-src/riscv-pk.git WORKDIR $WORK/riscv-pk/build RUN ../configure --prefix=$RISCV --host=riscv32-unknown-elf RUN make -j $(nproc) RUN make install RUN make distclean # install riscv-isa-sim (spike) WORKDIR $WORK RUN git clone https://github.com/riscv-software-src/riscv-isa-sim.git WORKDIR $WORK/riscv-isa-sim/build RUN ../configure --prefix=$RISCV --with-target=riscv32-unknown-elf RUN make -j $(nproc) RUN make install RUN make distclean WORKDIR $HOME
Docker の使い方
Docker のインストールは Install Docker Engine - Docker Docs から。あるいは macOS の人は homebrew (Docker - Homebrew Formulae) から。brew install --cask docker コマンドを実行して Docker をインストールする。
インストールが終わったら Docker Engine を起動しておく。
Dockerfile のあるディレクトリで以下のコマンドを実行すると Docker image が生成される。
$ docker build . -t spike_env
Docker image の生成が終了したら以下のコマンドで確認する。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE spike_env latest 2fa01e5c77eb 4 hours ago 15.2GB ubuntu latest 35a88802559d 3 weeks ago 78MB
コンテナを動かして中に入るためには以下のコマンドを実行する。
$ docker run -it spike_env
コンテナの外に出るためにはプロンプトに exit とタイプしてから Enter を押すか、ctrl + d を押す。
Hello world
Spike のインストールが終わったので Hello, world プログラムを動かしてみる。
hello.c に Hello world プログラムを記述する。
// hello.c #include <stdio.h> int main() { printf("Hello, world!\n"); return 0; }
RISC-V GNU Compiler Toolchain で hello.c をコンパイルし、実行ファイルとして hello を出力する。
$ riscv32-unknown-elf-gcc -o hello hello.c
Spike で hello を実行する。
$ spike --isa=rv32gc pk hello Hello, world!
Spike で Hello world プログラムを動かすことができた。
ここで、pk は RISC-V Proxy Kernel のこと。Spike は Spike だけでは入出力関連のシステムコールを実行できないため、これらの処理を Proxy Kernel を通してホスト OS 側で行うようにしている。
Hello world の内側
RISC-V で Hello world プログラムを動かすことができたが、ここまでではホストの環境で Hello world を実行したときとの違いがわからない。
なので、ここからは RISC-V の Hello world プログラムの内側をのぞいてみる。
まずは Hello world プログラムのアセンブリを見てみる。
以下のコマンドを実行すると Hello world プログラムの RISC-V アセンブリが hello.dump に出力される。
$ riscv32-unknown-elf-objdump -d hello > hello.dump
hello.dump はこんな感じになっている。RISC-V の仕様書 (RISC-V ISA manual - GitHub) を見てもらうと、RISC-V の命令が並んでいるなぁと思ってもらえるかも。
hello: file format elf32-littleriscv Disassembly of section .text: 00010094 <exit>: 10094: 1141 addi sp,sp,-16 10096: 4581 li a1,0 10098: c422 sw s0,8(sp) 1009a: c606 sw ra,12(sp) 1009c: 842a mv s0,a0 1009e: 7dc000ef jal 1087a <__call_exitprocs> 100a2: d481a783 lw a5,-696(gp) # 13988 <__stdio_exit_handler> 100a6: c391 beqz a5,100aa <exit+0x16> 100a8: 9782 jalr a5 100aa: 8522 mv a0,s0 100ac: 19c020ef jal 12248 <_exit> 000100b0 <register_fini>: 100b0: 00000793 li a5,0 100b4: c791 beqz a5,100c0 <register_fini+0x10> 100b6: 6549 lui a0,0x12 100b8: 99450513 addi a0,a0,-1644 # 11994 <__libc_fini_array> 100bc: 08d0006f j 10948 <atexit> 100c0: 8082 ret 000100c2 <_start>: 100c2: 00004197 auipc gp,0x4 100c6: b7e18193 addi gp,gp,-1154 # 13c40 <__global_pointer$> 100ca: d4818513 addi a0,gp,-696 # 13988 <__stdio_exit_handler> 100ce: 07018613 addi a2,gp,112 # 13cb0 <__BSS_END__> 100d2: 8e09 sub a2,a2,a0 100d4: 4581 li a1,0 100d6: 2d61 jal 1076e <memset> 100d8: 00001517 auipc a0,0x1 100dc: 87050513 addi a0,a0,-1936 # 10948 <atexit> 100e0: c519 beqz a0,100ee <_start+0x2c> 100e2: 00002517 auipc a0,0x2 100e6: 8b250513 addi a0,a0,-1870 # 11994 <__libc_fini_array> 100ea: 05f000ef jal 10948 <atexit> 100ee: 2d19 jal 10704 <__libc_init_array> 100f0: 4502 lw a0,0(sp) 100f2: 004c addi a1,sp,4 100f4: 4601 li a2,0 100f6: 20b1 jal 10142 <main> 100f8: bf71 j 10094 <exit> ...
次に、Spike が Hello world プログラムを実行したときの命令列をながめてみる。
以下のコマンドを実行すると、Spike が実行した命令列や命令の実行結果のコミット履歴などが hello.log に出力される。
$ spike --isa=rv32gc -l --log-commits --log=hello.log pk hello
hello.log はこんな感じになっている。全部で 515,083 行。スタートアップルーチンやら pk の処理などでやたら複雑になっていそう。
core 0: 0x00001000 (0x00000297) auipc t0, 0x0 core 0: 3 0x00001000 (0x00000297) x5 0x00001000 core 0: 0x00001004 (0x02028593) addi a1, t0, 32 core 0: 3 0x00001004 (0x02028593) x11 0x00001020 core 0: 0x00001008 (0xf1402573) csrr a0, mhartid core 0: 3 0x00001008 (0xf1402573) x10 0x00000000 core 0: 0x0000100c (0x0182a283) lw t0, 24(t0) core 0: 3 0x0000100c (0x0182a283) x5 0x80000000 mem 0x00001018 core 0: 0x00001010 (0x00028067) jr t0 core 0: 3 0x00001010 (0x00028067) core 0: 0x80000000 (0x1f80006f) j pc + 0x1f8 core 0: 3 0x80000000 (0x1f80006f) core 0: 0x800001f8 (0x00000093) li ra, 0 core 0: 3 0x800001f8 (0x00000093) x1 0x00000000 core 0: 0x800001fc (0x00000113) li sp, 0 core 0: 3 0x800001fc (0x00000113) x2 0x00000000 core 0: 0x80000200 (0x00000193) li gp, 0 core 0: 3 0x80000200 (0x00000193) x3 0x00000000 core 0: 0x80000204 (0x00000213) li tp, 0 core 0: 3 0x80000204 (0x00000213) x4 0x00000000 core 0: 0x80000208 (0x00000293) li t0, 0 core 0: 3 0x80000208 (0x00000293) x5 0x00000000 core 0: 0x8000020c (0x00000313) li t1, 0 ...