Page 1 of 3

Is there anything that could alter an MMC1 reg3 write?

Posted: Tue Feb 22, 2011 10:11 pm
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.

Posted: Tue Feb 22, 2011 10:20 pm
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).

Posted: Tue Feb 22, 2011 10:51 pm
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?

Posted: Tue Feb 22, 2011 11:19 pm
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?

Posted: Tue Feb 22, 2011 11:24 pm
by Dwedit
Don't feel too bad, even Capcom screwed up interrupted MMC1 bankswitching in the Mega Man games.

Posted: Tue Feb 22, 2011 11:34 pm
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.

Posted: Wed Feb 23, 2011 12:50 am
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

Posted: Wed Feb 23, 2011 7:04 am
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.

Posted: Wed Feb 23, 2011 8:02 am
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.

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

Posted: Wed Feb 23, 2011 8:40 am
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.

Posted: Wed Feb 23, 2011 11:09 am
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.

Posted: Wed Feb 23, 2011 11:18 am
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.

Posted: Wed Feb 23, 2011 11:18 am
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?

Posted: Wed Feb 23, 2011 12:11 pm
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.

Posted: Wed Feb 23, 2011 1:23 pm
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.