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
### Added
* Support for sprite drawing, works wit Tetris
* Support for sprite drawing, works with Tetris
* Support for timers
### Changed
......
......@@ -34,7 +34,7 @@ fn start_sdl() -> Graphics {
// creates the system window that is going to be used to
// show the emulator and sets it to the central are o screen
let mut window = video_subsystem
let window = video_subsystem
.window(
TITLE,
2 as u32 * DISPLAY_WIDTH as u32, //@todo check screen scale
......@@ -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");
......
......@@ -5,6 +5,7 @@ use crate::{
mmu::Mmu,
pad::Pad,
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
......@@ -192,6 +214,11 @@ impl Cpu {
self.mmu().pad()
}
#[inline(always)]
pub fn timer(&mut self) -> &mut Timer {
self.mmu().timer()
}
#[inline(always)]
pub fn halted(&self) -> bool {
self.halted
......
......@@ -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,
......
......@@ -4,6 +4,7 @@ use crate::{
mmu::Mmu,
pad::{Pad, PadKey},
ppu::{Ppu, Tile, FRAME_BUFFER_SIZE},
timer::Timer,
util::read_file,
};
......@@ -22,9 +23,10 @@ pub struct GameBoy {
impl GameBoy {
#[cfg_attr(feature = "wasm", wasm_bindgen(constructor))]
pub fn new() -> Self {
let pad = Pad::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);
Self { cpu: cpu }
}
......@@ -50,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
}
......@@ -61,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);
}
......@@ -144,6 +151,10 @@ impl GameBoy {
self.cpu.pad()
}
pub fn timer(&mut self) -> &mut Timer {
self.cpu.timer()
}
pub fn frame_buffer(&mut self) -> &Box<[u8; FRAME_BUFFER_SIZE]> {
&(self.ppu().frame_buffer)
}
......
......@@ -5,4 +5,5 @@ pub mod inst;
pub mod mmu;
pub mod pad;
pub mod ppu;
pub mod timer;
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 ROM_SIZE: usize = 32768;
......@@ -6,24 +6,29 @@ pub const RAM_SIZE: usize = 8192;
pub const ERAM_SIZE: usize = 8192;
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,
pad: Pad,
timer: Timer,
boot_active: bool,
boot: [u8; BIOS_SIZE],
rom: [u8; ROM_SIZE],
ram: [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 {
pub fn new(ppu: Ppu, pad: Pad) -> Self {
pub fn new(ppu: Ppu, pad: Pad, timer: Timer) -> Self {
Self {
ppu: ppu,
pad: pad,
timer: timer,
boot_active: true,
boot: [0u8; BIOS_SIZE],
rom: [0u8; ROM_SIZE],
......@@ -49,6 +54,10 @@ impl Mmu {
&mut self.pad
}
pub fn timer(&mut self) -> &mut Timer {
&mut self.timer
}
pub fn boot_active(&self) -> bool {
self.boot_active
}
......@@ -87,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),
......@@ -159,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 => {
......@@ -189,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),
......
......@@ -327,22 +327,22 @@ impl Ppu {
match self.mode {
PpuMode::OamRead => {
if self.mode_clock >= 80 {
self.mode_clock = 0;
self.mode = PpuMode::VramRead;
self.mode_clock = self.mode_clock - 80;
}
}
PpuMode::VramRead => {
if self.mode_clock >= 172 {
self.render_line();
self.mode_clock = 0;
self.mode = PpuMode::HBlank;
self.mode_clock = self.mode_clock - 172;
}
}
PpuMode::HBlank => {
if self.mode_clock >= 204 {
// increments the register that holds the
// information about the current line in drawign
// information about the current line in drawing
self.ly += 1;
// in case we've reached the end of the
......@@ -354,7 +354,7 @@ impl Ppu {
self.mode = PpuMode::OamRead;
}
self.mode_clock = 0;
self.mode_clock = self.mode_clock - 204;
}
}
PpuMode::VBlank => {
......@@ -369,7 +369,7 @@ impl Ppu {
self.ly = 0;
}
self.mode_clock = 0;
self.mode_clock = self.mode_clock - 456;
}
}
}
......@@ -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,13 +709,13 @@ 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];
// 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 + 1] = color[1];
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