https://blog.agile.esm.co.jp/entry/rvs32sim-rs

最近 ESM と RISC-V のロゴの配色が似ていると感じている @wat-aro です。

はたけやまさん

はおもしろい記事でしたね。 この記事は最後こう締めくくられています。

以上、Rubyを使ったシンプルなRISC-Vシミュレータの作り方のご紹介しました。

皆さんも梅雨の時期のおうち時間にお好きなプログラム言語でCPUシミュレータ自作なんていかがですか?

なのでこの記事を参考に Rust で RISC-V シミュレータを作成しました。

https://github.com/wat-aro/rv32sim.rs

処理の流れはだいたい元記事と同じですのでもっと詳しく知りたい人は元記事からどうぞ。

メモリ

まずメモリの定義です。writeread と、初期データの読み込み用の initialize メソッドを持っています。 32 bit のデータを 8 bit ずつ分けて data に格納します。

#[derive(Debug)]
pub struct Memory {
    data: Vec<u8>,
}

const MEMORY_SIZE: u32 = 1024 * 1024;

impl Memory {
    pub fn new() -> Self {
        Memory {
            data: vec![0; MEMORY_SIZE as usize],
        }
    }

    pub fn write(&mut self, addr: u32, word: u32) {
        let index = addr as usize;
        self.data[index] = (word & 0xff) as u8;
        self.data[index + 1] = ((word >> 8) & 0xff) as u8;
        self.data[index + 2] = ((word >> 16) & 0xff) as u8;
        self.data[index + 3] = ((word >> 24) & 0xff) as u8;
    }

    pub fn read(&self, addr: u32) -> u32 {
        let index = addr as usize;
        self.data[index] as u32
            | (self.data[index + 1] as u32) << 8
            | (self.data[index + 2] as u32) << 16
            | (self.data[index + 3] as u32) << 24
    }

    pub fn initialize(&mut self, data: Vec<u8>) {
        self.data.splice(..data.len(), data);
    }
}

命令デコーダ

元記事とは違いデコーダオブジェクトを作らずに、デコードした結果どの命令かわかるようにした Instruction を作成しました。

#[derive(Debug, PartialEq)]
pub enum Instruction {
    Add { rd: u32, rs1: u32, rs2: u32 },
    Sub { rd: u32, rs1: u32, rs2: u32 },
    Or { rd: u32, rs1: u32, rs2: u32 },
    And { rd: u32, rs1: u32, rs2: u32 },
    Addi { rd: u32, rs1: u32, imm: u32 },
    Slli { rd: u32, rs1: u32, imm: u32 },
    Beq { rs1: u32, rs2: u32, imm: u32 },
    Lw { rd: u32, rs1: u32, imm: u32 },
    Sw { rs1: u32, rs2: u32, imm: u32 },
}

こういうデータを扱いたい場合に enum は便利ですね。Instruction::decodeInstruction を返すようにしました。 元記事のほうでは NOP はデコーダの特別な状態になっていましたが、ここでは命令のひとつとして Instruction::Nop を実装しています。

impl Instruction {
    pub fn decode(inst: u32) -> Result<Instruction, Error> {
        let opcode = inst & 0x0000007f;
        let rd = (inst & 0x00000f80) >> 7;
        let funct3 = (inst & 0x00007000) >> 12;
        let rs1 = (inst & 0x000f8000) >> 15;
        let rs2 = (inst & 0x01f00000) >> 20;
        let funct7 = (inst & 0xfe000000) >> 25;

        match opcode {
            ...,
            // I-Type
            // I-Type
            0b0010011 => {
                let imm = (inst & 0xfff00000) >> 20;

                match funct3 {
                    0x0 => Ok(Addi { rd, rs1, imm }),
                    0x1 => Ok(Slli { rd, rs1, imm }),
                    _ => Err(Error::IllegalInstruction(inst)),
                }
            },
            ...
        }
    }
}

https://github.com/jameslzhu/riscv-card/blob/master/riscv-card.pdf にはお世話になりました。

CPU

まずはレジスタの定義から