第14章
M-modeの実装 (1. CSRの実装)
14.1 概要
「第II部 RV64IMACの実装」では、RV64IMACと例外、メモリマップドI/Oを実装しました。「第III部 特権/割り込みの実装」では、次の機能を実装します。
- 特権レベル (M-mode、S-mode、U-mode)
- 仮想記憶システム(ページング)
- 割り込み(CLINT、PLIC)
これらの機能を実装したCPUはOSを動かせる十分な機能を持っています。第III部の最後ではLinuxを動かします。
14.1.1 特権レベルとは何か?
CPUで動くアプリケーションは様々ですが、多くのアプリケーションはOS(Operating System、オペレーティングシステム)の上で動かすことを前提に作成されています。「OSの上で動かす」とは、アプリケーションはOSの機能を使い、OSに管理されながら実行されるということです。
多くのOSはデバイスやメモリなどのリソースの管理を行い、簡単にそれを扱うためのインターフェースをアプリケーションに提供します。また、アプリケーションのデータを別のアプリケーションから保護したり、OSが提供する方法でしかデバイスにアクセスできなくするなどのセキュリティ機能も備えています。
セキュリティ機能を実現するためには、OSがアプリケーションを実行するときにCPUが提供する一部の機能を制限する機能が必要です。RISC-Vでは、この機能を特権レベル(privilege level)という機能、枠組みによって提供しています。ほとんどの特権レベルの機能はCSRを通じて提供されます。
特権レベルはM-mode、S-mode、U-modeの3種類*1が用意されています。それぞれの特権レベルは2ビットの数値で表すことができます(リスト14.1)。数値が大きい方が高い特権レベルです。
[*1] V拡張が実装されている場合、さらに仮想化のための特権レベルが定義されます。
高い特権レベルには低い特権レベルの機能を制限する機能があったり、高い特権レベルでしか利用できない機能が定義されています。
特権レベルを表すPrivMode
型をeeiパッケージに定義してください(リスト14.1)。
1: enum PrivMode: logic<2> { 2: M = 2'b11, 3: S = 2'b01, 4: U = 2'b00, 5: }
14.1.2 特権レベルの実装順序
RISC-VのCPUに特権レベルを実装するとき、表14.1のいずれかの構成にする必要があります。特権レベルを実装していないときはM-modeだけが実装されているように扱います。
表14.1: RISC-VのCPUがとれる構成
存在する特権レベル | 実装する章 |
---|---|
M-mode | 第14章「M-modeの実装 (1. CSRの実装)」 |
M-mode、U-mode | 第16章「U-modeの実装」 |
M-mode、S-mode、U-mode | 第17章「S-modeの実装 (1. CSRの実装)」 |
CPUがリセット(起動)したときの特権レベルはM-modeです。現在の特権レベルを保持するレジスタをcsrunitモジュールに作成します(リスト14.2、リスト14.3)。
1: var mode: PrivMode;
1: always_ff { 2: if_reset { 3: mode = PrivMode::M;
本書で実装するM-modeのCSRのアドレスをすべて定義します(リスト14.4)。本章ではこの中の一部のCSRを実装し、新しく実装する機能で使うタイミングで他のCSRを解説、実装します
1: enum CsrAddr: logic<12> { 2: // Machine Information Registers 3: MIMPID = 12'hf13, 4: MHARTID = 12'hf14, 5: // Machine Trap Setup 6: MSTATUS = 12'h300, 7: MISA = 12'h301, 8: MEDELEG = 12'h302, 9: MIDELEG = 12'h303, 10: MIE = 12'h304, 11: MTVEC = 12'h305, 12: MCOUNTEREN = 12'h306, 13: // Machine Trap Handling 14: MSCRATCH = 12'h340, 15: MEPC = 12'h341, 16: MCAUSE = 12'h342, 17: MTVAL = 12'h343, 18: MIP = 12'h344, 19: // Machine Counter/Timers 20: MCYCLE = 12'hB00, 21: MINSTRET = 12'hB02, 22: // Custom 23: LED = 12'h800, 24: }
14.1.3 XLENの定義
M-modeのCSRの多くは、特権レベルがM-modeのときのXLENであるMXLENをビット幅として定義されています。S-mode、U-modeのときのXLENはそれぞれSXLEN、UXLENと定義されており、MXLEN >= SXLEN >= UXLEN
を満たします。仕様上はmstatusレジスタを使用してSXLEN、UXLENを変更できるように実装できますが、本書ではMXLEN、SXLEN、UXLENが常に64
(eeiパッケージに定義しているXLEN)になるように実装します。
14.2 misaレジスタ (Machine ISA)

図14.1: misaレジスタ
misaレジスタは、ハードウェアスレッドがサポートするISAを表すMXLENビットのレジスタです。MXLフィールドにはMXLENを表す数値(表14.2)が格納されています。Extensionsフィールドは下位ビットからそれぞれアルファベットのA、B、 Cと対応していて、それぞれのビットはそのアルファベットが表す拡張(例えばA拡張ならAビット、C拡張ならC)が実装されているなら1
に設定されています。仕様上はExtensionsフィールドを書き換えられるように実装できますが、本書では書き換えられないようにします。
表14.2: XLENと数値の対応
XLEN | 数値 |
---|---|
32 | 1 |
64 | 2 |
128 | 3 |
misaレジスタを作成し、読み込めるようにします(リスト14.5、リスト14.6)。CPUはRV64IMAC
なのでMXLフィールドに64
を表す2
を設定し、ExtensionsフィールドのM拡張(M)、基本整数命令セット(I)、C拡張(C)、A拡張(A)のビットを1
にしています。
1: let misa : UIntX = {2'd2, 1'b0 repeat XLEN - 28, 26'b00000000000001000100000101}; // M, I, C, A
1: rdata = case csr_addr { 2: CsrAddr::MISA : misa,
これ以降、AというCSRのBフィールド、ビットのことをA.Bと表記することがあります。
14.3 mimpidレジスタ (Machine Implementation ID)

図14.2: mimpidレジスタ
mimpidレジスタは、プロセッサ実装のバージョンを表す値を格納しているMXLENビットのレジスタです。値が0
のときは、mimpidレジスタが実装されていないことを示します。
他にもプロセッサの実装の情報を表すレジスタ(mvendorid*2、marchid*3)がありますが、本書では実装しません。
[*2] 製造業者のID(JEDEC ID)を格納します
[*3] マイクロアーキテクチャの種類を示すIDを格納します
せっかくなので、適当な値を設定しましょう。eeiパッケージにIDを定義して、読み込めるようにします(リスト14.7、リスト14.8)。
1: // Machine Implementation ID 2: const MACHINE_IMPLEMENTATION_ID: UIntX = 1;
1: rdata = case csr_addr { 2: CsrAddr::MISA : misa, 3: CsrAddr::MIMPID: MACHINE_IMPLEMENTATION_ID,
14.4 mhartidレジスタ (Hart ID)

図14.3: mhartidレジスタ
mhartidレジスタは、今実行しているハードウェアスレッド(hart)のIDを格納しているMXLENビットのレジスタです。複数のプロセッサ、ハードウェアスレッドが存在するときに、それぞれを区別するために使用します。IDはどんな値でも良いですが、環境内にIDが0
のハードウェアスレッドが1つ存在する必要があります。基本編で作るCPUは1コア1ハードウェアスレッドであるためmhartidレジスタに0
を設定します。
mhartレジスタを作成し、読み込めるようにします(リスト14.9、リスト14.10)。
1: let mhartid: UIntX = 0;
1: rdata = case csr_addr { 2: CsrAddr::MISA : misa, 3: CsrAddr::MIMPID : MACHINE_IMPLEMENTATION_ID, 4: CsrAddr::MHARTID: mhartid,
14.5 mstatusレジスタ (Machine Status)

図14.4: mstatusレジスタ
mstatusレジスタは、拡張の設定やトラップ、状態などを管理するMXLENビットのレジスタです。基本編では図14.4に示しているフィールドを、そのフィールドが必要になったときに実装します。とりあえず今のところは読み込みだけできるようにします(リスト14.11、リスト14.12、リスト14.13、リスト14.14、リスト14.15、リスト14.16)。
1: const MSTATUS_WMASK: UIntX = 'h0000_0000_0000_0000 as UIntX;
1: wmask = case csr_addr { 2: CsrAddr::MSTATUS: MSTATUS_WMASK,
1: var mstatus: UIntX;
1: rdata = case csr_addr { 2: CsrAddr::MISA : misa, 3: CsrAddr::MIMPID : MACHINE_IMPLEMENTATION_ID, 4: CsrAddr::MHARTID: mhartid, 5: CsrAddr::MSTATUS: mstatus,
1: always_ff { 2: if_reset { 3: mode = PrivMode::M; 4: mstatus = 0;
1: if is_wsc { 2: case csr_addr { 3: CsrAddr::MSTATUS: mstatus = wdata; 4: CsrAddr::MTVEC : mtvec = wdata;
14.6 ハードウェアパフォーマンスモニタ
RISC-Vには、ハードウェアの性能評価指標を得るためにmcycleとminstret、それぞれ29個のmhpmcounter、mhpmeventレジスタが定義されています。それぞれ次の値を得るために利用できます。
- mcycleレジスタ (64ビット)
- ハードウェアスレッドが起動(リセット)されてから経過したサイクル数
- minstretレジスタ (64ビット)
- ハードウェアスレッドがリタイア(実行完了)した命令数
- mhpmcounter、mhpmeventレジスタ (64ビット)
- mhpmeventレジスタで選択された指標がmhpmcounterレジスタに反映されます。
基本編ではmcycle、minstretレジスタを実装します。mhpmcounter、mhpmeventレジスタは表示するような指標がないため実装しません。また、mcountinhibitレジスタを使うとカウントを停止するかを制御できますが、これも実装しません。
14.6.1 mcycleレジスタ
mcycleレジスタを定義して読み込めるようにします。(リスト14.17、リスト14.18)。
1: var mcycle : UInt64;
1: CsrAddr::MCYCLE : mcycle,
always_ffブロックで、クロックごとに値を更新します(リスト14.19)。
1: always_ff { 2: if_reset { 3: mode = PrivMode::M; 4: mstatus = 0; 5: mtvec = 0; 6: mcycle = 0; 7: mepc = 0; 8: mcause = 0; 9: mtval = 0; 10: led = 0; 11: } else { 12: mcycle += 1;
14.6.2 minstretレジスタ
coreモジュールでinstretレジスタを作成し、トラップが発生していない命令がWBステージに到達した場合にインクリメントします(リスト14.20、リスト14.21)。
1: var minstret : UInt64;
1: always_ff { 2: if_reset { 3: minstret = 0; 4: } else { 5: if wbq_rvalid && wbq_rready && !wbq_rdata.raise_trap { 6: minstret += 1; 7: } 8: } 9: }
minstret
の値をcsrunitモジュールに渡し、読み込めるようにします(リスト14.22、リスト14.23、リスト14.24)。
1: minstret : input UInt64 ,
1: minstret ,
1: CsrAddr::MCYCLE : mcycle, 2: CsrAddr::MINSTRET: minstret, 3: CsrAddr::MEPC : mepc,
csrunitモジュールはMRET命令でもraise_trap
フラグを立てるため、このままではMRET命令でminstret
がインクリメントされません。そのため、トラップから戻る命令であることを示すフラグをcsrunitモジュールに作成し、正しくインクリメントされるようにします(リスト14.25、リスト14.26、リスト14.27、リスト14.28)。
1: trap_return: output logic ,
1: // Trap Return 2: assign trap_return = valid && is_mret && !raise_expt; 3: 4: // Trap 5: assign raise_trap = raise_expt || trap_return;
1: trap_return: csru_trap_return ,
1: wbq_wdata.raise_trap = csru_raise_trap && !csru_trap_return;
14.7 mscratchレジスタ (Machine Scratch)

図14.5: mscratchレジスタ
mscratchレジスタは、M-modeのときに自由に読み書きできるMXLENビットのレジスタです。
mscratchレジスタの典型的な用途はコンテキストスイッチです。コンテキストスイッチとは、実行しているアプリケーションAを別のアプリケーションBに切り替えることを指します。多くの場合、コンテキストスイッチはトラップによって開始しますが、Aの実行途中の状態(レジスタの値)を保存しないとAを実行再開できなくなります。そのため、コンテキストスイッチが始まったとき、つまりトラップが発生したときにレジスタの値をメモリに保存する必要があります。しかし、ストア命令はアドレスの指定にレジスタの値を使うため、アドレスの指定のために少なくとも1つのレジスタの値を犠牲にしなければならず、すべてのレジスタの値を完全に保存できません*4。
[*4] x0と即値を使うとアドレス0付近にすべてのレジスタの値を保存できますが、一般的な方法ではありません
この問題を回避するために、一時的な値の保存場所としてmscratchレジスタが使用されます。事前にmscratchレジスタにメモリアドレス(やメモリアドレスを得るための情報)を格納しておき、CSRRW命令でmscratchレジスタの値とレジスタの値を交換することで任意の場所にレジスタの値を保存できます。
mscratchレジスタを定義し、自由に読み書きできるようにします(リスト14.29、リスト14.30、リスト14.31、リスト14.32、リスト14.33、リスト14.34)。
1: var mcycle : UInt64; 2: var mscratch: UIntX ; 3: var mepc : UIntX ;
1: mtvec = 0; 2: mscratch = 0; 3: mcycle = 0;
1: CsrAddr::MINSTRET: minstret, 2: CsrAddr::MSCRATCH: mscratch, 3: CsrAddr::MEPC : mepc,
1: const MTVEC_WMASK : UIntX = 'hffff_ffff_ffff_fffc; 2: const MSCRATCH_WMASK: UIntX = 'hffff_ffff_ffff_ffff; 3: const MEPC_WMASK : UIntX = 'hffff_ffff_ffff_fffe;
1: CsrAddr::MTVEC : MTVEC_WMASK, 2: CsrAddr::MSCRATCH: MSCRATCH_WMASK, 3: CsrAddr::MEPC : MEPC_WMASK,
1: CsrAddr::MTVEC : mtvec = wdata; 2: CsrAddr::MSCRATCH: mscratch = wdata; 3: CsrAddr::MEPC : mepc = wdata;