rvsim-core 2.0.0

A cycle-level RISC-V 64-bit system simulator.
use crate::exec::compute::fpu;
use crate::isa::op::AluOp;

fn execute(op: AluOp, a: u64, b: u64, c: u64, is32: bool) -> u64 {
    fpu::execute_full(op, a, b, c, is32).0
}

#[test]
fn test_box_f32() {
    let f: f32 = 1.234;
    let boxed = box_f32(f);
    // RISC-V: 32-bit values are boxed in 64-bit registers by setting upper 32 bits to all 1s.
    assert_eq!(boxed >> 32, 0xFFFFFFFF, "Upper 32 bits must be all 1s");
    assert_eq!(boxed as u32, f.to_bits(), "Lower 32 bits must match f32 representation");
}

#[test]
fn test_nan_boxing_unboxing() {
    // Valid boxing
    let f_val: f32 = 42.0;
    let valid_boxed = box_f32(f_val);

    // We'll test this through an operation like FAdd with 0
    let zero = box_f32(0.0);
    let res = execute(AluOp::FAdd, valid_boxed, zero, 0, true);

    assert_eq!(res >> 32, 0xFFFFFFFF);
    assert_eq!(f32::from_bits(res as u32), 42.0);

    // Invalid boxing (upper bits not all 1s)
    let invalid_boxed = f_val.to_bits() as u64; // Upper bits are 0
    let res_invalid = execute(AluOp::FAdd, invalid_boxed, zero, 0, true);

    // RISC-V: If input is not properly NaN-boxed, it is treated as canonical NaN.
    let canon_nan_32 = 0x7fc00000u32;
    assert_eq!(
        res_invalid,
        box_f32(f32::from_bits(canon_nan_32)),
        "Invalidly boxed input must result in canonical NaN"
    );
}

#[test]
fn test_fmin_fmax_nan_handling() {
    let f_val = box_f32(10.0);
    let f_nan = box_f32(f32::NAN);

    // RISC-V fmin/fmax: if one op is NaN, return the other.
    let res_min = execute(AluOp::FMin, f_val, f_nan, 0, true);
    assert_eq!(res_min, f_val, "fmin(val, NaN) should be val");

    let res_min2 = execute(AluOp::FMin, f_nan, f_val, 0, true);
    assert_eq!(res_min2, f_val, "fmin(NaN, val) should be val");

    let res_max = execute(AluOp::FMax, f_val, f_nan, 0, true);
    assert_eq!(res_max, f_val, "fmax(val, NaN) should be val");

    let res_max2 = execute(AluOp::FMax, f_nan, f_val, 0, true);
    assert_eq!(res_max2, f_val, "fmax(NaN, val) should be val");

    // Both NaN: return canonical NaN
    let res_both_nan = execute(AluOp::FMin, f_nan, f_nan, 0, true);
    let canon_nan_32 = 0x7fc00000u32;
    assert_eq!(res_both_nan, box_f32(f32::from_bits(canon_nan_32)));
}

#[test]
fn test_canonical_nan_propagation() {
    // Signaling NaN (sNaN)
    let snan_bits = 0x7f800001u32;
    let snan = box_f32(f32::from_bits(snan_bits));
    let zero = box_f32(0.0);

    // Any op with sNaN should produce a canonical quiet NaN
    let res = execute(AluOp::FAdd, snan, zero, 0, true);
    let canon_nan_32 = 0x7fc00000u32;
    assert_eq!(res, box_f32(f32::from_bits(canon_nan_32)), "sNaN must be quieted to canonical NaN");
}

#[test]
fn test_f64_nan_boxing_not_applicable() {
    // NaN boxing applies only to 32-bit values; f64 ops use the full 64 bits.
    let d_val1 = f64::to_bits(1.0);
    let d_val2 = f64::to_bits(2.0);

    let res = execute(AluOp::FAdd, d_val1, d_val2, 0, false);
    assert_eq!(f64::from_bits(res), 3.0);
}

use crate::exec::compute::fpu::nan_handling::*;

#[test]
fn test_unbox_f32_direct() {
    let valid = box_f32(1.0);
    assert_eq!(unbox_f32(valid).to_bits(), 1.0f32.to_bits());

    let invalid = 1.0f32.to_bits() as u64; // upper bits are 0
    assert_eq!(unbox_f32(invalid).to_bits(), 0x7fc0_0000);
}

#[test]
fn test_fmin_fmax_f32_direct() {
    let pos_zero = f32::from_bits(0x0000_0000);
    let neg_zero = f32::from_bits(0x8000_0000);

    // fmin
    assert_eq!(fmin_f32(neg_zero, pos_zero).to_bits(), neg_zero.to_bits());
    assert_eq!(fmin_f32(pos_zero, neg_zero).to_bits(), neg_zero.to_bits());

    // fmax
    assert_eq!(fmax_f32(neg_zero, pos_zero).to_bits(), pos_zero.to_bits());
    assert_eq!(fmax_f32(pos_zero, neg_zero).to_bits(), pos_zero.to_bits());

    // Both NaNs
    let nan = f32::from_bits(0x7fc0_0000);
    assert_eq!(fmin_f32(nan, nan).to_bits(), 0x7fc0_0000);
    assert_eq!(fmax_f32(nan, nan).to_bits(), 0x7fc0_0000);

    // One NaN
    assert_eq!(fmin_f32(nan, 1.0).to_bits(), 1.0f32.to_bits());
    assert_eq!(fmin_f32(1.0, nan).to_bits(), 1.0f32.to_bits());
    assert_eq!(fmax_f32(nan, 1.0).to_bits(), 1.0f32.to_bits());
    assert_eq!(fmax_f32(1.0, nan).to_bits(), 1.0f32.to_bits());

    // normal min/max
    assert_eq!(fmin_f32(1.0, 2.0).to_bits(), 1.0f32.to_bits());
    assert_eq!(fmax_f32(1.0, 2.0).to_bits(), 2.0f32.to_bits());
}

#[test]
fn test_fmin_fmax_f64_direct() {
    let pos_zero = f64::from_bits(0x0000_0000_0000_0000);
    let neg_zero = f64::from_bits(0x8000_0000_0000_0000);

    // fmin
    assert_eq!(fmin_f64(neg_zero, pos_zero).to_bits(), neg_zero.to_bits());
    assert_eq!(fmin_f64(pos_zero, neg_zero).to_bits(), neg_zero.to_bits());

    // fmax
    assert_eq!(fmax_f64(neg_zero, pos_zero).to_bits(), pos_zero.to_bits());
    assert_eq!(fmax_f64(pos_zero, neg_zero).to_bits(), pos_zero.to_bits());

    // Both NaNs
    let nan = f64::from_bits(0x7ff8_0000_0000_0000);
    assert_eq!(fmin_f64(nan, nan).to_bits(), 0x7ff8_0000_0000_0000);
    assert_eq!(fmax_f64(nan, nan).to_bits(), 0x7ff8_0000_0000_0000);

    // One NaN
    assert_eq!(fmin_f64(nan, 1.0).to_bits(), 1.0f64.to_bits());
    assert_eq!(fmin_f64(1.0, nan).to_bits(), 1.0f64.to_bits());
    assert_eq!(fmax_f64(nan, 1.0).to_bits(), 1.0f64.to_bits());
    assert_eq!(fmax_f64(1.0, nan).to_bits(), 1.0f64.to_bits());

    // normal min/max
    assert_eq!(fmin_f64(1.0, 2.0).to_bits(), 1.0f64.to_bits());
    assert_eq!(fmax_f64(1.0, 2.0).to_bits(), 2.0f64.to_bits());
}