More accurate emulation of STOP
This commit is contained in:
parent
efb644bc72
commit
6f6f72dcbd
@ -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 */
|
||||
|
@ -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++);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user