arnaucube
arnaucube/rv32-rsPublic Self-hosted repository
add I-type arith&logic&shift ops
arnaucube git@arnaucube.com
Wed, 09 Sep 2026 21:28:13 +0200
commit

85c7e19cb631106cb9511fb21aa32322c8e63437

parent

d0bfde762de1d5edfe6edfce7bc1ecf555d56d39

1 files changed, 135 insertions(+), 24 deletions(-)

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

@@ -55,17 +55,17 @@ let rs1 = ((instruction >> 15) & 31) as usize;

let rs2 = ((instruction >> 20) & 31) as usize; let funct7 = instruction >> 25; // I-type: - // 31 20 19 15 14 12 11 7 6 0 + // 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 │ - // └───────────┴────────────┴────────────┴────────┴───────────┴─────────┘ + // 31 25 24 20 19 15 14 12 11 7 6 0 + // ┌───────────┬────────────┬────────────┬────────┬────────┬─────────┐ + // │ imm[11:5] │ rs2 │ rs1 │ funct3 │imm[4:0]│ opcode │ + // └───────────┴────────────┴────────────┴────────┴────────┴─────────┘ // B-type: - // 31 30 25 24 20 19 15 14 12 11 8 7 6 0 + // 31 30 25 24 20 19 15 14 12 11 8 7 6 0 // ┌─────┬─────────┬────────┬────────────┬────────┬────────┬──┬──────┐ // │imm12│imm[10:5]│ rs2 │ rs1 │ funct3 │imm[4:1]│11│opcode│ // └─────┴─────────┴────────┴────────────┴────────┴────────┴──┴──────┘

@@ -82,21 +82,29 @@ let mut result: Option<u32> = None;

// 0x7f=0111_1111, match the lowest 7 bits to read the opcode match instruction & 0x7f { - // I-type op'ns: - // Loads + // I-type op'ns, loads: 0x03 => { let (size, signed) = match funct3 { - // LB: load a byte at rs1 + immediate and sign-extend it into rd + // LB: load a byte at rs1 + immediate and sign-extend it + // into rd 0 => (1, true), - // LH: load a halfword at rs1 + immediate and sign-extend it into rd + // LH: load a halfword at rs1 + immediate and sign-extend it + // into rd 1 => (2, true), // LW: load a 32-bit word at rs1 + immediate into rd 2 => (4, false), - // LBU: load a byte at rs1 + immediate and zero-extend it into rd + // LBU: load a byte at rs1 + immediate and zero-extend it + // into rd 4 => (1, false), - // LHU: load a halfword at rs1 + immediate and zero-extend it into rd + // LHU: load a halfword at rs1 + immediate and zero-extend + // it into rd 5 => (2, false), - _ => return Err(anyhow!("illegal instruction in I-type: {}", instruction)), + _ => { + return Err(anyhow!( + "illegal instruction in I-type (load): {}", + instruction + )) + } }; let value = self.read(a.wrapping_add(imm), size)?; result = Some(if signed {

@@ -105,6 +113,44 @@ } else {

value }); } + // I-type arithmetic & logical & shifts op'ns: + 0x13 => { + result = Some(match funct3 { + // ADDI: add the sign-extended immediate to rs1, wrapping to + // 32 bits + 0 => a.wrapping_add(imm), + // SLTI: signed comparison. Set rd to one if rs1 is less + // than the immediate as signed values + 2 => u32::from((a as i32) < (imm as i32)), + // SLTIU: unsigned comparison. Compare rs1 and the + // sign-extended immediate as unsigned values + 3 => u32::from(a < imm), + // XORI: write the bitwise XOR of rs1 and the sign-extended + // immediate + 4 => a ^ imm, + // ORI: write the bitwise OR of rs1 and the sign-extended + // immediate + 6 => a | imm, + // ANDI: write the bitwise AND of rs1 and the sign-extended + // immediate + 7 => a & imm, + // SLLI: shift rs1 left by the five-bit immediate shift + // amount + 1 if funct7 == 0 => a << rs2, + // SRLI: logical right shift. Shift rs1 right by the + // immediate amount, filling with zeros + 5 if funct7 == 0 => a >> rs2, + // SRAI: arithmetic right shift. Shift rs1 right by the + // immediate amount, preserving its sign + 5 if funct7 == 0x20 => ((a as i32) >> rs2) as u32, + _ => { + return Err(anyhow!( + "illegal instruction in I-type (arithmetic/logical): {}", + instruction + )) + } + }); + } // S-type op'ns: 0x23 => { let size = match funct3 {

@@ -180,7 +226,8 @@ }

