Verylで作るCPU
Star

第16章
U-modeの実装

本章ではRISC-Vで最も低い特権レベルであるUserモード(U-mode)を実装します。U-modeはM-modeに管理されてアプリケーションを動かすための特権レベルであり、M-modeで利用できていたほとんどのCSR、機能が制限されます。

本章で実装、変更する主な機能は次の通りです。それぞれ解説しながら実装していきます。

  1. mstatusレジスタの一部のフィールド
  2. CSRのアクセス権限、MRET命令の実行権限の確認
  3. mcounterenレジスタ
  4. 割り込み条件、トラップの動作

16.1 misa.Extensionsの変更

U-modeを実装しているかどうかはmisa.ExtensionsのUビットで確認できます。

misa.ExtensionsのUビットを1にします(リスト16.1)。

リスト16.1: リスト16.1: Uビットを1にする (csrunit.veryl)
1:     let misa    : UIntX  = {2'd2, 1'b0 repeat XLEN - 28, 26'b00000100000001000100000101}; // U, M, I, C, A

16.2 mstatus.UXLの実装

U-modeのときのXLENはUXLENと定義されておりmstatus.UXLで確認できます。仕様上はmstatus.UXLの書き換えでUXLENを変更できるように実装できますが、本書ではUXLENが常に64になるように実装します。

mstatus.UXLを64を示す値である2に設定します(リスト16.2リスト16.3)。

リスト16.2: リスト16.2: mstatus.UXLの定義 (eei.veryl)
1:     // mstatus
2:     const MSTATUS_UXL: UInt64 = 2 << 32;
リスト16.3: リスト16.3: UXLの初期値を設定する (csrunit.veryl)
1:     always_ff {
2:         if_reset {
3:             mode     = PrivMode::M;
4:             mstatus  = MSTATUS_UXL;
5:             mtvec    = 0;

16.3 mstatus.TWの実装

mstatus.TWは、M-modeよりも低い特権レベルでWFI命令を実行するときに時間制限(Timeout Wait)を設けるためのビットです。mstatus.TWが0のとき時間制限はありません。1に設定されているとき、CPUの実装固有の時間だけ実行の再開を待ち、時間制限を過ぎるとIllegal instruction例外を発生させます。

本書ではmstatus.TWが1のときに無限時間待てることにして、例外の実装を省略します。mstatus.TWを書き換えられるようにします(リスト16.4)。

リスト16.4: リスト16.4: 書き込みマスクを変更する (csrunit.veryl)
1:     const MSTATUS_WMASK : UIntX = 'h0000_0000_0020_0088 as UIntX;

16.4 mstatus.MPPの実装

M-mode、U-modeだけが存在する環境でトラップが発生するとき、CPUはmstatusレジスタのMPPフィールドに現在の特権レベル(を示す値)を保存し、特権レベルをM-modeに変更します。また、MRET命令を実行するとmstatus.MPPの特権レベルに移動するようになります。

これにより、トラップによるU(M)-modeからM-modeへの遷移、MRET命令によるM-modeからU-modeへの遷移を実現できます。

MRET命令を実行するとmstatus.MPPは実装がサポートする最低の特権レベルに設定されます。

M-modeからU-modeに遷移したいときは、mstatus.MPPをU-modeの値に変更し、U-modeで実行を開始したいアドレスをmepcレジスタに設定してMRET命令を実行します。

mstatus.MPPに値を書き込めるようにします(リスト16.5)。

リスト16.5: リスト16.5: 書き込みマスクを変更する (csrunit.veryl)
1:     const MSTATUS_WMASK : UIntX = 'h0000_0000_0020_1888 as UIntX;

MPPには2'b00(U-mode)と2'b11(M-mode)のみ設定できるようにします。サポートしていない値を書き込もうとする場合は現在の値を維持します(リスト16.6リスト16.7)。

リスト16.6: リスト16.6: mstatusの書き込み (csrunit.veryl)
1:     CsrAddr::MSTATUS : mstatus  = validate_mstatus(mstatus, wdata);
リスト16.7: リスト16.7: mstatusレジスタの値を確認する関数 (csrunit.veryl)
1:     function validate_mstatus (
2:         mstatus: input UIntX,
3:         wdata  : input UIntX,
4:     ) -> UIntX {
5:         var result: UIntX;
6:         result = wdata;
7:         // MPP
8:         if wdata[12:11] != PrivMode::M && wdata[12:11] != PrivMode::U {
9:             result[12:11] = mstatus[12:11];
10:         }
11:         return result;
12:     }

トラップが発生する、トラップから戻るときの遷移先の特権レベルを求めます(リスト16.8リスト16.9リスト16.10リスト16.11リスト16.12)。

リスト16.8: リスト16.8: ビットを変数として定義する (csrunit.veryl)
1:     let mstatus_mpp : PrivMode = mstatus[12:11] as PrivMode;
2:     let mstatus_mpie: logic    = mstatus[7];
3:     let mstatus_mie : logic    = mstatus[3];
リスト16.9: リスト16.9: 割り込みの遷移先の特権レベルを示す変数 (csrunit.veryl)
1:     let interrupt_mode: PrivMode = PrivMode::M;
リスト16.10: リスト16.10: 例外の遷移先の特権レベルを示す変数 (csrunit.veryl)
1:     let expt_mode  : PrivMode = PrivMode::M;
リスト16.11: リスト16.11: MRET命令の遷移先の特権レベルを示す変数 (csrunit.veryl)
1:     let trap_return_mode: PrivMode = mstatus_mpp;
リスト16.12: リスト16.12: 遷移先の特権レベルを求める (csrunit.veryl)
1:     let trap_mode_next: PrivMode = switch {
2:         raise_expt     : expt_mode,
3:         raise_interrupt: interrupt_mode,
4:         trap_return    : trap_return_mode,
5:         default        : PrivMode::U,
6:     };

トラップが発生するとき、mstatus.MPPに現在の特権レベルを保存します(リスト16.13)。また、トラップから戻るとき、特権レベルをmstatus.MPPに設定し、mstatus.MPPに実装がサポートする最小の特権レベルであるPrivMode::Uを書き込みます。

リスト16.13: リスト16.13: 特権レベル、mstatus.MPPを更新する (csrunit.veryl)
1:     if raise_trap {
2:         if raise_expt || raise_interrupt {
3:             ...
4:             // save current privilege level to mstatus.mpp
5:             @<b<|mstatus[12:11] = mode;|
6:         } else if trap_return {
7:             ...
8:             // set mstatus.mpp = U (least privilege level)
9:             mstatus[12:11] = PrivMode::U;
10:         }
11:         mode = trap_mode_next;

16.5 CSRのアクセス権限の確認

CSRのアドレスをcsr_addrとするとき、csr_addr[9:8]の2ビットはそのCSRにアクセスできる最低の特権レベルを表しています。これを下回る特権レベルでCSRにアクセスしようとするとIllegal instruction例外が発生します。

CSRのアドレスと特権レベルを確認して、例外を起こすようにします(リスト16.14リスト16.15リスト16.16)。

リスト16.14: リスト16.14: 現在の特権レベルでCSRにアクセスできるか判定する (csrunit.veryl)
1:     let expt_csr_priv_violation: logic = is_wsc && csr_addr[9:8] >: mode; // attempt to access CSR without privilege level
リスト16.15: リスト16.15: 例外の発生条件に追加する (csrunit.veryl)
1:     let raise_expt: logic = valid && (expt_info.valid || expt_write_readonly_csr || expt_csr_priv_violation);
リスト16.16: リスト16.16: causeを設定する (csrunit.veryl)
1:     expt_write_readonly_csr: CsrCause::ILLEGAL_INSTRUCTION,
2:     expt_csr_priv_violation: CsrCause::ILLEGAL_INSTRUCTION,
3:     default                : 0,

16.6 mcounterenレジスタの実装

mcounterenレジスタ

図16.1: mcounterenレジスタ

mcounterenレジスタは、M-modeの次に低い特権レベルでハードウェアパフォーマンスモニタにアクセスできるようにするかを制御する32ビットのレジスタです(図16.1)。CY、TM、IRビットはそれぞれcycle、time、instretにアクセスできるかどうかを制御します*1

[*1] hpmcounterレジスタを制御するHPMビットもありますが、hpmcounterレジスタを実装していないので実装しません

本章でM-modeの次に低い特権レベルとしてU-modeを実装するため、mcounterenレジスタはU-modeでのアクセスを制御します。mcounterenレジスタで許可されていないままU-modeでcycle、time、instretレジスタにアクセスしようとすると、Illelgal Instruction例外が発生します。

mcounterenレジスタを作成し、CY、TM、IRビットに書き込みできるようにします(リスト16.17リスト16.19リスト16.20リスト16.21リスト16.18リスト16.22)。

リスト16.17: リスト16.17: mcounterenレジスタの定義 (csrunit.veryl)
1:     var mcounteren: UInt32;
リスト16.18: リスト16.18: mcounterenレジスタを0でリセットする (csrunit.veryl)
1:     mie        = 0;
2:     mcounteren = 0;
3:     mscratch   = 0;
リスト16.19: リスト16.19: rdataにmcounterenレジスタを設定する (csrunit.veryl)
1:     CsrAddr::MIE       : mie,
2:     CsrAddr::MCOUNTEREN: {1'b0 repeat XLEN - 32, mcounteren},
3:     CsrAddr::MCYCLE    : mcycle,
リスト16.20: リスト16.20: 書き込みマスクの定義 (csrunit.veryl)
1:     const MCOUNTEREN_WMASK: UIntX = 'h0000_0000_0000_0007 as UIntX;
リスト16.21: リスト16.21: wmaskに書き込みマスクを設定する (csrunit.veryl)
1:     CsrAddr::MIE       : MIE_WMASK,
2:     CsrAddr::MCOUNTEREN: MCOUNTEREN_WMASK,
3:     CsrAddr::MSCRATCH  : MSCRATCH_WMASK,
リスト16.22: リスト16.22: mcounterenレジスタの書き込み (csrunit.veryl)
1:     CsrAddr::MIE       : mie        = wdata;
2:     CsrAddr::MCOUNTEREN: mcounteren = wdata[31:0];
3:     CsrAddr::MSCRATCH  : mscratch   = wdata;

U-modeでハードウェアパフォーマンスモニタにアクセスするとき、mcounterenレジスタのビットが0ならIllegal instruction例外を発生させます(リスト16.23リスト16.24)。

リスト16.23: リスト16.23: U-modeのとき、mcounterenレジスタを確認する (csrunit.veryl)
1:     let expt_zicntr_priv       : logic = is_wsc && mode == PrivMode::U && case csr_addr {
2:         CsrAddr::CYCLE  : !mcounteren[0],
3:         CsrAddr::TIME   : !mcounteren[1],
4:         CsrAddr::INSTRET: !mcounteren[2],
5:         default         : 0,
6:     }; // attemp to access Zicntr CSR without permission
リスト16.24: リスト16.24: causeを設定する (csrunit.veryl)
1:     expt_csr_priv_violation: CsrCause::ILLEGAL_INSTRUCTION,
2:     expt_zicntr_priv       : CsrCause::ILLEGAL_INSTRUCTION,
3:     default                : 0,

16.7 MRET命令の実行を制限する

MRET命令はM-mode以上の特権レベルのときにしか実行できません。M-mode未満の特権レベルでMRET命令を実行しようとするとIllegal instruction例外が発生します。

命令がMRET命令のとき、特権レベルを確認して例外を発生させます(リスト16.25リスト16.26リスト16.27)。

リスト16.25: リスト16.25: MRET命令を実行するとき、現在の特権レベルを確認する (csrunit.veryl)
1:     let expt_trap_return_priv: logic = is_mret && mode <: PrivMode::M; // attempt to execute trap return instruction in low privilege level
リスト16.26: リスト16.26: 例外の発生条件に追加する (csrunit.veryl)
1:     let raise_expt: logic = valid && (expt_info.valid || expt_write_readonly_csr || expt_csr_priv_violation || expt_zicntr_priv || expt_trap_return_priv);
リスト16.27: リスト16.27: causeを設定する (csrunit.veryl)
1:         expt_zicntr_priv       : CsrCause::ILLEGAL_INSTRUCTION,
2:         expt_trap_return_priv  : CsrCause::ILLEGAL_INSTRUCTION,
3:         default                : 0,
4:     };

16.8 ECALL命令のcauseを変更する

M-modeでECALL命令を実行するとEnvironment call from M-mode例外が発生します。これに対してU-modeでECALL命令を実行するとEnvironment call from U-mode例外が発生します。特権レベルと例外の対応は表16.1のようになっています。

表16.1: ECALL命令を実行したときに発生する例外

特権レベル例外cause
M-modeEnvironment call from M-mode11
S-modeEnvironment call from S-mode9
U-modeEnvironment call from U-mode8

ここで各例外のcauseがU-modeのcauseに特権レベルの数値を足したものになっていることを利用します。CsrCause型にEnvironment call from U-mode例外のcauseを追加します(リスト16.28)。

リスト16.28: リスト16.28: CsrCause型に例外のcauseを追加する (eei.veryl)
1:     STORE_AMO_ADDRESS_MISALIGNED = 6,
2:     ENVIRONMENT_CALL_FROM_U_MODE = 8,
3:     ENVIRONMENT_CALL_FROM_M_MODE = 11,

csrunitモジュールのmodeレジスタをポート宣言に移動し、IDステージでECALL命令をデコードするときにcauseにmodeを足します(リスト16.29リスト16.30リスト16.31リスト16.32)。

リスト16.29: リスト16.29: modeレジスタをポートに移動する (csrunit.veryl)
1:     rdata      : output  UIntX               ,
2:     mode       : output  PrivMode            ,
3:     raise_trap : output  logic               ,
リスト16.30: リスト16.30: csrunitから現在の特権レベルを受け取る変数 (core.veryl)
1:     var csru_priv_mode  : PrivMode;
リスト16.31: リスト16.31: csrunitモジュールのインスタンスから現在の特権レベルを受け取る (core.veryl)
1:     rdata      : csru_rdata           ,
2:     mode       : csru_priv_mode       ,
3:     raise_trap : csru_raise_trap      ,
リスト16.32: リスト16.32: Environment call from U-mode例外のcauseに特権レベルの数値を足す (core.veryl)
1:     } else if ids_inst_bits == 32'h00000073 {
2:         // ECALL
3:         exq_wdata.expt.valid      = 1;
4:         exq_wdata.expt.cause      = CsrCause::ENVIRONMENT_CALL_FROM_U_MODE;
5:         exq_wdata.expt.cause[1:0] = csru_priv_mode;
6:         exq_wdata.expt.value      = 0;

16.9 割り込み条件の変更

M-modeだけが実装されたCPUで割り込みが発生する条件は「15.1.2 RISC-Vの割り込み」で解説しましたが、M-modeとU-modeだけが実装されたCPUで割り込みが発生する条件は少し異なります。M-modeとU-modeだけが実装されたCPUで割り込みが発生する条件は次の通りです。

  1. 割り込み原因に対応したmipレジスタのビットが1である
  2. 割り込み原因に対応したmieレジスタのビットが1である
  3. 現在の特権レベルがM-mode未満である。またはmstatus.MIEが1である

M-modeだけの場合と違い、現在の特権レベルがU-modeのときはグローバル割り込みイネーブルビット(mstatus.MIE)の値は考慮されずに割り込みが発生します。

現在の特権レベルによって割り込みが発生する条件を切り替えます。U-modeのときはmstatus.MIEを考慮しないようにします(リスト16.33)。

リスト16.33: リスト16.33: U-modeのとき、割り込みの発生条件を変更する (csrunit.veryl)
1:     let raise_interrupt  : logic = valid && can_intr && (mode != PrivMode::M || mstatus_mie) && interrupt_pending != 0;