use crate::exec::compute::alu;
use crate::isa::op::AluOp;
const ZERO: u64 = 0;
const ONE: u64 = 1;
const NEG1: u64 = -1i64 as u64;
const I64_MAX: u64 = i64::MAX as u64;
const I64_MIN: u64 = i64::MIN as u64;
const U64_MAX: u64 = u64::MAX;
const I32_MAX: u64 = i32::MAX as u64;
const I32_MIN: u64 = i32::MIN as i64 as u64;
const U32_MAX: u64 = u32::MAX as u64;
const ALTERNATING_A: u64 = 0xAAAA_AAAA_AAAA_AAAA;
const ALTERNATING_5: u64 = 0x5555_5555_5555_5555;
const HIGH_BIT_32: u64 = 0x8000_0000;
fn alu(op: AluOp, a: u64, b: u64, is32: bool) -> u64 {
alu::execute(op, a, b, 0, is32)
}
fn sext32(val: u32) -> u64 {
val as i32 as i64 as u64
}
#[test]
fn add_rv64_zero_plus_zero() {
assert_eq!(alu(AluOp::Add, ZERO, ZERO, false), 0);
}
#[test]
fn add_rv64_identity() {
assert_eq!(alu(AluOp::Add, 42, ZERO, false), 42);
assert_eq!(alu(AluOp::Add, ZERO, 42, false), 42);
}
#[test]
fn add_rv64_positive_plus_positive() {
assert_eq!(alu(AluOp::Add, 100, 200, false), 300);
}
#[test]
fn add_rv64_negative_plus_negative() {
let neg5 = -5i64 as u64;
let neg3 = -3i64 as u64;
let neg8 = -8i64 as u64;
assert_eq!(alu(AluOp::Add, neg5, neg3, false), neg8);
}
#[test]
fn add_rv64_positive_plus_negative() {
assert_eq!(alu(AluOp::Add, 10, -3i64 as u64, false), 7);
}
#[test]
fn add_rv64_neg1_plus_1() {
assert_eq!(alu(AluOp::Add, NEG1, ONE, false), 0);
}
#[test]
fn add_rv64_max_plus_1_wraps() {
assert_eq!(alu(AluOp::Add, I64_MAX, ONE, false), I64_MIN);
}
#[test]
fn add_rv64_unsigned_max_plus_1_wraps() {
assert_eq!(alu(AluOp::Add, U64_MAX, ONE, false), 0);
}
#[test]
fn add_rv64_min_plus_min() {
assert_eq!(alu(AluOp::Add, I64_MIN, I64_MIN, false), 0);
}
#[test]
fn add_rv64_large_values() {
assert_eq!(
alu(AluOp::Add, 0xDEAD_BEEF_CAFE_BABE, 0x1111_1111_1111_1111, false),
0xDEAD_BEEF_CAFE_BABE_u64.wrapping_add(0x1111_1111_1111_1111)
);
}
#[test]
fn addw_zero_plus_zero() {
assert_eq!(alu(AluOp::Add, ZERO, ZERO, true), 0);
}
#[test]
fn addw_positive_plus_positive() {
assert_eq!(alu(AluOp::Add, 100, 200, true), 300);
}
#[test]
fn addw_overflow_wraps_and_sign_extends() {
assert_eq!(alu(AluOp::Add, I32_MAX, ONE, true), I32_MIN);
}
#[test]
fn addw_negative_result_sign_extends() {
assert_eq!(alu(AluOp::Add, NEG1, ZERO, true), NEG1);
}
#[test]
fn addw_ignores_upper_32_bits_of_inputs() {
assert_eq!(alu(AluOp::Add, 0xDEAD_0000_0000_0001, 0xBEEF_0000_0000_0002, true), 3);
}
#[test]
fn addw_u32_max_plus_1() {
assert_eq!(alu(AluOp::Add, U32_MAX, ONE, true), 0);
}
#[test]
fn sub_rv64_zero_minus_zero() {
assert_eq!(alu(AluOp::Sub, ZERO, ZERO, false), 0);
}
#[test]
fn sub_rv64_positive_minus_positive() {
assert_eq!(alu(AluOp::Sub, 200, 100, false), 100);
}
#[test]
fn sub_rv64_zero_minus_one() {
assert_eq!(alu(AluOp::Sub, ZERO, ONE, false), NEG1);
}
#[test]
fn sub_rv64_min_minus_one_wraps() {
assert_eq!(alu(AluOp::Sub, I64_MIN, ONE, false), I64_MAX);
}
#[test]
fn sub_rv64_zero_minus_min() {
assert_eq!(alu(AluOp::Sub, ZERO, I64_MIN, false), I64_MIN);
}
#[test]
fn sub_rv64_self_minus_self() {
assert_eq!(alu(AluOp::Sub, 0xDEAD_BEEF, 0xDEAD_BEEF, false), 0);
}
#[test]
fn sub_rv64_negative_minus_negative() {
assert_eq!(alu(AluOp::Sub, -5i64 as u64, -3i64 as u64, false), -2i64 as u64);
}
#[test]
fn subw_positive_result() {
assert_eq!(alu(AluOp::Sub, 10, 3, true), 7);
}
#[test]
fn subw_negative_result_sign_extends() {
assert_eq!(alu(AluOp::Sub, 3, 10, true), -7i64 as u64);
}
#[test]
fn subw_overflow_wraps_and_sign_extends() {
assert_eq!(alu(AluOp::Sub, I32_MIN, ONE, true), sext32(i32::MAX as u32));
}
#[test]
fn subw_ignores_upper_bits() {
assert_eq!(alu(AluOp::Sub, 0xFF00_0000_0000_000A, 0xAB00_0000_0000_0003, true), 7);
}
#[test]
fn mul_rv64_zero_times_anything() {
assert_eq!(alu(AluOp::Mul, ZERO, 12345, false), 0);
assert_eq!(alu(AluOp::Mul, 12345, ZERO, false), 0);
}
#[test]
fn mul_rv64_identity() {
assert_eq!(alu(AluOp::Mul, 42, ONE, false), 42);
assert_eq!(alu(AluOp::Mul, ONE, 42, false), 42);
}
#[test]
fn mul_rv64_neg1_is_negate() {
assert_eq!(alu(AluOp::Mul, 42, NEG1, false), (-42i64) as u64);
}
#[test]
fn mul_rv64_neg1_times_neg1() {
assert_eq!(alu(AluOp::Mul, NEG1, NEG1, false), 1);
}
#[test]
fn mul_rv64_positive_times_positive() {
assert_eq!(alu(AluOp::Mul, 100, 200, false), 20_000);
}
#[test]
fn mul_rv64_overflow_wraps() {
let a = 0x1_0000_0000_u64; let b = 0x1_0000_0000_u64;
assert_eq!(alu(AluOp::Mul, a, b, false), 0); }
#[test]
fn mul_rv64_max_times_2() {
assert_eq!(alu(AluOp::Mul, I64_MAX, 2, false), I64_MAX.wrapping_mul(2));
}
#[test]
fn mulw_basic() {
assert_eq!(alu(AluOp::Mul, 7, 6, true), 42);
}
#[test]
fn mulw_overflow_wraps_and_sign_extends() {
assert_eq!(alu(AluOp::Mul, I32_MAX, 2, true), sext32(0xFFFF_FFFE));
}
#[test]
fn mulw_neg1_times_neg1() {
assert_eq!(alu(AluOp::Mul, NEG1, NEG1, true), 1);
}
#[test]
fn mulw_ignores_upper_bits() {
assert_eq!(alu(AluOp::Mul, 0xFFFF_FFFF_0000_0003, 0xFFFF_FFFF_0000_0004, true), 12);
}
#[test]
fn mulh_zero() {
assert_eq!(alu(AluOp::Mulh, ZERO, 42, false), 0);
}
#[test]
fn mulh_small_values_no_high_bits() {
assert_eq!(alu(AluOp::Mulh, 100, 200, false), 0);
}
#[test]
fn mulh_max_times_max() {
let expected = ((i64::MAX as i128 * i64::MAX as i128) >> 64) as u64;
assert_eq!(alu(AluOp::Mulh, I64_MAX, I64_MAX, false), expected);
}
#[test]
fn mulh_neg1_times_neg1() {
assert_eq!(alu(AluOp::Mulh, NEG1, NEG1, false), 0);
}
#[test]
fn mulh_neg1_times_positive() {
assert_eq!(alu(AluOp::Mulh, NEG1, ONE, false), NEG1);
}
#[test]
fn mulh_min_times_min() {
let expected = ((i64::MIN as i128 * i64::MIN as i128) >> 64) as u64;
assert_eq!(alu(AluOp::Mulh, I64_MIN, I64_MIN, false), expected);
}
#[test]
fn mulhsu_positive_times_positive() {
assert_eq!(alu(AluOp::Mulhsu, 100, 200, false), 0);
}
#[test]
fn mulhsu_negative_times_unsigned() {
let a_s = -1i128;
let b_u = u64::MAX as u128 as i128;
let expected = ((a_s * b_u) >> 64) as u64;
assert_eq!(alu(AluOp::Mulhsu, NEG1, U64_MAX, false), expected);
}
#[test]
fn mulhsu_min_times_max() {
let a_s = i64::MIN as i128;
let b_u = u64::MAX as u128 as i128;
let expected = ((a_s * b_u) >> 64) as u64;
assert_eq!(alu(AluOp::Mulhsu, I64_MIN, U64_MAX, false), expected);
}
#[test]
fn mulhu_zero() {
assert_eq!(alu(AluOp::Mulhu, ZERO, 42, false), 0);
}
#[test]
fn mulhu_small_values() {
assert_eq!(alu(AluOp::Mulhu, 100, 200, false), 0);
}
#[test]
fn mulhu_max_times_max() {
let expected = ((u64::MAX as u128 * u64::MAX as u128) >> 64) as u64;
assert_eq!(alu(AluOp::Mulhu, U64_MAX, U64_MAX, false), expected);
}
#[test]
fn mulhu_max_times_2() {
assert_eq!(alu(AluOp::Mulhu, U64_MAX, 2, false), 1);
}
#[test]
fn mul_mulh_overflow_detection_no_overflow() {
let a: u64 = 100;
let b: u64 = 200;
let lo = alu(AluOp::Mul, a, b, false);
let hi = alu(AluOp::Mulh, a, b, false);
let expected_hi = if (lo as i64) < 0 { NEG1 } else { 0 };
assert_eq!(hi, expected_hi);
}
#[test]
fn mul_mulh_overflow_detection_with_overflow() {
let a = I64_MAX;
let b: u64 = 2;
let lo = alu(AluOp::Mul, a, b, false);
let hi = alu(AluOp::Mulh, a, b, false);
let sign_ext = if (lo as i64) < 0 { NEG1 } else { 0 };
assert_ne!(hi, sign_ext, "MULH should indicate overflow occurred");
}
#[test]
fn div_rv64_divide_by_zero() {
assert_eq!(alu(AluOp::Div, 42, ZERO, false), NEG1);
}
#[test]
fn div_rv64_zero_divide_by_zero() {
assert_eq!(alu(AluOp::Div, ZERO, ZERO, false), NEG1);
}
#[test]
fn div_rv64_min_divide_by_zero() {
assert_eq!(alu(AluOp::Div, I64_MIN, ZERO, false), NEG1);
}
#[test]
fn div_rv64_signed_overflow() {
assert_eq!(alu(AluOp::Div, I64_MIN, NEG1, false), I64_MIN);
}
#[test]
fn div_rv64_identity() {
assert_eq!(alu(AluOp::Div, 42, ONE, false), 42);
}
#[test]
fn div_rv64_self_divide() {
assert_eq!(alu(AluOp::Div, 42, 42, false), 1);
assert_eq!(alu(AluOp::Div, NEG1, NEG1, false), 1);
}
#[test]
fn div_rv64_positive_by_positive() {
assert_eq!(alu(AluOp::Div, 100, 7, false), 14); }
#[test]
fn div_rv64_negative_by_positive() {
assert_eq!(alu(AluOp::Div, -100i64 as u64, 7, false), -14i64 as u64);
}
#[test]
fn div_rv64_positive_by_negative() {
assert_eq!(alu(AluOp::Div, 100, -7i64 as u64, false), -14i64 as u64);
}
#[test]
fn div_rv64_negative_by_negative() {
assert_eq!(alu(AluOp::Div, -100i64 as u64, -7i64 as u64, false), 14);
}
#[test]
fn div_rv64_neg1_by_1() {
assert_eq!(alu(AluOp::Div, NEG1, ONE, false), NEG1);
}
#[test]
fn divw_divide_by_zero() {
assert_eq!(alu(AluOp::Div, 42, ZERO, true), NEG1);
}
#[test]
fn divw_signed_overflow() {
assert_eq!(alu(AluOp::Div, I32_MIN, NEG1, true), I32_MIN);
}
#[test]
fn divw_basic() {
assert_eq!(alu(AluOp::Div, 100, 7, true), 14);
}
#[test]
fn divw_negative_result_sign_extends() {
assert_eq!(alu(AluOp::Div, -100i64 as u64, 7, true), sext32(-14i32 as u32));
}
#[test]
fn divw_ignores_upper_bits() {
assert_eq!(
alu(AluOp::Div, 0xDEAD_0000_0000_0064, 0xBEEF_0000_0000_0007, true),
14 );
}
#[test]
fn divu_rv64_divide_by_zero() {
assert_eq!(alu(AluOp::Divu, 42, ZERO, false), U64_MAX);
}
#[test]
fn divu_rv64_zero_divide_by_zero() {
assert_eq!(alu(AluOp::Divu, ZERO, ZERO, false), U64_MAX);
}
#[test]
fn divu_rv64_identity() {
assert_eq!(alu(AluOp::Divu, 42, ONE, false), 42);
}
#[test]
fn divu_rv64_self_divide() {
assert_eq!(alu(AluOp::Divu, 42, 42, false), 1);
}
#[test]
fn divu_rv64_large_unsigned() {
assert_eq!(alu(AluOp::Divu, I64_MIN, 2, false), 0x4000_0000_0000_0000);
}
#[test]
fn divu_rv64_max_by_1() {
assert_eq!(alu(AluOp::Divu, U64_MAX, ONE, false), U64_MAX);
}
#[test]
fn divu_rv64_max_by_max() {
assert_eq!(alu(AluOp::Divu, U64_MAX, U64_MAX, false), 1);
}
#[test]
fn divu_rv64_basic() {
assert_eq!(alu(AluOp::Divu, 100, 7, false), 14);
}
#[test]
fn divuw_divide_by_zero() {
assert_eq!(alu(AluOp::Divu, 42, ZERO, true), NEG1);
}
#[test]
fn divuw_divide_by_zero_upper_bits_set() {
let b_with_upper_bits = 0x0000_0001_0000_0000_u64;
assert_eq!(alu(AluOp::Divu, 42, b_with_upper_bits, true), NEG1);
}
#[test]
fn divuw_basic() {
assert_eq!(alu(AluOp::Divu, 100, 7, true), 14);
}
#[test]
fn divuw_high_bit_set_is_unsigned() {
assert_eq!(alu(AluOp::Divu, HIGH_BIT_32, ONE, true), sext32(0x8000_0000));
}
#[test]
fn divuw_u32_max_by_1() {
assert_eq!(alu(AluOp::Divu, U32_MAX, ONE, true), NEG1);
}
#[test]
fn divuw_u32_max_by_2() {
assert_eq!(alu(AluOp::Divu, U32_MAX, 2, true), sext32(0x7FFF_FFFF));
}
#[test]
fn divuw_ignores_upper_input_bits() {
assert_eq!(alu(AluOp::Divu, 0xFFFF_FFFF_0000_0064, 0xFFFF_FFFF_0000_0007, true), 14);
}
#[test]
fn divuw_result_sign_extends_when_bit31_set() {
assert_eq!(alu(AluOp::Divu, 0x8000_0002, ONE, true), sext32(0x8000_0002));
}
#[test]
fn rem_rv64_remainder_by_zero() {
assert_eq!(alu(AluOp::Rem, 42, ZERO, false), 42);
}
#[test]
fn rem_rv64_zero_remainder_by_zero() {
assert_eq!(alu(AluOp::Rem, ZERO, ZERO, false), 0);
}
#[test]
fn rem_rv64_min_remainder_by_zero() {
assert_eq!(alu(AluOp::Rem, I64_MIN, ZERO, false), I64_MIN);
}
#[test]
fn rem_rv64_signed_overflow() {
assert_eq!(alu(AluOp::Rem, I64_MIN, NEG1, false), 0);
}
#[test]
fn rem_rv64_exact_division() {
assert_eq!(alu(AluOp::Rem, 42, 7, false), 0);
}
#[test]
fn rem_rv64_positive_remainder() {
assert_eq!(alu(AluOp::Rem, 100, 7, false), 2);
}
#[test]
fn rem_rv64_negative_dividend() {
assert_eq!(alu(AluOp::Rem, -100i64 as u64, 7, false), -2i64 as u64);
}
#[test]
fn rem_rv64_negative_divisor() {
assert_eq!(alu(AluOp::Rem, 100, -7i64 as u64, false), 2);
}
#[test]
fn rem_rv64_both_negative() {
assert_eq!(alu(AluOp::Rem, -100i64 as u64, -7i64 as u64, false), -2i64 as u64);
}
#[test]
fn rem_rv64_identity_div_mul_rem() {
let a = 100_u64;
let b = 7_u64;
let q = alu(AluOp::Div, a, b, false);
let r = alu(AluOp::Rem, a, b, false);
assert_eq!(
(q as i64).wrapping_mul(b as i64).wrapping_add(r as i64) as u64,
a,
"q*b + r must equal a"
);
}
#[test]
fn remw_remainder_by_zero() {
assert_eq!(alu(AluOp::Rem, 42, ZERO, true), sext32(42));
}
#[test]
fn remw_remainder_by_zero_upper_bits_must_be_ignored() {
let a = 0xDEAD_BEEF_0000_002A_u64;
assert_eq!(alu(AluOp::Rem, a, ZERO, true), sext32(0x0000_002A));
}
#[test]
fn remw_remainder_by_zero_negative_dividend() {
let a = 0x0000_0001_8000_0000_u64; assert_eq!(alu(AluOp::Rem, a, ZERO, true), sext32(0x8000_0000));
}
#[test]
fn remw_signed_overflow() {
assert_eq!(alu(AluOp::Rem, I32_MIN, NEG1, true), 0);
}
#[test]
fn remw_basic() {
assert_eq!(alu(AluOp::Rem, 100, 7, true), sext32(2));
}
#[test]
fn remw_negative_result_sign_extends() {
assert_eq!(alu(AluOp::Rem, -100i64 as u64, 7, true), sext32(-2i32 as u32));
}
#[test]
fn remu_rv64_remainder_by_zero() {
assert_eq!(alu(AluOp::Remu, 42, ZERO, false), 42);
}
#[test]
fn remu_rv64_zero_remainder_by_zero() {
assert_eq!(alu(AluOp::Remu, ZERO, ZERO, false), 0);
}
#[test]
fn remu_rv64_max_remainder_by_zero() {
assert_eq!(alu(AluOp::Remu, U64_MAX, ZERO, false), U64_MAX);
}
#[test]
fn remu_rv64_exact_division() {
assert_eq!(alu(AluOp::Remu, 42, 7, false), 0);
}
#[test]
fn remu_rv64_basic() {
assert_eq!(alu(AluOp::Remu, 100, 7, false), 2);
}
#[test]
fn remu_rv64_large_unsigned() {
assert_eq!(alu(AluOp::Remu, U64_MAX, 2, false), 1);
}
#[test]
fn remu_rv64_identity_divu_mul_remu() {
let a = 100_u64;
let b = 7_u64;
let q = alu(AluOp::Divu, a, b, false);
let r = alu(AluOp::Remu, a, b, false);
assert_eq!(q.wrapping_mul(b).wrapping_add(r), a, "q*b + r must equal a");
}
#[test]
fn remuw_remainder_by_zero() {
assert_eq!(alu(AluOp::Remu, 42, ZERO, true), sext32(42));
}
#[test]
fn remuw_remainder_by_zero_upper_bits_must_be_ignored() {
let a = 0xDEAD_BEEF_0000_002A_u64;
assert_eq!(alu(AluOp::Remu, a, ZERO, true), sext32(0x0000_002A));
}
#[test]
fn remuw_remainder_by_zero_divisor_upper_bits_set() {
let b_with_upper_bits = 0x0000_0001_0000_0000_u64;
assert_eq!(alu(AluOp::Remu, 42, b_with_upper_bits, true), sext32(42));
}
#[test]
fn remuw_remainder_by_zero_negative_lower32() {
let a = 0x0000_0001_8000_0000_u64;
assert_eq!(alu(AluOp::Remu, a, ZERO, true), sext32(0x8000_0000));
}
#[test]
fn remuw_basic() {
assert_eq!(alu(AluOp::Remu, 100, 7, true), sext32(2));
}
#[test]
fn remuw_high_bit_set_is_unsigned() {
assert_eq!(alu(AluOp::Remu, 0x8000_0001, 0x8000_0000, true), sext32(1));
}
#[test]
fn remuw_u32_max_mod_2() {
assert_eq!(alu(AluOp::Remu, U32_MAX, 2, true), sext32(1));
}
#[test]
fn remuw_ignores_upper_bits() {
assert_eq!(
alu(AluOp::Remu, 0xFFFF_FFFF_0000_0064, 0xFFFF_FFFF_0000_0007, true),
sext32(2) );
}
#[test]
fn remuw_identity_divuw_mul_remuw() {
let a: u64 = 0x0000_0000_DEAD_BEEF;
let b: u64 = 0x0000_0000_0000_0007;
let q = alu(AluOp::Divu, a, b, true);
let r = alu(AluOp::Remu, a, b, true);
let q32 = q as u32;
let r32 = r as u32;
let a32 = a as u32;
assert_eq!(
q32.wrapping_mul(b as u32).wrapping_add(r32),
a32,
"DIVUW*b + REMUW must equal a[31:0]"
);
}
#[test]
fn add_rv64_alternating_bits() {
assert_eq!(alu(AluOp::Add, ALTERNATING_A, ALTERNATING_5, false), U64_MAX);
}
#[test]
fn sub_rv64_alternating_bits() {
assert_eq!(alu(AluOp::Sub, ALTERNATING_A, ALTERNATING_A, false), 0);
}
#[test]
fn mul_rv64_power_of_two() {
assert_eq!(alu(AluOp::Mul, 0x1234, 1 << 16, false), 0x1234_0000);
}
#[test]
fn div_rv64_power_of_two() {
assert_eq!(alu(AluOp::Div, 256, 16, false), 16);
}
#[test]
fn divu_rv64_max_by_power_of_two() {
assert_eq!(alu(AluOp::Divu, U64_MAX, 1 << 32, false), 0xFFFF_FFFF);
}
#[test]
fn rem_rv64_power_of_two() {
assert_eq!(alu(AluOp::Rem, 100, 64, false), 36);
}
#[test]
fn remu_rv64_power_of_two() {
assert_eq!(alu(AluOp::Remu, 100, 64, false), 36);
}
#[test]
fn all_w_operations_produce_sign_extended_results() {
let test_cases: Vec<(AluOp, u64, u64)> = vec![
(AluOp::Add, 0xFFFF_FFFF, 1), (AluOp::Add, 0x7FFF_FFFF, 1), (AluOp::Sub, 0, 1), (AluOp::Sub, 0x8000_0000, 1), (AluOp::Mul, 0x7FFF_FFFF, 2), (AluOp::Div, 100, 7),
(AluOp::Div, 0x8000_0000, NEG1), (AluOp::Divu, 0xFFFF_FFFF, 1),
(AluOp::Divu, 42, 0), (AluOp::Rem, 100, 7),
(AluOp::Rem, 42, 0), (AluOp::Remu, 100, 7),
(AluOp::Remu, 42, 0), ];
for (op, a, b) in test_cases {
let result = alu(op, a, b, true);
let bit31 = (result >> 31) & 1;
let upper = result >> 32;
let expected_upper = if bit31 == 1 { 0xFFFF_FFFF } else { 0 };
assert_eq!(
upper, expected_upper,
"Op {:?} with a={:#x}, b={:#x}: result {:#018x} is not properly sign-extended",
op, a, b, result
);
}
}