From 663aded7b203bdbf88d1e2c467f4342cd9b2b1fe Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Fri, 1 Jul 2016 22:31:06 -0400 Subject: [PATCH] Pass a few more tests and fix a few regressions --- .../Consoles/Nintendo/NES/APU.cs | 95 ++++++++++++------- .../Consoles/Nintendo/NES/NES.Core.cs | 20 ++++ .../Consoles/Nintendo/NES/NES.IStatable.cs | 3 +- .../Consoles/Nintendo/NES/PPU.cs | 1 + .../Consoles/Nintendo/NES/PPU.regs.cs | 30 +++++- 5 files changed, 109 insertions(+), 40 deletions(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs index 04ead94b44..ccb76d59f4 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs @@ -156,7 +156,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //if (unit == 1) Console.WriteLine("{0} timer_reload_value: {1}", unit, timer_reload_value); break; case 3: - len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F]; + if (apu.len_clock_active) + { + if (len_cnt==0) + { + len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F]+1; + } + } else + { + len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F]; + } + timer_reload_value = (timer_reload_value & 0xFF) | ((val & 0x07) << 8); timer_raw_reload_value = timer_reload_value * 2 + 2; //duty_step = 0; //?just a guess? @@ -165,6 +175,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //allow the lenctr_en to kill the len_cnt set_lenctr_en(lenctr_en); + + + //serves as a useful note-on diagnostic //if(unit==1) Console.WriteLine("{0} timer_reload_value: {1}", unit, timer_reload_value); @@ -432,7 +445,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //Console.WriteLine("noise period: {0}, vol: {1}", (val & 0xF), env_cnt_value); break; case 3: - len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F]; + if (apu.len_clock_active) + { + if (len_cnt == 0) + { + len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F] + 1; + } + } + else + { + len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F]; + } + set_lenctr_en(lenctr_en); env_start_flag = 1; break; @@ -574,7 +598,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES case 3: timer_cnt = (timer_cnt & 0xFF) | ((val & 0x7) << 8); timer_cnt_reload = timer_cnt + 1; - len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F]; + if (apu.len_clock_active) + { + if (len_cnt == 0) + { + len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F] + 1; + } + } + else + { + len_cnt = LENGTH_TABLE[(val >> 3) & 0x1F]; + } halt_flag = 1; //allow the lenctr_en to kill the len_cnt @@ -963,6 +997,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ser.Sync("sequencer_tick_delay", ref seq_tick); ser.Sync("seq_val_to_apply", ref seq_val); ser.Sync("sequencer_irq_flag", ref sequencer_irq_flag); + ser.Sync("len_clock_active", ref len_clock_active); pulse[0].SyncState(ser); @@ -984,6 +1019,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES byte pending_val = 0; public int seq_tick; public byte seq_val; + public bool len_clock_active; int sequencer_counter, sequencer_step, sequencer_mode, sequencer_irq_inhibit, sequencer_irq_assert; bool sequencer_irq, sequence_reset_pending, sequencer_irq_clear_pending, sequencer_irq_flag; @@ -1012,7 +1048,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //this is probably a hint that we're doing something a little wrong but making up for it with curcuitous chaos in other ways static int[][] sequencer_lut = new int[][]{ new int[]{7457,14913,22372,29830}, - new int[]{7458,14913,22372,29830,37282} + new int[]{7457,14913,22372,29830,37282} }; @@ -1128,7 +1164,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES sequencer_irq = false; sequencer_irq_flag = false; _WriteReg(0x4015, 0); - Console.WriteLine(seq_val); + //for 4017, its as if the last value written gets rewritten sequencer_mode = (seq_val >> 7) & 1; sequencer_irq_inhibit = (seq_val >> 6) & 1; @@ -1283,13 +1319,24 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES EmitSample(); + //we need to predict if there will be a length clock here, because the sequencer ticks last, but the + // timer reload shouldn't happen if length clock and write happen simultaneously + // I'm not sure if we can avoid this by simply processing the sequencer first + // but at the moment that would break everything, so this is good enough for now + if (sequencer_counter==14912 || + (sequencer_counter == 29828 && sequencer_mode==0) || + (sequencer_counter == 37280 && sequencer_mode == 1)) + { + len_clock_active = true; + } + //handle writes //notes: this set up is a bit convoluded at the moment, mainly because APU behaviour is not entirely understood //in partiuclar, there are several clock pulses affecting the APU, and when new written are latched is not known in detail //the current code simply matches known behaviour if (pending_reg != -1) { - if (pending_reg == 4015 || pending_reg == 4017) + if (pending_reg == 0x4015 || pending_reg == 0x4017 || pending_reg==0x4003) { _WriteReg(pending_reg, pending_val); pending_reg = -1; @@ -1301,7 +1348,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } } - + len_clock_active = false; + sequencer_tick(); sequencer_write_tick(seq_val); @@ -1327,39 +1375,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES SyncIRQ(); nes.irq_apu = irq_pending; - if (sequencer_irq_flag == false) - sequencer_irq = false; - - /* - //this (and the similar line below) is a crude hack - //we should be generating logic to suppress the $4015 clear when the assert signal is set instead - //be sure to test "apu_test" if you mess with this - //sequencer_irq |= sequencer_irq_assert; - - //if (toggle == 0) - //{ - //handle sequencer irq clear signal - sequencer_irq_assert = false; - if (sequencer_irq_clear_pending) - { - //Console.WriteLine("{0} {1,5} $4017 clear irq (delayed)", nes.Frame, sequencer_counter); - sequencer_irq_clear_pending = false; - sequencer_irq = false; - SyncIRQ(); - } - - toggle = 1; - - //latch whatever irq logic we had and send to cpu - nes.irq_apu = irq_pending; - } - else toggle = 0; - */ - + //since the units run concurrently, the APU frame sequencer is ran last because //it can change the ouput values of the pulse/triangle channels //we want the changes to affect it on the *next* cycle. + if (sequencer_irq_flag == false) + sequencer_irq = false; + if (DebugCallbackDivider != 0) { if (DebugCallbackTimer == 0) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs index 47f0edc448..6f617561bc 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs @@ -305,6 +305,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public bool dmc_realign; public bool IRQ_delay; public bool special_case_delay; // very ugly but the only option + public bool do_the_reread; #if VS2012 [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -388,6 +389,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES apu.RunDMCFetch(); dmc_dma_exec = false; apu.dmc_dma_countdown = -1; + do_the_reread = true; } } @@ -409,6 +411,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES cpu.ExecuteOne(); apu.RunOne(false); + if (ppu.double_2007_read > 0) + ppu.double_2007_read--; + + if (do_the_reread && cpu.RDY) + do_the_reread = false; + if (IRQ_delay) IRQ_delay = false; @@ -432,6 +440,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES #endif public byte ReadReg(int addr) { + byte ret_spec; switch (addr) { case 0x4000: case 0x4001: case 0x4002: case 0x4003: @@ -444,6 +453,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES case 0x4014: /*OAM DMA*/ break; case 0x4015: return (byte)((byte)(apu.ReadReg(addr) & 0xDF) + (byte)(DB&0x20)); case 0x4016: + { + // special hardware glitch case + ret_spec = read_joyport(addr); + if (do_the_reread) + { + ret_spec = read_joyport(addr); + do_the_reread = false; + } + return ret_spec; + + } case 0x4017: return read_joyport(addr); default: diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IStatable.cs index 541da6b945..2ac1a434a5 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IStatable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IStatable.cs @@ -71,9 +71,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ser.Sync("dmc_realign", ref dmc_realign); ser.Sync("IRQ_delay", ref IRQ_delay); ser.Sync("special_case_delay", ref special_case_delay); + ser.Sync("do_the_reread", ref do_the_reread); - ser.BeginSection("Board"); + ser.BeginSection("Board"); Board.SyncState(ser); if (Board is NESBoardBase && !((NESBoardBase)Board).SyncStateFlag) throw new InvalidOperationException("the current NES mapper didnt call base.SyncState"); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs index ac81b90224..87f6195e76 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs @@ -161,6 +161,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ser.Sync("soam_index", ref soam_index); ser.Sync("ppu_open_bus", ref ppu_open_bus); + ser.Sync("double_2007_read", ref double_2007_read); ser.Sync("ppu_open_bus_decay_timer", ref ppu_open_bus_decay_timer, false); ser.Sync("OAM", ref OAM, false); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs index 401963421c..f61b667c62 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs @@ -58,6 +58,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES // this byte is used to simulate open bus reads and writes // it should be modified by every read and write to a ppu register public byte ppu_open_bus; + public int double_2007_read; // emulates a hardware bug of back to back 2007 reads public int[] ppu_open_bus_decay_timer = new int[8]; public struct PPUSTATUS @@ -354,8 +355,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //printf("%04x:$%02x, %d\n",A,V,scanline); reg_2001.Value = value; } - byte read_2001() { return ppu_open_bus; } - byte peek_2001() { return ppu_open_bus; } + byte read_2001() {return ppu_open_bus; } + byte peek_2001() {return ppu_open_bus; } //PPU STATUS (read) void write_2002(byte value) { } @@ -585,10 +586,32 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public byte ReadReg(int addr) { + byte ret_spec; switch (addr) { case 0: return read_2000(); case 1: return read_2001(); case 2: return read_2002(); case 3: return read_2003(); - case 4: return read_2004(); case 5: return read_2005(); case 6: return read_2006(); case 7: return read_2007(); + case 4: return read_2004(); case 5: return read_2005(); case 6: return read_2006(); + + case 7: + { + if (double_2007_read>0) + { + double_2007_read = 0; + return ppu_open_bus; + } else + { + ret_spec = read_2007(); + double_2007_read = 2; + } + + if (nes.do_the_reread) + { + ret_spec = read_2007(); + ret_spec = read_2007(); + nes.do_the_reread = false; + } + return ret_spec; + } default: throw new InvalidOperationException(); } } @@ -605,6 +628,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES { PPUGenLatch = value; ppu_open_bus = value; + switch (addr) { case 0: write_2000(value); break; case 1: write_2001(value); break; case 2: write_2002(value); break; case 3: write_2003(value); break;