FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RustでRISC-V命令セットシミュレータを作ろう (4. MMUとスーパバイザモードの実装検討)

f:id:msyksphinz:20190224185310p:plain

Rustで作るRISC-Vシミュレータ。基本的な形が出来上がったので、今度はOSを動かすためのMMUとスーパバイザモードの実装に移っていく。 基本的なところは、C++で作った実装をコピーしていき、Rustに移植するだけでよいと思っている。

メモリアクセスを実行する場合には、一度アドレス変換を行う。この関数もC++で実装したものを移植する。

    fn read_memory_word(&mut self, addr: AddrType) -> XlenType {
        let (_result, phy_addr) = self.convert_virtual_address(addr, MemAccType::Fetch);

システムレジスタへのアクセスが増えてくるので気を付けて実装しなければならないのだが、システムレジスタの実装とビット切り出しのための定数の定義が面倒くさい。 C++ではそのままdefineで定義していたのだが、Rustでよい方法はないだろうか? enumで実装すると、複数の要素が同じ値を取ることが許されないルールなのか、コンパイルエラーとなってしまった。

enum {
  SYSREG_SSTATUS_SD_MSB: u8 = 63;
  SYSREG_SSTATUS_SD_LSB: u8 = 63;   // これはエラーとなる。
...
}

仕方がないので、constですべて定義した。

pub const SYSREG_FCSR_FRM_MSB: u8 = 7;
pub const SYSREG_FCSR_FRM_LSB: u8 = 5;
pub const SYSREG_FCSR_FFLAGS_MSB: u8 = 4;
pub const SYSREG_FCSR_FFLAGS_LSB: u8 = 0;
pub const SYSREG_SSTATUS_SD_MSB: u8 = 63;
pub const SYSREG_SSTATUS_SD_LSB: u8 = 63;
...

次に、各種様々なモードや、例外コードなどを使用しているが、これをenumで定義している。 これを、システムレジスタの値から生成するとき、u8などの値をenumに変換する必要がある。これはどうするのかわからなかったのだが、とりあえずfrom_u8を定義して回避している。

pub enum PrivMode {
    User = 0,
    Supervisor = 1,
    Hypervisor = 2,
    Machine = 3,
}

impl PrivMode {
    pub fn from_u8(n: u8) -> PrivMode {
        match n {
            0 => PrivMode::User,
            1 => PrivMode::Supervisor,
            2 => PrivMode::Hypervisor,
            3 => PrivMode::Machine,
            _ => PrivMode::Machine,
        }
    }
}

あとは、enumの比較は、通常はif文で行うことはできずmatchを使うのが通常儀礼なのだが、これは面倒くさすぎるので、PartialEq, Eqを使って回避している。

#[derive(PartialEq, Eq, Copy, Clone)]
pub enum PrivMode {
    User = 0,
    Supervisor = 1,
...
        if self.get_vm_mode() == VMMode::VmSv39
            && (priv_mode == PrivMode::Supervisor || priv_mode == PrivMode::User)
        {
            let ppn_idx: Vec<u8> = vec![12, 21, 30];
            let pte_len: Vec<u8> = vec![9, 9, 26];

メモリアクセスの例外発生などは、正常なアクセスができたかどうかをTupleで返すことで判定している。これももう少しきれいな実装ができないかなあ。

    fn read_memory_word(&mut self, addr: AddrType) -> XlenType {
        // let result: MemResult;
        // let phy_addr: AddrType;
        let (_result, phy_addr) = self.convert_virtual_address(addr, MemAccType::Fetch);
        assert!(phy_addr >= DRAM_BASE);

MMUのテストパタンは、まだ通らない。もう少しページテーブルウォークの実装を確認する必要がある。