ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

SCR1の解析 - ecall命令の処理とriscv-testsの終了条件の確認

スポンサーリンク

昨日の記事の結びで以下のように書いた。

ということはecall命令がうまく処理されていないことになるが、そもそもecall命令って何??な状態なので次回はecall命令の中身を把握するところからやっていきたい。

www.tech-diningyo.info

ということでecall命令の確認を行い、その後にaddi.Sがどのように最終アドレスである0xf4に辿り着くのかを確認していく。

ecall命令

callとつくくらいなので、サブルーチンへのジャンプ的な動作だとは思っているのだけど、改めて確認しておこう。

ecall命令の説明はISAの3.2に説明が記載されている。

f:id:diningyo-kpuku-jougeki:20180924222916p:plain

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開発日記にもエントリがあった。

msyksphinz.hatenablog.com

自作されている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;

ということで以前に、以下のように書いたとおりの動きをするのが正しいことになる。

本来なら、この0x4acecall命令を経て、命令読み出しアドレスがSCR1_EXIT_ADDR=0xF8に到達しなければならないのだが、今のVivadoシミュレーション上はそうなっていないためシミュレーションが正常に終了しないことがわかった。

このコメントを書いた際にはecall命令の動作を特にきちんと把握もせず、0xf4のアドレスから逆に辿っていただけであった。そのため、SCR1の内部で追うべき点が絞りきれていない状況であったが、今回の確認を経てSCR1のecall命令発行時の動作がVivadoシミュレータ上でどのように処理されているかに焦点をおいて解析をかけていけば良さそう。

続きは次のエントリにて。そろそろ動くといいなぁ。。