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
処理の流れはだいたい元記事と同じですのでもっと詳しく知りたい人は元記事からどうぞ。
まずメモリの定義です。write
と read
と、初期データの読み込み用の 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::decode
で Instruction
を返すようにしました。
元記事のほうでは 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 にはお世話になりました。
まずはレジスタの定義から