昨日の記事の結びで以下のように書いた。
ということは
ecall
命令がうまく処理されていないことになるが、そもそもecall
命令って何??な状態なので次回はecall
命令の中身を把握するところからやっていきたい。
ということでecall
命令の確認を行い、その後にaddi.Sがどのように最終アドレスである0xf4
に辿り着くのかを確認していく。
ecall命令
callとつくくらいなので、サブルーチンへのジャンプ的な動作だとは思っているのだけど、改めて確認しておこう。
ecall
命令の説明はISAの3.2に説明が記載されている。
The ECALL instruction is used to make a request to the supporting execution environment. When executed in U-mode, S-mode, or M-mode, it generates an environment-call-from-U-mode exception, environment-call-from-S-mode exception, or environment-call-from-M-mode exception, respectively, and performs no other operation. ECALL generates a different exception for each originating privilege mode so that environment call exceptions can be selectively delegated. A typical use case for Unix-like operating systems is to delegate to S-mode the environment-call-from-U-mode exception but not the others.
とりあえずざっくり言うと"現在のモードから見た特権モードに遷移して例外を発生させる"になりそう。
他にも探してみたらmsyksphinzさんのFPGA開発日記にもエントリがあった。
自作されているISSの実装に関してのエントリで、修正された部分のコードが載せられている↓。
case PrivMachine : { m_env->DebugPrint ("ECALL from Machine\n"); m_env->CSRWrite (SYSREG_ADDR_MCAUSE, Except_EcallFromMMode); m_env->SetPrivMode (PrivMachine); jmp_addr = 0x1c0; break; }
上記はMachineモードの処理だが
- MCAUSEレジスタへ要因のライト
- 動作モードをPrivMachineへ
jmp_addr=0x1c0
にセット
となっている。
ここで出てくるjmp_addr=0x1c0
は初期状態におけるMachineモードのハンドラのベクタアドレスなので例外発生させた後ハンドラにジャンプという動作で良さそう。
addi.Sのテストの終わり方
前項のecall
命令の動作を踏まえてaddi.Sの処理がどのように終了まで実行されるのかを確認していく。
addi.Sにおけるecall発行後の動作
前項で書いたとおり、ecall
を発行すると例外テーブルに飛ぶはず、ということがわかった。従って前回記載したaddi
命令のPASS/FAIL判定(以下のコード)のあとは、例外テーブルにジャンプすることになる。
#define RVTEST_PASS \ fence; \ mv a1, TESTNUM; \ li a0, 0x0; \ ecall /* 0x1c0にジャンプ */
とうことでriscv-testsをSCR1の環境で実行した場合の例外ハンドラのコードをチェックしていこう。
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;
上記を見るとここでの処理はmcause
レジスタをリードして、その中身に応じて各種処理をしていく、、という感じになる。
先ほどecall
命令の動作を確認した際に、msyksphinzさんのエントリから引用させてもらった内容にもあったように、mcause
レジスタにはなぜ例外が発生したかの要因が入っていて、SCR1の場合はMachineモードのみサポートのため、テストの最後に実行されたecall
命令が正常に動作すれば以下の部分が実行されることになる。
li t6, CAUSE_MACHINE_ECALL; \ beq t5, t6, _report; _report: \ j sc_exit; \
dumpファイルを使ってアドレスも確認
これをELFファイルから生成したdumpファイル上でもう一度見てみよう。以下がdumpファイルから必要な部分を抜粋したコードになる。
000000f4 <SIM_EXIT>: f4: 00000013 nop ~略~ 000001c0 <trap_vector>: 1c0: 34202f73 csrr t5,mcause 1c4: 4fa1 li t6,8 1c6: 03ff0563 beq t5,t6,1f0 <_report> 1ca: 4fa5 li t6,9 1cc: 03ff0263 beq t5,t6,1f0 <_report> 1d0: 4fad li t6,11 # CAUSE_MACHINE_ECALL 1d2: 01ff0f63 beq t5,t6,1f0 <_report> # <_report>へジャンプ 1d6: 00000f17 auipc t5,0x0 1da: e2af0f13 addi t5,t5,-470 # 0 <CL_SIZE-0x20> 1de: 000f0363 beqz t5,1e4 <trap_vector+0x24> 1e2: 8f02 jr t5 1e4: 34202f73 csrr t5,mcause 1e8: 000f5363 bgez t5,1ee <handle_exception> 1ec: a009 j 1ee <handle_exception> 000001ee <handle_exception>: 1ee: 4505 li a0,1 000001f0 <_report>: 1f0: 4700006f j 660 <sc_exit> # <sc_exit> へジャンプ 1f4: 00000013 nop ~略~ 00000660 <sc_exit>: 660: a95ff06f j f4 <SIM_EXIT> # <SIM_EXIT>へジャンプ 664: 00000013 nop
trap_vector直後からの処理を追っていくと以下のようになるはずだ。
- 0x1c0 : mcauseリード
- 0x1c4 - 0x1cc : 別のモードの要因チェック
- 0x1d0 : Machineモードの要因チェック
- 0x1d2 : <_report>にジャンプ
- 0x1ee : <sc_exit>にジャンプ
- 0x660 : <SIM_EXIT>にジャンプ
- 0xf4 : 実行終了
以前のエントリで書いたがこの0xf4はSCR1のシミュレーション環境における内側のforever
ループの終了条件である。
forever begin @(posedge clk) if (i_top.i_core_top.i_pipe_top.curr_pc == SCR1_EXIT_ADDR) begin bit test_pass; ~略~ break;
ということで以前に、以下のように書いたとおりの動きをするのが正しいことになる。
本来なら、この
0x4ac
のecall
命令を経て、命令読み出しアドレスがSCR1_EXIT_ADDR=0xF8
に到達しなければならないのだが、今のVivadoシミュレーション上はそうなっていないためシミュレーションが正常に終了しないことがわかった。
このコメントを書いた際にはecall
命令の動作を特にきちんと把握もせず、0xf4
のアドレスから逆に辿っていただけであった。そのため、SCR1の内部で追うべき点が絞りきれていない状況であったが、今回の確認を経てSCR1のecall
命令発行時の動作がVivadoシミュレータ上でどのように処理されているかに焦点をおいて解析をかけていけば良さそう。
続きは次のエントリにて。そろそろ動くといいなぁ。。