protected void RunEmulator(IEmulator emulator, Func <bool> tickFunc) { IProcessorCore cpu = (IProcessorCore)emulator.Components.First(c => c.Name == "Ricoh 2A03 CPU"); IComponentWithClock ppu = (IComponentWithClock)emulator.Components.First(c => c.Name == "Ricoh 2C02 PPU"); IComponentWithClock apu = (IComponentWithClock)emulator.Components.First(c => c.Name == "NES APU"); while (true) { if (tickFunc != null && !tickFunc()) { break; } int cycles = cpu.Step(); if (cycles < 0) { Assert.Fail("CPU step failed!"); break; } for (int i = 0; i < cycles; i++) { // PPU ticks 3 times during each CPU cycle ppu.Tick(); ppu.Tick(); ppu.Tick(); } for (int i = 0; i < cycles; i++) { apu.Tick(); } } }
internal NesApu(IMemoryBus cpuBus, IProcessorCore cpu) { this.cpuIrq = cpu.GetInterruptByName("IRQ"); this.pulse1 = new PulseChannel(true); this.pulse2 = new PulseChannel(false); this.triangle = new TriangleChannel(); this.noise = new NoiseChannel(); this.dmc = new DeltaChannel(cpuBus, cpuIrq); this.isApuCycle = false; this.buffer = new byte[NesApu.BUFFER_SIZE]; this.ptr = Marshal.AllocHGlobal(4); }
void IEmulator.Run() { int cycles; int cycleCount = 0; Stopwatch sw = new Stopwatch(); sw.Start(); this.run = true; IProcessorCore core = (IProcessorCore)this.cpu; IComponentWithClock ppuClock = (IComponentWithClock)this.ppu; IComponentWithClock apuClock = (IComponentWithClock)this.cpu.APU; while (this.run) { cycles = core.Step(); if (cycles < 0) { break; } for (int i = 0; i < cycles; i++) { // PPU ticks 3 times during each CPU cycle ppuClock.Tick(); ppuClock.Tick(); ppuClock.Tick(); } for (int i = 0; i < cycles; i++) { apuClock.Tick(); } cycleCount += cycles; if (sw.ElapsedMilliseconds >= 1000) { Trace.WriteLine(String.Format("Emulated clock speed: {0} MHz", cycleCount / (1000000f))); cycleCount = 0; sw.Restart(); } } stopEvent.Set(); }
public void RunCpuTest() { IEmulator emulator = this.GetInstance(); emulator.LoadFile(@"TestRoms\nestest\nestest.nes"); List <string> expectedOutput = new List <string>(File.ReadAllLines(@"TestResources\CpuTests\nestest.expected")); IProcessorCore cpu = (IProcessorCore)emulator.Components.First(c => c.Name == "Ricoh 2A03 CPU"); IComponentWithRegisters ppu = (IComponentWithRegisters)emulator.Components.First(c => c.Name == "Ricoh 2C02 PPU"); IMemoryBus cpuBus = (IMemoryBus)emulator.Components.First(c => c.Name == "CPU Bus"); // Jump to automated test entry point cpu.GetRegisterByName("PC").Value = 0xC000; // Set initial state cpu.GetRegisterByName("S").Value = 0xFD; ppu.GetRegisterByName("cycle").Value = 0; ppu.GetRegisterByName("scanline").Value = 241; IEnumerator <string> outputLineEnumerator = expectedOutput.GetEnumerator(); outputLineEnumerator.MoveNext(); base.RunEmulator(emulator, () => { string state = this.DumpEmulatorState(cpu, ppu, cpuBus); string expectedOutputLine = outputLineEnumerator.Current; if (!expectedOutputLine.StartsWith("#") && !String.Equals(state, expectedOutputLine, StringComparison.Ordinal)) { Debug.WriteLine("**** {0}", (object)state); Debug.WriteLine("Exp: {0}", (object)expectedOutputLine); Assert.Fail("Output mismatch!"); return(false); } Debug.WriteLine(state); return(outputLineEnumerator.MoveNext()); }); }
// Dump current CPU/PPU state in Nintendulator-ish format private string DumpEmulatorState(IProcessorCore cpu, IComponentWithRegisters ppu, IMemoryBus cpuBus) { UInt16 PC = (UInt16)cpu.GetRegisterByName("PC").Value; byte A = (byte)cpu.GetRegisterByName("A").Value; byte X = (byte)cpu.GetRegisterByName("X").Value; byte Y = (byte)cpu.GetRegisterByName("Y").Value; string P = cpu.GetRegisterByName("P").FormattedValue; byte S = (byte)cpu.GetRegisterByName("S").Value; int cycle = ppu.GetRegisterByName("cycle").Value; int scanline = ppu.GetRegisterByName("scanline").Value; StringBuilder state = new StringBuilder(); state.AppendFormat("{0:X4} ", PC); IEnumerable <byte> instructionBytes = cpu.GetInstructionBytes(PC); if (!instructionBytes.Any()) { state.AppendFormat("UNKNOWN: {0:X2}", cpuBus.Read(PC)); return(state.ToString()); } IInstruction instruction = cpu.DecodeInstruction(instructionBytes); state.AppendFormat("{0,-8} {1,4} {2} {3}", String.Join(" ", instructionBytes.Select(b => String.Format("{0:X2}", b))), instruction.Mnemonic, instruction.Operands, instruction.OperandsDetail); state.Append(' ', 48 - state.Length); state.AppendFormat("A:{0:X2} X:{1:X2} Y:{2:X2} P:{3} SP:{4:X2} CYC:{5,3} SL:{6}", A, X, Y, P, S, cycle, scanline); return(state.ToString()); }
internal Ricoh2C02(IProcessorCore cpu, IMemoryBus cpuBus, IMemoryBus ppuBus) { this.ppuBus = ppuBus; this.vbi = cpu.GetInterruptByName("NMI"); this.vbiTimer = new Stopwatch(); this.paletteMemory = new byte[0x20]; this.primaryOam = new byte[0x100]; this.secondaryOam = new byte[0x20]; // PPU registers on CPU bus cpuBus.RegisterMappedDevice(this, 0x2000, 0x2007); // Palette data ppuBus.RegisterMappedDevice(this, 0x3F00, 0x3F1F); // Set up power-on state this.SetPpuCtrl(0x00); this.SetPpuMask(0x00); this.SetOamAddr(0x00); this.spriteZeroHit = false; this.spriteOverflow = false; this.isVBlank = false; this.oddFrame = false; this.scanline = -1; this.cycle = 0; this.registers = new ReadOnlyCollection <IRegister>( new IRegister[] { new RegisterWrapper("scanline", "PPU Scanline", 16, () => this.scanline, s => this.scanline = s), new RegisterWrapper("cycle", "PPU Cycle", 16, () => this.cycle, c => this.cycle = c) }); //this.vbiTimer.Start(); }
public static IProcessorInterrupt GetInterruptByName(this IProcessorCore cpu, string interruptName) { return(cpu.Interrupts.FirstOrDefault(i => String.Equals(i.Name, interruptName, StringComparison.Ordinal))); }