diff --git a/src/core/gba.rs b/src/core/gba.rs index fea2cc8..3eefd16 100644 --- a/src/core/gba.rs +++ b/src/core/gba.rs @@ -3,20 +3,22 @@ use std::cell::RefCell; use std::rc::Rc; -use super::arm7tdmi::{Core, DecodedInstruction}; +use super::arm7tdmi::{exception::Exception, Core, DecodedInstruction}; use super::cartridge::Cartridge; use super::gpu::*; use super::interrupt::*; use super::ioregs::IoRegs; use super::sysbus::SysBus; -use super::EmuIoDev; +use super::timer::Timers; use super::GBAResult; +use super::SyncedIoDevice; use crate::backend::*; #[derive(Debug)] pub struct IoDevices { pub intc: InterruptController, pub gpu: Gpu, + pub timers: Timers, } impl IoDevices { @@ -24,6 +26,7 @@ impl IoDevices { IoDevices { intc: InterruptController::new(), gpu: Gpu::new(), + timers: Timers::new(), } } } @@ -46,7 +49,7 @@ impl GameBoyAdvance { let io = Rc::new(RefCell::new(IoDevices::new())); let ioregs = IoRegs::new(io.clone()); - let sysbus = SysBus::new(bios_rom, gamepak, ioregs); + let sysbus = SysBus::new(io.clone(), bios_rom, gamepak, ioregs); GameBoyAdvance { backend: backend, @@ -81,10 +84,17 @@ impl GameBoyAdvance { } pub fn emulate_peripherals(&mut self, cycles: usize) { + let mut irqs = IrqBitmask(0); let mut io = self.io.borrow_mut(); - let (_, irq) = io.gpu.step(cycles, &mut self.sysbus); - if let Some(irq) = irq { - io.intc.request_irq(&mut self.cpu, irq); + + io.timers.step(cycles, &mut self.sysbus, &mut irqs); + io.gpu.step(cycles, &mut self.sysbus, &mut irqs); + + if !self.cpu.cpsr.irq_disabled() { + io.intc.request_irqs(irqs); + if io.intc.irq_pending() { + self.cpu.exception(Exception::Irq); + } } } diff --git a/src/core/gpu.rs b/src/core/gpu.rs index 633921a..114c664 100644 --- a/src/core/gpu.rs +++ b/src/core/gpu.rs @@ -376,8 +376,8 @@ impl Gpu { } } -impl EmuIoDev for Gpu { - fn step(&mut self, cycles: usize, sb: &mut SysBus) -> (usize, Option) { +impl SyncedIoDevice for Gpu { + fn step(&mut self, cycles: usize, sb: &mut SysBus, irqs: &mut IrqBitmask) { self.cycles += cycles; if self.dispstat.vcount_setting() != 0 { @@ -385,7 +385,7 @@ impl EmuIoDev for Gpu { .set_vcount(self.dispstat.vcount_setting() == self.current_scanline as u16); } if self.dispstat.vcount_irq_enable() && self.dispstat.get_vcount() { - return (0, Some(Interrupt::LCD_VCounterMatch)); + irqs.set_LCD_VCounterMatch(true);; } match self.state { @@ -394,28 +394,22 @@ impl EmuIoDev for Gpu { self.current_scanline += 1; self.cycles -= Gpu::CYCLES_HDRAW; - let (new_state, irq) = if self.current_scanline < Gpu::DISPLAY_HEIGHT { + if self.current_scanline < Gpu::DISPLAY_HEIGHT { self.scanline(sb); // HBlank self.dispstat.set_hblank(true); - let irq = if self.dispstat.hblank_irq_enable() { - Some(Interrupt::LCD_HBlank) - } else { - None + if self.dispstat.hblank_irq_enable() { + irqs.set_LCD_HBlank(true); }; - (HBlank, irq) + self.state = HBlank; } else { self.scanline(sb); self.dispstat.set_vblank(true); - let irq = if self.dispstat.vblank_irq_enable() { - Some(Interrupt::LCD_VBlank) - } else { - None + if self.dispstat.vblank_irq_enable() { + irqs.set_LCD_VBlank(true); }; - (VBlank, irq) + self.state = VBlank; }; - self.state = new_state; - return (0, irq); } } HBlank => { @@ -424,7 +418,6 @@ impl EmuIoDev for Gpu { self.state = HDraw; self.dispstat.set_hblank(false); self.dispstat.set_vblank(false); - return (0, None); } } VBlank => { @@ -435,12 +428,9 @@ impl EmuIoDev for Gpu { self.dispstat.set_vblank(false); self.current_scanline = 0; self.scanline(sb); - return (0, None); } } } - - (0, None) } } diff --git a/src/core/interrupt.rs b/src/core/interrupt.rs index 70401f1..e02f19b 100644 --- a/src/core/interrupt.rs +++ b/src/core/interrupt.rs @@ -1,6 +1,4 @@ -use super::arm7tdmi::{exception::Exception, Core}; - -use crate::bit::BitIndex; +use super::arm7tdmi::Core; #[derive(Debug, Primitive, Copy, Clone, PartialEq)] #[allow(non_camel_case_types)] @@ -21,34 +19,57 @@ pub enum Interrupt { GamePak = 13, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct InterruptController { pub interrupt_master_enable: bool, - pub interrupt_enable: u16, - pub interrupt_flags: u16, + pub interrupt_enable: IrqBitmask, + pub interrupt_flags: IrqBitmask, } impl InterruptController { pub fn new() -> InterruptController { InterruptController { interrupt_master_enable: false, - interrupt_enable: 0, - interrupt_flags: 0, + ..Default::default() } } - pub fn interrupts_disabled(&self, cpu: &Core) -> bool { - cpu.cpsr.irq_disabled() | (self.interrupt_master_enable) - } - - pub fn request_irq(&mut self, cpu: &mut Core, irq: Interrupt) { - if self.interrupts_disabled(cpu) { + pub fn request_irqs(&mut self, flags: IrqBitmask) { + if !self.interrupt_master_enable { return; } - let irq_bit_index = irq as usize; - if self.interrupt_enable.bit(irq_bit_index) { - self.interrupt_flags = 1 << irq_bit_index; - cpu.exception(Exception::Irq); - } + self.interrupt_flags.0 |= flags.0 & self.interrupt_enable.0; + } + + pub fn irq_pending(&self) -> bool { + self.interrupt_master_enable & (self.interrupt_flags.0 != 0) } } + +impl IrqBitmask { + pub fn add_irq(&mut self, i: Interrupt) { + self.0 |= 1 << (i as usize); + } +} + +bitfield! { + #[derive(Default, Copy, Clone, PartialEq)] + #[allow(non_snake_case)] + pub struct IrqBitmask(u16); + impl Debug; + u16; + pub LCD_VBlank, set_LCD_VBlank: 0; + pub LCD_HBlank, set_LCD_HBlank: 1; + pub LCD_VCounterMatch, set_LCD_VCounterMatch: 2; + pub Timer0_Overflow, set_Timer0_Overflow: 3; + pub Timer1_Overflow, set_Timer1_Overflow: 4; + pub Timer2_Overflow, set_Timer2_Overflow: 5; + pub Timer3_Overflow, set_Timer3_Overflow: 6; + pub SerialCommunication, set_SerialCommunication: 7; + pub DMA0, set_DMA0: 8; + pub DMA1, set_DMA1: 9; + pub DMA2, set_DMA2: 10; + pub DMA3, set_DMA3: 11; + pub Keypad, set_Keypad: 12; + pub GamePak, set_GamePak: 13; +} diff --git a/src/core/ioregs.rs b/src/core/ioregs.rs index b9793e5..b3a77d8 100644 --- a/src/core/ioregs.rs +++ b/src/core/ioregs.rs @@ -166,8 +166,17 @@ impl Bus for IoRegs { REG_BLDY => io.gpu.bldy, REG_IME => io.intc.interrupt_master_enable as u16, - REG_IE => io.intc.interrupt_enable as u16, - REG_IF => io.intc.interrupt_flags as u16, + REG_IE => io.intc.interrupt_enable.0 as u16, + REG_IF => io.intc.interrupt_flags.0 as u16, + + REG_TM0CNT_L => io.timers[0].timer_data, + REG_TM0CNT_H => io.timers[0].timer_ctl.0, + REG_TM1CNT_L => io.timers[1].timer_data, + REG_TM1CNT_H => io.timers[1].timer_ctl.0, + REG_TM2CNT_L => io.timers[2].timer_data, + REG_TM2CNT_H => io.timers[2].timer_ctl.0, + REG_TM3CNT_L => io.timers[3].timer_data, + REG_TM3CNT_H => io.timers[3].timer_ctl.0, REG_POSTFLG => self.post_boot_flag as u16, REG_HALTCNT => 0, @@ -216,9 +225,33 @@ impl Bus for IoRegs { REG_BLDALPHA => io.gpu.bldalpha = value, REG_BLDY => io.gpu.bldy = value, - REG_IME => io.intc.interrupt_master_enable = value == 1, - REG_IE => io.intc.interrupt_enable = value, - REG_IF => io.intc.interrupt_flags &= !value, + REG_IME => io.intc.interrupt_master_enable = value != 0, + REG_IE => io.intc.interrupt_enable.0 = value, + REG_IF => io.intc.interrupt_flags.0 &= !value, + + REG_TM0CNT_L => { + io.timers[0].timer_data = value; + io.timers[0].initial_data = value; + } + REG_TM0CNT_H => io.timers[0].timer_ctl.0 = value, + + REG_TM1CNT_L => { + io.timers[1].timer_data = value; + io.timers[0].initial_data = value; + } + REG_TM1CNT_H => io.timers[1].timer_ctl.0 = value, + + REG_TM2CNT_L => { + io.timers[2].timer_data = value; + io.timers[0].initial_data = value; + } + REG_TM2CNT_H => io.timers[2].timer_ctl.0 = value, + + REG_TM3CNT_L => { + io.timers[3].timer_data = value; + io.timers[0].initial_data = value; + } + REG_TM3CNT_H => io.timers[3].timer_ctl.0 = value, REG_POSTFLG => self.post_boot_flag = value != 0, REG_HALTCNT => {} diff --git a/src/core/mod.rs b/src/core/mod.rs index 5cc1e30..ab29ecc 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -6,16 +6,18 @@ pub use sysbus::SysBus; pub mod interrupt; pub mod ioregs; pub use interrupt::Interrupt; +pub use interrupt::IrqBitmask; pub mod gba; pub use gba::GameBoyAdvance; pub mod dma; pub mod keypad; pub mod palette; +pub mod timer; use crate::debugger; -pub trait EmuIoDev { - fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option); +pub trait SyncedIoDevice { + fn step(&mut self, cycles: usize, sb: &mut SysBus, irqs: &mut IrqBitmask); } #[derive(Debug)] diff --git a/src/core/sysbus.rs b/src/core/sysbus.rs index 57d3e53..93c41d3 100644 --- a/src/core/sysbus.rs +++ b/src/core/sysbus.rs @@ -1,9 +1,12 @@ -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use std::cell::RefCell; +use std::rc::Rc; -use super::{cartridge::Cartridge, ioregs::IoRegs}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use super::arm7tdmi::bus::{Bus, MemoryAccess, MemoryAccessWidth}; use super::arm7tdmi::Addr; +use super::gba::IoDevices; +use super::{cartridge::Cartridge, ioregs::IoRegs}; const VIDEO_RAM_SIZE: usize = 128 * 1024; const WORK_RAM_SIZE: usize = 256 * 1024; @@ -132,6 +135,8 @@ impl Bus for DummyBus { #[derive(Debug)] pub struct SysBus { + pub io: Rc>, + bios: BoxedMemory, onboard_work_ram: BoxedMemory, internal_work_ram: BoxedMemory, @@ -145,8 +150,15 @@ pub struct SysBus { } impl SysBus { - pub fn new(bios_rom: Vec, gamepak: Cartridge, ioregs: IoRegs) -> SysBus { + pub fn new( + io: Rc>, + bios_rom: Vec, + gamepak: Cartridge, + ioregs: IoRegs, + ) -> SysBus { SysBus { + io: io, + bios: BoxedMemory::new(bios_rom.into_boxed_slice(), 0xff_ffff), onboard_work_ram: BoxedMemory::new_with_waitstate( vec![0; WORK_RAM_SIZE].into_boxed_slice(), diff --git a/src/core/timer.rs b/src/core/timer.rs new file mode 100644 index 0000000..c25c3eb --- /dev/null +++ b/src/core/timer.rs @@ -0,0 +1,125 @@ +use super::interrupt::{Interrupt, IrqBitmask}; +use super::sysbus::SysBus; +use super::SyncedIoDevice; + +use num::FromPrimitive; + +#[derive(Debug, Default)] +pub struct Timer { + // registers + pub timer_ctl: TimerCtl, + pub timer_data: u16, + + timer_id: usize, + reg: u32, + target: u32, + fired: bool, + pub initial_data: u16, + + pub cycles: usize, +} + +pub enum TimerAction { + Overflow, + Increment, +} + +impl Timer { + pub fn new(timer_id: usize) -> Timer { + if timer_id > 3 { + panic!("invalid timer id {}", timer_id); + } + Timer { + timer_id: timer_id, + ..Timer::default() + } + } + + fn get_irq(&self) -> Interrupt { + Interrupt::from_usize(self.timer_id + 8).unwrap() + } + + fn frequency(&self) -> usize { + match self.timer_ctl.prescalar() { + 0 => 1, + 1 => 64, + 2 => 256, + 3 => 1024, + _ => unreachable!(), + } + } + + pub fn add_cycles(&mut self, cycles: usize, irqs: &mut IrqBitmask) -> TimerAction { + self.cycles += cycles; + + let frequency = self.frequency(); + while self.cycles >= frequency { + self.cycles -= frequency; + self.timer_data = self.timer_data.wrapping_add(1); + if self.timer_data == 0 { + if self.timer_ctl.irq_enabled() { + irqs.add_irq(self.get_irq()); + } + println!("timer{} overflow", self.timer_id); + return TimerAction::Overflow; + } + } + + return TimerAction::Increment; + } +} + +#[derive(Debug)] +pub struct Timers([Timer; 4]); + +impl std::ops::Index for Timers { + type Output = Timer; + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + +impl std::ops::IndexMut for Timers { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} + +impl Timers { + pub fn new() -> Timers { + Timers([Timer::new(0), Timer::new(1), Timer::new(2), Timer::new(3)]) + } +} + +impl SyncedIoDevice for Timers { + fn step(&mut self, cycles: usize, _sb: &mut SysBus, irqs: &mut IrqBitmask) { + for i in 0..4 { + if self[i].timer_ctl.enabled() && !self[i].timer_ctl.cascade() { + match self[i].add_cycles(cycles, irqs) { + TimerAction::Overflow => match i { + 3 => {} + _ => { + let next_i = i +1; + if self[next_i].timer_ctl.cascade() { + println!("{:?} is cascade!", self[next_i]); + self[next_i].add_cycles(1, irqs); + } + } + }, + _ => {} + } + } + } + } +} + +bitfield! { + #[derive(Default)] + pub struct TimerCtl(u16); + impl Debug; + u16; + prescalar, _ : 1, 0; + cascade, _ : 2; + irq_enabled, _ : 6; + enabled, set_enabled : 7; +}