DMA and frame timer updates
This commit is contained in:
parent
0a55bd8c29
commit
88251911f7
|
@ -2,7 +2,6 @@
|
||||||
//ALSO - consider whether we should even be doing it: the nonlinear-mixing behaviour probably depends on those biases being there.
|
//ALSO - consider whether we should even be doing it: the nonlinear-mixing behaviour probably depends on those biases being there.
|
||||||
//if we have a better high-pass filter somewhere then we might could cope with the weird biases
|
//if we have a better high-pass filter somewhere then we might could cope with the weird biases
|
||||||
//(mix higher integer precision with the non-linear mixer and then highpass filter befoure outputting s16s)
|
//(mix higher integer precision with the non-linear mixer and then highpass filter befoure outputting s16s)
|
||||||
//TODO - DMC cpu suspending - http://forums.nesdev.com/viewtopic.php?p=62690#p62690
|
|
||||||
|
|
||||||
//http://wiki.nesdev.com/w/index.php/APU_Mixer_Emulation
|
//http://wiki.nesdev.com/w/index.php/APU_Mixer_Emulation
|
||||||
//http://wiki.nesdev.com/w/index.php/APU
|
//http://wiki.nesdev.com/w/index.php/APU
|
||||||
|
@ -744,9 +743,36 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
//Any time the sample buffer is in an empty state and bytes remaining is not zero, the following occur:
|
//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)
|
// also note that the halt for DMC DMA occurs on APU cycles only (hence the timer check)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!sample_buffer_filled && sample_length > 0 && apu.dmc_dma_countdown == -1 && delay==0)
|
||||||
|
{
|
||||||
|
// calls from write take one less cycle, but start on a write instead of a read
|
||||||
|
if (!apu.call_from_write)
|
||||||
|
{
|
||||||
|
if (timer%2==1)
|
||||||
|
{
|
||||||
|
delay = 3;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
delay = 2;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
if (timer % 2 == 1)
|
||||||
|
{
|
||||||
|
delay = 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delay = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// I did some tests in Visual 2A03 and there seems to be some delay betwen when a DMC is first needed and when the
|
// I did some tests in Visual 2A03 and there seems to be some delay betwen when a DMC is first needed and when the
|
||||||
// process to execute the DMA starts. The details are not currently known, but it seems to be a 2 cycle delay
|
// process to execute the DMA starts. The details are not currently known, but it seems to be a 2 cycle delay
|
||||||
if (delay!=0)
|
if (delay != 0)
|
||||||
{
|
{
|
||||||
delay--;
|
delay--;
|
||||||
if (delay == 0)
|
if (delay == 0)
|
||||||
|
@ -762,14 +788,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
apu.call_from_write = false;
|
apu.call_from_write = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sample_buffer_filled && sample_length > 0 && apu.dmc_dma_countdown == -1 && timer%2==0 && delay==0)
|
|
||||||
{
|
|
||||||
delay = 2;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -887,7 +907,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
|
|
||||||
public void Fetch()
|
public void Fetch()
|
||||||
{
|
{
|
||||||
//TODO - cpu/apu DMC reads need to be emulated better!
|
|
||||||
if (sample_length != 0)
|
if (sample_length != 0)
|
||||||
{
|
{
|
||||||
sample_buffer = apu.nes.ReadMemory((ushort)sample_address);
|
sample_buffer = apu.nes.ReadMemory((ushort)sample_address);
|
||||||
|
@ -895,10 +914,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
sample_address = (ushort)(sample_address + 1);
|
sample_address = (ushort)(sample_address + 1);
|
||||||
//Console.WriteLine(sample_length);
|
//Console.WriteLine(sample_length);
|
||||||
//Console.WriteLine(user_length);
|
//Console.WriteLine(user_length);
|
||||||
//sample_length--;
|
sample_length--;
|
||||||
apu.pending_length_change = 1;
|
//apu.pending_length_change = 1;
|
||||||
}
|
}
|
||||||
if ((sample_length-1) == 0)
|
if (sample_length == 0)
|
||||||
{
|
{
|
||||||
if (loop_flag)
|
if (loop_flag)
|
||||||
{
|
{
|
||||||
|
@ -931,6 +950,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
ser.Sync("toggle", ref toggle);
|
ser.Sync("toggle", ref toggle);
|
||||||
ser.Sync("sample_length_delay", ref pending_length_change);
|
ser.Sync("sample_length_delay", ref pending_length_change);
|
||||||
ser.Sync("dmc_called_from_write", ref call_from_write);
|
ser.Sync("dmc_called_from_write", ref call_from_write);
|
||||||
|
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);
|
||||||
|
|
||||||
|
|
||||||
pulse[0].SyncState(ser);
|
pulse[0].SyncState(ser);
|
||||||
pulse[1].SyncState(ser);
|
pulse[1].SyncState(ser);
|
||||||
|
@ -949,9 +972,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
bool dmc_irq;
|
bool dmc_irq;
|
||||||
int pending_reg = -1;
|
int pending_reg = -1;
|
||||||
byte pending_val = 0;
|
byte pending_val = 0;
|
||||||
|
public int seq_tick;
|
||||||
|
public byte seq_val;
|
||||||
|
|
||||||
int sequencer_counter, sequencer_step, sequencer_mode, sequencer_irq_inhibit;
|
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_assert;
|
bool sequencer_irq, sequence_reset_pending, sequencer_irq_clear_pending, sequencer_irq_flag;
|
||||||
|
|
||||||
public void RunDMCFetch()
|
public void RunDMCFetch()
|
||||||
{
|
{
|
||||||
|
@ -976,17 +1001,51 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
//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
|
//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
|
//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[][]{
|
static int[][] sequencer_lut = new int[][]{
|
||||||
new int[]{7458,14914,22372,29830},
|
new int[]{7457,14913,22372,29830},
|
||||||
new int[]{7458,14914,22372,29830,37282}
|
new int[]{7458,14913,22372,29830,37282}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void sequencer_write_tick(byte val)
|
||||||
|
{
|
||||||
|
if (seq_tick>0)
|
||||||
|
{
|
||||||
|
seq_tick--;
|
||||||
|
if (seq_tick==0)
|
||||||
|
{
|
||||||
|
sequencer_mode = (val >> 7) & 1;
|
||||||
|
//Console.WriteLine("apu 4017 = {0:X2}", val);
|
||||||
|
sequencer_irq_inhibit = (val >> 6) & 1;
|
||||||
|
if (sequencer_irq_inhibit == 1)
|
||||||
|
{
|
||||||
|
sequencer_irq_flag = false;
|
||||||
|
}
|
||||||
|
sequencer_reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void sequencer_tick()
|
void sequencer_tick()
|
||||||
{
|
{
|
||||||
sequencer_counter++;
|
sequencer_counter++;
|
||||||
if (sequence_reset_pending)
|
if (sequencer_mode==0 && sequencer_counter==29829)
|
||||||
{
|
{
|
||||||
sequencer_reset();
|
if (sequencer_irq_inhibit==0)
|
||||||
sequence_reset_pending = false;
|
{
|
||||||
|
sequencer_irq_assert = 2;
|
||||||
|
sequencer_irq_flag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
HalfFrame();
|
||||||
|
}
|
||||||
|
if (sequencer_mode == 0 && sequencer_counter == 29828 && sequencer_irq_inhibit == 0)
|
||||||
|
{
|
||||||
|
//sequencer_irq_assert = 2;
|
||||||
|
sequencer_irq_flag = true;
|
||||||
|
}
|
||||||
|
if (sequencer_mode == 1 && sequencer_counter == 37281)
|
||||||
|
{
|
||||||
|
HalfFrame();
|
||||||
}
|
}
|
||||||
if (sequencer_lut[sequencer_mode][sequencer_step] != sequencer_counter)
|
if (sequencer_lut[sequencer_mode][sequencer_step] != sequencer_counter)
|
||||||
return;
|
return;
|
||||||
|
@ -1006,18 +1065,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
{
|
{
|
||||||
case 0: //4-step
|
case 0: //4-step
|
||||||
quarter = true;
|
quarter = true;
|
||||||
half = (sequencer_step == 1 || sequencer_step == 3);
|
half = sequencer_step == 1;
|
||||||
reset = sequencer_step == 3;
|
reset = sequencer_step == 3;
|
||||||
if (reset && sequencer_irq_inhibit == 0)
|
if (reset && sequencer_irq_inhibit == 0)
|
||||||
{
|
{
|
||||||
//Console.WriteLine("{0} {1,5} set irq_assert", nes.Frame, sequencer_counter);
|
//Console.WriteLine("{0} {1,5} set irq_assert", nes.Frame, sequencer_counter);
|
||||||
sequencer_irq_assert = true;
|
//sequencer_irq_assert = 2;
|
||||||
|
sequencer_irq_flag = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1: //5-step
|
case 1: //5-step
|
||||||
quarter = sequencer_step != 3;
|
quarter = sequencer_step != 3;
|
||||||
half = (sequencer_step == 1 || sequencer_step == 4);
|
half = sequencer_step == 1;
|
||||||
reset = sequencer_step == 4;
|
reset = sequencer_step == 4;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1056,6 +1116,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
{
|
{
|
||||||
//need to study what happens to apu and stuff..
|
//need to study what happens to apu and stuff..
|
||||||
sequencer_irq = false;
|
sequencer_irq = false;
|
||||||
|
sequencer_irq_flag = false;
|
||||||
_WriteReg(0x4015, 0);
|
_WriteReg(0x4015, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1100,15 +1161,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
}
|
}
|
||||||
else if (addr == 0x4017)
|
else if (addr == 0x4017)
|
||||||
{
|
{
|
||||||
//Console.WriteLine("apu 4017 = {0:X2}", val);
|
if (toggle==0)
|
||||||
sequencer_mode = (val >> 7) & 1;
|
|
||||||
sequencer_irq_inhibit = (val >> 6) & 1;
|
|
||||||
if (sequencer_irq_inhibit == 1)
|
|
||||||
{
|
{
|
||||||
sequencer_irq_clear_pending = true;
|
seq_tick = 4;
|
||||||
|
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
seq_tick = 3;
|
||||||
}
|
}
|
||||||
sequence_reset_pending = true;
|
|
||||||
break;
|
seq_val = val;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1128,7 +1190,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
int tri_nonzero = triangle.IsLenCntNonZero() ? 1 : 0;
|
int tri_nonzero = triangle.IsLenCntNonZero() ? 1 : 0;
|
||||||
int pulse1_nonzero = pulse[1].IsLenCntNonZero() ? 1 : 0;
|
int pulse1_nonzero = pulse[1].IsLenCntNonZero() ? 1 : 0;
|
||||||
int pulse0_nonzero = pulse[0].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);
|
int ret = ((dmc_irq ? 1 : 0) << 7) | ((sequencer_irq_flag ? 1 : 0) << 6) | (dmc_nonzero << 4) | (noise_nonzero << 3) | (tri_nonzero << 2) | (pulse1_nonzero << 1) | (pulse0_nonzero);
|
||||||
return (byte)ret;
|
return (byte)ret;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -1145,7 +1207,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
{
|
{
|
||||||
byte ret = PeekReg(0x4015);
|
byte ret = PeekReg(0x4015);
|
||||||
//Console.WriteLine("{0} {1,5} $4015 clear irq, was at {2}", nes.Frame, sequencer_counter, sequencer_irq);
|
//Console.WriteLine("{0} {1,5} $4015 clear irq, was at {2}", nes.Frame, sequencer_counter, sequencer_irq);
|
||||||
sequencer_irq = false;
|
sequencer_irq_flag = false;
|
||||||
SyncIRQ();
|
SyncIRQ();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -1184,17 +1246,52 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
EmitSample();
|
EmitSample();
|
||||||
|
|
||||||
|
//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) _WriteReg(pending_reg, pending_val);
|
||||||
|
pending_reg = -1;
|
||||||
|
|
||||||
|
|
||||||
|
sequencer_tick();
|
||||||
|
sequencer_write_tick(seq_val);
|
||||||
|
|
||||||
|
if (toggle==0)
|
||||||
|
{
|
||||||
|
toggle = 1;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
toggle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (sequencer_irq_assert>0) {
|
||||||
|
sequencer_irq_assert--;
|
||||||
|
if (sequencer_irq_assert==0)
|
||||||
|
{
|
||||||
|
sequencer_irq = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SyncIRQ();
|
||||||
|
nes.irq_apu = irq_pending;
|
||||||
|
|
||||||
|
if (sequencer_irq_flag == false)
|
||||||
|
sequencer_irq = false;
|
||||||
|
|
||||||
|
/*
|
||||||
//this (and the similar line below) is a crude hack
|
//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
|
//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
|
//be sure to test "apu_test" if you mess with this
|
||||||
sequencer_irq |= sequencer_irq_assert;
|
//sequencer_irq |= sequencer_irq_assert;
|
||||||
|
|
||||||
if (toggle == 0)
|
//if (toggle == 0)
|
||||||
{
|
//{
|
||||||
//handle sequencer irq clear signal
|
//handle sequencer irq clear signal
|
||||||
sequencer_irq_assert = false;
|
sequencer_irq_assert = false;
|
||||||
if (sequencer_irq_clear_pending)
|
if (sequencer_irq_clear_pending)
|
||||||
|
@ -1204,10 +1301,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
sequencer_irq = false;
|
sequencer_irq = false;
|
||||||
SyncIRQ();
|
SyncIRQ();
|
||||||
}
|
}
|
||||||
//handle writes from the odd clock cycle
|
|
||||||
if (pending_reg != -1) _WriteReg(pending_reg, pending_val);
|
|
||||||
pending_reg = -1;
|
|
||||||
|
|
||||||
|
|
||||||
toggle = 1;
|
toggle = 1;
|
||||||
|
|
||||||
|
@ -1215,11 +1308,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
nes.irq_apu = irq_pending;
|
nes.irq_apu = irq_pending;
|
||||||
}
|
}
|
||||||
else toggle = 0;
|
else toggle = 0;
|
||||||
|
*/
|
||||||
|
|
||||||
sequencer_tick();
|
|
||||||
sequencer_irq |= sequencer_irq_assert;
|
|
||||||
SyncIRQ();
|
|
||||||
|
|
||||||
//since the units run concurrently, the APU frame sequencer is ran last because
|
//since the units run concurrently, the APU frame sequencer is ran last because
|
||||||
//it can change the ouput values of the pulse/triangle channels
|
//it can change the ouput values of the pulse/triangle channels
|
||||||
|
|
|
@ -299,6 +299,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
public ushort oam_dma_addr;
|
public ushort oam_dma_addr;
|
||||||
public byte oam_dma_byte;
|
public byte oam_dma_byte;
|
||||||
public bool dmc_dma_exec=false;
|
public bool dmc_dma_exec=false;
|
||||||
|
public bool dmc_realign;
|
||||||
|
public bool IRQ_delay;
|
||||||
|
public bool special_case_delay; // very ugly but the only option
|
||||||
|
|
||||||
#if VS2012
|
#if VS2012
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
@ -323,7 +326,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
sprdma_countdown--;
|
sprdma_countdown--;
|
||||||
if (sprdma_countdown == 0)
|
if (sprdma_countdown == 0)
|
||||||
{
|
{
|
||||||
if (cpu.TotalExecutedCycles%2==1)
|
if (cpu.TotalExecutedCycles%2==0)
|
||||||
{
|
{
|
||||||
cpu_deadcounter = 2;
|
cpu_deadcounter = 2;
|
||||||
} else
|
} else
|
||||||
|
@ -333,10 +336,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
oam_dma_exec = true;
|
oam_dma_exec = true;
|
||||||
cpu.RDY = false;
|
cpu.RDY = false;
|
||||||
oam_dma_index = 0;
|
oam_dma_index = 0;
|
||||||
|
special_case_delay = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oam_dma_exec && apu.dmc_dma_countdown !=1 && apu.dmc_dma_countdown !=2)
|
if (oam_dma_exec && apu.dmc_dma_countdown !=1 && !dmc_realign)
|
||||||
{
|
{
|
||||||
if (cpu_deadcounter==0)
|
if (cpu_deadcounter==0)
|
||||||
{
|
{
|
||||||
|
@ -355,6 +359,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
{
|
{
|
||||||
cpu_deadcounter--;
|
cpu_deadcounter--;
|
||||||
}
|
}
|
||||||
|
} else if (apu.dmc_dma_countdown==1)
|
||||||
|
{
|
||||||
|
dmc_realign = true;
|
||||||
|
} else if (dmc_realign)
|
||||||
|
{
|
||||||
|
dmc_realign = false;
|
||||||
}
|
}
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// OAM DMA end
|
// OAM DMA end
|
||||||
|
@ -382,12 +392,29 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
// dmc dma end
|
// dmc dma end
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
apu.RunOne(true);
|
apu.RunOne(true);
|
||||||
|
|
||||||
cpu.IRQ = _irq_apu || Board.IRQSignal;
|
if (cpu.RDY && !IRQ_delay)
|
||||||
cpu.ExecuteOne();
|
{
|
||||||
|
cpu.IRQ = _irq_apu || Board.IRQSignal;
|
||||||
|
} else if (special_case_delay || apu.dmc_dma_countdown==3)
|
||||||
|
{
|
||||||
|
cpu.IRQ = _irq_apu || Board.IRQSignal;
|
||||||
|
special_case_delay = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cpu.ExecuteOne();
|
||||||
apu.RunOne(false);
|
apu.RunOne(false);
|
||||||
|
|
||||||
|
if (IRQ_delay)
|
||||||
|
IRQ_delay = false;
|
||||||
|
|
||||||
if (!dmc_dma_exec && !oam_dma_exec && !cpu.RDY)
|
if (!dmc_dma_exec && !oam_dma_exec && !cpu.RDY)
|
||||||
|
{
|
||||||
cpu.RDY = true;
|
cpu.RDY = true;
|
||||||
|
IRQ_delay = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ppu.ppu_open_bus_decay(0);
|
ppu.ppu_open_bus_decay(0);
|
||||||
|
|
Loading…
Reference in New Issue