From becc4f155ca0e046c601c9a12b4e03b718ea1818 Mon Sep 17 00:00:00 2001 From: Logan McNaughton <848146+loganmc10@users.noreply.github.com> Date: Tue, 3 Nov 2026 08:51:04 +0000 Subject: [PATCH] handle address errors (#1337) --- src/device/cop0.rs | 6 +++++- src/device/cpu.rs | 23 ++++++++++++++++++++--- src/device/cpu_instructions.rs | 18 ++++++++++++++---- src/device/exceptions.rs | 7 +++++++ src/device/rsp_cpu.rs | 3 +++ 5 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/device/cop0.rs b/src/device/cop0.rs index f09f2a2e..887a5fbf 100644 --- a/src/device/cop0.rs +++ b/src/device/cop0.rs @@ -49,7 +49,7 @@ pub const COP0_STATUS_CU2: u64 = 1 << 30; pub const COP0_CAUSE_EXCCODE_MOD: u64 = 1 << 2; pub const COP0_CAUSE_EXCCODE_TLBL: u64 = 2 << 2; pub const COP0_CAUSE_EXCCODE_TLBS: u64 = 3 << 2; -//const COP0_CAUSE_EXCCODE_ADEL: u64 = 4 << 2; +pub const COP0_CAUSE_EXCCODE_ADEL: u64 = 4 << 2; //const COP0_CAUSE_EXCCODE_ADES: u64 = 5 << 2; pub const COP0_CAUSE_EXCCODE_SYS: u64 = 8 << 2; pub const COP0_CAUSE_EXCCODE_BP: u64 = 9 << 2; @@ -169,6 +169,10 @@ fn eret(device: &mut device::Device, _opcode: u32) { } device.cpu.branch_state.state = device::cpu::State::Exception; device.cpu.llbit = false; + if device.cpu.pc & 3 != 0 { + device::exceptions::address_error_exception(device, device.cpu.pc); + return; + } device::exceptions::check_pending_interrupts(device) } diff --git a/src/device/cpu.rs b/src/device/cpu.rs index e64c4f2e..d163e0a0 100644 --- a/src/device/cpu.rs +++ b/src/device/cpu.rs @@ -9,6 +9,9 @@ pub enum State { DelaySlotNotTaken, Discard, Exception, + // jr/jalr to a misaligned target: the address error is raised after the delay slot + TakeAddressError, + DelaySlotAddressError, } #[derive(Clone, serde::Serialize, serde::Deserialize)] @@ -242,12 +245,17 @@ pub fn init(device: &mut device::Device) { } pub fn in_delay_slot(device: &device::Device) -> bool { - device.cpu.branch_state.state == State::DelaySlotTaken - || device.cpu.branch_state.state == State::DelaySlotNotTaken + matches!( + device.cpu.branch_state.state, + State::DelaySlotTaken | State::DelaySlotNotTaken | State::DelaySlotAddressError + ) } pub fn in_delay_slot_taken(device: &device::Device) -> bool { - device.cpu.branch_state.state == State::DelaySlotTaken + matches!( + device.cpu.branch_state.state, + State::DelaySlotTaken | State::DelaySlotAddressError + ) } pub fn run(device: &mut device::Device) { @@ -298,6 +306,15 @@ pub fn run(device: &mut device::Device) { device.cpu.branch_state.state = State::Step } State::Exception => device.cpu.branch_state.state = State::Step, + State::TakeAddressError => { + device.cpu.pc += 4; + device.cpu.branch_state.state = State::DelaySlotAddressError + } + State::DelaySlotAddressError => { + device.cpu.pc = device.cpu.branch_state.pc; + device.cpu.branch_state.state = State::Step; + device::exceptions::address_error_exception(device, device.cpu.pc) + } } device::cop0::add_cycles(device, 1); if device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG] > device.cpu.next_event_count { diff --git a/src/device/cpu_instructions.rs b/src/device/cpu_instructions.rs index fea35e6c..a8c8e4c3 100644 --- a/src/device/cpu_instructions.rs +++ b/src/device/cpu_instructions.rs @@ -964,8 +964,13 @@ pub fn srav(device: &mut device::Device, opcode: u32) { pub fn jr(device: &mut device::Device, opcode: u32) { if !device::cpu::in_delay_slot_taken(device) { - device.cpu.branch_state.state = device::cpu::State::Take; - device.cpu.branch_state.pc = device.cpu.gpr[rs(opcode) as usize]; + let target = device.cpu.gpr[rs(opcode) as usize]; + device.cpu.branch_state.state = if target & 3 == 0 { + device::cpu::State::Take + } else { + device::cpu::State::TakeAddressError + }; + device.cpu.branch_state.pc = target; } else if !device::cpu::in_delay_slot(device) { device.cpu.branch_state.state = device::cpu::State::NotTaken; } @@ -975,8 +980,13 @@ pub fn jalr(device: &mut device::Device, opcode: u32) { let in_delay_slot_taken = device::cpu::in_delay_slot_taken(device); if !in_delay_slot_taken { - device.cpu.branch_state.state = device::cpu::State::Take; - device.cpu.branch_state.pc = device.cpu.gpr[rs(opcode) as usize]; + let target = device.cpu.gpr[rs(opcode) as usize]; + device.cpu.branch_state.state = if target & 3 == 0 { + device::cpu::State::Take + } else { + device::cpu::State::TakeAddressError + }; + device.cpu.branch_state.pc = target; } else if !device::cpu::in_delay_slot(device) { device.cpu.branch_state.state = device::cpu::State::NotTaken; } diff --git a/src/device/exceptions.rs b/src/device/exceptions.rs index 534b3706..5a91c18c 100644 --- a/src/device/exceptions.rs +++ b/src/device/exceptions.rs @@ -79,6 +79,13 @@ pub fn cop_unusable_exception(device: &mut device::Device, cop: u64) { exception_general(device, 0x180) } +pub fn address_error_exception(device: &mut device::Device, address: u64) { + set_exception_cause(device, device::cop0::COP0_CAUSE_EXCCODE_ADEL); + device.cpu.cop0.regs[device::cop0::COP0_BADVADDR_REG] = address; + + exception_general(device, 0x180) +} + pub fn tlb_miss_exception( device: &mut device::Device, address: u64, diff --git a/src/device/rsp_cpu.rs b/src/device/rsp_cpu.rs index 45193b7a..04decb23 100644 --- a/src/device/rsp_cpu.rs +++ b/src/device/rsp_cpu.rs @@ -170,6 +170,9 @@ pub fn run(device: &mut device::Device) -> u64 { device::cpu::State::Exception => { device.rsp.cpu.branch_state.state = device::cpu::State::Step } + device::cpu::State::TakeAddressError | device::cpu::State::DelaySlotAddressError => { + unreachable!() // VR4300-only states + } } device.rsp.regs2[device::rsp_interface::SP_PC_REG] &= 0xFFC;