diff --git a/examples/sdl/src/main.rs b/examples/sdl/src/main.rs index f72bf2aa808ef50cc397c68fd013715008f6cd8a..2e236739c81b0488159c8132e66c0d5408420ba3 100644 --- a/examples/sdl/src/main.rs +++ b/examples/sdl/src/main.rs @@ -84,7 +84,8 @@ fn main() { let mut game_boy = GameBoy::new(); game_boy.load_boot_default(); - game_boy.load_rom_file("../../res/roms.prop/tetris.gb"); + //game_boy.load_rom_file("../../res/roms.prop/tetris.gb"); + game_boy.load_rom_file("../../res/roms.prop/dr_mario.gb"); //game_boy.load_rom_file("../../res/roms.prop/alleyway.gb"); //game_boy.load_rom_file("../../res/roms/firstwhite.gb"); diff --git a/src/cpu.rs b/src/cpu.rs index 34d659d3669bb8a8de51ca49f4ff76267d6c3742..9d538684346b700d201f9e220673d215ea31d877 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -4,7 +4,8 @@ use crate::{ inst::{EXTENDED, INSTRUCTIONS}, mmu::Mmu, pad::Pad, - ppu::Ppu, timer::Timer, + ppu::Ppu, + timer::Timer, }; pub const PREFIX: u8 = 0xcb; @@ -129,6 +130,27 @@ impl Cpu { return 16; } + + // @todo aggregate the handling of these interrupts + if (self.mmu.ie & 0x04 == 0x04) && self.mmu.timer().int_tima() { + println!("Going to run Timer interrupt handler (0x50)"); + + self.disable_int(); + self.push_word(pc); + self.pc = 0x50; + + // acknowledges that the timer interrupt has been + // properly handled + self.mmu.timer().ack_tima(); + + // in case the CPU is currently halted waiting + // for an interrupt, releases it + if self.halted { + self.halted = false; + } + + return 16; + } } // in case the CPU is currently in the halted state @@ -191,7 +213,7 @@ impl Cpu { pub fn pad(&mut self) -> &mut Pad { self.mmu().pad() } - + #[inline(always)] pub fn timer(&mut self) -> &mut Timer { self.mmu().timer() diff --git a/src/data.rs b/src/data.rs index c9330da6e7c417932c73e6ade7f54d794db89d5f..c77a3e8e5cc6a85a32c0a09832a9d8e89e14cd2e 100644 --- a/src/data.rs +++ b/src/data.rs @@ -9,8 +9,6 @@ pub enum BootRom { MgbBootix, } -/// Static data corresponding to the DMG boot ROM -/// allows freely using the emulator without external dependency. pub const DMG_BOOT: [u8; 256] = [ 49, 254, 255, 175, 33, 255, 159, 50, 203, 124, 32, 251, 33, 38, 255, 14, 17, 62, 128, 50, 226, 12, 62, 243, 226, 50, 62, 119, 119, 62, 252, 224, 71, 17, 4, 1, 33, 16, 128, 26, 205, 149, 0, diff --git a/src/gb.rs b/src/gb.rs index f9a12c84f9cd375222890984bb1b1a20352cf5f3..acfd5d9825dea000a104bef7fed1ce42213fd263 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -4,7 +4,8 @@ use crate::{ mmu::Mmu, pad::{Pad, PadKey}, ppu::{Ppu, Tile, FRAME_BUFFER_SIZE}, - util::read_file, timer::Timer, + timer::Timer, + util::read_file, }; #[cfg(feature = "wasm")] @@ -51,6 +52,7 @@ impl GameBoy { pub fn clock(&mut self) -> u8 { let cycles = self.cpu_clock(); self.ppu_clock(cycles); + self.timer_clock(cycles); cycles } @@ -62,6 +64,10 @@ impl GameBoy { self.ppu().clock(cycles) } + pub fn timer_clock(&mut self, cycles: u8) { + self.timer().clock(cycles) + } + pub fn load_rom(&mut self, data: &[u8]) { self.cpu.mmu().write_rom(0x0000, data); } diff --git a/src/mmu.rs b/src/mmu.rs index ed98c3ec0efc4dec3e39c25c5d9c8d47efb0b727..e3dae1ddcfd9fe68afa9179e48b6ca229dcc045e 100644 --- a/src/mmu.rs +++ b/src/mmu.rs @@ -96,28 +96,30 @@ impl Mmu { 0x000 | 0x100 | 0x200 | 0x300 | 0x400 | 0x500 | 0x600 | 0x700 | 0x800 | 0x900 | 0xa00 | 0xb00 | 0xc00 | 0xd00 => self.ram[(addr & 0x1fff) as usize], 0xe00 => self.ppu.oam[(addr & 0x009f) as usize], - 0xf00 => { - if addr == 0xffff { - self.ie - } else if addr >= 0xff80 { - self.ppu.hram[(addr & 0x007f) as usize] - } else { - match addr & 0x00f0 { - 0x00 => match addr & 0x00ff { - 0x00 => self.pad.read(addr), - _ => { - println!("Reading from unknown IO control 0x{:04x}", addr); - 0x00 - } - }, - 0x40 | 0x50 | 0x60 | 0x70 => self.ppu.read(addr), + 0xf00 => match addr & 0x00ff { + 0x0f => { + let value = if self.ppu.int_vblank() { 0x01 } else { 0x00 } + | if self.timer.int_tima() { 0x04 } else { 0x00 }; + value + } + 0x80..=0xfe => self.ppu.hram[(addr & 0x007f) as usize], + 0xff => self.ie, + _ => match addr & 0x00f0 { + 0x00 => match addr & 0x00ff { + 0x00 => self.pad.read(addr), + 0x04..=0x07 => self.timer.read(addr), _ => { println!("Reading from unknown IO control 0x{:04x}", addr); 0x00 } + }, + 0x40 | 0x50 | 0x60 | 0x70 => self.ppu.read(addr), + _ => { + println!("Reading from unknown IO control 0x{:04x}", addr); + 0x00 } - } - } + }, + }, addr => panic!("Reading from unknown location 0x{:04x}", addr), }, addr => panic!("Reading from unknown location 0x{:04x}", addr), @@ -168,15 +170,18 @@ impl Mmu { self.ppu.oam[(addr & 0x009f) as usize] = value; self.ppu.update_object(addr, value); } - 0xf00 => { - if addr == 0xffff { - self.ie = value; - } else if addr >= 0xff80 { - self.ppu.hram[(addr & 0x007f) as usize] = value; - } else { + 0xf00 => match addr & 0x00ff { + 0x0f => { + self.ppu.set_int_vblank(value & 0x01 == 0x01); + self.timer.set_int_tima(value & 0x04 == 0x04); + } + 0x80..=0xfe => self.ppu.hram[(addr & 0x007f) as usize] = value, + 0xff => self.ie = value, + _ => { match addr & 0x00f0 { 0x00 => match addr & 0x00ff { 0x00 => self.pad.write(addr, value), + 0x04..=0x07 => self.timer.write(addr, value), _ => println!("Writing to unknown IO control 0x{:04x}", addr), }, 0x40 | 0x60 | 0x70 => { @@ -198,7 +203,7 @@ impl Mmu { _ => println!("Writing to unknown IO control 0x{:04x}", addr), } } - } + }, addr => panic!("Writing in unknown location 0x{:04x}", addr), }, addr => panic!("Writing in unknown location 0x{:04x}", addr), diff --git a/src/ppu.rs b/src/ppu.rs index 66a5c397a780fd1fdcadd01936fc4e7f29d27b40..c2073b2fde7864f4103b46f4840f55cf6669964f 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -498,6 +498,10 @@ impl Ppu { self.int_vblank } + pub fn set_int_vblank(&mut self, value: bool) { + self.int_vblank = value; + } + pub fn ack_vblank(&mut self) { self.int_vblank = false; } @@ -705,10 +709,10 @@ impl Ppu { // the object is only considered visible if it's a priority // or if the underlying pixel is transparent (zero value) let is_visible = obj.priority || self.color_buffer[color_offset] == 0; - if is_visible { + let pixel = tile_row[if obj.xflip { 7 - x } else { x }]; + if is_visible && pixel != 0 { // obtains the current pixel data from the tile row and // re-maps it according to the object palette - let pixel = tile_row[if obj.xflip { 7 - x } else { x }]; let color = palette[pixel as usize]; // sets the color pixel in the frame buffer diff --git a/src/timer.rs b/src/timer.rs index 0302f9be229362cfbb4156f9d09c8190a8c398e8..0169e9e8ceec2700463b66134338174179ef1c15 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -3,7 +3,11 @@ pub struct Timer { tima: u8, tma: u8, tac: u8, - ratio: u16, + div_clock: u16, + tima_clock: u16, + tima_enabled: bool, + tima_ratio: u16, + int_tima: bool, } impl Timer { @@ -13,15 +17,73 @@ impl Timer { tima: 0, tma: 0, tac: 0x0, - ratio: 1024 + div_clock: 0, + tima_clock: 0, + tima_enabled: false, + tima_ratio: 1024, + int_tima: false, + } + } + + pub fn clock(&mut self, cycles: u8) { + self.div_clock += cycles as u16; + self.tima_clock += cycles as u16; + + if self.div_clock >= 256 { + self.div = self.div.wrapping_add(1); + self.div_clock = self.div_clock - 256; + } + + if self.tima_enabled && self.tima_clock >= self.tima_ratio { + if self.tima == 0xff { + self.int_tima = true; + self.tima = self.tma; + } + + self.tima = self.tima.wrapping_add(1); + self.tima_clock = self.tima_clock - self.tima_ratio; } } pub fn read(&mut self, addr: u16) -> u8 { - 0x00 + match addr & 0x00ff { + 0x04 => self.div, + 0x05 => self.tima, + 0x06 => self.tma, + 0x07 => self.tac, + addr => panic!("Reding from unknown Timer location 0x{:04x}", addr), + } } pub fn write(&mut self, addr: u16, value: u8) { + match addr & 0x00ff { + 0x04 => self.div = 0, + 0x05 => self.tima = value, + 0x06 => self.tma = value, + 0x07 => { + self.tac = value; + match value & 0x03 { + 0x00 => self.tima_ratio = 1024, + 0x01 => self.tima_ratio = 16, + 0x02 => self.tima_ratio = 64, + 0x03 => self.tima_ratio = 256, + value => panic!("Invalid TAC value 0x{:02x}", value), + } + self.tima_enabled = value & 0x04 == 0x04; + } + addr => panic!("Writing to unknown Timer location 0x{:04x}", addr), + } + } + + pub fn int_tima(&self) -> bool { + self.int_tima } -} + pub fn set_int_tima(&mut self, value: bool) { + self.int_tima = value; + } + + pub fn ack_tima(&mut self) { + self.int_tima = false; + } +}