rvsim-core 2.0.0

A cycle-level RISC-V 64-bit system simulator.
//! The architectural effects of retiring an instruction.
//!
//! Shared by the in-order and out-of-order commit stages. Each function
//! changes only what it is given.

use crate::arch::Hart;
use crate::arch::trap::irq_to_trap;
use crate::exec::compute::vector::shadow::VectorWrites;
use crate::isa::csr;
use crate::isa::privileged::{PrivilegeMode, Trap};
use crate::isa::reg::RegIdx;
use crate::isa::rvv::VectorConfig;

/// Interrupts in the privileged spec's fixed decreasing priority order (MEI,
/// MSI, MTI, SEI, SSI, STI), as `(mip bit, mie bit)`. `mideleg` delegates
/// each interrupt at its `mip` bit position.
const INTERRUPT_PRIORITY: [(u64, u64); 6] = [
    (csr::MIP_MEIP, csr::MIE_MEIP),
    (csr::MIP_MSIP, csr::MIE_MSIP),
    (csr::MIP_MTIP, csr::MIE_MTIE),
    (csr::MIP_SEIP, csr::MIE_SEIP),
    (csr::MIP_SSIP, csr::MIE_SSIP),
    (csr::MIP_STIP, csr::MIE_STIE),
];

/// The interrupt `hart` takes now, if any: the highest-priority one
/// pending, enabled and not masked at its privilege level.
#[must_use]
pub fn pending_interrupt(hart: &Hart) -> Option<Trap> {
    let csrs = &hart.csrs;
    let m_global_ie = (csrs.mstatus & csr::MSTATUS_MIE) != 0;
    let s_global_ie = (csrs.mstatus & csr::MSTATUS_SIE) != 0;
    let privilege = hart.privilege;

    let check = |bit: u64, enable_bit: u64| -> Option<Trap> {
        if (csrs.mip & bit) == 0 || (csrs.mie & enable_bit) == 0 {
            return None;
        }
        let delegated = (csrs.mideleg & bit) != 0;
        let target = if delegated { PrivilegeMode::Supervisor } else { PrivilegeMode::Machine };
        let below = privilege.to_u8() < target.to_u8();
        let enabled_here = privilege == target
            && match target {
                PrivilegeMode::Machine => m_global_ie,
                PrivilegeMode::Supervisor => s_global_ie,
                PrivilegeMode::User => false,
            };
        (below || enabled_here).then(|| irq_to_trap(bit))
    };

    // Interrupts destined for M-mode are taken before any destined for S-mode.
    [false, true].into_iter().find_map(|to_supervisor| {
        INTERRUPT_PRIORITY
            .iter()
            .filter(|&&(bit, _)| (csrs.mideleg & bit != 0) == to_supervisor)
            .find_map(|&(bit, enable_bit)| check(bit, enable_bit))
    })
}

/// Writes an integer register; writes to x0 are dropped.
pub const fn write_int(hart: &mut Hart, rd: RegIdx, value: u64) {
    if !rd.is_zero() {
        hart.regs.write(rd, value);
    }
}

/// Writes a floating-point register and marks the FP state dirty.
pub const fn write_fp(hart: &mut Hart, rd: RegIdx, value: u64) {
    hart.regs.write_f(rd, value);
    mark_fp_dirty(hart);
}

/// Applies a vector instruction's register writes.
pub fn apply_vector_writes(hart: &mut Hart, writes: &VectorWrites) {
    writes.apply(hart.regs.vpr_mut());
    mark_vector_retired(hart);
}

/// Marks the vector state dirty and clears `vstart`, as every vector
/// instruction that completes does.
pub const fn mark_vector_retired(hart: &mut Hart) {
    hart.csrs.mstatus = (hart.csrs.mstatus & !csr::MSTATUS_VS) | csr::MSTATUS_VS_DIRTY;
    hart.csrs.vstart = 0;
}

/// Accrues IEEE exception flags into `fflags`.
pub const fn accrue_fp_flags(hart: &mut Hart, flags: u8) {
    if flags != 0 {
        hart.csrs.fflags |= flags as u64;
        mark_fp_dirty(hart);
    }
}

/// Applies what a `vset{i}vl{i}` computed.
pub const fn apply_vector_config(hart: &mut Hart, config: VectorConfig) {
    hart.csrs.vtype = config.vtype;
    hart.csrs.vl = config.vl;
    mark_vector_retired(hart);
}

/// Retires a WFI; returns whether the hart now waits.
///
/// It waits when some interrupt is enabled or pending. With nothing enabled
/// or pending it would never wake, so it acts as a no-op instead, as
/// `OpenSBI`'s early boot relies on.
pub const fn wfi(hart: &mut Hart) -> bool {
    let waits = hart.csrs.mie != 0 || hart.csrs.mip != 0;
    if waits {
        hart.wfi_waiting = true;
    }
    waits
}

const fn mark_fp_dirty(hart: &mut Hart) {
    hart.csrs.mstatus = (hart.csrs.mstatus & !csr::MSTATUS_FS) | csr::MSTATUS_FS_DIRTY;
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::Config;

    #[test]
    fn no_interrupt_is_taken_when_none_is_pending() {
        let config = Config::default();
        let mut sys = crate::system::SystemState::build(&config, "");
        let state = sys.core_ctx(0);

        assert!(pending_interrupt(state.hart).is_none());
    }

    #[test]
    fn a_pending_enabled_machine_interrupt_is_taken_in_machine_mode() {
        let config = Config::default();
        let mut sys = crate::system::SystemState::build(&config, "");
        let state = sys.core_ctx(0);

        state.hart.csrs.mip = csr::MIP_MEIP;
        state.hart.csrs.mie = csr::MIE_MEIP;
        state.hart.csrs.mstatus |= csr::MSTATUS_MIE;
        state.hart.privilege = PrivilegeMode::Machine;

        assert_eq!(pending_interrupt(state.hart), Some(Trap::MachineExternalInterrupt));
    }

    #[test]
    fn a_delegated_interrupt_is_taken_in_supervisor_mode() {
        let config = Config::default();
        let mut sys = crate::system::SystemState::build(&config, "");
        let state = sys.core_ctx(0);

        state.hart.csrs.mip = csr::MIP_SEIP;
        state.hart.csrs.mie = csr::MIE_SEIP;
        state.hart.csrs.mstatus |= csr::MSTATUS_SIE;
        state.hart.csrs.mideleg |= csr::MIP_SEIP;
        state.hart.privilege = PrivilegeMode::Supervisor;

        assert_eq!(pending_interrupt(state.hart), Some(Trap::SupervisorExternalInterrupt));
    }
}