diff --git a/BizHawk.Emulation/CPUs/MOS 6502X/Execute.cs b/BizHawk.Emulation/CPUs/MOS 6502X/Execute.cs index 70ac5e2aa8..a24e87e50d 100644 --- a/BizHawk.Emulation/CPUs/MOS 6502X/Execute.cs +++ b/BizHawk.Emulation/CPUs/MOS 6502X/Execute.cs @@ -1,5 +1,4 @@ //http://nesdev.parodius.com/6502_cpu.txt -//TODO - correct brk/irq/nmi interrupting and prioritization //TODO - rename unofficial NOPs as DOPs? (see immediate instr tests) using System; @@ -9,7 +8,7 @@ namespace BizHawk.Emulation.CPUs.M6502 { static Uop[][] Microcode = new Uop[][]{ //0x00 - /*BRK [implied]*/ new Uop[] { Uop.Fetch2, Uop.PushPCH, Uop.PushPCL, Uop.PushP_BRK, Uop.FetchPCLVector, Uop.FetchPCHVector, Uop.End }, + /*BRK [implied]*/ new Uop[] { Uop.Fetch2, Uop.PushPCH, Uop.PushPCL, Uop.PushP_BRK, Uop.FetchPCLVector, Uop.FetchPCHVector, Uop.End_SuppressInterrupt }, /*ORA (addr,X) [indexed indirect READ]*/ new Uop[] { Uop.Fetch2, Uop.IdxInd_Stage3, Uop.IdxInd_Stage4, Uop.IdxInd_Stage5, Uop.IdxInd_Stage6_READ_ORA, Uop.End }, /*JAM*/ new Uop[] { Uop.End }, /*SLO* (addr,X) [indexed indirect RMW] [unofficial]*/ new Uop[] { Uop.Fetch2, Uop.End }, @@ -282,14 +281,16 @@ namespace BizHawk.Emulation.CPUs.M6502 /*ISB* addr,X [absolute indexed RMW X] [unofficial]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_X, Uop.AbsIdx_Stage4, Uop.AbsIdx_RMW_Stage5, Uop.AbsIdx_RMW_Stage6_Unofficial, Uop.AbsIdx_RMW_Stage7, Uop.End }, //0x100 /*VOP_Fetch1*/ new Uop[] { Uop.Fetch1 }, - /*VOP_RelativeStuff*/ new Uop[] { Uop.RelBranch_Stage3, Uop.End }, + /*VOP_RelativeStuff*/ new Uop[] { Uop.RelBranch_Stage3, Uop.End_BranchSpecial }, /*VOP_RelativeStuff2*/ new Uop[] { Uop.RelBranch_Stage4, Uop.End }, + /*VOP_RelativeStuff2*/ new Uop[] { Uop.End_SuppressInterrupt }, //i assume these are dummy fetches.... maybe theyre just nops? supposedly these take 7 cycles so thats the only way i can make sense of it //one of them might be the next instruction's fetch, and whatever fetch follows it. //the interrupt would then take place if necessary, using a cached PC. but im not so sure about that. - /*VOP_NMI*/ new Uop[] { Uop.FetchDummy, Uop.FetchDummy, Uop.PushPCH, Uop.PushPCL, Uop.PushP_NMI, Uop.FetchPCLVector, Uop.FetchPCHVector, Uop.End }, - /*VOP_IRQ*/ new Uop[] { Uop.FetchDummy, Uop.FetchDummy, Uop.PushPCH, Uop.PushPCL, Uop.PushP_IRQ, Uop.FetchPCLVector, Uop.FetchPCHVector, Uop.End }, - /*VOP_RESET*/ new Uop[] { Uop.FetchDummy, Uop.FetchDummy, Uop.PushDummy, Uop.PushDummy, Uop.PushP_Reset, Uop.FetchPCLVector, Uop.FetchPCHVector, Uop.End }, + /*VOP_NMI*/ new Uop[] { Uop.FetchDummy, Uop.FetchDummy, Uop.PushPCH, Uop.PushPCL, Uop.PushP_NMI, Uop.FetchPCLVector, Uop.FetchPCHVector, Uop.End_SuppressInterrupt }, + /*VOP_IRQ*/ new Uop[] { Uop.FetchDummy, Uop.FetchDummy, Uop.PushPCH, Uop.PushPCL, Uop.PushP_IRQ, Uop.FetchPCLVector, Uop.FetchPCHVector, Uop.End_SuppressInterrupt }, + /*VOP_RESET*/ new Uop[] { Uop.FetchDummy, Uop.FetchDummy, Uop.PushDummy, Uop.PushDummy, Uop.PushP_Reset, Uop.FetchPCLVector, Uop.FetchPCHVector, Uop.End_SuppressInterrupt }, + /*VOP_Fetch1_NoInterrupt*/ new Uop[] { Uop.Fetch1_Real }, }; enum Uop @@ -297,7 +298,7 @@ namespace BizHawk.Emulation.CPUs.M6502 //sometimes i used this as a marker for unsupported instructions, but it is very inconsistent Unsupported, - Fetch1, Fetch2, Fetch3, + Fetch1, Fetch1_Real, Fetch2, Fetch3, //used by instructions with no second opcode byte (6502 fetches a byte anyway but won't increment PC for these) FetchDummy, @@ -385,50 +386,45 @@ namespace BizHawk.Emulation.CPUs.M6502 End, End_ISpecial, //same as end, but preserves the iflag set by the instruction + End_BranchSpecial, + End_SuppressInterrupt, } const int VOP_Fetch1 = 256; const int VOP_RelativeStuff = 257; const int VOP_RelativeStuff2 = 258; - const int VOP_NMI = 259; - const int VOP_IRQ = 260; - const int VOP_RESET = 261; + const int VOP_RelativeStuff3 = 259; + const int VOP_NMI = 260; + const int VOP_IRQ = 261; + const int VOP_RESET = 262; + const int VOP_Fetch1_NoInterrupt = 263; + //opcode bytes.. theoretically redundant with the temp variables? who knows. int opcode; - byte opcode2, opcode3; //opcode bytes.. theoretically redundant with the temp variables? who knows. + byte opcode2, opcode3; + int ea, alu_temp; //cpu internal temp variables int mi; //microcode index - //bool branch_taken; //only needed for the timing debug bool iflag_pending; //iflag must be stored after it is checked in some cases (CLI and SEI). + //tracks whether an interrupt condition has popped up recently. + //not sure if this is real or not but it helps with the branch_irq_hack + bool interrupt_pending; + bool branch_irq_hack; //see Uop.RelBranch_Stage3 for more details + + bool Interrupted + { + get + { + return NMI || (IRQ && !FlagI); + } + } + void FetchDummy() { DummyReadMemory(PC); } - ////timing debug - //int ctr = 0; - //int realOpcode = 0; - //public static byte[] CycTable = new byte[] - //{ - ///*0x00*/ 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6, - ///*0x10*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, - ///*0x20*/ 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6, - ///*0x30*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, - ///*0x40*/ 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6, - ///*0x50*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, - ///*0x60*/ 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6, - ///*0x70*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, - ///*0x80*/ 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, - ///*0x90*/ 2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5, - ///*0xA0*/ 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, - ///*0xB0*/ 2,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4, - ///*0xC0*/ 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6, - ///*0xD0*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, - ///*0xE0*/ 2,6,3,8,3,3,5,5,2,2,2,2,4,4,6,6, - ///*0xF0*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, - //}; - public void Execute(int cycles) { for (int i = 0; i < cycles; i++) @@ -445,6 +441,8 @@ namespace BizHawk.Emulation.CPUs.M6502 TotalExecutedCycles++; + interrupt_pending |= Interrupted; + RETRY: Uop uop = Microcode[opcode][mi]; switch (uop) @@ -454,44 +452,34 @@ namespace BizHawk.Emulation.CPUs.M6502 { bool my_iflag = FlagI; FlagI = iflag_pending; - if (NMI) + if (!branch_irq_hack) { - ea = NMIVector; - opcode = VOP_NMI; - NMI = false; - mi = 0; - goto RETRY; + interrupt_pending = false; + if (NMI) + { + ea = NMIVector; + opcode = VOP_NMI; + NMI = false; + mi = 0; + goto RETRY; + } + else if (IRQ && !my_iflag) + { + ea = IRQVector; + opcode = VOP_IRQ; + mi = 0; + goto RETRY; + } } - else if (IRQ && !my_iflag) - { - ea = IRQVector; - opcode = VOP_IRQ; - mi = 0; - goto RETRY; - } -#if TIMINGDEBUG - if (debug) - { - int ideal = CycTable[realOpcode] + (branch_taken ? 1 : 0); - int actual = TotalExecutedCycles - ctr; - Console.Write(" | ideal={0}", ideal); - Console.Write(" actual={0}", actual); - if (actual != ideal) Console.WriteLine(" !!!"); else Console.WriteLine(); - Console.Write(State()); - } - branch_taken = false; - opcode = ReadMemory(PC++); - realOpcode = opcode; - mi = -1; - ctr = TotalExecutedCycles; - break; -#else - if(debug) Console.WriteLine(State()); - opcode = ReadMemory(PC++); - mi = -1; - break; -#endif + goto case Uop.Fetch1_Real; } + + case Uop.Fetch1_Real: + if (debug) Console.WriteLine(State()); + branch_irq_hack = false; + opcode = ReadMemory(PC++); + mi = -1; + break; case Uop.Fetch2: opcode2 = ReadMemory(PC++); break; case Uop.Fetch3: opcode3 = ReadMemory(PC++); break; @@ -526,6 +514,16 @@ namespace BizHawk.Emulation.CPUs.M6502 S--; break; case Uop.FetchPCLVector: + if (ea == BRKVector && FlagB && NMI) + { + NMI = false; + ea = NMIVector; + } + if(ea == IRQVector && !FlagB && NMI) + { + NMI = false; + ea = NMIVector; + } alu_temp = ReadMemory((ushort)ea); break; case Uop.FetchPCHVector: @@ -652,18 +650,26 @@ namespace BizHawk.Emulation.CPUs.M6502 opcode = VOP_RelativeStuff; mi = -1; } + break; case Uop.RelBranch_Stage3: FetchDummy(); alu_temp = (byte)PC + (int)(sbyte)opcode2; PC &= 0xFF00; PC |= (ushort)((alu_temp&0xFF)); - if(alu_temp.Bit(8)) + if (alu_temp.Bit(8)) { //we need to carry the add, and then we'll be ready to fetch the next instruction opcode = VOP_RelativeStuff2; mi = -1; } + else + { + //to pass cpu_interrupts_v2/5-branch_delays_irq we need to handle a quirk here + //if we decide to interrupt in the next cycle, this condition will cause it to get deferred by one instruction + if(!interrupt_pending) + branch_irq_hack = true; + } break; case Uop.RelBranch_Stage4: FetchDummy(); @@ -678,7 +684,7 @@ namespace BizHawk.Emulation.CPUs.M6502 case Uop.JSR: PC = (ushort)((ReadMemory((ushort)(PC)) << 8) + opcode2); break; case Uop.PullP: P = ReadMemory((ushort)(S++ + 0x100)); - FlagT = true; + FlagT = true; //force T always to remain true break; case Uop.PullPCL: PC &= 0xFF00; @@ -968,7 +974,7 @@ namespace BizHawk.Emulation.CPUs.M6502 P = ReadMemory((ushort)(S + 0x100)); iflag_pending = FlagI; FlagI = my_iflag; - FlagT = true; //why? + FlagT = true; //force T always to remain true break; } @@ -1239,11 +1245,18 @@ namespace BizHawk.Emulation.CPUs.M6502 mi = 0; goto RETRY; + case Uop.End_SuppressInterrupt: + opcode = VOP_Fetch1_NoInterrupt; + mi = 0; + goto RETRY; + case Uop.End: opcode = VOP_Fetch1; mi = 0; iflag_pending = FlagI; goto RETRY; + case Uop.End_BranchSpecial: + goto case Uop.End; } mi++; diff --git a/BizHawk.Emulation/CPUs/MOS 6502X/MOS6502X.cs b/BizHawk.Emulation/CPUs/MOS 6502X/MOS6502X.cs index d65c362dac..65eff5243f 100644 --- a/BizHawk.Emulation/CPUs/MOS 6502X/MOS6502X.cs +++ b/BizHawk.Emulation/CPUs/MOS 6502X/MOS6502X.cs @@ -96,6 +96,8 @@ namespace BizHawk.Emulation.CPUs.M6502 ser.Sync("alu_temp", ref alu_temp); ser.Sync("mi", ref mi); ser.Sync("iflag_pending", ref iflag_pending); + ser.Sync("interrupt_pending", ref interrupt_pending); + ser.Sync("branch_irq_hack", ref branch_irq_hack); ser.EndSection(); } diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs index 27c0bc2aed..e517535364 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs @@ -47,11 +47,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo //4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778 //PAL }; - + class PulseUnit { public PulseUnit(int unit) { this.unit = unit; } - public int unit; + public int unit; //reg0 int duty_cnt, env_loop, env_constant, env_cnt_value; @@ -87,10 +87,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo public bool IsLenCntNonZero() { return len_cnt > 0; } + public void WriteReg(int addr, byte val) { //Console.WriteLine("write pulse {0:X} {1:X}", addr, val); - switch(addr) + switch (addr) { case 0: env_cnt_value = val & 0xF; @@ -120,7 +121,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo //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); break; @@ -163,7 +164,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo if (swp_divider_counter == 0) { swp_divider_counter = sweep_divider_cnt + 1; - + //divider was clocked: process sweep pitch bend if (sweep_shiftcount != 0 && !swp_silence) { @@ -180,7 +181,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo sweep_reload = false; } } - + //env_loopdoubles as "halt length counter" if (env_loop == 0 && len_cnt > 0) len_cnt--; @@ -196,7 +197,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo } else { - if(env_divider != 0) env_divider--; + if (env_divider != 0) env_divider--; if (env_divider == 0) { env_divider = (env_cnt_value + 1); @@ -219,7 +220,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo else env_output = env_counter; if (timer_counter > 0) timer_counter--; - if (timer_counter == 0 && timer_raw_reload_value!=0) + if (timer_counter == 0 && timer_raw_reload_value != 0) { duty_step = (duty_step + 1) & 7; duty_value = PULSE_DUTY[duty_cnt, duty_step] == 1; @@ -233,8 +234,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo if (swp_silence) sample = 0; - if (len_cnt==0) //length counter is 0 - sample = 0; //silenced + if (len_cnt == 0) //length counter is 0 + sample = 0; //silenced } else sample = 0; //duty cycle is 0, silenced. @@ -297,13 +298,13 @@ namespace BizHawk.Emulation.Consoles.Nintendo case 0: env_cnt_value = val & 0xF; env_constant = (val >> 4) & 1; - env_loop = (val>>5)&1; + env_loop = (val >> 5) & 1; break; case 1: break; case 2: period_cnt = NOISE_TABLE[val & 0xF]; - mode_cnt = (val>>7)&1; + mode_cnt = (val >> 7) & 1; //Console.WriteLine("noise period: {0}, vol: {1}", (val & 0xF), env_cnt_value); break; case 3: @@ -350,7 +351,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo } public void clock_length_and_sweep() { - + if (len_cnt > 0 && env_loop == 0) len_cnt--; } @@ -373,10 +374,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo shift_register >>= 1; shift_register &= ~(1 << 14); shift_register |= (feedback << 14); - noise_bit = (shift_register & 1)!=0; + noise_bit = (shift_register & 1) != 0; } - if (noise_bit || len_cnt==0) sample = 0; + if (noise_bit || len_cnt == 0) sample = 0; else sample = env_output; } @@ -463,21 +464,21 @@ namespace BizHawk.Emulation.Consoles.Nintendo //is clocked in frame counter. if (en) { - if(timer>0) timer--; + if (timer > 0) timer--; if (timer == 0) { seq = (seq + 1) & 0x1F; timer = timer_cnt_reload; } //if(CFG_DECLICK) - //sample = TRIANGLE_TABLE[(seq+8)&0x1F]; + //sample = TRIANGLE_TABLE[(seq+8)&0x1F]; //else - sample = TRIANGLE_TABLE[seq]; + sample = TRIANGLE_TABLE[seq]; } } - + public void clock_length_and_sweep() { //env_loopdoubles as "halt length counter" @@ -487,7 +488,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo public void clock_linear_counter() { - // Console.WriteLine("linear_counter: {0}", linear_counter); + // Console.WriteLine("linear_counter: {0}", linear_counter); if (halt_flag == 1) { linear_counter = linear_counter_reload; @@ -543,7 +544,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo ser.Sync("irq_enabled", ref irq_enabled); ser.Sync("loop_flag", ref loop_flag); ser.Sync("timer_reload", ref timer_reload); - + ser.Sync("timer", ref timer); ser.Sync("user_address", ref user_address); ser.Sync("user_length", ref user_length); @@ -597,7 +598,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo //apu.nes.LogLine("dmc out sample: {0}", out_deltacounter); } - if (out_bits_remaining==0) + if (out_bits_remaining == 0) { out_bits_remaining = 7; if (sample_length > 0) @@ -619,7 +620,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo public void set_lenctr_en(bool en) { - if (!en) + if (!en) //disable playback sample_length = 0; else @@ -688,12 +689,20 @@ namespace BizHawk.Emulation.Consoles.Nintendo public void SyncState(Serializer ser) { + ser.Sync("irq_pending", ref irq_pending); + ser.Sync("dmc_irq", ref dmc_irq); + ser.Sync("pending_reg", ref pending_reg); + ser.Sync("pending_val", ref pending_val); + ser.Sync("sequencer_counter", ref sequencer_counter); ser.Sync("sequencer_step", ref sequencer_step); ser.Sync("sequencer_mode", ref sequencer_mode); - ser.Sync("sequencer_irq_inhibit", ref sequencer_irq_inhibit); + ser.Sync("sequencer_irq_inhibit;", ref sequencer_irq_inhibit); ser.Sync("sequencer_irq", ref sequencer_irq); - ser.Sync("dmc_irq", ref dmc_irq); + ser.Sync("sequence_reset_pending", ref sequence_reset_pending); + ser.Sync("sequencer_irq_clear_pending", ref sequencer_irq_clear_pending); + ser.Sync("sequencer_irq_assert", ref sequencer_irq_assert); + pulse[0].SyncState(ser); pulse[1].SyncState(ser); triangle.SyncState(ser); @@ -705,115 +714,153 @@ namespace BizHawk.Emulation.Consoles.Nintendo TriangleUnit triangle = new TriangleUnit(); NoiseUnit noise = new NoiseUnit(); DMCUnit dmc; - bool sequencer_irq; - public bool dmc_irq; + + bool irq_pending; + bool dmc_irq; + int pending_reg = -1; + byte pending_val = 0; + + int sequencer_counter, sequencer_step, sequencer_mode, sequencer_irq_inhibit; + bool sequencer_irq, sequence_reset_pending, sequencer_irq_clear_pending, sequencer_irq_assert; public void RunDMCFetch() { dmc.Fetch(); } - int sequencer_counter, sequencer_step, sequencer_mode, sequencer_irq_inhibit; void sequencer_reset() { sequencer_counter = 0; if (sequencer_mode == 1) { - sequencer_step = 5; - sequencer_check(); + sequencer_step = 0; + QuarterFrame(); + HalfFrame(); } else - sequencer_step = 1; + sequencer_step = 0; } - //21477272 master clock - //1789772 cpu clock (master / 12) - //240 apu clock (master / 89490) = (cpu / 7457) + //these figures are not valid for PAL. they must be recalculated with nintendulator's values above + //these values (the NTSC at least) are derived from nintendulator. they are all 2 higher than the specifications, due to some shortcoming in the emulation + //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[]{7458,14914,22372,29830}, + new int[]{7458,14914,22372,29830,37282} + }; + void sequencer_tick() { sequencer_counter++; - //this figure is not valid for PAL. it must be recalculated - if (sequencer_counter != 7457) return; - sequencer_counter = 0; - sequencer_step++; + if (sequence_reset_pending) + { + sequencer_reset(); + sequence_reset_pending = false; + } + if (sequencer_lut[sequencer_mode][sequencer_step] != sequencer_counter) + return; sequencer_check(); } public void SyncIRQ() { - nes.irq_apu = sequencer_irq | dmc_irq; - //if (nes.irq_apu) Console.WriteLine("apu irq"); - nes.sync_irq(); + irq_pending = sequencer_irq | dmc_irq; } void sequencer_check() { //Console.WriteLine("sequencer mode {0} step {1}", sequencer_mode, sequencer_step); + bool quarter, half, reset; switch (sequencer_mode) { case 0: //4-step - pulse[0].clock_env(); - pulse[1].clock_env(); - triangle.clock_linear_counter(); - noise.clock_env(); - if (sequencer_step == 2 || sequencer_step == 4) + quarter = true; + half = (sequencer_step == 1 || sequencer_step == 3); + reset = sequencer_step == 3; + if (reset && sequencer_irq_inhibit == 0) { - pulse[0].clock_length_and_sweep(); - pulse[1].clock_length_and_sweep(); - triangle.clock_length_and_sweep(); - noise.clock_length_and_sweep(); - } - if (sequencer_step == 4) - { - if (sequencer_irq_inhibit == 0) - { - sequencer_irq = true; - SyncIRQ(); - } - sequencer_step = 1; + //Console.WriteLine("{0} {1,5} set irq_assert", nes.Frame, sequencer_counter); + sequencer_irq_assert = true; } break; + case 1: //5-step - if (sequencer_step != 4) - { - pulse[0].clock_env(); - pulse[1].clock_env(); - triangle.clock_linear_counter(); - noise.clock_env(); - } - if (sequencer_step == 2 || sequencer_step == 5) - { - pulse[0].clock_length_and_sweep(); - pulse[1].clock_length_and_sweep(); - triangle.clock_length_and_sweep(); - noise.clock_length_and_sweep(); - } - if (sequencer_step == 5) - sequencer_step = 1; + quarter = sequencer_step != 3; + half = (sequencer_step == 1 || sequencer_step == 4); + reset = sequencer_step == 4; break; + + default: + throw new InvalidOperationException(); } + + if (reset) + { + sequencer_counter = 0; + sequencer_step = 0; + } + else sequencer_step++; + + if (quarter) QuarterFrame(); + if (half) HalfFrame(); } + void HalfFrame() + { + pulse[0].clock_length_and_sweep(); + pulse[1].clock_length_and_sweep(); + triangle.clock_length_and_sweep(); + noise.clock_length_and_sweep(); + } + + void QuarterFrame() + { + pulse[0].clock_env(); + pulse[1].clock_env(); + triangle.clock_linear_counter(); + noise.clock_env(); + } public void WriteReg(int addr, byte val) + { + pending_reg = addr; + pending_val = val; + } + + void _WriteReg(int addr, byte val) { //Console.WriteLine("{0:X4} = {1:X2}", addr, val); switch (addr) { - case 0x4000: case 0x4001: case 0x4002: case 0x4003: + case 0x4000: + case 0x4001: + case 0x4002: + case 0x4003: pulse[0].WriteReg(addr - 0x4000, val); break; - case 0x4004: case 0x4005: case 0x4006: case 0x4007: + case 0x4004: + case 0x4005: + case 0x4006: + case 0x4007: pulse[1].WriteReg(addr - 0x4004, val); break; - case 0x4008: case 0x4009: case 0x400A: case 0x400B: + case 0x4008: + case 0x4009: + case 0x400A: + case 0x400B: triangle.WriteReg(addr - 0x4008, val); break; - case 0x400C: case 0x400D: case 0x400E: case 0x400F: + case 0x400C: + case 0x400D: + case 0x400E: + case 0x400F: noise.WriteReg(addr - 0x400C, val); break; - case 0x4010: case 0x4011: case 0x4012: case 0x4013: + case 0x4010: + case 0x4011: + case 0x4012: + case 0x4013: dmc.WriteReg(addr - 0x4010, val); break; case 0x4015: @@ -824,14 +871,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo dmc.set_lenctr_en(val.Bit(4)); break; case 0x4017: - sequencer_mode = (val>>7)&1; + //Console.WriteLine("apu 4017 = {0:X2}", val); + sequencer_mode = (val >> 7) & 1; sequencer_irq_inhibit = (val >> 6) & 1; if (sequencer_irq_inhibit == 1) { - sequencer_irq = false; - SyncIRQ(); + sequencer_irq_clear_pending = true; } - sequencer_reset(); + sequence_reset_pending = true; break; } } @@ -841,36 +888,32 @@ namespace BizHawk.Emulation.Consoles.Nintendo switch (addr) { case 0x4015: - { - //notice a missing bit here. should properly emulate with empty bus - //if an interrupt flag was set at the same moment of the read, it will read back as 1 but it will not be cleared. - int dmc_nonzero = dmc.IsLenCntNonZero() ? 1 : 0; - int noise_nonzero = noise.IsLenCntNonZero() ? 1 : 0; - int tri_nonzero = triangle.IsLenCntNonZero() ? 1 : 0; - int pulse1_nonzero = pulse[1].IsLenCntNonZero() ? 1 : 0; - int pulse0_nonzero = pulse[0].IsLenCntNonZero() ? 1 : 0; - int ret = ((dmc_irq?1:0) << 7) | ((sequencer_irq?1:0) << 6) | (dmc_nonzero << 4) | (noise_nonzero << 3) | (tri_nonzero<<2) | (pulse1_nonzero<<1) | (pulse0_nonzero); - sequencer_irq = false; - SyncIRQ(); - return (byte)ret; - } + { + //notice a missing bit here. should properly emulate with empty bus + //if an interrupt flag was set at the same moment of the read, it will read back as 1 but it will not be cleared. + int dmc_nonzero = dmc.IsLenCntNonZero() ? 1 : 0; + int noise_nonzero = noise.IsLenCntNonZero() ? 1 : 0; + int tri_nonzero = triangle.IsLenCntNonZero() ? 1 : 0; + int pulse1_nonzero = pulse[1].IsLenCntNonZero() ? 1 : 0; + int pulse0_nonzero = pulse[0].IsLenCntNonZero() ? 1 : 0; + int ret = ((dmc_irq ? 1 : 0) << 7) | ((sequencer_irq ? 1 : 0) << 6) | (dmc_nonzero << 4) | (noise_nonzero << 3) | (tri_nonzero << 2) | (pulse1_nonzero << 1) | (pulse0_nonzero); + //Console.WriteLine("{0} {1,5} $4015 clear irq, was at {2}", nes.Frame, sequencer_counter, sequencer_irq); + sequencer_irq = false; + SyncIRQ(); + return (byte)ret; + } default: //don't return 0xFF here or SMB will break return 0x00; } } - public void Run(int cycles) - { - for (int i = 0; i < cycles; i++) - RunOne(); - } - public void DiscardSamples() { metaspu.buffer.clear(); } + int toggle = 0; public void RunOne() { pulse[0].Run(); @@ -883,12 +926,42 @@ namespace BizHawk.Emulation.Consoles.Nintendo mix += pulse[0].sample; mix += pulse[1].sample; mix += triangle.sample; - mix += noise.sample>>1; + mix += noise.sample >> 1; mix += dmc.sample; EmitSample(mix); + //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(); + } + + //handle writes from the odd clock cycle + if (pending_reg != -1) _WriteReg(pending_reg, pending_val); + pending_reg = -1; + toggle = 1; + + //latch whatever irq logic we had and send to cpu + nes.irq_apu = irq_pending; + } + else toggle = 0; + sequencer_tick(); + sequencer_irq |= sequencer_irq_assert; + SyncIRQ(); + //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. @@ -909,9 +982,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo samp = 0; else panic_sample = samp; - + int this_samp = samp; - const double kMixRate = 44100.0/1789772.0; + const double kMixRate = 44100.0 / 1789772.0; const double kInvMixRate = (1 / kMixRate); timer += kMixRate; accumulate += samp; @@ -922,7 +995,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo timer -= 1; double ratio = (timer / kMixRate); double fractional = (this_samp - last_hwsamp) * ratio; - double factional_remainder = (this_samp - last_hwsamp) * (1-ratio); + double factional_remainder = (this_samp - last_hwsamp) * (1 - ratio); accumulate += fractional; accumulate *= 436; //32768/(15*4) -- adjust later for other sound channels @@ -936,7 +1009,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo } MetaspuSoundProvider metaspu = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); - public int MaxVolume { get; set; } // not supported + public int MaxVolume { get; set; } // not supported void ISoundProvider.GetSamples(short[] samples) { @@ -954,14 +1027,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo { //Console.WriteLine("a: {0} with todo: {1}",squeue.Count,samples.Length/2); - for (int i = 0; i < samples.Length/2; i++) + for (int i = 0; i < samples.Length / 2; i++) { int samp = 0; if (squeue.Count != 0) samp = squeue.Dequeue(); - - samples[i*2+0] = (short)(samp); - samples[i*2+1] = (short)(samp); + + samples[i * 2 + 0] = (short)(samp); + samples[i * 2 + 1] = (short)(samp); //bw.Write((short)samp); } } diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs index aa6ced4e41..436f654044 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs @@ -23,7 +23,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo CartInfo cart; //the current cart prototype. should be moved into the board, perhaps INESBoard board; //the board hardware that is currently driving things public bool SoundOn = true; - bool _irq_apu, _irq_cart; + int sprdma_countdown; //used to + bool _irq_apu, _irq_cart; //various irq signals that get merged to the cpu irq pin + + //irq state management public bool irq_apu { get { return _irq_apu; } set { _irq_apu = value; sync_irq(); } } public bool irq_cart { get { return _irq_cart; } set { _irq_cart = value; sync_irq(); } } void sync_irq() @@ -95,10 +98,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo static ByteBuffer cpu_sequence_NTSC = new ByteBuffer(new byte[]{3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3}); static ByteBuffer cpu_sequence_PAL = new ByteBuffer(new byte[]{4,3,3,3,3,4,3,3,3,3,4,3,3,3,3,4,3,3,3,3,4,3,3,3,3,4,3,3,3,3,4,3,3,3,3,4,3,3,3,3}); public int cpu_step, cpu_stepcounter, cpu_deadcounter; - public void DmaTimingHack(int amount) - { - cpu_deadcounter += amount; - } protected void RunCpuOne() { cpu_stepcounter++; @@ -107,10 +106,24 @@ namespace BizHawk.Emulation.Consoles.Nintendo cpu_step++; cpu_step &= 31; cpu_stepcounter = 0; - if (cpu_deadcounter == 0) + + if (sprdma_countdown > 0) + { + sprdma_countdown--; + if (sprdma_countdown == 0) + { + //its weird that this is 514.. normally itd be 512 (and people would say its wrong) or 513 (and people would say its right) + //but 514 passes test 4-irq_and_dma + cpu_deadcounter = 514; + } + } + + if(cpu_deadcounter>0) + cpu_deadcounter--; + else cpu.ExecuteOne(); - else cpu_deadcounter--; - if (SoundOn) apu.RunOne(); //THIS ISNT SAFE!!!!!!!!! + + if (SoundOn) apu.RunOne(); //THIS ISNT SAFE!!!!!!!!! SOUND MUST ALWAYS RUN!!!! ppu.PostCpuInstructionOne(); } } @@ -176,10 +189,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo { //read joystick port //many todos here - lagged = false; + lagged = false; byte ret; if(addr == 0x4016) - ret = ports[0].Read(); + ret = ports[0].Read(); else ret = ports[1].Read(); return ret; } @@ -193,7 +206,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo WriteMemory(0x2004, db); addr++; } - DmaTimingHack(513); + //schedule a sprite dma event for beginning 1 cycle in the future. + //this receives 2 because thats just the way it works out. + sprdma_countdown = 2; } /// diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs index beb09dd396..8c2b464414 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs @@ -597,9 +597,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo ser.Sync("cpu_accumulate", ref cpu_accumulate); ser.Sync("_irq_apu", ref _irq_apu); ser.Sync("_irq_cart", ref _irq_cart); + ser.Sync("sprdma_countdown", ref sprdma_countdown); + ser.Sync("cpu_step", ref cpu_step); + ser.Sync("cpu_stepcounter", ref cpu_stepcounter); + ser.Sync("cpu_deadcounter", ref cpu_deadcounter); sync_irq(); - //string inp = GetControllersAsMnemonic(); TODO sorry bout that - //ser.SyncFixedString("input", ref inp, 32); board.SyncState(ser); ppu.SyncState(ser); apu.SyncState(ser); diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs index 33f39b5569..96fe6dff0b 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs @@ -95,6 +95,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo ser.Sync("PPUGenLatch", ref PPUGenLatch); ser.Sync("vtoggle", ref vtoggle); ser.Sync("VRAMBuffer", ref VRAMBuffer); + ser.Sync("ppu_addr_temp", ref ppu_addr_temp); ser.Sync("OAM", ref OAM, false); ser.Sync("PALRAM", ref PALRAM, false); diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs index 3787d9c818..1dc84848ae 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs @@ -27,39 +27,67 @@ namespace BizHawk.Emulation.Consoles.Nintendo public short[] xbuf = new short[256*240]; - void Read_bgdata(ref BGDataRecord bgdata) { - int addr = ppur.get_ntread(); - bgdata.nt = ppubus_read(addr, true); - runppu(kFetchTime); - - addr = ppur.get_atread(); - byte at = ppubus_read(addr, true); - - //modify at to get appropriate palette shift - if((ppur.vt&2)!=0) at >>= 4; - if((ppur.ht&2)!=0) at >>= 2; - at &= 0x03; - at <<= 2; - - bgdata.at = at; - - //horizontal scroll clocked at cycle 3 and then - //vertical scroll at 251 - runppu(1); - if (reg_2001.PPUON) + int ppu_addr_temp; + void Read_bgdata(ref BGDataRecord bgdata) + { + for (int i = 0; i < 8; i++) + Read_bgdata(i,ref bgdata); + } + void Read_bgdata(int cycle, ref BGDataRecord bgdata) + { + switch (cycle) { - ppur.increment_hsc(); - if (ppur.status.cycle == 251) - ppur.increment_vs(); - } - runppu(1); + case 0: + ppu_addr_temp = ppur.get_ntread(); + bgdata.nt = ppubus_read(ppu_addr_temp, true); + runppu(1); + break; + case 1: + runppu(1); + break; + case 2: + { + ppu_addr_temp = ppur.get_atread(); + byte at = ppubus_read(ppu_addr_temp, true); - addr = ppur.get_ptread(bgdata.nt); - bgdata.pt_0 = ppubus_read(addr, true); - runppu(kFetchTime); - addr |= 8; - bgdata.pt_1 = ppubus_read(addr, true); - runppu(kFetchTime); + //modify at to get appropriate palette shift + if ((ppur.vt & 2) != 0) at >>= 4; + if ((ppur.ht & 2) != 0) at >>= 2; + at &= 0x03; + at <<= 2; + bgdata.at = at; + + //horizontal scroll clocked at cycle 3 and then + //vertical scroll at 251 + runppu(1); + if (reg_2001.PPUON) + { + ppur.increment_hsc(); + if (ppur.status.cycle == 251) + ppur.increment_vs(); + } + break; + } + case 3: + runppu(1); + break; + case 4: + ppu_addr_temp = ppur.get_ptread(bgdata.nt); + bgdata.pt_0 = ppubus_read(ppu_addr_temp, true); + runppu(1); + break; + case 5: + runppu(1); + break; + case 6: + ppu_addr_temp |= 8; + bgdata.pt_1 = ppubus_read(ppu_addr_temp, true); + runppu(1); + break; + case 7: + runppu(1); + break; + } //switch(cycle) } unsafe struct TempOAM @@ -124,12 +152,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo { ppur.status.cycle = 0; - //if (!reg_2001.PPUON) - //{ - // runppu(kLineTime); - // continue; - //} - ppur.status.sl = sl; int yp = sl - 1; @@ -150,12 +172,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo //two of those tiles were read in the last scanline. for (int xt = 0; xt < 32; xt++) { - Read_bgdata(ref bgdata[xt + 2]); - //ok, we're also going to draw here. //unless we're on the first dummy scanline if (sl != 0) { + + int xstart = xt << 3; oamcount = oamcounts[renderslot]; int target = (yp << 8) + xstart; @@ -167,6 +189,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo for (int xp = 0; xp < 8; xp++, rasterpos++) { + //process the current clock's worth of bg data fetching + //this needs to be split into 8 pieces or else exact sprite 0 hitting wont work due to the cpu not running while the sprite renders below + Read_bgdata(xp, ref bgdata[xt + 2]); + //bg pos is different from raster pos due to its offsetability. //so adjust for that here int bgpos = rasterpos + ppur.fh; @@ -187,18 +213,27 @@ namespace BizHawk.Emulation.Consoles.Nintendo } else { + if (!renderspritenow) + { + //according to qeed's doc, use palette 0 or $2006's value if it is & 0x3Fxx + int addr = ppur.get_2007access(); + if ((addr & 0x3F00) == 0x3F00) + { + pixel = addr & 0x1F; + } + } pixelcolor = PALRAM[pixel]; - pixelcolor |= 0x8000; + pixelcolor |= 0x8000; //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later } if (!nes.CoreInputComm.NES_ShowBG) - pixelcolor = 0x8000; + pixelcolor = 0x8000; //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later //look for a sprite to be drawn bool havepixel = false; for (int s = 0; s < oamcount; s++) { - TempOAM *oam = &oams[(renderslot<<6)+s]; + TempOAM* oam = &oams[(renderslot << 6) + s]; { int x = oam->oam[3]; if (rasterpos >= x && rasterpos < x + 8) @@ -241,7 +276,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo if ((pixel & 3) != 0) { drawsprite = false; - } + } } if (drawsprite && nes.CoreInputComm.NES_ShowOBJ) @@ -258,12 +293,20 @@ namespace BizHawk.Emulation.Consoles.Nintendo }//oamcount loop + + if (reg_2001.color_disable) + pixelcolor &= 0x30; + xbuf[target] = PaletteAdjustPixel(pixelcolor); target++; } //loop across 8 pixels } //scanline != 0 + else + { + Read_bgdata(ref bgdata[xt + 2]); + } } //loop across 32 tiles