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) 差分をみる
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) 差分をみる
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) 差分をみる
const MSTATUS_UXL: UInt64 = 2 << 32;
const MSTATUS_SXL: UInt64 = 2 << 34;
▼リスト17.4: mstatus.SXLの初期値を設定する (csrunit.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) 差分をみる
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) 差分をみる
var scounteren: UInt32;
▼リスト17.7: scounterenレジスタを0でリセットする (csrunit.veryl) 差分をみる
mtval = 0;
scounteren = 0;
led = 0;
▼リスト17.8: rdataにscounterenレジスタの値を設定する (csrunit.veryl) 差分をみる
CsrAddr::MTVAL : mtval,
CsrAddr::SCOUNTEREN: {1'b0 repeat XLEN - 32, scounteren},
CsrAddr::LED : led,
▼リスト17.9: 書き込みマスクの定義 (csrunit.veryl) 差分をみる
const SCOUNTEREN_WMASK: UIntX = 'h0000_0000_0000_0007 as UIntX;
▼リスト17.10: wmaskに書き込みマスクを設定する (csrunit.veryl) 差分をみる
CsrAddr::MTVAL : MTVAL_WMASK,
CsrAddr::SCOUNTEREN: SCOUNTEREN_WMASK,
CsrAddr::LED : LED_WMASK,
▼リスト17.11: scounterenレジスタに書き込む (csrunit.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) 差分をみる
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レジスタはmstatusレジスタの一部をS-modeで読み込み、書き込みできるようにしたSXLENビットのレジスタです。 本章ではmstatusレジスタに読み込み、書き込みマスクを適用することでsstatusレジスタを実装します。
sstatusレジスタの書き込みマスクを定義します ( リスト13、 リスト14 )。
▼リスト17.13: 書き込みマスクの定義 (csrunit.veryl) 差分をみる
const SSTATUS_WMASK : UIntX = 'h0000_0000_0000_0000 as UIntX;
▼リスト17.14: wmaskに書き込みマスクを設定する (csrunit.veryl) 差分をみる
CsrAddr::MTVAL : MTVAL_WMASK,
CsrAddr::SSTATUS : SSTATUS_WMASK,
CsrAddr::SCOUNTEREN: SCOUNTEREN_WMASK,
読み込みマスクを定義し、mstatusレジスタにマスクを適用した値をsstatusレジスタの値にします ( リスト15、 リスト16、 リスト17 )。
▼リスト17.15: 読み込みマスクの定義 (csrunit.veryl) 差分をみる
const SSTATUS_RMASK: UIntX = 'h8000_0003_018f_e762;
▼リスト17.16: sstatusの値をmstatusにマスクを適用したものにする (csrunit.veryl) 差分をみる
let sstatus : UIntX = mstatus & SSTATUS_RMASK;
▼リスト17.17: rdataにsstatusレジスタの値を設定する (csrunit.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) 差分をみる
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に遷移する条件は次の通りです。
- 割り込み原因に対応したmipレジスタのビットが
1である - 割り込み原因に対応したmieレジスタのビットが
1である - 現在の特権レベルがM-mode未満である。またはmstatus.MIEが
1である - 割り込み原因に対応したmidelegレジスタのビットが
0である
割り込みでS-modeに遷移する条件は次の通りです。
- 割り込み原因に対応したsipレジスタのビットが
1である - 割り込み原因に対応したsieレジスタのビットが
1である - 現在の特権レベルが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) 差分をみる
var stvec : UIntX ;
var sscratch : UIntX ;
var sepc : UIntX ;
var scause : UIntX ;
var stval : UIntX ;
▼リスト17.20: レジスタを0でリセットする (csrunit.veryl) 差分をみる
stvec = 0;
sscratch = 0;
sepc = 0;
scause = 0;
stval = 0;
▼リスト17.21: rdataにレジスタの値を割り当てる (csrunit.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) 差分をみる
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) 差分をみる
CsrAddr::STVEC : STVEC_WMASK,
CsrAddr::SSCRATCH : SSCRATCH_WMASK,
CsrAddr::SEPC : SEPC_WMASK,
CsrAddr::SCAUSE : SCAUSE_WMASK,
CsrAddr::STVAL : STVAL_WMASK,
▼リスト17.24: レジスタの書き込み (csrunit.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) 差分をみる
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) 差分をみる
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) 差分をみる
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) 差分をみる
const MSTATUS_WMASK : UIntX = 'h0000_0000_0020_19aa as UIntX;
▼リスト17.29: 書き込みマスクを変更する (csrunit.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) 差分をみる
} 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) 差分をみる
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) 差分をみる
let is_sret: logic = inst_bits == 32'h10200073;
▼リスト17.33: SRET命令のとき遷移先の特権レベル、アドレスを変更する (csrunit.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) 差分をみる
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) 差分をみる
} 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) 差分をみる
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) 差分をみる
const MSTATUS_WMASK : UIntX = 'h0000_0000_0060_19aa as UIntX;
例外を判定します ( リスト38、 リスト39 )。
▼リスト17.38: TSRビットを表す変数 (csrunit.veryl) 差分をみる
let mstatus_tsr : logic = mstatus[22];
▼リスト17.39: mstatus.TSRが1のときにS-modeでSRET命令を実行したら例外にする (csrunit.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) 差分をみる
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) 差分をみる
CsrAddr::MIP : MIP_WMASK,
mip_regレジスタを作成します。 mipの値を、mip_regとACLINTの状態をOR演算したものに変更します (リスト42)。
▼リスト17.42: レジスタを作成して変数に適用する (csrunit.veryl) 差分をみる
var mip_reg: UIntX;
let mip : UIntX = mip_reg | {
mip_regレジスタのリセット、書き込みを実装します ( リスト43、 リスト44 )。 wdataにはACLINTの状態が含まれているので、書き込みマスクをもう一度適用します。
▼リスト17.43: レジスタの値を0でリセットする (csrunit.veryl) 差分をみる
mie = 0;
mip_reg = 0;
mcounteren = 0;
▼リスト17.44: mipレジスタの書き込み (csrunit.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) 差分をみる
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レジスタの実装

medeleg、mideleg、sip、sieレジスタを実装します。
medeleg、midelegレジスタはそれぞれ委譲できる例外、割り込みに対応するビットだけ書き換えられるようにします。 sipレジスタはmidelegレジスタで委譲された割り込みに対応するビットだけ値を参照できるように、 sieレジスタはmidelegレジスタで委譲された割り込みに対応するビットだけ書き換えられるようにします。
レジスタを作成し、読み込めるようにします ( リスト46、 リスト47、 リスト48、 リスト49、 リスト50、 リスト51 )。
▼リスト17.46: medeleg、midelegレジスタの定義 (csrunit.veryl) 差分をみる
var medeleg : UInt64;
var mideleg : UIntX ;
▼リスト17.47: sie、sieレジスタの定義 (csrunit.veryl) 差分をみる
let sip : UIntX = mip & mideleg;
var sie : UIntX ;
▼リスト17.48: medeleg、midelegレジスタを0でリセットする (csrunit.veryl) 差分をみる
medeleg = 0;
mideleg = 0;
▼リスト17.49: sieレジスタを0でリセットする (csrunit.veryl) 差分をみる
sie = 0;
▼リスト17.50: rdataにmedeleg、midelegレジスタの値を割り当てる (csrunit.veryl) 差分をみる
CsrAddr::MEDELEG : medeleg,
CsrAddr::MIDELEG : mideleg,
▼リスト17.51: rdataにsip、sieレジスタの値を割り当てる (csrunit.veryl) 差分をみる
CsrAddr::SIP : sip,
CsrAddr::SIE : sie & mideleg,
書き込みマスクを設定し、書き込めるようにします ( リスト52、 リスト53、 リスト54、 リスト55、 リスト56、 リスト57 )。
▼リスト17.52: 書き込みマスクの定義 (csrunit.veryl) 差分をみる
const MEDELEG_WMASK : UIntX = 'hffff_ffff_fffe_f7ff;
const MIDELEG_WMASK : UIntX = 'h0000_0000_0000_0222 as UIntX;
▼リスト17.53: 書き込みマスクの定義 (csrunit.veryl) 差分をみる
const SIE_WMASK : UIntX = 'h0000_0000_0000_0222 as UIntX;
▼リスト17.54: wmaskに書き込みマスクを設定する (csrunit.veryl) 差分をみる
CsrAddr::MEDELEG : MEDELEG_WMASK,
CsrAddr::MIDELEG : MIDELEG_WMASK,
▼リスト17.55: wmaskに書き込みマスクを設定する (csrunit.veryl) 差分をみる
CsrAddr::SIE : SIE_WMASK & mideleg,
▼リスト17.56: medeleg、midelegレジスタの書き込み (csrunit.veryl) 差分をみる
CsrAddr::MEDELEG : medeleg = wdata;
CsrAddr::MIDELEG : mideleg = wdata;
▼リスト17.57: sieレジスタの書き込み (csrunit.veryl) 差分をみる
CsrAddr::SIE : sie = wdata;
割り込み条件、トラップの動作を変更する
作成したCSRを利用して、割り込みが発生する条件、トラップが発生したときのCSRの操作を変更します。
例外が発生するとき、遷移先の特権レベルをmedelegレジスタによって変更します (リスト58)。
▼リスト17.58: 例外の遷移先の特権レベルを求める (csrunit.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) 差分をみる
// 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) 差分をみる
// 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) 差分をみる
// 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デバイスのメモリマップ
| オフセット | レジスタ |
|---|---|
| 0000 | SETSSIP0 |
| 0004 | SETSSIP1 |
| .. | .. |
| 3ff8 | SETSSIP4094 |
| 3ffc | MTIME |
▼リスト17.62: setssipをインターフェースに追加する (aclint_if.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) 差分をみる
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) 差分をみる
let setssip: UIntX = {1'b0 repeat XLEN - 2, aclint.setssip, 1'b0};
▼リスト17.65: setssipでmipを更新する (csrunit.veryl) 差分をみる
} else {
mcycle += 1;
mip_reg |= setssip;
▼リスト17.66: setssipでmipを更新する (csrunit.veryl) 差分をみる
CsrAddr::MIP : mip_reg = (wdata & MIP_WMASK) | setssip;
多くの実装ではこれらの割り込みを委譲できないように実装するようです。そのため、本書で実装するコアでも委譲できないように実装します。 ↩︎