Is there anything that could alter an MMC1 reg3 write?

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

User avatar
bigjt_2
Posts: 82
Joined: Wed Feb 10, 2010 4:00 pm
Location: Indianapolis, IN

Is there anything that could alter an MMC1 reg3 write?

Post by bigjt_2 »

...besides the obvious of doing five more writes to register 3?
Or for that matter, is there anything that could cause the MMC1 to ignore a reg3 ($E000-$FFFF) write? And hence ignore a PRG bank switch?

I've got this weird crash happening now in my code, caused by the wrong PRG bank being loaded at the wrong time.

I'm looking at my Nintendulator dump file that I captured leading up to the crash. Specifically within that file, I'm looking at the previous two runs of a certain subroutine before the game crashes. This sub backs up the current PRG bank number in a variable so that it can jump back to he current sub later, then loads 0 into the accumulator, then jumps to the PRG bank-switching sub.

On the second-to-last run before the game crashes, the code executes perfectly fine. I need it to load bank 0, so I load 0 into A and then jump to the switching sub to do my five writes to $E000/four LSRs. Then scrolling down, I look at the next time a pointer is loading from somewhere in ROM and it's loading the correct data, indicating bank 0 is indeed loaded.

Here's the odd part. The next time this same subroutine runs (and again, the last time it runs before the game crashes) I'm again loading in bank 0, and the swapping subroutine also runs perfectly. However, when I scroll down even further and look at the exact same address being read out of ROM via a pointer, I see two $FF values. So I've checked this address in the debugger and found that $FF is the value when bank 1 is loaded. Bank 1, coincidentally, was the previous PRG bank loaded both times before jumping to this sub. It's like it changed the first time, but ignored the writes to $E000 on the second time. About 1 1/2 scanlines later, the game crashes, of course.

My first thought was that I must be screwing up an offset in a write, that's causing some random value to get written to $E000-$FFFF. I've painstakingly looked at every store instruction after this bank swapping subroutine (the one that gets called right before the crash), and I can't find stores to anything besides RAM or non-MMC1 registers numbered $7FFF and lower.

I'm re-reading this now and realizing it must be confusing as hell to anyone else. Here's a snippet from my dump file that might explain it better, I've annotated with some labels and comments:

bank_backup_se:
FF8D LDA $004B = 01 ;;$004B = prg_bank ;;the current PRG bank
;;loaded
FF90 STA $004E = 01 ;;$004E = bank_bak_se ;;the variable that holds
;;the current PRG bank, as I will need to return
;;to it once this group of code is done
FF93 LDA #$00 ;;target bank = bank 0
FF95 STA $004B = 01
FF98 JSR $E82B

swap_PRG_bank:
E82B LDA $004B = 00
E82E AND #$1F
E830 STA $E000 = 40
E833 LSR A
E834 STA $E000 = 40
E837 LSR A
E838 STA $E000 = 40
E83B LSR A
E83C STA $E000 = 40
E83F LSR A
E840 STA $E000 = 40
E843 RTS
FF9B RTS

;;so pretty standard stuff. now... scrolling down to the next immediate
;;instance when something is read out of the current PRG bank...

FED6 LDA $A1AE,Y @ A1BA = B4
FED9 STA $0053 = 50
FEDC LDA $A1AF,Y @ A1BB = A2
FEDF STA $0054 = A3

;;On the next frame, bank_backup_se and swap_PRG_bank run exactly
;;the same. However, the next time I scroll down a couple dozen lines
;;to find the pointer mentioned above...

FED6 LDA $A1AE,Y @ A1BA = FF
FED9 STA $0053 = 50
FEDC LDA $A1AF,Y @ A1BB = FF
FEDF STA $0054 = A3

;;and it's now apparent that I've pissed off the nesdev/6502/MMC1 gods
;;and they are now giving me the big middle finger.
Last edited by bigjt_2 on Tue Feb 22, 2011 10:30 pm, edited 1 time in total.
User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless »

Could an IRQ or NMI be firing when you don't expect it, and the handler switching banks out from under you (and not restoring them when the handler exits)?

