RISC-V 用のプロセッサのテストをするためにクロスコンパイラを導入してみました。
意外と詰まりどころがあったりしたので、インストールやコンパイルの方法をまとめておきたいと思います。
以下の二つの記事を参考にして書いています。
1. 使用したもの
- OS: Ubuntu 20.04
- Processor: AMD EPYC 7702P 64-Core
- riscv-gnu-toolchain
- Commit: d45cfc68be6ce0a2f69daf66e64fc446224b3416 (2021年4月7日)
riscv-gnu-toolchain
が今回のメインのクロスコンパイラです。
一応コミットのバージョンも書いておきましたが、最新のもので基本的には問題ないと思います。
2. インストール
以下のコマンドに倣って実行すればインストールできるはずです。--prefix
については自分がこのクロスコンパイラをインストールしたいパスを入れてください。
また、--with-arch
では自分が入れたい ISA
のみを基本指定すればいいのですが、筆者の環境では a
(Atomic 拡張) がないとエラーが出て怒られました。
一方、c
(Compressed 命令) を入れると、クロスコンパイル後に生成されるバイナリが 32 ビットではなく、16 ビットに圧縮されてしまうので、自作プロセッサのテストをしたいという今回の用途には向きません。
浮動小数点命令をサポートしたい場合は f
(Float) や d
(Double) を追加で入れるといいと思います。
拡張については詳しくは「オープンソース CPU「RISC-V」の研究」などに載っています。
$ git clone https://github.com/riscv/riscv-gnu-toolchain
$ ./configure --prefix=/opt/riscv32 --with-arch=rv32ima --with-abi=ilp32d
$ make linux
かなり時間がかかると思いますが、これがエラーなく終了するとクロスコンパイラが使えるようになっているはずです。
使いやすいように PATH
を通しておきましょう。/opt/riscv32
の部分は先程 --prefix
で指定したパスに適宜変更してください。
export PATH=/opt/riscv32/bin:$PATH
これを .bashrc
や .zshrc
などに追記して、以下のコマンドを実行してください。(.bashrc
に追記した場合だけ書いておきますが、他のシェルの場合も同じです。)
$ source ~/.bashrc
一度やってうまくいかなかった場合は、全てディレクトリごと削除して git clone
からやり直すのがいいと思います。
3. ソースコード作成
今回は fib(10)
を求めるプログラムをコンパイルしてみましょう。
以下の GitHub にあげている部分を参考にしながら進めていきます。
まず、再帰関数を用いて fib.c を実装しましょう。
int fib(int n) {
if(n <= 1) return 1;
return fib(n-1) + fib(n-2);
}
int main() {
fib(10);
for(;;) {}
return 0;
}
return
の前に for (;;) {}
を入れて無限ループにすることで、main
関数から抜けないようにしています。
次に、不要な初期化ルーチンが走らないように、以下の start.S を実装します。
.section .text.init;
.globl _start
_start:
call main
さらに、命令が 0 番地から実行されるように、以下の link.ld を作成します。
OUTPUT_ARCH( "riscv" )
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
.text.init : { *(.text.init) }
.tohost : { *(.tohost) }
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
_end = .;
}
4. コンパイル
これまで 3 つのファイル (fib.c、start.S、link.ld) を作成したと思いますが、これを元に次はコンパイルを行っていきます。
まず、前二つのファイルを実行ファイルにコンパイルします。riscv32-unknown-linux-gnu-gcc
の部分は OS によっては (?)、riscv32-unknown-elf-gcc
のこともあるようです。 -march
の部分はインストールの際に指定した --with-arch
と同じにすればいいはずです。
$ riscv32-unknown-linux-gnu-gcc -march=rv32ima -c -o start.o start.S
$ riscv32-unknown-linux-gnu-gcc -march=rv32ima -c -o fib.o fib.c
次に、先程生成した start.o
と fib.o
を link.ld
を使ってリンクしていきます。
$ riscv32-unknown-linux-gnu-ld fib.o start.o -Tlink.ld -nostartfiles -static -o fib.elf
さらに、コンパイルを進めてバイナリに変換します。
$ riscv32-unknown-linux-gnu-objcopy -O binary fib.elf fib.bin
ここまでで、基本的には終わりなのですが、Verilog
で使いやすいようにフォーマットを変形して出力しておきましょう。
fib.bin
はバイナリファイルなのですが、これを二進数の Verilog
用の表記に直すのは意外と難しかったです。
具体的にどうしたのかは以下の記事を参考にしてください。
実行したコードは以下のようになっていて、一度 hexdump
で 16 進数に変換してから hex2bin.sh で処理をしています。
$ hexdump -v -e '/4 "%08X" "\n"' fib.bin > fib.hex
$ ./hex2bin.sh fib.hex > fib.b
#!/bin/sh
while read line; do
printf '32'\''b'
echo "obase=2; ibase=16; ${line}" | bc | printf "%32s" $(cat) | sed -e 's/ /0/g'
echo ','
done < $1
fib.hex、fib.b のファイルは GitHub にあげているので参考にしてください。
また、disassemble をしたい場合には以下のようにして実行できます。
$ riscv32-unknown-linux-gnu-objdump -d fib.elf > fib.dump
これらをいちいち全て手で打って実行するのは面倒なので、 Makefile
にまとめておきました。
適宜少し修正すれば使えるはずなので、ご利用ください。
使い方としては、ARG
にコンパイルしたいファイル名 (拡張子抜き) を指定して、以下のようなコマンドを打つだけです。
$ make ARG=fib
さらに、今は C 言語からコンパイルしていますが、アセンブリからコンパイルしたい場合には、25 ~ 26 行目を以下のようにコメントアウトして make
コマンドを先程と同様に打ってください。
# $(ARG).S: $(ARG).c
# $(CC) $(CFLAGS) -S -o $(ARG).S $(ARG).c
5. まとめ
今回は RISC-V の自作プロセッサをテストするために、riscv-gnu-toolchain
というクロスコンパイラを使う方法を紹介しました。
インストールもオプションで意外と手こずったり、コンパイルもややこしかったりとつまりどころは意外とありました。
この記事が少しでも参考になれば嬉しいです