More accurate emulation of STOP

This commit is contained in:
Lior Halphon 2021-07-10 15:07:23 +03:00
parent efb644bc72
commit 6f6f72dcbd
4 changed files with 70 additions and 38 deletions

View File

@ -297,7 +297,6 @@ typedef enum {
#define SGB_NTSC_FREQUENCY (21477272 / 5) #define SGB_NTSC_FREQUENCY (21477272 / 5)
#define SGB_PAL_FREQUENCY (21281370 / 5) #define SGB_PAL_FREQUENCY (21281370 / 5)
#define DIV_CYCLES (0x100) #define DIV_CYCLES (0x100)
#define INTERNAL_DIV_CYCLES (0x40000)
#if !defined(MIN) #if !defined(MIN)
#define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) #define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
@ -532,6 +531,8 @@ struct GB_gameboy_internal_s {
uint16_t serial_length; uint16_t serial_length;
uint8_t double_speed_alignment; uint8_t double_speed_alignment;
uint8_t serial_count; uint8_t serial_count;
int32_t speed_switch_halt_countdown;
uint8_t speed_switch_freeze; // Solely for realigning the PPU, should be removed when the odd modes are implemented
); );
/* APU */ /* APU */

View File

@ -357,6 +357,9 @@ static void nop(GB_gameboy_t *gb, uint8_t opcode)
static void enter_stop_mode(GB_gameboy_t *gb) static void enter_stop_mode(GB_gameboy_t *gb)
{ {
GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0); GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0);
if (!gb->ime) { // TODO: I don't trust this if,
gb->div_cycles = -4; // Emulate the CPU-side DIV-reset signal being held
}
gb->stopped = true; gb->stopped = true;
gb->oam_ppu_blocked = !gb->oam_read_blocked; gb->oam_ppu_blocked = !gb->oam_read_blocked;
gb->vram_ppu_blocked = !gb->vram_read_blocked; gb->vram_ppu_blocked = !gb->vram_read_blocked;
@ -369,53 +372,56 @@ static void leave_stop_mode(GB_gameboy_t *gb)
gb->oam_ppu_blocked = false; gb->oam_ppu_blocked = false;
gb->vram_ppu_blocked = false; gb->vram_ppu_blocked = false;
gb->cgb_palettes_ppu_blocked = false; gb->cgb_palettes_ppu_blocked = false;
/* The CPU takes more time to wake up then the other components */
for (unsigned i = 0x1FFF; i--;) {
GB_advance_cycles(gb, 0x10);
}
GB_advance_cycles(gb, gb->cgb_double_speed? 0x10 : 0xC);
GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0);
} }
static void stop(GB_gameboy_t *gb, uint8_t opcode) static void stop(GB_gameboy_t *gb, uint8_t opcode)
{ {
if (gb->io_registers[GB_IO_KEY1] & 0x1) { flush_pending_cycles(gb);
bool exit_by_joyp = ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF);
bool speed_switch = (gb->io_registers[GB_IO_KEY1] & 0x1) && !exit_by_joyp;
bool immediate_exit = speed_switch || exit_by_joyp;
bool interrupt_pending = (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F);
// When entering with IF&IE, the 2nd byte of STOP is actually executed
if (!exit_by_joyp) {
enter_stop_mode(gb);
}
if (!interrupt_pending) {
/* Todo: is PC being actually read? */
cycle_read_inc_oam_bug(gb, gb->pc++);
}
/* Todo: speed switching takes a fractional number of M-cycles. It make
every active component (APU, PPU) unaligned with the CPU. */
if (speed_switch) {
flush_pending_cycles(gb); flush_pending_cycles(gb);
bool needs_alignment = false;
GB_advance_cycles(gb, 0x4); if (gb->io_registers[GB_IO_LCDC] & 0x80) {
/* Make sure we keep the CPU ticks aligned correctly when returning from double speed mode */
if (gb->double_speed_alignment & 7) {
GB_advance_cycles(gb, 0x4);
needs_alignment = true;
GB_log(gb, "ROM triggered PPU odd mode, which is currently not supported. Reverting to even-mode.\n"); GB_log(gb, "ROM triggered PPU odd mode, which is currently not supported. Reverting to even-mode.\n");
if (gb->double_speed_alignment & 7) {
gb->speed_switch_freeze = 6;
}
else {
gb->speed_switch_freeze = 4;
}
} }
gb->cgb_double_speed ^= true; gb->cgb_double_speed ^= true;
gb->io_registers[GB_IO_KEY1] = 0; gb->io_registers[GB_IO_KEY1] = 0;
enter_stop_mode(gb); gb->speed_switch_halt_countdown = 0x20008;
leave_stop_mode(gb);
if (!needs_alignment) {
GB_advance_cycles(gb, 0x4);
}
} }
else {
GB_timing_sync(gb); if (immediate_exit) {
if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { leave_stop_mode(gb);
/* TODO: HW Bug? When STOP is executed while a button is down, the CPU enters halt if (!interrupt_pending) {
mode instead. Fine details not confirmed yet. */
gb->halted = true; gb->halted = true;
gb->just_halted = true;
} }
else { else {
enter_stop_mode(gb); gb->speed_switch_halt_countdown = 0;
} }
} }
/* Todo: is PC being actually read? */
gb->pc++;
} }
/* Operand naming conventions for functions: /* Operand naming conventions for functions:
@ -1603,11 +1609,13 @@ void GB_cpu_run(GB_gameboy_t *gb)
/* Wake up from HALT mode without calling interrupt code. */ /* Wake up from HALT mode without calling interrupt code. */
if (gb->halted && !effective_ime && interrupt_queue) { if (gb->halted && !effective_ime && interrupt_queue) {
gb->halted = false; gb->halted = false;
gb->speed_switch_halt_countdown = 0;
} }
/* Call interrupt */ /* Call interrupt */
else if (effective_ime && interrupt_queue) { else if (effective_ime && interrupt_queue) {
gb->halted = false; gb->halted = false;
gb->speed_switch_halt_countdown = 0;
uint16_t call_addr = gb->pc; uint16_t call_addr = gb->pc;
gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++); gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++);

