use crate::arch::Hart;
use crate::arch::translation::SfenceVmaInfo;
use crate::exec::cbo;
use crate::exec::compute::alu;
use crate::exec::compute::fpu;
use crate::exec::compute::vector::fpu::is_vec_fp;
use crate::exec::execute::fpu::host::{
clear_host_fp_flags, read_host_fp_flags, restore_host_round_mode, set_host_round_mode,
};
use crate::exec::execute::fpu::nan_handling::box_f32;
use crate::exec::inst::Inst;
use crate::exec::signals::{OpASrc, OpBSrc};
use crate::exec::state::ArchState;
use crate::isa::csr;
use crate::isa::csr::CsrAddr;
use crate::isa::encoding::rv64i::{funct3, opcodes};
use crate::isa::fp::RoundingMode;
use crate::isa::op::{AluOp, CsrOp, SystemOp, VectorOp};
use crate::isa::privileged::{PrivilegeMode, Trap};
use crate::trace_csr;
const FUNCT3_SHIFT: u32 = 12;
const FUNCT3_MASK: u32 = 0x7;
const JALR_ALIGNMENT_MASK: u64 = !1;
const MSTATUS_TVM_BIT: u32 = 20;
const MSTATUS_TW_BIT: u32 = 21;
const MSTATUS_TSR_BIT: u32 = 22;
pub const fn operands(inst: &Inst) -> (u64, u64) {
let op_a = match inst.ctrl.a_src {
OpASrc::Reg1 => inst.rv1,
OpASrc::Pc => inst.pc,
OpASrc::Zero => 0,
};
let op_b = match inst.ctrl.b_src {
OpBSrc::Reg2 => inst.rv2,
OpBSrc::Imm => inst.imm as u64,
OpBSrc::Zero => 0,
};
(op_a, op_b)
}
const fn fs_off(hart: &Hart) -> bool {
hart.csrs.mstatus & csr::MSTATUS_FS == 0
}
const fn vs_off(hart: &Hart) -> bool {
hart.csrs.mstatus & csr::MSTATUS_VS == 0
}
pub const fn unit_disabled(hart: &Hart, inst: &Inst) -> bool {
let is_vector = !matches!(inst.ctrl.vec_op, VectorOp::None);
let is_fp = inst.ctrl.fp_reg_write
|| inst.ctrl.rs1_fp
|| inst.ctrl.rs2_fp
|| inst.ctrl.rs3_fp
|| is_vector_fp(inst.ctrl.vec_op);
(is_vector && vs_off(hart)) || (is_fp && fs_off(hart))
}
const fn is_vector_fp(op: VectorOp) -> bool {
is_vec_fp(op)
|| matches!(
op,
VectorOp::VFRedMax
| VectorOp::VFRedMin
| VectorOp::VFRedOSum
| VectorOp::VFRedUSum
| VectorOp::VFWRedOSum
| VectorOp::VFWRedUSum
)
}
fn csr_unit_disabled(state: &impl ArchState, addr: CsrAddr) -> bool {
let fp_csr = addr == csr::FFLAGS || addr == csr::FRM || addr == csr::FCSR;
let vector_csr = addr == csr::VSTART
|| addr == csr::VXSAT
|| addr == csr::VXRM
|| addr == csr::VCSR
|| addr == csr::VL
|| addr == csr::VTYPE
|| addr == csr::VLENB;
(fp_csr && fs_off(state.hart())) || (vector_csr && vs_off(state.hart()))
}
const fn mstatus_bit(hart: &Hart, bit: u32) -> bool {
(hart.csrs.mstatus >> bit) & 1 != 0
}
pub const fn privileged_op_fault(hart: &Hart, inst: &Inst) -> Option<Trap> {
let privilege = hart.privilege;
let illegal = match inst.ctrl.system_op {
SystemOp::Mret => !matches!(privilege, PrivilegeMode::Machine),
SystemOp::Sret => match privilege {
PrivilegeMode::User => true,
PrivilegeMode::Supervisor => mstatus_bit(hart, MSTATUS_TSR_BIT),
PrivilegeMode::Machine => false,
},
SystemOp::Wfi => match privilege {
PrivilegeMode::User => true,
PrivilegeMode::Supervisor => mstatus_bit(hart, MSTATUS_TW_BIT),
PrivilegeMode::Machine => false,
},
SystemOp::SfenceVma => {
matches!(privilege, PrivilegeMode::Supervisor) && mstatus_bit(hart, MSTATUS_TVM_BIT)
}
_ => false,
};
if illegal { Some(Trap::IllegalInstruction(inst.bits)) } else { None }
}
pub const fn ecall_trap(hart: &Hart) -> Trap {
match hart.privilege {
PrivilegeMode::User => Trap::EnvironmentCallFromUMode,
PrivilegeMode::Supervisor => Trap::EnvironmentCallFromSMode,
PrivilegeMode::Machine => Trap::EnvironmentCallFromMMode,
}
}
#[derive(Clone, Debug)]
pub enum SystemEffect {
NotSystem,
Trap(Trap),
AtRetire,
SfenceVma(SfenceVmaInfo),
Cbo,
Csr(CsrAccess),
}
pub fn system_effect(state: &impl ArchState, inst: &Inst) -> SystemEffect {
if let Some(trap) = privileged_op_fault(state.hart(), inst) {
return SystemEffect::Trap(trap);
}
match inst.ctrl.system_op {
SystemOp::None | SystemOp::Fence => SystemEffect::NotSystem,
SystemOp::FenceI | SystemOp::Mret | SystemOp::Sret | SystemOp::Wfi => {
SystemEffect::AtRetire
}
SystemOp::SfenceVma => SystemEffect::SfenceVma(SfenceVmaInfo {
rs1_idx: inst.rs1,
rs2_idx: inst.rs2,
rs1_val: inst.rv1,
rs2_val: inst.rv2,
}),
SystemOp::CboZero | SystemOp::CboInval | SystemOp::CboClean | SystemOp::CboFlush => {
let hart = state.hart();
match cbo::gate(&hart.csrs, hart.privilege, inst.ctrl.system_op, inst.bits) {
Ok(_) => SystemEffect::Cbo,
Err(trap) => SystemEffect::Trap(trap),
}
}
SystemOp::Ecall => SystemEffect::Trap(ecall_trap(state.hart())),
SystemOp::Csr => match csr_access(state, inst) {
Ok(access) => SystemEffect::Csr(access),
Err(trap) => SystemEffect::Trap(trap),
},
}
}
#[derive(Clone, Copy, Debug)]
pub struct CsrWrite {
pub addr: CsrAddr,
pub old: u64,
pub new: u64,
}
#[derive(Clone, Debug)]
pub struct CsrAccess {
pub old: u64,
pub update: Option<CsrWrite>,
}
pub fn csr_access(state: &impl ArchState, inst: &Inst) -> Result<CsrAccess, Trap> {
let addr = inst.ctrl.csr_addr;
let illegal = Trap::IllegalInstruction(inst.bits);
let writes = csr_op_writes(inst);
let privilege = state.hart().privilege;
let satp_trapped = addr == csr::SATP
&& matches!(privilege, PrivilegeMode::Supervisor)
&& mstatus_bit(state.hart(), MSTATUS_TVM_BIT);
if satp_trapped
|| csr_unit_disabled(state, addr)
|| counter_access_denied(state, inst)
|| !state.hart().is_valid_csr(addr)
|| u32::from(privilege.to_u8()) < addr.privilege_level() as u32
|| (addr.is_read_only() && writes)
{
return Err(illegal);
}
let old = state.csr_read(addr);
let base = state.csr_read_for_update(addr);
let src = match inst.ctrl.csr_op {
CsrOp::Rwi | CsrOp::Rsi | CsrOp::Rci => u64::from(inst.rs1.as_u8() & 0x1f),
_ => inst.rv1,
};
let new = match inst.ctrl.csr_op {
CsrOp::Rw | CsrOp::Rwi => src,
CsrOp::Rs | CsrOp::Rsi => base | src,
CsrOp::Rc | CsrOp::Rci => base & !src,
CsrOp::None => old,
};
trace_csr!(state.tracing();
op = "write-deferred",
pc = %crate::common::trace::Hex(inst.pc),
csr_addr = %crate::common::trace::Hex32(addr.as_u32()),
csr_op = ?inst.ctrl.csr_op,
old_val = %crate::common::trace::Hex(old),
new_val = %crate::common::trace::Hex(new),
writes,
"EX: CSR access"
);
let update = writes.then_some(CsrWrite { addr, old, new });
Ok(CsrAccess { old, update })
}
const fn csr_op_writes(inst: &Inst) -> bool {
match inst.ctrl.csr_op {
CsrOp::Rw | CsrOp::Rwi => true,
CsrOp::Rs | CsrOp::Rc => !inst.rs1.is_zero(),
CsrOp::Rsi | CsrOp::Rci => (inst.rs1.as_u8() & 0x1f) != 0,
CsrOp::None => false,
}
}
fn counter_access_denied(state: &impl ArchState, inst: &Inst) -> bool {
let addr = inst.ctrl.csr_addr;
let bit = if addr == csr::CYCLE {
0
} else if addr == csr::TIME {
1
} else if addr == csr::INSTRET {
2
} else {
return false;
};
let mask = 1u64 << bit;
let csrs = &state.hart().csrs;
match state.hart().privilege {
PrivilegeMode::Supervisor => csrs.mcounteren & mask == 0,
PrivilegeMode::User => csrs.mcounteren & mask == 0 || csrs.scounteren & mask == 0,
PrivilegeMode::Machine => false,
}
}
pub fn evaluate(state: &impl ArchState, inst: &Inst, op_a: u64, op_b: u64) -> (u64, u8) {
let fp_rm = inst.ctrl.fp_rm.or_else(|| RoundingMode::from_bits(state.hart().csrs.frm as u8));
compute_alu(inst.ctrl.alu, op_a, op_b, inst.rv3, inst.ctrl.is_f16, inst.ctrl.is_rv32, fp_rm)
}
pub const fn check_target_alignment(hart: &Hart, target: u64) -> Result<(), Trap> {
if target & crate::arch::csr::ialign_low_bits(hart.csrs.misa) == 0 {
return Ok(());
}
Err(Trap::InstructionAddressMisaligned(target))
}
#[must_use]
pub const fn branch_taken(inst: u32, op_a: u64, op_b: u64) -> bool {
match (inst >> FUNCT3_SHIFT) & FUNCT3_MASK {
funct3::BEQ => op_a == op_b,
funct3::BNE => op_a != op_b,
funct3::BLT => (op_a as i64) < (op_b as i64),
funct3::BGE => (op_a as i64) >= (op_b as i64),
funct3::BLTU => op_a < op_b,
funct3::BGEU => op_a >= op_b,
_ => false,
}
}
#[must_use]
pub const fn is_jalr(inst: &Inst) -> bool {
(inst.bits & crate::isa::instruction::OPCODE_MASK) == opcodes::OP_JALR
}
#[must_use]
pub const fn jump_target(inst: &Inst) -> u64 {
if is_jalr(inst) {
inst.rv1.wrapping_add(inst.imm as u64) & JALR_ALIGNMENT_MASK
} else {
inst.pc.wrapping_add(inst.imm as u64)
}
}
fn on_host_fpu(rm: RoundingMode, convert: impl FnOnce() -> u64) -> (u64, u8) {
let saved = set_host_round_mode(rm);
clear_host_fp_flags();
let value = std::hint::black_box(convert());
let flags = read_host_fp_flags();
restore_host_round_mode(saved);
(value, flags.bits())
}
pub fn compute_alu(
alu_op: AluOp,
op_a: u64,
op_b: u64,
op_c: u64,
is_f16: bool,
is_rv32: bool,
fp_rm: Option<RoundingMode>,
) -> (u64, u8) {
use crate::exec::compute::fpu::half::{box_f16, f16_to_f32, unbox_f16};
use crate::exec::compute::fpu::nan_handling::{
box_f32_canon, canonicalize_f64_bits, unbox_f32,
};
use std::hint::black_box;
let rm = fp_rm.unwrap_or(RoundingMode::Rne);
match alu_op {
AluOp::FCvtSW if !is_f16 => on_host_fpu(rm, || {
let v = black_box(op_a as i32);
if is_rv32 { box_f32(v as f32) } else { f64::from(v).to_bits() }
}),
AluOp::FCvtSWU if !is_f16 => on_host_fpu(rm, || {
let v = black_box(op_a as u32);
if is_rv32 { box_f32(v as f32) } else { f64::from(v).to_bits() }
}),
AluOp::FCvtSL if !is_f16 => on_host_fpu(rm, || {
let v = black_box(op_a as i64);
if is_rv32 { box_f32(v as f32) } else { (v as f64).to_bits() }
}),
AluOp::FCvtSLU if !is_f16 => on_host_fpu(rm, || {
let v = black_box(op_a);
if is_rv32 { box_f32(v as f32) } else { (v as f64).to_bits() }
}),
AluOp::FCvtSD if !is_f16 => {
on_host_fpu(rm, || box_f32_canon(black_box(f64::from_bits(op_a)) as f32))
}
AluOp::FCvtDS if !is_f16 => {
on_host_fpu(rm, || canonicalize_f64_bits(f64::from(black_box(unbox_f32(op_a)))))
}
AluOp::FCvtSH if !is_f16 => on_host_fpu(rm, || box_f32_canon(f16_to_f32(unbox_f16(op_a)))),
AluOp::FCvtDH if !is_f16 => on_host_fpu(rm, || {
canonicalize_f64_bits(f64::from(black_box(f16_to_f32(unbox_f16(op_a)))))
}),
AluOp::FMvToF => {
let value = if is_f16 {
box_f16(op_a as u16)
} else if is_rv32 {
box_f32(f32::from_bits(op_a as u32))
} else {
op_a
};
(value, 0)
}
_ if is_fp_op(alu_op) => {
let (result, fp_flags) =
fpu::execute_full_rm(alu_op, op_a, op_b, op_c, is_f16, is_rv32, rm);
(result, fp_flags.bits())
}
_ => (alu::execute(alu_op, op_a, op_b, op_c, is_rv32), 0),
}
}
const fn is_fp_op(alu_op: AluOp) -> bool {
matches!(
alu_op,
AluOp::FAdd
| AluOp::FSub
| AluOp::FMul
| AluOp::FDiv
| AluOp::FSqrt
| AluOp::FMin
| AluOp::FMax
| AluOp::FMAdd
| AluOp::FMSub
| AluOp::FNMAdd
| AluOp::FNMSub
| AluOp::FSgnJ
| AluOp::FSgnJN
| AluOp::FSgnJX
| AluOp::FEq
| AluOp::FLt
| AluOp::FLe
| AluOp::FClass
| AluOp::FCvtWS
| AluOp::FCvtWUS
| AluOp::FCvtLS
| AluOp::FCvtLUS
| AluOp::FCvtSW
| AluOp::FCvtSWU
| AluOp::FCvtSL
| AluOp::FCvtSLU
| AluOp::FCvtSD
| AluOp::FCvtDS
| AluOp::FCvtSH
| AluOp::FCvtHS
| AluOp::FCvtDH
| AluOp::FCvtHD
| AluOp::FMvToX
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn int_to_float_conversions_are_exact_for_small_values() {
let rne = Some(RoundingMode::Rne);
let (res, flags) = compute_alu(AluOp::FCvtSW, 1, 0, 0, false, false, rne);
assert_eq!(res, (1.0f64).to_bits());
assert_eq!(flags, 0);
let (res, flags) = compute_alu(AluOp::FCvtSW, 1, 0, 0, false, true, rne);
assert_eq!(res, 0xFFFF_FFFF_0000_0000 | u64::from((1.0f32).to_bits()));
assert_eq!(flags, 0);
}
#[test]
fn move_to_float_passes_the_bits_through() {
let (res, flags) =
compute_alu(AluOp::FMvToF, 42, 0, 0, false, false, Some(RoundingMode::Rne));
assert_eq!(res, 42);
assert_eq!(flags, 0);
}
#[test]
fn inexact_conversion_raises_the_inexact_flag() {
let (_, flags) = compute_alu(
AluOp::FCvtSL,
(1u64 << 60) + 1,
0,
0,
false,
true,
Some(RoundingMode::Rne),
);
assert_ne!(flags, 0);
}
}