Mysterious mmc3 chr-rom bankswitching glitch.

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

Post Reply
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Mysterious mmc3 chr-rom bankswitching glitch.

Post by GradualGames »

I'm currently using MMC3 for my game. Accounting for dropped frames I discovered I needed to make chr bankswapping atomic with sprite updates, as in the following. That part works fine.

What's really mysterious is that I'm now getting glitches with background chr-data. I've verified in the debugger my two variables bg_chr_bank0 and bg_chr_bank1 *are not modified* when this glitch occurs.

I'm having trouble imagining what else could be wrong, here. I know MMC3 is not subject to bus conflicts.

This is from my current nmi routine. All chr-rom banks are updated every frame based on what was calculated in the previous frame. I currently do not protect the bg chr updates the way I protect the spr chr updates, however, I verified that these values are not changing, yet the glitches appear in the bg tiles and nowhere else (they select the wrong tiles).

Code: Select all

    lda #MMC3_SELECT_2K_CHR_BANK0
    sta MMC3_BANK_SELECT
    lda bg_chr_bank0
    sta MMC3_BANK_DATA

    lda #MMC3_SELECT_2K_CHR_BANK1
    sta MMC3_BANK_SELECT
    lda bg_chr_bank1
    sta MMC3_BANK_DATA

    lda sprites_ready
    beq :+
    lda #MMC3_SELECT_1K_CHR_BANK0
    sta MMC3_BANK_SELECT
    lda spr_chr_bank0
    sta MMC3_BANK_DATA

    lda #MMC3_SELECT_1K_CHR_BANK1
    sta MMC3_BANK_SELECT
    lda spr_chr_bank1
    sta MMC3_BANK_DATA

    lda #MMC3_SELECT_1K_CHR_BANK2
    sta MMC3_BANK_SELECT
    lda spr_chr_bank2
    sta MMC3_BANK_DATA

    lda #MMC3_SELECT_1K_CHR_BANK3
    sta MMC3_BANK_SELECT
    lda spr_chr_bank3
    sta MMC3_BANK_DATA

    jsr sprite_update_all
    lda #0
    sta sprites_ready
    :

User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Mysterious mmc3 chr-rom bankswitching glitch.

Post by thefox »

GradualGames wrote:What's really mysterious is that I'm now getting glitches with background chr-data. I've verified in the debugger my two variables bg_chr_bank0 and bg_chr_bank1 *are not modified* when this glitch occurs.
Does your "main thread" perform MMC3 writes? If so, something like this could happen:

Code: Select all

; main thread:
    lda #MMC3_SELECT_PRG_BANK0 ; or whatever
    sta MMC3_BANK_SELECT
    ; NMI happens here. When code resumes, MMC3_BANK_SELECT == MMC3_SELECT_2K_CHR_BANK0.
    lda #blah
    sta MMC3_BANK_DATA
    ...

; NMI:
    lda #MMC3_SELECT_2K_CHR_BANK0
    sta MMC3_BANK_SELECT ; MMC3_BANK_SELECT is changed.
    lda bg_chr_bank0
    sta MMC3_BANK_DATA
    ...
Long story short, you need to ensure atomicity of the BANK_SELECT+BANK_DATA writes, if you haven't done so already.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Mysterious mmc3 chr-rom bankswitching glitch.

Post by GradualGames »

thefox wrote:
GradualGames wrote:What's really mysterious is that I'm now getting glitches with background chr-data. I've verified in the debugger my two variables bg_chr_bank0 and bg_chr_bank1 *are not modified* when this glitch occurs.
Does your "main thread" perform MMC3 writes? If so, something like this could happen:

Code: Select all

; main thread:
    lda #MMC3_SELECT_PRG_BANK0 ; or whatever
    sta MMC3_BANK_SELECT
    ; NMI happens here. When code resumes, MMC3_BANK_SELECT == MMC3_SELECT_2K_CHR_BANK0.
    lda #blah
    sta MMC3_BANK_DATA
    ...

; NMI:
    lda #MMC3_SELECT_2K_CHR_BANK0
    sta MMC3_BANK_SELECT ; MMC3_BANK_SELECT is changed.
    lda bg_chr_bank0
    sta MMC3_BANK_DATA
    ...
Long story short, you need to ensure atomicity of the BANK_SELECT+BANK_DATA writes, if you haven't done so already.
Ah! You're absolutely right, it's probably a race condition between the prg bankswapping and the chr bankswapping. Thanks!! Don't know why this didn't occur to me yet.
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Mysterious mmc3 chr-rom bankswitching glitch.

