arnaucube
arnaucube/rv32-rsPublic Self-hosted repository
implement MUL,DIV,REM (from M extension), reorganize tests to reuse logic
arnaucube git@arnaucube.com
Mon, 07 Sep 2026 16:48:12 +0200
commit

40c9492d99e9b28af1dc5494b992764db91d4c5c

parent

143a69f721d80e2e98dfb004f2e1e77da7e79d77

1 files changed, 79 insertions(+), 8 deletions(-)

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

@@ -87,6 +87,30 @@ // OR: write the bitwise OR of rs1 and rs2 into rd

(0, 6) => a | b, // AND: write the bitwise AND of rs1 and rs2 into rd (0, 7) => a & b, + + // Note: MUL,DIV,REM are not part of RV32I, but they are in + // the M extension. + + // MUL: write the low 32 bits of the product of rs1 and rs2 + (1, 0) => a.wrapping_mul(b), + // DIV: signed quotient toward zero; zero divisors return + // all ones + (1, 4) => { + if b == 0 { + u32::MAX + } else { + (a as i32).wrapping_div(b as i32) as u32 + } + } + // REM: signed remainder; zero divisors return the dividend + (1, 6) => { + if b == 0 { + a + } else { + (a as i32).wrapping_rem(b as i32) as u32 + } + } + _ => return Err(anyhow!("illegal instruction (A): {}", instruction)), }); }

@@ -123,6 +147,10 @@ "srl" => (0b0000000, 0b101),

"sra" => (0b0100000, 0b101), "or" => (0b0000000, 0b110), "and" => (0b0000000, 0b111), + // M extension + "mul" => (0b0000001, 0b000), + "div" => (0b0000001, 0b100), + "rem" => (0b0000001, 0b110), _ => panic!("unknown R-type instruction: {name}"), };

@@ -130,8 +158,8 @@ (funct7 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | OPCODE_R

} #[test] - fn test_r_type_operations() { - let test_vectors = [ + fn test_ops_r_type() { + let test_vectors = vec![ // (op, rs1, rs2, rd) ("add", 10, 20, 30), ("add", u32::MAX, 1, 0), // overflow

@@ -142,19 +170,62 @@ ("sll", 1, 31, 0x8000_0000), // SLL maximum shift

("sll", 1, 32, 1), // SLL shift wrapping ("slt", (-10i32) as u32, 20, 1), ("sltu", 10, 20, 1), - ("slt", 0xffff_ffff, 0, 1), // Signed comparison: -1 < 0 - ("slt", 0xffff_ffff, 0, 1), // Signed vs unsigned distinction + ("slt", 0xffff_ffff, 0, 1), // signed comparison: -1 < 0 + ("slt", 0xffff_ffff, 0, 1), // signed vs unsigned distinction ("sltu", 0xffff_ffff, 0, 0), ("xor", 0b1010, 0b1100, 0b0110), ("srl", 0x8000_0000, 4, 0x0800_0000), - ("srl", 0x8000_0000, 1, 0x4000_0000), // Logical shift should zero-fill + ("srl", 0x8000_0000, 1, 0x4000_0000), // logical shift should zero-fill ("sra", 0x8000_0000, 4, 0xf800_0000), - ("sra", 0x8000_0000, 1, 0xc000_0000), // Arithmetic shift should sign-fill - ("sra", 0xffff_ffff, 31, 0xffff_ffff), // Arithmetic shift of -1 stays -1 + ("sra", 0x8000_0000, 1, 0xc000_0000), // arithmetic shift should sign-fill + ("sra", 0xffff_ffff, 31, 0xffff_ffff), // arithmetic shift of -1 stays -1 ("or", 0b1010, 0b1100, 0b1110), ("and", 0b1010, 0b1100, 0b1000), ]; - + test_ops_r_types_opt(test_vectors); + } + #[test] + fn test_ops_m_extension() { + let test_vectors = vec![ + ("mul", 10, 20, 200), + ("mul", 0, 123, 0), + ("mul", 1, u32::MAX, u32::MAX), + ("mul", u32::MAX, 2, u32::MAX - 1), // result wraps to lower 32 bits + ("mul", (-1i32) as u32, 10, (-10i32) as u32), // -1 * 10 = -10 + ("mul", (-1i32) as u32, (-1i32) as u32, 1), // -1 * -1 = 1 + ("mul", 0x8000_0000, 2, 0), // INT_MIN * 2 = 0 modulo 2^32 + ("mul", 0x8000_0000, 0xffff_ffff, 0x8000_0000), // INT_MIN * -1 also wraps to INT_MIN + ("div", 20, 5, 4), + ("div", 21, 5, 4), // truncates toward zero + ("div", 0, 10, 0), + ("div", 10, 1, 10), + ("div", 10, (-1i32) as u32, (-10i32) as u32), + ("div", (-20i32) as u32, 5, (-4i32) as u32), // negative dividend + ("div", 20, (-5i32) as u32, (-4i32) as u32), // negative divisor + ("div", (-20i32) as u32, (-5i32) as u32, 4), // negative / negative + ("div", (-21i32) as u32, 5, (-4i32) as u32), // truncation toward zero: -21 / 5 = -4 + // RISC-V special case: + ("div", 0x8000_0000, 0xffff_ffff, 0x8000_0000), // INT_MIN / -1 = INT_MIN (overflow) + ("div", 10, 0, u32::MAX), // division by zero returns -1 for signed DIV + ("div", 0x8000_0000, 0, u32::MAX), + ("rem", 20, 5, 0), + ("rem", 21, 5, 1), + ("rem", 0, 10, 0), + ("rem", 10, 1, 0), + ("rem", 23, 5, 3), // Positive remainder. + ("rem", (-23i32) as u32, 5, (-3i32) as u32), // Remainder follows the sign of the dividend. + ("rem", 23, (-5i32) as u32, 3), // Negative divisor does not change the sign of the remainder. + ("rem", (-23i32) as u32, (-5i32) as u32, (-3i32) as u32), // Negative / negative. + ("rem", 3, 5, 3), // Dividend smaller than divisor. + // RISC-V special case: + ("rem", 0x8000_0000, 0xffff_ffff, 0), // INT_MIN % -1 = 0. + ("rem", 123, 0, 123), // Remainder by zero returns the dividend. + ("rem", u32::MAX, 0, u32::MAX), + ("rem", 0x8000_0000, 0, 0x8000_0000), + ]; + test_ops_r_types_opt(test_vectors); + } + fn test_ops_r_types_opt(test_vectors: Vec<(&str, u32, u32, u32)>) { for (name, rs1_value, rs2_value, expected) in test_vectors { let rd = 3; let rs1 = 1;