summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Lee <me@nwlee.tech>2026-09-25 22:39:01 -0500
committerNathan Lee <me@nwlee.tech>2026-09-25 22:39:01 -0500
commit6858c9e7c97f772eb357e3eee0c1fcfb8adcb699 (patch)
tree691b3591fa0fc817b88f3a587fa0d613f9d475c1
scaffold emulator from wrv32em
-rw-r--r--.gitignore3
-rw-r--r--website-emu/Cargo.toml7
-rw-r--r--website-emu/src/core.rs291
-rw-r--r--website-emu/src/lib.rs259
-rw-r--r--website-emu/src/mem.rs105
-rw-r--r--website-emu/src/state.rs19
6 files changed, 684 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..92d2735
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+website-emu/target
+website-emu/Cargo.lock
+.idea/ \ No newline at end of file
diff --git a/website-emu/Cargo.toml b/website-emu/Cargo.toml
new file mode 100644
index 0000000..575b12b
--- /dev/null
+++ b/website-emu/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "website-emu"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+wasm-bindgen = "0.2"
diff --git a/website-emu/src/core.rs b/website-emu/src/core.rs
new file mode 100644
index 0000000..6817c94
--- /dev/null
+++ b/website-emu/src/core.rs
@@ -0,0 +1,291 @@
+use crate::state::CpuState;
+
+pub fn add(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = state.registers[rs1].wrapping_add(state.registers[rs2]);
+}
+
+pub fn sub(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = state.registers[rs1].wrapping_sub(state.registers[rs2]);
+}
+
+pub fn xor(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = state.registers[rs1] ^ state.registers[rs2];
+}
+
+pub fn or(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = state.registers[rs1] | state.registers[rs2];
+}
+
+pub fn and(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = state.registers[rs1] & state.registers[rs2];
+}
+
+pub fn sll(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = state.registers[rs1] << (state.registers[rs2] & 0b11111);
+}
+
+pub fn srl(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = state.registers[rs1] >> (state.registers[rs2] & 0b11111);
+}
+
+pub fn sra(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] =
+ ((state.registers[rs1] as i32) >> (state.registers[rs2] & 0b11111)) as u32;
+}
+
+pub fn slt(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ let rs1_signed = state.registers[rs1] as i32;
+ let rs2_signed = state.registers[rs2] as i32;
+
+ state.registers[rd] = if rs1_signed < rs2_signed { 1 } else { 0 };
+}
+
+pub fn sltu(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = if state.registers[rs1] < state.registers[rs2] {
+ 1
+ } else {
+ 0
+ };
+}
+
+pub fn mul(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = state.registers[rs1].wrapping_mul(state.registers[rs2]);
+}
+
+pub fn mulh(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = (((state.registers[rs1] as i32 as i64)
+ * (state.registers[rs2] as i32 as i64))
+ >> 32) as u32;
+}
+
+pub fn mulhsu(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ let rs1_signed = (state.registers[rs1] as i32) as i64;
+ let rs2_unsigned = state.registers[rs2] as i64;
+
+ state.registers[rd] = ((rs1_signed * rs2_unsigned) >> 32) as u32;
+}
+
+pub fn mulhu(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] =
+ ((state.registers[rs1] as u64 * state.registers[rs2] as u64) >> 32) as u32;
+}
+
+pub fn div(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ let a = state.registers[rs1] as i32;
+ let b = state.registers[rs2] as i32;
+
+ state.registers[rd] = if b == 0 {
+ u32::MAX
+ } else if a == i32::MIN && b == -1 {
+ a as u32
+ } else {
+ (a / b) as u32
+ };
+}
+
+pub fn divu(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = if state.registers[rs2] == 0 {
+ u32::MAX
+ } else {
+ state.registers[rs1] / state.registers[rs2]
+ };
+}
+
+pub fn rem(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ let a = state.registers[rs1] as i32;
+ let b = state.registers[rs2] as i32;
+
+ state.registers[rd] = if b == 0 {
+ a as u32
+ } else if a == i32::MIN && b == -1 {
+ 0
+ } else {
+ (a % b) as u32
+ };
+}
+
+pub fn remu(state: &mut CpuState, rd: usize, rs1: usize, rs2: usize) {
+ state.registers[rd] = if state.registers[rs2] == 0 {
+ state.registers[rs1]
+ } else {
+ state.registers[rs1] % state.registers[rs2]
+ };
+}
+
+pub fn addi(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = state.registers[rs1].wrapping_add(imm);
+}
+
+pub fn xori(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = state.registers[rs1] ^ imm;
+}
+
+pub fn ori(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = state.registers[rs1] | imm;
+}
+
+pub fn andi(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = state.registers[rs1] & imm;
+}
+
+pub fn slli(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = state.registers[rs1] << (imm & 0b11111);
+}
+pub fn srli(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = state.registers[rs1] >> (imm & 0b11111);
+}
+pub fn srai(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = ((state.registers[rs1] as i32) >> (imm & 0b11111)) as u32;
+}
+
+pub fn slti(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = if (state.registers[rs1] as i32) < (imm as i32) {
+ 1
+ } else {
+ 0
+ };
+}
+
+pub fn sltiu(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = if state.registers[rs1] < imm { 1 } else { 0 };
+}
+pub fn lb(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ let byte = state.memory.read(state.registers[rs1] + imm).unwrap();
+ state.registers[rd] = (byte as i8) as u32;
+}
+
+pub fn lh(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ let halfword = state.memory.read_halfword(state.registers[rs1] + imm).unwrap();
+ state.registers[rd] = (halfword as i16) as u32;
+}
+
+pub fn lw(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ let addr = state.registers[rs1].wrapping_add(imm);
+ let word = state.memory.read_word(addr).unwrap();
+ state.registers[rd] = word;
+}
+
+pub fn lbu(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = state
+ .memory
+ .read(state.registers[rs1].wrapping_add(imm))
+ .unwrap() as u32;
+}
+
+pub fn lhu(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = state.memory.read_halfword(state.registers[rs1] + imm).unwrap() as u32;
+}
+pub fn sb(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) {
+ let addr = state.registers[rs1] + imm;
+ state
+ .memory
+ .write(addr, state.registers[rs2] as u8)
+ .unwrap();
+}
+
+pub fn sh(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) {
+ let addr = (state.registers[rs1] + imm) as usize;
+
+ let halfword= (state.registers[rs2] & 0xFFFF) as u16;
+
+ state.memory.write_halfword(addr as u32, halfword).unwrap();
+}
+
+pub fn sw(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) {
+ let addr = (state.registers[rs1] + imm) as usize;
+
+ state.memory.write_word(addr as u32, state.registers[rs2]).unwrap();
+}
+
+pub fn beq(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) {
+ if state.registers[rs1] == state.registers[rs2] {
+ state.pc = state.pc.wrapping_add(imm);
+ } else {
+ state.pc += 4;
+ }
+}
+
+pub fn bne(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) {
+ if state.registers[rs1] != state.registers[rs2] {
+ state.pc = state.pc.wrapping_add(imm);
+ } else {
+ state.pc += 4;
+ }
+}
+
+pub fn blt(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) {
+ if (state.registers[rs1] as i32) < (state.registers[rs2] as i32) {
+ state.pc = state.pc.wrapping_add(imm);
+ } else {
+ state.pc += 4;
+ }
+}
+
+pub fn bge(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) {
+ if (state.registers[rs1] as i32) >= (state.registers[rs2] as i32) {
+ state.pc = state.pc.wrapping_add(imm);
+ } else {
+ state.pc += 4;
+ }
+}
+
+pub fn bltu(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) {
+ if state.registers[rs1] < state.registers[rs2] {
+ state.pc = state.pc.wrapping_add(imm);
+ } else {
+ state.pc += 4;
+ }
+}
+
+pub fn bgeu(state: &mut CpuState, rs1: usize, rs2: usize, imm: u32) {
+ if state.registers[rs1] >= state.registers[rs2] {
+ state.pc = state.pc.wrapping_add(imm);
+ } else {
+ state.pc += 4;
+ }
+}
+pub fn jal(state: &mut CpuState, rd: usize, imm: u32) {
+ state.registers[rd] = state.pc + 4;
+ state.pc = state.pc.wrapping_add(imm);
+}
+
+pub fn jalr(state: &mut CpuState, rd: usize, rs1: usize, imm: u32) {
+ state.registers[rd] = state.pc + 4;
+ state.pc = state.registers[rs1] + imm;
+}
+pub fn lui(state: &mut CpuState, rd: usize, imm: u32) {
+ state.registers[rd] = imm << 12;
+}
+pub fn auipc(state: &mut CpuState, rd: usize, imm: u32) {
+ state.registers[rd] = state.pc + (imm << 12);
+}
+
+pub fn csrrw(state: &mut CpuState, rd: usize, rs1: usize, csr: usize) {
+ state.registers[rd] = state.csr[csr];
+ state.csr[csr] = state.registers[rs1];
+}
+
+pub fn csrrwi(state: &mut CpuState, rd: usize, imm: u32, csr: usize) {
+ state.registers[rd] = state.csr[csr];
+ state.csr[csr] = imm;
+}
+
+pub fn csrrc(state: &mut CpuState, rd: usize, rs1: usize, csr: usize) {
+ state.registers[rd] = state.csr[csr];
+ state.csr[csr] &= !state.registers[rs1];
+}
+
+pub fn csrrci(state: &mut CpuState, rd: usize, imm: u32, csr: usize) {
+ state.registers[rd] = state.csr[csr];
+ state.csr[csr] &= !imm;
+}
+
+pub fn csrrs(state: &mut CpuState, rd: usize, rs1: usize, csr: usize) {
+ state.registers[rd] = state.csr[csr];
+ state.csr[csr] |= state.registers[rs1];
+}
+
+pub fn csrrsi(state: &mut CpuState, rd: usize, imm: u32, csr: usize) {
+ state.registers[rd] = state.csr[csr];
+ state.csr[csr] |= imm;
+} \ No newline at end of file
diff --git a/website-emu/src/lib.rs b/website-emu/src/lib.rs
new file mode 100644
index 0000000..7c1a285
--- /dev/null
+++ b/website-emu/src/lib.rs
@@ -0,0 +1,259 @@
+mod core;
+mod mem;
+mod state;
+
+use wasm_bindgen::prelude::wasm_bindgen;
+use crate::core::*;
+use crate::mem::*;
+use crate::state::CpuState;
+
+const REGISTER_NAME_WIDTH: u32 = 0b11111;
+
+const DRAM_LOAD: u32 = 0x20000000;
+const OS_LOAD: u32 = 0x80000000;
+const INITRD_LOAD: u32 = 0;
+
+#[wasm_bindgen]
+pub fn boot(image: &[u8], initrd: &[u8], memory: &[u8]) {
+ let mut cpu_memory = CpuMemory {
+ devices: Vec::new(),
+ };
+
+ let memory_len = memory.len();
+ let os_size = image.len();
+ let initrd_size = initrd.len();
+
+ let ram_device = LogicalMemory {
+ base: DRAM_LOAD,
+ size: memory_len as u32,
+ dram: memory.to_vec().into_boxed_slice(),
+ };
+
+ let os_image = LogicalMemory {
+ base: OS_LOAD,
+ size: os_size as u32,
+ dram: image.to_vec().into_boxed_slice(),
+ };
+
+ let initrd_image = LogicalMemory {
+ base: INITRD_LOAD,
+ size: initrd_size as u32,
+ dram: initrd.to_vec().into_boxed_slice(),
+ };
+
+ cpu_memory.register_device(
+ DRAM_LOAD..DRAM_LOAD + memory_len as u32,
+ Box::new(ram_device)
+ );
+
+ let mut state = CpuState::new(cpu_memory);
+
+ state.pc = 0x80000000;
+ state.registers[10] = 0;
+ state.registers[2] = DRAM_LOAD + memory_len as u32;
+
+ loop {
+ {
+ handle_instruction(&mut state);
+ state.registers[0] = 0;
+ }
+ }
+}
+
+fn handle_instruction(state: &mut CpuState) {
+ let pc = state.pc;
+ let word = state.memory.read_instruction(pc).expect(format!("pc {:x} does not map to valid memory", pc).as_str());
+ let opcode = word & 0x7f;
+ match opcode {
+ 0b0110011 => {
+ let f7 = word >> 25;
+ let f3 = (word >> 12) & 0b111;
+
+ let rs2 = ((word >> 20) & REGISTER_NAME_WIDTH) as usize;
+ let rs1 = ((word >> 15) & REGISTER_NAME_WIDTH) as usize;
+ let rd = ((word >> 7) & REGISTER_NAME_WIDTH) as usize;
+
+ match (f7 << 3) | f3 {
+ 0 => add(state, rd, rs1, rs2),
+ 256 => sub(state, rd, rs1, rs2),
+ 4 => xor(state, rd, rs1, rs2),
+ 6 => or(state, rd, rs1, rs2),
+ 7 => and(state, rd, rs1, rs2),
+ 1 => sll(state, rd, rs1, rs2),
+ 5 => srl(state, rd, rs1, rs2),
+ 261 => sra(state, rd, rs1, rs2),
+ 2 => slt(state, rd, rs1, rs2),
+ 3 => sltu(state, rd, rs1, rs2),
+ 8 => mul(state, rd, rs1, rs2),
+ 9 => mulh(state, rd, rs1, rs2),
+ 10 => mulhsu(state, rd, rs1, rs2),
+ 11 => mulhu(state, rd, rs1, rs2),
+ 12 => div(state, rd, rs1, rs2),
+ 13 => divu(state, rd, rs1, rs2),
+ 14 => rem(state, rd, rs1, rs2),
+ 15 => remu(state, rd, rs1, rs2),
+ _ => panic!("Illegal instruction: {:x}", word),
+ }
+ }
+ 0b0010011 => {
+ let imm = ((word as i32) >> 20) as u32;
+ let f3 = (word >> 12) & 0b111;
+
+ let rs1 = ((word >> 15) & 0b11111) as usize;
+ let rd = ((word >> 7) & 0b11111) as usize;
+
+ match f3 {
+ 0 => addi(state, rd, rs1, imm),
+ 4 => xori(state, rd, rs1, imm),
+ 6 => ori(state, rd, rs1, imm),
+ 7 => andi(state, rd, rs1, imm),
+ 1 => {
+ if (imm >> 5) != 0 {
+ panic!("Illegal instruction: {:x}", word);
+ }
+ slli(state, rd, rs1, imm);
+ }
+ 5 => {
+ if (imm >> 5) != 0 {
+ panic!("Illegal instruction: {:x}", word);
+ }
+ if (imm >> 5) & 0b1111111 == 0 {
+ srli(state, rd, rs1, imm);
+ } else {
+ srai(state, rd, rs1, imm);
+ }
+ }
+ 2 => slti(state, rd, rs1, imm),
+ 3 => sltiu(state, rd, rs1, imm),
+ _ => panic!("Illegal instruction: {:x}", word),
+ }
+ }
+ 0b0000011 => {
+ let imm = ((word as i32) >> 20) as u32;
+ let f3 = (word >> 12) & 0b111;
+
+ let rs1 = ((word >> 15) & 0b11111) as usize;
+ let rd = ((word >> 7) & 0b11111) as usize;
+
+ match f3 {
+ 0 => lb(state, rd, rs1, imm),
+ 1 => lh(state, rd, rs1, imm),
+ 2 => lw(state, rd, rs1, imm),
+ 4 => lbu(state, rd, rs1, imm),
+ 5 => lhu(state, rd, rs1, imm),
+ _ => panic!("Illegal instruction: {:x}", word),
+ }
+ }
+ 0b0100011 => {
+ let imm_low = (word >> 7) & 0b11111;
+ let imm_high = (word >> 25) & 0b1111111;
+ let f3 = (word >> 12) & 0b111;
+ let rs1 = ((word >> 15) & REGISTER_NAME_WIDTH) as usize;
+ let rs2 = ((word >> 20) & REGISTER_NAME_WIDTH) as usize;
+
+ let imm = ((((imm_high << 5) | imm_low) as i32) << 20 >> 20) as u32;
+
+ match f3 {
+ 0 => sb(state, rs1, rs2, imm),
+ 1 => sh(state, rs1, rs2, imm),
+ 2 => sw(state, rs1, rs2, imm),
+ _ => panic!("Illegal instruction: {:x}", word),
+ };
+ }
+ 0b1100011 => {
+ let f3 = (word >> 12) & 0b111;
+ let rs1 = ((word >> 15) & REGISTER_NAME_WIDTH) as usize;
+ let rs2 = ((word >> 20) & REGISTER_NAME_WIDTH) as usize;
+
+ let imm = (((word >> 31) & 0x1) << 12)
+ | (((word >> 7) & 0x1) << 11)
+ | (((word >> 25) & 0x3f) << 5)
+ | (((word >> 8) & 0xf) << 1);
+
+ let imm = ((imm as i32) << 19 >> 19) as u32;
+
+ match f3 {
+ 0 => beq(state, rs1, rs2, imm),
+ 1 => bne(state, rs1, rs2, imm),
+ 4 => blt(state, rs1, rs2, imm),
+ 5 => bge(state, rs1, rs2, imm),
+ 6 => bltu(state, rs1, rs2, imm),
+ 7 => bgeu(state, rs1, rs2, imm),
+ _ => panic!("Illegal instruction: {:x}", word),
+ }
+ return;
+ }
+ 0b1101111 => {
+ let rd = (word >> 7) & REGISTER_NAME_WIDTH;
+ let imm = (((word >> 31) & 0x1) << 20)
+ | (((word >> 12) & 0xFF) << 12)
+ | (((word >> 20) & 0x1) << 11)
+ | (((word >> 21) & 0x3FF) << 1);
+ let imm = ((imm as i32) << 11 >> 11) as u32;
+ jal(state, rd as usize, imm);
+ return;
+ }
+ 0b1100111 => {
+ let rs1 = (word >> 15) & REGISTER_NAME_WIDTH;
+ let rd = (word >> 7) & REGISTER_NAME_WIDTH;
+ let imm = word >> 20;
+
+ let sentinel = (word >> 12) & 0b111;
+ if sentinel != 0 {
+ panic!("Illegal instruction: {:x}", word);
+ }
+
+ jalr(state, rd as usize, rs1 as usize, imm);
+ return;
+ }
+ 0b0110111 => {
+ let imm = word >> 12;
+ let rd = ((word >> 7) & 0b11111) as usize;
+ lui(state, rd, imm);
+ }
+ 0b0010111 => {
+ let imm = word >> 12;
+ let rd = (word >> 7) & REGISTER_NAME_WIDTH;
+ auipc(state, rd as usize, imm);
+ }
+ 0b1110011 => {
+ let imm = word >> 20;
+ let rd = ((word >> 7) & REGISTER_NAME_WIDTH) as usize;
+ let rs1 = ((word >> 15) & REGISTER_NAME_WIDTH) as usize;
+ let f3 = (word >> 12) & 0b111;
+
+ match f3 {
+ 0 => {
+ match imm {
+ 0 => trap_to_kernel(state), // ecall
+ 258 => return_to_userspace(state), // sret
+ 261 => {
+ println!("wfi");
+ }
+ _ => panic!("Illegal instruction: {:x}", word),
+ }
+ } // ecall
+ 1 => csrrw(state, rd, rs1, imm as usize),
+ 2 => csrrs(state, rd, rs1, imm as usize),
+ 3 => csrrc(state, rd, rs1, imm as usize),
+ 5 => csrrwi(state, rd, rs1 as u32, imm as usize),
+ 6 => csrrsi(state, rd, rs1 as u32, imm as usize),
+ 7 => csrrci(state, rd, rs1 as u32, imm as usize),
+
+ _ => panic!("Illegal instruction: {:x}", word),
+ }
+ } // ecall (ebreak not implemented)
+ _ => panic!("Illegal instruction or todo: {:032b}", word),
+ }
+
+ state.pc += 4;
+}
+
+fn trap_to_kernel(state: &mut CpuState) {
+ state.csr[0x341 /* mepc */] = state.pc;
+ state.pc = state.csr[0x305 /* mtvec */];
+}
+
+fn return_to_userspace(state: &mut CpuState) {
+ state.pc = state.csr[0x341 /* mepc */] + 4;
+}
diff --git a/website-emu/src/mem.rs b/website-emu/src/mem.rs
new file mode 100644
index 0000000..c5f1b5c
--- /dev/null
+++ b/website-emu/src/mem.rs
@@ -0,0 +1,105 @@
+use std::ops::Range;
+
+pub trait MemoryDevice {
+ fn read(&self, address: u32) -> Result<u8, ()>;
+ fn write(&mut self, address: u32, value: u8) -> Result<(), ()>;
+}
+
+pub struct CpuMemory {
+ pub devices: Vec<(Range<u32>, Box<dyn MemoryDevice>)>,
+}
+
+impl CpuMemory {
+ fn get_device(&self, address: u32) -> Option<&Box<dyn MemoryDevice>> {
+ self.devices
+ .iter()
+ .find(|(range, _)| range.contains(&address))
+ .map(|(_, device)| device)
+ }
+ fn get_device_mut(&mut self, address: u32) -> Option<&mut Box<dyn MemoryDevice>> {
+ self.devices
+ .iter_mut()
+ .find(|(range, _)| range.contains(&address))
+ .map(|(_, device)| device)
+ }
+
+ pub fn register_device(&mut self, range: Range<u32>, device: Box<dyn MemoryDevice>) {
+ self.devices.push((range, device));
+ }
+
+ pub fn read(&self, address: u32) -> Result<u8, ()> {
+ if let Some(device) = self.get_device(address) {
+ device.read(address)
+ } else {
+ Err(())
+ }
+ }
+
+ pub fn write(&mut self, address: u32, value: u8) -> Result<(), ()> {
+ if let Some(device) = self.get_device_mut(address) {
+ device.write(address, value)
+ } else {
+ Err(())
+ }
+ }
+
+ pub fn read_halfword(&self, address: u32) -> Result<u16, ()> {
+ let low = self.read(address)?;
+ let high = self.read(address + 1)?;
+ Ok((high as u16) << 8 | low as u16)
+ }
+
+ pub fn write_halfword(&mut self, address: u32, value: u16) -> Result<(), ()> {
+ self.write(address + 1, (value >> 8) as u8)?;
+ self.write(address, (value & 0xFF) as u8)?;
+ Ok(())
+ }
+
+ pub fn read_word(&self, address: u32) -> Result<u32, ()> {
+ let low = self.read_halfword(address)?;
+ let high = self.read_halfword(address + 2)?;
+ Ok((high as u32) << 16 | low as u32)
+ }
+
+ pub fn write_word(&mut self, address: u32, value: u32) -> Result<(), ()> {
+ self.write_halfword(address + 2, (value >> 16) as u16)?;
+ self.write_halfword(address, (value & 0xFFFF) as u16)
+ }
+
+ pub fn read_instruction(&self, address: u32) -> Result<u32, ()> {
+ if let Some(device) = self.get_device(address) {
+ Ok(u32::from_le_bytes([
+ device.read(address)?,
+ device.read(address + 1)?,
+ device.read(address + 2)?,
+ device.read(address + 3)?,
+ ]))
+ } else {
+ Err(())
+ }
+ }
+}
+
+pub struct LogicalMemory {
+ pub base: u32,
+ pub size: u32,
+ pub dram: Box<[u8]>,
+}
+
+impl MemoryDevice for LogicalMemory {
+ fn read(&self, address: u32) -> Result<u8, ()> {
+ if address - self.base > self.size {
+ Err(())
+ } else {
+ Ok(self.dram[(address - self.base) as usize])
+ }
+ }
+ fn write(&mut self, address: u32, value: u8) -> Result<(), ()> {
+ if address - self.base > self.size {
+ Err(())
+ } else {
+ self.dram[(address - self.base) as usize] = value;
+ Ok(())
+ }
+ }
+}
diff --git a/website-emu/src/state.rs b/website-emu/src/state.rs
new file mode 100644
index 0000000..f26167f
--- /dev/null
+++ b/website-emu/src/state.rs
@@ -0,0 +1,19 @@
+use crate::mem::CpuMemory;
+
+pub struct CpuState {
+ pub memory: CpuMemory,
+ pub registers: [u32; 32],
+ pub csr: [u32; 4096],
+ pub pc: u32,
+}
+
+impl CpuState {
+ pub fn new(memory: CpuMemory) -> CpuState {
+ CpuState {
+ memory,
+ registers: [0u32; 32],
+ csr: [0u32; 4096],
+ pc: 0
+ }
+ }
+}