第5章
riscv-testsによるテスト
第3章では、RV32IのCPUを実装しました。簡単なテストを作成して動作を確かめましたが、まだテストできていない命令が複数あります。そこで、riscv-testsというテストを利用することで、CPUがある程度正しく動いているらしいことを確かめます。
5.1 riscv-testsとは何か?
riscv-testsは、RISC-Vのプロセッサ向けのユニットテストやベンチマークテストの集合です。命令や機能ごとにテストが用意されており、これを利用することで簡単に実装を確かめられます。すべての命令のすべての場合を網羅するようなテストではないため、riscv-testsをパスしても、確実に実装が正しいとは言えないことに注意してください*1。
[*1] 実装の正しさを完全に確かめるには形式的検証(formal verification)を行う必要があります
GitHubのriscv-software-src/riscv-testsからソースコードをダウンロードできます。
5.2 riscv-testsのビルド
https://github.com/nananapo/riscv-tests-bin/tree/bin4
完成品を上記のURLにおいておきます。core/testにコピーしてください。
5.2.1 riscv-testsをビルドする
まず、riscv-testsをcloneします(リスト5.1)。
$ git clone https://github.com/riscv-software-src/riscv-tests $ cd riscv-tests $ git submodule update --init --recursive
riscv-testsは、プログラムの実行が0x80000000
から始まると仮定した設定になっています。しかし、CPUはアドレス0x00000000
から実行を開始するため、リンカにわたす設定ファイルenv/p/link.ld
を変更する必要があります(リスト5.2)。
riscv-testsをビルドします。必要なソフトウェアがインストールされていない場合、適宜インストールしてください(リスト5.3)。
$ cd riscv-testsをcloneしたディレクトリ $ autoconf $ ./configure --prefix=core/testへのパス $ make $ make install
core/testにshareディレクトリが作成されます。
5.2.2 成果物を$readmemhで読み込める形式に変換する
riscv-testsをビルドできましたが、これは$readmemh
システムタスクで読み込める形式(以降HEX形式と呼びます)ではありません。これをCPUでテストを実行できるように、ビルドしたテストのバイナリファイルをHEX形式に変換します。
まず、バイナリファイルをHEX形式に変換するPythonプログラムtest/bin2hex.py
を作成します(リスト5.4)。
このプログラムは、第二引数に指定されるバイナリファイルを、第一引数に与えられた数のバイト毎に区切り、16進数のテキストで出力します。
HEXファイルに変換する前に、ビルドした成果物を確認する必要があります。例えばtest/share/riscv-tests/isa/rv32ui-p-add
はELFファイル*2です。CPUはELFを直接に実行する機能を持っていないため、riscv64-unknown-elf-objcopy
を利用して、ELFファイルを余計な情報を取り除いたバイナリファイルに変換します(リスト5.5)。
[*2] ELF(Executable and Linkable Format)とは実行可能ファイルの形式です
$ find share/ -type f -not -name "*.dump" -exec riscv32-unknown-elf-objcopy -O binary {} {}.bin \;
最後に、objcopyで生成されたバイナリファイルを、PythonプログラムでHEXファイルに変換します(リスト5.6)。
$ find share/ -type f -name "*.bin" -exec sh -c "python3 bin2hex.py 4 {} > {}.hex" \;
5.3 テスト内容の確認
riscv-testsには複数のテストが用意されていますが、本章では、名前がrv32ui-p-
から始まるRV32I向けのテストを利用します。
例えば、ADD命令のテストであるtest/share/riscv-tests/isa/rv32ui-p-add.dump
を読んでみます(リスト5.7)。rv32ui-p-add.dump
は、rv32ui-p-add
のダンプファイルです。
命令のテストは次の流れで実行されます。
- _start : reset_vectorにジャンプする。
- reset_vector : 各種状態を初期化する。
- test_* : テストを実行する。命令の結果がおかしかったらfailにジャンプする。最後まで正常に実行できたらpassにジャンプする。
- fail、pass : テストの成否をレジスタに書き込み、trap_vectorにジャンプする。
- trap_vector : write_tohostにジャンプする。
- write_tohost : テスト結果をメモリに書き込む。ここでループする。
_start
から実行を開始し、最終的にwrite_tohost
に移動します。テスト結果はメモリの.tohost
に書き込まれます。.tohost
のアドレスは、リンカの設定ファイルに記述されています(リスト5.8)。プログラムのサイズは0x1000
よりも小さいため、.tohost
のアドレスは0x1000
になります。
5.4 テストの終了検知
テストを実行するとき、テストの終了を検知して、成功か失敗かを報告する必要があります。
riscv-testsはテストの終了を示すために、.tohost
にLSBが1
な値を書き込みます。書き込まれた値が32'h1
のとき、テストが正常に終了したことを表しています。それ以外のときは、テストが失敗したことを表しています。
riscv-testsが終了したことを検知する処理をtopモジュールに記述します。topモジュールでメモリへのアクセスを監視し、.tohost
にLSBが1
な値が書き込まれたら、test_success
に結果を書き込んでテストを終了します。(リスト5.9)。
test_success
はポートとして定義します(リスト5.10)。
アトリビュートによって、終了検知のコードとtest_success
ポートはTEST_MODE
マクロが定義されているときにのみ存在するようになっています。
5.5 テストの実行
試しにADD命令のテストを実行してみましょう。ADD命令のテストのHEXファイルはtest/share/riscv-tests/isa/rv32ui-p-add.bin.hex
です。
TEST_MODEマクロを定義してシミュレータをビルドし、正常に動くことを確認します(リスト5.11)。
$ make build $ make sim VERILATOR_FLAGS="-DTEST_MODE" ← TEST_MODEマクロを定義してビルドする $ ./obj_dir/sim test/share/riscv-tests/isa/rv32ui-p-add.bin.hex 0 # 4 00000000 : 0500006f # 8 00000050 : 00000093 ... # 593 00000040 : fc3f2223 itype : 000100 imm : ffffffc4 rs1[30] : 0000103c rs2[ 3] : 00000001 op1 : 0000103c op2 : ffffffc4 alu res : 00001000 mem stall : 1 mem rdata : ff1ff06f riscv-tests success! - ~/core/src/top.sv:26: Verilog $finish
riscv-tests success!
と表示され、テストが正常終了しました*3。
[*3] 実行が終了しない場合はどこかしらにバグがあります。rv32ui-p-add.dumpと実行ログを見比べて、頑張って原因を探してください
5.6 複数のテストの自動実行
ADD命令以外の命令もテストしたいですが、わざわざコマンドを手打ちしたくありません。自動でテストを実行して、その結果を報告するプログラムを作成しましょう。
test/test.py
を作成し、次のように記述します(リスト5.12)。
このPythonプログラムは、第2引数で指定したディレクトリに存在する、第3引数で指定した文字列を名前に含むファイルを、第1引数で指定したシミュレータで実行し、その結果を報告します。
次のオプションの引数が存在します。
- -r
- 第2引数で指定されたディレクトリの中にあるディレクトリも走査します。 デフォルトでは走査しません。
- -e 拡張子
-
指定した拡張子のファイルのみを対象にテストします。
HEXファイルをテストしたい場合は、
-e hex
にします。 デフォルトではhex
が指定されています。 - -o ディレクトリ
-
指定したディレクトリにテスト結果を格納します。
デフォルトでは
result
ディレクトリに格納します。 - -t 時間
- テストに時間制限を設けます。 0を指定すると時間制限はなくなります。 デフォルト値は10(秒)です。
テストが成功したか失敗したかの判定には、シミュレータの終了コードを利用しています。テストが失敗したときに終了コードが1
になるように、Verilatorに渡しているC++プログラムを変更します(リスト5.13)。
それでは、RV32Iのテストを実行しましょう。riscv-testsのRV32I向けのテストの接頭辞であるrv32ui-p-
を引数に指定します(リスト5.14)。
$ make build $ make sim VERILATOR_FLAGS="-DTEST_MODE" $ python3 test/test.py -r obj_dir/sim test/share rv32ui-p- PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-lh.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-sb.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-sltiu.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-sh.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-bltu.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-or.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-sra.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-xor.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-addi.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-srai.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-srli.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-auipc.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-slli.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-slti.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-lb.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-lw.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-bge.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-sub.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-xori.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-sw.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-beq.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-fence_i.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-jal.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-and.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-lui.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-bgeu.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-slt.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-sll.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-jalr.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-add.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-simple.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-andi.bin.hex FAIL : ~/core/test/share/riscv-tests/isa/rv32ui-p-ma_data.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-lhu.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-lbu.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-sltu.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-ori.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-blt.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-bne.bin.hex PASS : ~/core/test/share/riscv-tests/isa/rv32ui-p-srl.bin.hex Test Result : 39 / 40
rv32ui-p-
から始まる40個のテストの内、39個のテストに成功しました。テストの詳細な結果はresultsディレクトリに格納されています。
rv32ui-p-ma_data
は、ロードストアするサイズに整列されていないアドレスへのロードストア命令のテストです。これは後の章で例外として対処するため、今は無視します。