Merge pull request #655 from alyosha-tas/master

Inital DMA commits
This commit is contained in:
alyosha-tas 2016-06-29 09:49:11 -04:00 committed by GitHub
commit 029f46626a
3 changed files with 214 additions and 107 deletions

View File

@ -19,7 +19,7 @@ using BizHawk.Common.NumberExtensions;
namespace BizHawk.Emulation.Cores.Nintendo.NES
{
public sealed class APU
public sealed class APU
{
public static bool CFG_DECLICK = true;
@ -29,7 +29,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
public int NoiseV = 247;
public int DMCV = 167;
public int dmc_dma_countdown=-1;
public int dmc_dma_countdown = -1;
public bool call_from_write;
public bool recalculate = false;
@ -61,16 +62,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
{0,1,1,1,1,0,0,0}, //(50%)
{1,0,0,1,1,1,1,1}, //(25% negated (75%))
};
static byte[] TRIANGLE_TABLE =
static byte[] TRIANGLE_TABLE =
{
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
};
static int[] NOISE_TABLE_NTSC =
static int[] NOISE_TABLE_NTSC =
{
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068
};
static int[] NOISE_TABLE_PAL =
static int[] NOISE_TABLE_PAL =
{
4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778
};
@ -578,8 +579,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
{
get
{
bool en = len_cnt != 0 && linear_counter != 0;
return !en;
bool en = len_cnt != 0 && linear_counter != 0;
return !en;
}
}
@ -613,11 +614,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
seq = (seq + 1) & 0x1F;
timer = timer_cnt_reload;
}
if(CFG_DECLICK) // this looks ugly...
if (CFG_DECLICK) // this looks ugly...
newsample = TRIANGLE_TABLE[(seq + 8) & 0x1F];
else
newsample = TRIANGLE_TABLE[seq];
//special hack: frequently, games will use the maximum frequency triangle in order to mute it
//apparently this results in the DAC for the triangle wave outputting a steady level at about 7.5
//so we'll emulate it at the digital level
@ -689,9 +690,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
bool loop_flag;
int timer_reload;
//dmc delay per visual 2a03
int delay;
int timer;
int user_address;
uint user_length, sample_length;
public uint user_length, sample_length;
int sample_address, sample_buffer;
bool sample_buffer_filled;
@ -721,6 +725,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
ser.Sync("out_deltacounter", ref out_deltacounter);
ser.Sync("out_silence", ref out_silence);
ser.Sync("dmc_call_delay", ref delay);
//int sample = 0; //junk
//ser.Sync("sample", ref sample);
ser.EndSection();
@ -737,9 +743,34 @@ 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:
// 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;
}
// 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
if (delay!=0)
{
delay--;
if (delay == 0)
{
if (!apu.call_from_write)
{
apu.dmc_dma_countdown = 4;
}
else
{
apu.dmc_dma_countdown = 3;
apu.call_from_write = false;
}
}
}
if (!sample_buffer_filled && sample_length > 0 && apu.dmc_dma_countdown == -1 && timer%2==0 && delay==0)
{
delay = 2;
}
}
void Clock()
@ -788,7 +819,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
}
else out_bits_remaining--;
}
public void set_lenctr_en(bool en)
@ -810,6 +841,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
sample_length = user_length;
}
if (!sample_buffer_filled)
{
// apparently the dmc is different if called from a cpu write, let's try
apu.call_from_write = true;
}
}
//irq is acknowledged or sure to be clear, in either case
@ -832,6 +868,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
loop_flag = val.Bit(6);
timer_reload = DMC_RATE[val & 0xF];
if (!irq_enabled) apu.dmc_irq = false;
//apu.dmc_irq = false;
apu.SyncIRQ();
break;
case 1:
@ -858,9 +895,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
sample_address = (ushort)(sample_address + 1);
//Console.WriteLine(sample_length);
//Console.WriteLine(user_length);
sample_length--;
//sample_length--;
apu.pending_length_change = 1;
}
if (sample_length == 0)
if ((sample_length-1) == 0)
{
if (loop_flag)
{
@ -891,7 +929,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
ser.Sync("dmc_dma_countdown", ref dmc_dma_countdown);
ser.Sync("toggle", ref toggle);
ser.Sync("sample_length_delay", ref pending_length_change);
ser.Sync("dmc_called_from_write", ref call_from_write);
pulse[0].SyncState(ser);
pulse[1].SyncState(ser);
@ -1034,20 +1073,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
int channel = index >> 2;
switch (channel)
{
case 0:
pulse[0].WriteReg(reg, val);
case 0:
pulse[0].WriteReg(reg, val);
break;
case 1:
pulse[1].WriteReg(reg, val);
case 1:
pulse[1].WriteReg(reg, val);
break;
case 2:
triangle.WriteReg(reg, val);
case 2:
triangle.WriteReg(reg, val);
break;
case 3:
noise.WriteReg(reg, val);
case 3:
noise.WriteReg(reg, val);
break;
case 4:
dmc.WriteReg(reg, val);
case 4:
dmc.WriteReg(reg, val);
break;
case 5:
if (addr == 0x4015)
@ -1057,7 +1096,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)
{
@ -1121,62 +1160,84 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
public int DebugCallbackTimer;
int toggle = 0;
public void RunOne()
int pending_length_change;
public void RunOne(bool read)
{
pulse[0].Run();
pulse[1].Run();
triangle.Run();
noise.Run();
dmc.Run();
EmitSample();
//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)
if (read)
{
//handle sequencer irq clear signal
sequencer_irq_assert = false;
if (sequencer_irq_clear_pending)
pulse[0].Run();
pulse[1].Run();
triangle.Run();
noise.Run();
dmc.Run();
}
else
{
if (pending_length_change>0)
{
//Console.WriteLine("{0} {1,5} $4017 clear irq (delayed)", nes.Frame, sequencer_counter);
sequencer_irq_clear_pending = false;
sequencer_irq = false;
SyncIRQ();
pending_length_change--;
if (pending_length_change==0)
{
dmc.sample_length--;
}
}
//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;
EmitSample();
sequencer_tick();
sequencer_irq |= sequencer_irq_assert;
SyncIRQ();
//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;
//since the units run concurrently, the APU frame sequencer is ran last because
//it can change the ouput values of the pulse/triangle channels
//we want the changes to affect it on the *next* cycle.
if (DebugCallbackDivider != 0)
{
if (DebugCallbackTimer == 0)
if (toggle == 0)
{
if (DebugCallback != null)
DebugCallback();
DebugCallbackTimer = DebugCallbackDivider;
}
else DebugCallbackTimer--;
//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.
if (DebugCallbackDivider != 0)
{
if (DebugCallbackTimer == 0)
{
if (DebugCallback != null)
DebugCallback();
DebugCallbackTimer = DebugCallbackDivider;
}
else DebugCallbackTimer--;
}
}
}
public struct Delta
@ -1211,7 +1272,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
static APU()
{
const double scale = 43803.0;
pulse_table = new int[31];
tnd_table = new int[203];
pulse_table[0] = tnd_table[0] = 0;
@ -1242,7 +1302,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if (!EnableNoise) s_noise = 0;
if (!EnableDMC) s_dmc = 0;
*/
//const float NOISEADJUST = 0.5f;
//linear approximation

View File

@ -294,6 +294,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
static ByteBuffer cpu_sequence_PAL = new ByteBuffer(new byte[]{3,3,3,4,3});
public int cpu_step, cpu_stepcounter, cpu_deadcounter;
public int oam_dma_index;
public bool oam_dma_exec=false;
public ushort oam_dma_addr;
public byte oam_dma_byte;
public bool dmc_dma_exec=false;
#if VS2012
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
@ -306,49 +312,86 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if(cpu_step == 5) cpu_step=0;
cpu_stepcounter = 0;
///////////////////////////
// OAM DMA start
///////////////////////////
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
// according to nesdev wiki, http://wiki.nesdev.com/w/index.php/PPU_OAM this is 513 on even cycles and 514 on odd cycles
// TODO: Implement that
cpu_deadcounter += 514;
if (cpu.TotalExecutedCycles%2==1)
{
cpu_deadcounter = 2;
} else
{
cpu_deadcounter = 1;
}
oam_dma_exec = true;
cpu.RDY = false;
oam_dma_index = 0;
}
}
if (oam_dma_exec && apu.dmc_dma_countdown !=1 && apu.dmc_dma_countdown !=2)
{
if (cpu_deadcounter==0)
{
if (oam_dma_index%2==0) {
oam_dma_byte = ReadMemory(oam_dma_addr);
oam_dma_addr++;
} else
{
WriteMemory(0x2004, oam_dma_byte);
}
oam_dma_index++;
if (oam_dma_index == 512) oam_dma_exec = false;
} else
{
cpu_deadcounter--;
}
}
/////////////////////////////
// OAM DMA end
/////////////////////////////
/////////////////////////////
// dmc dma start
/////////////////////////////
if (apu.dmc_dma_countdown>0)
{
cpu.RDY = false;
dmc_dma_exec = true;
apu.dmc_dma_countdown--;
if (apu.dmc_dma_countdown==0)
{
apu.RunDMCFetch();
cpu.RDY = true;
}
if (apu.dmc_dma_countdown==0)
{
dmc_dma_exec = false;
apu.dmc_dma_countdown = -1;
}
}
/////////////////////////////
// dmc dma end
/////////////////////////////
apu.RunOne(true);
if (cpu_deadcounter > 0)
{
cpu_deadcounter--;
}
else
{
cpu.IRQ = _irq_apu || Board.IRQSignal;
cpu.ExecuteOne();
}
cpu.IRQ = _irq_apu || Board.IRQSignal;
cpu.ExecuteOne();
apu.RunOne(false);
if (!dmc_dma_exec && !oam_dma_exec && !cpu.RDY)
cpu.RDY = true;
ppu.ppu_open_bus_decay(0);
apu.RunOne();
Board.ClockCPU();
ppu.PostCpuInstructionOne();
}
@ -452,16 +495,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
void Exec_OAMDma(byte val)
{
ushort addr = (ushort)(val << 8);
for (int i = 0; i < 256; i++)
{
byte db = ReadMemory((ushort)addr);
WriteMemory(0x2004, db);
addr++;
}
//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;
oam_dma_addr = (ushort)(val << 8);
sprdma_countdown = 1;
}
/// <summary>
@ -670,4 +708,4 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
}
}
}
}

View File

@ -61,7 +61,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
ser.Sync("cpu_step", ref cpu_step);
ser.Sync("cpu_stepcounter", ref cpu_stepcounter);
ser.Sync("cpu_deadcounter", ref cpu_deadcounter);
ser.BeginSection("Board");
//oam related
ser.Sync("Oam_Dma_Index", ref oam_dma_index);
ser.Sync("Oam_Dma_Exec", ref oam_dma_exec);
ser.Sync("Oam_Dma_Addr", ref oam_dma_addr);
ser.Sync("Oam_Dma_Byte", ref oam_dma_byte);
ser.Sync("Dmc_Dma_Exec", ref dmc_dma_exec);
ser.BeginSection("Board");
Board.SyncState(ser);
if (Board is NESBoardBase && !((NESBoardBase)Board).SyncStateFlag)
throw new InvalidOperationException("the current NES mapper didnt call base.SyncState");