U-modeの実装
本章ではRISC-Vで最も低い特権レベルであるUserモード(U-mode)を実装します。 U-modeはM-modeに管理されてアプリケーションを動かすための特権レベルであり、 M-modeで利用できていたほとんどのCSR、機能が制限されます。
本章で実装、変更する主な機能は次の通りです。 それぞれ解説しながら実装していきます。
- mstatusレジスタの一部のフィールド
- CSRのアクセス権限、MRET命令の実行権限の確認
- mcounterenレジスタ
- 割り込み条件、トラップの動作
misa.Extensionsの変更
U-modeを実装しているかどうかはmisa.ExtensionsのUビットで確認できます。
misa.ExtensionsのUビットを1にします (リスト1)。
▼リスト16.1: Uビットを1にする (csrunit.veryl) 差分をみる
let misa : UIntX = {2'd2, 1'b0 repeat XLEN - 28, 26'b00000100000001000100000101}; // U, M, I, C, A
mstatus.UXLの実装
U-modeのときのXLENはUXLENと定義されておりmstatus.UXLで確認できます。 仕様上はmstatus.UXLの書き換えでUXLENを変更できるように実装できますが、 本書ではUXLENが常に64になるように実装します。
mstatus.UXLを64を示す値である2に設定します ( リスト2、 リスト3 )。
▼リスト16.2: mstatus.UXLの定義 (eei.veryl) 差分をみる
// mstatus
const MSTATUS_UXL: UInt64 = 2 << 32;
▼リスト16.3: UXLの初期値を設定する (csrunit.veryl) 差分をみる
always_ff {
if_reset {
mode = PrivMode::M;
mstatus = MSTATUS_UXL;
mtvec = 0;
mstatus.TWの実装
mstatus.TWは、M-modeよりも低い特権レベルでWFI命令を実行するときに時間制限(Timeout Wait)を設けるためのビットです。 mstatus.TWが0のとき時間制限はありません。 1に設定されているとき、CPUの実装固有の時間だけ実行の再開を待ち、 時間制限を過ぎるとIllegal instruction例外を発生させます。
本書ではmstatus.TWが1のときに無限時間待てることにして、例外の実装を省略します。 mstatus.TWを書き換えられるようにします (リスト4)。
▼リスト16.4: 書き込みマスクを変更する (csrunit.veryl) 差分をみる
const MSTATUS_WMASK : UIntX = 'h0000_0000_0020_0088 as UIntX;
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に値を書き込めるようにします (リスト5)。
▼リスト16.5: 書き込みマスクを変更する (csrunit.veryl) 差分をみる
const MSTATUS_WMASK : UIntX = 'h0000_0000_0020_1888 as UIntX;
MPPには2'b00(U-mode)と2'b11(M-mode)のみ設定できるようにします。 サポートしていない値を書き込もうとする場合は現在の値を維持します ( リスト6、 リスト7 )。
▼リスト16.6: mstatusの書き込み (csrunit.veryl) 差分をみる
CsrAddr::MSTATUS : mstatus = validate_mstatus(mstatus, wdata);
▼リスト16.7: mstatusレジスタの値を確認する関数 (csrunit.veryl) 差分をみる
function validate_mstatus (
mstatus: input UIntX,
wdata : input UIntX,
) -> UIntX {
var result: UIntX;
result = wdata;
// MPP
if wdata[12:11] != PrivMode::M && wdata[12:11] != PrivMode::U {
result[12:11] = mstatus[12:11];
}
return result;
}
トラップが発生する、トラップから戻るときの遷移先の特権レベルを求めます ( リスト8、 リスト9、 リスト10、 リスト11、 リスト12 )。
▼リスト16.8: ビットを変数として定義する (csrunit.veryl) 差分をみる
let mstatus_mpp : PrivMode = mstatus[12:11] as PrivMode;
let mstatus_mpie: logic = mstatus[7];
let mstatus_mie : logic = mstatus[3];
▼リスト16.9: 割り込みの遷移先の特権レベルを示す変数 (csrunit.veryl) 差分をみる
let interrupt_mode: PrivMode = PrivMode::M;
▼リスト16.10: 例外の遷移先の特権レベルを示す変数 (csrunit.veryl) 差分をみる
let expt_mode : PrivMode = PrivMode::M;
▼リスト16.11: MRET命令の遷移先の特権レベルを示す変数 (csrunit.veryl) 差分をみる
let trap_return_mode: PrivMode = mstatus_mpp;
▼リスト16.12: 遷移先の特権レベルを求める (csrunit.veryl) 差分をみる
let trap_mode_next: PrivMode = switch {
raise_expt : expt_mode,
raise_interrupt: interrupt_mode,
trap_return : trap_return_mode,
default : PrivMode::U,
};
トラップが発生するとき、mstatus.MPPに現在の特権レベルを保存します (リスト13)。 また、トラップから戻るとき、特権レベルをmstatus.MPPに設定し、 mstatus.MPPに実装がサポートする最小の特権レベルであるPrivMode::Uを書き込みます。
▼リスト16.13: 特権レベル、mstatus.MPPを更新する (csrunit.veryl)
if raise_trap {
if raise_expt || raise_interrupt {
...
// save current privilege level to mstatus.mpp
@<b<|mstatus[12:11] = mode;|
} else if trap_return {
...
// set mstatus.mpp = U (least privilege level)
mstatus[12:11] = PrivMode::U;
}
mode = trap_mode_next;
CSRのアクセス権限の確認
CSRのアドレスをcsr_addrとするとき、 csr_addr[9:8]の2ビットはそのCSRにアクセスできる最低の特権レベルを表しています。 これを下回る特権レベルでCSRにアクセスしようとするとIllegal instruction例外が発生します。
CSRのアドレスと特権レベルを確認して、例外を起こすようにします ( リスト14、 リスト15、 リスト16 )。
▼リスト16.14: 現在の特権レベルでCSRにアクセスできるか判定する (csrunit.veryl) 差分をみる
let expt_csr_priv_violation: logic = is_wsc && csr_addr[9:8] >: mode; // attempt to access CSR without privilege level
▼リスト16.15: 例外の発生条件に追加する (csrunit.veryl) 差分をみる
let raise_expt: logic = valid && (expt_info.valid || expt_write_readonly_csr || expt_csr_priv_violation);
▼リスト16.16: causeを設定する (csrunit.veryl) 差分をみる
expt_write_readonly_csr: CsrCause::ILLEGAL_INSTRUCTION,
expt_csr_priv_violation: CsrCause::ILLEGAL_INSTRUCTION,
default : 0,
mcounterenレジスタの実装
mcounterenレジスタは、M-modeの次に低い特権レベルで ハードウェアパフォーマンスモニタにアクセスできるようにするかを制御する32ビットのレジスタです(図1)。 CY、TM、IRビットはそれぞれcycle、time、instretにアクセスできるかどうかを制御します[1]。
本章でM-modeの次に低い特権レベルとしてU-modeを実装するため、 mcounterenレジスタはU-modeでのアクセスを制御します。 mcounterenレジスタで許可されていないままU-modeでcycle、time、instretレジスタにアクセスしようとすると、 Illelgal Instruction例外が発生します。
mcounterenレジスタを作成し、CY、TM、IRビットに書き込みできるようにします ( リスト17、 リスト19、 リスト20、 リスト21、 リスト18、 リスト22 )。
▼リスト16.17: mcounterenレジスタの定義 (csrunit.veryl) 差分をみる
var mcounteren: UInt32;
▼リスト16.18: mcounterenレジスタを0でリセットする (csrunit.veryl) 差分をみる
mie = 0;
mcounteren = 0;
mscratch = 0;
▼リスト16.19: rdataにmcounterenレジスタを設定する (csrunit.veryl) 差分をみる
CsrAddr::MIE : mie,
CsrAddr::MCOUNTEREN: {1'b0 repeat XLEN - 32, mcounteren},
CsrAddr::MCYCLE : mcycle,
▼リスト16.20: 書き込みマスクの定義 (csrunit.veryl) 差分をみる
const MCOUNTEREN_WMASK: UIntX = 'h0000_0000_0000_0007 as UIntX;
▼リスト16.21: wmaskに書き込みマスクを設定する (csrunit.veryl) 差分をみる
CsrAddr::MIE : MIE_WMASK,
CsrAddr::MCOUNTEREN: MCOUNTEREN_WMASK,
CsrAddr::MSCRATCH : MSCRATCH_WMASK,
▼リスト16.22: mcounterenレジスタの書き込み (csrunit.veryl) 差分をみる
CsrAddr::MIE : mie = wdata;
CsrAddr::MCOUNTEREN: mcounteren = wdata[31:0];
CsrAddr::MSCRATCH : mscratch = wdata;
U-modeでハードウェアパフォーマンスモニタにアクセスするとき、 mcounterenレジスタのビットが0ならIllegal instruction例外を発生させます ( リスト23、 リスト24 )。
▼リスト16.23: U-modeのとき、mcounterenレジスタを確認する (csrunit.veryl) 差分をみる
let expt_zicntr_priv : logic = is_wsc && mode == PrivMode::U && case csr_addr {
CsrAddr::CYCLE : !mcounteren[0],
CsrAddr::TIME : !mcounteren[1],
CsrAddr::INSTRET: !mcounteren[2],
default : 0,
}; // attemp to access Zicntr CSR without permission
▼リスト16.24: causeを設定する (csrunit.veryl) 差分をみる
expt_csr_priv_violation: CsrCause::ILLEGAL_INSTRUCTION,
expt_zicntr_priv : CsrCause::ILLEGAL_INSTRUCTION,
default : 0,
MRET命令の実行を制限する
MRET命令はM-mode以上の特権レベルのときにしか実行できません。 M-mode未満の特権レベルでMRET命令を実行しようとするとIllegal instruction例外が発生します。
命令がMRET命令のとき、特権レベルを確認して例外を発生させます ( リスト25、 リスト26、 リスト27 )。
▼リスト16.25: MRET命令を実行するとき、現在の特権レベルを確認する (csrunit.veryl) 差分をみる
let expt_trap_return_priv: logic = is_mret && mode <: PrivMode::M; // attempt to execute trap return instruction in low privilege level
▼リスト16.26: 例外の発生条件に追加する (csrunit.veryl) 差分をみる
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: causeを設定する (csrunit.veryl) 差分をみる
expt_zicntr_priv : CsrCause::ILLEGAL_INSTRUCTION,
expt_trap_return_priv : CsrCause::ILLEGAL_INSTRUCTION,
default : 0,
};
ECALL命令のcauseを変更する
M-modeでECALL命令を実行するとEnvironment call from M-mode例外が発生します。 これに対してU-modeでECALL命令を実行するとEnvironment call from U-mode例外が発生します。 特権レベルと例外の対応は表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 |
▼リスト16.28: CsrCause型に例外のcauseを追加する (eei.veryl) 差分をみる
STORE_AMO_ADDRESS_MISALIGNED = 6,
ENVIRONMENT_CALL_FROM_U_MODE = 8,
ENVIRONMENT_CALL_FROM_M_MODE = 11,
csrunitモジュールのmodeレジスタをポート宣言に移動し、 IDステージでECALL命令をデコードするときにcauseにmodeを足します ( リスト29、 リスト30、 リスト31、 リスト32 )。
▼リスト16.29: modeレジスタをポートに移動する (csrunit.veryl) 差分をみる
rdata : output UIntX ,
mode : output PrivMode ,
raise_trap : output logic ,
▼リスト16.30: csrunitから現在の特権レベルを受け取る変数 (core.veryl) 差分をみる
var csru_priv_mode : PrivMode;
▼リスト16.31: csrunitモジュールのインスタンスから現在の特権レベルを受け取る (core.veryl) 差分をみる
rdata : csru_rdata ,
mode : csru_priv_mode ,
raise_trap : csru_raise_trap ,
▼リスト16.32: Environment call from U-mode例外のcauseに特権レベルの数値を足す (core.veryl) 差分をみる
} else if ids_inst_bits == 32'h00000073 {
// ECALL
exq_wdata.expt.valid = 1;
exq_wdata.expt.cause = CsrCause::ENVIRONMENT_CALL_FROM_U_MODE;
exq_wdata.expt.cause[1:0] = csru_priv_mode;
exq_wdata.expt.value = 0;
割り込み条件の変更
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を考慮しないようにします (リスト33)。
▼リスト16.33: U-modeのとき、割り込みの発生条件を変更する (csrunit.veryl) 差分をみる
let raise_interrupt : logic = valid && can_intr && (mode != PrivMode::M || mstatus_mie) && interrupt_pending != 0;
hpmcounterレジスタを制御するHPMビットもありますが、hpmcounterレジスタを実装していないので実装しません ↩︎