Verylで作るCPU
Star

第17章
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: リスト17.1: CSRのアドレスを定義する (eei.veryl)
1:     enum CsrAddr: logic<12> {
2:         // Supervisor Trap Setup
3:         SSTATUS = 12'h100,
4:         SIE = 12'h104,
5:         STVEC = 12'h105,
6:         SCOUNTEREN = 12'h106,
7:         // Supervisor Trap Handling
8:         SSCRATCH = 12'h140,
9:         SEPC = 12'h141,
10:         SCAUSE = 12'h142,
11:         STVAL = 12'h143,
12:         SIP = 12'h144,
13:         // Supervisor Protection and Translation
14:         SATP = 12'h180,

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

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

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

リスト17.2: リスト17.2: Sビットを1にする (csrunit.veryl)
1:     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に設定します(リスト17.3リスト17.4)。

リスト17.3: リスト17.3: mstatus.SXLの定義 (eei.veryl)
1:     const MSTATUS_UXL: UInt64 = 2 << 32;
2:     const MSTATUS_SXL: UInt64 = 2 << 34;
リスト17.4: リスト17.4: mstatus.SXLの初期値を設定する (csrunit.veryl)
1:     always_ff {
2:         if_reset {
3:             mode       = PrivMode::M;
4:             mstatus    = MSTATUS_SXL | MSTATUS_UXL;

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

リスト17.5: リスト17.5: MPPにS-modeを書き込めるようにする (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] == 2'b10 {
9:             result[12:11] = mstatus[12:11];
10:         }
11:         return result;
12:     }

17.2 scounterenレジスタの実装

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

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

scounterenレジスタを作成し、読み書きできるようにします(リスト17.6リスト17.7リスト17.8リスト17.9リスト17.10リスト17.11)。

リスト17.6: リスト17.6: scounternレジスタの定義 (csrunit.veryl)
1:     var scounteren: UInt32;
リスト17.7: リスト17.7: scounterenレジスタを0でリセットする (csrunit.veryl)
1:     mtval      = 0;
2:     scounteren = 0;
3:     led        = 0;
リスト17.8: リスト17.8: rdataにscounterenレジスタの値を設定する (csrunit.veryl)
1:     CsrAddr::MTVAL     : mtval,
2:     CsrAddr::SCOUNTEREN: {1'b0 repeat XLEN - 32, scounteren},
3:     CsrAddr::LED       : led,
リスト17.9: リスト17.9: 書き込みマスクの定義 (csrunit.veryl)
1:     const SCOUNTEREN_WMASK: UIntX = 'h0000_0000_0000_0007 as UIntX;
リスト17.10: リスト17.10: wmaskに書き込みマスクを設定する (csrunit.veryl)
1:     CsrAddr::MTVAL     : MTVAL_WMASK,
2:     CsrAddr::SCOUNTEREN: SCOUNTEREN_WMASK,
3:     CsrAddr::LED       : LED_WMASK,
リスト17.11: リスト17.11: scounterenレジスタに書き込む (csrunit.veryl)
1:     CsrAddr::MTVAL     : mtval      = wdata;
2:     CsrAddr::SCOUNTEREN: scounteren = wdata[31:0];
3:     CsrAddr::LED       : led        = wdata;

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

リスト17.12: リスト17.12: 許可の確認ロジックを変更する (csrunit.veryl)
1:     let expt_zicntr_priv       : logic = is_wsc && (mode <= PrivMode::S && case csr_addr {
2:         CsrAddr::CYCLE  : !mcounteren[0],
3:         CsrAddr::TIME   : !mcounteren[1],
4:         CsrAddr::INSTRET: !mcounteren[2],
5:         default         : 0,
6:     } || mode <= PrivMode::U && case csr_addr {
7:         CsrAddr::CYCLE  : !scounteren[0],
8:         CsrAddr::TIME   : !scounteren[1],
9:         CsrAddr::INSTRET: !scounteren[2],
10:         default         : 0,
11:     }); // attempt to access Zicntr CSR without permission

17.3 sstatusレジスタの実装

sstatusレジスタ

図17.1: sstatusレジスタ

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

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

リスト17.13: リスト17.13: 書き込みマスクの定義 (csrunit.veryl)
1:     const SSTATUS_WMASK   : UIntX = 'h0000_0000_0000_0000 as UIntX;
リスト17.14: リスト17.14: wmaskに書き込みマスクを設定する (csrunit.veryl)
1:     CsrAddr::MTVAL     : MTVAL_WMASK,
2:     CsrAddr::SSTATUS   : SSTATUS_WMASK,
3:     CsrAddr::SCOUNTEREN: SCOUNTEREN_WMASK,

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

リスト17.15: リスト17.15: 読み込みマスクの定義 (csrunit.veryl)
1:     const SSTATUS_RMASK: UIntX = 'h8000_0003_018f_e762;
リスト17.16: リスト17.16: sstatusの値をmstatusにマスクを適用したものにする (csrunit.veryl)
1:     let sstatus   : UIntX  = mstatus & SSTATUS_RMASK;
リスト17.17: リスト17.17: rdataにsstatusレジスタの値を設定する (csrunit.veryl)
1:     CsrAddr::MTVAL     : mtval,
2:     CsrAddr::SSTATUS   : sstatus,
3:     CsrAddr::SCOUNTEREN: {1'b0 repeat XLEN - 32, scounteren},

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

リスト17.18: リスト17.18: sstatusレジスタへの書き込みでmstatusレジスタに書き込む (csrunit.veryl)
1:     CsrAddr::SSTATUS   : mstatus    = validate_mstatus(mstatus, wdata | mstatus & ~SSTATUS_WMASK);

17.4 トラップの委譲

17.4.1 トラップの委譲

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

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

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

S-modeに委譲されたトラップで使用するstvec、sscratch、sepc、scause、stvalレジスタを作成します(リスト17.19リスト17.20リスト17.21リスト17.22リスト17.23リスト17.24)。

リスト17.19: リスト17.19: レジスタの定義 (csrunit.veryl)
1:     var stvec     : UIntX ;
2:     var sscratch  : UIntX ;
3:     var sepc      : UIntX ;
4:     var scause    : UIntX ;
5:     var stval     : UIntX ;
リスト17.20: リスト17.20: レジスタを0でリセットする (csrunit.veryl)
1:     stvec      = 0;
2:     sscratch   = 0;
3:     sepc       = 0;
4:     scause     = 0;
5:     stval      = 0;
リスト17.21: リスト17.21: rdataにレジスタの値を割り当てる (csrunit.veryl)
1:     CsrAddr::STVEC     : stvec,
2:     CsrAddr::SSCRATCH  : sscratch,
3:     CsrAddr::SEPC      : sepc,
4:     CsrAddr::SCAUSE    : scause,
5:     CsrAddr::STVAL     : stval,

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

リスト17.22: リスト17.22: 書き込みマスクの定義 (csrunit.veryl)
1:     const STVEC_WMASK     : UIntX = 'hffff_ffff_ffff_fffd;
2:     const SSCRATCH_WMASK  : UIntX = 'hffff_ffff_ffff_ffff;
3:     const SEPC_WMASK      : UIntX = 'hffff_ffff_ffff_fffe;
4:     const SCAUSE_WMASK    : UIntX = 'hffff_ffff_ffff_ffff;
5:     const STVAL_WMASK     : UIntX = 'hffff_ffff_ffff_ffff;
リスト17.23: リスト17.23: wmaskに書き込みマスクを設定する (csrunit.veryl)
1:     CsrAddr::STVEC     : STVEC_WMASK,
2:     CsrAddr::SSCRATCH  : SSCRATCH_WMASK,
3:     CsrAddr::SEPC      : SEPC_WMASK,
4:     CsrAddr::SCAUSE    : SCAUSE_WMASK,
5:     CsrAddr::STVAL     : STVAL_WMASK,
リスト17.24: リスト17.24: レジスタの書き込み (csrunit.veryl)
1:     CsrAddr::STVEC     : stvec      = wdata;
2:     CsrAddr::SSCRATCH  : sscratch   = wdata;
3:     CsrAddr::SEPC      : sepc       = wdata;
4:     CsrAddr::SCAUSE    : scause     = wdata;
5:     CsrAddr::STVAL     : stval      = wdata;

17.4.3 stvecレジスタの実装

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

リスト17.25: リスト17.25: トラップベクタを遷移先の特権レベルによって変更する (csrunit.veryl)
1:     let interrupt_xtvec : Addr = if interrupt_mode == PrivMode::M ? mtvec : stvec;
2:     let interrupt_vector: Addr = if interrupt_xtvec[0] == 0 ?
3:         {interrupt_xtvec[msb:2], 2'b0}
4:     : // Direct
5:         {interrupt_xtvec[msb:2] + interrupt_cause[msb - 2:0], 2'b0}
6:     ; // Vectored
リスト17.26: リスト17.26: トラップベクタを遷移先の特権レベルによって変更する (csrunit.veryl)
1:     let expt_xtvec : Addr     = if expt_mode == PrivMode::M ? mtvec : stvec;
2:     let expt_vector: Addr     = {expt_xtvec[msb:2], 2'b0};

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

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

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

リスト17.27: リスト17.27: 遷移先の特権レベルによってトラップ処理を分岐する (csrunit.veryl)
1: if raise_expt || raise_interrupt {
2:     let xepc: Addr = if raise_expt ? pc : // exception
3:      if raise_interrupt && is_wfi ? pc + 4 : pc; // interrupt when wfi / interrupt
4:     if trap_mode_next == PrivMode::M {
5:         mepc   = xepc;
6:         mcause = trap_cause;
7:         if raise_expt {
8:             mtval = expt_value;
9:         }
10:         // save mstatus.mie to mstatus.mpie
11:         // and set mstatus.mie = 0
12:         mstatus[7] = mstatus[3];
13:         mstatus[3] = 0;
14:         // save current privilege level to mstatus.mpp
15:         mstatus[12:11] = mode;
16:     } else {
17:         sepc   = xepc;
18:         scause = trap_cause;
19:         if raise_expt {
20:             stval = expt_value;
21:         }
22:     }

17.4.5 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ビットに書き込めるようにします(リスト17.28リスト17.29)。

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

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

リスト17.30: リスト17.30: sstatus.SPIE、SIE、SPPをトラップで変更する (csrunit.veryl)
1:     } else {
2:         sepc   = xepc;
3:         scause = trap_cause;
4:         if raise_expt {
5:             stval = expt_value;
6:         }
7:         // save sstatus.sie to sstatus.spie
8:         // and set sstatus.sie = 0
9:         mstatus[5] = mstatus[1];
10:         mstatus[1] = 0;
11:         // save current privilege mode (S or U) to sstatus.spp
12:         mstatus[8] = mode[0];
13:     }

17.4.6 SRET命令を実装する

SRET命令の実装

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

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

リスト17.31: リスト17.31: SRET命令のときvalidを1にする (inst_decoder.veryl)
1:    OP_SYSTEM: f3 != 3'b000 && f3 != 3'b100 || // CSRR(W|S|C)[I]
2:     bits == 32'h00000073 || // ECALL
3:     bits == 32'h00100073 || // EBREAK
4:     bits == 32'h30200073 || //MRET
5:     bits == 32'h10200073 || //SRET
6:     bits == 32'h10500073, // WFI

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

リスト17.32: リスト17.32: SRT命令の判定 (csrunit.veryl)
1:     let is_sret: logic = inst_bits == 32'h10200073;
リスト17.33: リスト17.33: SRET命令のとき遷移先の特権レベル、アドレスを変更する (csrunit.veryl)
1:     assign trap_return        = valid && (is_mret || is_sret) && !raise_expt && !raise_interrupt;
2:     let trap_return_mode  : PrivMode = if is_mret ? mstatus_mpp : mstatus_spp;
3:     let trap_return_vector: Addr     = if is_mret ? mepc : sepc;
リスト17.34: リスト17.34: trap_return_vectorをtrap_vectorに割り当てる (csrunit.veryl)
1:     assign trap_vector = switch {
2:         raise_expt     : expt_vector,
3:         raise_interrupt: interrupt_vector,
4:         trap_return    : trap_return_vector,
5:         default        : 0,
6:     };

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

リスト17.35: リスト17.35: SRET命令によるsstatusの変更 (csrunit.veryl)
1:     } else if trap_return {
2:         if is_mret {
3:             // set mstatus.mie = mstatus.mpie
4:             //     mstatus.mpie = 0
5:             mstatus[3] = mstatus[7];
6:             mstatus[7] = 0;
7:             // set mstatus.mpp = U (least privilege level)
8:             mstatus[12:11] = PrivMode::U;
9:         } else if is_sret {
10:             // set sstatus.sie = sstatus.spie
11:             //     sstatus.spie = 0
12:             mstatus[1] = mstatus[5];
13:             mstatus[5] = 0;
14:             // set sstatus.spp = U (least privilege level)
15:             mstatus[8] = 0;
16:         }
17:     }

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

リスト17.36: リスト17.36: SRET命令を実行するときに特権レベルを確認する (csrunit.veryl)
1:     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を変更できるようにします(リスト17.37)。

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

例外を判定します(リスト17.38リスト17.39)。

リスト17.38: リスト17.38: TSRビットを表す変数 (csrunit.veryl)
1:     let mstatus_tsr : logic    = mstatus[22];
リスト17.39: リスト17.39: mstatus.TSRが1のときにS-modeでSRET命令を実行したら例外にする (csrunit.veryl)
1:     let expt_trap_return_priv: logic = (is_mret && mode <: PrivMode::M) || (is_sret && (mode <: PrivMode::S || (mode == PrivMode::S && mstatus_tsr)));

17.4.7 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ビットを変更できるようにします。

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

リスト17.40: リスト17.40: 書き込みマスクの定義 / 変更 (csrunit.veryl)
1:     const MIP_WMASK       : UIntX = 'h0000_0000_0000_0222 as UIntX;
2:     const MIE_WMASK       : UIntX = 'h0000_0000_0000_02aa as UIntX;
リスト17.41: リスト17.41: wmaskに書き込みマスクを設定する (csrunit.veryl)
1:     CsrAddr::MIP       : MIP_WMASK,

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

リスト17.42: リスト17.42: レジスタを作成して変数に適用する (csrunit.veryl)
1:     var mip_reg: UIntX;
2:     let mip    : UIntX = mip_reg | {

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

リスト17.43: リスト17.43: レジスタの値を0でリセットする (csrunit.veryl)
1:     mie        = 0;
2:     mip_reg    = 0;
3:     mcounteren = 0;
リスト17.44: リスト17.44: mipレジスタの書き込み (csrunit.veryl)
1:     CsrAddr::MTVEC     : mtvec      = wdata;
2:     CsrAddr::MIP       : mip_reg    = wdata & MIP_WMASK;
3:     CsrAddr::MIE       : mie        = wdata;

causeの設定

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

リスト17.45: リスト17.45: 割り込み原因の追加 (csrunit.veryl)
1:     let interrupt_cause  : UIntX = switch {
2:         interrupt_pending[3]: CsrCause::MACHINE_SOFTWARE_INTERRUPT,
3:         interrupt_pending[7]: CsrCause::MACHINE_TIMER_INTERRUPT,
4:         interrupt_pending[9]: CsrCause::SUPERVISOR_EXTERNAL_INTERRUPT,
5:         interrupt_pending[1]: CsrCause::SUPERVISOR_SOFTWARE_INTERRUPT,
6:         interrupt_pending[5]: CsrCause::SUPERVISOR_TIMER_INTERRUPT,
7:         default             : 0,
8:     };

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

sipレジスタ

図17.2: sipレジスタ

sieレジスタ

図17.3: sieレジスタ

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

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

レジスタを作成し、読み込めるようにします(リスト17.46リスト17.47リスト17.48リスト17.49リスト17.50リスト17.51)。

リスト17.46: リスト17.46: medeleg、midelegレジスタの定義 (csrunit.veryl)
1:     var medeleg   : UInt64;
2:     var mideleg   : UIntX ;
リスト17.47: リスト17.47: sie、sieレジスタの定義 (csrunit.veryl)
1:     let sip       : UIntX  = mip & mideleg;
2:     var sie       : UIntX ;
リスト17.48: リスト17.48: medeleg、midelegレジスタを0でリセットする (csrunit.veryl)
1:     medeleg    = 0;
2:     mideleg    = 0;
リスト17.49: リスト17.49: sieレジスタを0でリセットする (csrunit.veryl)
1:     sie        = 0;
リスト17.50: リスト17.50: rdataにmedeleg、midelegレジスタの値を割り当てる (csrunit.veryl)
1:     CsrAddr::MEDELEG   : medeleg,
2:     CsrAddr::MIDELEG   : mideleg,
リスト17.51: リスト17.51: rdataにsip、sieレジスタの値を割り当てる (csrunit.veryl)
1:     CsrAddr::SIP       : sip,
2:     CsrAddr::SIE       : sie & mideleg,

書き込みマスクを設定し、書き込めるようにします(リスト17.52リスト17.53リスト17.54リスト17.55リスト17.56リスト17.57)。

リスト17.52: リスト17.52: 書き込みマスクの定義 (csrunit.veryl)
1:     const MEDELEG_WMASK   : UIntX = 'hffff_ffff_fffe_f7ff;
2:     const MIDELEG_WMASK   : UIntX = 'h0000_0000_0000_0222 as UIntX;
リスト17.53: リスト17.53: 書き込みマスクの定義 (csrunit.veryl)
1:     const SIE_WMASK       : UIntX = 'h0000_0000_0000_0222 as UIntX;
リスト17.54: リスト17.54: wmaskに書き込みマスクを設定する (csrunit.veryl)
1:     CsrAddr::MEDELEG   : MEDELEG_WMASK,
2:     CsrAddr::MIDELEG   : MIDELEG_WMASK,
リスト17.55: リスト17.55: wmaskに書き込みマスクを設定する (csrunit.veryl)
1:     CsrAddr::SIE       : SIE_WMASK & mideleg,
リスト17.56: リスト17.56: medeleg、midelegレジスタの書き込み (csrunit.veryl)
1:     CsrAddr::MEDELEG   : medeleg    = wdata;
2:     CsrAddr::MIDELEG   : mideleg    = wdata;
リスト17.57: リスト17.57: sieレジスタの書き込み (csrunit.veryl)
1:     CsrAddr::SIE       : sie        = wdata;

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

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

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

リスト17.58: リスト17.58: 例外の遷移先の特権レベルを求める (csrunit.veryl)
1:     let expt_mode  : PrivMode = if mode == PrivMode::M || !medeleg[expt_cause[5:0]] ? PrivMode::M : PrivMode::S;

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

リスト17.59: リスト17.59: M-modeに遷移する割り込みを示す変数 (csrunit.veryl)
1:     // Interrupt to M-mode
2:     let interrupt_pending_mmode: UIntX = mip & mie & ~mideleg;
3:     let raise_interrupt_mmode  : logic = (mode != PrivMode::M || mstatus_mie) && interrupt_pending_mmode != 0;
4:     let interrupt_cause_mmode  : UIntX = switch {
5:         interrupt_pending_mmode[3]: CsrCause::MACHINE_SOFTWARE_INTERRUPT,
6:         interrupt_pending_mmode[7]: CsrCause::MACHINE_TIMER_INTERRUPT,
7:         interrupt_pending_mmode[9]: CsrCause::SUPERVISOR_EXTERNAL_INTERRUPT,
8:         interrupt_pending_mmode[1]: CsrCause::SUPERVISOR_SOFTWARE_INTERRUPT,
9:         interrupt_pending_mmode[5]: CsrCause::SUPERVISOR_TIMER_INTERRUPT,
10:         default                   : 0,
11:     };
リスト17.60: リスト17.60: S-modeに遷移する割り込みを示す変数 (csrunit.veryl)
1:     // Interrupt to S-mode
2:     let interrupt_pending_smode: UIntX = sip & sie;
3:     let raise_interrupt_smode  : logic = (mode <: PrivMode::S || (mode == PrivMode::S && mstatus_sie)) && interrupt_pending_smode != 0;
4:     let interrupt_cause_smode  : UIntX = switch {
5:         interrupt_pending_smode[9]: CsrCause::SUPERVISOR_EXTERNAL_INTERRUPT,
6:         interrupt_pending_smode[1]: CsrCause::SUPERVISOR_SOFTWARE_INTERRUPT,
7:         interrupt_pending_smode[5]: CsrCause::SUPERVISOR_TIMER_INTERRUPT,
8:         default                   : 0,
9:     };

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

リスト17.61: リスト17.61: M-mode、S-modeに遷移する割り込みを調停する (csrunit.veryl)
1:     // Interrupt
2:     let raise_interrupt : logic = valid && can_intr && (raise_interrupt_mmode || raise_interrupt_smode);
3:     let interrupt_cause : UIntX = if raise_interrupt_mmode ? interrupt_cause_mmode : interrupt_cause_smode;
4:     let interrupt_xtvec : Addr  = if interrupt_mode == PrivMode::M ? mtvec : stvec;
5:     let interrupt_vector: Addr  = if interrupt_xtvec[0] == 0 ?
6:         {interrupt_xtvec[msb:2], 2'b0}
7:     : // Direct
8:         {interrupt_xtvec[msb:2] + interrupt_cause[msb - 2:0], 2'b0}
9:     ; // Vectored
10:     let interrupt_mode: PrivMode = if raise_interrupt_mmode ? PrivMode::M : PrivMode::S;

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

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

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

オフセットレジスタ
0000SETSSIP0
0004SETSSIP1
....
3ff8SETSSIP4094
3ffcMTIME
setssipレジスタ

図17.4: setssipレジスタ

今のところmhartidが0のハードウェアスレッドしか存在しないため、SETSSIP0のみ実装します。aclint_ifインターフェースに、mipレジスタのSSIPビットを1にする要求のためのsetssipを作成します(リスト17.62)。

リスト17.62: リスト17.62: setssipをインターフェースに追加する (aclint_if.veryl)
1: interface aclint_if {
2:     var msip   : logic ;
3:     var mtip   : logic ;
4:     var mtime  : UInt64;
5:     var setssip: logic ;
6:     modport master {
7:         msip   : output,
8:         mtip   : output,
9:         mtime  : output,
10:         setssip: output,
11:     }

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

リスト17.63: リスト17.63: SETSSIP0に書き込むときsetssipにLSBを割り当てる (aclint_memory.veryl)
1:     always_comb {
2:         aclint.setssip = 0;
3:         if membus.valid && membus.wen && membus.addr == MMAP_ACLINT_SETSSIP {
4:             aclint.setssip = membus.wdata[0];
5:         }
6:     }

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

リスト17.64: リスト17.64: setssipをXLENビットに拡張する (csrunit.veryl)
1:     let setssip: UIntX = {1'b0 repeat XLEN - 2, aclint.setssip, 1'b0};
リスト17.65: リスト17.65: setssipでmipを更新する (csrunit.veryl)
1:     } else {
2:         mcycle  += 1;
3:         mip_reg |= setssip;
リスト17.66: リスト17.66: setssipでmipを更新する (csrunit.veryl)
1:     CsrAddr::MIP       : mip_reg    = (wdata & MIP_WMASK) | setssip;