第16章
U-modeの実装
本章ではRISC-Vで最も低い特権レベルであるUserモード(U-mode)を実装します。U-modeはM-modeに管理されてアプリケーションを動かすための特権レベルであり、M-modeで利用できていたほとんどのCSR、機能が制限されます。
本章で実装、変更する主な機能は次の通りです。それぞれ解説しながら実装していきます。
- mstatusレジスタの一部のフィールド
- CSRのアクセス権限、MRET命令の実行権限の確認
- mcounterenレジスタ
- 割り込み条件、トラップの動作
16.1 misa.Extensionsの変更
U-modeを実装しているかどうかはmisa.ExtensionsのUビットで確認できます。
misa.ExtensionsのUビットを1
にします(リスト16.1)。
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)。
1: // mstatus 2: const MSTATUS_UXL: UInt64 = 2 << 32;
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)。
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)。
1: const MSTATUS_WMASK : UIntX = 'h0000_0000_0020_1888 as UIntX;
MPPには2'b00
(U-mode)と2'b11
(M-mode)のみ設定できるようにします。サポートしていない値を書き込もうとする場合は現在の値を維持します(リスト16.6、リスト16.7)。
1: CsrAddr::MSTATUS : mstatus = validate_mstatus(mstatus, wdata);
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)。
1: let mstatus_mpp : PrivMode = mstatus[12:11] as PrivMode; 2: let mstatus_mpie: logic = mstatus[7]; 3: let mstatus_mie : logic = mstatus[3];
1: let interrupt_mode: PrivMode = PrivMode::M;
1: let expt_mode : PrivMode = PrivMode::M;
1: let trap_return_mode: PrivMode = mstatus_mpp;
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
を書き込みます。
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)。
1: let expt_csr_priv_violation: logic = is_wsc && csr_addr[9:8] >: mode; // attempt to access CSR without privilege level
1: let raise_expt: logic = valid && (expt_info.valid || expt_write_readonly_csr || expt_csr_priv_violation);
1: expt_write_readonly_csr: CsrCause::ILLEGAL_INSTRUCTION, 2: expt_csr_priv_violation: CsrCause::ILLEGAL_INSTRUCTION, 3: default : 0,
16.6 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)。
1: var mcounteren: UInt32;
1: mie = 0; 2: mcounteren = 0; 3: mscratch = 0;
1: CsrAddr::MIE : mie, 2: CsrAddr::MCOUNTEREN: {1'b0 repeat XLEN - 32, mcounteren}, 3: CsrAddr::MCYCLE : mcycle,
1: const MCOUNTEREN_WMASK: UIntX = 'h0000_0000_0000_0007 as UIntX;
1: CsrAddr::MIE : MIE_WMASK, 2: CsrAddr::MCOUNTEREN: MCOUNTEREN_WMASK, 3: CsrAddr::MSCRATCH : MSCRATCH_WMASK,
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)。
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
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)。
1: let expt_trap_return_priv: logic = is_mret && mode <: PrivMode::M; // attempt to execute trap return instruction in low privilege level
1: let raise_expt: logic = valid && (expt_info.valid || expt_write_readonly_csr || expt_csr_priv_violation || expt_zicntr_priv || expt_trap_return_priv);
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-mode | Environment call from M-mode | 11 |
S-mode | Environment call from S-mode | 9 |
U-mode | Environment call from U-mode | 8 |
ここで各例外のcauseがU-modeのcauseに特権レベルの数値を足したものになっていることを利用します。CsrCause
型にEnvironment call from U-mode例外のcauseを追加します(リスト16.28)。
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)。
1: rdata : output UIntX , 2: mode : output PrivMode , 3: raise_trap : output logic ,
1: var csru_priv_mode : PrivMode;
1: rdata : csru_rdata , 2: mode : csru_priv_mode , 3: raise_trap : csru_raise_trap ,
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で割り込みが発生する条件は次の通りです。
- 割り込み原因に対応したmipレジスタのビットが
1
である - 割り込み原因に対応したmieレジスタのビットが
1
である - 現在の特権レベルがM-mode未満である。またはmstatus.MIEが
1
である
M-modeだけの場合と違い、現在の特権レベルがU-modeのときはグローバル割り込みイネーブルビット(mstatus.MIE)の値は考慮されずに割り込みが発生します。
現在の特権レベルによって割り込みが発生する条件を切り替えます。U-modeのときはmstatus.MIEを考慮しないようにします(リスト16.33)。
1: let raise_interrupt : logic = valid && can_intr && (mode != PrivMode::M || mstatus_mie) && interrupt_pending != 0;