use serde::{Deserialize, Serialize};
pub mod bits {
pub const VBLANK: u16 = 1 << 0;
pub const HBLANK: u16 = 1 << 1;
pub const VCOUNT: u16 = 1 << 2;
pub const TIMER0: u16 = 1 << 3;
pub const TIMER1: u16 = 1 << 4;
pub const TIMER2: u16 = 1 << 5;
pub const TIMER3: u16 = 1 << 6;
pub const SERIAL: u16 = 1 << 7;
pub const DMA0: u16 = 1 << 8;
pub const DMA1: u16 = 1 << 9;
pub const DMA2: u16 = 1 << 10;
pub const DMA3: u16 = 1 << 11;
pub const KEYPAD: u16 = 1 << 12;
pub const GAMEPAK: u16 = 1 << 13;
}
pub const IRQ_MASK: u16 = 0x3FFF;
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct InterruptController {
pub ie: u16,
pub if_flags: u16,
pub ime: bool,
#[serde(default)]
irq_cycles_late: u32,
}
impl InterruptController {
pub fn new() -> Self {
Self::default()
}
pub fn irq_line(&self) -> bool {
self.ime && (self.ie & self.if_flags & IRQ_MASK) != 0
}
pub fn active_irq_sources(&self) -> u16 {
if self.ime {
self.active_pending_sources()
} else {
0
}
}
pub fn active_pending_sources(&self) -> u16 {
self.ie & self.if_flags & IRQ_MASK
}
pub fn halt_exit_line(&self) -> bool {
self.active_pending_sources() != 0
}
pub fn active_halt_sources(&self) -> u16 {
self.active_pending_sources()
}
pub fn raise(&mut self, sources: u16) {
self.if_flags |= sources & IRQ_MASK;
}
pub fn raise_late(&mut self, sources: u16, cycles_late: u32) {
self.raise(sources);
self.irq_cycles_late = self.irq_cycles_late.max(cycles_late);
}
pub fn take_irq_cycles_late(&mut self) -> u32 {
let cycles_late = self.irq_cycles_late;
self.irq_cycles_late = 0;
cycles_late
}
pub fn acknowledge(&mut self, value: u16) {
self.if_flags &= !(value & IRQ_MASK);
}
pub fn write_ie(&mut self, value: u16) {
self.ie = value & IRQ_MASK;
}
pub fn write_if(&mut self, value: u16) {
self.acknowledge(value);
}
pub fn write_ime(&mut self, value: u16) {
self.ime = (value & 1) != 0;
}
pub fn read_ime(&self) -> u16 {
u16::from(self.ime)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_are_cleared_and_line_low() {
let ic = InterruptController::new();
assert_eq!(ic.ie, 0);
assert_eq!(ic.if_flags, 0);
assert!(!ic.ime);
assert!(!ic.irq_line());
}
#[test]
fn line_requires_ime_ie_and_pending() {
let mut ic = InterruptController::new();
ic.write_ie(bits::TIMER0);
ic.raise(bits::TIMER0);
assert!(!ic.irq_line(), "IME=0 must keep line low");
ic.write_ime(1);
assert!(ic.irq_line(), "IME=1 + IE & IF must assert line");
}
#[test]
fn write_one_to_clear_if() {
let mut ic = InterruptController::new();
ic.write_ie(0xFFFF);
ic.write_ime(1);
ic.raise(bits::TIMER0 | bits::VBLANK);
assert!(ic.irq_line());
ic.write_if(bits::TIMER0);
assert_eq!(ic.if_flags, bits::VBLANK);
assert!(ic.irq_line(), "VBlank still pending");
ic.write_if(bits::VBLANK);
assert!(!ic.irq_line());
}
#[test]
fn ie_and_if_mask_unused_bits() {
let mut ic = InterruptController::new();
ic.write_ie(0xFFFF);
assert_eq!(ic.ie, IRQ_MASK);
ic.raise(0xFFFF);
assert_eq!(ic.if_flags, IRQ_MASK);
}
#[test]
fn ime_only_bit_0_is_significant() {
let mut ic = InterruptController::new();
ic.write_ime(0x1234);
assert!(!ic.ime, "bit 0 is 0 → disabled");
assert_eq!(ic.read_ime(), 0);
ic.write_ime(0x1235);
assert!(ic.ime);
assert_eq!(ic.read_ime(), 1);
}
}