Post by tepples »

One way to prevent races is to set a policy that only the IRQ and NMI handlers ever change any window register other than 7 (PRG bank in CPU $A000-$BFFF), and they must select window register 7 before returning. This may be inconvenient if your main thread switches $8000-$9FFF and $A000-$BFFF independently. But it's convenient if your main thread switches only $A000-$BFFF, such as if you're using fixed $8000-$9FFF and $E000-$FFFF (P bit set in register select) and switching DPCM in window 6 (CPU $C000-$DFFF). Thus only the NMI handler and PIT IRQ handler change windows 0-5, and only the audio engine (called by the NMI handler) ever changes window 6 ($C000).
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Mysterious mmc3 chr-rom bankswitching glitch.

Post by GradualGames »

*edit* whoops, didn't realize tepples just posted.

Hmm, I'm confused by something. Why doesn't writing to the bank select register just reset mmc3's internal state? In other words, say my main thread does this:

Code: Select all


  ;write to bank select (for PRG)
  ; <<NMI INTERRUPTS HERE
  ;write to bank data

and my nmi does this:

Code: Select all


  ;write to bank select (for CHR)
  ;write to bank data

So there are two writes to bank select in a row. I don't understand why that doesn't (always) work without implementing my own lock of some kind. Shouldn't writing to the select register more than once just reset the state and expect a data write right after?
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Mysterious mmc3 chr-rom bankswitching glitch.

Post by tepples »

The only "state" of the window select register ($8000) is A. which window (0-7) will be changed when a bank number is written to $8001, B. whether CPU $8000 and $C000 are swapped, and C. whether PPU $0000 and $1000 are swapped.

Writing $06 then $01 to $8000 then writing $02 then $03 to $8001 will not affect window 6. It will only switch window 1 to bank $02 then bank $03. There is no stack of window select states unless you implement one in software.
User avatar
Quietust
Posts: 1918
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Mysterious mmc3 chr-rom bankswitching glitch.

Post by Quietust »

If you want a "foolproof" solution, ensure that all $8000 writes get mirrored to a special variable (going to that variable first), ideally with a macro:

Code: Select all

LDA #slot
STA var_mmc3reg
STA $8000
LDA #bank
STA $8001
Then, copy that variable back to $8000 ("LDA var_mmc3reg" + "STA $8000") at the end of your interrupt handlers.

It'll increase your code size, but it'll save you from having to track down these sorts of problems.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
AWJ
Posts: 433
Joined: Mon Nov 10, 2008 3:09 pm

Re: Mysterious mmc3 chr-rom bankswitching glitch.

Post by AWJ »

GradualGames wrote:*edit* whoops, didn't realize tepples just posted.

Hmm, I'm confused by something. Why doesn't writing to the bank select register just reset mmc3's internal state? In other words, say my main thread does this:

Code: Select all


  ;write to bank select (for PRG)
  ; <<NMI INTERRUPTS HERE
  ;write to bank data

and my nmi does this:

Code: Select all


  ;write to bank select (for CHR)
  ;write to bank data

So there are two writes to bank select in a row. I don't understand why that doesn't (always) work without implementing my own lock of some kind. Shouldn't writing to the select register more than once just reset the state and expect a data write right after?
Here's how that sequence looks from the MMC3's point of view:

Code: Select all

  ;write to bank select (for PRG)
  ; <<NMI INTERRUPTS HERE (MMC3 doesn't know or care)
  ;write to bank select (for CHR)
  ;write to bank data (goes to CHR bank)
  ; <<RTI (MMC3 doesn't know or care)
  ;write to bank data (goes to CHR bank--oops!)
The effect is that the data that was meant to go to the PRG register ends up going to the CHR register instead, and both banks end up wrong.

One way to deal with this is to keep a shadow copy of "bank select from non-interrupt thread" in RAM, and have the interrupt thread restore it before RTI'ing. i.e. every time you bank switch from the non-interrupt thread, do this:

Code: Select all

lda whatever
sta shadowbanksel
sta $8000
and at the end of your NMI handler do this:

Code: Select all

lda shadowbanksel
sta $8000
rti
Note that you must write to the shadow register before writing to the real register. If you wonder why, think about what happens if the interrupt fires between the two writes.
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Mysterious mmc3 chr-rom bankswitching glitch.

Post by GradualGames »

Quietust, AWJ, thanks! Storing a shadow register of the bank select fixed the issue for me. Funny thing is I had been using the shadow register, store first technique with UnROM in the past, I am not sure why it wasn't obvious to me how to apply the same technique to mmc3.
Post Reply