nes accuracy fixes

- pass more apu_test 4-jitter and 6-irq_flag_timing (necessary for timing on other tests)
- pass all cpu interrupt tests
- pass all sprite hit tests
This commit is contained in:
zeromus 2012-03-25 09:25:27 +00:00
parent f694959da9
commit d3321f552f
7 changed files with 390 additions and 241 deletions

View File

@ -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++;

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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;
}
/// <summary>

View File

@ -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);

View File

@ -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);

View File

@ -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