View File

@ -145,7 +145,6 @@ static void increase_tima(GB_gameboy_t *gb)
static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value)
{ {
/* TIMA increases when a specific high-bit becomes a low-bit. */ /* TIMA increases when a specific high-bit becomes a low-bit. */
value &= INTERNAL_DIV_CYCLES - 1;
uint16_t triggers = gb->div_counter & ~value; uint16_t triggers = gb->div_counter & ~value;
if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & GB_TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) { if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & GB_TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) {
increase_tima(gb); increase_tima(gb);
@ -356,7 +355,24 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode
} }
if (gb->speed_switch_halt_countdown) {
gb->speed_switch_halt_countdown -= cycles;
if (gb->speed_switch_halt_countdown <= 0) {
gb->speed_switch_halt_countdown = 0;
gb->halted = false;
}
}
gb->debugger_ticks += cycles; gb->debugger_ticks += cycles;
if (gb->speed_switch_freeze) {
if (gb->speed_switch_freeze >= cycles) {
gb->speed_switch_freeze -= cycles;
return;
}
cycles -= gb->speed_switch_freeze;
gb->speed_switch_freeze = 0;
}
if (!gb->cgb_double_speed) { if (!gb->cgb_double_speed) {
cycles <<= 1; cycles <<= 1;

View File

@ -27,7 +27,7 @@ static FILE *log_file;
static void replace_extension(const char *src, size_t length, char *dest, const char *ext); static void replace_extension(const char *src, size_t length, char *dest, const char *ext);
static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster, push_slower, static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster, push_slower,
do_not_stop, push_a_twice, start_is_bad, allow_weird_sp_values, large_stack, push_right, do_not_stop, push_a_twice, start_is_bad, allow_weird_sp_values, large_stack, push_right,
semi_random, limit_start, pointer_control; semi_random, limit_start, pointer_control, unsafe_speed_switch;
static unsigned int test_length = 60 * 40; static unsigned int test_length = 60 * 40;
GB_gameboy_t gb; GB_gameboy_t gb;
@ -60,6 +60,9 @@ static char *async_input_callback(GB_gameboy_t *gb)
static void handle_buttons(GB_gameboy_t *gb) static void handle_buttons(GB_gameboy_t *gb)
{ {
if (!gb->cgb_double_speed && unsafe_speed_switch) {
return;
}
/* Do not press any buttons during the last two seconds, this might cause a /* Do not press any buttons during the last two seconds, this might cause a
screenshot to be taken while the LCD is off if the press makes the game screenshot to be taken while the LCD is off if the press makes the game
load graphics. */ load graphics. */
@ -129,7 +132,7 @@ static void vblank(GB_gameboy_t *gb)
gb->registers[GB_REGISTER_SP], gb->backtrace_size); gb->registers[GB_REGISTER_SP], gb->backtrace_size);
frames = test_length - 1; frames = test_length - 1;
} }
if (gb->halted && !gb->interrupt_enable) { if (gb->halted && !gb->interrupt_enable && gb->speed_switch_halt_countdown == 0) {
GB_log(gb, "The game is deadlocked.\n"); GB_log(gb, "The game is deadlocked.\n");
frames = test_length - 1; frames = test_length - 1;
} }
@ -265,9 +268,7 @@ static void replace_extension(const char *src, size_t length, char *dest, const
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
#define str(x) #x fprintf(stderr, "SameBoy Tester v" GB_VERSION "\n");
#define xstr(x) str(x)
fprintf(stderr, "SameBoy Tester v" xstr(VERSION) "\n");
if (argc == 1) { if (argc == 1) {
fprintf(stderr, "Usage: %s [--dmg] [--start] [--length seconds] [--sav] [--boot path to boot ROM]" fprintf(stderr, "Usage: %s [--dmg] [--start] [--length seconds] [--sav] [--boot path to boot ROM]"
@ -406,7 +407,8 @@ int main(int argc, char **argv)
strcmp((const char *)(gb.rom + 0x134), "ONI 5") == 0; strcmp((const char *)(gb.rom + 0x134), "ONI 5") == 0;
b_is_confirm = strcmp((const char *)(gb.rom + 0x134), "ELITE SOCCER") == 0 || b_is_confirm = strcmp((const char *)(gb.rom + 0x134), "ELITE SOCCER") == 0 ||
strcmp((const char *)(gb.rom + 0x134), "SOCCER") == 0 || strcmp((const char *)(gb.rom + 0x134), "SOCCER") == 0 ||
strcmp((const char *)(gb.rom + 0x134), "GEX GECKO") == 0; strcmp((const char *)(gb.rom + 0x134), "GEX GECKO") == 0 ||
strcmp((const char *)(gb.rom + 0x134), "BABE") == 0;
push_faster = strcmp((const char *)(gb.rom + 0x134), "MOGURA DE PON!") == 0 || push_faster = strcmp((const char *)(gb.rom + 0x134), "MOGURA DE PON!") == 0 ||
strcmp((const char *)(gb.rom + 0x134), "HUGO2 1/2") == 0 || strcmp((const char *)(gb.rom + 0x134), "HUGO2 1/2") == 0 ||
strcmp((const char *)(gb.rom + 0x134), "HUGO") == 0; strcmp((const char *)(gb.rom + 0x134), "HUGO") == 0;
@ -445,6 +447,11 @@ int main(int argc, char **argv)
pointer_control = memcmp((const char *)(gb.rom + 0x134), "LEGO ATEAM BLPP", strlen("LEGO ATEAM BLPP")) == 0; pointer_control = memcmp((const char *)(gb.rom + 0x134), "LEGO ATEAM BLPP", strlen("LEGO ATEAM BLPP")) == 0;
push_faster |= pointer_control; push_faster |= pointer_control;
/* Games that perform an unsafe speed switch, don't input until in double speed */
unsafe_speed_switch = strcmp((const char *)(gb.rom + 0x134), "GBVideo") == 0 || // lulz this is my fault
strcmp((const char *)(gb.rom + 0x134), "POKEMONGOLD 2") == 0; // Pokemon Adventure
/* Run emulation */ /* Run emulation */
running = true; running = true;
gb.turbo = gb.turbo_dont_skip = gb.disable_rendering = true; gb.turbo = gb.turbo_dont_skip = gb.disable_rendering = true;