Accurate emulation of (most aspects of) stop mode

This commit is contained in:
Lior Halphon 2019-06-07 13:53:50 +03:00
parent 4c34e0a6e0
commit 64879f5b02
6 changed files with 136 additions and 105 deletions

View File

@ -407,6 +407,7 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.apu_cycles = 0; gb->apu.apu_cycles = 0;
if (!cycles) return; if (!cycles) return;
if (likely(!gb->stopped || GB_is_cgb(gb))) {
/* To align the square signal to 1MHz */ /* To align the square signal to 1MHz */
gb->apu.lf_div ^= cycles & 1; gb->apu.lf_div ^= cycles & 1;
gb->apu.noise_channel.alignment += cycles; gb->apu.noise_channel.alignment += cycles;
@ -508,6 +509,7 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.noise_channel.sample_countdown -= cycles_left; gb->apu.noise_channel.sample_countdown -= cycles_left;
} }
} }
}
if (gb->apu_output.sample_rate) { if (gb->apu_output.sample_rate) {
gb->apu_output.cycles_since_render += cycles; gb->apu_output.cycles_since_render += cycles;

View File

@ -137,13 +137,13 @@ static void display_vblank(GB_gameboy_t *gb)
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) {
/* LCD is off, set screen to white or black (if LCD is on in stop mode) */ /* LCD is off, set screen to white or black (if LCD is on in stop mode) */
if (gb->sgb) { if (gb->sgb) {
uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? 0x3 : 0x0; uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped && GB_is_cgb(gb) ? 0x3 : 0x0;
for (unsigned i = 0; i < WIDTH * LINES; i++) { for (unsigned i = 0; i < WIDTH * LINES; i++) {
gb->sgb->screen_buffer[i] = color; gb->sgb->screen_buffer[i] = color;
} }
} }
else { else {
uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped && GB_is_cgb(gb) ?
gb->rgb_encode_callback(gb, 0, 0, 0) : gb->rgb_encode_callback(gb, 0, 0, 0) :
gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
for (unsigned i = 0; i < WIDTH * LINES; i++) { for (unsigned i = 0; i < WIDTH * LINES; i++) {
@ -555,6 +555,15 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
*/ */
void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
{ {
/* The PPU does not advance while in STOP mode on the DMG */
if (gb->stopped && !GB_is_cgb(gb)) {
gb->cycles_in_stop_mode += cycles;
if (gb->cycles_in_stop_mode >= LCDC_PERIOD) {
gb->cycles_in_stop_mode -= LCDC_PERIOD;
display_vblank(gb);
}
return;
}
GB_object_t *objects = (GB_object_t *) &gb->oam; GB_object_t *objects = (GB_object_t *) &gb->oam;
GB_STATE_MACHINE(gb, display, cycles, 2) { GB_STATE_MACHINE(gb, display, cycles, 2) {
@ -696,7 +705,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) { for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) {
if (GB_is_cgb(gb)) { if (GB_is_cgb(gb)) {
add_object_from_index(gb, gb->oam_search_index); add_object_from_index(gb, gb->oam_search_index);
/* The CGB does not care about the accessed OAM row as there's no OAM bug*/ /* The CGB does not care about the accessed OAM row as there's no OAM bug */
} }
GB_SLEEP(gb, display, 8, 2); GB_SLEEP(gb, display, 8, 2);
if (!GB_is_cgb(gb)) { if (!GB_is_cgb(gb)) {

View File

@ -478,6 +478,7 @@ struct GB_gameboy_internal_s {
bool lyc_interrupt_line; bool lyc_interrupt_line;
bool cgb_palettes_blocked; bool cgb_palettes_blocked;
uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases. uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases.
uint32_t cycles_in_stop_mode;
); );
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */

View File

@ -56,7 +56,6 @@ void GB_update_joyp(GB_gameboy_t *gb)
if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) {
/* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */ /* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */
gb->io_registers[GB_IO_IF] |= 0x10; gb->io_registers[GB_IO_IF] |= 0x10;
gb->stopped = false;
} }
gb->io_registers[GB_IO_JOYP] |= 0xC0; gb->io_registers[GB_IO_JOYP] |= 0xC0;

View File

@ -233,9 +233,17 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
} }
} }
else {
if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) {
/* HW Bug? When STOP is executed while a button is down, the CPU halts forever
yet the other hardware keeps running. */
gb->interrupt_enable = 0;
gb->halted = true;
}
else { else {
gb->stopped = true; gb->stopped = true;
} }
}
/* Todo: is PC being actually read? */ /* Todo: is PC being actually read? */
gb->pc++; gb->pc++;
@ -1389,7 +1397,15 @@ void GB_cpu_run(GB_gameboy_t *gb)
return; return;
} }
if (gb->stopped) { if (gb->stopped) {
GB_advance_cycles(gb, 64); GB_advance_cycles(gb, 4);
if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) {
gb->stopped = false;
/* The CPU takes more time to wake up then the other components */
for (unsigned i = 0x800; i--;) {
GB_advance_cycles(gb, 0x40);
}
GB_advance_cycles(gb, 8);
}
return; return;
} }

View File

@ -210,8 +210,10 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
// Affected by speed boost // Affected by speed boost
gb->dma_cycles += cycles; gb->dma_cycles += cycles;
if (!gb->stopped) {
GB_timers_run(gb, cycles); GB_timers_run(gb, cycles);
advance_serial(gb, cycles); advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode
}
gb->debugger_ticks += cycles; gb->debugger_ticks += cycles;
@ -227,8 +229,10 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
gb->cycles_since_input_ir_change += cycles; gb->cycles_since_input_ir_change += cycles;
gb->cycles_since_last_sync += cycles; gb->cycles_since_last_sync += cycles;
gb->cycles_since_run += cycles; gb->cycles_since_run += cycles;
if (!gb->stopped) { // TODO: Verify what happens in STOP mode
GB_dma_run(gb); GB_dma_run(gb);
GB_hdma_run(gb); GB_hdma_run(gb);
}
GB_apu_run(gb); GB_apu_run(gb);
GB_display_run(gb, cycles); GB_display_run(gb, cycles);
GB_ir_run(gb); GB_ir_run(gb);