Help me: MMC3 IRQs

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Help me: MMC3 IRQs
by on (#83572)
It's not clear to me about A12 (for a lack of a better label, I mean PPU address bit 1000h) rises. Can the IRQ counter be clocked twice in a scanline, for example? Could someone clarify when A12 should rise in a frame?

Power Blade 2 has a major problem here: the status bat "follows" a certain sprite, so it keeps going up, down... :(

In short words, could someone clarify the A12 timing during a frame?

by on (#83573)
Here's how it works as I understand it:

If background patterns are at $0000 and sprite patterns are at $1000, PA12 will rise eight times, at x=260, 268, ..., 316. It is believed that the MMC3's scanline counter ignores rising edges closer than x dots apart (conjectured to be about 12), so the counter is clocked only once. But if 8x16 pixel sprites are used, PA12 will skip a rising edge if the least significant bit of attribute 1 (the tile number) is false because patterns are read from $0000-$0FFF instead of $1000-$1FFF. This creates a delay of 16 dots between rising edges, which is greater than x and therefore clocks the counter again.

by on (#84512)
I guess instead of making my own thread about this, I'll simply post my question here. I am almost done with MMC3, all of blargg's test pass (except revision A, because I haven't tried yet ;)). Yet in at least 3 games there are some issues.

- Mega Man 5
- Boss 'intro' screen shakes on the bottom half of the bar (The part with their name on it).

- Mega Man 6
- Knight Man's stage, the floor shakes at the part with the spikes that move down on top of you
- Centaur Man's stage, the floor shakes at the trippy part where the water is suspended above you

- Rad Racer 2
- There are sometimes small glitches on the left side of the road

I can't figure out where this is coming from, the PPU passes all the VBL Timing (Doesn't pass sprite overflow timing tests which /may/ be connected, but highly unlikely since A12 only depends on fetches made), and the MMC3 IRQ Timing tests. So whence cometh the shake? It's making me want to pull out my hair, because aside from these small bugs, every game that uses MMC3 looks perfect.. Here is my code for clocking the IRQ Counter, which also may be wrong:

Code:
        private void PPU_AddressLineUpdating(int addr)
        {
            oldA12 = newA12;
            newA12 = addr & 0x1000;

            if (oldA12 < newA12)
            {
                if (timer > 16)
                {
                    if (irqCounter == 0 || irqReload)
                    {
                        irqReload = false;
                        irqCounter = irqRefresh;
                    }
                    else
                    {
                        irqCounter--;
                    }

                    if (irqCounter == 0 && irqEnabled)
                    {
                        cpu.IrqRequest = true;
                    }
                }

                timer = 0;
            }
        }


the variable names should be obvious, but just in-case they aren't:

- timer: this is increased along with each dot on the PPU
- irqCounter: the value that is decreased on each rising edge of A12
- irqRefresh: the value written into $C000
- irqReload: set on writes to $C001
- irqEnabled: set on writes to $E001, cleared on writes to $E000

CPU Timing passes all test, PPU timing passes all relevant tests, MMC3 passes all tests. So yeah, I don't get why I'm having /ANY/ problems, but I am. Any suggestions?

by on (#84513)
I'll assume that "timer" ticks up every cycle no matter what...
So we have a check if A12 went to zero, checks if it had already happened within 16 cycles, then happens IMMEDIATELY otherwise. So far so good.
But I'm a tad concerned about the cpu.IrqRequest flag. There are at least 3 IRQ sources (APU Frame IRQ, DMC IRQ, and Mapper IRQs), how are you telling them apart?

The Mapper IRQ flag stays on until the game clears it with a mapper write. The CPU has its own interrupt disabling bit. The interrupt triggers whenever the CPU hasn't disabled interrupts and while the mapper requests it. They can retrigger if the game never clears the interrupt flag on the mapper.

Are you passing the CLI latency test? Should have a lag of 1 instruction between CLI/PLP/RTI and the interrupt happening.

A dumb mistake that I recently fixed was that PLP/RTI instructions weren't checking for interrupts afterwards properly (weren't checking at all in my case). Other dumb mistakes I've made resulted in Frame IRQs becoming enabled after loading state, so it looks like infinite MMC3 IRQ triggering as the game draws its status bar 100 times, but it's really frame IRQs staying on because the game isn't trying to clear them.


Other stuff:
You time a PPU write at the END of the last CPU cycle. So if you are at pixel #240 when the write starts, you are 12 pixels later at #252 by the time the write finishes (on NTSC). Y scroll change happens at pixel #251. So you get shaky stuff if your PPU pixel is sometimes before #240 before the second $2006 write, and sometimes after pixel #240.

I just checked Megaman 5 in FCEUX. It completes the second $2006 write within a range of pixel #275-285, well past the shaky range.

by on (#84514)
I tried getting the CLI latency test to pass, but all my 'fixes' started looking really hack-ish so I decided to wait, do you really think that could be the problem here? How do you handle it?

I guess there should be a way to tell different interrupts apart from the other, it always seemed weird to me to use one flag, but it's worked just fine until now. The DMC constantly asserts the IrqRequest flag until it's acknowledged, but the FrameIrq and mapper irq's don't. So I guess this is a problem :oops:

MMC3 has got to be one of the biggest pains in my rump to implement, all other mappers so far took between 15-30 minutes. This has taken 2-3 days already.

EDIT: I should also point out that I have a cycle accurate PPU, that does all the appropriate fetches and passes timing tests including the "odd frame" shortening test, sprite 0 hit tests, and vbl timing tests.

by on (#84515)
Try something like this:

Code:
        private void PPU_AddressLineUpdating(int addr)
        {
            //oldA12 = newA12;
            //newA12 = addr & 0x1000;

            //if (oldA12 < newA12)
            if ((oldA12 ^ addr) & 0x1000 &&  //if A12 has changed
             (addr & 0x1000) &&              //and A12 is now high
             timer > 16)                     //and it was previously low long enough
            {
                //if (timer > 16)
                //{
                    int oldIrqCounter = irqCounter;
                    if (irqCounter == 0 || irqReload)
                    {
                        irqReload = false;
                        irqCounter = irqRefresh;
                    }
                    else
                    {
                        irqCounter--;
                    }
                    //this isn't 100% correct, but works for most games
                    //see http://wiki.nesdev.com/w/index.php/MMC3#Variants
                    if (oldIrqCounter > 0 && irqCounter == 0 && irqEnabled)
                    {
                        cpu.IrqRequest = true;
                    }
             }
                oldA12 = addr;
                //timer = 0;
            //}
        }


Timer should indicate how long A12 has been low, so wherever you increment timer, use logic similar to the following:

Code:
if (!(current_address & 0x1000))
 timer += 1; //or whatever is appropriate
else
 timer = 0;


My emulator doesn't differentiate between different IRQ sources. Technically, it's wrong, but I haven't found this to cause problems with any games.

by on (#84529)
Thanks for the suggestions James, I implemented them exactly as you said, and I still get shaking in Mega Man 5 and 6, and the artifacts in Rad Racer 2..

This is beginning to look more and more like a timing issue in the PPU to me. I ran a few more NMI and VBL tests and some didn't pass, so I think this might be one of the (if not the only) causes.

EDIT: Here are 2 images, one showing the glitched scanline in mega man 3 working fine, and another showing an artifact in rad racer 2

Mega Man 3
Rad Racer 2

by on (#84531)
Why don't you write down what cycle range the scroll writes are happening at, and compare with Nintendulator or FCEUX?

by on (#84532)
By Scroll writes, I assume you mean $2006?

by on (#84533)
Try logging writes to any PPU port during lines 0-239.

by on (#84535)
Code:
Mine:

 86
 98 - 12 diff
131 - 33 diff
143 - 12 diff
176 - 33 diff
188 - 12 diff
221 - 33 diff
233 - 12 diff
266 - 33 diff
278 - 12 diff
311 - 33 diff
323 - 12 diff
 15 - 33 diff
 27 - 12 diff

Nintendulator:

23.333  ~70
27.333  ~82 - 12 diff
38.333 ~115 - 33 diff
42.333 ~127 - 12 diff
53.333 ~160 - 33 diff
57.333 ~172 - 12 diff
68.333 ~205 - 33 diff
72.333 ~217 - 12 diff
83.333 ~250 - 33 diff
87.333 ~262 - 12 diff
98.333 ~295 - 33 diff


I have the same timing patterns that nintendulator produces on writes to $2006, mine are just ahead by about 16 dots.

EDIT: Just logged all registers as tepples suggested. There are $2006 writes occuring during VActive and before HBlank..

Could this be that I am emulating per cycle, but not doing cycle catch up when registers are written?

Code:
var cycles = cpu.Execute();

while (cycles-- != 0) {
apu.Execute();

for (int i = 0; i < 3; i++)
    ppu.Execute();
}

by on (#84537)
You should at least be queuing the register writes with timestamps, to keep the timing consistent.

by on (#84540)
If I change to the cycle catch up method, how would I handle NMI timing? This was always the biggest worry I had.

I THINK you would have to use a timestamp, and when NMI's are enabled in $2000, calculate the time that NMI would fire, and if they are disabled, cancel that time out? Is that a proper NMI logic for that method?

by on (#84541)
More or less. You also need to catch up whenever the CPU reads from the PPU, or waits on sprite 0 hits will break. It gets more complicated when you try and factor PPU sensitive scanline IRQ's in, such as the MMC3 you're having issues with.

How were you planning on getting accurate IRQ firing in the first place?

by on (#84542)
Catch-up: It's more than a hamburger topping.

by on (#84544)
@James, I wasn't really planning on getting my emulator to be this accurate, but now that I really need to to get MMC3 IRQ and other things working properly, It's time for a structural change!

@tepples, rofl. That's a good start for a Wiki entry, but it doesn't go into much detail about how the NMI should be timed, etc etc.

Example: Say that NMI is enabled in $2000.7, how would I calculate when it's fired? Do I differentiate based on if $2002.7 is high? Or should I differentiate based on current scanline value (>240)?

What about APU Frame IRQ's? What about jittering?

These are the kinds of things that run through my mind when I am considering changing to a catch-up system.

by on (#84547)
beannaich wrote:
Say that NMI is enabled in $2000.7, how would I calculate when it's fired?

Start of line 241.

Quote:
What about APU Frame IRQ's?

Other articles on the wiki describe when things happen on an NES, such as APU Frame Counter. Fire them when they happen.

by on (#84563)
beannaich wrote:
@James, I wasn't really planning on getting my emulator to be this accurate, but now that I really need to to get MMC3 IRQ and other things working properly, It's time for a structural change!

I hear ya. I initially wrote my emulator in 2003 just to see if I could do it. I had no plans of it becoming a long-term project. Two weeks ago, I was making changes to fix glitches in Mickey's Adventures in Numberland. In between, I've made several big changes as my understanding of how things work evolved; I'm sure I'll do that again. But hey, that's what makes this fun for me.

by on (#84569)
tepples wrote:
Start of line 241.


I was under the assumption that $2000.7 could be toggled to pull /NMI low multiple times per VBlank period? If not, that makes things much easier :lol:

James wrote:
But hey, that's what makes this fun for me.


Agreed, I wouldn't be doing any of this if it weren't fun!

by on (#84572)
beannaich wrote:
tepples wrote:
Start of line 241.

I was under the assumption that $2000.7 could be toggled to pull /NMI low multiple times per VBlank period?

It can. If the current scanline is 241 through 260, and $2002 has not been read since the start of 241, and $2000.7 goes from 0 to 1, you should trigger an NMI, just as you'd trigger an IRQ if the next opcode is $00 (BRK #ii).