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

Merge branch 'joamag/timer' into 'master'

Initial support for timers

See merge request !4
parents e58ffb01 164e108f
No related branches found
No related tags found
1 merge request!4Initial support for timers
Pipeline #935 passed
...@@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
* Support for sprite drawing, works wit Tetris * Support for sprite drawing, works with Tetris
* Support for timers
### Changed ### Changed
......
...@@ -34,7 +34,7 @@ fn start_sdl() -> Graphics { ...@@ -34,7 +34,7 @@ fn start_sdl() -> Graphics {
// creates the system window that is going to be used to // creates the system window that is going to be used to
// show the emulator and sets it to the central are o screen // show the emulator and sets it to the central are o screen
let mut window = video_subsystem let window = video_subsystem
.window( .window(
TITLE, TITLE,
2 as u32 * DISPLAY_WIDTH as u32, //@todo check screen scale 2 as u32 * DISPLAY_WIDTH as u32, //@todo check screen scale
...@@ -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");
......
...@@ -5,6 +5,7 @@ use crate::{ ...@@ -5,6 +5,7 @@ use crate::{
mmu::Mmu, mmu::Mmu,
pad::Pad, pad::Pad,
ppu::Ppu, 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
...@@ -192,6 +214,11 @@ impl Cpu { ...@@ -192,6 +214,11 @@ impl Cpu {
self.mmu().pad() self.mmu().pad()
} }
#[inline(always)]
pub fn timer(&mut self) -> &mut Timer {
self.mmu().timer()
}
#[inline(always)] #[inline(always)]
pub fn halted(&self) -> bool { pub fn halted(&self) -> bool {
self.halted self.halted
......
...@@ -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,6 +4,7 @@ use crate::{ ...@@ -4,6 +4,7 @@ use crate::{
mmu::Mmu, mmu::Mmu,
pad::{Pad, PadKey}, pad::{Pad, PadKey},
ppu::{Ppu, Tile, FRAME_BUFFER_SIZE}, ppu::{Ppu, Tile, FRAME_BUFFER_SIZE},
timer::Timer,
util::read_file, util::read_file,
}; };
...@@ -22,9 +23,10 @@ pub struct GameBoy { ...@@ -22,9 +23,10 @@ pub struct GameBoy {
impl GameBoy { impl GameBoy {
#[cfg_attr(feature = "wasm", wasm_bindgen(constructor))] #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))]
pub fn new() -> Self { pub fn new() -> Self {
let pad = Pad::new();
let ppu = Ppu::new(); let ppu = Ppu::new();
let mmu = Mmu::new(ppu, pad); let pad = Pad::new();
let timer = Timer::new();
let mmu = Mmu::new(ppu, pad, timer);
let cpu = Cpu::new(mmu); let cpu = Cpu::new(mmu);
Self { cpu: cpu } Self { cpu: cpu }
} }
...@@ -50,6 +52,7 @@ impl GameBoy { ...@@ -50,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
} }
...@@ -61,6 +64,10 @@ impl GameBoy { ...@@ -61,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);
} }
...@@ -144,6 +151,10 @@ impl GameBoy { ...@@ -144,6 +151,10 @@ impl GameBoy {
self.cpu.pad() self.cpu.pad()
} }
pub fn timer(&mut self) -> &mut Timer {
self.cpu.timer()
}
pub fn frame_buffer(&mut self) -> &Box<[u8; FRAME_BUFFER_SIZE]> { pub fn frame_buffer(&mut self) -> &Box<[u8; FRAME_BUFFER_SIZE]> {
&(self.ppu().frame_buffer) &(self.ppu().frame_buffer)
} }
......
...@@ -5,4 +5,5 @@ pub mod inst; ...@@ -5,4 +5,5 @@ pub mod inst;
pub mod mmu; pub mod mmu;
pub mod pad; pub mod pad;
pub mod ppu; pub mod ppu;
pub mod timer;
pub mod util; pub mod util;
use crate::{pad::Pad, ppu::Ppu}; use crate::{pad::Pad, ppu::Ppu, timer::Timer};
pub const BIOS_SIZE: usize = 256; pub const BIOS_SIZE: usize = 256;
pub const ROM_SIZE: usize = 32768; pub const ROM_SIZE: usize = 32768;
...@@ -6,24 +6,29 @@ pub const RAM_SIZE: usize = 8192; ...@@ -6,24 +6,29 @@ pub const RAM_SIZE: usize = 8192;
pub const ERAM_SIZE: usize = 8192; pub const ERAM_SIZE: usize = 8192;
pub struct Mmu { pub struct Mmu {
/// Register that controls the interrupts that are considered
/// to be enabled and should be triggered.
pub ie: u8,
/// Reference to the PPU (Pixel Processing Unit) that is going
/// to be used both for VRAM reading/writing and to forward
/// some of the access operations.
ppu: Ppu, ppu: Ppu,
pad: Pad, pad: Pad,
timer: Timer,
boot_active: bool, boot_active: bool,
boot: [u8; BIOS_SIZE], boot: [u8; BIOS_SIZE],
rom: [u8; ROM_SIZE], rom: [u8; ROM_SIZE],
ram: [u8; RAM_SIZE], ram: [u8; RAM_SIZE],
eram: [u8; RAM_SIZE], eram: [u8; RAM_SIZE],
/// Registers that controls the interrupts that are considered
/// to be enabled and should be triggered.
pub ie: u8,
} }
impl Mmu { impl Mmu {
pub fn new(ppu: Ppu, pad: Pad) -> Self { pub fn new(ppu: Ppu, pad: Pad, timer: Timer) -> Self {
Self { Self {
ppu: ppu, ppu: ppu,
pad: pad, pad: pad,
timer: timer,
boot_active: true, boot_active: true,
boot: [0u8; BIOS_SIZE], boot: [0u8; BIOS_SIZE],
rom: [0u8; ROM_SIZE], rom: [0u8; ROM_SIZE],
...@@ -49,6 +54,10 @@ impl Mmu { ...@@ -49,6 +54,10 @@ impl Mmu {
&mut self.pad &mut self.pad
} }
pub fn timer(&mut self) -> &mut Timer {
&mut self.timer
}
pub fn boot_active(&self) -> bool { pub fn boot_active(&self) -> bool {
self.boot_active self.boot_active
} }
...@@ -87,28 +96,30 @@ impl Mmu { ...@@ -87,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),
...@@ -159,15 +170,18 @@ impl Mmu { ...@@ -159,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 => {
...@@ -189,7 +203,7 @@ impl Mmu { ...@@ -189,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),
......
...@@ -327,22 +327,22 @@ impl Ppu { ...@@ -327,22 +327,22 @@ impl Ppu {
match self.mode { match self.mode {
PpuMode::OamRead => { PpuMode::OamRead => {
if self.mode_clock >= 80 { if self.mode_clock >= 80 {
self.mode_clock = 0;
self.mode = PpuMode::VramRead; self.mode = PpuMode::VramRead;
self.mode_clock = self.mode_clock - 80;
} }
} }
PpuMode::VramRead => { PpuMode::VramRead => {
if self.mode_clock >= 172 { if self.mode_clock >= 172 {
self.render_line(); self.render_line();
self.mode_clock = 0;
self.mode = PpuMode::HBlank; self.mode = PpuMode::HBlank;
self.mode_clock = self.mode_clock - 172;
} }
} }
PpuMode::HBlank => { PpuMode::HBlank => {
if self.mode_clock >= 204 { if self.mode_clock >= 204 {
// increments the register that holds the // increments the register that holds the
// information about the current line in drawign // information about the current line in drawing
self.ly += 1; self.ly += 1;
// in case we've reached the end of the // in case we've reached the end of the
...@@ -354,7 +354,7 @@ impl Ppu { ...@@ -354,7 +354,7 @@ impl Ppu {
self.mode = PpuMode::OamRead; self.mode = PpuMode::OamRead;
} }
self.mode_clock = 0; self.mode_clock = self.mode_clock - 204;
} }
} }
PpuMode::VBlank => { PpuMode::VBlank => {
...@@ -369,7 +369,7 @@ impl Ppu { ...@@ -369,7 +369,7 @@ impl Ppu {
self.ly = 0; self.ly = 0;
} }
self.mode_clock = 0; self.mode_clock = self.mode_clock - 456;
} }
} }
} }
...@@ -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,13 +709,13 @@ impl Ppu { ...@@ -705,13 +709,13 @@ 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];
// set the color pixel in the frame buffer // sets the color pixel in the frame buffer
self.frame_buffer[frame_offset] = color[0]; self.frame_buffer[frame_offset] = color[0];
self.frame_buffer[frame_offset + 1] = color[1]; self.frame_buffer[frame_offset + 1] = color[1];
self.frame_buffer[frame_offset + 2] = color[2]; self.frame_buffer[frame_offset + 2] = color[2];
......
pub struct Timer {
div: u8,
tima: u8,
tma: u8,
tac: u8,
div_clock: u16,
tima_clock: u16,
tima_enabled: bool,
tima_ratio: u16,
int_tima: bool,
}
impl Timer {
pub fn new() -> Self {
Self {
div: 0,
tima: 0,
tma: 0,
tac: 0x0,
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 {
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;
}
}
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