mirror of https://github.com/LIJI32/SameBoy.git
Accurate emulation of NR10 write glitches
This commit is contained in:
parent
7542de74e7
commit
d662407df0
147
Core/apu.c
147
Core/apu.c
|
@ -476,11 +476,9 @@ static void trigger_sweep_calculation(GB_gameboy_t *gb)
|
|||
}
|
||||
|
||||
/* Recalculation and overflow check only occurs after a delay */
|
||||
gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div;
|
||||
if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) {
|
||||
// gb->apu.square_sweep_calculate_countdown += 2;
|
||||
}
|
||||
gb->apu.enable_zombie_calculate_stepping = false;
|
||||
gb->apu.square_sweep_calculate_countdown = gb->io_registers[GB_IO_NR10] & 0x7;
|
||||
gb->apu.square_sweep_calculate_countdown_reload_timer = 3 + (gb->model <= GB_MODEL_CGB_C);
|
||||
gb->apu.square_sweep_stop_calc_if_no_zombie_write = false;
|
||||
gb->apu.unshifted_sweep = !(gb->io_registers[GB_IO_NR10] & 0x7);
|
||||
gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7;
|
||||
}
|
||||
|
@ -618,10 +616,26 @@ static void step_lfsr(GB_gameboy_t *gb, unsigned cycles_offset)
|
|||
}
|
||||
}
|
||||
|
||||
static void sweep_calculation_done(GB_gameboy_t *gb, unsigned cycles)
|
||||
{
|
||||
/* APU bug: sweep frequency is checked after adding the sweep delta twice */
|
||||
if (gb->apu.channel_1_restart_hold == 0) {
|
||||
gb->apu.shadow_sweep_sample_length = gb->apu.square_channels[GB_SQUARE_1].sample_length;
|
||||
}
|
||||
if (gb->io_registers[GB_IO_NR10] & 8) {
|
||||
gb->apu.sweep_length_addend ^= 0x7FF;
|
||||
}
|
||||
if (gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend > 0x7FF && !(gb->io_registers[GB_IO_NR10] & 8)) {
|
||||
gb->apu.is_active[GB_SQUARE_1] = false;
|
||||
update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown * 2 - cycles);
|
||||
}
|
||||
gb->apu.channel1_completed_addend = gb->apu.sweep_length_addend;
|
||||
}
|
||||
|
||||
void GB_apu_run(GB_gameboy_t *gb, bool force)
|
||||
{
|
||||
uint32_t clock_rate = GB_get_clock_rate(gb) * 2;
|
||||
if (!force ||
|
||||
if (force ||
|
||||
(gb->apu.apu_cycles > 0x1000) ||
|
||||
(gb->apu_output.sample_cycles >= clock_rate) ||
|
||||
(gb->apu.square_sweep_calculate_countdown || gb->apu.channel_1_restart_hold) ||
|
||||
|
@ -671,26 +685,35 @@ void GB_apu_run(GB_gameboy_t *gb, bool force)
|
|||
/* To align the square signal to 1MHz */
|
||||
gb->apu.lf_div ^= cycles & 1;
|
||||
gb->apu.noise_channel.alignment += cycles;
|
||||
|
||||
unsigned sweep_cycles = cycles / 2;
|
||||
if ((cycles & 1) && !gb->apu.lf_div) {
|
||||
sweep_cycles++;
|
||||
}
|
||||
if (gb->apu.square_sweep_calculate_countdown_reload_timer > sweep_cycles) {
|
||||
gb->apu.square_sweep_calculate_countdown_reload_timer -= sweep_cycles;
|
||||
sweep_cycles = 0;
|
||||
if (gb->io_registers[GB_IO_NR10] & 0x7) {
|
||||
gb->apu.square_sweep_calculate_countdown = gb->io_registers[GB_IO_NR10] & 0x7;
|
||||
}
|
||||
}
|
||||
else {
|
||||
sweep_cycles -= gb->apu.square_sweep_calculate_countdown_reload_timer;
|
||||
gb->apu.square_sweep_calculate_countdown_reload_timer = 0;
|
||||
if (gb->apu.square_sweep_stop_calc_if_no_zombie_write) {
|
||||
gb->apu.square_sweep_stop_calc_if_no_zombie_write = 0;
|
||||
gb->apu.square_sweep_calculate_countdown = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (gb->apu.square_sweep_calculate_countdown &&
|
||||
(((gb->io_registers[GB_IO_NR10] & 7) || gb->apu.unshifted_sweep) ||
|
||||
gb->apu.square_sweep_calculate_countdown <= 3)) { // Calculation is paused if the lower bits are 0
|
||||
if (gb->apu.square_sweep_calculate_countdown > cycles) {
|
||||
gb->apu.square_sweep_calculate_countdown -= cycles;
|
||||
gb->apu.square_sweep_calculate_countdown <= 1)) { // Calculation is paused if the lower bits are 0
|
||||
if (gb->apu.square_sweep_calculate_countdown > sweep_cycles) {
|
||||
gb->apu.square_sweep_calculate_countdown -= sweep_cycles;
|
||||
}
|
||||
else {
|
||||
/* APU bug: sweep frequency is checked after adding the sweep delta twice */
|
||||
if (gb->apu.channel_1_restart_hold == 0) {
|
||||
gb->apu.shadow_sweep_sample_length = gb->apu.square_channels[GB_SQUARE_1].sample_length;
|
||||
}
|
||||
if (gb->io_registers[GB_IO_NR10] & 8) {
|
||||
gb->apu.sweep_length_addend ^= 0x7FF;
|
||||
}
|
||||
if (gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend > 0x7FF && !(gb->io_registers[GB_IO_NR10] & 8)) {
|
||||
gb->apu.is_active[GB_SQUARE_1] = false;
|
||||
update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles);
|
||||
}
|
||||
gb->apu.channel1_completed_addend = gb->apu.sweep_length_addend;
|
||||
sweep_calculation_done(gb, cycles);
|
||||
|
||||
gb->apu.square_sweep_calculate_countdown = 0;
|
||||
}
|
||||
|
@ -1077,8 +1100,75 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
break;
|
||||
|
||||
/* Square channels */
|
||||
case GB_IO_NR10:{
|
||||
case GB_IO_NR10: {
|
||||
bool old_negate = gb->io_registers[GB_IO_NR10] & 8;
|
||||
// TODO: Check all of these in APU odd mode
|
||||
if (gb->model <= GB_MODEL_CGB_C) {
|
||||
bool zombie_tick = !(gb->io_registers[GB_IO_NR10] & 7) &&
|
||||
(gb->apu.lf_div ^ gb->cgb_double_speed) &&
|
||||
gb->apu.square_sweep_calculate_countdown &&
|
||||
!gb->apu.square_sweep_calculate_countdown_reload_timer;
|
||||
gb->apu.square_sweep_stop_calc_if_no_zombie_write = false;
|
||||
if (zombie_tick) {
|
||||
gb->apu.square_sweep_calculate_countdown--;
|
||||
if (!gb->apu.square_sweep_calculate_countdown) {
|
||||
sweep_calculation_done(gb, 0);
|
||||
}
|
||||
}
|
||||
switch (gb->apu.square_sweep_calculate_countdown_reload_timer) {
|
||||
case 1: {
|
||||
if (!gb->apu.lf_div) {
|
||||
/* This is some instance-specific data corruption. It might also be affect by revision.
|
||||
At least for my CGB-0 (haven't tested any other CGB-0s), the '3' case is non-deterministic. */
|
||||
static const uint8_t corruption[8] = {0, 7, 5, 7, 3, 3, 5, 7}; // Two of my CGB-Cs, CGB-A
|
||||
// static const uint8_t corruption[8] = {0, 7, 1, 3, 3, 3, 5, 7}; // My other CGB-C
|
||||
// static const uint8_t corruption[8] = {0, 1, 1, 3, 3, 5, 5, 7}; // My CGB-B
|
||||
// static const uint8_t corruption[8] = {0, 7, 1, *, 3, 3, 5, 7}; // My CGB-0
|
||||
|
||||
gb->apu.square_sweep_calculate_countdown = corruption[gb->apu.square_sweep_calculate_countdown & 7];
|
||||
gb->apu.square_sweep_calculate_countdown_reload_timer = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
if (gb->apu.lf_div) {
|
||||
gb->apu.square_sweep_calculate_countdown = value & 7; // TODO: Confirm for non-zero?
|
||||
gb->apu.square_sweep_calculate_countdown_reload_timer = 0;
|
||||
}
|
||||
else {
|
||||
case 3:
|
||||
// Countdown just reloaded, re-reload it with glitch value (FF & 7)
|
||||
gb->apu.square_sweep_calculate_countdown = 7;
|
||||
gb->apu.square_sweep_stop_calc_if_no_zombie_write = true;
|
||||
|
||||
}
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (gb->apu.square_sweep_calculate_countdown_reload_timer == 2) {
|
||||
// Countdown just reloaded, re-reload it
|
||||
gb->apu.square_sweep_calculate_countdown = value & 0x7;
|
||||
if (!gb->apu.square_sweep_calculate_countdown) {
|
||||
sweep_calculation_done(gb, 0);
|
||||
}
|
||||
}
|
||||
else if (gb->apu.square_sweep_calculate_countdown_reload_timer == 1 && (value & 7) == 0) {
|
||||
// TODO: Odd glitch? Check schematics what the hell
|
||||
gb->apu.square_sweep_calculate_countdown--;
|
||||
if (!gb->apu.square_sweep_calculate_countdown) {
|
||||
sweep_calculation_done(gb, 0);
|
||||
}
|
||||
}
|
||||
if ((value & 7) && !(gb->io_registers[GB_IO_NR10] & 7) && !gb->apu.lf_div && gb->apu.square_sweep_calculate_countdown > 1) {
|
||||
// TODO: Another odd glitch? Ditto
|
||||
gb->apu.square_sweep_calculate_countdown--;
|
||||
if (!gb->apu.square_sweep_calculate_countdown) {
|
||||
sweep_calculation_done(gb, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
gb->io_registers[GB_IO_NR10] = value;
|
||||
if (gb->apu.shadow_sweep_sample_length + gb->apu.channel1_completed_addend + old_negate > 0x7FF &&
|
||||
!(value & 8)) {
|
||||
|
@ -1217,19 +1307,14 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
if (index == GB_SQUARE_1) {
|
||||
gb->apu.shadow_sweep_sample_length = 0;
|
||||
gb->apu.channel1_completed_addend = 0;
|
||||
gb->apu.square_sweep_stop_calc_if_no_zombie_write = false;
|
||||
if (gb->io_registers[GB_IO_NR10] & 7) {
|
||||
/* APU bug: if shift is nonzero, overflow check also occurs on trigger */
|
||||
gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div;
|
||||
if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) {
|
||||
/* TODO: I used to think this is correct, but it caused several regressions.
|
||||
More research is needed to figure how calculation time is different
|
||||
in models prior to CGB-D */
|
||||
// gb->apu.square_sweep_calculate_countdown += 2;
|
||||
}
|
||||
gb->apu.enable_zombie_calculate_stepping = false;
|
||||
gb->apu.square_sweep_calculate_countdown = gb->io_registers[GB_IO_NR10] & 0x7;
|
||||
gb->apu.square_sweep_calculate_countdown_reload_timer = 3 + (gb->model <= GB_MODEL_CGB_C);
|
||||
gb->apu.unshifted_sweep = false;
|
||||
if (!was_active) {
|
||||
gb->apu.square_sweep_calculate_countdown += 2;
|
||||
gb->apu.square_sweep_calculate_countdown += 1;
|
||||
}
|
||||
gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length;
|
||||
gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7);
|
||||
|
|
|
@ -70,11 +70,12 @@ typedef struct
|
|||
// need to divide the signal.
|
||||
|
||||
uint8_t square_sweep_countdown; // In 128Hz
|
||||
uint8_t square_sweep_calculate_countdown; // In 2 MHz
|
||||
uint8_t square_sweep_calculate_countdown; // In 1 MHz
|
||||
uint8_t square_sweep_calculate_countdown_reload_timer; // In 1 Mhz, for glitches related to reloading square_sweep_calculate_countdown
|
||||
uint16_t sweep_length_addend;
|
||||
uint16_t shadow_sweep_sample_length;
|
||||
bool unshifted_sweep;
|
||||
bool enable_zombie_calculate_stepping;
|
||||
bool square_sweep_stop_calc_if_no_zombie_write;
|
||||
|
||||
uint8_t channel_1_restart_hold;
|
||||
uint16_t channel1_completed_addend;
|
||||
|
|
|
@ -1919,8 +1919,8 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
|
|||
((gb->io_registers[GB_IO_NR10] & 0x7) && (gb->io_registers[GB_IO_NR10] & 0x70))? "active" : "inactive",
|
||||
(gb->io_registers[GB_IO_NR10] & 0x8) ? "decreasing" : "increasing");
|
||||
if (gb->apu.square_sweep_calculate_countdown) {
|
||||
GB_log(gb, " On going frequency calculation will be ready in %u APU ticks\n",
|
||||
gb->apu.square_sweep_calculate_countdown);
|
||||
GB_log(gb, " On-going frequency calculation will be ready in %u APU ticks\n",
|
||||
gb->apu.square_sweep_calculate_countdown * 2 + 1 - gb->apu.lf_div);
|
||||
}
|
||||
else {
|
||||
GB_log(gb, " Shadow frequency register: 0x%03x\n", gb->apu.shadow_sweep_sample_length);
|
||||
|
|
|
@ -22,7 +22,6 @@ typedef enum {
|
|||
GB_CONFLICT_SGB_LCDC,
|
||||
GB_CONFLICT_WX,
|
||||
GB_CONFLICT_LCDC_CGB,
|
||||
GB_CONFLICT_NR10,
|
||||
GB_CONFLICT_SCX_CGB,
|
||||
GB_CONFLICT_LCDC_CGB_DOUBLE,
|
||||
GB_CONFLICT_STAT_CGB_DOUBLE,
|
||||
|
@ -36,7 +35,6 @@ static const conflict_t cgb_conflict_map[0x80] = {
|
|||
[GB_IO_BGP] = GB_CONFLICT_PALETTE_CGB,
|
||||
[GB_IO_OBP0] = GB_CONFLICT_PALETTE_CGB,
|
||||
[GB_IO_OBP1] = GB_CONFLICT_PALETTE_CGB,
|
||||
[GB_IO_NR10] = GB_CONFLICT_NR10,
|
||||
[GB_IO_SCX] = GB_CONFLICT_SCX_CGB,
|
||||
};
|
||||
|
||||
|
@ -46,7 +44,6 @@ static const conflict_t cgb_double_conflict_map[0x80] = {
|
|||
[GB_IO_LYC] = GB_CONFLICT_READ_OLD,
|
||||
[GB_IO_STAT] = GB_CONFLICT_STAT_CGB_DOUBLE,
|
||||
// Unconfirmed yet
|
||||
[GB_IO_NR10] = GB_CONFLICT_NR10,
|
||||
[GB_IO_SCX] = GB_CONFLICT_SCX_CGB,
|
||||
};
|
||||
|
||||
|
@ -63,7 +60,6 @@ static const conflict_t dmg_conflict_map[0x80] = {
|
|||
[GB_IO_OBP1] = GB_CONFLICT_PALETTE_DMG,
|
||||
[GB_IO_WY] = GB_CONFLICT_READ_OLD,
|
||||
[GB_IO_WX] = GB_CONFLICT_WX,
|
||||
[GB_IO_NR10] = GB_CONFLICT_NR10,
|
||||
|
||||
/* Todo: these were not verified at all */
|
||||
[GB_IO_SCX] = GB_CONFLICT_READ_NEW,
|
||||
|
@ -82,7 +78,6 @@ static const conflict_t sgb_conflict_map[0x80] = {
|
|||
[GB_IO_OBP1] = GB_CONFLICT_READ_NEW,
|
||||
[GB_IO_WY] = GB_CONFLICT_READ_OLD,
|
||||
[GB_IO_WX] = GB_CONFLICT_WX,
|
||||
[GB_IO_NR10] = GB_CONFLICT_NR10,
|
||||
|
||||
/* Todo: these were not verified at all */
|
||||
[GB_IO_SCX] = GB_CONFLICT_READ_NEW,
|
||||
|
@ -216,11 +211,11 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
}
|
||||
|
||||
case GB_CONFLICT_DMG_LCDC: {
|
||||
/* Similar to the palette registers, these interact directly with the LCD, so they appear to be affected by it. Both my DMG (B, blob) and Game Boy Light behave this way though.
|
||||
/* Similar to the palette registers, these interact directly with the LCD, so they appear to be affected by
|
||||
it. Both my DMG (B, blob) and Game Boy Light behave this way though.
|
||||
|
||||
Additionally, LCDC.1 is very nasty because on the it is read both by the FIFO when popping pixels,
|
||||
and the object-fetching state machine, and both behave differently when it comes to access conflicts.
|
||||
Hacks ahead.
|
||||
Additionally, LCDC.1 is very nasty because it is read both by the FIFO when popping pixels, and the
|
||||
object-fetching state machine, and both behave differently when it comes to access conflicts. Hacks ahead.
|
||||
*/
|
||||
|
||||
uint8_t old_value = GB_read_memory(gb, addr);
|
||||
|
@ -319,27 +314,6 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GB_CONFLICT_NR10:
|
||||
/* Hack: Due to the coupling between DIV and the APU, GB_apu_run only runs at M-cycle
|
||||
resolutions, but this quirk requires 2MHz even in single speed mode. To work
|
||||
around this, we specifically just step the calculate countdown if needed. */
|
||||
GB_advance_cycles(gb, gb->pending_cycles);
|
||||
if (gb->model <= GB_MODEL_CGB_C) {
|
||||
// TODO: Double speed mode? This logic is also a bit weird, it needs more tests
|
||||
GB_apu_run(gb, true);
|
||||
if (gb->apu.square_sweep_calculate_countdown > 3 && gb->apu.enable_zombie_calculate_stepping) {
|
||||
gb->apu.square_sweep_calculate_countdown -= 2;
|
||||
}
|
||||
gb->apu.enable_zombie_calculate_stepping = true;
|
||||
/* TODO: this causes audio regressions in the Donkey Kong Land series.
|
||||
The exact behavior of this quirk should be further investigated, as it seems
|
||||
more complicated than a single FF pseudo-write. */
|
||||
// GB_write_memory(gb, addr, 0xFF);
|
||||
}
|
||||
GB_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 4;
|
||||
break;
|
||||
|
||||
case GB_CONFLICT_SCX_CGB:
|
||||
if (gb->cgb_double_speed) {
|
||||
|
|
Loading…
Reference in New Issue