From 64879f5b02b3b583e9da7c8c5f84ca1794644b86 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 7 Jun 2019 13:53:50 +0300 Subject: [PATCH] Accurate emulation of (most aspects of) stop mode --- Core/apu.c | 192 ++++++++++++++++++++++++------------------------ Core/display.c | 15 +++- Core/gb.h | 1 + Core/joypad.c | 1 - Core/sm83_cpu.c | 20 ++++- Core/timing.c | 12 ++- 6 files changed, 136 insertions(+), 105 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index d5537a6..7a54c43 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -406,106 +406,108 @@ void GB_apu_run(GB_gameboy_t *gb) uint8_t cycles = gb->apu.apu_cycles >> 2; gb->apu.apu_cycles = 0; if (!cycles) return; + + if (likely(!gb->stopped || GB_is_cgb(gb))) { + /* To align the square signal to 1MHz */ + gb->apu.lf_div ^= cycles & 1; + gb->apu.noise_channel.alignment += cycles; - /* To align the square signal to 1MHz */ - gb->apu.lf_div ^= cycles & 1; - gb->apu.noise_channel.alignment += cycles; - - if (gb->apu.square_sweep_calculate_countdown) { - if (gb->apu.square_sweep_calculate_countdown > cycles) { - gb->apu.square_sweep_calculate_countdown -= cycles; - } - else { - /* APU bug: sweep frequency is checked after adding the sweep delta twice */ - gb->apu.new_sweep_sample_legnth = new_sweep_sample_legnth(gb); - if (gb->apu.new_sweep_sample_legnth > 0x7ff) { - gb->apu.is_active[GB_SQUARE_1] = false; - update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles); - gb->apu.sweep_enabled = false; - } - gb->apu.sweep_decreasing |= gb->io_registers[GB_IO_NR10] & 8; - gb->apu.square_sweep_calculate_countdown = 0; - } - } - - UNROLL - for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { - if (gb->apu.is_active[i]) { - uint8_t cycles_left = cycles; - while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) { - cycles_left -= gb->apu.square_channels[i].sample_countdown + 1; - gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1; - gb->apu.square_channels[i].current_sample_index++; - gb->apu.square_channels[i].current_sample_index &= 0x7; - - update_square_sample(gb, i); - } - if (cycles_left) { - gb->apu.square_channels[i].sample_countdown -= cycles_left; - } - } - } - - gb->apu.wave_channel.wave_form_just_read = false; - if (gb->apu.is_active[GB_WAVE]) { - uint8_t cycles_left = cycles; - while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { - cycles_left -= gb->apu.wave_channel.sample_countdown + 1; - gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; - gb->apu.wave_channel.current_sample_index++; - gb->apu.wave_channel.current_sample_index &= 0x1F; - gb->apu.wave_channel.current_sample = - gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index]; - update_sample(gb, GB_WAVE, - gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, - cycles - cycles_left); - gb->apu.wave_channel.wave_form_just_read = true; - } - if (cycles_left) { - gb->apu.wave_channel.sample_countdown -= cycles_left; - gb->apu.wave_channel.wave_form_just_read = false; - } - } - - if (gb->apu.is_active[GB_NOISE]) { - uint8_t cycles_left = cycles; - while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) { - cycles_left -= gb->apu.noise_channel.sample_countdown + 1; - gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3; - - /* Step LFSR */ - unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; - /* Todo: is this formula is different on a GBA? */ - bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1; - gb->apu.noise_channel.lfsr >>= 1; - - if (new_high_bit) { - gb->apu.noise_channel.lfsr |= high_bit_mask; + if (gb->apu.square_sweep_calculate_countdown) { + if (gb->apu.square_sweep_calculate_countdown > cycles) { + gb->apu.square_sweep_calculate_countdown -= cycles; } else { - /* This code is not redundent, it's relevant when switching LFSR widths */ - gb->apu.noise_channel.lfsr &= ~high_bit_mask; + /* APU bug: sweep frequency is checked after adding the sweep delta twice */ + gb->apu.new_sweep_sample_legnth = new_sweep_sample_legnth(gb); + if (gb->apu.new_sweep_sample_legnth > 0x7ff) { + gb->apu.is_active[GB_SQUARE_1] = false; + update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles); + gb->apu.sweep_enabled = false; + } + gb->apu.sweep_decreasing |= gb->io_registers[GB_IO_NR10] & 8; + gb->apu.square_sweep_calculate_countdown = 0; } - - gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; - if (gb->model == GB_MODEL_CGB_C) { - /* Todo: This was confirmed to happen on a CGB-C. This may or may not happen on pre-CGB models. - Because this degrades audio quality, and testing this on a pre-CGB device requires audio records, - I'll assume these devices are innocent until proven guilty. - - Also happens on CGB-B, but not on CGB-D. - */ - gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample; - } - gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1; - - update_sample(gb, GB_NOISE, - gb->apu.current_lfsr_sample ? - gb->apu.noise_channel.current_volume : 0, - 0); } - if (cycles_left) { - gb->apu.noise_channel.sample_countdown -= cycles_left; + + UNROLL + for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { + if (gb->apu.is_active[i]) { + uint8_t cycles_left = cycles; + while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) { + cycles_left -= gb->apu.square_channels[i].sample_countdown + 1; + gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1; + gb->apu.square_channels[i].current_sample_index++; + gb->apu.square_channels[i].current_sample_index &= 0x7; + + update_square_sample(gb, i); + } + if (cycles_left) { + gb->apu.square_channels[i].sample_countdown -= cycles_left; + } + } + } + + gb->apu.wave_channel.wave_form_just_read = false; + if (gb->apu.is_active[GB_WAVE]) { + uint8_t cycles_left = cycles; + while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { + cycles_left -= gb->apu.wave_channel.sample_countdown + 1; + gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; + gb->apu.wave_channel.current_sample_index++; + gb->apu.wave_channel.current_sample_index &= 0x1F; + gb->apu.wave_channel.current_sample = + gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index]; + update_sample(gb, GB_WAVE, + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, + cycles - cycles_left); + gb->apu.wave_channel.wave_form_just_read = true; + } + if (cycles_left) { + gb->apu.wave_channel.sample_countdown -= cycles_left; + gb->apu.wave_channel.wave_form_just_read = false; + } + } + + if (gb->apu.is_active[GB_NOISE]) { + uint8_t cycles_left = cycles; + while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) { + cycles_left -= gb->apu.noise_channel.sample_countdown + 1; + gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3; + + /* Step LFSR */ + unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; + /* Todo: is this formula is different on a GBA? */ + bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1; + gb->apu.noise_channel.lfsr >>= 1; + + if (new_high_bit) { + gb->apu.noise_channel.lfsr |= high_bit_mask; + } + else { + /* This code is not redundent, it's relevant when switching LFSR widths */ + gb->apu.noise_channel.lfsr &= ~high_bit_mask; + } + + gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; + if (gb->model == GB_MODEL_CGB_C) { + /* Todo: This was confirmed to happen on a CGB-C. This may or may not happen on pre-CGB models. + Because this degrades audio quality, and testing this on a pre-CGB device requires audio records, + I'll assume these devices are innocent until proven guilty. + + Also happens on CGB-B, but not on CGB-D. + */ + gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample; + } + gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1; + + update_sample(gb, GB_NOISE, + gb->apu.current_lfsr_sample ? + gb->apu.noise_channel.current_volume : 0, + 0); + } + if (cycles_left) { + gb->apu.noise_channel.sample_countdown -= cycles_left; + } } } diff --git a/Core/display.c b/Core/display.c index 22f6e8d..c84b3a9 100644 --- a/Core/display.c +++ b/Core/display.c @@ -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)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ 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++) { gb->sgb->screen_buffer[i] = color; } } 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, 0xFF, 0xFF, 0xFF); 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) { + /* 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_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++) { if (GB_is_cgb(gb)) { 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); if (!GB_is_cgb(gb)) { diff --git a/Core/gb.h b/Core/gb.h index 30654d5..f429b45 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -478,6 +478,7 @@ struct GB_gameboy_internal_s { bool lyc_interrupt_line; 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. + uint32_t cycles_in_stop_mode; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/joypad.c b/Core/joypad.c index 7713cbd..ebdf2f5 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -56,7 +56,6 @@ void GB_update_joyp(GB_gameboy_t *gb) 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. */ gb->io_registers[GB_IO_IF] |= 0x10; - gb->stopped = false; } gb->io_registers[GB_IO_JOYP] |= 0xC0; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 1416000..cf8bf09 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -234,7 +234,15 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) } else { - gb->stopped = true; + 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 { + gb->stopped = true; + } } /* Todo: is PC being actually read? */ @@ -1389,7 +1397,15 @@ void GB_cpu_run(GB_gameboy_t *gb) return; } 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; } diff --git a/Core/timing.c b/Core/timing.c index 8affd86..039f750 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -210,8 +210,10 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) // Affected by speed boost gb->dma_cycles += cycles; - GB_timers_run(gb, cycles); - advance_serial(gb, cycles); + if (!gb->stopped) { + GB_timers_run(gb, cycles); + advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode + } 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_last_sync += cycles; gb->cycles_since_run += cycles; - GB_dma_run(gb); - GB_hdma_run(gb); + if (!gb->stopped) { // TODO: Verify what happens in STOP mode + GB_dma_run(gb); + GB_hdma_run(gb); + } GB_apu_run(gb); GB_display_run(gb, cycles); GB_ir_run(gb);