diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs index 9108d62e37..984bded955 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs @@ -29,6 +29,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public int NoiseV = 247; public int DMCV = 167; + public int dmc_dma_countdown=-1; + public bool recalculate = false; NES nes; @@ -677,6 +679,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES out_silence = true; DMC_RATE = pal ? DMC_RATE_PAL : DMC_RATE_NTSC; timer_reload = DMC_RATE[0]; + timer = timer_reload; sample_buffer_filled = false; out_deltacounter = 64; out_bits_remaining = 0; @@ -687,8 +690,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES int timer_reload; int timer; - int user_address, user_length; - int sample_address, sample_length, sample_buffer; + int user_address; + uint user_length, sample_length; + int sample_address, sample_buffer; bool sample_buffer_filled; int out_shift, out_bits_remaining, out_deltacounter; @@ -731,9 +735,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES Clock(); } - //Any time the sample buffer is in an empty state and bytes remaining is not zero, the following occur: - if (!sample_buffer_filled && sample_length > 0) - Fetch(); + //Any time the sample buffer is in an empty state and bytes remaining is not zero, the following occur: + // also note that the halt for DMC DMA occurs on APU cycles only (hence the timer check) + if (!sample_buffer_filled && sample_length > 0 && timer % 2 == 1 && apu.dmc_dma_countdown==-1) + apu.dmc_dma_countdown = 5; } @@ -798,11 +803,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES else { //only start playback if playback is stopped + //Console.Write(sample_length); Console.Write(" "); Console.Write(sample_buffer_filled); Console.Write(" "); Console.Write(apu.dmc_irq); Console.Write("\n"); if (sample_length == 0) { sample_address = user_address; sample_length = user_length; - + } } @@ -837,7 +843,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES user_address = 0xC000 | (val << 6); break; case 3: - user_length = (val << 4) + 1; + user_length = ((uint)val << 4) + 1; break; } } @@ -845,10 +851,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public void Fetch() { //TODO - cpu/apu DMC reads need to be emulated better! - sample_buffer = apu.nes.ReadMemory((ushort)sample_address); - sample_buffer_filled = true; - sample_address = (ushort)(sample_address + 1); - sample_length--; + if (sample_length != 0) + { + sample_buffer = apu.nes.ReadMemory((ushort)sample_address); + sample_buffer_filled = true; + sample_address = (ushort)(sample_address + 1); + //Console.WriteLine(sample_length); + //Console.WriteLine(user_length); + sample_length--; + } if (sample_length == 0) { if (loop_flag) @@ -878,6 +889,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ser.Sync("sequencer_irq_clear_pending", ref sequencer_irq_clear_pending); ser.Sync("sequencer_irq_assert", ref sequencer_irq_assert); + ser.Sync("dmc_dma_countdown", ref dmc_dma_countdown); + ser.Sync("toggle", ref toggle); + + pulse[0].SyncState(ser); pulse[1].SyncState(ser); triangle.SyncState(ser); @@ -1042,6 +1057,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES triangle.set_lenctr_en((val >> 2) & 1); noise.set_lenctr_en((val >> 3) & 1); dmc.set_lenctr_en(val.Bit(4)); + } else if (addr == 0x4017) { @@ -1150,14 +1166,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //it can change the ouput values of the pulse/triangle channels //we want the changes to affect it on the *next* cycle. - if(DebugCallbackDivider != 0) + if (DebugCallbackDivider != 0) { - if(DebugCallbackTimer==0) + if (DebugCallbackTimer == 0) { - if(DebugCallback != null) + if (DebugCallback != null) DebugCallback(); DebugCallbackTimer = DebugCallbackDivider; - } else DebugCallbackTimer--; + } + else DebugCallbackTimer--; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs index d320f931f0..fbf81ed7cf 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs @@ -317,15 +317,36 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES cpu_deadcounter += 514; } } - + + if (apu.dmc_dma_countdown>0) + { + cpu.RDY = false; + apu.dmc_dma_countdown--; + if (apu.dmc_dma_countdown==0) + { + apu.RunDMCFetch(); + cpu.RDY = true; + } + + if (apu.dmc_dma_countdown==0) + { + + + apu.dmc_dma_countdown = -1; + } + } + if (cpu_deadcounter > 0) + { cpu_deadcounter--; - else + } + else { cpu.IRQ = _irq_apu || Board.IRQSignal; cpu.ExecuteOne(); } + ppu.ppu_open_bus_decay(0); apu.RunOne(); Board.ClockCPU(); ppu.PostCpuInstructionOne(); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs index e54bc3a1d6..ac81b90224 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs @@ -157,8 +157,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ser.Sync("Prev_soam_index", ref soam_index_prev); ser.Sync("Spr_Zero_Go", ref sprite_zero_go); ser.Sync("Spr_zero_in_Range", ref sprite_zero_in_range); + ser.Sync("Is_even_cycle", ref is_even_cycle); + ser.Sync("soam_index", ref soam_index); - ser.Sync("OAM", ref OAM, false); + ser.Sync("ppu_open_bus", ref ppu_open_bus); + ser.Sync("ppu_open_bus_decay_timer", ref ppu_open_bus_decay_timer, false); + + ser.Sync("OAM", ref OAM, false); ser.Sync("PALRAM", ref PALRAM, false); ser.Sync("Reg2002_objoverflow", ref Reg2002_objoverflow); @@ -181,6 +186,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES regs_reset(); ppudead = 2; idleSynch = true; + ppu_open_bus = 0; + ppu_open_bus_decay_timer = new int[8]; } #if VS2012 diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs index 013c21c4ac..401963421c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs @@ -55,6 +55,10 @@ 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[] ppu_open_bus_decay_timer = new int[8]; public struct PPUSTATUS { @@ -338,9 +342,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES NMI_PendingInstructions = 2; } reg_2000.Value = value; + + } - byte read_2000() { return PPUGenLatch; } - byte peek_2000() { return PPUGenLatch; } + byte read_2000() { return ppu_open_bus; } + byte peek_2000() { return ppu_open_bus; } //PPU MASK (write) void write_2001(byte value) @@ -348,8 +354,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //printf("%04x:$%02x, %d\n",A,V,scanline); reg_2001.Value = value; } - byte read_2001() { return PPUGenLatch; } - byte peek_2001() { return PPUGenLatch; } + byte read_2001() { return ppu_open_bus; } + byte peek_2001() { return ppu_open_bus; } //PPU STATUS (read) void write_2002(byte value) { } @@ -365,11 +371,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES Reg2002_vblank_active = 0; Reg2002_vblank_active_pending = false; + // update the open bus here + ppu_open_bus = ret; + ppu_open_bus_decay(2); return ret; } byte peek_2002() { - return (byte)((Reg2002_vblank_active << 7) | (Reg2002_objhit << 6) | (Reg2002_objoverflow << 5) | (PPUGenLatch & 0x1F)); + return (byte)((Reg2002_vblank_active << 7) | (Reg2002_objhit << 6) | (Reg2002_objoverflow << 5) | (ppu_open_bus & 0x1F)); } void clear_2002() @@ -384,8 +393,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //just record the oam buffer write target reg_2003 = value; } - byte read_2003() { return PPUGenLatch; } - byte peek_2003() { return PPUGenLatch; } + byte read_2003() { return ppu_open_bus; } + byte peek_2003() { return ppu_open_bus; } //OAM DATA (write) void write_2004(byte value) @@ -397,37 +406,41 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } byte read_2004() { - // behaviour depends on whether things are being rendered or not + byte ret; + // behaviour depends on whether things are being rendered or not if (reg_2001.show_bg || reg_2001.show_obj) { if (ppur.status.sl < 241) { if (ppur.status.cycle < 64) { - return 0xFF; // during this time all reads return FF + ret = 0xFF; // during this time all reads return FF } else if (ppur.status.cycle < 256) { - return read_value; + ret = read_value; } else if (ppur.status.cycle < 320) { - return read_value; + ret = read_value; } else { - return soam[0]; + ret = soam[0]; } } else { - return OAM[reg_2003]; + ret = OAM[reg_2003]; } } else { - return OAM[reg_2003]; + ret = OAM[reg_2003]; } + ppu_open_bus = ret; + ppu_open_bus_decay(1); + return ret; } byte peek_2004() { return OAM[reg_2003]; } @@ -448,8 +461,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } vtoggle ^= true; } - byte read_2005() { return PPUGenLatch; } - byte peek_2005() { return PPUGenLatch; } + byte read_2005() { return ppu_open_bus; } + byte peek_2005() { return ppu_open_bus; } //VRAM address register (write) void write_2006(byte value) @@ -479,8 +492,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES vtoggle ^= true; } - byte read_2006() { return PPUGenLatch; } - byte peek_2006() { return PPUGenLatch; } + byte read_2006() { return ppu_open_bus; } + byte peek_2006() { return ppu_open_bus; } //VRAM data register (r/w) void write_2007(byte value) @@ -516,7 +529,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES byte read_2007() { int addr = ppur.get_2007access() & 0x3FFF; - + int bus_case = 0; //ordinarily we return the buffered values byte ret = VRAMBuffer; @@ -527,14 +540,25 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES if ((addr & 0x3F00) == 0x3F00) { //TODO apply greyscale shit? - ret = PALRAM[addr & 0x1F]; + ret = (byte)(PALRAM[addr & 0x1F] + ((byte)(ppu_open_bus & 0xC0))); + bus_case = 1; } ppur.increment2007(ppur.status.rendering && reg_2001.PPUON, reg_2000.vram_incr32 != 0); //see comments in $2006 - nes.Board.AddressPPU(ppur.get_2007access()); - + nes.Board.AddressPPU(ppur.get_2007access()); + + // update open bus here + ppu_open_bus = ret; + if (bus_case==0) + { + ppu_open_bus_decay(1); + } else + { + ppu_open_bus_decay(3); + } + return ret; } byte peek_2007() @@ -580,12 +604,64 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public void WriteReg(int addr, byte value) { 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; case 4: write_2004(value); break; case 5: write_2005(value); break; case 6: write_2006(value); break; case 7: write_2007(value); break; default: throw new InvalidOperationException(); } + } + + + public void ppu_open_bus_decay(byte action) + { + // if there is no action, decrement the timer + if (action==0) + { + for (int i = 0; i < 8; i++) + { + if (ppu_open_bus_decay_timer[i] == 0) + { + ppu_open_bus = (byte)(ppu_open_bus & (0xff - (1 << i))); + ppu_open_bus_decay_timer[i] = 1786840; // about 1 second worth of cycles + } + else + { + ppu_open_bus_decay_timer[i]--; + } + + } + } + + // reset the timer for all bits (reg 2004 / 2007 (non-palette) + if (action==1) + { + for (int i=0; i<8; i++) + { + ppu_open_bus_decay_timer[i] = 1786840; + } + + } + + // reset the timer for high 3 bits (reg 2002) + if (action == 2) + { + ppu_open_bus_decay_timer[7] = 1786840; + ppu_open_bus_decay_timer[6] = 1786840; + ppu_open_bus_decay_timer[5] = 1786840; + } + + // reset the timer for all low 6 bits (reg 2007 (palette)) + if (action == 3) + { + for (int i = 0; i < 6; i++) + { + ppu_open_bus_decay_timer[i] = 1786840; + } + } + // other values of action are reserved for possibly needed expansions, but this passes + // ppu_open_bus for now. } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs index 8e1b801a12..a6e86246f6 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs @@ -369,7 +369,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //1. is it sprite#0? //2. is the bg pixel nonzero? //then, it is spritehit. - Reg2002_objhit |= (sprite_zero_go && s==0 && pixel != 0 && rasterpos < 255); + Reg2002_objhit |= (sprite_zero_go && s==0 && pixel != 0 && rasterpos < 255 && reg_2001.show_bg && reg_2001.show_obj); //priority handling, if in front of BG: bool drawsprite = !(((t_oam[s].oam_attr & 0x20) != 0) && ((pixel & 3) != 0));