Skip to content

S-modeの実装 (1. CSRの実装)

本章ではSupervisorモード(S-mode)を実装します。 S-modeは主にOSのようなシステムアプリケーションを動かすために使用される特権レベルです。 S-modeがある環境には必ずU-modeが実装されています。

S-modeを導入することで変わる主要な機能はトラップです。 M-mode、U-modeだけの環境ではトラップで特権レベルをM-modeに変更していましたが、 M-modeではなくS-modeに遷移できるようになります。 これに伴い、トラップ関連のCSR(stvec、sepc、scause、stvalなど)が追加されます。

S-modeで新しく導入される大きな機能として仮想記憶システムがあります。 仮想記憶システムはページングを使って仮想的なアドレスを使用できるようにする仕組みです。 これについては第18章「S-modeの実装 (2. 仮想記憶システム)」で解説します。

他にはscounterenレジスタ、トラップから戻るためのSRET命令などが追加されます。 また、Supervisor software interruptを提供するSSWIデバイスも実装します。 それぞれ解説しながら実装します。

eeiパッケージに、本書で実装するS-modeのCSRをすべて定義します。

▼リスト17.1: CSRのアドレスを定義する (eei.veryl) 差分をみる

veryl
enum CsrAddr: logic<12> {
    // Supervisor Trap Setup
    SSTATUS = 12'h100,
    SIE = 12'h104,
    STVEC = 12'h105,
    SCOUNTEREN = 12'h106,
    // Supervisor Trap Handling
    SSCRATCH = 12'h140,
    SEPC = 12'h141,
    SCAUSE = 12'h142,
    STVAL = 12'h143,
    SIP = 12'h144,
    // Supervisor Protection and Translation
    SATP = 12'h180,

misa.Extensions、mstatus.SXL、mstatus.MPPの実装

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

misa.ExtensionsのSビットを1に設定します (リスト2)。

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

veryl
let misa      : UIntX  = {2'd2, 1'b0 repeat XLEN - 28, 26'b00000101000001000100000101}; // U, S, M, I, C, A

S-modeのときのXLENはSXLENと定義されており、mstatus.SXLで確認できます。 本書ではSXLENが常に64になるように実装します。

mstatus.SXLを64を示す値である2に設定します ( リスト3、 リスト4 )。

▼リスト17.3: mstatus.SXLの定義 (eei.veryl) 差分をみる

veryl
const MSTATUS_UXL: UInt64 = 2 << 32;
const MSTATUS_SXL: UInt64 = 2 << 34;

▼リスト17.4: mstatus.SXLの初期値を設定する (csrunit.veryl)

veryl
always_ff {
    if_reset {
        mode       = PrivMode::M;
        mstatus    = MSTATUS_SXL | MSTATUS_UXL;

今のところmstatus.MPPにはM-modeとU-modeを示す値しか書き込めないようにしているので、 S-modeの値(2'b10)も書き込めるように変更します (リスト5)。 これにより、MRET命令でS-modeに移動できるようになります。

▼リスト17.5: MPPにS-modeを書き込めるようにする (csrunit.veryl) 差分をみる

veryl
function validate_mstatus (
    mstatus: input UIntX,
    wdata  : input UIntX,
) -> UIntX {
    var result: UIntX;
    result = wdata;
    // MPP
    if wdata[12:11] == 2'b10 {
        result[12:11] = mstatus[12:11];
    }
    return result;
}

scounterenレジスタの実装

「16.6 mcounterenレジスタの実装」では、 ハードウェアパフォーマンスモニタにU-modeでアクセスできるかをmcounterenレジスタで制御できるようにしました。 S-modeを導入するとmcounterenレジスタは S-modeがハードウェアパフォーマンスモニタにアクセスできるかを制御するレジスタに変わります。 また、mcounterenレジスタの代わりに U-modeでハードウェアパフォーマンスモニタにアクセスできるかを制御する32ビットのscounterenレジスタが追加されます。

scounterenレジスタのフィールドのビット配置はmcounterenレジスタと同じです。 また、U-modeでハードウェアパフォーマンスにアクセスできる条件は、 mcounterenレジスタとscounterenレジスタの両方によって許可されている場合になります。

scounterenレジスタを作成し、読み書きできるようにします ( リスト6、 リスト7、 リスト8、 リスト9、 リスト10、 リスト11 )。

▼リスト17.6: scounternレジスタの定義 (csrunit.veryl) 差分をみる

veryl
var scounteren: UInt32;

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

veryl
mtval      = 0;
scounteren = 0;
led        = 0;

▼リスト17.8: rdataにscounterenレジスタの値を設定する (csrunit.veryl) 差分をみる

veryl
CsrAddr::MTVAL     : mtval,
CsrAddr::SCOUNTEREN: {1'b0 repeat XLEN - 32, scounteren},
CsrAddr::LED       : led,

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

veryl
const SCOUNTEREN_WMASK: UIntX = 'h0000_0000_0000_0007 as UIntX;

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

veryl
CsrAddr::MTVAL     : MTVAL_WMASK,
CsrAddr::SCOUNTEREN: SCOUNTEREN_WMASK,
CsrAddr::LED       : LED_WMASK,

▼リスト17.11: scounterenレジスタに書き込む (csrunit.veryl) 差分をみる

veryl
CsrAddr::MTVAL     : mtval      = wdata;
CsrAddr::SCOUNTEREN: scounteren = wdata[31:0];
CsrAddr::LED       : led        = wdata;

ハードウェアパフォーマンスモニタにアクセスするときに許可を確認する仕組みを実装します (リスト12)。 S-modeでアクセスするときはmcounterenレジスタだけ確認し、 U-modeでアクセスするときはmcounterenレジスタとscounterenレジスタを確認します。

▼リスト17.12: 許可の確認ロジックを変更する (csrunit.veryl) 差分をみる

veryl
let expt_zicntr_priv       : logic = is_wsc && (mode <= PrivMode::S && case csr_addr {
    CsrAddr::CYCLE  : !mcounteren[0],
    CsrAddr::TIME   : !mcounteren[1],
    CsrAddr::INSTRET: !mcounteren[2],
    default         : 0,
} || mode <= PrivMode::U && case csr_addr {
    CsrAddr::CYCLE  : !scounteren[0],
    CsrAddr::TIME   : !scounteren[1],
    CsrAddr::INSTRET: !scounteren[2],
    default         : 0,
}); // attempt to access Zicntr CSR without permission

sstatusレジスタの実装

sstatusレジスタ sstatusレジスタはmstatusレジスタの一部をS-modeで読み込み、書き込みできるようにしたSXLENビットのレジスタです。 本章ではmstatusレジスタに読み込み、書き込みマスクを適用することでsstatusレジスタを実装します。

sstatusレジスタの書き込みマスクを定義します ( リスト13、 リスト14 )。

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

veryl
const SSTATUS_WMASK   : UIntX = 'h0000_0000_0000_0000 as UIntX;

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

veryl
CsrAddr::MTVAL     : MTVAL_WMASK,
CsrAddr::SSTATUS   : SSTATUS_WMASK,
CsrAddr::SCOUNTEREN: SCOUNTEREN_WMASK,

読み込みマスクを定義し、mstatusレジスタにマスクを適用した値をsstatusレジスタの値にします ( リスト15、 リスト16、 リスト17 )。

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

veryl
const SSTATUS_RMASK: UIntX = 'h8000_0003_018f_e762;

▼リスト17.16: sstatusの値をmstatusにマスクを適用したものにする (csrunit.veryl) 差分をみる

veryl
let sstatus   : UIntX  = mstatus & SSTATUS_RMASK;

▼リスト17.17: rdataにsstatusレジスタの値を設定する (csrunit.veryl) 差分をみる

veryl
CsrAddr::MTVAL     : mtval,
CsrAddr::SSTATUS   : sstatus,
CsrAddr::SCOUNTEREN: {1'b0 repeat XLEN - 32, scounteren},

マスクを適用した書き込みを実装します (リスト18)。 書き込みマスクが適用されたwdataと、 書き込みマスクをビット反転した値でマスクされたmstatusレジスタの値のORを書き込みます。

▼リスト17.18: sstatusレジスタへの書き込みでmstatusレジスタに書き込む (csrunit.veryl) 差分をみる

veryl
CsrAddr::SSTATUS   : mstatus    = validate_mstatus(mstatus, wdata | mstatus & ~SSTATUS_WMASK);

トラップの委譲

トラップの委譲

S-modeが実装されているとき、 S-modeとU-modeで発生するトラップの遷移先の特権レベルをM-modeからS-modeに変更(委譲)することができます。 特権レベルがM-modeのときに発生したトラップの特権レベルの遷移先をS-modeに変更することはできません。

M-modeからS-modeに委譲されたトラップのトラップベクタは、mtvecではなくstvecになります。 また、 mepcではなくsepcにトラップが発生した命令アドレスを格納し、 scauseにトラップの原因を示す値、 stvalに例外に固有の情報、 sstatus.SPPにトラップ前の特権レベル、 sstatus.SPIEにsstatus.SIE、 sstatus.SIEに0を格納します。 これ以降、トラップでx-modeに遷移するときに変更、参照するCSRを例えば xtvec、xepc、xcause、xtval、mstatus.xPPのように頭文字をxにして呼ぶことがあります。

例外の委譲

medelegレジスタは、どの例外を委譲するかを制御する64ビットのレジスタです。 medelegレジスタの下からi番目のビットが立っているとき、S-mode、U-modeで発生したcauseがiの例外をS-modeに委譲します。 M-modeで発生した例外はS-modeに委譲されません。

Environment call from M-mode例外のように委譲することができない命令のmedelegレジスタのビットは1に変更できません。

割り込みの委譲

midelegレジスタは、どの割り込みを委譲するかを制御するMXLENビットのレジスタです。 各割り込みはmie、mipレジスタと同じ場所のmidelegレジスタのビットによって委譲されるかどうかが制御されます。

M-mode、S-mode、U-modeが実装されたCPUで、割り込みでM-modeに遷移する条件は次の通りです。

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

割り込みでS-modeに遷移する条件は次の通りです。

  1. 割り込み原因に対応したsipレジスタのビットが1である
  2. 割り込み原因に対応したsieレジスタのビットが1である
  3. 現在の特権レベルがS-mode未満である。またはS-modeのとき、sstatus.SIEが1である

sip、sieレジスタは、それぞれmip、mieレジスタの委譲された割り込みのビットだけ読み込み、書き込みできるようにしたレジスタです。 委譲されていない割り込みに対応したビットは読み込み専用の0になります。 S-modeに委譲された割り込みは、特権レベルがM-modeのときは発生しません。

S-modeに委譲された割り込みは外部割り込み、ソフトウェア割り込み、タイマ割り込みの順に優先されます。 委譲されていない割り込みを同じタイミングで発生させられるとき、委譲されていない割り込みが優先されます。

本書ではM-modeの外部割り込み(Machine external interrupt)、 ソフトウェア割り込み(Machine software interrupt)、 タイマ割り込み(Machine timer interrupt)はS-modeに委譲できないように実装します[1]

トラップに関連するレジスタを作成する

S-modeに委譲されたトラップで使用するstvec、sscratch、sepc、scause、stvalレジスタを作成します ( リスト19、 リスト20、 リスト21、 リスト22、 リスト23、 リスト24 )。

▼リスト17.19: レジスタの定義 (csrunit.veryl) 差分をみる

veryl
var stvec     : UIntX ;
var sscratch  : UIntX ;
var sepc      : UIntX ;
var scause    : UIntX ;
var stval     : UIntX ;

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

veryl
stvec      = 0;
sscratch   = 0;
sepc       = 0;
scause     = 0;
stval      = 0;

▼リスト17.21: rdataにレジスタの値を割り当てる (csrunit.veryl) 差分をみる

veryl
CsrAddr::STVEC     : stvec,
CsrAddr::SSCRATCH  : sscratch,
CsrAddr::SEPC      : sepc,
CsrAddr::SCAUSE    : scause,
CsrAddr::STVAL     : stval,

それぞれ、mtvec、mscratch、mepc、mcause、mtvalレジスタと同じ書き込みマスクを設定します。

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

veryl
const STVEC_WMASK     : UIntX = 'hffff_ffff_ffff_fffd;
const SSCRATCH_WMASK  : UIntX = 'hffff_ffff_ffff_ffff;
const SEPC_WMASK      : UIntX = 'hffff_ffff_ffff_fffe;
const SCAUSE_WMASK    : UIntX = 'hffff_ffff_ffff_ffff;
const STVAL_WMASK     : UIntX = 'hffff_ffff_ffff_ffff;

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

veryl
CsrAddr::STVEC     : STVEC_WMASK,
CsrAddr::SSCRATCH  : SSCRATCH_WMASK,
CsrAddr::SEPC      : SEPC_WMASK,
CsrAddr::SCAUSE    : SCAUSE_WMASK,
CsrAddr::STVAL     : STVAL_WMASK,

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

veryl
CsrAddr::STVEC     : stvec      = wdata;
CsrAddr::SSCRATCH  : sscratch   = wdata;
CsrAddr::SEPC      : sepc       = wdata;
CsrAddr::SCAUSE    : scause     = wdata;
CsrAddr::STVAL     : stval      = wdata;

stvecレジスタの実装

トラップが発生するとき、 遷移先の特権レベルがS-modeならstvecレジスタの値にジャンプするようにします ( リスト25、 リスト26 )。 割り込み、例外それぞれにレジスタを選択する変数を定義し、 mtvecを使っていたところを新しい変数に置き換えます。

▼リスト17.25: トラップベクタを遷移先の特権レベルによって変更する (csrunit.veryl) 差分をみる

veryl
let interrupt_xtvec : Addr = if interrupt_mode == PrivMode::M ? mtvec : stvec;
let interrupt_vector: Addr = if interrupt_xtvec[0] == 0 ?
    {interrupt_xtvec[msb:2], 2'b0}
: // Direct
    {interrupt_xtvec[msb:2] + interrupt_cause[msb - 2:0], 2'b0}
; // Vectored

▼リスト17.26: トラップベクタを遷移先の特権レベルによって変更する (csrunit.veryl) 差分をみる

veryl
let expt_xtvec : Addr     = if expt_mode == PrivMode::M ? mtvec : stvec;
let expt_vector: Addr     = {expt_xtvec[msb:2], 2'b0};

トラップでsepc、scause、stvalレジスタを変更する

トラップが発生するとき、 遷移先の特権レベルがS-modeならsepc、scause、stvalレジスタを変更するようにします。

トラップ時にtrap_mode_nextで処理を分岐します (リスト27)。

▼リスト17.27: 遷移先の特権レベルによってトラップ処理を分岐する (csrunit.veryl) 差分をみる

veryl
if raise_expt || raise_interrupt {
    let xepc: Addr = if raise_expt ? pc : // exception
    if raise_interrupt && is_wfi ? pc + 4 : pc; // interrupt when wfi / interrupt
    if trap_mode_next == PrivMode::M {
        mepc   = xepc;
        mcause = trap_cause;
        if raise_expt {
            mtval = expt_value;
        }
        // save mstatus.mie to mstatus.mpie
        // and set mstatus.mie = 0
        mstatus[7] = mstatus[3];
        mstatus[3] = 0;
        // save current privilege level to mstatus.mpp
        mstatus[12:11] = mode;
    } else {
       sepc   = xepc;
       scause = trap_cause;
       if raise_expt {
           stval = expt_value;
       }
    }

mstatusのSIE、SPIE、SPPビットを実装する

mstatusレジスタのSIE、SPIE、SPPビットを実装します。 mstatus.SIEはS-modeに委譲された割り込みのグローバル割り込みイネーブルビットです。 mstatus.SPIEはS-modeに委譲されたトラップが発生するときにmstatus.SIEを退避するビットです。 mstatus.SPPはS-modeに委譲されたトラップが発生するときに、トラップ前の特権レベルを書き込むビットです。 S-modeに委譲されたトラップはS-modeかU-modeでしか発生しないため、 mstatus.SPPは特権レベルを区別するために十分な1ビット幅のフィールドになっています。

mstatus、sstatusレジスタのSIE、SPIE、SPPビットに書き込めるようにします ( リスト28、 リスト29 )。

▼リスト17.28: 書き込みマスクを変更する (csrunit.veryl) 差分をみる

veryl
const MSTATUS_WMASK   : UIntX = 'h0000_0000_0020_19aa as UIntX;

▼リスト17.29: 書き込みマスクを変更する (csrunit.veryl) 差分をみる

veryl
const SSTATUS_WMASK   : UIntX = 'h0000_0000_0000_0122 as UIntX;

トラップでS-modeに遷移するとき、 sstatus.SPIEにsstatus.SIE、 sstatus.SIEに0、 sstatus.SPPにトラップ前の特権レベルを格納します (リスト30)。

▼リスト17.30: sstatus.SPIE、SIE、SPPをトラップで変更する (csrunit.veryl) 差分をみる

veryl
} else {
    sepc   = xepc;
    scause = trap_cause;
    if raise_expt {
        stval = expt_value;
    }
    // save sstatus.sie to sstatus.spie
    // and set sstatus.sie = 0
    mstatus[5] = mstatus[1];
    mstatus[1] = 0;
    // save current privilege mode (S or U) to sstatus.spp
    mstatus[8] = mode[0];
}

SRET命令を実装する

SRET命令の実装

SRET命令は、S-modeのCSR(sepc、sstatusなど)を利用してトラップ処理から戻るための命令です。 SRET命令はS-mode以上の特権レベルのときにしか実行できません。

inst_decoderモジュールでSRET命令をデコードできるようにします (リスト31)。

▼リスト17.31: SRET命令のときvalidを1にする (inst_decoder.veryl) 差分をみる

veryl
OP_SYSTEM: f3 != 3'b000 && f3 != 3'b100 || // CSRR(W|S|C)[I]
 bits == 32'h00000073 || // ECALL
 bits == 32'h00100073 || // EBREAK
 bits == 32'h30200073 || //MRET
 bits == 32'h10200073 || //SRET
 bits == 32'h10500073, // WFI

SRET命令を判定し、ジャンプ先と遷移先の特権レベルを命令によって切り替えます ( リスト32、 リスト33、 リスト34 )。

▼リスト17.32: SRT命令の判定 (csrunit.veryl) 差分をみる

veryl
let is_sret: logic = inst_bits == 32'h10200073;

▼リスト17.33: SRET命令のとき遷移先の特権レベル、アドレスを変更する (csrunit.veryl) 差分をみる

veryl
assign trap_return        = valid && (is_mret || is_sret) && !raise_expt && !raise_interrupt;
let trap_return_mode  : PrivMode = if is_mret ? mstatus_mpp : mstatus_spp;
let trap_return_vector: Addr     = if is_mret ? mepc : sepc;

▼リスト17.34: trap_return_vectorをtrap_vectorに割り当てる (csrunit.veryl) 差分をみる

veryl
assign trap_vector = switch {
    raise_expt     : expt_vector,
    raise_interrupt: interrupt_vector,
    trap_return    : trap_return_vector,
    default        : 0,
};

SRET命令を実行するとき、 sstatus.SIEにsstatus.SPIE、 sstatus.SPIEに0、 sstatus.SPPに実装がサポートする最小の特権レベル(U-mode)を示す値を格納します (リスト35)。

▼リスト17.35: SRET命令によるsstatusの変更 (csrunit.veryl) 差分をみる

veryl
} else if trap_return {
    if is_mret {
        // set mstatus.mie = mstatus.mpie
        //     mstatus.mpie = 0
        mstatus[3] = mstatus[7];
        mstatus[7] = 0;
        // set mstatus.mpp = U (least privilege level)
        mstatus[12:11] = PrivMode::U;
    } else if is_sret {
        // set sstatus.sie = sstatus.spie
        //     sstatus.spie = 0
        mstatus[1] = mstatus[5];
        mstatus[5] = 0;
        // set sstatus.spp = U (least privilege level)
        mstatus[8] = 0;
    }
}

SRET命令をS-mode未満の特権レベルで実行しようとしたら例外が発生するようにします (リスト36)。

▼リスト17.36: SRET命令を実行するときに特権レベルを確認する (csrunit.veryl) 差分をみる

veryl
let expt_trap_return_priv: logic = (is_mret && mode <: PrivMode::M) || (is_sret && mode <: PrivMode::S);

mstatus.TSRの実装

mstatusレジスタのTSR(Trap SRET)ビットは、 SRET命令をS-modeで実行したときに例外を発生させるかを制御するビットです。 1のとき、Illegal instruction例外が発生するようになります。

mstatus.TSRを変更できるようにします (リスト37)。

▼リスト17.37: 書き込みマスクを変更する (csrunit.veryl) 差分をみる

veryl
const MSTATUS_WMASK   : UIntX = 'h0000_0000_0060_19aa as UIntX;

例外を判定します ( リスト38、 リスト39 )。

▼リスト17.38: TSRビットを表す変数 (csrunit.veryl) 差分をみる

veryl
let mstatus_tsr : logic    = mstatus[22];

▼リスト17.39: mstatus.TSRが1のときにS-modeでSRET命令を実行したら例外にする (csrunit.veryl) 差分をみる

veryl
let expt_trap_return_priv: logic = (is_mret && mode <: PrivMode::M) || (is_sret && (mode <: PrivMode::S || (mode == PrivMode::S && mstatus_tsr)));

SEI、SSI、STIを実装する

S-modeを導入すると、 S-modeの外部割り込み(Supervisor external interrupt)、 ソフトウェア割り込み(Supervisor software interrupt)、 タイマ割り込み(Supervisor timer interrupt)に対応する mip、mieレジスタのビットを変更できるようになります。

例外、割り込みはそれぞれmedeleg、midelegレジスタでS-modeに処理を委譲することができます。 委譲された割り込みのmipレジスタの値はsipレジスタで観測できるようになり、 割り込みを有効にするかをsieレジスタで制御できるようになります。

mip、mieレジスタの変更

mipレジスタのSEIP、SSIP、STIPビット、 mieレジスタのSEIE、SSIE、STIEビットを変更できるようにします。

書き込みマスクを変更、実装します ( リスト40、 リスト41 )。

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

veryl
const MIP_WMASK       : UIntX = 'h0000_0000_0000_0222 as UIntX;
const MIE_WMASK       : UIntX = 'h0000_0000_0000_02aa as UIntX;

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

veryl
CsrAddr::MIP       : MIP_WMASK,

mip_regレジスタを作成します。 mipの値を、mip_regとACLINTの状態をOR演算したものに変更します (リスト42)。

▼リスト17.42: レジスタを作成して変数に適用する (csrunit.veryl) 差分をみる

veryl
var mip_reg: UIntX;
let mip    : UIntX = mip_reg | {

mip_regレジスタのリセット、書き込みを実装します ( リスト43、 リスト44 )。 wdataにはACLINTの状態が含まれているので、書き込みマスクをもう一度適用します。

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

veryl
mie        = 0;
mip_reg    = 0;
mcounteren = 0;

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

veryl
CsrAddr::MTVEC     : mtvec      = wdata;
CsrAddr::MIP       : mip_reg    = wdata & MIP_WMASK;
CsrAddr::MIE       : mie        = wdata;

causeの設定

S-modeの割り込みのcauseを設定します (リスト45)。

▼リスト17.45: 割り込み原因の追加 (csrunit.veryl) 差分をみる

veryl
let interrupt_cause  : UIntX = switch {
    interrupt_pending[3]: CsrCause::MACHINE_SOFTWARE_INTERRUPT,
    interrupt_pending[7]: CsrCause::MACHINE_TIMER_INTERRUPT,
    interrupt_pending[9]: CsrCause::SUPERVISOR_EXTERNAL_INTERRUPT,
    interrupt_pending[1]: CsrCause::SUPERVISOR_SOFTWARE_INTERRUPT,
    interrupt_pending[5]: CsrCause::SUPERVISOR_TIMER_INTERRUPT,
    default             : 0,
};

medeleg、mideleg、sip、sieレジスタの実装

sipレジスタsieレジスタ medeleg、mideleg、sip、sieレジスタを実装します。

medeleg、midelegレジスタはそれぞれ委譲できる例外、割り込みに対応するビットだけ書き換えられるようにします。 sipレジスタはmidelegレジスタで委譲された割り込みに対応するビットだけ値を参照できるように、 sieレジスタはmidelegレジスタで委譲された割り込みに対応するビットだけ書き換えられるようにします。

レジスタを作成し、読み込めるようにします ( リスト46、 リスト47、 リスト48、 リスト49、 リスト50、 リスト51 )。

▼リスト17.46: medeleg、midelegレジスタの定義 (csrunit.veryl) 差分をみる

veryl
var medeleg   : UInt64;
var mideleg   : UIntX ;

▼リスト17.47: sie、sieレジスタの定義 (csrunit.veryl) 差分をみる

veryl
let sip       : UIntX  = mip & mideleg;
var sie       : UIntX ;

▼リスト17.48: medeleg、midelegレジスタを0でリセットする (csrunit.veryl) 差分をみる

veryl
medeleg    = 0;
mideleg    = 0;

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

veryl
sie        = 0;

▼リスト17.50: rdataにmedeleg、midelegレジスタの値を割り当てる (csrunit.veryl) 差分をみる

veryl
CsrAddr::MEDELEG   : medeleg,
CsrAddr::MIDELEG   : mideleg,

▼リスト17.51: rdataにsip、sieレジスタの値を割り当てる (csrunit.veryl) 差分をみる

veryl
CsrAddr::SIP       : sip,
CsrAddr::SIE       : sie & mideleg,

書き込みマスクを設定し、書き込めるようにします ( リスト52、 リスト53、 リスト54、 リスト55、 リスト56、 リスト57 )。

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

veryl
const MEDELEG_WMASK   : UIntX = 'hffff_ffff_fffe_f7ff;
const MIDELEG_WMASK   : UIntX = 'h0000_0000_0000_0222 as UIntX;

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

veryl
const SIE_WMASK       : UIntX = 'h0000_0000_0000_0222 as UIntX;

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

veryl
CsrAddr::MEDELEG   : MEDELEG_WMASK,
CsrAddr::MIDELEG   : MIDELEG_WMASK,

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

veryl
CsrAddr::SIE       : SIE_WMASK & mideleg,

▼リスト17.56: medeleg、midelegレジスタの書き込み (csrunit.veryl) 差分をみる

veryl
CsrAddr::MEDELEG   : medeleg    = wdata;
CsrAddr::MIDELEG   : mideleg    = wdata;

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

veryl
CsrAddr::SIE       : sie        = wdata;

割り込み条件、トラップの動作を変更する

作成したCSRを利用して、割り込みが発生する条件、トラップが発生したときのCSRの操作を変更します。

例外が発生するとき、遷移先の特権レベルをmedelegレジスタによって変更します (リスト58)。

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

veryl
let expt_mode  : PrivMode = if mode == PrivMode::M || !medeleg[expt_cause[5:0]] ? PrivMode::M : PrivMode::S;

割り込みの発生条件と参照するCSRを、遷移先の特権レベルごとに用意します ( リスト59、 リスト60 )。

▼リスト17.59: M-modeに遷移する割り込みを示す変数 (csrunit.veryl) 差分をみる

veryl
// Interrupt to M-mode
let interrupt_pending_mmode: UIntX = mip & mie & ~mideleg;
let raise_interrupt_mmode  : logic = (mode != PrivMode::M || mstatus_mie) && interrupt_pending_mmode != 0;
let interrupt_cause_mmode  : UIntX = switch {
    interrupt_pending_mmode[3]: CsrCause::MACHINE_SOFTWARE_INTERRUPT,
    interrupt_pending_mmode[7]: CsrCause::MACHINE_TIMER_INTERRUPT,
    interrupt_pending_mmode[9]: CsrCause::SUPERVISOR_EXTERNAL_INTERRUPT,
    interrupt_pending_mmode[1]: CsrCause::SUPERVISOR_SOFTWARE_INTERRUPT,
    interrupt_pending_mmode[5]: CsrCause::SUPERVISOR_TIMER_INTERRUPT,
    default                   : 0,
};

▼リスト17.60: S-modeに遷移する割り込みを示す変数 (csrunit.veryl) 差分をみる

veryl
// Interrupt to S-mode
let interrupt_pending_smode: UIntX = sip & sie;
let raise_interrupt_smode  : logic = (mode <: PrivMode::S || (mode == PrivMode::S && mstatus_sie)) && interrupt_pending_smode != 0;
let interrupt_cause_smode  : UIntX = switch {
    interrupt_pending_smode[9]: CsrCause::SUPERVISOR_EXTERNAL_INTERRUPT,
    interrupt_pending_smode[1]: CsrCause::SUPERVISOR_SOFTWARE_INTERRUPT,
    interrupt_pending_smode[5]: CsrCause::SUPERVISOR_TIMER_INTERRUPT,
    default                   : 0,
};

M-mode向けの割り込みを優先して利用します (リスト61)。

▼リスト17.61: M-mode、S-modeに遷移する割り込みを調停する (csrunit.veryl) 差分をみる

veryl
// Interrupt
let raise_interrupt : logic = valid && can_intr && (raise_interrupt_mmode || raise_interrupt_smode);
let interrupt_cause : UIntX = if raise_interrupt_mmode ? interrupt_cause_mmode : interrupt_cause_smode;
let interrupt_xtvec : Addr  = if interrupt_mode == PrivMode::M ? mtvec : stvec;
let interrupt_vector: Addr  = if interrupt_xtvec[0] == 0 ?
    {interrupt_xtvec[msb:2], 2'b0}
: // Direct
    {interrupt_xtvec[msb:2] + interrupt_cause[msb - 2:0], 2'b0}
; // Vectored
let interrupt_mode: PrivMode = if raise_interrupt_mmode ? PrivMode::M : PrivMode::S;

ソフトウェア割り込みの実装 (SSWI)

SSWIデバイスはソフトウェア割り込み(Supervisor software insterrupt)を提供するためのデバイスです。 SSWIデバイスにはハードウェアスレッド毎に4バイトのSETSSIPレジスタが用意されています(表1) SETSSIPレジスタを読み込むと常に0を返しますが、 最下位ビットに1を書き込むとそれに対応するハードウェアスレッドのmip.SSIPビットが1になります。

表17.1: SSWIデバイスのメモリマップ

オフセットレジスタ
0000SETSSIP0
0004SETSSIP1
....
3ff8SETSSIP4094
3ffcMTIME
![setssipレジスタ](./images/23-smode-csr/setssip.png) 今のところmhartidが`0`のハードウェアスレッドしか存在しないため、SETSSIP0のみ実装します。 aclint_ifインターフェースに、 mipレジスタのSSIPビットを`1`にする要求のための`setssip`を作成します ( リスト62 )。

▼リスト17.62: setssipをインターフェースに追加する (aclint_if.veryl) 差分をみる

veryl
interface aclint_if {
    var msip   : logic ;
    var mtip   : logic ;
    var mtime  : UInt64;
    var setssip: logic ;
    modport master {
        msip   : output,
        mtip   : output,
        mtime  : output,
        setssip: output,
    }

aclintモジュールでSETSSIP0への書き込みを検知し、最下位ビットをsetssipに接続します ( リスト63 )。

▼リスト17.63: SETSSIP0に書き込むときsetssipにLSBを割り当てる (aclint_memory.veryl) 差分をみる

veryl
always_comb {
    aclint.setssip = 0;
    if membus.valid && membus.wen && membus.addr == MMAP_ACLINT_SETSSIP {
        aclint.setssip = membus.wdata[0];
    }
}

csrunitモジュールでsetssipを確認し、mip.SSIPを立てるようにします ( リスト64、 リスト65、 リスト66 )。

▼リスト17.64: setssipをXLENビットに拡張する (csrunit.veryl) 差分をみる

veryl
let setssip: UIntX = {1'b0 repeat XLEN - 2, aclint.setssip, 1'b0};

▼リスト17.65: setssipでmipを更新する (csrunit.veryl) 差分をみる

veryl
} else {
    mcycle  += 1;
    mip_reg |= setssip;

▼リスト17.66: setssipでmipを更新する (csrunit.veryl) 差分をみる

veryl
CsrAddr::MIP       : mip_reg    = (wdata & MIP_WMASK) | setssip;

  1. 多くの実装ではこれらの割り込みを委譲できないように実装するようです。そのため、本書で実装するコアでも委譲できないように実装します。 ↩︎