GBA interrupt registers

3use bincode::{Decode, Encode};
4use jgenesis_common::num::GetBit;
5use jgenesis_proc_macros::EnumAll;
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}