Accurate emulation of (most aspects of) stop mode
This commit is contained in:
parent
4c34e0a6e0
commit
64879f5b02
190
Core/apu.c
190
Core/apu.c
@ -407,105 +407,107 @@ void GB_apu_run(GB_gameboy_t *gb)
|
|||||||
gb->apu.apu_cycles = 0;
|
gb->apu.apu_cycles = 0;
|
||||||
if (!cycles) return;
|
if (!cycles) return;
|
||||||
|
|
||||||
/* To align the square signal to 1MHz */
|
if (likely(!gb->stopped || GB_is_cgb(gb))) {
|
||||||
gb->apu.lf_div ^= cycles & 1;
|
/* To align the square signal to 1MHz */
|
||||||
gb->apu.noise_channel.alignment += cycles;
|
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) {
|
||||||
if (gb->apu.square_sweep_calculate_countdown > cycles) {
|
if (gb->apu.square_sweep_calculate_countdown > cycles) {
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* This code is not redundent, it's relevant when switching LFSR widths */
|
/* APU bug: sweep frequency is checked after adding the sweep delta twice */
|
||||||
gb->apu.noise_channel.lfsr &= ~high_bit_mask;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)) {
|
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) */
|
/* LCD is off, set screen to white or black (if LCD is on in stop mode) */
|
||||||
if (gb->sgb) {
|
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++) {
|
for (unsigned i = 0; i < WIDTH * LINES; i++) {
|
||||||
gb->sgb->screen_buffer[i] = color;
|
gb->sgb->screen_buffer[i] = color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
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, 0, 0, 0) :
|
||||||
gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
|
gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
|
||||||
for (unsigned i = 0; i < WIDTH * LINES; i++) {
|
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)
|
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_object_t *objects = (GB_object_t *) &gb->oam;
|
||||||
|
|
||||||
GB_STATE_MACHINE(gb, display, cycles, 2) {
|
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++) {
|
for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) {
|
||||||
if (GB_is_cgb(gb)) {
|
if (GB_is_cgb(gb)) {
|
||||||
add_object_from_index(gb, gb->oam_search_index);
|
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);
|
GB_SLEEP(gb, display, 8, 2);
|
||||||
if (!GB_is_cgb(gb)) {
|
if (!GB_is_cgb(gb)) {
|
||||||
|
@ -478,6 +478,7 @@ struct GB_gameboy_internal_s {
|
|||||||
bool lyc_interrupt_line;
|
bool lyc_interrupt_line;
|
||||||
bool cgb_palettes_blocked;
|
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.
|
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 */
|
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
|
||||||
|
@ -56,7 +56,6 @@ void GB_update_joyp(GB_gameboy_t *gb)
|
|||||||
if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) {
|
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. */
|
/* 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->io_registers[GB_IO_IF] |= 0x10;
|
||||||
gb->stopped = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gb->io_registers[GB_IO_JOYP] |= 0xC0;
|
gb->io_registers[GB_IO_JOYP] |= 0xC0;
|
||||||
|
@ -234,7 +234,15 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
|
|||||||
|
|
||||||
}
|
}
|
||||||
else {
|
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? */
|
/* Todo: is PC being actually read? */
|
||||||
@ -1389,7 +1397,15 @@ void GB_cpu_run(GB_gameboy_t *gb)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (gb->stopped) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,8 +210,10 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
// Affected by speed boost
|
// Affected by speed boost
|
||||||
gb->dma_cycles += cycles;
|
gb->dma_cycles += cycles;
|
||||||
|
|
||||||
GB_timers_run(gb, cycles);
|
if (!gb->stopped) {
|
||||||
advance_serial(gb, cycles);
|
GB_timers_run(gb, cycles);
|
||||||
|
advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode
|
||||||
|
}
|
||||||
|
|
||||||
gb->debugger_ticks += cycles;
|
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_input_ir_change += cycles;
|
||||||
gb->cycles_since_last_sync += cycles;
|
gb->cycles_since_last_sync += cycles;
|
||||||
gb->cycles_since_run += cycles;
|
gb->cycles_since_run += cycles;
|
||||||
GB_dma_run(gb);
|
if (!gb->stopped) { // TODO: Verify what happens in STOP mode
|
||||||
GB_hdma_run(gb);
|
GB_dma_run(gb);
|
||||||
|
GB_hdma_run(gb);
|
||||||
|
}
|
||||||
GB_apu_run(gb);
|
GB_apu_run(gb);
|
||||||
GB_display_run(gb, cycles);
|
GB_display_run(gb, cycles);
|
||||||
GB_ir_run(gb);
|
GB_ir_run(gb);
|
||||||
|
Loading…
Reference in New Issue
Block a user