Skip to content

Commit

Permalink
Revert "Divide number of cycles by 2 in double-speed mode"
Browse files Browse the repository at this point in the history
This reverts commit f493dd1.
  • Loading branch information
aksiksi committed Jul 13, 2024
1 parent 7e60034 commit 12e58e3
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 41 deletions.
11 changes: 5 additions & 6 deletions lib/src/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,20 +197,19 @@ impl Cpu {
cycles.taken() as u16
};

cycles += int_cycles as u16;

if self.speed {
cycles /= 2;
}

if self.stopped {
// If we've entered STOP mode for a speed switch, block the CPU for a bit.
// Also, do not run through DMA.
cycles += 8200;
} else {
cycles += int_cycles as u16;
cycles += self.dma(cycles);
}

if self.speed {
cycles /= 2;
}

(cycles, inst)
}

Expand Down
36 changes: 20 additions & 16 deletions lib/src/dma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,33 +54,31 @@ impl DmaController {
let mut cycles_taken = 0;

// OAM DMA
// Runs twice as fast in double-speed mode
if memory.ppu().oam_dma_active {
self.oam_dma(cycles, memory, speed);
self.oam_dma(cycles, memory);
}

// HDMA
if self.cgb && memory.io().hdma_active {
// HDMA is a blocking operation. However, it needs the number of
// cycles spent in the CPU to be able to figure out the next PPU
// mode for the HBLANK check.
cycles_taken = self.hdma(cycles, memory, speed);
cycles_taken = self.hdma(cycles, speed, memory);
}

cycles_taken
}

