use crate::common::PhysAddr;
use crate::config::Config;
use crate::isa::privileged::{PrivilegeMode, Trap};
use crate::isa::reg::RegIdx;
use crate::system::SystemState;
fn create_test_cpu() -> SystemState {
let config = Config::default();
let mut state = SystemState::build(&config, "");
state.direct_mode = false;
state
}
#[test]
fn test_trap_clears_load_reservation() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.set_reservation(PhysAddr::new(0x8000_0000));
state.trap(&Trap::IllegalInstruction(0), state.hart.pc);
assert!(!state.check_reservation(PhysAddr::new(0x8000_0000)));
}
#[test]
fn test_trap_direct_mode_illegal_instruction_zero_exits() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.direct_mode = true;
state.exit_signal.store(u64::MAX, std::sync::atomic::Ordering::Relaxed);
state.trap(&Trap::IllegalInstruction(0), state.hart.pc);
assert_eq!(state.check_exit(), Some(0));
}
#[test]
fn test_trap_direct_mode_other_exceptions_set_exit_code_1() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.direct_mode = true;
state.exit_signal.store(u64::MAX, std::sync::atomic::Ordering::Relaxed);
state.trap(&Trap::LoadAddressMisaligned(0x8000_0001), state.hart.pc);
assert_eq!(state.check_exit(), Some(1));
}
#[test]
fn test_trap_direct_mode_ecall_from_umode_processed() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.direct_mode = true;
state.hart.privilege = PrivilegeMode::User;
state.exit_signal.store(u64::MAX, std::sync::atomic::Ordering::Relaxed);
state.hart.csrs.mtvec = 0x8000_0000;
state.trap(&Trap::EnvironmentCallFromUMode, state.hart.pc);
}
#[test]
fn test_trap_sets_mcause_without_interrupt_bit_for_exceptions() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.trap(&Trap::IllegalInstruction(0), state.hart.pc);
let mcause = state.hart.csrs.mcause;
assert_eq!(mcause & (1u64 << 63), 0);
}
#[test]
fn test_trap_exceptions_dont_set_interrupt_bit() {
let exceptions = vec![
Trap::InstructionAddressMisaligned(0),
Trap::InstructionAccessFault(0),
Trap::IllegalInstruction(0),
Trap::Breakpoint(0),
Trap::LoadAddressMisaligned(0),
Trap::LoadAccessFault(0),
Trap::StoreAddressMisaligned(0),
Trap::StoreAccessFault(0),
Trap::EnvironmentCallFromUMode,
Trap::EnvironmentCallFromSMode,
Trap::EnvironmentCallFromMMode,
];
for exception in exceptions {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.trap(&exception, state.hart.pc);
assert_eq!(state.hart.csrs.mcause & (1u64 << 63), 0);
}
}
#[test]
fn test_trap_ecall_from_all_modes() {
let ecalls = vec![
Trap::EnvironmentCallFromUMode,
Trap::EnvironmentCallFromSMode,
Trap::EnvironmentCallFromMMode,
];
for ecall in ecalls {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.trap(&ecall, state.hart.pc);
assert_eq!(state.hart.csrs.mcause & (1u64 << 63), 0);
}
}
#[test]
fn test_trap_page_faults() {
let page_faults = vec![
Trap::InstructionPageFault(0x1000_0000),
Trap::LoadPageFault(0x2000_0000),
Trap::StorePageFault(0x3000_0000),
];
for fault_trap in page_faults {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.trap(&fault_trap, state.hart.pc);
assert_eq!(state.hart.csrs.mcause & (1u64 << 63), 0);
}
}
#[test]
fn test_trap_double_fault_detection() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
let handler_pc = 0x8000_0000;
state.hart.csrs.mtvec = handler_pc;
state.trap(&Trap::IllegalInstruction(0), handler_pc);
assert_eq!(state.check_exit(), None);
assert_eq!(state.hart.pc, handler_pc); }
#[test]
fn test_trap_interrupts_set_interrupt_bit() {
let interrupts = vec![
Trap::UserSoftwareInterrupt,
Trap::SupervisorSoftwareInterrupt,
Trap::MachineSoftwareInterrupt,
Trap::SupervisorTimerInterrupt,
Trap::MachineTimerInterrupt,
Trap::UserExternalInterrupt,
Trap::SupervisorExternalInterrupt,
Trap::MachineExternalInterrupt,
];
for interrupt in interrupts {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
state.trap(&interrupt, state.hart.pc);
assert_ne!(state.hart.csrs.mcause & (1u64 << 63), 0);
}
}
#[test]
fn test_trap_machine_timer_interrupt() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
state.trap(&Trap::MachineTimerInterrupt, state.hart.pc);
assert_ne!(state.hart.csrs.mcause & (1u64 << 63), 0);
}
#[test]
fn test_trap_supervisor_timer_interrupt() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.csrs.stvec = 0x8000_1000;
state.hart.csrs.mideleg = 1 << 5; state.hart.pc = 0x8000_2000;
state.trap(&Trap::SupervisorTimerInterrupt, state.hart.pc);
assert_eq!(state.hart.privilege, PrivilegeMode::Supervisor);
}
#[test]
fn test_trap_machine_software_interrupt() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
state.trap(&Trap::MachineSoftwareInterrupt, state.hart.pc);
assert_ne!(state.hart.csrs.mcause & (1u64 << 63), 0);
}
#[test]
fn test_trap_supervisor_software_interrupt() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.csrs.stvec = 0x8000_1000;
state.hart.csrs.mideleg = 1 << 1; state.hart.pc = 0x8000_2000;
state.trap(&Trap::SupervisorSoftwareInterrupt, state.hart.pc);
assert_eq!(state.hart.privilege, PrivilegeMode::Supervisor);
}
#[test]
fn test_trap_machine_external_interrupt() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
state.trap(&Trap::MachineExternalInterrupt, state.hart.pc);
assert_ne!(state.hart.csrs.mcause & (1u64 << 63), 0);
}
#[test]
fn test_trap_supervisor_external_interrupt() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.csrs.stvec = 0x8000_1000;
state.hart.csrs.mideleg = 1 << 9; state.hart.pc = 0x8000_2000;
state.trap(&Trap::SupervisorExternalInterrupt, state.hart.pc);
assert_eq!(state.hart.privilege, PrivilegeMode::Supervisor);
}
#[test]
fn test_trap_user_software_interrupt() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.stvec = 0x8000_0000;
state.trap(&Trap::UserSoftwareInterrupt, state.hart.pc);
}
#[test]
fn test_trap_user_external_interrupt() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.stvec = 0x8000_0000;
state.trap(&Trap::UserExternalInterrupt, state.hart.pc);
}
#[test]
fn test_trap_delegation_to_supervisor_with_medeleg() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.csrs.stvec = 0x8000_1000;
state.hart.csrs.medeleg = 1 << 12;
let old_pc = state.hart.pc;
state.trap(&Trap::InstructionPageFault(0x1000), old_pc);
assert_eq!(state.hart.csrs.scause & !CAUSE_INTERRUPT_BIT, 12);
assert_eq!(state.hart.csrs.sepc, old_pc);
}
#[test]
fn test_trap_delegation_to_supervisor_with_mideleg() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.csrs.stvec = 0x8000_1000;
state.hart.csrs.mideleg = 1 << 1;
state.trap(&Trap::SupervisorSoftwareInterrupt, state.hart.pc);
assert_ne!(state.hart.csrs.scause & CAUSE_INTERRUPT_BIT, 0);
}
#[test]
fn test_trap_no_delegation_when_medeleg_not_set() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.csrs.stvec = 0; state.hart.csrs.medeleg = 0; state.hart.pc = 0x8000_2000;
state.trap(&Trap::IllegalInstruction(0), state.hart.pc);
assert_eq!(state.hart.privilege, PrivilegeMode::Machine);
}
#[test]
fn test_trap_delegation_only_from_lower_privilege() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.csrs.stvec = 0x8000_1000;
state.hart.csrs.medeleg = 1 << 2;
state.trap(&Trap::IllegalInstruction(0), state.hart.pc);
assert_eq!(state.hart.privilege, PrivilegeMode::Machine);
}
#[test]
fn test_trap_user_mode_no_delegation_without_medeleg() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.csrs.stvec = 0x8000_1000; state.hart.csrs.medeleg = 0;
state.trap(&Trap::LoadAddressMisaligned(0x1001), state.hart.pc);
assert_eq!(state.hart.privilege, PrivilegeMode::Machine);
assert_eq!(state.hart.pc, 0x8000_0000);
}
#[test]
fn test_trap_vectored_mode_direct_for_exceptions() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
let base = 0x8000_0000;
state.hart.csrs.mtvec = base | 1;
let old_pc = state.hart.pc;
state.trap(&Trap::IllegalInstruction(0), old_pc);
assert_eq!(state.hart.pc, base);
}
#[test]
fn test_trap_vectored_mode_offset_for_interrupts() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
let base = 0x8000_0000;
state.hart.csrs.mtvec = base | 1;
state.trap(&Trap::MachineTimerInterrupt, state.hart.pc);
assert_eq!(state.hart.pc, base + 28);
}
#[test]
fn test_trap_direct_mode_no_offset() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
let base = 0x8000_0000;
state.hart.csrs.mtvec = base;
state.trap(&Trap::MachineTimerInterrupt, state.hart.pc);
assert_eq!(state.hart.pc, base);
}
#[test]
fn test_trap_supervisor_vectored_mode() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.mtvec = 0x8000_0000;
let base = 0x8000_1000;
state.hart.csrs.stvec = base | 1; state.hart.csrs.mideleg = 1 << 5; state.hart.pc = 0x8000_2000;
state.trap(&Trap::SupervisorTimerInterrupt, state.hart.pc);
assert_eq!(state.hart.pc, base + 20);
}
#[test]
fn test_trap_tval_for_address_exceptions() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
let fault_addr = 0x1234_5678;
state.trap(&Trap::LoadAddressMisaligned(fault_addr), state.hart.pc);
assert_eq!(state.hart.csrs.mtval, fault_addr);
}
#[test]
fn test_trap_tval_for_page_faults() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
let fault_addr = 0xdead_beef;
state.trap(&Trap::StorePageFault(fault_addr), state.hart.pc);
assert_eq!(state.hart.csrs.mtval, fault_addr);
}
#[test]
fn test_trap_tval_for_illegal_instruction() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
let bad_instr = 0xdeadbeef;
state.trap(&Trap::IllegalInstruction(bad_instr), state.hart.pc);
assert_eq!(state.hart.csrs.mtval, bad_instr as u64);
}
#[test]
fn test_trap_tval_zero_for_ecall() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
state.trap(&Trap::EnvironmentCallFromMMode, state.hart.pc);
assert_eq!(state.hart.csrs.mtval, 0);
}
#[test]
fn test_trap_stval_on_delegation() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.stvec = 0x8000_1000;
state.hart.csrs.medeleg = 1 << 13; state.hart.pc = 0x8000_2000;
let fault_addr = 0xcafe_babe;
state.trap(&Trap::LoadPageFault(fault_addr), state.hart.pc);
assert_eq!(state.hart.csrs.stval, fault_addr);
}
#[test]
fn test_trap_saves_previous_privilege_in_mpp() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Supervisor;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
state.trap(&Trap::IllegalInstruction(0), state.hart.pc);
assert_eq!(state.hart.csrs.mstatus >> 11 & 0b11, 1);
}
#[test]
fn test_trap_disables_mie_and_saves_to_mpie() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000; state.hart.csrs.mstatus = 1 << 3;
state.trap(&Trap::IllegalInstruction(0), state.hart.pc);
assert_eq!(state.hart.csrs.mstatus & (1 << 3), 0);
assert_ne!(state.hart.csrs.mstatus & (1 << 7), 0);
}
#[test]
fn test_trap_saves_previous_privilege_in_spp_on_delegation() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.stvec = 0x8000_1000;
state.hart.csrs.medeleg = 1 << 2; state.hart.pc = 0x8000_2000;
state.trap(&Trap::IllegalInstruction(0), state.hart.pc);
assert_eq!(state.hart.csrs.mstatus >> 8 & 1, 0);
}
#[test]
fn test_trap_disables_sie_on_delegation() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.stvec = 0x8000_1000;
state.hart.csrs.medeleg = 1 << 2; state.hart.pc = 0x8000_2000;
state.trap(&Trap::IllegalInstruction(0), state.hart.pc);
assert_eq!(state.hart.privilege, PrivilegeMode::Supervisor);
assert_eq!(state.hart.csrs.sepc, 0x8000_2000);
}
#[test]
fn test_trap_requested_trap_custom_code() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
let custom_code = 42;
state.trap(&Trap::RequestedTrap(custom_code), state.hart.pc);
assert_eq!(state.hart.csrs.mcause, custom_code);
}
#[test]
fn test_trap_double_fault_trap_variant() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
state.trap(&Trap::DoubleFault(0x1234), state.hart.pc);
}
#[test]
fn test_trap_breakpoint() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
state.trap(&Trap::Breakpoint(0), state.hart.pc);
assert_eq!(state.hart.csrs.mcause & !CAUSE_INTERRUPT_BIT, 3);
}
#[test]
fn test_trap_all_access_faults() {
let faults = vec![
(Trap::InstructionAccessFault(0x1000), 1),
(Trap::LoadAccessFault(0x2000), 5),
(Trap::StoreAccessFault(0x3000), 7),
];
for (fault, expected_code) in faults {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
state.trap(&fault, state.hart.pc);
assert_eq!(state.hart.csrs.mcause, expected_code);
}
}
#[test]
fn test_trap_all_misaligned() {
let misaligned = vec![
(Trap::InstructionAddressMisaligned(0x1001), 0),
(Trap::LoadAddressMisaligned(0x2001), 4),
(Trap::StoreAddressMisaligned(0x3001), 6),
];
for (trap, expected_code) in misaligned {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
state.trap(&trap, state.hart.pc);
assert_eq!(state.hart.csrs.mcause, expected_code);
}
}
#[test]
fn test_trap_preserves_registers() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
state.hart.pc = 0x8000_1000;
state.hart.regs.write(RegIdx::new(1), 0x1234);
state.hart.regs.write(RegIdx::new(2), 0x5678);
state.trap(&Trap::IllegalInstruction(0), state.hart.pc);
assert_eq!(state.hart.regs.read(RegIdx::new(1)), 0x1234);
assert_eq!(state.hart.regs.read(RegIdx::new(2)), 0x5678);
}
#[test]
fn test_trap_updates_mepc_correctly() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::Machine;
state.hart.csrs.mtvec = 0x8000_0000;
let trap_pc = 0x8000_1234;
state.trap(&Trap::IllegalInstruction(0), trap_pc);
assert_eq!(state.hart.csrs.mepc, trap_pc);
}
#[test]
fn test_trap_updates_sepc_on_delegation() {
let mut sys = create_test_cpu();
let mut state = sys.core_ctx(0);
state.hart.privilege = PrivilegeMode::User;
state.hart.csrs.stvec = 0x8000_1000;
state.hart.csrs.medeleg = 1 << 2; let trap_pc = 0x8000_5678;
state.trap(&Trap::IllegalInstruction(0), trap_pc);
assert_eq!(state.hart.csrs.sepc, trap_pc);
}
use crate::isa::privileged::cause::CAUSE_INTERRUPT_BIT;