arnaucube
arnaucube/rv32-rsPublic Self-hosted repository
impl store op'ns, SB,SH,SW
arnaucube git@arnaucube.com
Tue, 08 Sep 2026 22:04:55 +0200
commit

e7dd3ba1d668839d763f5b6eac6f66b8d6f68ac3

parent

a6cd70428792691e6c2c04c68db4c7aa10fe5412

1 files changed, 94 insertions(+), 2 deletions(-)

jump to
M src/lib.rs → src/lib.rs

@@ -59,11 +59,19 @@ // 31 20 19 15 14 12 11 7 6 0

// ┌────────────────────────┬────────────┬────────┬────────┬─────────┐ // │ imm[11:0] │ rs1 │ funct3 │ rd │ opcode │ // └────────────────────────┴────────────┴────────┴────────┴─────────┘ + // S-type: + // 31 25 24 20 19 15 14 12 11 7 6 0 + // ┌───────────┬────────────┬────────────┬────────┬───────────┬─────────┐ + // │ imm[11:5] │ rs2 │ rs1 │ funct3 │ imm[4:0] │ opcode │ + // └───────────┴────────────┴────────────┴────────┴───────────┴─────────┘ + // + // Note: we keep `imm` as the I-type imm, and in the S-type we compute + // the S-type respective imm from 25-31 and 7-11 positions let imm = sign_extend(instruction >> 20, 12); let a = self.regs[rs1]; let b = self.regs[rs2]; - let result: Option<u32>; + let mut result: Option<u32> = None; // 0x7f=0111_1111, match the lowest 7 bits to read the opcode match instruction & 0x7f {

@@ -90,6 +98,23 @@ } else {

value }); } + // S-type op'ns: + 0x23 => { + let size = match funct3 { + // SB: store the low byte of rs2 at rs1 + immediate + 0 => 1, + // SH: store the low halfword of rs2 at rs1 + immediate + 1 => 2, + // SW: store the full 32-bit value of rs2 at rs1 + immediate + 2 => 4, + _ => return Err(anyhow!("illegal instruction: {}", instruction)), + }; + // for the S-type imm, get it from 25-31 and 7-11 positions + let s_type_imm = ((instruction >> 25) << 5) | ((instruction >> 7) & 31); + let offset = sign_extend(s_type_imm, 12); + let range = self.range(a.wrapping_add(offset), size)?; + self.mem[range].copy_from_slice(&b.to_le_bytes()[..size]); + } // R-type (register-register) integer operations 0x33 => {

@@ -207,6 +232,21 @@ let imm = (imm as u32) & 0xfff;

(imm << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | OPCODE_LOAD } + fn encode_s(name: &str, rs1: u32, rs2: u32, imm: i32) -> u32 { + let (opcode, funct3) = match name { + "sb" => (0b0100011, 0b000), + "sh" => (0b0100011, 0b001), + "sw" => (0b0100011, 0b010), + _ => panic!("unknown store instruction: {name}"), + }; + + let imm = imm as u32; + + let imm_11_5 = (imm >> 5) & 0x7f; + let imm_4_0 = imm & 0x1f; + + (imm_11_5 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (imm_4_0 << 7) | opcode + } #[test] fn test_ops_r_type() {

@@ -303,7 +343,7 @@ }

#[test] fn test_ops_load() { - let test_vectors: [(&str, u32, i32, u32); 8] = [ + let test_vectors: Vec<(&str, u32, i32, u32)> = vec![ ("lb", 0x0000_0080, 0, 0xffff_ff80), ("lbu", 0x0000_0080, 0, 0x0000_0080), ("lb", 0x0000_00ff, 0, 0xffff_ffff),

@@ -336,6 +376,58 @@ address={address}, \

expected={expected:#010x}, got={:#010x}", cpu.regs[rd as usize], + ); + } + } + #[test] + fn test_ops_store() { + let test_vectors: Vec<(&str, u32, i32)> = vec![ + ("sb", 0x0000_0080, 0), + ("sb", 0x0000_00ff, 0), + ("sb", 0x1234_5678, 0), + ("sh", 0x0000_8000, 0), + ("sh", 0x0000_ffff, 0), + ("sw", 0x1234_5678, 0), + ]; + + for (name, value, address) in test_vectors { + let rs1 = 1; + let rs2 = 2; + + let instruction = encode_s(name, rs1, rs2, address); + + let mut memory = instruction.to_le_bytes().to_vec(); + let data_address = memory.len() as u32; + + // add space for the value being stored + memory.extend_from_slice(&[0u8; 4]); + + let mut cpu = RV32::new(memory); + + cpu.regs[rs1 as usize] = data_address; + cpu.regs[rs2 as usize] = value; + + cpu.step().unwrap(); + + let stored = u32::from_le_bytes( + cpu.mem[data_address as usize..data_address as usize + 4] + .try_into() + .unwrap(), + ); + + let expected = match name { + "sb" => value & 0xff, + "sh" => value & 0xffff, + "sw" => value, + _ => unreachable!(), + }; + + assert_eq!( + stored, expected, + "{name} failed: value={value:#010x}, + address={address}, \ + expected={expected:#010x}, + got={stored:#010x}", ); } }