A HBlank interrupt cannot occur in the last M-cycle of HBlank. Correct emulation of STAT access conflicts on the CGB (Test: CPU-E, single speed only). Fixes a minor graphical glitch in Pokémon Puzzle Challenge.

This commit is contained in:
Lior Halphon 2018-05-25 23:42:36 +03:00
parent 249acb04cc
commit 855ffb490a
4 changed files with 25 additions and 7 deletions

View File

@ -84,7 +84,7 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe
/*
Each line is 456 cycles. Without scrolling, sprites or a window::
Each line is 456 cycles. Without scrolling, sprites or a window:
Mode 2 - 80 cycles / OAM Transfer
Mode 3 - 172 cycles / Rendering
Mode 0 - 204 cycles / HBlank
@ -260,7 +260,7 @@ void GB_STAT_update(GB_gameboy_t *gb)
}
switch (gb->io_registers[GB_IO_STAT] & 3) {
case 0: gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8) && !gb->is_first_line_mode2; break;
case 0: gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8) && !gb->mode_0_interrupt_disable; break;
case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break;
/* The OAM interrupt is handled differently, it reads the writable flags from STAT less frequenctly,
and is not based on the mode bits of STAT. */
@ -582,8 +582,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
GB_SLEEP(gb, display, 23, 1);
}
/* Todo: Merge this with the normal line routine */
/* Handle the very first line 0 */
gb->is_first_line_mode2 = true;
gb->mode_0_interrupt_disable = true;
gb->current_line = 0;
gb->ly_for_comparison = 0;
gb->io_registers[GB_IO_STAT] &= ~3;
@ -601,7 +602,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
gb->vram_read_blocked = !gb->is_cgb;
gb->oam_write_blocked = true;
gb->vram_write_blocked = !gb->is_cgb;
gb->is_first_line_mode2 = false;
gb->mode_0_interrupt_disable = false;
GB_STAT_update(gb);
gb->cycles_for_line += 2;
@ -632,6 +633,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
/* Mode 0 is shorter in the very first line */
GB_SLEEP(gb, display, 5, LINE_LENGTH - gb->cycles_for_line - 8);
gb->mode_0_interrupt_disable = true;
gb->current_line = 1;
while (true) {
/* Lines 0 - 143 */
@ -656,6 +658,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
GB_SLEEP(gb, display, 7, 1);
gb->mode_0_interrupt_disable = false;
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 2;
gb->oam_write_blocked = true;
@ -820,6 +823,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
gb->hdma_starting = true;
}
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line);
gb->mode_0_interrupt_disable = true;
}
/* Lines 144 - 152 */
@ -842,6 +846,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 1;
gb->io_registers[GB_IO_IF] |= 1;
gb->mode_0_interrupt_disable = false;
trigger_oam_interrupt(gb);
GB_STAT_update(gb);
gb->oam_interrupt_line = false;

View File

@ -422,7 +422,7 @@ struct GB_gameboy_internal_s {
uint8_t oam_search_index;
uint8_t accessed_oam_row;
uint8_t extra_penalty_for_sprite_at_0;
bool is_first_line_mode2;
bool mode_0_interrupt_disable;
bool oam_interrupt_line;
bool lyc_interrupt_line;
);

View File

@ -665,7 +665,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
case GB_IO_STAT:
/* A DMG bug: http://www.devrs.com/gb/files/faqs.html#GBBugs */
if (!gb->is_cgb && !gb->stat_interrupt_line && !gb->is_first_line_mode2 &&
/* TODO: Confirm gb->mode_0_interrupt_disable usage */
if (!gb->is_cgb && !gb->stat_interrupt_line && !gb->mode_0_interrupt_disable &&
(gb->io_registers[GB_IO_STAT] & 0x3) < 2 && (gb->io_registers[GB_IO_LCDC] & 0x80)) {
gb->io_registers[GB_IO_IF] |= 2;
}

View File

@ -15,12 +15,15 @@ typedef enum {
GB_CONFLICT_READ_OR,
/* If the CPU and another component write at the same time, the CPU's value "wins" */
GB_CONFLICT_WRITE_CPU,
/* Register specific values */
GB_CONFLICT_STAT_CGB,
} GB_conflict_t;
/* Todo: How does double speed mode affect these? */
static const GB_conflict_t cgb_conflict_map[0x80] = {
[GB_IO_IF] = GB_CONFLICT_WRITE_CPU,
[GB_IO_LYC] = GB_CONFLICT_WRITE_CPU,
[GB_IO_STAT] = GB_CONFLICT_STAT_CGB,
/* Todo: most values not verified, and probably differ between revisions */
};
@ -92,14 +95,23 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_write_memory(gb, addr, value);
gb->pending_cycles = 5;
return;
}
case GB_CONFLICT_WRITE_CPU:
GB_advance_cycles(gb, gb->pending_cycles + 1);
GB_write_memory(gb, addr, value);
gb->pending_cycles = 3;
return;
case GB_CONFLICT_STAT_CGB: {
/* The LYC bit behaves differently */
uint8_t old_value = GB_read_memory(gb, addr);
GB_advance_cycles(gb, gb->pending_cycles);
GB_write_memory(gb, addr, (old_value & 0x40) | (value & ~0x40));
GB_advance_cycles(gb, 1);
GB_write_memory(gb, addr, value);
gb->pending_cycles = 3;
}
}
}