[NES] general ppu timing, mmc3, and nt view fixups.

This commit is contained in:
zeromus 2011-06-07 07:14:34 +00:00
parent d0ea2f7106
commit d05b81238e
12 changed files with 116 additions and 74 deletions

View File

@ -3,13 +3,13 @@ look for tests here: http://wiki.nesdev.com/w/index.php/Emulator_tests
==blargg's tests== ==blargg's tests==
MMC3: 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: ppu tests:
PASS for all but power_up_palette.nes which is lame PASS for all but power_up_palette.nes which is lame
ppu_vbl_nmi: ppu_vbl_nmi:
FAIL for 9. But hardly anyone passes this..... FAIL for 10. But hardly anyone passes this.....
sprite hit tests: sprite hit tests:
FAIL for 9 and 10. fceux passes 10 (oldppu) and nintendulator passes both. FAIL for 9 and 10. fceux passes 10 (oldppu) and nintendulator passes both.
@ -21,9 +21,6 @@ cpu_timing_test.nes
official_only.nes official_only.nes
PASS PASS
all_instrs.nes
FAIL (freezes emu??). I think this requires undocumented instructions.
instr_timing.nes instr_timing.nes
PASS PASS
@ -33,6 +30,9 @@ cpu_interrupts.nes
instr_misc.nes instr_misc.nes
PASS PASS
all_instrs.nes
FAIL (undocumented opcodes)
============= =============
nestest.nes nestest.nes

View File

@ -13,6 +13,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo
{ {
void Create(NES nes); void Create(NES nes);
bool Configure(NES.EDetectionOrigin origin); 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 ReadPRG(int addr);
byte ReadPPU(int addr); byte PeekPPU(int addr); byte ReadPPU(int addr); byte PeekPPU(int addr);
void AddressPPU(int addr); void AddressPPU(int addr);
@ -49,6 +53,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
this.NES = nes; this.NES = nes;
} }
public abstract bool Configure(NES.EDetectionOrigin origin); public abstract bool Configure(NES.EDetectionOrigin origin);
public virtual void ClockPPU() { }
public CartInfo Cart { get { return NES.cart; } } public CartInfo Cart { get { return NES.cart; } }
public NES NES { get; set; } public NES NES { get; set; }

View File

@ -109,7 +109,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
//state //state
public NES.NESBoardBase.EMirrorType mirror; public NES.NESBoardBase.EMirrorType mirror;
int ppubus_state, ppubus_statecounter; int a12_old;
byte irq_reload, irq_counter; byte irq_reload, irq_counter;
bool irq_pending, irq_enable; bool irq_pending, irq_enable;
@ -147,6 +147,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
SyncIRQ(); SyncIRQ();
break; break;
case 0x6001: //$E001 - IRQ Enable case 0x6001: //$E001 - IRQ Enable
//board.NES.LogLine("irq en");
irq_enable = true; irq_enable = true;
SyncIRQ(); SyncIRQ();
break; break;
@ -156,7 +157,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo
void IRQ_EQ_Pass() void IRQ_EQ_Pass()
{ {
if (irq_enable) if (irq_enable)
{
//board.NES.LogLine("mmc3 IRQ");
irq_pending = true; irq_pending = true;
}
SyncIRQ(); SyncIRQ();
} }
@ -174,7 +178,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo
else else
{ {
irq_counter--; irq_counter--;
//Console.WriteLine(irq_counter);
if (irq_counter == 0) if (irq_counter == 0)
{ {
IRQ_EQ_Pass(); IRQ_EQ_Pass();
@ -182,22 +185,50 @@ namespace BizHawk.Emulation.Consoles.Nintendo
} }
} }
//TODO - this should be determined from NES timestamps to correctly emulate ppu writes interfering //it really seems like these should be the same but i cant seem to unify them.
public void Tick_PPU(int addr) //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++; if (separator_counter > 0)
int state = (addr >> 12) & 1; separator_counter--;
if (ppubus_state == 0 && ppubus_statecounter > 1 && state == 1)
if (irq_countdown > 0)
{ {
ppubus_statecounter = 0; irq_countdown--;
if (irq_countdown == 0)
{
//board.NES.LogLine("ClockIRQ");
ClockIRQ(); ClockIRQ();
} }
if (ppubus_state != state)
{
ppubus_state = state;
} }
} }
public void AddressPPU(int addr)
{
int a12 = (addr >> 12) & 1;
bool rising_edge = (a12 == 1 && a12_old == 0);
if (rising_edge)
{
if (separator_counter > 0)
{
separator_counter = 15;
}
else
{
separator_counter = 15;
irq_countdown = 15;
}
}
a12_old = a12;
}
} }
public abstract class MMC3_Family_Board_Base : NES.NESBoardBase public abstract class MMC3_Family_Board_Base : NES.NESBoardBase
@ -276,7 +307,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo
public override void AddressPPU(int addr) public override void AddressPPU(int addr)
{ {
mmc3.Tick_PPU(addr); mmc3.AddressPPU(addr);
}
public override void ClockPPU()
{
mmc3.ClockPPU();
} }
protected override void BaseSetup() protected override void BaseSetup()

