MMC3 (again) -- and/or general IRQ timing issues?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
MMC3 (again) -- and/or general IRQ timing issues?
by on (#11615)
Yeah yeah... lots of topics about this troublesome mapper. Oddly the most common mapper, yet one of the hardest to emulate (I'd put it as the 2nd hardest -- right behind mapper 90)

I understand about the A12 rises and how the counter works and all that. My issue is with the timing.

I have Blargg's MMC3 tests passing, as well as the titlescreen of Kick Master and in-game Wolverine working glitch-free (have yet to test some other problem points in Kick Master -- but I'm confident they'll be fine). It's HOW I got them working that concerns me...

I understand that A12 rises on the 5th cycle of every 8 fetch cycles (4, 12, 20, 28, etc for BG fetches if BG uses $1xxx.... 260,268, etc for Sprite fetches when they use $1xxx)

I built my emu to clock the counter at these times, then ran the test ROMs. Blargg's gave me a "too early" error, so I pushed it back a few cycles (I figured it was due to my PPU/CPU sync method -- perhaps it would have to go THROUGH the cycle rather than up to the cycle or something like that -- making an off-by-1 error likely (and even expected)).

However I was concerned when I found out I had to push the time back THREE ppu cycles from the norm in order to get a passing result in blargg's test. And even more (9!) to get Wolverine and Kick Master working without jitters -- and even up to 11! (where I have it now) to clear some garbage that appears on the right-hand side of the screen in Wolverine. (blargg's tests seem to pass when I adjust 3-11 cycles inclusive, but fail outside of that)

There's no way my calculations could be 11 cycles off... that's too big of a gap to chalk it up to ppu/cpu/mapper sync issues. But then this made me think back to an issue I had with APU IRQs... and how when I tripped an IRQ exactly when the frame IRQ flag raised, I got errors. I ended up solving the problem by adding a delay of 2 CPU cycles between when the IRQ flag raised and when the IRQ actually happened. Currently I only do that for APU Frame IRQs... but is that the case for EVERY IRQ? That might explain the 11 cycle offset -- 2 CPU cycles would eat at least 6 cycles of that. But that still leaves 5 ppu cycles of "adjust" time in my MMC3 code (could it be a 3 CPU cycle delay?)

I'm going to mess around with this some more and will probably report back with more crap. This whole thing of delays and stuff is a pain... why can't IRQs just happen immediately ;_;




While I'm on the subject -- anyone have any good recommendations for MMC3 problem games which make good test ROMs? My list is basically:

- Mega Man 3 (it has BG and sprites both use the $1xxx pattern table for the pause-status bar -- Pausing in the first half of Gemini Man's stage will glitch if you don't handle that properly)

- Crystalis (scanline -1 checking -- otherwise the screen shifts when you talk to people and there's that garbage line as you scroll on the map)

- Kick Master, Wolverine (both just generally very picky about timing -- Kick Master also does weird things and will deadlock if IRQs trip when they shouldn't (end of stage 2))


I'd really like some games which use a backwards setup (BG uses $1xxx, sprites use $0xxx) or other weird behavior like that so I can test. If anyone can recommend some like that or just some they have trouble with I'd be grateful.



EDIT-

Added the 2 cpu cycle delay between IRQ flag raise and actual IRQ tripping. Still need to adjust my MMC3 code by 3 ppu cycles (which is what I expected -- but that still seems like too much... 1 cycle if any, should be enough). If I up the delay to 3 CPU cycles, then my Frame IRQs come in too late according to blargg's tests.

Could MMC3 IRQs somehow be slower to register than APU IRQs? Is it possible there's a 3 CPU cycle gap for MMC3 but only a 2 cycle gap for APU Frame IRQs?

gah my brain!


EDIT AGAIN -

Bah it just dawned on me that 3 PPU cycles = 1 CPU cycle -- and it being off by 3 PPU cycles is explained by the PPU/CPU sync issue (it has to go THROUGH the cycle rather than just UP TO the cycle). So I guess I answered all of my questions then XD. Although I'd still like test ROM recommendataions -- plus this post might help someone else *shrug*

^^;
Re: MMC3 (again) -- and/or general IRQ timing issues?
by on (#11616)
copy/paste from my source code:

I'm not sure if IRQ handling is 100% correct. Problem games:
- Armadillo: statusbar shakes in-game
- Days of Thunder: screen shakes when entering/exiting pits. garbage scanline on the game over textbox (not in PAL version)
- Juuouki: statusbar shakes in-game
- Mickey's Safari in Letterland: bottom half of statusbar shakes in-game
- Super Mario Bros 3 (J): white line above the 'checkerboard' shouldn't be visible on the Famicom (different MMC3 revision?), should be on NES though
- Ys III: statusbar shakes at the first conversation (pending IRQs are involved)

by on (#11617)
Huh, I seem to share all those problems =/ (couldn't test armadillo though -- just mapper 4 so far, didn't get to 118 yet)

Nintendulator shares them as well (at least the games I checked -- except it seemed to run Armadillo just fine).

Ys III was TOTALLY borked in both my emu and Nintendulator. Weird fadeouts at weird times, the first conversation wasn't even visible (only the sprites and the status bar were). Is my ROM bad or what?

by on (#11618)
YS3 is mapper 118, 'mine' does the same thing I described in Nintendulator, it's from GoodNES, crc32 of the whole file: $e1739a6a

Some other games from the top of my head I used to have problems with:
- Time Zone: status bar shaking on the 3rd level, needed precise MMC3 interrupt timing
- Downtown Special - Kunio-kun no Jidaigeki Dayo Zenin Shuugou!: my MMC3 interrupt implementation used to be off by 1 PPU cycle, that caused this game's status screen to shake sometimes

And another one I think everyone knows about: SMB3 checkerboard shaking when the title scrolls down, MMC3 counter gets clocked twice in one scanline.

by on (#11629)
...

by on (#11639)
Just some random comments, not necessarily helpful.

Disch wrote:
If I up the delay to 3 CPU cycles, then my Frame IRQs come in too late according to blargg's tests.


Remember that the timing has to be in relation to something, so a failing timing tests means that one of the two events being timed is off. Looking at my MMC3 timing test, the timing reference point is based on my PPU synchronization routine. That relies the timing of bit 7 when reading $2002 and the PPU's frame length when all rendering is disabled. But I'm assuming you have the PPU VBL/NMI test ROMs passing, so those should rule out any error for this timing reference point.

Quote:
Could MMC3 IRQs somehow be slower to register than APU IRQs? Is it possible there's a 3 CPU cycle gap for MMC3 but only a 2 cycle gap for APU Frame IRQs?


Wouldn't surprise me. I think you're pushing past what is known. I hope that some day we know exactly what happens in these cases, and why.

by on (#62252)
!BUMP! I was going to make a new post for my MMC3 IRQ question, but I figured why not be efficient and keep everything all in one place! :-P

So as I understand it from Disch's MMC3 doc...the IRQ reload request register ($C001-$DFFF, odd) doesn't _directly_ force the IRQ counter to reload itself from the IRQ latch value. What it really does is actually cause the IRQ counter to be *cleared* to 0, which _then_ causes the IRQ counter to be reloaded at the end of the next scanline since that's what naturally occurs when the IRQ counter is 0.

So really the IRQ reload command register causes an *indirect* reload of the IRQ counter register. Is that correct?

Another question I have is: does this manual reload cause an IRQ to occur (since what the reload command really does, as per the above, is clear the counter to 0 and cause a reload which would normally cause an interrupt)? In this case, let's assume the reload latch value is non-zero - to make things simpler.

UPDATE: I think reading Blargg's doc and the Wiki have cleared this up. Specifically, the Wiki says:
Quote:
Writing to $C001 will cause the counter to be reloaded on the NEXT rising edge of PPU A12...

So that tells me that my interpretation of Disch's doc was correct in that the "reload counter" register is really a "clear counter" register. In my opinion this should really be fixed on the Wiki cause it is technically incorrect. I'd be happy to change it myself if someone replies to this post and agrees with me. ;)

And Blargg's doc says:
Quote:
The IRQ flag is not set when the counter is cleared by writing to $C001.

And the above tells me that the answer to my second question is NO. Doing a manually clear of the IRQ counter does _not_ cause an interrupt.

Haha...put enough of these docs together and you can draw some pretty precise conclusions. ;)

Pz!

Jonathon :)

by on (#62260)
I'm doubtful that $C001 changes the IRQ counter at all. It probably works more like how the APU hardware works -- it sets a "reset" flag, then when the IRQ counter is clocked, it checks the state of that reset flag before decrementing the counter.

This especially makes sense when you look at RAMBO-1's "reset with X+1" quirk. RAMBO-1 probably has the same reset flag, and merely checks for zero before the decrement rather than after it.

/me adds to list of things to change in his docs

by on (#62262)
Okay, just so that I'm 110% sure that I am understanding things correctly...say that we have the following scenario:

- The IRQ counter is at a value of 1.
- At the end of the next scanline the count will be decremented to 0 and an interrupt to the CPU will occur.

HOWEVER, _before_ the end of the next scanline the user writes to $C001, thereby requesting that the IRQ counter be cleared.

Now, at the end of the next scanline the IRQ controller sees the "Clear Request" flag has been asserted. So the IRQ counter is cleared to 0, _but_ _no_ interrupt is asserted.

Then, at the end of the next scanline the IRQ counter is reloaded with the latch value since the IRQ count value was cleared to 0 by the user in the previous scanline.

Is that a proper sequence of events?

Thanks!

Jonathon

by on (#62264)
Quote:
HOWEVER, _before_ the end of the next scanline the user writes to $C001, thereby requesting that the IRQ counter be cleared.


It's more like it's requesting it be reset, not cleared.

The whole "cleared" thing is almost like a hackish way to make it reset because it will reset when it's at zero.

Quote:
Now, at the end of the next scanline the IRQ controller sees the "Clear Request" flag has been asserted. So the IRQ counter is cleared to 0, _but_ _no_ interrupt is asserted.


It wouldn't be cleared to zero. It would be reset to whatever was last written to $C000.

You only set it to zero if you're not doing the "reset flag" thing. If you do both, you're adding a scanline delay to the counter.

Other than that detail, I think you have it.

Basically it's like this:

On $C000 write:
- set 'LATCH' to value written

On $C001 write:
- set "reset counter" flag

On IRQ clock (every scanline)
- if "reset counter" flag is set:
-- clear the "reset counter" flag
-- set IRQ counter to LATCH
-- do not generate an IRQ

- otherwise ("reset counter" flag was not set)
-- decrement IRQ counter (wrap 0 -> LATCH)
-- if IRQ counter is zero after decrementing, and if IRQs are enabled, generate an IRQ.
EXCELLENT!!
by on (#62265)
AWESOME! Now I'm certain I've got it figured out! =D

May I make one suggestion though? :) I would change the "clear" term that you use in your mapper 4 MMC3 doc to "reset". It was really confusing me - when I think "clear" I think clear vs. set (as in bits) - so "clear" would mean value goes to 0. But that's not really what happens. The counter is really "reset" to the last value written to $C000. Now that I understand this all three docs (yours, Blargg's, and the Wiki's) match up! This is just my opinion...but I really think it would help. ;)

It would also be awesome if you added this "reset flag" method. That really helped me as well.

Haha, I would even add the last part of your post to the doc as well - straightened me out good!

THANKS FOR ALL YOUR HELP DISCH!!! :-D

Oh...and if you do make any of those changes can you send me your new 004.txt file?? Pretty please? :) You can send it to "webmaster" at my website's domain name.

by on (#62266)
Verilog would probably look something like so:

Code:
wire irq;

assign irq = irq_en & ~|counter;
assign reset_count = reset_latch | ~|counter;

// reset_latch should be set on C001 writes, cleared on irq_check
always @(posedge clk)
   if (irq_check)
      if (reset_count)
        counter <= latch;
      else
        counter <= counter - 1'b1;

by on (#62267)
ReaperSMS wrote:
Verilog would probably look something like so:

Agreed! Now that I understand how $C001 works that is. lol

by on (#62290)
Should interrupts be enabled to the CPU upon system reset? Or does the software first have to write to $E001 to enable them? If they are enabled at reset then in my case an MMC3 interrupt would occur immediately upon reset since my IRQ counter is initialized to 0 at reset.

by on (#62293)
Upon reset, IRQ, or NMI, the 6502 CPU turns on the IRQ ignore flag in the processor status register, as if it had executed the SEI instruction. It is the responsibility of the program to set up all registers of the mapper before turning off this flag (with the CLI instruction).

by on (#62295)
That's right. Okay, so that tells me that I should have the interrupt output of the mapper enabled upon reset. And even though the IRQ line will be asserted from the MMC3, the CPU is not going to recognize it until the user calls the CLI instruction. Makes sense. Thanks!

by on (#62301)
Okay, so that fixed it. I am now passing _all_ of Blargg's MMC3 IRQ test ROMs. But unfortunately, when I say all I really mean _all_ of them - including _both_ Rev A and Rev B tests (#5 and #6). I don't think that's supposed to happen - in fact, I didn't even think it was possible. Well...more debugging. If anyone has any ideas let me know.

But hey, I'm passing all of Blargg's tests! Progress!! :)

Pz!

Jonathon

by on (#62306)
Well this stinks. Now that I have _enabled_ the IRQ output by default Super Mario Bros 2, which used to work, is no longer working. And I can see that the game keeps jumping to FFFE-FFFF over and over again. I don't get. I didn't think that SMB2 even used the MMC3 interrupt at all. But apparently it does, otherwise why would they clear the interrupt flag (i.e. CLI)?

So if I make my MMC3 disable interrupts at system reset then I won't pass Blargg's tests but SMB2 will work.

If I make my MMC3 enable interrupts at system reset then Blargg's tests pass, but SMB2 no longer works.

Wha??

Any help?

Thanks!

Jonathon

by on (#62316)
jwdonal wrote:
I didn't think that SMB2 even used the MMC3 interrupt at all. But apparently it does, otherwise why would they clear the interrupt flag (i.e. CLI)?

I forget: Does SMB2 use DMC IRQ?

by on (#62317)
Don't recall if it uses DMC interrupts, but it definitely does not use MMC3 interrupts.

jwdonal - are you triggering interrupts only if they're enabled (i.e. by writes to $e001)?

by on (#62322)
There's no way SMB2 uses DMC interrupts, and I'm very doubtful it uses MMC3 interrupts.

(at least I doubt it uses MMC3 interrupts during the main game. It might use them for some subscreen or something)

by on (#62323)
SMB2 was originally MMC1 with CHR-RAM before it became MMC3 with CHR-ROM.

by on (#62333)
Dwedit wrote:
SMB2 was originally MMC1


Interesting -- what's the story there? Was this just in development or something? I'm unaware of any non-MMC3 version of SMB2.

by on (#62334)
James wrote:
Dwedit wrote:
SMB2 was originally MMC1

Interesting -- what's the story there? Was this just in development or something? I'm unaware of any non-MMC3 version of SMB2.

Dwedit is trying to say that early prototypes of SMB2 were SNROM, just like the other popular FDS ports (Metroid, Kid Icarus, The Legend of Zelda). It was mapper hacked to MMC3 before release, with new features such as animated background tiles and an expanded ending.

by on (#62345)
cool page -- thanks.