ASFRV32I
ASFRV32Iは1ファイル189行のVerilogで実装したシングルサイクルのRISC-V RV32IのCPUです。RISC-V Unpriviledged ISA 20191213 に準拠しています。
この記事ではASFRV32Iの使い方を説明していきます。ASFRV32Iの設計と実装の詳細は別の記事を参照してください。
ASFRV32Iの動かし方
Icarus verlogでシミュレーションできます。Linuxなら大抵のディストリビューションでバイナリが用意されていると思います。Windows版もあります。
ダウンロード
gitでダウンロードしてください。
$ git clone https://github.com/asfdrwe/ASFRV32I.git
論理合成
Icarus verlogで論理合成します。
iverilog -o RV32I RV32I.v RV32I_test.v
シミュレーション実行
test.hexを読み込んで実行します。
./RV32I
プログラム(test.hex)の書き方
ASFRV32Iのメモリは命令データ兼用で4KBです。1行が8bit16進数のテキスト形式のtest.hexを読み込み実行します。RISC-Vバイナリを実行するためにはこのテキスト形式に変換する必要があります。
ハンドアセンブルの場合
RV32Iは4バイト固定長です。ASFRV32Iではリトルエンディアンで書きます。
lui x1, 0x20000(x1レジスタの上位20bitに0x20000を代入し下位12bitに0x000を代入)ならば機械語は
0b_0010_0000_0000_0000_0000_00001_0110111(2進数)
0x_20_00_00_B7(16進数)
なのでtest.hexは次のようになります。
B7
00
00
20
gcc
RISC-Vのツールチェイン を取得してビルドしてください。
ファイル取得
$ git clone https://github.com/riscv/riscv-gnu-toolchain
$ cd riscv-gnu-toolchain
$ git submodule update --init --recursive
ビルドしてパスを通します。
$ ./configure --prefix=/opt/riscv
$ make linux
$ make install
$ export PATH=/opt/riscv/bin:$PATH
ASFRV32IはOSなしのベアメタルです。ASFRV32Iはプログラムを0番地から実行するので下記のlink.ldのようなリンカスクリプトで実行コード(.textセクション)を0番地に配置しています。
OUTPUT_ARCH( "riscv" )
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
.text.init : { *(.text.init) }
. = ALIGN(0x0100);
.tohost : { *(.tohost) }
. = ALIGN(0x0100);
.text : { *(.text) }
. = ALIGN(0x0100);
.data : { *(.data) }
.bss : { *(.bss) }
_end = .;
}
アセンブリ
アセンブリで記述する場合は次のように実装します。_startをエントリポイントとして0番地に配置され_startからプログラムが実行されます。
test.S
.globl _start
_start:
lui x1, 0x50000
addi x1, x1, 0xca
_loop:
jal x2, _loop
.data
.align 4
testdata:
.dword 41
コンパイルは次のように行います。
$ riscv64-unknown-elf-gcc -o test.bin test.S -march=rv32g -mabi=ilp32 -nostdlib -nostartfiles -T ./link.ld
バイナリの変換
elf形式のバイナリを1行8bit16進数のテキストファイルに変換します。freedom-bin2hex.py をダウンロードしてください。実行にはpythonが必要です。
objcopyでbinaryをダンプしてfreedom-bin2hex.pyで1行8bit16進数のテキストファイルに変換します。
$ riscv64-unknown-elf-objcopy -O binary test test.bin
$ python freedom-bin2hex.py -w 8 test.bin test.hex
c言語
ベアメタルなのでint main()は使えません。printf等も使えません。スタックポインタを設定するスタートアップルーチンがないとサブルーチンの呼び出しができないので、0x0800をスタックに設定したstart.Sをスタートアップルーチンとします。start.Sは_mainをエントリポイントとしています。
.globl _start
_start:
li sp, 0x0800
jal _main
j
Cでのコード例
test2.c
void _main()
{
static int a, b, c;
a = 1;
b = 2;
c = 1 + 2;
int d, e, f, h;
d = 10;
e = 15;
f = e - d;
h = a - 10;
return;
}
コンパイルは次のようにstart.Sも一緒にアセンブル&リンクして-nostdlib -nostartfilesを指定してください。
ASFRV32Iでは未対応命令はNOP扱いなので掛け算等はNOPになるはずです(未確認)。
-march=rv32iを指定してlibgccをリンクすれば掛け算もRV32Iの範囲でなんとかしてくれるとは思いますが確認していません。
$ riscv64-unknown-elf-gcc -o test2.bin start.S test2.c -march=rv32g -mabi=ilp32 -nostdlib -nostartfiles -T ./link.ld
バイナリからtest.hexへの変換はアセンブリのときと同じです。
$ riscv64-unknown-elf-objcopy test2 -O binary test2.bin
$ freedom-bin2hex.py -w 8 test2.bin test.hex
riscv-tests
riscv-tests を使うことでRV32Iに正しく準拠しているか確認できます。
ファイル取得
$ git clone https://github.com/riscv/riscv-tests
$ cd riscv-tests
$ git submodule update --init --recursive
$ autoconf
ASFRV32Iはメモリが4KBで割り込みやCSR関係を実装していないので、0番地から実行するように修正しCSR命令と割り込み関連を削除します。
risc-testsのトップディレクトリで riscv-tests.patch を当ててください。
$ patch -p1 < ASFRV32Iの場所/riscv-tests.patch
hex形式にしたものとコンパイル後に逆アセンブルしたもの(*.dump)をtest以下に用意してあります。ASFRV32Iではecallが0番地への無条件分岐なのことに気をつけて動作確認してみてください。