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
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

3gengames wrote:Something like:

Code: Select all

NMI:
PHA
LDA $2002
INC Frame
PLA
RTI
You can make this a bit shorter though:

Code: Select all

NMI:
BIT $2002
INC Frame
RTI
Yes, this is a valid way to handle NMIs, and prevents mapper writes from colliding, but it does introduce problems in case of lag frames. If the game loop takes longer than a frame to complete, not only the gameplay will slow down, but also the music/sfx, and raster effects (such as status bars) will glitch up.
3gengames
Formerly 65024U
Posts: 2281
Joined: Sat Mar 27, 2010 12:57 pm

Post by 3gengames »

So basically put the Frame INC in there but do your music stuff at the end? Although that'd cut into vblank time....maybe that's not a good idea....Oh well, I'm sure you have a plan to fix it already if it's not yet.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

3gengames wrote:So basically put the Frame INC in there but do your music stuff at the end?
That kinda defeats the purpose, since his music code does some bankswitching.
User avatar
Bregalad
Posts: 8036
Joined: Fri Nov 12, 2004 2:49 pm
Location: Caen, France

Post by Bregalad »

OK to fix the problem without introduce this "fake NMIs" that would have the huge incovenient of making the music slows down if the game slows down (such as SMB, Zelda)...

1) At the begining of EVERY MMC1 writes (not only Reg3 wrties), disable NMIs via $2000, and re-enable them at the end. Yes it's a waste of cycles but it's vital if you don't want this part of code to be in-interuptable. If you ever use IRQs (unlikely on a MMC1 but who knowns) be sure to sei/cli this part of code too.

2) At the begining of every NMI, push the old bank switched in to the stack, and restore it by writing to Reg3 at the end.

This will work with no bugs and solve all your problems, with the inconvenient of potentially stealing a dozen of NMI cycles because of the non-interruptible work.
If your NMI timing is either very tight (you use 100% of the available time for VRAM updates) or must be precise (you use the NMI interrupt to sync with video like Battletoads) then you'll have to find some workarround :
1) Instead of disabling interrupts via $2000, simply increment a variable, and decrement it when the write is finished.

2) During all NMI MMC1 writes (not only Reg3 writes), be sure to reset the shift counter (write $80 to any reg) so that the next write is the first one, even if a previous write was interrupted !

3) Then at the end of NMI, if the variable isn't zero, re-do the complete write to the register by tricking the return address on the stack.
To handle this properly, you'd need up to 4 variables if the main code randomly access any of the 4 MMC1 regs, but in a typical case, you'd only do this for Reg3.

This method not only is a bit more complex than the previous one, but also it won't work if you decide to use 32kb bank switching or high 16kb switching. Writing $80 to a reg will put the MMC1 in 16kb low switching so if you use anything other than this, this method will not work. You'd need a second workaround to do it that way, but I don't feel like making one right now.
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.
OK I just took some throught about this, and I ended up with the conclusion that it's the exact same as MMC3. Look at the following piece of ultra-optimized code that bankswitch a 16k prg bank, the first part is MMC1, and the second does the exact same with the MMC3 :

Code: Select all

lda #$01
ldx #$00
stx Reg3
sta Reg3
sta Reg3
stx Reg3
stx Reg3

ldx #$06
ldy #$18
stx $8000
sty $8001
inx
iny
stx $8000
sty $8001
Both are exactly 24 cycles long.

Of course, if you want to switch a variable bank, and take into account what I said above, the MMC3 would probably end up a bit faster, but you'd still have to deal with $8000 being affected by an interrupt.
Useless, lumbering half-wits don't scare us.
User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless »

Bregalad wrote:This will work with no bugs and solve all your problems, with the inconvenient of potentially stealing a dozen of NMI cycles because of the non-interruptible work.
Unless there is other rogue code that is writing to the MMC1 registers, or if the hypotheses is false and the rogue bank switch is coming from some other source than an inconvenient NMI / IRQ.

That is a pretty strong statement "no bugs, will solve all problems". I hear that from vendors almost daily....

However, that approach to writing to the MMC1 register is a good idea, in general. It is coding defensively, and I endorse it.

But the blanket statement, I find, well, kind of short-sighted.
User avatar
Dwedit
Posts: 4470
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Post by Dwedit »

The difference of MMC3 is that you can have shadow versions of the bank switching registers, whenever you write to the real one, write to the shadow first.
Push the shadow versions onto the stack, and restore the shadow values before the IRQ finishes. Then you're in a guaranteed consistent state at any interruption, unlike with MMC1 where the count of writes matters.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

Bregalad wrote:OK I just took some throught about this, and I ended up with the conclusion that it's the exact same as MMC3. Look at the following piece of ultra-optimized code that bankswitch a 16k prg bank, the first part is MMC1, and the second does the exact same with the MMC3 :

