use crate::rv64::test_utils::{TEST_BASE_ADDR, TestInterpreterState, initialize_state};
use crate::v::vector_registers::{VectorRegisters, VectorRegistersExt};
use crate::{ExecutableInstruction, ExecutionError, VirtualMemory};
use ab_riscv_primitives::prelude::*;
use core::array;
fn setup(
vl: u32,
vsew: Vsew,
vlmul: Vlmul,
) -> TestInterpreterState<Zve64xLoadInstruction<Reg<u64>>> {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
let vtype = Vtype::from_raw::<Reg<u64>>(encode_vtype(vsew, vlmul)).unwrap();
state.ext_state.set_vtype(Some(vtype));
state.ext_state.set_vl(vl);
state.ext_state.set_vstart(0);
state
}
fn encode_vtype(vsew: Vsew, vlmul: Vlmul) -> u64 {
(vlmul.to_bits() as u64) | ((vsew.to_bits() as u64) << 3)
}
fn write_mem(
state: &mut TestInterpreterState<Zve64xLoadInstruction<Reg<u64>>>,
addr: u64,
data: &[u8],
) {
for (i, &b) in data.iter().enumerate() {
state.memory.write::<u8>(addr + i as u64, b).unwrap();
}
}
fn vreg_byte(
state: &TestInterpreterState<Zve64xLoadInstruction<Reg<u64>>>,
reg: VReg,
offset: usize,
) -> u8 {
state.ext_state.read_vreg()[usize::from(reg.bits())][offset]
}
fn vreg_bytes(
state: &TestInterpreterState<Zve64xLoadInstruction<Reg<u64>>>,
reg: VReg,
) -> [u8; 16] {
state.ext_state.read_vreg()[usize::from(reg.bits())]
}
fn set_vreg(
state: &mut TestInterpreterState<Zve64xLoadInstruction<Reg<u64>>>,
reg: VReg,
data: &[u8],
) {
let dst = &mut state.ext_state.write_vreg()[usize::from(reg.bits())];
dst[..data.len()].copy_from_slice(data);
}
fn exec_one(
state: &mut TestInterpreterState<Zve64xLoadInstruction<Reg<u64>>>,
instr: Zve64xLoadInstruction<Reg<u64>>,
) -> Result<(), ExecutionError<u64>> {
instr.execute(state).map(|_| ())
}
#[test]
fn vlr_single_register_loads_vlenb_bytes() {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
let data = array::from_fn::<_, 16, _>(|i| i as u8);
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlr {
vd: VReg::V2,
rs1: Reg::A0,
nreg: 1,
eew: Eew::E8,
},
)
.unwrap();
assert_eq!(vreg_bytes(&state, VReg::V2), data);
assert_eq!(state.ext_state.vs_dirty_count(), 1);
}
#[test]
fn vlr_two_registers_loads_two_vlenb_blocks() {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
let data = array::from_fn::<_, 32, _>(|i| i as u8);
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlr {
vd: VReg::V2,
rs1: Reg::A0,
nreg: 2,
eew: Eew::E8,
},
)
.unwrap();
assert_eq!(&vreg_bytes(&state, VReg::V2), &data[..16]);
assert_eq!(&vreg_bytes(&state, VReg::V3), &data[16..]);
}
#[test]
fn vlr_four_registers() {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
let data = array::from_fn::<_, 64, _>(|i| i as u8);
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlr {
vd: VReg::V4,
rs1: Reg::A0,
nreg: 4,
eew: Eew::E8,
},
)
.unwrap();
for i in 0u8..4 {
let expected: [u8; 16] = data[i as usize * 16..(i as usize + 1) * 16]
.try_into()
.unwrap();
let reg = VReg::from_bits(4 + i).unwrap();
assert_eq!(vreg_bytes(&state, reg), expected);
}
}
#[test]
fn vlr_ignores_vtype_and_vl() {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
let data = [0xABu8; 16];
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlr {
vd: VReg::V0,
rs1: Reg::A0,
nreg: 1,
eew: Eew::E8,
},
)
.unwrap();
assert_eq!(vreg_bytes(&state, VReg::V0), data);
}
#[test]
fn vlr_resets_vstart_on_success() {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
state.ext_state.set_vstart(7);
let data = [0u8; 16];
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlr {
vd: VReg::V1,
rs1: Reg::A0,
nreg: 1,
eew: Eew::E8,
},
)
.unwrap();
assert_eq!(
state.ext_state.vstart(),
0,
"Vlr must reset vstart on completion"
);
}
#[test]
fn vlr_misaligned_vd_is_illegal() {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vlr {
vd: VReg::V3,
rs1: Reg::A0,
nreg: 2,
eew: Eew::E8,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::IllegalInstruction { .. }));
}
#[test]
fn vlr_out_of_bounds_memory_returns_error() {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
state.regs.write(Reg::A0, 0);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vlr {
vd: VReg::V0,
rs1: Reg::A0,
nreg: 1,
eew: Eew::E8,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::MemoryAccess(_)));
}
#[test]
fn vlm_loads_ceil_vl_over_8_bytes() {
let mut state = setup(10, Vsew::E8, Vlmul::M1);
let data = [0b10110101u8, 0b00000011u8];
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlm {
vd: VReg::V3,
rs1: Reg::A0,
},
)
.unwrap();
assert_eq!(vreg_byte(&state, VReg::V3, 0), 0b10110101);
assert_eq!(vreg_byte(&state, VReg::V3, 1), 0b00000011);
assert_eq!(state.ext_state.vs_dirty_count(), 1);
assert_eq!(state.ext_state.vstart(), 0, "vstart must be reset");
}
#[test]
fn vlm_vl_8_loads_exactly_1_byte() {
let mut state = setup(8, Vsew::E8, Vlmul::M1);
write_mem(&mut state, TEST_BASE_ADDR, &[0xFFu8]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlm {
vd: VReg::V1,
rs1: Reg::A0,
},
)
.unwrap();
assert_eq!(vreg_byte(&state, VReg::V1, 0), 0xFF);
}
#[test]
fn vlm_vl_0_loads_no_bytes_and_leaves_dst_unchanged() {
let mut state = setup(0, Vsew::E8, Vlmul::M1);
set_vreg(&mut state, VReg::V5, &[0xABu8; 16]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlm {
vd: VReg::V5,
rs1: Reg::A0,
},
)
.unwrap();
assert_eq!(vreg_bytes(&state, VReg::V5), [0xABu8; 16]);
}
#[test]
fn vlm_does_not_require_valid_vtype() {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
state.ext_state.set_vl(3);
write_mem(&mut state, TEST_BASE_ADDR, &[0x07u8]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlm {
vd: VReg::V0,
rs1: Reg::A0,
},
)
.unwrap();
assert_eq!(vreg_byte(&state, VReg::V0, 0), 0x07);
}
#[test]
fn vlm_vector_not_allowed_is_illegal() {
let mut state = setup(4, Vsew::E8, Vlmul::M1);
state.ext_state.set_vector_allowed(false);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vlm {
vd: VReg::V1,
rs1: Reg::A0,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::IllegalInstruction { .. }));
}
#[test]
fn vle_e8_loads_vl_bytes_sequentially() {
let mut state = setup(4, Vsew::E8, Vlmul::M1);
let data = [10u8, 20, 30, 40];
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
},
)
.unwrap();
assert_eq!(vreg_byte(&state, VReg::V1, 0), 10);
assert_eq!(vreg_byte(&state, VReg::V1, 1), 20);
assert_eq!(vreg_byte(&state, VReg::V1, 2), 30);
assert_eq!(vreg_byte(&state, VReg::V1, 3), 40);
assert_eq!(state.ext_state.vs_dirty_count(), 1);
assert_eq!(state.ext_state.vstart(), 0);
}
#[test]
fn vle_e32_loads_vl_words_sequentially() {
let mut state = setup(3, Vsew::E32, Vlmul::M1);
let data = array::from_fn::<_, 12, _>(|i| i as u8);
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V2,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
},
)
.unwrap();
assert_eq!(vreg_bytes(&state, VReg::V2)[0..4], [0, 1, 2, 3]);
assert_eq!(vreg_bytes(&state, VReg::V2)[4..8], [4, 5, 6, 7]);
assert_eq!(vreg_bytes(&state, VReg::V2)[8..12], [8, 9, 10, 11]);
}
#[test]
fn vle_e64_loads_vl_doublewords() {
let mut state = setup(2, Vsew::E64, Vlmul::M1);
let val0 = 0x0102030405060708_u64;
let val1 = 0xDEADBEEFCAFEBABE_u64;
write_mem(&mut state, TEST_BASE_ADDR, &val0.to_le_bytes());
write_mem(&mut state, TEST_BASE_ADDR + 8, &val1.to_le_bytes());
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V4,
rs1: Reg::A0,
vm: true,
eew: Eew::E64,
},
)
.unwrap();
assert_eq!(vreg_bytes(&state, VReg::V4)[0..8], val0.to_le_bytes());
assert_eq!(vreg_bytes(&state, VReg::V4)[8..16], val1.to_le_bytes());
}
#[test]
fn vle_vl_0_does_not_write_any_elements() {
let mut state = setup(0, Vsew::E32, Vlmul::M1);
set_vreg(&mut state, VReg::V7, &[0xFFu8; 16]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V7,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
},
)
.unwrap();
assert_eq!(vreg_bytes(&state, VReg::V7), [0xFFu8; 16]);
assert_eq!(state.ext_state.vs_dirty_count(), 1);
}
#[test]
fn vle_masked_skips_inactive_elements_undisturbed() {
let mut state = setup(8, Vsew::E8, Vlmul::M1);
set_vreg(&mut state, VReg::V2, &[0xEEu8; 16]);
set_vreg(&mut state, VReg::V0, &{
let mut m = [0u8; 16];
m[0] = 0b00110101;
m
});
write_mem(&mut state, TEST_BASE_ADDR, &[1, 2, 3, 4, 5, 6, 7, 8]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V2,
rs1: Reg::A0,
vm: false,
eew: Eew::E8,
},
)
.unwrap();
let reg = vreg_bytes(&state, VReg::V2);
assert_eq!(reg[0], 1, "element 0 active");
assert_eq!(reg[1], 0xEE, "element 1 inactive, undisturbed");
assert_eq!(reg[2], 3, "element 2 active");
assert_eq!(reg[3], 0xEE, "element 3 inactive, undisturbed");
assert_eq!(reg[4], 5, "element 4 active");
assert_eq!(reg[5], 6, "element 5 active");
assert_eq!(reg[6], 0xEE, "element 6 inactive, undisturbed");
assert_eq!(reg[7], 0xEE, "element 7 inactive, undisturbed");
}
#[test]
fn vle_respects_vstart_skips_earlier_elements() {
let mut state = setup(4, Vsew::E8, Vlmul::M1);
set_vreg(&mut state, VReg::V1, &[0xCCu8; 16]);
write_mem(&mut state, TEST_BASE_ADDR, &[10, 20, 30, 40]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
state.ext_state.set_vstart(2);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
},
)
.unwrap();
let reg = vreg_bytes(&state, VReg::V1);
assert_eq!(reg[0], 0xCC, "element 0 below vstart, undisturbed");
assert_eq!(reg[1], 0xCC, "element 1 below vstart, undisturbed");
assert_eq!(reg[2], 30, "element 2 loaded");
assert_eq!(reg[3], 40, "element 3 loaded");
assert_eq!(state.ext_state.vstart(), 0, "vstart reset after completion");
}
#[test]
fn vle_vtype_vill_is_illegal() {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::IllegalInstruction { .. }));
}
#[test]
fn vle_vector_not_allowed_is_illegal() {
let mut state = setup(4, Vsew::E32, Vlmul::M1);
state.ext_state.set_vector_allowed(false);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::IllegalInstruction { .. }));
}
#[test]
fn vle_masked_vd_overlapping_v0_is_illegal() {
let mut state = setup(4, Vsew::E8, Vlmul::M1);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V0,
rs1: Reg::A0,
vm: false,
eew: Eew::E8,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::IllegalInstruction { .. }));
}
#[test]
fn vle_eew_wider_than_sew_uses_multiple_registers() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
let data = array::from_fn::<_, 16, _>(|i| i as u8);
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V2,
rs1: Reg::A0,
vm: true,
eew: Eew::E64,
},
)
.unwrap();
assert_eq!(vreg_bytes(&state, VReg::V2)[0..8], data[0..8]);
assert_eq!(vreg_bytes(&state, VReg::V2)[8..16], data[8..16]);
}
#[test]
fn vle_misaligned_vd_for_emul2_is_illegal() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V3,
rs1: Reg::A0,
vm: true,
eew: Eew::E64,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::IllegalInstruction { .. }));
}
#[test]
fn vle_memory_fault_propagates() {
let mut state = setup(4, Vsew::E32, Vlmul::M1);
state.regs.write(Reg::A0, 0);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::MemoryAccess(_)));
}
#[test]
fn vleff_no_fault_behaves_like_vle() {
let mut state = setup(4, Vsew::E8, Vlmul::M1);
write_mem(&mut state, TEST_BASE_ADDR, &[1, 2, 3, 4]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vleff {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
},
)
.unwrap();
let reg = vreg_bytes(&state, VReg::V1);
assert_eq!(reg[0], 1);
assert_eq!(reg[1], 2);
assert_eq!(reg[2], 3);
assert_eq!(reg[3], 4);
assert_eq!(state.ext_state.vl(), 4);
assert_eq!(state.ext_state.vstart(), 0);
}
#[test]
fn vleff_fault_at_i0_traps() {
let mut state = setup(4, Vsew::E32, Vlmul::M1);
state.regs.write(Reg::A0, 0);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vleff {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::MemoryAccess(_)));
assert_eq!(state.ext_state.vl(), 4);
}
#[test]
fn vleff_fault_at_i1_truncates_vl_to_1() {
let mem_top = TEST_BASE_ADDR + 8191;
let mut state = setup(4, Vsew::E32, Vlmul::M1);
let aligned_addr = mem_top - 3;
write_mem(&mut state, aligned_addr, &[0xAAu8, 0xBBu8, 0xCCu8, 0xDDu8]);
state.regs.write(Reg::A0, aligned_addr);
exec_one(
&mut state,
Zve64xLoadInstruction::Vleff {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
},
)
.unwrap();
assert_eq!(vreg_bytes(&state, VReg::V1)[0..4], [0xAA, 0xBB, 0xCC, 0xDD]);
assert_eq!(state.ext_state.vl(), 1);
assert_eq!(state.ext_state.vs_dirty_count(), 1);
assert_eq!(state.ext_state.vstart(), 0);
}
#[test]
fn vleff_fault_at_i2_truncates_vl_to_2() {
let mem_end = TEST_BASE_ADDR + 8192;
let base = mem_end - 2; let mut state = setup(4, Vsew::E8, Vlmul::M1);
write_mem(&mut state, base, &[0x11u8, 0x22u8]);
state.regs.write(Reg::A0, base);
exec_one(
&mut state,
Zve64xLoadInstruction::Vleff {
vd: VReg::V3,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
},
)
.unwrap();
assert_eq!(vreg_byte(&state, VReg::V3, 0), 0x11);
assert_eq!(vreg_byte(&state, VReg::V3, 1), 0x22);
assert_eq!(state.ext_state.vl(), 2);
}
#[test]
fn vlse_positive_stride_loads_at_stride_intervals() {
let mut state = setup(3, Vsew::E32, Vlmul::M1);
state
.memory
.write::<u32>(TEST_BASE_ADDR, 0xAAAAAAAA)
.unwrap();
state
.memory
.write::<u32>(TEST_BASE_ADDR + 8, 0xBBBBBBBB)
.unwrap();
state
.memory
.write::<u32>(TEST_BASE_ADDR + 16, 0xCCCCCCCC)
.unwrap();
state.regs.write(Reg::A0, TEST_BASE_ADDR);
state.regs.write(Reg::A1, 8);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlse {
vd: VReg::V1,
rs1: Reg::A0,
rs2: Reg::A1,
vm: true,
eew: Eew::E32,
},
)
.unwrap();
let reg = vreg_bytes(&state, VReg::V1);
assert_eq!(
u32::from_le_bytes(reg[0..4].try_into().unwrap()),
0xAAAAAAAA
);
assert_eq!(
u32::from_le_bytes(reg[4..8].try_into().unwrap()),
0xBBBBBBBB
);
assert_eq!(
u32::from_le_bytes(reg[8..12].try_into().unwrap()),
0xCCCCCCCC
);
assert_eq!(state.ext_state.vstart(), 0);
}
#[test]
fn vlse_negative_stride_loads_in_reverse() {
let mut state = setup(3, Vsew::E8, Vlmul::M1);
let base = TEST_BASE_ADDR + 2;
write_mem(&mut state, TEST_BASE_ADDR, &[0x30u8, 0x20, 0x10]);
state.regs.write(Reg::A0, base);
state.regs.write(Reg::A1, (-1i64).cast_unsigned());
exec_one(
&mut state,
Zve64xLoadInstruction::Vlse {
vd: VReg::V2,
rs1: Reg::A0,
rs2: Reg::A1,
vm: true,
eew: Eew::E8,
},
)
.unwrap();
assert_eq!(vreg_byte(&state, VReg::V2, 0), 0x10);
assert_eq!(vreg_byte(&state, VReg::V2, 1), 0x20);
assert_eq!(vreg_byte(&state, VReg::V2, 2), 0x30);
}
#[test]
fn vlse_zero_stride_loads_same_address_repeatedly() {
let mut state = setup(4, Vsew::E32, Vlmul::M1);
state
.memory
.write::<u32>(TEST_BASE_ADDR, 0xDEADBEEFu32)
.unwrap();
state.regs.write(Reg::A0, TEST_BASE_ADDR);
state.regs.write(Reg::A1, 0u64);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlse {
vd: VReg::V1,
rs1: Reg::A0,
rs2: Reg::A1,
vm: true,
eew: Eew::E32,
},
)
.unwrap();
let reg = vreg_bytes(&state, VReg::V1);
for i in 0..4 {
assert_eq!(
u32::from_le_bytes(reg[i * 4..(i + 1) * 4].try_into().unwrap()),
0xDEADBEEF,
"element {i}"
);
}
}
#[test]
fn vlse_masked_skips_inactive_elements() {
let mut state = setup(4, Vsew::E8, Vlmul::M1);
set_vreg(&mut state, VReg::V0, &{
let mut m = [0u8; 16];
m[0] = 0b0101;
m
});
set_vreg(&mut state, VReg::V2, &[0xDDu8; 16]);
write_mem(&mut state, TEST_BASE_ADDR, &[10u8, 20, 30, 40]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
state.regs.write(Reg::A1, 1u64);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlse {
vd: VReg::V2,
rs1: Reg::A0,
rs2: Reg::A1,
vm: false,
eew: Eew::E8,
},
)
.unwrap();
let reg = vreg_bytes(&state, VReg::V2);
assert_eq!(reg[0], 10, "element 0 active");
assert_eq!(reg[1], 0xDD, "element 1 inactive");
assert_eq!(reg[2], 30, "element 2 active");
assert_eq!(reg[3], 0xDD, "element 3 inactive");
}
#[test]
fn vluxei_e32_data_e32_index_basic() {
let mut state = setup(3, Vsew::E32, Vlmul::M1);
let index_base = TEST_BASE_ADDR;
let data_base = TEST_BASE_ADDR + 0x100;
state.memory.write::<u32>(index_base, 12u32).unwrap();
state.memory.write::<u32>(index_base + 4, 0u32).unwrap();
state.memory.write::<u32>(index_base + 8, 8u32).unwrap();
state.memory.write::<u32>(data_base, 0x11111111u32).unwrap();
state
.memory
.write::<u32>(data_base + 8, 0x22222222u32)
.unwrap();
state
.memory
.write::<u32>(data_base + 12, 0x33333333u32)
.unwrap();
state.regs.write(Reg::A0, index_base);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V4,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
},
)
.unwrap();
state.regs.write(Reg::A0, data_base);
exec_one(
&mut state,
Zve64xLoadInstruction::Vluxei {
vd: VReg::V1,
rs1: Reg::A0,
vs2: VReg::V4,
vm: true,
eew: Eew::E32,
},
)
.unwrap();
let reg = vreg_bytes(&state, VReg::V1);
assert_eq!(
u32::from_le_bytes(reg[0..4].try_into().unwrap()),
0x33333333,
"elem0 at offset 12"
);
assert_eq!(
u32::from_le_bytes(reg[4..8].try_into().unwrap()),
0x11111111,
"elem1 at offset 0"
);
assert_eq!(
u32::from_le_bytes(reg[8..12].try_into().unwrap()),
0x22222222,
"elem2 at offset 8"
);
assert_eq!(state.ext_state.vstart(), 0);
}
#[test]
fn vluxei_index_eew_smaller_than_data_eew() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
let mut idx_reg = [0u8; 16];
idx_reg[0] = 4;
idx_reg[1] = 0;
set_vreg(&mut state, VReg::V6, &idx_reg);
let data_base = TEST_BASE_ADDR;
state.memory.write::<u32>(data_base, 0xAABBCCDDu32).unwrap();
state
.memory
.write::<u32>(data_base + 4, 0x11223344u32)
.unwrap();
state.regs.write(Reg::A0, data_base);
exec_one(
&mut state,
Zve64xLoadInstruction::Vluxei {
vd: VReg::V1,
rs1: Reg::A0,
vs2: VReg::V6,
vm: true,
eew: Eew::E8,
},
)
.unwrap();
let reg = vreg_bytes(&state, VReg::V1);
assert_eq!(
u32::from_le_bytes(reg[0..4].try_into().unwrap()),
0x11223344,
"elem0 at offset 4"
);
assert_eq!(
u32::from_le_bytes(reg[4..8].try_into().unwrap()),
0xAABBCCDDu32,
"elem1 at offset 0"
);
}
#[test]
fn vluxei_vd_vs2_overlap_is_illegal() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vluxei {
vd: VReg::V3,
rs1: Reg::A0,
vs2: VReg::V3,
vm: true,
eew: Eew::E32,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::IllegalInstruction { .. }));
}
#[test]
fn vluxei_masked_vd_overlapping_v0_is_illegal() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vluxei {
vd: VReg::V0,
rs1: Reg::A0,
vs2: VReg::V4,
vm: false,
eew: Eew::E32,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::IllegalInstruction { .. }));
}
#[test]
fn vloxei_functionally_identical_to_vluxei() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
let mut idx = [0u8; 16];
idx[0] = 4;
idx[1..4].copy_from_slice(&[0u8, 0, 0]);
idx[4] = 0; set_vreg(&mut state, VReg::V5, &idx);
state
.memory
.write::<u32>(TEST_BASE_ADDR, 0x12345678u32)
.unwrap();
state
.memory
.write::<u32>(TEST_BASE_ADDR + 4, 0x87654321u32)
.unwrap();
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vloxei {
vd: VReg::V1,
rs1: Reg::A0,
vs2: VReg::V5,
vm: true,
eew: Eew::E32,
},
)
.unwrap();
let reg = vreg_bytes(&state, VReg::V1);
assert_eq!(
u32::from_le_bytes(reg[0..4].try_into().unwrap()),
0x87654321
);
assert_eq!(
u32::from_le_bytes(reg[4..8].try_into().unwrap()),
0x12345678
);
}
#[test]
fn vlseg_nf2_e8_interleaved_fields() {
let mut state = setup(4, Vsew::E8, Vlmul::M1);
write_mem(
&mut state,
TEST_BASE_ADDR,
&[10, 20, 11, 21, 12, 22, 13, 23],
);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlseg {
vd: VReg::V2,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
nf: 2,
},
)
.unwrap();
let v2 = vreg_bytes(&state, VReg::V2);
assert_eq!(v2[0], 10);
assert_eq!(v2[1], 11);
assert_eq!(v2[2], 12);
assert_eq!(v2[3], 13);
let v3 = vreg_bytes(&state, VReg::V3);
assert_eq!(v3[0], 20);
assert_eq!(v3[1], 21);
assert_eq!(v3[2], 22);
assert_eq!(v3[3], 23);
assert_eq!(state.ext_state.vstart(), 0);
assert_eq!(state.ext_state.vs_dirty_count(), 1);
}
#[test]
fn vlseg_nf3_e32() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
let data = array::from_fn::<_, 24, _>(|i| i as u8 + 1);
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlseg {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
nf: 3,
},
)
.unwrap();
assert_eq!(vreg_bytes(&state, VReg::V1)[0..4], data[0..4]);
assert_eq!(vreg_bytes(&state, VReg::V2)[0..4], data[4..8]);
assert_eq!(vreg_bytes(&state, VReg::V3)[0..4], data[8..12]);
assert_eq!(vreg_bytes(&state, VReg::V1)[4..8], data[12..16]);
assert_eq!(vreg_bytes(&state, VReg::V2)[4..8], data[16..20]);
assert_eq!(vreg_bytes(&state, VReg::V3)[4..8], data[20..24]);
}
#[test]
fn vlseg_register_group_overflow_is_illegal() {
let mut state = setup(2, Vsew::E8, Vlmul::M1);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vlseg {
vd: VReg::V30,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
nf: 8,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::IllegalInstruction { .. }));
}
#[test]
fn vlseg_masked_vd_at_v0_is_illegal() {
let mut state = setup(2, Vsew::E8, Vlmul::M1);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vlseg {
vd: VReg::V0,
rs1: Reg::A0,
vm: false,
eew: Eew::E8,
nf: 2,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::IllegalInstruction { .. }));
}
#[test]
fn vlsegff_no_fault_loads_all_segments() {
let mut state = setup(3, Vsew::E8, Vlmul::M1);
write_mem(&mut state, TEST_BASE_ADDR, &[1, 2, 3, 4, 5, 6]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlsegff {
vd: VReg::V2,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
nf: 2,
},
)
.unwrap();
assert_eq!(vreg_byte(&state, VReg::V2, 0), 1);
assert_eq!(vreg_byte(&state, VReg::V3, 0), 2);
assert_eq!(vreg_byte(&state, VReg::V2, 1), 3);
assert_eq!(vreg_byte(&state, VReg::V3, 1), 4);
assert_eq!(vreg_byte(&state, VReg::V2, 2), 5);
assert_eq!(vreg_byte(&state, VReg::V3, 2), 6);
assert_eq!(state.ext_state.vl(), 3);
}
#[test]
fn vlsegff_fault_at_segment_1_truncates_vl() {
let mem_end = TEST_BASE_ADDR + 8192;
let base = mem_end - 2;
let mut state = setup(3, Vsew::E8, Vlmul::M1);
write_mem(&mut state, base, &[0xAAu8, 0xBBu8]);
state.regs.write(Reg::A0, base);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlsegff {
vd: VReg::V4,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
nf: 2,
},
)
.unwrap();
assert_eq!(vreg_byte(&state, VReg::V4, 0), 0xAA);
assert_eq!(vreg_byte(&state, VReg::V5, 0), 0xBB);
assert_eq!(state.ext_state.vl(), 1);
}
#[test]
fn vlsseg_nf2_e32_with_stride() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
state
.memory
.write::<u32>(TEST_BASE_ADDR, 0xAAAAAAAAu32)
.unwrap();
state
.memory
.write::<u32>(TEST_BASE_ADDR + 4, 0xBBBBBBBBu32)
.unwrap();
state
.memory
.write::<u32>(TEST_BASE_ADDR + 16, 0xCCCCCCCCu32)
.unwrap();
state
.memory
.write::<u32>(TEST_BASE_ADDR + 20, 0xDDDDDDDDu32)
.unwrap();
state.regs.write(Reg::A0, TEST_BASE_ADDR);
state.regs.write(Reg::A1, 16u64);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlsseg {
vd: VReg::V2,
rs1: Reg::A0,
rs2: Reg::A1,
vm: true,
eew: Eew::E32,
nf: 2,
},
)
.unwrap();
let v2 = vreg_bytes(&state, VReg::V2);
let v3 = vreg_bytes(&state, VReg::V3);
assert_eq!(u32::from_le_bytes(v2[0..4].try_into().unwrap()), 0xAAAAAAAA);
assert_eq!(u32::from_le_bytes(v2[4..8].try_into().unwrap()), 0xCCCCCCCC);
assert_eq!(u32::from_le_bytes(v3[0..4].try_into().unwrap()), 0xBBBBBBBB);
assert_eq!(u32::from_le_bytes(v3[4..8].try_into().unwrap()), 0xDDDDDDDD);
assert_eq!(state.ext_state.vstart(), 0);
}
#[test]
fn vlsseg_fault_at_f1_of_i0_marks_vs_dirty_and_sets_vstart() {
let mem_end = TEST_BASE_ADDR + 8192;
let base = mem_end - 4; let mut state = setup(2, Vsew::E32, Vlmul::M1);
state.memory.write::<u32>(base, 0xDEADBEEFu32).unwrap();
state.regs.write(Reg::A0, base);
state.regs.write(Reg::A1, 8u64);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vlsseg {
vd: VReg::V2,
rs1: Reg::A0,
rs2: Reg::A1,
vm: true,
eew: Eew::E32,
nf: 2,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::MemoryAccess(_)));
assert_eq!(
state.ext_state.vs_dirty_count(),
1,
"VS must be marked dirty"
);
assert_eq!(
state.ext_state.vstart(),
0u16,
"vstart must record the faulting element"
);
let v2 = vreg_bytes(&state, VReg::V2);
assert_eq!(u32::from_le_bytes(v2[0..4].try_into().unwrap()), 0xDEADBEEF);
}
#[test]
fn vlsseg_fault_at_i1_f0_marks_vs_dirty_and_sets_vstart() {
let mem_end = TEST_BASE_ADDR + 8192;
let base = mem_end - 4;
let mut state = setup(3, Vsew::E8, Vlmul::M1);
write_mem(&mut state, base, &[0xAAu8, 0xBBu8, 0xCCu8, 0xDDu8]);
state.regs.write(Reg::A0, base);
state.regs.write(Reg::A1, 2u64);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vlsseg {
vd: VReg::V2,
rs1: Reg::A0,
rs2: Reg::A1,
vm: true,
eew: Eew::E8,
nf: 2,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::MemoryAccess(_)));
assert_eq!(state.ext_state.vs_dirty_count(), 1);
assert_eq!(state.ext_state.vstart(), 2u16);
}
#[test]
fn vluxseg_nf2_e32_indexed() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
let data_base = TEST_BASE_ADDR;
state.memory.write::<u32>(data_base, 0x11111111u32).unwrap();
state
.memory
.write::<u32>(data_base + 4, 0x22222222u32)
.unwrap();
state
.memory
.write::<u32>(data_base + 8, 0x33333333u32)
.unwrap();
state
.memory
.write::<u32>(data_base + 12, 0x44444444u32)
.unwrap();
let mut idx_bytes = [0u8; 16];
idx_bytes[0..4].copy_from_slice(&8u32.to_le_bytes());
idx_bytes[4..8].copy_from_slice(&0u32.to_le_bytes());
set_vreg(&mut state, VReg::V8, &idx_bytes);
state.regs.write(Reg::A0, data_base);
exec_one(
&mut state,
Zve64xLoadInstruction::Vluxseg {
vd: VReg::V2,
rs1: Reg::A0,
vs2: VReg::V8,
vm: true,
eew: Eew::E32,
nf: 2,
},
)
.unwrap();
let v2 = vreg_bytes(&state, VReg::V2);
let v3 = vreg_bytes(&state, VReg::V3);
assert_eq!(u32::from_le_bytes(v2[0..4].try_into().unwrap()), 0x33333333);
assert_eq!(u32::from_le_bytes(v3[0..4].try_into().unwrap()), 0x44444444);
assert_eq!(u32::from_le_bytes(v2[4..8].try_into().unwrap()), 0x11111111);
assert_eq!(u32::from_le_bytes(v3[4..8].try_into().unwrap()), 0x22222222);
assert_eq!(state.ext_state.vstart(), 0);
}
#[test]
fn vluxseg_field_vs2_overlap_is_illegal() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vluxseg {
vd: VReg::V2,
rs1: Reg::A0,
vs2: VReg::V3,
vm: true,
eew: Eew::E32,
nf: 2,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::IllegalInstruction { .. }));
}
#[test]
fn vloxseg_same_result_as_vluxseg() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
let data_base = TEST_BASE_ADDR;
state.memory.write::<u32>(data_base, 0xABCDEF01u32).unwrap();
state
.memory
.write::<u32>(data_base + 4, 0x10FEDCBA)
.unwrap();
let mut idx_bytes = [0u8; 16];
idx_bytes[0..4].copy_from_slice(&4u32.to_le_bytes());
idx_bytes[4..8].copy_from_slice(&0u32.to_le_bytes());
set_vreg(&mut state, VReg::V6, &idx_bytes);
state.regs.write(Reg::A0, data_base);
exec_one(
&mut state,
Zve64xLoadInstruction::Vloxseg {
vd: VReg::V2,
rs1: Reg::A0,
vs2: VReg::V6,
vm: true,
eew: Eew::E32,
nf: 1,
},
)
.unwrap();
let v2 = vreg_bytes(&state, VReg::V2);
assert_eq!(u32::from_le_bytes(v2[0..4].try_into().unwrap()), 0x10FEDCBA);
assert_eq!(u32::from_le_bytes(v2[4..8].try_into().unwrap()), 0xABCDEF01);
}
#[test]
fn all_non_vlr_loads_reset_vstart_on_success() {
let mut state = setup(2, Vsew::E32, Vlmul::M1);
write_mem(&mut state, TEST_BASE_ADDR, &[0u8; 32]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
state.regs.write(Reg::A1, 4u64);
let indices_bytes = {
let mut b = [0u8; 16];
b[4..8].copy_from_slice(&0u32.to_le_bytes());
b
};
set_vreg(&mut state, VReg::V8, &indices_bytes);
for instr in [
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
},
Zve64xLoadInstruction::Vleff {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
},
Zve64xLoadInstruction::Vlse {
vd: VReg::V1,
rs1: Reg::A0,
rs2: Reg::A1,
vm: true,
eew: Eew::E32,
},
Zve64xLoadInstruction::Vlm {
vd: VReg::V1,
rs1: Reg::A0,
},
Zve64xLoadInstruction::Vluxei {
vd: VReg::V1,
rs1: Reg::A0,
vs2: VReg::V8,
vm: true,
eew: Eew::E32,
},
Zve64xLoadInstruction::Vloxei {
vd: VReg::V1,
rs1: Reg::A0,
vs2: VReg::V8,
vm: true,
eew: Eew::E32,
},
] {
state.ext_state.set_vstart(5);
exec_one(&mut state, instr).unwrap();
assert_eq!(
state.ext_state.vstart(),
0,
"vstart not reset for {:?}",
instr
);
}
}
#[test]
fn mark_vs_dirty_called_exactly_once_on_success() {
let mut state = setup(4, Vsew::E8, Vlmul::M1);
write_mem(&mut state, TEST_BASE_ADDR, &[0u8; 32]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
},
)
.unwrap();
assert_eq!(state.ext_state.vs_dirty_count(), 1);
}
#[test]
fn mark_vs_dirty_not_called_on_illegal_instruction_error() {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
let _ = exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E32,
},
);
assert_eq!(state.ext_state.vs_dirty_count(), 0);
}
#[test]
fn vle_e8_all_elements_across_register_boundary_m2() {
let mut state = setup(20, Vsew::E8, Vlmul::M2);
let data = array::from_fn::<_, 20, _>(|i| i as u8 + 1);
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V2,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
},
)
.unwrap();
for i in 0..16usize {
assert_eq!(vreg_byte(&state, VReg::V2, i), (i + 1) as u8, "V2[{i}]");
}
for i in 0..4usize {
assert_eq!(vreg_byte(&state, VReg::V3, i), (17 + i) as u8, "V3[{i}]");
}
}
#[test]
fn vle_e16_loads_half_words() {
let mut state = setup(3, Vsew::E16, Vlmul::M1);
let vals = [0x0102_u16, 0x0304, 0x0506];
let data = vals.map(|v| v.to_le_bytes());
write_mem(&mut state, TEST_BASE_ADDR, data.as_flattened());
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E16,
},
)
.unwrap();
let reg = vreg_bytes(&state, VReg::V1);
assert_eq!(u16::from_le_bytes(reg[0..2].try_into().unwrap()), 0x0102);
assert_eq!(u16::from_le_bytes(reg[2..4].try_into().unwrap()), 0x0304);
assert_eq!(u16::from_le_bytes(reg[4..6].try_into().unwrap()), 0x0506);
}
#[test]
fn vle_vl_equals_vlmax_loads_all_elements() {
let mut state = setup(16, Vsew::E8, Vlmul::M1);
let data = array::from_fn::<_, 16, _>(|i| i as u8);
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
},
)
.unwrap();
assert_eq!(vreg_bytes(&state, VReg::V1), data);
}
#[test]
fn vle_fractional_lmul_mf2_e8() {
let mut state = setup(4, Vsew::E8, Vlmul::Mf2);
write_mem(&mut state, TEST_BASE_ADDR, &[5u8, 6, 7, 8]);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
},
)
.unwrap();
assert_eq!(vreg_byte(&state, VReg::V1, 0), 5);
assert_eq!(vreg_byte(&state, VReg::V1, 1), 6);
assert_eq!(vreg_byte(&state, VReg::V1, 2), 7);
assert_eq!(vreg_byte(&state, VReg::V1, 3), 8);
}
#[test]
fn vle_mask_spanning_two_bytes() {
let mut state = setup(12, Vsew::E8, Vlmul::M1);
set_vreg(&mut state, VReg::V2, &[0xEEu8; 16]);
set_vreg(&mut state, VReg::V0, &{
let mut m = [0u8; 16];
m[0] = 0b11001010;
m[1] = 0b00001101;
m
});
let data = array::from_fn::<_, 12, _>(|i| i as u8 + 1);
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V2,
rs1: Reg::A0,
vm: false,
eew: Eew::E8,
},
)
.unwrap();
let reg = vreg_bytes(&state, VReg::V2);
let active: &[usize] = &[1, 3, 6, 7, 8, 10, 11];
for (i, &byte) in reg.iter().enumerate().take(12usize) {
if active.contains(&i) {
assert_eq!(byte, (i + 1) as u8, "element {i} should be loaded");
} else {
assert_eq!(byte, 0xEE, "element {i} should be undisturbed");
}
}
}
#[test]
fn vle_fault_after_first_element_marks_vs_dirty() {
let mem_end = TEST_BASE_ADDR + 8192;
let base = mem_end - 2;
let mut state = setup(4, Vsew::E8, Vlmul::M1);
write_mem(&mut state, base, &[0xAAu8, 0xBBu8]);
state.regs.write(Reg::A0, base);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::MemoryAccess(_)));
assert_eq!(vreg_byte(&state, VReg::V1, 0), 0xAA, "element 0 committed");
assert_eq!(vreg_byte(&state, VReg::V1, 1), 0xBB, "element 1 committed");
assert_eq!(
state.ext_state.vs_dirty_count(),
1,
"vs_dirty must be marked after partial write"
);
}
#[test]
fn vle_fault_after_first_element_sets_vstart_to_faulting_index() {
let mem_end = TEST_BASE_ADDR + 8192;
let base = mem_end - 2;
let mut state = setup(4, Vsew::E8, Vlmul::M1);
write_mem(&mut state, base, &[0x11u8, 0x22u8]);
state.regs.write(Reg::A0, base);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V2,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::MemoryAccess(_)));
assert_eq!(
state.ext_state.vstart(),
2,
"vstart must record the faulting element index"
);
}
#[test]
fn vle_fault_at_first_element_does_not_mark_vs_dirty() {
let mut state = setup(4, Vsew::E8, Vlmul::M1);
state.regs.write(Reg::A0, 0);
let err = exec_one(
&mut state,
Zve64xLoadInstruction::Vle {
vd: VReg::V1,
rs1: Reg::A0,
vm: true,
eew: Eew::E8,
},
)
.unwrap_err();
assert!(matches!(err, ExecutionError::MemoryAccess(_)));
assert_eq!(
state.ext_state.vs_dirty_count(),
0,
"vs_dirty must not be marked when no element was written"
);
assert_eq!(
state.ext_state.vstart(),
0,
"vstart must not be modified when fault is at the first element"
);
}
#[test]
fn vlr_eight_registers() {
let mut state = initialize_state([]);
state.ext_state.init_vector_csrs();
let data = array::from_fn::<_, 128, _>(|i| i as u8);
write_mem(&mut state, TEST_BASE_ADDR, &data);
state.regs.write(Reg::A0, TEST_BASE_ADDR);
exec_one(
&mut state,
Zve64xLoadInstruction::Vlr {
vd: VReg::V0,
rs1: Reg::A0,
nreg: 8,
eew: Eew::E8,
},
)
.unwrap();
for r in 0u8..8 {
let reg = VReg::from_bits(r).unwrap();
let expected: [u8; 16] = data[r as usize * 16..(r as usize + 1) * 16]
.try_into()
.unwrap();
assert_eq!(vreg_bytes(&state, reg), expected, "register v{r}");
}
}