It is currently Sun Oct 22, 2017 7:48 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 9 posts ] 
Author Message
PostPosted: Sun Jun 11, 2017 11:59 am 
Offline
User avatar

Joined: Sun Nov 09, 2008 9:18 pm
Posts: 984
Location: Pennsylvania, USA
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:
    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
    :



Top
 Profile  
 
PostPosted: Sun Jun 11, 2017 12:11 pm 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 2963
Location: Tampere, Finland
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:
; 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: kkfos.aspekt.fi


Top
 Profile  
 
PostPosted: Sun Jun 11, 2017 12:14 pm 
Offline
User avatar

Joined: Sun Nov 09, 2008 9:18 pm
Posts: 984
Location: Pennsylvania, USA
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:
; 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.


Top
 Profile  
 
PostPosted: Sun Jun 11, 2017 1:46 pm 
Online

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19116
Location: NE Indiana, USA (NTSC)
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).


Top
 Profile  
 
PostPosted: Sun Jun 11, 2017 1:48 pm 
Offline
User avatar

Joined: Sun Nov 09, 2008 9:18 pm
Posts: 984
Location: Pennsylvania, USA
*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:

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



and my nmi does this:
Code:

  ;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?


Top
 Profile  
 
PostPosted: Sun Jun 11, 2017 1:51 pm 
Online

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19116
Location: NE Indiana, USA (NTSC)
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.


Top
 Profile  
 
PostPosted: Sun Jun 11, 2017 8:15 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1390
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:
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.


Top
 Profile  
 
PostPosted: Sun Jun 11, 2017 8:18 pm 
Online

Joined: Mon Nov 10, 2008 3:09 pm
Posts: 430
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:

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



and my nmi does this:
Code:

  ;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:
  ;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:
lda whatever
sta shadowbanksel
sta $8000


and at the end of your NMI handler do this:

Code:
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.


Top
 Profile  
 
PostPosted: Wed Jun 14, 2017 6:51 am 
Offline
User avatar

Joined: Sun Nov 09, 2008 9:18 pm
Posts: 984
Location: Pennsylvania, USA
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.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 9 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 9 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