Skip to content
Snippets Groups Projects
Verified Commit 164e108f authored by João Magalhães's avatar João Magalhães :rocket:
Browse files

feat: initial timer support

parent 735728b6
No related branches found
No related tags found
1 merge request!4Initial support for timers
Pipeline #934 passed
...@@ -84,7 +84,8 @@ fn main() { ...@@ -84,7 +84,8 @@ fn main() {
let mut game_boy = GameBoy::new(); let mut game_boy = GameBoy::new();
game_boy.load_boot_default(); 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.prop/alleyway.gb");
//game_boy.load_rom_file("../../res/roms/firstwhite.gb"); //game_boy.load_rom_file("../../res/roms/firstwhite.gb");
......
...@@ -4,7 +4,8 @@ use crate::{ ...@@ -4,7 +4,8 @@ use crate::{
inst::{EXTENDED, INSTRUCTIONS}, inst::{EXTENDED, INSTRUCTIONS},
mmu::Mmu, mmu::Mmu,
pad::Pad, pad::Pad,
ppu::Ppu, timer::Timer, ppu::Ppu,
timer::Timer,
}; };
pub const PREFIX: u8 = 0xcb; pub const PREFIX: u8 = 0xcb;
...@@ -129,6 +130,27 @@ impl Cpu { ...@@ -129,6 +130,27 @@ impl Cpu {
return 16; 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 // in case the CPU is currently in the halted state
...@@ -191,7 +213,7 @@ impl Cpu { ...@@ -191,7 +213,7 @@ impl Cpu {
pub fn pad(&mut self) -> &mut Pad { pub fn pad(&mut self) -> &mut Pad {
self.mmu().pad() self.mmu().pad()
} }
#[inline(always)] #[inline(always)]
pub fn timer(&mut self) -> &mut Timer { pub fn timer(&mut self) -> &mut Timer {
self.mmu().timer() self.mmu().timer()
......
...@@ -9,8 +9,6 @@ pub enum BootRom { ...@@ -9,8 +9,6 @@ pub enum BootRom {
MgbBootix, MgbBootix,
} }
/// Static data corresponding to the DMG boot ROM
/// allows freely using the emulator without external dependency.
pub const DMG_BOOT: [u8; 256] = [ 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, 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, 12, 62, 243, 226, 50, 62, 119, 119, 62, 252, 224, 71, 17, 4, 1, 33, 16, 128, 26, 205, 149, 0,
......
...@@ -4,7 +4,8 @@ use crate::{ ...@@ -4,7 +4,8 @@ use crate::{
mmu::Mmu, mmu::Mmu,
pad::{Pad, PadKey}, pad::{Pad, PadKey},
ppu::{Ppu, Tile, FRAME_BUFFER_SIZE}, ppu::{Ppu, Tile, FRAME_BUFFER_SIZE},
util::read_file, timer::Timer, timer::Timer,
util::read_file,
}; };
#[cfg(feature = "wasm")] #[cfg(feature = "wasm")]
...@@ -51,6 +52,7 @@ impl GameBoy { ...@@ -51,6 +52,7 @@ impl GameBoy {
pub fn clock(&mut self) -> u8 { pub fn clock(&mut self) -> u8 {
let cycles = self.cpu_clock(); let cycles = self.cpu_clock();
self.ppu_clock(cycles); self.ppu_clock(cycles);
self.timer_clock(cycles);
cycles cycles
} }
...@@ -62,6 +64,10 @@ impl GameBoy { ...@@ -62,6 +64,10 @@ impl GameBoy {
self.ppu().clock(cycles) self.ppu().clock(cycles)
} }
pub fn timer_clock(&mut self, cycles: u8) {
self.timer().clock(cycles)
}
pub fn load_rom(&mut self, data: &[u8]) { pub fn load_rom(&mut self, data: &[u8]) {
self.cpu.mmu().write_rom(0x0000, data); self.cpu.mmu().write_rom(0x0000, data);
} }
......
...@@ -96,28 +96,30 @@ impl Mmu { ...@@ -96,28 +96,30 @@ impl Mmu {
0x000 | 0x100 | 0x200 | 0x300 | 0x400 | 0x500 | 0x600 | 0x700 | 0x800 | 0x900 0x000 | 0x100 | 0x200 | 0x300 | 0x400 | 0x500 | 0x600 | 0x700 | 0x800 | 0x900
| 0xa00 | 0xb00 | 0xc00 | 0xd00 => self.ram[(addr & 0x1fff) as usize], | 0xa00 | 0xb00 | 0xc00 | 0xd00 => self.ram[(addr & 0x1fff) as usize],
0xe00 => self.ppu.oam[(addr & 0x009f) as usize], 0xe00 => self.ppu.oam[(addr & 0x009f) as usize],
0xf00 => { 0xf00 => match addr & 0x00ff {
if addr == 0xffff { 0x0f => {
self.ie let value = if self.ppu.int_vblank() { 0x01 } else { 0x00 }
} else if addr >= 0xff80 { | if self.timer.int_tima() { 0x04 } else { 0x00 };
self.ppu.hram[(addr & 0x007f) as usize] value
} else { }
match addr & 0x00f0 { 0x80..=0xfe => self.ppu.hram[(addr & 0x007f) as usize],
0x00 => match addr & 0x00ff { 0xff => self.ie,
0x00 => self.pad.read(addr), _ => match addr & 0x00f0 {
_ => { 0x00 => match addr & 0x00ff {
println!("Reading from unknown IO control 0x{:04x}", addr); 0x00 => self.pad.read(addr),
0x00 0x04..=0x07 => self.timer.read(addr),
}
},
0x40 | 0x50 | 0x60 | 0x70 => self.ppu.read(addr),
_ => { _ => {
println!("Reading from unknown IO control 0x{:04x}", addr); println!("Reading from unknown IO control 0x{:04x}", addr);
0x00 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),
}, },
addr => panic!("Reading from unknown location 0x{:04x}", addr), addr => panic!("Reading from unknown location 0x{:04x}", addr),
...@@ -168,15 +170,18 @@ impl Mmu { ...@@ -168,15 +170,18 @@ impl Mmu {
self.ppu.oam[(addr & 0x009f) as usize] = value; self.ppu.oam[(addr & 0x009f) as usize] = value;
self.ppu.update_object(addr, value); self.ppu.update_object(addr, value);
} }
0xf00 => { 0xf00 => match addr & 0x00ff {
if addr == 0xffff { 0x0f => {
self.ie = value; self.ppu.set_int_vblank(value & 0x01 == 0x01);
} else if addr >= 0xff80 { self.timer.set_int_tima(value & 0x04 == 0x04);
self.ppu.hram[(addr & 0x007f) as usize] = value; }
} else { 0x80..=0xfe => self.ppu.hram[(addr & 0x007f) as usize] = value,
0xff => self.ie = value,
_ => {
match addr & 0x00f0 { match addr & 0x00f0 {
0x00 => match addr & 0x00ff { 0x00 => match addr & 0x00ff {
0x00 => self.pad.write(addr, value), 0x00 => self.pad.write(addr, value),
0x04..=0x07 => self.timer.write(addr, value),
_ => println!("Writing to unknown IO control 0x{:04x}", addr), _ => println!("Writing to unknown IO control 0x{:04x}", addr),
}, },
0x40 | 0x60 | 0x70 => { 0x40 | 0x60 | 0x70 => {
...@@ -198,7 +203,7 @@ impl Mmu { ...@@ -198,7 +203,7 @@ impl Mmu {
_ => println!("Writing to unknown IO control 0x{:04x}", addr), _ => 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),
}, },
addr => panic!("Writing in unknown location 0x{:04x}", addr), addr => panic!("Writing in unknown location 0x{:04x}", addr),
......
...@@ -498,6 +498,10 @@ impl Ppu { ...@@ -498,6 +498,10 @@ impl Ppu {
self.int_vblank self.int_vblank
} }
pub fn set_int_vblank(&mut self, value: bool) {
self.int_vblank = value;
}
pub fn ack_vblank(&mut self) { pub fn ack_vblank(&mut self) {
self.int_vblank = false; self.int_vblank = false;
} }
...@@ -705,10 +709,10 @@ impl Ppu { ...@@ -705,10 +709,10 @@ impl Ppu {
// the object is only considered visible if it's a priority // the object is only considered visible if it's a priority
// or if the underlying pixel is transparent (zero value) // or if the underlying pixel is transparent (zero value)
let is_visible = obj.priority || self.color_buffer[color_offset] == 0; 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 // obtains the current pixel data from the tile row and
// re-maps it according to the object palette // 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]; let color = palette[pixel as usize];
// sets the color pixel in the frame buffer // sets the color pixel in the frame buffer
......
...@@ -3,7 +3,11 @@ pub struct Timer { ...@@ -3,7 +3,11 @@ pub struct Timer {
tima: u8, tima: u8,
tma: u8, tma: u8,
tac: u8, tac: u8,
ratio: u16, div_clock: u16,
tima_clock: u16,
tima_enabled: bool,
tima_ratio: u16,
int_tima: bool,
} }
impl Timer { impl Timer {
...@@ -13,15 +17,73 @@ impl Timer { ...@@ -13,15 +17,73 @@ impl Timer {
tima: 0, tima: 0,
tma: 0, tma: 0,
tac: 0x0, 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 { 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) { 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;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment