It is currently Tue Oct 16, 2018 7:36 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 23 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Thu Aug 09, 2018 11:44 am 
Offline

Joined: Wed Apr 04, 2018 7:29 pm
Posts: 37
Location: Montreal, Canada
Hi.

Hypothetical scenario. Let's say I do this a bank switch:

Code:
lda xxx
sta $8000
; NMI triggers here
lda yyy
sta $8001


And the NMI triggers between the 8000/8001. Lets say the NMI also does bank switches for audio and other things. Lets also assume the NMI is being nice and pushing/popping all registers.

What will happen when the code resumes and does that 8001 store?
Will it do anything, will it be ignored?

I read that it is good practice to first store the bank you want to switch to to a variable, so you can restore all banks at the end of NMI to prevent these kind of issues. Does it apply to this exact scenario?

Thanks.

-Mat


Top
 Profile  
 
PostPosted: Thu Aug 09, 2018 11:50 am 
Offline
User avatar

Joined: Fri Nov 19, 2004 7:35 pm
Posts: 4093
It depends entirely on what your NMI code does. If your NMI code does any writes to 8000, the write to 8001 from your main code will affect the bank that NMI was messing with rather than what you intended for your code to do.

So what you do is use a Shadow variable for the 8000 register. Write to the shadow variable, then to the actual 8000. Then your NMI can set 8000 back to the value from the shadow variable, thus preventing problems.

_________________
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!


Top
 Profile  
 
PostPosted: Thu Aug 09, 2018 11:53 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6875
Location: Canada
One possible suggestion might be that you might only ever need to swap one PRG bank outside of NMI/IRQ, so you could just always write to $8000 to re-select the bank before returning to the main thread. (CHR banking in NMI/IRQ only, $C000 banking only in NMI for music maybe.)


Top
 Profile  
 
PostPosted: Thu Aug 09, 2018 11:55 am 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 3132
Location: Tampere, Finland
Yeah from the point of view of MMC3 it doesn't know that an NMI happened, it's just interpreting the writes sequentially.

This situation is fairly easy to deal with when it comes to MMC3. Some other mappers like MMC1 are more annoying (if an interrupt can happen in the middle of the 5-bit write sequence, you have to detect that situation and resume from the beginning).

_________________
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi


Top
 Profile  
 
PostPosted: Thu Aug 09, 2018 12:02 pm 
Offline

Joined: Wed Apr 04, 2018 7:29 pm
Posts: 37
Location: Montreal, Canada
Oh thank you guys. I get it now.

For some reason I always though bank swap always required a pair of writes (8000 + 8001) but that's not the case. The value of the 8000 persists until it is modified, so that's the only value that you need to shadow (and restore at the end of NMI) to prevent that case from being a problem. Easy.

Thanks again.

-Mat


Top
 Profile  
 
PostPosted: Thu Aug 09, 2018 12:27 pm 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1705
As a general tip for situations where the bank switch is a hassle of multiple writes that you absolutely don't want to be interrupted since you cannot buffer it (like in MMC1 where you have five writes to the same register one after another, so each unexpected write to it would totally screw you up):

Use a global variable that you initialize to 0 at the start of the program and then you do this (the code refers to your example, not to MMC1):
Code:
; Bank write preparation.
LDX xxx
LDY yyy

; Flag is set.
INC BankIsBeingWritten

; Bank writes are as close together as possible.
STX $8000
STY $8001

; Flag is reset.
DEC BankIsBeingWriten


Now, the NMI would have three distinct situations to react to:

1. Gameplay logic has finished:
The NMI does all the normal stuff.

2. Gameplay logic is in the middle of execution:
The NMI still does the music update, but not the graphics.

3. The BankIsBeingWritten flag is not set to 0:
The NMI does no writes whatsoever to any NES register. (Except for saving and restoring the A, X and Y register of course.) It immediately leaves again.

This means, in the very, very unlikely case where the NMI starts in the middle of a bank switch, you have a different situation than during a regular lag. In this rare situation, the music will also lag for one frame, but you can be sure that nothing will screw up your bank switch setup.


Also, you might have noticed that I changed your bank writes a bit.
Instead of
LDA xxx, STA $8000, LDA yyy, STA $8001
I used
LDX xxx, LDY yyy, STX $8000, STY $8001.

This way, the moments from the start of the first bank to the end of the last bank write are even shorter and it's more unlikely that NMI hits here.
If the NMI hits somewhere between LDX and LDY, you don't need to care since there haven't been any actual bank writes yet.
So the room for a problematic interruption is exactly between only two commands.
(Might not be important for MMC3, I don't know. But it's a general hint: If the writes cannot be interrupted, put them as close together as possible, so that NMI situation 3 is minimized.)

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Last edited by DRW on Thu Aug 09, 2018 12:31 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Thu Aug 09, 2018 12:28 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 20656
Location: NE Indiana, USA (NTSC)
The Curse of Possum Hollow has two shadow registers: $8000 bank and $A000 bank. They are always set through one subroutine that copies them to registers 6 and 7, called mmc3_restore_bank, in this order:

1. Write $06 to $8000
2. Write bank number for $8000 to $8001, changing bank at window $8000
3. Write $07 to $8000
4. Write bank number for $A000 to $8001, changing bank at window $A000

The NMI and IRQ handlers call this subroutine before they finish. So even if a call to mmc3_restore_bank happens during a call to mmc3_restore_bank, the following three sequences are possible. All produce the correct result:

1. Write $06 to $8000
2. Write bank number for $8000 to $8001, changing bank at window $8000
3. Write $07 to $8000
4. Write bank number for $A000 to $8001, changing bank at window $A000
[Return]
2. Write bank number for $8000 to $8001, changing bank at window $A000 (wrong bank but will be corrected immediately)
3. Write $07 to $8000
4. Write bank number for $A000 to $8001, changing bank at window $A000

1. Write $06 to $8000
2. Write bank number for $8000 to $8001, changing bank at window $8000
3. Write $07 to $8000
4. Write bank number for $A000 to $8001, changing bank at window $A000
[Return]
3. Write $07 to $8000
4. Write bank number for $A000 to $8001, changing bank at window $A000

1. Write $06 to $8000
2. Write bank number for $8000 to $8001, changing bank at window $8000
3. Write $07 to $8000
4. Write bank number for $A000 to $8001, changing bank at window $A000
[Return]
4. Write bank number for $A000 to $8001, changing bank at window $A000

The only times registers 0-5 get written are in an interrupt (NMI or IRQ) and during bulk CHR RAM loading (during which IRQ is off and most NMI processing is skipped).

To make reentrancy easier, the NMI handler does not call the audio driver. Instead, the main loop checks a few times to see how far the audio has fallen behind and repeatedly calls the audio driver until it's up to date.


Top
 Profile  
 
PostPosted: Thu Aug 09, 2018 12:48 pm 
Offline
User avatar

Joined: Thu Sep 15, 2016 6:29 am
Posts: 773
Location: Denmark (PAL)
I'm curious about scenarios where it makes sense to bank switch outside of NMI, but also outside of timed code (ie. on a specific scanline, NMI guaranteed to not happen).
I'm guessing it would be related to a special case requiring you to read data from another bank? but I can't imagine a scenario where I really need to do that while rendering is turned on.


Top
 Profile  
 
PostPosted: Thu Aug 09, 2018 1:13 pm 
Offline

Joined: Wed Apr 04, 2018 7:29 pm
Posts: 37
Location: Montreal, Canada
tepples wrote:
The Curse of Possum Hollow has two shadow registers: $8000 bank and $A000 bank. They are always set through one subroutine that copies them to registers 6 and 7, called mmc3_restore_bank, in this order:


Oh thanks. I didn't consider the scanline IRQ could also interfere.

Wouldnt simply shadowing the 8000 reg work?

Would something like that work? (Not shown: irq/nmi push/pop all registers and stuff). I cant help but feel like there is a flaw. Its too simple.

Code:
main:
   lda ...
   sta shadow8000
   sta 8000
   ; ===> IRQ OR NMI FIRES HERE!
   lda ...
   sta 8001

irq:
   lda shadow8000
   sta shadow8000_copy
   
   lda ...
   sta shadow8000
   sta 8000
   ; ===> NMI FIRES HERE!
   lda ...
   sta 8001
   
   lda shadow8000_copy
   sta shadow8000
   sta 8000
   
   rti

nmi:

   lda ...
   sta 8000
   lda ...
   sta 8001
   
   lda shadow8000
   sta 8000
   
   rti


-Mat


Top
 Profile  
 
PostPosted: Thu Aug 09, 2018 1:29 pm 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 3132
Location: Tampere, Finland
bleubleu wrote:
Oh thanks. I didn't consider the scanline IRQ could also interfere.

NMI (or another IRQ) could technically interrupt your IRQ handler, but how often do you really have an IRQ at the bottom of the screen? So it depends.

If you want to make it really foolproof, push shadow8000 on stack and restore as you'd CPU registers. Then you're only limited by the stack size.

_________________
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi


Top
 Profile  
 
PostPosted: Thu Aug 09, 2018 9:47 pm 
Offline
Formerly ~J-@D!~
User avatar

Joined: Sun Mar 12, 2006 12:36 am
Posts: 472
Location: Rive nord de Montréal
What I like about the FME-7 over the other mappers (like MMC3) is that its simple protocol for its functions lends to very simple and fool-proof code when dealing with its registers:
Code:
;; calling with A: mapper register value, X: mapper register address
    stx mapper_reg  ; shadow register
    stx $8000
    sta $A000

Moreover, you can as easily remember the PRG banks, should you need to change/restore them in NMI:
Code:
switch_prgbank:
    sta mapper_prgbank_save - 8, x
    ; fallthrough to this function (optimized tail-call)
mapper_write:
    stx mapper_reg
    stx $8000
    sta $A000


Here, mapper_prgbank_save is an array big enough for the 4 PRG bank registers, and we assume that this function is called only when switching PRG banks (where X=8..B) outside of NMI. The NMI just have to restore the PRG banks and write mapper_reg to $8000 at its end.

_________________
((λ (x) (x x)) (λ (x) (x x)))


Top
 Profile  
 
PostPosted: Thu Aug 09, 2018 10:36 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10892
Location: Rio de Janeiro - Brazil
Just thought of another way to detect a botched PRG bankswitch that doesn't require flags: it's common practice to store the number of the bank at a fixed location in each bank, and if that's the case, you can just compare the desired bank number against that location after the switch is supposedly done, and repeat the process if there's no match.

There are better solutions for the MMC3, as has already been explained, but maybe this could work well for another mapper.


Top
 Profile  
 
PostPosted: Fri Aug 10, 2018 12:02 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1705
tokumaru wrote:
Just thought of another way to detect a botched PRG bankswitch that doesn't require flags: it's common practice to store the number of the bank at a fixed location in each bank, and if that's the case, you can just compare the desired bank number against that location after the switch is supposedly done, and repeat the process if there's no match.

But how would you do this, for example, in MMC1?

MMC1 requires five writes to the same register. How would you detect whether the NMI hit in the middle of these writes by simply comparing the bank?

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Fri Aug 10, 2018 12:04 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6875
Location: Canada
It's not a technique for MMC1, and probably not MMC3 either.

It's useful for e.g. UxROM or AxROM that have more atomic state, if you need to bankswitch in an IRQ or NMI you can inspect the bank number to know what to put back afterward.

Edit: tokumaru was suggesting a diffferent technique than I thought: verify and retry. See below.


Last edited by rainwarrior on Fri Aug 10, 2018 4:06 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Fri Aug 10, 2018 12:31 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10892
Location: Rio de Janeiro - Brazil
No, no, I actually meant for any mapper with non-atomic switching. Something like this:

Code:
  lda #04 ;try to select bank 4
SelectBank:
  ;[MAPPER-SPECIFIC CODE TO SELECT BANK GOES HERE]
  cmp BankNumber ;see if the desired bank number matches what the ROM says is mapped
  bne SelectBank ;keep trying until you get a match...

MMC1 would require trashing the accumulator though, so you'd probably have to copy the bank number to X or Y for the comparison.

Nothing groundbreaking or particularly innovative, it's just that sometimes we already have identifiers in every bank (like in the example rainwarrior mentioned), and also the bank number in a register, so this comparison can be cheaper than using flags and/or shadow registers.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 23 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: Google [Bot] and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group