Skip to content

U-modeの実装

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

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

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

misa.Extensionsの変更

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

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

▼リスト16.1: Uビットを1にする (csrunit.veryl) 差分をみる

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) 差分をみる

veryl
// mstatus
const MSTATUS_UXL: UInt64 = 2 << 32;

▼リスト16.3: UXLの初期値を設定する (csrunit.veryl) 差分をみる

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) 差分をみる

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) 差分をみる

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) 差分をみる

veryl
CsrAddr::MSTATUS : mstatus  = validate_mstatus(mstatus, wdata);

▼リスト16.7: mstatusレジスタの値を確認する関数 (csrunit.veryl) 差分をみる

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) 差分をみる

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) 差分をみる

veryl
let interrupt_mode: PrivMode = PrivMode::M;

▼リスト16.10: 例外の遷移先の特権レベルを示す変数 (csrunit.veryl) 差分をみる

veryl
let expt_mode  : PrivMode = PrivMode::M;

▼リスト16.11: MRET命令の遷移先の特権レベルを示す変数 (csrunit.veryl) 差分をみる

veryl
let trap_return_mode: PrivMode = mstatus_mpp;

▼リスト16.12: 遷移先の特権レベルを求める (csrunit.veryl) 差分をみる

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)

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) 差分をみる

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) 差分をみる

veryl
let raise_expt: logic = valid && (expt_info.valid || expt_write_readonly_csr || expt_csr_priv_violation);

▼リスト16.16: causeを設定する (csrunit.veryl) 差分をみる

veryl
expt_write_readonly_csr: CsrCause::ILLEGAL_INSTRUCTION,
expt_csr_priv_violation: CsrCause::ILLEGAL_INSTRUCTION,
default                : 0,

mcounterenレジスタの実装

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) 差分をみる

veryl
var mcounteren: UInt32;

▼リスト16.18: mcounterenレジスタを0でリセットする (csrunit.veryl) 差分をみる

veryl
mie        = 0;
mcounteren = 0;
mscratch   = 0;

▼リスト16.19: rdataにmcounterenレジスタを設定する (csrunit.veryl) 差分をみる

veryl
CsrAddr::MIE       : mie,
CsrAddr::MCOUNTEREN: {1'b0 repeat XLEN - 32, mcounteren},
CsrAddr::MCYCLE    : mcycle,

▼リスト16.20: 書き込みマスクの定義 (csrunit.veryl) 差分をみる

veryl
const MCOUNTEREN_WMASK: UIntX = 'h0000_0000_0000_0007 as UIntX;

▼リスト16.21: wmaskに書き込みマスクを設定する (csrunit.veryl) 差分をみる

veryl
CsrAddr::MIE       : MIE_WMASK,
CsrAddr::MCOUNTEREN: MCOUNTEREN_WMASK,
CsrAddr::MSCRATCH  : MSCRATCH_WMASK,

▼リスト16.22: mcounterenレジスタの書き込み (csrunit.veryl) 差分をみる

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) 差分をみる

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) 差分をみる

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) 差分をみる

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) 差分をみる

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) 差分をみる

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-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を追加します ( リスト28 )。

▼リスト16.28: CsrCause型に例外のcauseを追加する (eei.veryl) 差分をみる

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) 差分をみる

veryl
rdata      : output  UIntX               ,
mode       : output  PrivMode            ,
raise_trap : output  logic               ,

▼リスト16.30: csrunitから現在の特権レベルを受け取る変数 (core.veryl) 差分をみる

veryl
var csru_priv_mode  : PrivMode;

▼リスト16.31: csrunitモジュールのインスタンスから現在の特権レベルを受け取る (core.veryl) 差分をみる

veryl
rdata      : csru_rdata           ,
mode       : csru_priv_mode       ,
raise_trap : csru_raise_trap      ,

▼リスト16.32: Environment call from U-mode例外のcauseに特権レベルの数値を足す (core.veryl) 差分をみる

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で割り込みが発生する条件は次の通りです。

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

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

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

▼リスト16.33: U-modeのとき、割り込みの発生条件を変更する (csrunit.veryl) 差分をみる

veryl
let raise_interrupt  : logic = valid && can_intr && (mode != PrivMode::M || mstatus_mie) && interrupt_pending != 0;

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