use crate::v::vector_registers::VectorRegistersExt;
pub use crate::v::zve64x::arith::zve64x_arith_helpers::{
OpSrc, check_vreg_group_alignment as check_vd, check_vreg_group_alignment as check_vs, sew_mask,
};
use crate::v::zve64x::arith::zve64x_arith_helpers::{
read_element_u64, sign_extend, write_element_u64,
};
use crate::v::zve64x::load::zve64x_load_helpers::{mask_bit, snapshot_mask};
use crate::v::zve64x::zve64x_helpers::INSTRUCTION_SIZE;
use crate::{ExecutionError, InterpreterState, ProgramCounter, VirtualMemory};
use ab_riscv_primitives::instructions::v::{Vsew, Vxrm};
use ab_riscv_primitives::registers::general_purpose::Register;
use ab_riscv_primitives::registers::vector::VReg;
use core::fmt;
#[inline(always)]
fn round_increment(val: u64, shift: u32, mode: Vxrm, current_result_lsb: u64) -> u64 {
if shift == 0 {
return 0;
}
let d_minus1_bit = (val >> (shift - 1)) & 1;
let sticky = if shift >= 2 {
(val & ((1u64 << (shift - 1)).wrapping_sub(1))) != 0
} else {
false
};
match mode {
Vxrm::Rnu => d_minus1_bit,
Vxrm::Rne => {
d_minus1_bit
& (if sticky || current_result_lsb != 0 {
1
} else {
0
})
}
Vxrm::Rdn => 0,
Vxrm::Rod => {
if current_result_lsb == 0 && (d_minus1_bit != 0 || sticky) {
1
} else {
0
}
}
}
}
#[inline(always)]
pub fn rounded_srl(val: u64, shift: u32, mode: Vxrm) -> u64 {
let truncated = val >> shift;
let r = round_increment(val, shift, mode, truncated & 1);
truncated.wrapping_add(r)
}
#[inline(always)]
pub fn rounded_sra(val: u64, shift: u32, mode: Vxrm, sew: Vsew) -> u64 {
let signed = sign_extend(val, sew);
let truncated_signed = signed >> shift;
let r = round_increment(val, shift, mode, truncated_signed.cast_unsigned() & 1);
truncated_signed.cast_unsigned().wrapping_add(r)
}
#[inline(always)]
pub fn sat_addu(a: u64, b: u64, sew: Vsew, vxsat: &mut bool) -> u64 {
let mask = sew_mask(sew);
let a_w = a & mask;
let b_w = b & mask;
let result = a_w.wrapping_add(b_w);
if result & mask < a_w {
*vxsat = true;
mask
} else {
result & mask
}
}
#[inline(always)]
pub fn sat_add(a: u64, b: u64, sew: Vsew, vxsat: &mut bool) -> u64 {
let sa = sign_extend(a, sew) as i128;
let sb = sign_extend(b, sew) as i128;
let result = sa.wrapping_add(sb);
let min_val = i128::MIN >> (i128::BITS - u32::from(sew.bits()));
let max_val = i128::MAX >> (i128::BITS - u32::from(sew.bits()));
if result < min_val {
*vxsat = true;
(min_val as i64).cast_unsigned() & sew_mask(sew)
} else if result > max_val {
*vxsat = true;
(max_val as i64).cast_unsigned() & sew_mask(sew)
} else {
(result as i64).cast_unsigned() & sew_mask(sew)
}
}
#[inline(always)]
pub fn sat_subu(a: u64, b: u64, sew: Vsew, vxsat: &mut bool) -> u64 {
let mask = sew_mask(sew);
let a_w = a & mask;
let b_w = b & mask;
if a_w < b_w {
*vxsat = true;
0
} else {
(a_w - b_w) & mask
}
}
#[inline(always)]
pub fn sat_sub(a: u64, b: u64, sew: Vsew, vxsat: &mut bool) -> u64 {
let sa = sign_extend(a, sew) as i128;
let sb = sign_extend(b, sew) as i128;
let result = sa.wrapping_sub(sb);
let min_val = i128::MIN >> (i128::BITS - u32::from(sew.bits()));
let max_val = i128::MAX >> (i128::BITS - u32::from(sew.bits()));
if result < min_val {
*vxsat = true;
(min_val as i64).cast_unsigned() & sew_mask(sew)
} else if result > max_val {
*vxsat = true;
(max_val as i64).cast_unsigned() & sew_mask(sew)
} else {
(result as i64).cast_unsigned() & sew_mask(sew)
}
}
#[inline(always)]
pub fn avg_addu(a: u64, b: u64, sew: Vsew, mode: Vxrm) -> u64 {
let mask = sew_mask(sew);
let a_w = a & mask;
let b_w = b & mask;
let sum = a_w.wrapping_add(b_w);
let carry = if sum & mask < a_w { 1u64 } else { 0u64 };
let r = round_increment(sum & mask, 1, mode, (sum >> 1) & 1);
let shifted = (carry << (sew.bits() as u32 - 1)) | ((sum & mask) >> 1);
(shifted.wrapping_add(r)) & mask
}
#[inline(always)]
pub fn avg_add(a: u64, b: u64, sew: Vsew, mode: Vxrm) -> u64 {
let sa = sign_extend(a, sew);
let sb = sign_extend(b, sew);
let sum = (sa as i128).wrapping_add(sb as i128);
let r = match mode {
Vxrm::Rnu => (sum & 1).cast_unsigned() as u64,
Vxrm::Rne => {
let result_lsb = ((sum >> 1) & 1).cast_unsigned() as u64;
((sum & 1).cast_unsigned() as u64) & result_lsb
}
Vxrm::Rdn => 0,
Vxrm::Rod => {
let result_lsb = (sum >> 1) & 1;
if result_lsb == 0 && (sum & 1) != 0 {
1
} else {
0
}
}
};
let result = (sum >> 1) + r as i128;
(result as i64).cast_unsigned() & sew_mask(sew)
}
#[inline(always)]
pub fn avg_subu(a: u64, b: u64, sew: Vsew, mode: Vxrm) -> u64 {
let mask = sew_mask(sew);
let a_w = a & mask;
let b_w = b & mask;
let diff = a_w.wrapping_sub(b_w);
let borrow = if a_w < b_w { 1u64 } else { 0u64 };
let r = round_increment(diff & mask, 1, mode, (diff >> 1) & 1);
let sign_fill = borrow.wrapping_neg(); let shifted = (sign_fill << (sew.bits() as u32 - 1)) | ((diff & mask) >> 1);
(shifted.wrapping_add(r)) & mask
}
#[inline(always)]
pub fn avg_sub(a: u64, b: u64, sew: Vsew, mode: Vxrm) -> u64 {
let sa = sign_extend(a, sew);
let sb = sign_extend(b, sew);
let diff = (sa as i128).wrapping_sub(sb as i128);
let r = match mode {
Vxrm::Rnu => (diff & 1).cast_unsigned() as u64,
Vxrm::Rne => {
let result_lsb = ((diff >> 1) & 1).cast_unsigned() as u64;
((diff & 1).cast_unsigned() as u64) & result_lsb
}
Vxrm::Rdn => 0,
Vxrm::Rod => {
let result_lsb = (diff >> 1) & 1;
if result_lsb == 0 && (diff & 1) != 0 {
1
} else {
0
}
}
};
let result = (diff >> 1) + r as i128;
(result as i64).cast_unsigned() & sew_mask(sew)
}
#[inline(always)]
pub fn smul(a: u64, b: u64, sew: Vsew, mode: Vxrm, vxsat: &mut bool) -> u64 {
let min_sew = i64::MIN >> (i64::BITS - u32::from(sew.bits()));
let max_sew = i64::MAX >> (i64::BITS - u32::from(sew.bits()));
let sa = i128::from(sign_extend(a, sew));
let sb = i128::from(sign_extend(b, sew));
if sa == i128::from(min_sew) && sb == i128::from(min_sew) {
*vxsat = true;
return max_sew.cast_unsigned() & sew_mask(sew);
}
let product = sa * sb;
let doubled = product << 1;
let shift = u32::from(sew.bits());
let low_bits = (doubled.cast_unsigned() & u128::from(sew_mask(sew))) as u64;
let truncated = doubled >> shift;
let r = round_increment(
low_bits,
shift.min(64),
mode,
(truncated.cast_unsigned() as u64) & 1,
);
let result = (truncated as i64).wrapping_add(r.cast_signed());
if result < min_sew {
*vxsat = true;
min_sew.cast_unsigned() & sew_mask(sew)
} else if result > max_sew {
*vxsat = true;
max_sew.cast_unsigned() & sew_mask(sew)
} else {
result.cast_unsigned() & sew_mask(sew)
}
}
#[inline(always)]
pub fn nclipu(vs2_elem: u64, shamt: u32, sew: Vsew, mode: Vxrm, vxsat: &mut bool) -> u64 {
let shifted = rounded_srl(vs2_elem, shamt, mode);
let max_dst = sew_mask(sew);
if shifted > max_dst {
*vxsat = true;
max_dst
} else {
shifted & max_dst
}
}
#[inline(always)]
pub fn nclip(vs2_elem: u64, shamt: u32, sew: Vsew, mode: Vxrm, vxsat: &mut bool) -> u64 {
let double_sew_bits = sew.bits() * 2;
let shift_amt = i64::BITS - u32::from(double_sew_bits);
let signed_wide = (vs2_elem.cast_signed() << shift_amt) >> shift_amt;
let low_bits = signed_wide.cast_unsigned()
& if double_sew_bits == 64 {
u64::MAX
} else {
(1u64 << double_sew_bits) - 1
};
let truncated = signed_wide >> shamt;
let r = round_increment(low_bits, shamt, mode, (truncated.cast_unsigned()) & 1);
let rounded = truncated.wrapping_add(r.cast_signed());
let min_dst = i64::MIN >> (i64::BITS - u32::from(sew.bits()));
let max_dst = i64::MAX >> (i64::BITS - u32::from(sew.bits()));
if rounded < min_dst {
*vxsat = true;
min_dst.cast_unsigned() & sew_mask(sew)
} else if rounded > max_dst {
*vxsat = true;
max_dst.cast_unsigned() & sew_mask(sew)
} else {
rounded.cast_unsigned() & sew_mask(sew)
}
}
#[inline(always)]
pub unsafe fn read_wide_element_u64<const VLENB: usize>(
vreg: &[[u8; VLENB]; 32],
base_reg: usize,
elem_i: u32,
sew: Vsew,
) -> u64 {
let double_sew_bytes = usize::from(sew.bytes()) * 2;
let elems_per_reg = VLENB / double_sew_bytes;
let reg_off = elem_i as usize / elems_per_reg;
let byte_off = (elem_i as usize % elems_per_reg) * double_sew_bytes;
let reg = unsafe { vreg.get_unchecked(base_reg + reg_off) };
let src = unsafe { reg.get_unchecked(byte_off..byte_off + double_sew_bytes) };
let mut buf = [0u8; 8];
unsafe { buf.get_unchecked_mut(..double_sew_bytes) }.copy_from_slice(src);
u64::from_le_bytes(buf)
}
#[inline(always)]
#[expect(clippy::too_many_arguments, reason = "Internal API")]
#[doc(hidden)]
pub unsafe fn execute_fixed_point_op<Reg, ExtState, Memory, PC, IH, CustomError, F>(
state: &mut InterpreterState<Reg, ExtState, Memory, PC, IH, CustomError>,
vd: VReg,
vs2: VReg,
src: OpSrc,
vm: bool,
vl: u32,
vstart: u32,
sew: Vsew,
op: F,
) where
Reg: Register,
[(); Reg::N]:,
ExtState: VectorRegistersExt<Reg, CustomError>,
[(); ExtState::ELEN as usize]:,
[(); ExtState::VLEN as usize]:,
[(); ExtState::VLENB as usize]:,
Memory: VirtualMemory,
PC: ProgramCounter<Reg::Type, Memory, CustomError>,
CustomError: fmt::Debug,
F: Fn(u64, u64, Vsew, Vxrm, &mut bool) -> u64,
{
let vxrm = state.ext_state.vxrm();
let mask_buf = unsafe { snapshot_mask(state.ext_state.read_vreg(), vm, vl) };
let vd_base = vd.bits();
let vs2_base = vs2.bits();
let mut any_sat = false;
for i in vstart..vl {
if !mask_bit(&mask_buf, i) {
continue;
}
let a =
unsafe { read_element_u64(state.ext_state.read_vreg(), usize::from(vs2_base), i, sew) };
let b = match &src {
OpSrc::Vreg(vs1_base) => {
unsafe {
read_element_u64(state.ext_state.read_vreg(), usize::from(*vs1_base), i, sew)
}
}
OpSrc::Scalar(val) => *val,
};
let result = op(a, b, sew, vxrm, &mut any_sat);
unsafe {
write_element_u64(state.ext_state.write_vreg(), vd_base, i, sew, result);
}
}
if any_sat {
state.ext_state.set_vxsat(true);
}
state.ext_state.mark_vs_dirty();
state.ext_state.reset_vstart();
}
#[inline(always)]
#[expect(clippy::too_many_arguments, reason = "Internal API")]
#[doc(hidden)]
pub unsafe fn execute_narrowing_clip_op<Reg, ExtState, Memory, PC, IH, CustomError, F>(
state: &mut InterpreterState<Reg, ExtState, Memory, PC, IH, CustomError>,
vd: VReg,
vs2: VReg,
src: OpSrc,
vm: bool,
vl: u32,
vstart: u32,
sew: Vsew,
op: F,
) where
Reg: Register,
[(); Reg::N]:,
ExtState: VectorRegistersExt<Reg, CustomError>,
[(); ExtState::ELEN as usize]:,
[(); ExtState::VLEN as usize]:,
[(); ExtState::VLENB as usize]:,
Memory: VirtualMemory,
PC: ProgramCounter<Reg::Type, Memory, CustomError>,
CustomError: fmt::Debug,
F: Fn(u64, u32, Vsew, Vxrm, &mut bool) -> u64,
{
let vxrm = state.ext_state.vxrm();
let mask_buf = unsafe { snapshot_mask(state.ext_state.read_vreg(), vm, vl) };
let vd_base = vd.bits();
let vs2_base = vs2.bits();
let mut any_sat = false;
let shamt_mask = u64::from(sew.bits() * 2 - 1);
for i in vstart..vl {
if !mask_bit(&mask_buf, i) {
continue;
}
let wide_a = unsafe {
read_wide_element_u64(state.ext_state.read_vreg(), usize::from(vs2_base), i, sew)
};
let shamt = match &src {
OpSrc::Vreg(vs1_base) => {
let raw = unsafe {
read_element_u64(state.ext_state.read_vreg(), usize::from(*vs1_base), i, sew)
};
(raw & shamt_mask) as u32
}
OpSrc::Scalar(val) => (*val & shamt_mask) as u32,
};
let result = op(wide_a, shamt, sew, vxrm, &mut any_sat);
unsafe {
write_element_u64(state.ext_state.write_vreg(), vd_base, i, sew, result);
}
}
if any_sat {
state.ext_state.set_vxsat(true);
}
state.ext_state.mark_vs_dirty();
state.ext_state.reset_vstart();
}
#[inline(always)]
pub fn check_narrowing_sew<Reg, ExtState, Memory, PC, IH, CustomError>(
state: &InterpreterState<Reg, ExtState, Memory, PC, IH, CustomError>,
sew: Vsew,
) -> Result<(), ExecutionError<Reg::Type, CustomError>>
where
Reg: Register,
[(); Reg::N]:,
PC: ProgramCounter<Reg::Type, Memory, CustomError>,
{
if sew.bits() > 32 {
return Err(ExecutionError::IllegalInstruction {
address: state.instruction_fetcher.old_pc(INSTRUCTION_SIZE),
});
}
Ok(())
}
#[inline(always)]
pub fn check_vs2_narrowing_alignment<Reg, ExtState, Memory, PC, IH, CustomError>(
state: &InterpreterState<Reg, ExtState, Memory, PC, IH, CustomError>,
vs2: VReg,
group_regs: u8,
) -> Result<(), ExecutionError<Reg::Type, CustomError>>
where
Reg: Register,
[(); Reg::N]:,
PC: ProgramCounter<Reg::Type, Memory, CustomError>,
{
let double_group = group_regs.saturating_mul(2);
let vs2_idx = vs2.bits();
if !vs2_idx.is_multiple_of(double_group) || vs2_idx + double_group > 32 {
return Err(ExecutionError::IllegalInstruction {
address: state.instruction_fetcher.old_pc(INSTRUCTION_SIZE),
});
}
Ok(())
}