diff --git a/BizHawk.Emulation/Consoles/Nintendo/Docs/test_status.txt b/BizHawk.Emulation/Consoles/Nintendo/Docs/test_status.txt index 5f989894d0..b1ad3f778f 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/Docs/test_status.txt +++ b/BizHawk.Emulation/Consoles/Nintendo/Docs/test_status.txt @@ -3,13 +3,13 @@ look for tests here: http://wiki.nesdev.com/w/index.php/Emulator_tests ==blargg's tests== MMC3: - FAIL for 4 and 6. i think 4 might not fail until some invisible PPU operations are correct. neither nintendulator or fceux pass these. + FAIL for 6. neither nintendulator or fceux pass this. we should be able to pass it once mmc3 variant support is finalized. ppu tests: PASS for all but power_up_palette.nes which is lame ppu_vbl_nmi: - FAIL for 9. But hardly anyone passes this..... + FAIL for 10. But hardly anyone passes this..... sprite hit tests: FAIL for 9 and 10. fceux passes 10 (oldppu) and nintendulator passes both. @@ -21,9 +21,6 @@ cpu_timing_test.nes official_only.nes PASS -all_instrs.nes - FAIL (freezes emu??). I think this requires undocumented instructions. - instr_timing.nes PASS @@ -33,6 +30,9 @@ cpu_interrupts.nes instr_misc.nes PASS +all_instrs.nes + FAIL (undocumented opcodes) + ============= nestest.nes diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/BoardSystem.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/BoardSystem.cs index e8ed2a9fce..ecd97ea62f 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/BoardSystem.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/BoardSystem.cs @@ -13,6 +13,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo { void Create(NES nes); bool Configure(NES.EDetectionOrigin origin); + + //gets called once per PPU clock, for boards with complex behaviour which must be monitoring clock (i.e. mmc3 irq counter) + void ClockPPU(); + byte ReadPRG(int addr); byte ReadPPU(int addr); byte PeekPPU(int addr); void AddressPPU(int addr); @@ -49,6 +53,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo this.NES = nes; } public abstract bool Configure(NES.EDetectionOrigin origin); + public virtual void ClockPPU() { } public CartInfo Cart { get { return NES.cart; } } public NES NES { get; set; } diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/MMC3_family.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/MMC3_family.cs index dc1eedf90c..a452433958 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/MMC3_family.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/MMC3_family.cs @@ -109,7 +109,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo //state public NES.NESBoardBase.EMirrorType mirror; - int ppubus_state, ppubus_statecounter; + int a12_old; byte irq_reload, irq_counter; bool irq_pending, irq_enable; @@ -147,6 +147,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo SyncIRQ(); break; case 0x6001: //$E001 - IRQ Enable + //board.NES.LogLine("irq en"); irq_enable = true; SyncIRQ(); break; @@ -156,7 +157,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo void IRQ_EQ_Pass() { if (irq_enable) + { + //board.NES.LogLine("mmc3 IRQ"); irq_pending = true; + } SyncIRQ(); } @@ -174,7 +178,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo else { irq_counter--; - //Console.WriteLine(irq_counter); if (irq_counter == 0) { IRQ_EQ_Pass(); @@ -182,20 +185,48 @@ namespace BizHawk.Emulation.Consoles.Nintendo } } - //TODO - this should be determined from NES timestamps to correctly emulate ppu writes interfering - public void Tick_PPU(int addr) + //it really seems like these should be the same but i cant seem to unify them. + //theres no sense in delaying the IRQ, so its logic must be tied to the separator. + //the hint, of course, is that the countdown value is the same. + //will someone else try to unify them? + int separator_counter; + int irq_countdown; + + public void ClockPPU() { - ppubus_statecounter++; - int state = (addr >> 12) & 1; - if (ppubus_state == 0 && ppubus_statecounter > 1 && state == 1) + if (separator_counter > 0) + separator_counter--; + + if (irq_countdown > 0) { - ppubus_statecounter = 0; - ClockIRQ(); + irq_countdown--; + if (irq_countdown == 0) + { + //board.NES.LogLine("ClockIRQ"); + ClockIRQ(); + } } - if (ppubus_state != state) + } + + + public void AddressPPU(int addr) + { + int a12 = (addr >> 12) & 1; + bool rising_edge = (a12 == 1 && a12_old == 0); + if (rising_edge) { - ppubus_state = state; + if (separator_counter > 0) + { + separator_counter = 15; + } + else + { + separator_counter = 15; + irq_countdown = 15; + } } + + a12_old = a12; } } @@ -276,9 +307,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo public override void AddressPPU(int addr) { - mmc3.Tick_PPU(addr); + mmc3.AddressPPU(addr); } - + + public override void ClockPPU() + { + mmc3.ClockPPU(); + } + protected override void BaseSetup() { wram_mask = (Cart.wram_size * 1024) - 1; diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TQROM.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TQROM.cs index 115e7613c2..dfadad75da 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TQROM.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TQROM.cs @@ -33,7 +33,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo int use_ram = (bank_1k >> 6) & 1; if (use_ram == 1) { - mmc3.Tick_PPU(addr); addr = ((bank_1k&0x3f) << 10) | (addr & 0x3FF); addr &= 0x1FFF; return VRAM[addr]; @@ -52,7 +51,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo int use_ram = (bank_1k >> 6) & 1; if (use_ram == 1) { - mmc3.Tick_PPU(addr); addr = ((bank_1k & 0x3f) << 10) | (addr & 0x3FF); addr &= 0x1FFF; VRAM[addr] = value; diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TVROM.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TVROM.cs index 584ecee7b7..81b7e198fe 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TVROM.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TVROM.cs @@ -30,7 +30,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo public override byte ReadPPU(int addr) { - mmc3.Tick_PPU(addr); if (addr < 0x2000) { //read patterns from mapper controlled area @@ -44,7 +43,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo public override void WritePPU(int addr, byte value) { - mmc3.Tick_PPU(addr); if (addr < 0x2000) { //nothing wired here diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs index 5b2eb01a6f..46a29a8df7 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs @@ -90,6 +90,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo protected void RunCpu(int ppu_cycles) { + //not being used right now. maybe needed later. + //Timestamp += ppu_cycles; + int cycles = ppu_cycles; if (ppu.PAL) cycles *= 15; @@ -111,7 +114,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo cpu_accumulate -= 48*todo; cpu.Execute(todo); apu.Run(todo); - ppu.TickCpu(); + ppu.PostCpuInstruction(todo); } } diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs index cf43999127..1e4ade3ba3 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs @@ -231,6 +231,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo bool lagged = true; bool islag = false; public int Frame { get { return _frame; } set { _frame = value; } } + public long Timestamp { get; private set; } public int LagCount { get { return _lagcount; } set { _lagcount = value; } } public bool IsLagFrame { get { return islag; } } diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs index 5b3dfb41fd..a14e9f9bde 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs @@ -23,14 +23,19 @@ namespace BizHawk.Emulation.Consoles.Nintendo } //when the ppu issues a read it goes through here and into the game board - public byte ppubus_read(int addr) + public byte ppubus_read(int addr, bool ppu) { + //speculative -- hardware doesnt touch the bus when the PPU is disabled + //(without this, smb3 title screen creates garbage when skipped) + if (!reg_2001.PPUON && ppu) + return 0xFF; + nes.board.AddressPPU(addr); //apply freeze - if (ppubus_freeze[addr].IsFrozen) - return ppubus_freeze[addr].value; - else - return nes.board.ReadPPU(addr); + if (ppubus_freeze[addr].IsFrozen) + return ppubus_freeze[addr].value; + else + return nes.board.ReadPPU(addr); } //debug tools peek into the ppu through this @@ -91,12 +96,15 @@ namespace BizHawk.Emulation.Consoles.Nintendo nes.cpu.NMI = true; } - public void TickCpu() + //this gets called once after each cpu instruction executes. + //anything that needs to happen at instruction granularity can get checked here + //to save having to check it at ppu cycle granularity + public void PostCpuInstruction(int todo) { - if (NMI_PendingCycles > 0) + if (NMI_PendingInstructions > 0) { - NMI_PendingCycles--; - if (NMI_PendingCycles == 0) + NMI_PendingInstructions--; + if (NMI_PendingInstructions <= 0) { TriggerNMI(); } @@ -105,34 +113,31 @@ namespace BizHawk.Emulation.Consoles.Nintendo void runppu(int x) { - //pputime+=x; - - //DON'T LIKE THIS.... ppur.status.cycle += x; while(ppur.status.cycle >= ppur.status.end_cycle) ppur.status.cycle -= ppur.status.end_cycle; - if(x == 0) return; - - nes.RunCpu(1); - x--; - - if (Reg2002_vblank_active_pending) + //run one ppu cycle at a time so we can interact with the ppu and clockPPU at high granularity + for (int i = 0; i < x; i++) { + //might not actually run a cpu cycle if there are none to be run right now + nes.RunCpu(1); + if (Reg2002_vblank_active_pending) - Reg2002_vblank_active = 1; - Reg2002_vblank_active_pending = false; - } + { + if (Reg2002_vblank_active_pending) + Reg2002_vblank_active = 1; + Reg2002_vblank_active_pending = false; + } - if (Reg2002_vblank_clear_pending) - { - Reg2002_vblank_active = 0; - Reg2002_vblank_clear_pending = false; - } + if (Reg2002_vblank_clear_pending) + { + Reg2002_vblank_active = 0; + Reg2002_vblank_clear_pending = false; + } - - if (x == 0) return; - nes.RunCpu(x); + nes.board.ClockPPU(); + } } //hack diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs index d92e61aafb..e1d226cd43 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs @@ -296,7 +296,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo Bit Reg2002_vblank_active; //Vertical blank start (0: has not started; 1: has started) bool Reg2002_vblank_active_pending; //set of Reg2002_vblank_active is pending bool Reg2002_vblank_clear_pending; //ppu's clear of vblank flag is pending - int NMI_PendingCycles; + int NMI_PendingInstructions; byte PPUGenLatch; public PPUREGS ppur; public Reg_2000 reg_2000; @@ -330,7 +330,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo if (!reg_2000.vblank_nmi_gen & ((value & 0x80) != 0) && (Reg2002_vblank_active) && !Reg2002_vblank_clear_pending) { //if we just unleashed the vblank interrupt then activate it now - NMI_PendingCycles = 2; + NMI_PendingInstructions = 2; } reg_2000.Value = value; } @@ -465,7 +465,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo byte ret = VRAMBuffer; //in any case, we read from the ppu bus - VRAMBuffer = ppubus_read(addr); + VRAMBuffer = ppubus_read(addr,false); //but reads from the palette are implemented in the PPU and return immediately if ((addr & 0x3F00) == 0x3F00) diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs index f11629dc7e..2d91b3c6e7 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs @@ -27,11 +27,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo void Read_bgdata(ref BGDataRecord bgdata) { int addr = ppur.get_ntread(); - bgdata.nt = ppubus_read(addr); + bgdata.nt = ppubus_read(addr, true); runppu(kFetchTime); addr = ppur.get_atread(); - byte at = ppubus_read(addr); + byte at = ppubus_read(addr, true); //modify at to get appropriate palette shift if((ppur.vt&2)!=0) at >>= 4; @@ -53,10 +53,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo runppu(1); addr = ppur.get_ptread(bgdata.nt); - bgdata.pt_0 = ppubus_read(addr); + bgdata.pt_0 = ppubus_read(addr, true); runppu(kFetchTime); addr |= 8; - bgdata.pt_1 = ppubus_read(addr); + bgdata.pt_1 = ppubus_read(addr, true); runppu(kFetchTime); } @@ -380,11 +380,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo //pattern table fetches int addr = patternAddress; - oam->patterns[0] = ppubus_read(addr); + oam->patterns[0] = ppubus_read(addr, true); if (realSprite) runppu(kFetchTime); addr += 8; - oam->patterns[1] = ppubus_read(addr); + oam->patterns[1] = ppubus_read(addr, true); if (realSprite) runppu(kFetchTime); //hflip @@ -405,7 +405,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo //screen (or basically, the first nametable address that will be accessed when //the PPU is fetching background data on the next scanline). //(not implemented yet) - runppu(kFetchTime * 2); if (sl == 0) { if (idleSynch && reg_2001.show_bg && !PAL) @@ -421,6 +420,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo for (int xt = 0; xt < 2; xt++) Read_bgdata(ref bgdata[xt]); + runppu(kFetchTime * 2); + //After memory access 170, the PPU simply rests for 4 cycles (or the //equivelant of half a memory access cycle) before repeating the whole //pixel/scanline rendering process. If the scanline being rendered is the very diff --git a/BizHawk.MultiClient/NEStools/NESNameTableViewer.cs b/BizHawk.MultiClient/NEStools/NESNameTableViewer.cs index 439f5e1d08..b0468951e5 100644 --- a/BizHawk.MultiClient/NEStools/NESNameTableViewer.cs +++ b/BizHawk.MultiClient/NEStools/NESNameTableViewer.cs @@ -63,8 +63,8 @@ namespace BizHawk.MultiClient int nt = ppu.ppubus_peek(ntbyte_ptr + 0x2000); int at = ppu.ppubus_peek(atbyte_ptr + 0x2000); - if((ty&1)!=0) at >>= 4; - if((tx&1)!=0) at >>= 2; + if((ty&2)!=0) at >>= 4; + if((tx&2)!=0) at >>= 2; at &= 0x03; at <<= 2; @@ -74,7 +74,11 @@ namespace BizHawk.MultiClient int pt_0 = ppu.ppubus_peek(pt_addr); int pt_1 = ppu.ppubus_peek(pt_addr + 8); int pixel = ((pt_0 >> (7 - bgpx)) & 1) | (((pt_1 >> (7 - bgpx)) & 1) << 1); - pixel |= at; + + //if the pixel is transparent, draw the backdrop color + //TODO - consider making this optional? nintendulator does it and fceux doesnt need to do it due to buggy palette logic which creates the same effect + if(pixel!=0) + pixel |= at; pixel = ppu.PALRAM[pixel]; int cvalue = Nes.LookupColor(pixel); diff --git a/BizHawk.MultiClient/NEStools/NESPPU.cs b/BizHawk.MultiClient/NEStools/NESPPU.cs index 2dfbdf4d51..1c1b930623 100644 --- a/BizHawk.MultiClient/NEStools/NESPPU.cs +++ b/BizHawk.MultiClient/NEStools/NESPPU.cs @@ -115,15 +115,6 @@ namespace BizHawk.MultiClient } PatternView.pattern.UnlockBits(bmpdata); PatternView.Refresh(); - - //Nametable viewer - for (int i = 0; i < 30; i++) - { - for (int j = 0; j < 32; j++) - { - int v = Nes.ppu.ppubus_read(0x2000 + i + j); - } - } } private void NESPPU_Load(object sender, EventArgs e)