Code: Select all

lda #$01
ldx #$00
stx Reg3
sta Reg3
sta Reg3
stx Reg3
stx Reg3

ldx #$06
ldy #$18
stx $8000
sty $8001
inx
iny
stx $8000
sty $8001
Both are exactly 24 cycles long.
On the MMC3, unless you're mapper-hacking, you probably want the two 8 KiB banks pointed at different places, switched separately instead of as a unit.

EDIT: removed bad example
Last edited by tepples on Thu Feb 24, 2011 6:25 am, edited 1 time in total.
frantik
Posts: 370
Joined: Tue Mar 03, 2009 3:56 pm

Post by frantik »

how about just implement a way to check that you're on the right bank before leaving the bank switch routine.. if you're not, do the routine again

kinda dirty but if it works, it works
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

And then end up in an endless loop because you keep writing five times in a row, but you keep writing bits 3, 4, 0, 1, 2 instead of 0, 1, 2, 3, 4. Unlike with $2002 reads resetting the $2005/$2006 latch, there isn't anything on the MMC1 that only resets the 5-write state without also resetting the PRG banking mode to fixed-$C000. Not that there's anything wrong with that if you're just using MMC1 as an alternative to adding a 7420 PRG RAM decoder to your UxROM.
User avatar
MottZilla
Posts: 2835
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

Or if you are wanting to use CHR-ROM, the other benefit to MMC1 over UxROM.
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

CHR ROM on SFROM/SJROM/SKROM/SLROM needs to be swapped 4 KiB at a time, which in my mind defeats the advantage of CHR ROM over CHR RAM. With CHR ROM on MMC1, you have to repeat the hero's sprite cels in each set of enemy sprite cels.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

MottZilla wrote:Or if you are wanting to use CHR-ROM, the other benefit to MMC1 over UxROM.
I don't see many reasons why anyone would want to use CHR-ROM that's swapable in 4KB chunks over CHR-RAM though. It's not like you'll be doing CHR animations, because that would require a lot of tiles (the non-animated ones) to be repeated.

The only reason I can think of is PRG-ROM space... If you use CHR-ROM there will be 256KB of PRG-ROM just for the program, while with CHR-RAM, under normal circumstances, you'd have to share the PRG-ROM space with the tiles.
User avatar
bigjt_2
Posts: 82
Joined: Wed Feb 10, 2010 4:00 pm
Location: Indianapolis, IN

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

Post by bigjt_2 »

Yep, found the problem. Clueless' first hypothesis was correct:
clueless wrote: 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.
My problem in bug-checking was that I was ONLY checking for this sound engine backup-bank/switch-bank routine I described in the first post. However, as I explained, many different sections of code utilize the simple PRG bank switching subroutine. The PRG bank always gets backed up before jumping to it and then restored later when the engine is ready to return to the other swappable banks. When I did a check for the actual bank-switch sub, on the immediate previous occurance this sub was called it was indeed cut up by my NMI. It only got through four writes to $E000 and then NMI happened.

Ugh... I thought I'd had my code timed better to keep everything condensed within one frame, but it appears I was wrong.

Now I first started thinking of trying to let the problem go away as I further optimized the code, but it seems like I'd better have one of the fixes you guys mentioned just in case the code does spill out of frame here and there, like some of the extra checks within the sub that Bregalad mentioned and so on. Like I said, I thought I had everything safely working within the frame with twenty-odd scanlines to spare, but I may be getting to the point on my project where it's too hard to gae-ron-tee that everything will fit within the frame 100% of the time.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

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

Post by tokumaru »

bigjt_2 wrote:Like I said, I thought I had everything safely working within the frame with twenty-odd scanlines to spare, but I may be getting to the point on my project where it's too hard to gae-ron-tee that everything will fit within the frame 100% of the time.
See? I've been told that I overcomplicate things when I suggest newbies use more robust NMI handlers, but it's almost a certainty that one day they will reach the CPU's limit, and then they'll be dealing with a very hard to track bug. One could argue that dealing with this problem is a part of the learning process, and I kinda agree, but there are cases when fixing this is very frustrating because it requires great changes in the game's architecture.
frantik
Posts: 370
Joined: Tue Mar 03, 2009 3:56 pm

Post by frantik »

tepples wrote:And then end up in an endless loop because you keep writing five times in a row, but you keep writing bits 3, 4, 0, 1, 2 instead of 0, 1, 2, 3, 4. Unlike with $2002 reads resetting the $2005/$2006 latch, there isn't anything on the MMC1 that only resets the 5-write state without also resetting the PRG banking mode to fixed-$C000.

ok, add a check to see if vblank fired during the routine (using a flag or frame counter) and if it did, reset the mapper too. if you have the mapper swapping routine in the fixed bank then you should be ok right?
Post Reply