1//! GBA interrupt registers 2 3use bincode::{Decode, Encode}; 4use jgenesis_common::num::GetBit; 5use jgenesis_proc_macros::EnumAll; 6 7#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumAll, Encode, Decode)] 8pub enum InterruptType { 9 VBlank = 0, 10 HBlank = 1, 11 VCounter = 2, 12 Timer0 = 3, 13 Timer1 = 4, 14 Timer2 = 5, 15 Timer3 = 6, 16 Serial = 7, 17 Dma0 = 8, 18 Dma1 = 9, 19 Dma2 = 10, 20 Dma3 = 11, 21 Keypad = 12, 22 GamePak = 13, 23} 24 25impl InterruptType { 26 pub const DMA: [Self; 4] = [Self::Dma0, Self::Dma1, Self::Dma2, Self::Dma3]; 27 28 pub const TIMER: [Self; 4] = [Self::Timer0, Self::Timer1, Self::Timer2, Self::Timer3]; 29 30 fn bit_mask(self) -> u16 { 31 1 << (self as u8) 32 } 33 34 fn name(self) -> &'static str { 35 match self { 36 Self::VBlank => "VBlank", 37 Self::HBlank => "HBlank", 38 Self::VCounter => "V counter match", 39 Self::Timer0 => "Timer 0 overflow", 40 Self::Timer1 => "Timer 1 overflow", 41 Self::Timer2 => "Timer 2 overflow", 42 Self::Timer3 => "Timer 3 overflow", 43 Self::Serial => "Serial", 44 Self::Dma0 => "DMA 0", 45 Self::Dma1 => "DMA 1", 46 Self::Dma2 => "DMA 2", 47 Self::Dma3 => "DMA 3", 48 Self::Keypad => "Keypad", 49 Self::GamePak => "Game Pak", 50 } 51 } 52} 53 54#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 55enum PendingWrite { 56 Ime(bool), 57 Ie(u16), 58 If(u16), 59 SetFlag(InterruptType), 60} 61 62#[derive(Debug, Clone, Copy, Default, Encode, Decode)] 63struct IrqFlags { 64 ime: bool, 65 enabled: u16, 66 flags: u16, 67 pending: bool, 68} 69 70impl IrqFlags { 71 fn apply_write(&mut self, write: PendingWrite) { 72 match write { 73 PendingWrite::Ime(ime) => self.ime = ime, 74 PendingWrite::Ie(enabled) => self.enabled = enabled, 75 PendingWrite::If(flags) => { 76 // Writing 1 to a bit clears that flag 77 self.flags &= !flags; 78 } 79 PendingWrite::SetFlag(interrupt) => { 80 self.flags |= interrupt.bit_mask(); 81 } 82 } 83 84 self.pending = self.ime && self.enabled & self.flags != 0; 85 } 86} 87 88#[derive(Debug, Clone, Encode, Decode)] 89pub struct InterruptRegisters { 90 irq: IrqFlags, 91 irq_synchronized: IrqFlags, 92 irq_pending: bool, 93 halted: bool, 94 stopped: bool, 95 stop_ending: bool, 96 pending_writes: Vec<(PendingWrite, u64)>, 97} 98 99impl InterruptRegisters { 100 pub fn new() -> Self { 101 Self { 102 irq: IrqFlags::default(), 103 irq_synchronized: IrqFlags::default(), 104 irq_pending: false, 105 halted: false, 106 stopped: false, 107 stop_ending: false, 108 pending_writes: Vec::with_capacity(10), 109 } 110 } 111 112 pub fn cpu_bus_cycle(&mut self, cycles: u64) { 113 self.apply_pending_writes(cycles); 114 self.irq_pending = self.irq_synchronized.pending; 115 116 self.halted &= self.irq.enabled & self.irq.flags == 0; 117 self.stopped &= self.halted; 118 } 119 120 pub fn irq(&self) -> bool { 121 self.irq_synchronized.pending 122 } 123 124 pub fn write_ime(&mut self, value: u16, cycles: u64) { 125 self.push_pending_write(PendingWrite::Ime(value.bit(0)), cycles); 126 log::trace!("IME write: {value:04X} (enabled = {}) (cycles = {cycles})", value.bit(0)); 127 } 128 129 pub fn read_ime(&mut self, cycles: u64) -> u16 { 130 self.apply_pending_writes(cycles); 131 self.irq.ime.into() 132 } 133 134 pub fn write_ie(&mut self, value: u16, cycles: u64) { 135 self.push_pending_write(PendingWrite::Ie(value), cycles); 136 log::trace!("IE write: {value:04X} (cycles = {cycles})"); 137 } 138 139 pub fn read_ie(&mut self, cycles: u64) -> u16 { 140 self.apply_pending_writes(cycles); 141 self.irq.enabled 142 } 143 144 pub fn write_if(&mut self, value: u16, cycles: u64) { 145 // Clearing interrupt flags via IF write affects any flag sets that occur on the same cycle 146 self.pending_writes.retain(|&(write, write_cycles)| { 147 let PendingWrite::SetFlag(interrupt) = write else { return true }; 148 !(cycles == write_cycles && value & interrupt.bit_mask() != 0) 149 }); 150 151 self.push_pending_write(PendingWrite::If(value), cycles); 152 log::trace!("IF write: {value:04X} (cycles = {cycles})"); 153 } 154 155 pub fn read_if(&mut self, cycles: u64) -> u16 { 156 self.apply_pending_writes(cycles); 157 self.irq.flags 158 } 159 160 pub fn set_flag(&mut self, interrupt: InterruptType, cycles: u64) { 161 if self.stopped { 162 // IRQs during stop don't seem to set the flag in IF, but they can end stop 163 self.stop_ending |= self.irq.enabled & interrupt.bit_mask() != 0; 164 return; 165 } 166 167 // Setting interrupt flag has no affect if the flag is cleared via IF write on the same cycle 168 let interrupt_bit = interrupt.bit_mask(); 169 if self.pending_writes.iter().any(|&(write, write_cycles)| { 170 let PendingWrite::If(flags) = write else { return false }; 171 cycles == write_cycles && flags & interrupt_bit != 0 172 }) { 173 log::trace!( 174 "Skipping {} interrupt flag set due to pending IF write (cycles = {cycles})", 175 interrupt.name() 176 ); 177 return; 178 } 179 180 self.push_pending_write(PendingWrite::SetFlag(interrupt), cycles); 181 log::trace!("{} interrupt flag set (cycles = {cycles})", interrupt.name()); 182 } 183 184 pub fn write_haltcnt(&mut self, value: u8) { 185 self.halted = true; 186 self.stopped = value.bit(7); 187 188 log::trace!("HALTCNT write: {value:02X}, CPU is halted"); 189 log::trace!(" Stopped: {}", self.stopped); 190 } 191 192 pub fn cpu_halted(&self) -> bool { 193 self.halted 194 } 195 196 pub fn stopped(&self) -> bool { 197 self.stopped 198 } 199 200 pub fn stop_is_ending(&self) -> bool { 201 self.stop_ending 202 } 203 204 pub fn clear_stop(&mut self) { 205 self.stopped = false; 206 self.halted = false; 207 self.stop_ending = false; 208 } 209 210 fn push_pending_write(&mut self, write: PendingWrite, cycles: u64) { 211 let i = self 212 .pending_writes 213 .iter() 214 .position(|&(_, other_cycles)| other_cycles > cycles) 215 .unwrap_or(self.pending_writes.len()); 216 self.pending_writes.insert(i, (write, cycles)); 217 } 218 219 #[allow(clippy::int_plus_one)] 220 fn apply_pending_writes(&mut self, cycles: u64) { 221 if self.pending_writes.is_empty() { 222 return; 223 } 224 225 for &(write, write_cycles) in &self.pending_writes { 226 // 1-cycle delay before writes and flag sets are visible for reads 227 if cycles >= write_cycles + 1 { 228 self.irq.apply_write(write); 229 } 230 231 // 2-cycle delay before writes affect the IRQ synchronizer 232 if cycles >= write_cycles + 2 { 233 self.irq_synchronized.apply_write(write); 234 } 235 } 236 237 self.pending_writes.retain(|&(_, write_cycles)| cycles < write_cycles + 2); 238 } 239}