// B-type operations 0x63 => { let taken = match funct3 { - // BEQ: branch by the signed PC-relative offset if the registers are equal + // BEQ: branch by the signed PC-relative offset if the + // registers are equal 0 => a == b, // BNE: branch if the registers are unequal 1 => a != b,

@@ -195,8 +242,8 @@ 7 => a >= b,

_ => return Err(anyhow!("illegal instruction in B-type: {}", instruction)), }; if taken { - // offset: reconstruct imm for B-type from positions 31, 31-25, - // 11-8, 7, and imm[0]=0 + // offset: reconstruct imm for B-type from positions 31, + // 31-25, 11-8, 7, and imm[0]=0 let b_type_imm = ((instruction >> 31) << 12) | (((instruction >> 7) & 1) << 11) | (((instruction >> 25) & 0x3f) << 5)

@@ -254,18 +301,35 @@

(funct7 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | OPCODE_R } fn encode_i(name: &str, rd: u32, rs1: u32, imm: i32) -> u32 { - let funct3 = match name { - "lb" => 0b000, - "lh" => 0b001, - "lw" => 0b010, - "lbu" => 0b100, - "lhu" => 0b101, - _ => panic!("unknown load instruction: {name}"), + let (funct3, opcode) = match name { + // loads: opcode = 0x03 + "lb" => (0b000, OPCODE_LOAD), + "lh" => (0b001, OPCODE_LOAD), + "lw" => (0b010, OPCODE_LOAD), + "lbu" => (0b100, OPCODE_LOAD), + "lhu" => (0b101, OPCODE_LOAD), + // arithmetic & logic & shifts: opcode = 0x13 + "addi" => (0b000, 0x13), + "slti" => (0b010, 0x13), + "sltiu" => (0b011, 0x13), + "xori" => (0b100, 0x13), + "ori" => (0b110, 0x13), + "andi" => (0b111, 0x13), + "slli" => (0b001, 0x13), + "srli" => (0b101, 0x13), + "srai" => (0b101, 0x13), + + _ => panic!("unknown I-type instruction: {name}"), + }; + let funct7 = if name == "srai" { + 0b010_0000 + } else { + 0b000_0000 }; let imm = (imm as u32) & 0xfff; - (imm << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | OPCODE_LOAD + (imm << 20) | (funct7 << 25) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode } fn encode_s(name: &str, rs1: u32, rs2: u32, imm: i32) -> u32 { let (opcode, funct3) = match name {

@@ -449,6 +513,53 @@ "{name} failed: memory={memory_value:#010x},

address={address}, \ expected={expected:#010x}, got={:#010x}", + cpu.regs[rd as usize], + ); + } + } + #[test] + fn test_ops_i_type_arithmetic() { + // almost same test vectors as for `test_ops_r_type`, same names with + // `i` as suffix + let test_vectors: Vec<(&str, u32, i32, u32)> = vec![ + // (name, rs1, immediate, expected rd) + ("addi", 10, 20, 30), + ("addi", u32::MAX, 1, 0), // overflow + ("slli", 1, 4, 16), + ("slli", 1, 31, 0x8000_0000), // SLL maximum shift + ("slti", (-10i32) as u32, 20, 1), + ("sltiu", 10, 20, 1), + ("slti", 0xffff_ffff, 0, 1), // signed comparison: -1 < 0 + ("slti", 0xffff_ffff, 0, 1), // signed vs unsigned distinction + ("sltiu", 0xffff_ffff, 0, 0), + ("xori", 0b1010, 0b1100, 0b0110), + ("srli", 0x8000_0000, 4, 0x0800_0000), + ("srli", 0x8000_0000, 1, 0x4000_0000), // logical shift should zero-fill + ("srai", 0x8000_0000, 4, 0xf800_0000), + ("srai", 0x8000_0000, 1, 0xc000_0000), // arithmetic shift should sign-fill + ("srai", 0xffff_ffff, 31, 0xffff_ffff), // arithmetic shift of -1 stays -1 + ("ori", 0b1010, 0b1100, 0b1110), + ("andi", 0b1010, 0b1100, 0b1000), + ]; + + for (name, rs1_value, imm, expected) in test_vectors { + dbg!(name); + let rd = 1; + let rs1 = 2; + + let instruction = encode_i(name, rd, rs1, imm); + let mut cpu = RV32::new(instruction.to_le_bytes().to_vec()); + + cpu.regs[rs1 as usize] = rs1_value; + + cpu.step().unwrap(); + + assert_eq!( + cpu.regs[rd as usize], expected, + "{name} failed: rs1={rs1_value:#010x}, \ + imm={imm}, \ + expected={expected:#010x}, \ + got={:#010x}", cpu.regs[rd as usize], ); }