arnaucube
arnaucube/rv32-rsPublic Self-hosted repository
add lui, auipc, jal, jalr opcodes
arnaucube git@arnaucube.com
Sat, 12 Sep 2026 08:51:09 +0200
commit

bc50a5c2e8fc47d03ceb4478a991a85c89d64d26

parent

85c7e19cb631106cb9511fb21aa32322c8e63437

2 files changed, 169 insertions(+), 2 deletions(-)

jump to
M README.md → README.md

@@ -1,6 +1,6 @@

# rv32-rs -RISC-V (RV32I variant) implementation by hand to get familiar with Risc-V's internals. Do not use, for learning purposes only. +RISC-V (RV32I variant, with some of the M extension) implementation by hand to get familiar with Risc-V's internals. Do not use, for learning purposes only. Follows the specification from: - https://docs.riscv.org/reference/isa/v20260120/unpriv/rv32.html
M src/lib.rs → src/lib.rs

@@ -113,6 +113,7 @@ } else {

value }); } + // I-type arithmetic & logical & shifts op'ns: 0x13 => { result = Some(match funct3 {

@@ -151,6 +152,7 @@ ))

} }); } + // S-type op'ns: 0x23 => { let size = match funct3 {

@@ -223,10 +225,11 @@

_ => return Err(anyhow!("illegal instruction in R-type: {}", instruction)), }); } + // B-type operations 0x63 => { let taken = match funct3 { - // BEQ: branch by the signed PC-relative offset if the + // BEQ: branch by the signed pc-relative offset if the // registers are equal 0 => a == b, // BNE: branch if the registers are unequal

@@ -252,6 +255,28 @@ next_pc = self.pc.wrapping_add(sign_extend(b_type_imm, 13));

} } + // U-type operations + // LUI: load the upper 20 immediate bits into rd, clearing the lower + // 12 bits + 0x37 => result = Some(instruction & 0xfffff000), + // AUIPC: add the upper immediate to the current pc and write rd + 0x17 => result = Some(self.pc.wrapping_add(instruction & 0xfffff000)), + + // J-type operations + // JAL: jump by a signed pc-relative offset and save pc + 4 in rd + 0x6f => { + let offset = ((instruction >> 31) << 20) + | (((instruction >> 12) & 0xff) << 12) + | (((instruction >> 20) & 1) << 11) + | (((instruction >> 21) & 0x3ff) << 1); + next_pc = self.pc.wrapping_add(sign_extend(offset, 21)); + result = Some(self.pc.wrapping_add(4)); + } + // JALR (I-type): jump to rs1 + immediate with bit zero cleared, saving pc + 4 in rd + 0x67 if funct3 == 0 => { + next_pc = a.wrapping_add(imm) & !1; + result = Some(self.pc.wrapping_add(4)); + } _ => return Err(anyhow!("illegal instruction: {}", instruction)), }

@@ -318,6 +343,7 @@ "andi" => (0b111, 0x13),

"slli" => (0b001, 0x13), "srli" => (0b101, 0x13), "srai" => (0b101, 0x13), + "jalr" => (0b000, 0x67), _ => panic!("unknown I-type instruction: {name}"), };

@@ -383,6 +409,39 @@ | (imm4_1 << 8)

| (imm11 << 7) | 0b1100011 } + fn encode_j(name: &str, rd: u32, offset: i32) -> u32 { + assert!(matches!(name, "jal")); + assert!(offset % 2 == 0); + assert!((-1 << 20..=1 << 20).contains(&offset)); + + let imm = offset as u32; + + let imm20 = (imm >> 20) & 0x1; + let imm10_1 = (imm >> 1) & 0x3ff; + let imm11 = (imm >> 11) & 0x1; + let imm19_12 = (imm >> 12) & 0xff; + + (imm20 << 31) + | (imm19_12 << 12) + | (imm11 << 20) + | (imm10_1 << 21) + | ((rd & 0x1f) << 7) + | 0x6f + } + fn encode_u(name: &str, rd: u32, immediate: u32) -> u32 { + let opcode = match name { + "lui" => 0x37, + "auipc" => 0x17, + _ => panic!("unsupported U-type instruction: {name}"), + }; + + assert!( + immediate <= 0xfffff, + "U-type immediate must fit in 20 bits: {immediate:#x}" + ); + + ((immediate & 0xfffff) << 12) | ((rd & 0x1f) << 7) | opcode + } // R-type ops test #[test]