fn oam_dma(&mut self, mut cycles: u16, memory: &mut MemoryBus, speed: bool) {
fn oam_dma(&mut self, mut cycles: u16, memory: &mut MemoryBus) {
let dma_reg = memory.read(Self::DMA_ADDR);
let source_start_addr = (dma_reg as u16) << 8;
let cycles_per_byte = if speed { 2 } else { 4 };

// OAM DMA transfers 1 byte every 4 clock cycles
while cycles >= cycles_per_byte && self.oam_dma_counter < 162 {
while cycles >= 4 && self.oam_dma_counter < 162 {
if self.oam_dma_counter < 2 {
// Delay cycles
self.oam_dma_counter += 1;
cycles -= cycles_per_byte;
cycles -= 4;
continue;
}

Expand All @@ -94,7 +92,7 @@ impl DmaController {
memory.ppu_mut().oam[oam_index] = data;

self.oam_dma_counter += 1;
cycles -= cycles_per_byte;
cycles -= 4;
}

if self.oam_dma_counter == 162 {
Expand All @@ -104,8 +102,7 @@ impl DmaController {
}
}

// TODO(aksiksi): Rewrite this method... it's a bit messier than needed
fn hdma(&mut self, cycles: u16, memory: &mut MemoryBus, speed: bool) -> u16 {
fn hdma(&mut self, cycles: u16, speed: bool, memory: &mut MemoryBus) -> u16 {
// Determine source and destination addresses
let source_addr_upper = memory.read(0xFF51) as u16;
let source_addr_lower = memory.read(0xFF52) as u16;
Expand Down Expand Up @@ -153,15 +150,21 @@ impl DmaController {
// This needs to be done because we could be _just before_ HBLANK, but since
// DMA kicks in before the PPU has a chance to catch up, we need to look ahead
// based on the number of cycles spent in the CPU.
let (next_mode, _) = memory.ppu().next_mode(cycles);
let (next_mode, cycles_in_mode) = memory.ppu().next_mode(cycles, speed);

if next_mode != StatMode::Hblank {
// If we are not in HBLANK, there is nothing to do
return 0;
}

// One 16-byte chunk during each HBLANK, regardless of CPU speed
1
// Figure out the number of chunks we can transfer during this HBLANK
let num_chunks = if speed {
cycles_in_mode / 64
} else {
cycles_in_mode / 32
};

num_chunks as u8
};

// Figure out the start and end chunks for this step
Expand Down Expand Up @@ -215,11 +218,12 @@ impl DmaController {
memory.io_mut().hdma_reg_write(start_reg | remaining_length);
}

// Setup time + num chunks * bytes per chunk (16) * 2 cycles per byte
if speed {
110 + num_chunks as u16 * 16 * 2
// In double-speed mode, HDMA transfers a chunk (16 bytes) every
// 64 cycles
num_chunks as u16 * 64
} else {
220 + num_chunks as u16 * 16 * 2
num_chunks as u16 * 32
}
}
}
3 changes: 3 additions & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use cpu::Interrupt;
use cartridge::{Cartridge, Controller};
pub use error::{Error, Result};
use joypad::JoypadEvent;
use memory::MemoryWrite;
use ppu::FrameBuffer;

#[derive(serde::Deserialize, serde::Serialize)]
Expand Down Expand Up @@ -95,6 +96,8 @@ impl Gameboy {
}

if self.cpu.stopped {
// Reset DIV on speed switch
self.cpu.memory.write(0xFF04u16, 0u8);
self.cpu.stopped = false;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ impl MemoryBus {
// Execute a step of the PPU.
//
// The PPU will "catch up" based on what happened in the CPU.
self.ppu.step(cycles, interrupts);
self.ppu.step(cycles, speed, interrupts);

// Update the internal timer and trigger an interrupt, if needed
// Note that the timer may tick multiple times for a single instruction
Expand Down
32 changes: 23 additions & 9 deletions lib/src/ppu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,11 +486,19 @@ impl Ppu {
}

/// Returns the next: (dot, scanline, STAT mode)
fn get_next_dot(&self, cycles: u16) -> (u16, u8, StatMode) {
fn get_next_dot(&self, cycles: u16, speed: bool) -> (u16, u8, StatMode) {
let mut line = self.ly;
let mut dot = self.dot;

dot += cycles;
// Figure out the number of pixels to render in this step
let dots = if speed {
// If we are in double-speed mode, we render a pixel every 2 cycles
cycles / 2
} else {
cycles
};

dot += dots;

if dot >= Self::DOTS_PER_LINE {
// Move to the next scanline
Expand Down Expand Up @@ -530,8 +538,8 @@ impl Ppu {
///
/// If any interrupts need to be triggered, they are pushed to the input `interrupts`
/// vector.
pub fn step(&mut self, cycles: u16, interrupts: &mut Vec<Interrupt>) {
let (dot, line, mode) = self.get_next_dot(cycles);
pub fn step(&mut self, cycles: u16, speed: bool, interrupts: &mut Vec<Interrupt>) {
let (dot, line, mode) = self.get_next_dot(cycles, speed);

self.dot = dot;
self.ly = line;
Expand Down Expand Up @@ -591,20 +599,26 @@ impl Ppu {
stat_mode_change
}

/// Given a number of cycles, returns the _next_ mode and number of dots
/// Given a number of cycles, returns the _next_ mode and number of cycles
/// the PPU would remain in that mode.
pub fn next_mode(&self, cycles: u16) -> (StatMode, u16) {
let (.., mode) = self.get_next_dot(cycles);
pub fn next_mode(&self, cycles: u16, speed: bool) -> (StatMode, u16) {
let (.., mode) = self.get_next_dot(cycles, speed);

// Determine the number of dots the PPU would spend in the next mode
// Determine the number of cycles the PPU would spend in the next mode
let dots = match mode {
StatMode::OamScan => Self::OAM_SCAN_DOTS,
StatMode::OamRead => Self::OAM_READ_DOTS,
StatMode::Hblank => Self::HBLANK_DOTS,
StatMode::Vblank => Self::VBLANK_DOTS,
};

(mode, dots)
let cycles_in_mode = if speed {
dots * 2
} else {
dots
};

(mode, cycles_in_mode as u16)
}

/// Render pixel data to the internal frame buffer
Expand Down
12 changes: 3 additions & 9 deletions lib/src/timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl Timer {
return false;
}

let threshold = self.tima_ratio(speed);
let threshold = self.tima_ratio();
let mut interrupt = false;

self.tima_counter += cycles;
Expand All @@ -92,19 +92,13 @@ impl Timer {
/// Returns the ratio of CPU clock cycle time to current TIMA clock
/// mode in TAC
#[inline]
fn tima_ratio(&self, speed: bool) -> u16 {
let threshold = match self.tac & 0x3 {
fn tima_ratio(&self) -> u16 {
match self.tac & 0x3 {
0 => Self::TIMER_RATIO * 4,
1 => Self::TIMER_RATIO / 16,
2 => Self::TIMER_RATIO / 4,
3 => Self::TIMER_RATIO,
_ => unreachable!(),
};

if speed {
threshold / 2
} else {
threshold
}
}

Expand Down

0 comments on commit 12e58e3

Please sign in to comment.