View File

@ -33,7 +33,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo
int use_ram = (bank_1k >> 6) & 1; int use_ram = (bank_1k >> 6) & 1;
if (use_ram == 1) if (use_ram == 1)
{ {
mmc3.Tick_PPU(addr);
addr = ((bank_1k&0x3f) << 10) | (addr & 0x3FF); addr = ((bank_1k&0x3f) << 10) | (addr & 0x3FF);
addr &= 0x1FFF; addr &= 0x1FFF;
return VRAM[addr]; return VRAM[addr];
@ -52,7 +51,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo
int use_ram = (bank_1k >> 6) & 1; int use_ram = (bank_1k >> 6) & 1;
if (use_ram == 1) if (use_ram == 1)
{ {
mmc3.Tick_PPU(addr);
addr = ((bank_1k & 0x3f) << 10) | (addr & 0x3FF); addr = ((bank_1k & 0x3f) << 10) | (addr & 0x3FF);
addr &= 0x1FFF; addr &= 0x1FFF;
VRAM[addr] = value; VRAM[addr] = value;

View File

@ -30,7 +30,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo
public override byte ReadPPU(int addr) public override byte ReadPPU(int addr)
{ {
mmc3.Tick_PPU(addr);
if (addr < 0x2000) if (addr < 0x2000)
{ {
//read patterns from mapper controlled area //read patterns from mapper controlled area
@ -44,7 +43,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo
public override void WritePPU(int addr, byte value) public override void WritePPU(int addr, byte value)
{ {
mmc3.Tick_PPU(addr);
if (addr < 0x2000) if (addr < 0x2000)
{ {
//nothing wired here //nothing wired here

View File

@ -90,6 +90,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo
protected void RunCpu(int ppu_cycles) protected void RunCpu(int ppu_cycles)
{ {
//not being used right now. maybe needed later.
//Timestamp += ppu_cycles;
int cycles = ppu_cycles; int cycles = ppu_cycles;
if (ppu.PAL) if (ppu.PAL)
cycles *= 15; cycles *= 15;
@ -111,7 +114,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
cpu_accumulate -= 48*todo; cpu_accumulate -= 48*todo;
cpu.Execute(todo); cpu.Execute(todo);
apu.Run(todo); apu.Run(todo);
ppu.TickCpu(); ppu.PostCpuInstruction(todo);
} }
} }

View File

@ -231,6 +231,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
bool lagged = true; bool lagged = true;
bool islag = false; bool islag = false;
public int Frame { get { return _frame; } set { _frame = value; } } 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 int LagCount { get { return _lagcount; } set { _lagcount = value; } }
public bool IsLagFrame { get { return islag; } } public bool IsLagFrame { get { return islag; } }

View File

@ -23,8 +23,13 @@ namespace BizHawk.Emulation.Consoles.Nintendo
} }
//when the ppu issues a read it goes through here and into the game board //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); nes.board.AddressPPU(addr);
//apply freeze //apply freeze
if (ppubus_freeze[addr].IsFrozen) if (ppubus_freeze[addr].IsFrozen)
@ -91,12 +96,15 @@ namespace BizHawk.Emulation.Consoles.Nintendo
nes.cpu.NMI = true; 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--; NMI_PendingInstructions--;
if (NMI_PendingCycles == 0) if (NMI_PendingInstructions <= 0)
{ {
TriggerNMI(); TriggerNMI();
} }
@ -105,17 +113,15 @@ namespace BizHawk.Emulation.Consoles.Nintendo
void runppu(int x) void runppu(int x)
{ {
//pputime+=x;
//DON'T LIKE THIS....
ppur.status.cycle += x; ppur.status.cycle += x;
while(ppur.status.cycle >= ppur.status.end_cycle) while(ppur.status.cycle >= ppur.status.end_cycle)
ppur.status.cycle -= ppur.status.end_cycle; ppur.status.cycle -= ppur.status.end_cycle;
if(x == 0) return; //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); nes.RunCpu(1);
x--;
if (Reg2002_vblank_active_pending) if (Reg2002_vblank_active_pending)
{ {
@ -130,9 +136,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
Reg2002_vblank_clear_pending = false; Reg2002_vblank_clear_pending = false;
} }
nes.board.ClockPPU();
if (x == 0) return; }
nes.RunCpu(x);
} }
//hack //hack

View File

@ -296,7 +296,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
Bit Reg2002_vblank_active; //Vertical blank start (0: has not started; 1: has started) 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_active_pending; //set of Reg2002_vblank_active is pending
bool Reg2002_vblank_clear_pending; //ppu's clear of vblank flag is pending bool Reg2002_vblank_clear_pending; //ppu's clear of vblank flag is pending
int NMI_PendingCycles; int NMI_PendingInstructions;
byte PPUGenLatch; byte PPUGenLatch;
public PPUREGS ppur; public PPUREGS ppur;
public Reg_2000 reg_2000; 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 (!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 //if we just unleashed the vblank interrupt then activate it now
NMI_PendingCycles = 2; NMI_PendingInstructions = 2;
} }
reg_2000.Value = value; reg_2000.Value = value;
} }
@ -465,7 +465,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
byte ret = VRAMBuffer; byte ret = VRAMBuffer;
//in any case, we read from the ppu bus //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 //but reads from the palette are implemented in the PPU and return immediately
if ((addr & 0x3F00) == 0x3F00) if ((addr & 0x3F00) == 0x3F00)

View File

@ -27,11 +27,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo
void Read_bgdata(ref BGDataRecord bgdata) { void Read_bgdata(ref BGDataRecord bgdata) {
int addr = ppur.get_ntread(); int addr = ppur.get_ntread();
bgdata.nt = ppubus_read(addr); bgdata.nt = ppubus_read(addr, true);
runppu(kFetchTime); runppu(kFetchTime);
addr = ppur.get_atread(); addr = ppur.get_atread();
byte at = ppubus_read(addr); byte at = ppubus_read(addr, true);
//modify at to get appropriate palette shift //modify at to get appropriate palette shift
if((ppur.vt&2)!=0) at >>= 4; if((ppur.vt&2)!=0) at >>= 4;
@ -53,10 +53,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo
runppu(1); runppu(1);
addr = ppur.get_ptread(bgdata.nt); addr = ppur.get_ptread(bgdata.nt);
bgdata.pt_0 = ppubus_read(addr); bgdata.pt_0 = ppubus_read(addr, true);
runppu(kFetchTime); runppu(kFetchTime);
addr |= 8; addr |= 8;
bgdata.pt_1 = ppubus_read(addr); bgdata.pt_1 = ppubus_read(addr, true);
runppu(kFetchTime); runppu(kFetchTime);
} }
@ -380,11 +380,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo
//pattern table fetches //pattern table fetches
int addr = patternAddress; int addr = patternAddress;
oam->patterns[0] = ppubus_read(addr); oam->patterns[0] = ppubus_read(addr, true);
if (realSprite) runppu(kFetchTime); if (realSprite) runppu(kFetchTime);
addr += 8; addr += 8;
oam->patterns[1] = ppubus_read(addr); oam->patterns[1] = ppubus_read(addr, true);
if (realSprite) runppu(kFetchTime); if (realSprite) runppu(kFetchTime);
//hflip //hflip
@ -405,7 +405,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo
//screen (or basically, the first nametable address that will be accessed when //screen (or basically, the first nametable address that will be accessed when
//the PPU is fetching background data on the next scanline). //the PPU is fetching background data on the next scanline).
//(not implemented yet) //(not implemented yet)
runppu(kFetchTime * 2);
if (sl == 0) if (sl == 0)
{ {
if (idleSynch && reg_2001.show_bg && !PAL) if (idleSynch && reg_2001.show_bg && !PAL)
@ -421,6 +420,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
for (int xt = 0; xt < 2; xt++) for (int xt = 0; xt < 2; xt++)
Read_bgdata(ref bgdata[xt]); Read_bgdata(ref bgdata[xt]);
runppu(kFetchTime * 2);
//After memory access 170, the PPU simply rests for 4 cycles (or the //After memory access 170, the PPU simply rests for 4 cycles (or the
//equivelant of half a memory access cycle) before repeating the whole //equivelant of half a memory access cycle) before repeating the whole
//pixel/scanline rendering process. If the scanline being rendered is the very //pixel/scanline rendering process. If the scanline being rendered is the very

View File

@ -63,8 +63,8 @@ namespace BizHawk.MultiClient
int nt = ppu.ppubus_peek(ntbyte_ptr + 0x2000); int nt = ppu.ppubus_peek(ntbyte_ptr + 0x2000);
int at = ppu.ppubus_peek(atbyte_ptr + 0x2000); int at = ppu.ppubus_peek(atbyte_ptr + 0x2000);
if((ty&1)!=0) at >>= 4; if((ty&2)!=0) at >>= 4;
if((tx&1)!=0) at >>= 2; if((tx&2)!=0) at >>= 2;
at &= 0x03; at &= 0x03;
at <<= 2; at <<= 2;
@ -74,6 +74,10 @@ namespace BizHawk.MultiClient
int pt_0 = ppu.ppubus_peek(pt_addr); int pt_0 = ppu.ppubus_peek(pt_addr);
int pt_1 = ppu.ppubus_peek(pt_addr + 8); int pt_1 = ppu.ppubus_peek(pt_addr + 8);
int pixel = ((pt_0 >> (7 - bgpx)) & 1) | (((pt_1 >> (7 - bgpx)) & 1) << 1); int pixel = ((pt_0 >> (7 - bgpx)) & 1) | (((pt_1 >> (7 - bgpx)) & 1) << 1);
//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 |= at;
pixel = ppu.PALRAM[pixel]; pixel = ppu.PALRAM[pixel];

View File

@ -115,15 +115,6 @@ namespace BizHawk.MultiClient
} }
PatternView.pattern.UnlockBits(bmpdata); PatternView.pattern.UnlockBits(bmpdata);
PatternView.Refresh(); 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) private void NESPPU_Load(object sender, EventArgs e)