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_PAL_FREQUENCY (21281370 / 5)
#define DIV_CYCLES (0x100)
#define INTERNAL_DIV_CYCLES (0x40000)
#if !defined(MIN)
#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;
uint8_t double_speed_alignment;
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 */

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)
{
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->oam_ppu_blocked = !gb->oam_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->vram_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)
{
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);
bool needs_alignment = false;
GB_advance_cycles(gb, 0x4);
/* 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;
if (gb->io_registers[GB_IO_LCDC] & 0x80) {
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->io_registers[GB_IO_KEY1] = 0;
enter_stop_mode(gb);
leave_stop_mode(gb);
if (!needs_alignment) {
GB_advance_cycles(gb, 0x4);
}
gb->speed_switch_halt_countdown = 0x20008;
}
else {
GB_timing_sync(gb);
if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) {
/* TODO: HW Bug? When STOP is executed while a button is down, the CPU enters halt
mode instead. Fine details not confirmed yet. */
if (immediate_exit) {
leave_stop_mode(gb);
if (!interrupt_pending) {
gb->halted = true;
gb->just_halted = true;
}
else {
enter_stop_mode(gb);
gb->speed_switch_halt_countdown = 0;
}
}
/* Todo: is PC being actually read? */
gb->pc++;
}
/* 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. */
if (gb->halted && !effective_ime && interrupt_queue) {
gb->halted = false;
gb->speed_switch_halt_countdown = 0;
}
/* Call interrupt */
else if (effective_ime && interrupt_queue) {
gb->halted = false;
gb->speed_switch_halt_countdown = 0;
uint16_t call_addr = 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)
{
/* TIMA increases when a specific high-bit becomes a low-bit. */
value &= INTERNAL_DIV_CYCLES - 1;
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])) {
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
}
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;
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) {
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 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,
semi_random, limit_start, pointer_control;
semi_random, limit_start, pointer_control, unsafe_speed_switch;
static unsigned int test_length = 60 * 40;
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)
{
if (!gb->cgb_double_speed && unsafe_speed_switch) {
return;
}
/* 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
load graphics. */
@ -129,7 +132,7 @@ static void vblank(GB_gameboy_t *gb)
gb->registers[GB_REGISTER_SP], gb->backtrace_size);
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");
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)
{
#define str(x) #x
#define xstr(x) str(x)
fprintf(stderr, "SameBoy Tester v" xstr(VERSION) "\n");
fprintf(stderr, "SameBoy Tester v" GB_VERSION "\n");
if (argc == 1) {
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;
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), "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 ||
strcmp((const char *)(gb.rom + 0x134), "HUGO2 1/2") == 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;
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 */
running = true;
gb.turbo = gb.turbo_dont_skip = gb.disable_rendering = true;