昨日の記事でVivadoシミュレータ上で実行したriscv-testsのaddiテストが正常に終了しない件についての調査を行い、テストベンチ上の$finish
に辿り着く条件を満たしていないことがわかった。
結構SCR1の中身を真面目に追っていかないとわからない感じがしたが、そもそもこのriscv-testsで何が実行されているのかをちゃんとは把握していないのでriscv-testsの中身の確認をしていく。ということもありタイトルもそれっぽく変更。
addi.Sの中身の確認
現在実行中のaddi命令の試験についての大まかな流れを確認していく。
現在Vivadoシミュレータ上にインスタンスされているSCR1は32bitCPUなのでrv32uiのディレクトリの下のコードがビルドされているが、中身はrv64uiへのインクルードパスが記載されており実際にコードとしては使用されるのはrv64uiのaddiテストになる。
# See LICENSE for license details. #***************************************************************************** # addi.S #----------------------------------------------------------------------------- # # Test addi instruction. # #include "riscv_test.h" #include "test_macros.h" RVTEST_RV64U RVTEST_CODE_BEGIN #------------------------------------------------------------- # Arithmetic tests #------------------------------------------------------------- TEST_IMM_OP( 2, addi, 0x00000000, 0x00000000, 0x000 ); TEST_IMM_OP( 3, addi, 0x00000002, 0x00000001, 0x001 ); TEST_IMM_OP( 4, addi, 0x0000000a, 0x00000003, 0x007 ); TEST_IMM_OP( 5, addi, 0xfffffffffffff800, 0x0000000000000000, 0x800 ); TEST_IMM_OP( 6, addi, 0xffffffff80000000, 0xffffffff80000000, 0x000 ); TEST_IMM_OP( 7, addi, 0xffffffff7ffff800, 0xffffffff80000000, 0x800 ); TEST_IMM_OP( 8, addi, 0x00000000000007ff, 0x00000000, 0x7ff ); TEST_IMM_OP( 9, addi, 0x000000007fffffff, 0x7fffffff, 0x000 ); TEST_IMM_OP( 10, addi, 0x00000000800007fe, 0x7fffffff, 0x7ff ); TEST_IMM_OP( 11, addi, 0xffffffff800007ff, 0xffffffff80000000, 0x7ff ); TEST_IMM_OP( 12, addi, 0x000000007ffff7ff, 0x000000007fffffff, 0x800 ); TEST_IMM_OP( 13, addi, 0xffffffffffffffff, 0x0000000000000000, 0xfff ); TEST_IMM_OP( 14, addi, 0x0000000000000000, 0xffffffffffffffff, 0x001 ); TEST_IMM_OP( 15, addi, 0xfffffffffffffffe, 0xffffffffffffffff, 0xfff ); TEST_IMM_OP( 16, addi, 0x0000000080000000, 0x7fffffff, 0x001 ); #------------------------------------------------------------- # Source/Destination tests #------------------------------------------------------------- TEST_IMM_SRC1_EQ_DEST( 17, addi, 24, 13, 11 ); #------------------------------------------------------------- # Bypassing tests #------------------------------------------------------------- TEST_IMM_DEST_BYPASS( 18, 0, addi, 24, 13, 11 ); TEST_IMM_DEST_BYPASS( 19, 1, addi, 23, 13, 10 ); TEST_IMM_DEST_BYPASS( 20, 2, addi, 22, 13, 9 ); TEST_IMM_SRC1_BYPASS( 21, 0, addi, 24, 13, 11 ); TEST_IMM_SRC1_BYPASS( 22, 1, addi, 23, 13, 10 ); TEST_IMM_SRC1_BYPASS( 23, 2, addi, 22, 13, 9 ); TEST_IMM_ZEROSRC1( 24, addi, 32, 32 ); TEST_IMM_ZERODEST( 25, addi, 33, 50 ); TEST_PASSFAIL RVTEST_CODE_END .data RVTEST_DATA_BEGIN TEST_DATA RVTEST_DATA_END
上記のようにRVTEST_CODE_BEGIN
で各種初期化を実行した後、addi命令の基本的な処理をTEST_IMM_OP
やTEST_IMM_DEST_BYPASS
といったマクロに渡して命令のテストを行っていく。
RVTEST_CODE_BEGIN
で各種初期化
まずはRVTEST_CODE_BEGIN
から。オリジナルのriscv-testsのgithubではこのマクロはsubmoduleとして登録されているriscv-envに登録されているriscv_test.hに実装されているが、SCR1の環境ではこのファイルに実装されている各種テスト用マクロと、riscv-tests/macros/scalar/test_macros.hに登録されている各テスト実行用のマクロをまとめたファイルがtests/common/riscv_macros.hに用意されている。
#define RVTEST_CODE_BEGIN \ .section .text.init; \ .org 0xC0, 0x00; \ .align 6; \ .weak stvec_handler; \ .weak mtvec_handler; \ trap_vector: \ /* test whether the test came from pass/fail */ \ csrr t5, mcause; \ li t6, CAUSE_USER_ECALL; \ beq t5, t6, _report; \ li t6, CAUSE_SUPERVISOR_ECALL; \ beq t5, t6, _report; \ li t6, CAUSE_MACHINE_ECALL; \ beq t5, t6, _report; \ /* if an mtvec_handler is defined, jump to it */ \ la t5, mtvec_handler; \ beqz t5, 1f; \ jr t5; \ /* was it an interrupt or an exception? */ \ 1: csrr t5, mcause; \ bgez t5, handle_exception; \ INTERRUPT_HANDLER; \ handle_exception: \ /* we don't know how to handle whatever the exception was */ \ other_exception: \ /* some unhandlable exception occurred */ \ li a0, 0x1; \ _report: \ j sc_exit; \ .align 6; \ .globl _start; \ _start: \ RISCV_MULTICORE_DISABLE; \ /*INIT_SPTBR;*/ \ /*INIT_PMP;*/ \ DELEGATE_NO_TRAPS; \ li TESTNUM, 0; \ la t0, trap_vector; \ csrw mtvec, t0; \ CHECK_XLEN; \ /* if an stvec_handler is defined, delegate exceptions to it */ \ la t0, stvec_handler; \ beqz t0, 1f; \ csrw stvec, t0; \ li t0, (1 << CAUSE_LOAD_PAGE_FAULT) | \ (1 << CAUSE_STORE_PAGE_FAULT) | \ (1 << CAUSE_FETCH_PAGE_FAULT) | \ (1 << CAUSE_MISALIGNED_FETCH) | \ (1 << CAUSE_USER_ECALL) | \ (1 << CAUSE_BREAKPOINT); \ csrw medeleg, t0; \ csrr t1, medeleg; \ bne t0, t1, other_exception; \ 1: csrwi mstatus, 0; \ init; \ EXTRA_INIT; \ EXTRA_INIT_TIMER; \ la t0, _run_test; \ csrw mepc, t0; \ csrr a0, mhartid; \ mret; \ .section .text; \ _run_test:
やってることをざっくり書くと以下の通り。
trap_vector
の実装_start
の宣言と実装
因みに上記のアセンブラコード中で使用されているa0
やらt0
やらはRISC-Vの持っているレジスタ(x0-x31とか)を指しており、コンパイラでどのように使用するかによって名前が割り振られている。最初、何も知らずにアセンブラみて混乱したので一応。
対応は以下の通りで、ここではSCR1に関係しているx0-x31のみ記載(この表自体はuser-level ISAのchapter 20に記載されている)。
Register | ABI Name(※) | Description | Saver |
---|---|---|---|
x0 | zero | Hard-wired zero | - |
x1 | ra | Return address | Caller |
x2 | sp | Stack pointer | Callee |
x3 | gp | Global pointer | - |
x4 | tp | Thread pointer | - |
x5 | t0 | Temporary/alternate link register | Caller |
x6-7 | t1-2 | Temporaries | Caller |
x8 | s0/fp | Saved register/frame pointer | Callee |
x9 | s1 | Saved register | Callee |
x10-11 | a0-1 | Function arguments/return values | Caller |
x12-17 | a2-7 | Function arguments | Caller |
x18-27 | s2-11 | Saved registers | Callee |
x28-31 | t3-6 | Temporaries | Caller |
※ABI(Application Binary Interface)の略らしい。
因みに以下にこの辺のアセンブラのことがまとめてられているのでこちらも参照するとわかりやすいはず。
各命令のテスト
tests/common/riscv_macros.hに定義されているテスト用マクロを使って命令のテストを実行する。
一例としてTEST_IMM_OP
の中身は以下のようになっている。(ファイルはsim/tests/common/riscv_macros.h)
#define TEST_IMM_OP( testnum, inst, result, val1, imm ) \ TEST_CASE( testnum, x30, result, \ li x1, MASK_XLEN(val1); \ inst x30, x1, SEXT_IMM(imm); \ )
マクロの中ではTEST_CASE
マクロを呼んでおり、この中でテストが実行される↓
#define TEST_CASE( testnum, testreg, correctval, code... ) \ test_ ## testnum: \ code; \ li x29, MASK_XLEN(correctval); \ li TESTNUM, testnum; \ bne testreg, x29, fail;
実際の処理としてはTEST_IMM_OP
のinst
引数に設定した命令(今回はaddi)を実行した結果を格納したx30
と期待値が格納されているx29
を比較して値が不一致の場合にfail
ラベルにジャンプする。
テストのPASS/FAIL判定
このfail
ラベルは各テストの最後に実行されるTEST_PASSFAIL
マクロに定義されている。
#define TEST_PASSFAIL \ bne x0, TESTNUM, pass; \ fail: \ RVTEST_FAIL; \ pass: \ RVTEST_PASS \
fail
/pass
ラベルで実行されるのはriscv_test.h
にて定義されるRVTEST_FAIL
/RVTEST_PASS
マクロでa1
レジスタに最後に実行された試験番号(TEST_NUM
)、a0
レジスタにテスト結果(0x0:PASS/0x1:FAIL)を格納して対象命令の試験が終了となる。
#define RVTEST_PASS \ fence; \ mv a1, TESTNUM; \ li a0, 0x0; \ ecall #define TESTNUM x28 #define RVTEST_FAIL \ fence; \ mv a1, TESTNUM; \ li a0, 0x1; \ ecall
ということで流れをまとめると
RVTEST_CODE_BEGIN
でテストの準備TEST_IMM_OP
等のテスト用マクロを使ってテストを実行- 各テスト結果がPASSならそのまま次のテストへ
- FAILの場合には、failラベルにジャンプして試験終了
- テストの結果自体は最終状態における
a0
レジスタの値をチェックすればわかるようになっている。 - PASS or FAILにかかわらず最終的には
ecall
が発行される
これで大体内容は把握できたかな。やっぱり流れ的には最後のRVTEST_PASS
/RVTEST_FAIL
からtrap_handler
へジャンプした後にsc_exit
を経て最終アドレスな気がする。
ということはecall
命令がうまく処理されていないことになるが、そもそもecall
命令って何??な状態なので次回はecall
命令の中身を把握するところからやっていきたい。