Failing Blargg's MMC3 IRQ Tests

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Failing Blargg's MMC3 IRQ Tests
by on (#239816)
I'm trying to implement the MMC3 IRQ, but I'm failing the first of blargg's tests called clocking. I get the error "Should reload when clocked when counter is 0", but from my code I'm definitely reloading the counter. Looking at the source of the test, I'm presuming that it's a timing issue, but I've looked at the wiki and other forum posts, and I'm not sure where I'm going wrong. Am I missing something?

This code runs every time CHR is being read:
Code:
if(this->prevA12 == 0 && (addr >> 15)) {
    if(this->CPUcycleCount - this->prevCPUCycleCount > 2) {
        if(this->IRQReload) {
            this->IRQReload = false;
            this->IRQCounter = this->IRQLatch;
        } else {
            this->IRQCounter--;
            if(this->IRQCounter == 0) {
                this->IRQCounter = this->IRQLatch;
                if(this->IRQEnabled) this->IRQCalled = true;
            }
        }
    }
    this->prevCPUCycleCount = this->CPUcycleCount;
}
this->prevA12 = addr >> 15;
Re: Failing Blargg's MMC3 IRQ Tests
by on (#239822)
I suspect the problem is that if IRQCounter is set to 0 before entering that block of code, it will become negative (or underflow to a very large positive integer).

For reference, here's what my MMC3 IRQ code looks like:
Code:
void    MAPINT  PPUCycle (int Addr, int Scanline, int Cycle, int IsRendering)
{
        if (IRQaddr)
                IRQaddr--;
        if ((!IRQaddr) && (Addr & 0x1000))
        {
                unsigned char count = IRQcounter;
                if (!IRQcounter || IRQreload)
                        IRQcounter = IRQlatch;
                else    IRQcounter--;
                if ((count || IRQreload) && !IRQcounter && IRQenabled)
                        EMU->SetIRQ(0);
                IRQreload = 0;
        }
        if (Addr & 0x1000)
                IRQaddr = 8;
}

where writing to $C000 updates IRQlatch, $C001 clears IRQcounter and sets IRQreload, $E000 clears IRQenabled, and $E001 sets IRQenabled.
Last I checked, this code passes test #6 (MMC3_alt), not #5 (MMC3) - it's impossible to pass both of them at the same time, because they're designed for different chip revisions.
Re: Failing Blargg's MMC3 IRQ Tests
by on (#239837)
I put an assert to ensure that IRQCounter is always nonzero before decrementing it, but it never caused an exception, so I'm pretty sure that that's not the cause.

I tried to implement my code the same way you have yours, and it still stopped at the same error, but I noticed that your IRQ checking code was in the PPUCycle function. Does that mean every ppu cycle does a read somewhere in CHR? Currently, I only run that code on the second write to $2006, a read or write to $2007, BG and sprite tile fetches.

Or is there another reason that you think that this error might occur?
Re: Failing Blargg's MMC3 IRQ Tests
by on (#239839)
ace314159 wrote:
I'm trying to implement the MMC3 IRQ, but I'm failing the first of blargg's tests called clocking. I get the error "Should reload when clocked when counter is 0", but from my code I'm definitely reloading the counter. Looking at the source of the test, I'm presuming that it's a timing issue, but I've looked at the wiki and other forum posts, and I'm not sure where I'm going wrong. Am I missing something?

This code runs every time CHR is being read:
Code:
if(this->prevA12 == 0 && (addr >> 15))
...
this->prevA12 = addr >> 15;


The above two lines look awfully suspicious to me.

As for the question of whether accesses other that tile accesses are relevant, the real hardware counts multiple transitions on A12 as a single action unless A12 sits low continuously for awhile. No particular effort is made to calibrate the circuit that decides whether A12 has been low long enough to separate discrete actions; instead, software will ensure that the time between events will always be very short or very long. Non-contrived software that would work on all real variations of real hardware would likely be tolerant of a few variations in how scan lines are detected.
Re: Failing Blargg's MMC3 IRQ Tests
by on (#239845)
You're right! Those lines are wrong. I got confused and thought 0x1000 had a bit set in the 15th bit instead of the 12th bit, and so I shifted right by 15 to see if that bit was set. As a hack, I manually shifted the address from $2006 left 3 to make it work. I'm surprised the previous tests passed with this incorrect behavior. Thank you for realizing that critical flaw, but I'm still getting that same error. :(

You mentioned that there isn't any definite calibration to ensure that A12 has been low long enough, but I've looked at the forums and Quietust's code and saw that they waited for at least 8 PPU cycles to pass. Is that just an approximation?

The reason I asked for if accesses other than the tiles and $2006 and $2007 are relevant is because I was thinking that maybe I wasn't clocking the IRQ counter enough. Are you aware of any other reasons for this error?

Thank you for the help!
Re: Failing Blargg's MMC3 IRQ Tests
by on (#239846)
I figured it out! It turns out I wasn't supposed to reset the counter after writing to $E000. I guess I should've guessed that since the wiki says that the counter is unaffected, but the doc located at http://nesdev.com/mmc3.txt, had code showing that the counter was reset after writign to $E000. I'm assuming that doc is outdated?

However, in order for the code to work, I had to disable any existing IRQs that were called after a write to $E000. Is this supposed to occur or is something else wrong and this is just a hack to make it work?
Re: Failing Blargg's MMC3 IRQ Tests
by on (#239847)
ace314159 wrote:
I figured it out! It turns out I wasn't supposed to reset the counter after writing to $E000. I guess I should've guessed that since the wiki says that the counter is unaffected, but the doc located at http://nesdev.com/mmc3.txt, had code showing that the counter was reset after writign to $E000. I'm assuming that doc is outdated?

Goroh's reverse-engineering efforts and documentation is from 22 years ago, while details of operation and newer findings/analysis are all in the wiki page; you should use the latter. Historic documents like Goroh's and many others are kept around for fair/legitimate reasons (read: they should not be deleted/removed, as the authors have not requested such). You will find conflicting information everywhere as you travel through documentations spanning the past 22+ years.