Are your IRQs and NMIs disabled when the MMC1 bank switch routine is actually running (or at least, timed to not occur then)?

In my MMC1 experiment, I store the bank's number at $bfff. My NMI handler saves that on the stack (lda, pha), and on NMI exit, it checks to see if the bank has changed, and if so, restores it. But I don't have code to prevent an NMI during the MMC1 switcher (I was no where near close to running over my CPU budget, so it wasn't a problem yet).
User avatar
bigjt_2
Posts: 82
Joined: Wed Feb 10, 2010 4:00 pm
Location: Indianapolis, IN

Post by bigjt_2 »

Hey clueless, thanks for the suggestions. Let's see...
clueless wrote:Could an IRQ or NMI be firing when you don't expect it, and the handler switching banks out from under you (and not restoring them when the handler exits)?
Actually, this code is part of the game's sound engine. Specifically, you might say it's the "driver" part (is that the correct term?) that runs every frame and runs through all the sound data and writes to he necessary sound registers. All this code is jumped to at the end of my NMI handler, after the xScroll writes to $2005 and right before I restore my Accumulator and X and Y registers.
clueless wrote: Are your IRQs and NMIs disabled when the MMC1 bank switch routine is actually running (or at least, timed to not occur then)?
NMI is definitely not off, as the block of code this runs in gets ran as the tail end of the NMI handler. So the run_sound stuff is basically the first thing ran every frame. No other IRQs are enabled.
clueless wrote: In my MMC1 experiment, I store the bank's number at $bfff. My NMI handler saves that on the stack (lda, pha), and on NMI exit, it checks to see if the bank has changed, and if so, restores it. But I don't have code to prevent an NMI during the MMC1 switcher (I was no where near close to running over my CPU budget, so it wasn't a problem yet).
I actually switch banks a few times or more every frame. I keep specific things in specific PRG banks. So for example, all sound data is in bank 0, the first four level's data/boss data is in bank 1, (actually 2 in NESASM, because everything is split into 8K banks), then all enemy code is in another 16K bank, etc. So what I do is, usually the level data is running, and then at a given time it may need to pull some enemy data. So the basic structure looks like this:

JSR level stuff
JSR more level stuff
JSR etc.

JSR backup_PRG_bank
JSR swap_PRG_bank ;usually this is included in the above sub, just separating it here for clarity.

JSR enemy stuff ;code in new bank is sandwitched between
JSR more enemy stuff ;these backup/restore subs
JSR etc. etc. etc.

JSR restore_PRG_bank
JSR swap_PRG_bank ;again, usually included in the restore sub above

JSR even more level stuff
JSR something else blah blah blah

Is that kind of the same thing you're talking about?
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

So this means that your sound engine, which is called at the end of your NMI handler, switches banks, right? Does it also back up the current bank number? Is it possible that it's overwriting the backup made in the game loop?
User avatar
Dwedit
Posts: 4470
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Post by Dwedit »

Don't feel too bad, even Capcom screwed up interrupted MMC1 bankswitching in the Mega Man games.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

This is the reason I absolutely hate the convoluted way MMC1 registers are written to... it's just too error-prone. I also hate how long it takes to complete one single lousy mapper write, because the overhead of bankswitching several times in the same frame becomes too significant.
User avatar
koitsu
Posts: 4203
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Post by koitsu »

tokumaru wrote:This is the reason I absolutely hate the convoluted way MMC1 registers are written to... it's just too error-prone. I also hate how long it takes to complete one single lousy mapper write, because the overhead of bankswitching several times in the same frame becomes too significant.
Yup, and is exactly why for years I've wanted an MMC3 devcart. Zero interest in this MMC1 crap. ;D
User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless »

bigjt_2 wrote:Is that kind of the same thing you're talking about?
Yes, I think. My hypothesises were as follows:
  1. Part way through changing the MMC1 bank via the 'lsr, sta' unrolled loop, your code gets interrupted, and the interrupt handler resets the latch, changes banks, does its work, then resets the bank back to what it found on entry. Your "main" thread then resumes banging out the final bits tot he MMC1 bank register, which is no longer properly latched, so the MMC1 sees an incomplete switch request.
  2. An interrupt handler swaps banks and simply never switches back, independent of the first bug proposal
But in your post (I think) you ruled though out, and I'm out of ideas atm.
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

For hypothesis 1, would it be worth it to try checking, from the NMI handler, whether the interrupted program counter on the stack is within $FF00-$FFFF? Then you could place all your low-level MMC1 handling code there.
User avatar
thefox
Posts: 3139
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Is there anything that could alter an MMC1 reg3 write?

Post by thefox »

bigjt_2 wrote:I'm looking at my Nintendulator dump file that I captured leading up to the crash.
Set a breakpoint at some point which leads to the crash and single step through the mapping code. The debugger shows you what PRG banks are currently mapped in. Unfortunately it doesn't display mapper state though.

If the code gets interrupted that would also show up in the CPU logs.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
Bregalad
Posts: 8036
Joined: Fri Nov 12, 2004 2:49 pm
Location: Caen, France

Post by Bregalad »

Yup, and is exactly why for years I've wanted an MMC3 devcart. Zero interest in this MMC1 crap. ;D
What about the crappy limitations of MMC3's line counter ?
And also to switch a 16k bank in MMC3 it needs no less than 4 writes, only one less than the 5 MMC1 writes... probably not a major save of time if you want my opinion.

Of course the MMC5 fixes all these problems.
Useless, lumbering half-wits don't scare us.
User avatar
MottZilla
Posts: 2835
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

clueless wrote:
bigjt_2 wrote:Is that kind of the same thing you're talking about?
Yes, I think. My hypothesises were as follows:
  1. Part way through changing the MMC1 bank via the 'lsr, sta' unrolled loop, your code gets interrupted, and the interrupt handler resets the latch, changes banks, does its work, then resets the bank back to what it found on entry. Your "main" thread then resumes banging out the final bits tot he MMC1 bank register, which is no longer properly latched, so the MMC1 sees an incomplete switch request.
  2. An interrupt handler swaps banks and simply never switches back, independent of the first bug proposal
But in your post (I think) you ruled though out, and I'm out of ideas atm.
The interrupted register write (cause it takes so damn long) seems like a possible cause. I agree that the slow nature of the MMC1 register writes is annoying enough to deter developers from using it and instead wanting to stick to discrete mappers like UxROM. It would be nice though to have something like a mix of UxROM, CNROM, and H/V mirroring control. That or as mentioned a MMC3 development board.
User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless »

Guys, he's using MMC1. Hopefully his cart will get fabricated. That is not going to happen with MMC3 or MMC5.

bigjt, does this bug manifest itself fairly quickly from boot-up, or after the game has been playing for a while?

Would a CPU instruction trace (w/ perl or python post-processing) help figure anything out?
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

MottZilla wrote:I agree that the slow nature of the MMC1 register writes is annoying enough to deter developers from using it and instead wanting to stick to discrete mappers like UxROM.
The big reason to switch from UxROM or AxROM to SNROM is PRG RAM support, especially if you want to be able to save more than 32 bits of state from one play session to the next. To do so on UxROM would involve adding a 74HC20, which decodes $6000-$7FFF and prevents bus conflicts on the ROM.
3gengames
Formerly 65024U
Posts: 2281
Joined: Sat Mar 27, 2010 12:57 pm

Post by 3gengames »

Maybe this would be a good time to replace your NMI like I had an idea to earlier, which I asked someone earlier if it was a good idea and this is maybe what could fix it.


Something like:

Code: Select all

NMI:
PHA
LDA $2002
INC Frame
PLA
RTI
That way nothing gets messed with in the code, but a bad thing about this is that it'd pretty much go to 30FPS when alot of stuff is happening. Maybe this idea will help you fix it, hope it does. Plus it allows for different NMI's depending on the games kernal instead of using RAM to point to one. Can't wait to see the progress, good luck.
Post Reply