@@ -671,6 +730,114 @@ rs2={rs2_value:#010x}, offset={offset}, \

expected_pc={expected_pc:#010x}, \ got={:#010x}", cpu.pc, + ); + } + } + #[test] + fn test_ops_upper_and_jump() { + let test_vectors: Vec<(&str, u32, u32, u32, u32)> = vec![ + // name, rd, immediate, pc, expected + ("lui", 1, 0x12345, 0, 0x1234_5000), + ("lui", 1, 0xfffff, 0, 0xffff_f000), + ("auipc", 1, 0x12345, 0x1000, 0x1234_6000), + ("auipc", 1, 0xfffff, 0x1000, 0x0000_0000), + ]; + + for (name, rd, immediate, pc, expected) in test_vectors { + let instruction = encode_u(name, rd, immediate); + let mut memory = vec![0; pc as usize + 4]; + memory[pc as usize..pc as usize + 4].copy_from_slice(&instruction.to_le_bytes()); + let mut cpu = RV32::new(memory); + + cpu.pc = pc; + cpu.step().unwrap(); + + assert_eq!( + cpu.regs[rd as usize], expected, + "{name} failed: immediate={immediate:#010x}, \ + pc={pc:#010x}, expected={expected:#010x}, got={:#010x}", + cpu.regs[rd as usize], + ); + } + } + + #[test] + fn test_ops_jumps() { + // JAL + let test_vectors: Vec<(&str, i32, u32)> = vec![ + ("jal", 8, 0x0000_0008), + ("jal", -8, 0xffff_fff8), + ("jal", 2048, 0x0000_0800), + ("jal", -2048, 0xffff_f800), + ]; + + for (name, offset, expected_pc) in test_vectors { + let rd = 1; + let pc = 0x1000; + + let instruction = encode_j(name, rd, offset); + let mut memory = vec![0; pc as usize + 4]; + memory[pc as usize..pc as usize + 4].copy_from_slice(&instruction.to_le_bytes()); + let mut cpu = RV32::new(memory); + + cpu.pc = pc; + cpu.step().unwrap(); + + assert_eq!( + cpu.pc, + pc.wrapping_add(expected_pc), + "{name} failed: offset={offset}, \ + expected_pc={:#010x}, got={:#010x}", + pc.wrapping_add(expected_pc), + cpu.pc, + ); + + assert_eq!( + cpu.regs[rd as usize], + pc.wrapping_add(4), + "{name} failed to save pc + 4: expected={:#010x}, got={:#010x}", + pc.wrapping_add(4), + cpu.regs[rd as usize], + ); + } + + // JALR + let test_vectors: Vec<(&str, u32, i32, u32)> = vec![ + ("jalr", 0x2000, 4, 0x2004), + ("jalr", 0x2000, -4, 0x1ffc), + ("jalr", 0x2001, 0, 0x2000), // target bit 0 must be cleared. + ("jalr", 0x2000, 5, 0x2004), + ]; + + for (name, rs1_value, immediate, expected_pc) in test_vectors { + let rd = 1; + let rs1 = 2; + let pc = 0x1000; + + let instruction = encode_i(name, rd, rs1, immediate); + + let mut memory = vec![0; pc as usize + 4]; + memory[pc as usize..pc as usize + 4].copy_from_slice(&instruction.to_le_bytes()); + let mut cpu = RV32::new(memory); + cpu.pc = pc; + cpu.regs[rs1 as usize] = rs1_value; + + cpu.step().unwrap(); + + assert_eq!( + cpu.pc, expected_pc, + "{name} failed: rs1={rs1_value:#010x}, \ + immediate={immediate}, expected_pc={expected_pc:#010x}, \ + got={:#010x}", + cpu.pc, + ); + + assert_eq!( + cpu.regs[rd as usize], + pc.wrapping_add(4), + "{name} failed to save pc + 4: expected={:#010x}, got={:#010x}", + pc.wrapping_add(4), + cpu.regs[rd as usize], ); } }