Famitone skips ahead

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

JoeGtake2
Posts: 333
Joined: Tue Jul 01, 2014 4:02 pm

Famitone skips ahead

Post by JoeGtake2 »

So, this is a subtle thing but it's driving me crazy. I know without much more, it'll likely be hard to answer, but curious if anyone has run into this before.

I am using FamiTone, and have my FamiTone update at the end of the NMI. Ironically, on my screen transitions, it seems to 'skip forward' a beat...not lag behind, but actually, when the screen loads, it is a beat or so ahead.

Anyone ever run into this?

[Subject Fairy was here]
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Famitone woes?

Post by lidnariq »

Only guess: NMI can be triggered multiple times for each vblank if you don't read from $2002 during your NMI handler and your NMI handler finishes in less than 20 scanlines you toggle $2000.7 anywhere before the NMI flag is cleared by hardware.
Last edited by lidnariq on Sat Nov 07, 2015 8:18 pm, edited 1 time in total.
JoeGtake2
Posts: 333
Joined: Tue Jul 01, 2014 4:02 pm

Re: Famitone woes?

Post by JoeGtake2 »

Ha! Now I'm glad I asked. Added a read of $2002 during the NMI and it seems to have fixed the problem completely!

That was easy. Good instinct! haha
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Famitone skips ahead

Post by GradualGames »

Fascinating---I'd never heard of this issue. I do not read $2002 in any of my vblank routines and I have yet to run into this issue (assuming it is not unique to FamiTone, I do not use it), nor had I spotted it being discussed anywhere here or on the wiki. Can anyone go into more detail on this? Makes me concerned I'm avoiding possible bugs by coincidence :)
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Famitone skips ahead

Post by rainwarrior »

Writing to $2000 operates mask on the NMI flag (via bit 7).

The only way to clear the NMI flag is by reading $2002, or waiting for the end of vblank to occur.

If you write a 0 to bit 7 of $2000, the NMI flag is masked, outputting a 0 instead of its current state.

If you write a 1 to bit 7 of $2000, the NMI flag is unmasked, outputting its current state, which can be 0 or 1.

The NMI interrupt is triggered whenever the NMI signal goes from 0 to 1, so "manually" unmasking it during vblank causes a new NMI interrupt to trigger.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Famitone skips ahead

Post by lidnariq »

You can find the exact logic that rainwarrior talks about in Visual2C02:

/NMI externally is NOT node _int (via three inverters)
node _int is node 1014 NOR node 5731
node 1014 is NOT vbl_flag
node 5731 is NOT enable_nmi

(By deMorgan's laws, that's equivalent to: /NMI is vbl_flag NAND enable_nmi )
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Famitone skips ahead

Post by GradualGames »

In my game right now, at the end of vblank, I set the vram address with two writes to $2006 and then set the scroll with two writes to $2005. I don't ever read $2002. I also use $2006 sometimes when loading graphics with bg and sprites turned off (I use mapper 2 and chr-ram). When I do so---I always change my vblank routine to a "no-op" and wait for vblank to end (*edit* wait for a SHORT vblank to return BEFORE the end of the vblank period, forgive me for not being in the habit of precise language) before doing a lot of writes to VRAM. So I'm wondering, because I've been careful in every other way, have I got it set up so that $2006 is always writing to the correct hi/lo register whenever I use it? I noticed on the wiki reading $2002 says it resets the latch that determines which lo or hi byte of $2006 gets written to, guaranteeing you're starting over again. While I spent as little as time as possible studying the hardware carefully while building my first two games, I'm now genuinely interested in absorbing more details like this. I just wish I understood why I haven't run into any issues with this since it appears that reading $2002 is important to do during nmi.
Last edited by GradualGames on Mon Nov 09, 2015 7:31 am, edited 1 time in total.
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Famitone skips ahead

Post by GradualGames »

Maybe this is why. The wiki (Disch's nmi article in particular) says: "Once you turn NMIs off, it is very easy to forget to turn them back on. What's worse, if you forget to read $2002 and you turn them on in the middle of VBlank, it will immediately trigger an NMI and cause your handler to run past the end of VBlank, starting all sorts of havoc."

I never turn NMI off...could this be why I haven't run into this issue? Joe do you ever turn nmi off? I don't think it's wrong to do one or the other, but it looks like the lesson here is if you ever turn it off, you need to be reading $2002 in your nmi?
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Famitone skips ahead

Post by GradualGames »

rainwarrior wrote:Writing to $2000 operates mask on the NMI flag (via bit 7).

The only way to clear the NMI flag is by reading $2002, or waiting for the end of vblank to occur.

If you write a 0 to bit 7 of $2000, the NMI flag is masked, outputting a 0 instead of its current state.

If you write a 1 to bit 7 of $2000, the NMI flag is unmasked, outputting its current state, which can be 0 or 1.

The NMI interrupt is triggered whenever the NMI signal goes from 0 to 1, so "manually" unmasking it during vblank causes a new NMI interrupt to trigger.
Are you saying precisely what I just posted above, here?
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Famitone skips ahead

Post by tokumaru »

I just leave NMIs on the whole time.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Famitone skips ahead

Post by thefox »

GradualGames wrote:Are you saying precisely what I just posted above, here?
Yes, if you don't turn NMIs off, you won't have this problem.

And I've said this before, but if you don't turn your NMIs off and are doing some kind of a bulk transfers in the "main thread" (i.e. uploading a lot of CHR data, bypassing the vblank buffers), you also have to make sure not to blindly do a $2002 read in the NMI handler, because it could happen that the NMI starts at the exact moment when you're trying to do the PPU address writes in the main thread, messing up the even/odd latch and your transfer. It's very unlikely to happen, but when it happens, you might rip out a hair or two.
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: Famitone skips ahead

Post by GradualGames »

thefox wrote:
GradualGames wrote:Are you saying precisely what I just posted above, here?
Yes, if you don't turn NMIs off, you won't have this problem.

And I've said this before, but if you don't turn your NMIs off and are doing some kind of a bulk transfers in the "main thread" (i.e. uploading a lot of CHR data, bypassing the vblank buffers), you also have to make sure not to blindly do a $2002 read in the NMI handler, because it could happen that the NMI starts at the exact moment when you're trying to do the PPU address writes in the main thread, messing up the even/odd latch and your transfer. It's very unlikely to happen, but when it happens, you might rip out a hair or two.
In addition to (the possibility of) an nmi happening between writes to $2006 (during the main thread, with graphics off, about to upload chr data...), I also have an indirect address for the currently active vblank routine---which I swap out during the main thread when changing game state. For any situations like this where you have a 2 byte address whose writes could be interrupted by nmi, is it better to use a third byte to act as a mutex in these situations? In general, I'm being careful to wait til the start of a new frame before doing expensive operations, but I suppose it is conceivable that some of these run long enough that another nmi could occur. Given how consistent the timing and behavior is on the NES I'm not sure in practice if it is worth it to go to this length. On the other hand, I protect all my bankswitching from being messed up by nmi (I do a bankswitch for the music engine at the end of nmi) by saving the bank I'mabout to switch to before actually switching, so if nmi interrupts, it can restore the correct bank. Prior to implementing that I DID observe some crashes. Interesting that I haven't run into crashes in these other situations. Probably because in the bankswitch case---you're doing this all the time with a huge variety of execution times during gameplay. But in the game state change case---you've got much less volatile operations going on, so these issues are a lot less likely.
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Famitone skips ahead

Post by rainwarrior »

I also think leaving the NMI on permanently is a good strategy. When you need to do full screen updates, you can still turn rendering off and just use a variable to tell your NMI handler not to touch the PPU while you're doing it. The big advantage of this is you can still play music seamlessly during the transition.

Here's a simplified example of how mine is structured:

Code: Select all

nmi_handler:
	; prevent re-entry from an NMI while still in the NMI handler
	lda nmi_lock
	beq :+
		rts
	:
	lda #1
	sta nmi_lock
	; update the ppu only on request from the main thread
	lda ppu_update_ready
	beq ppu_update_done
	; ...
	; do all PPU interaction here
	; OAM DMA
	; Nametable/palette upload
	; set scroll, etc.
	lda #0
	sta ppu_update_ready
ppu_update_done:
	jsr music_play
	; unlock
	lda #0
	sta nmi_lock
	rts

nmi:
	pha
	txa
	pha
	tya
	pha
	jsr nmi_handler
	pla
	tay
	pla
	tax
	pla
	rti
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Famitone skips ahead

Post by GradualGames »

Thanks for this, rainwarrior. I've just implemented this approach in my new project. I feel like it was the last missing piece of the puzzle to feel like I was doing all the basics truly correctly. My approach prior to this was to always wait til vblank flagged that it was done, swap out for a "no-op" nmi routine (I tend to always have one indirect address in zp for the currently active nmi routine), then proceed to modify the ppu (load chr data while graphics are turned off). It works just fine, but, you have to be more careful with that approach. This way, it's like you have a mutex for the ppu. Much nicer. It even works for swapping out an indirect address for the currently active nmi routine.
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Famitone skips ahead

Post by rainwarrior »

The other thing I forgot to mention is that any write to ppu_update_ready in that example should wait for the NMI thread to empty it before proceeding, presuming that all your PPU update data is single-buffered.

Code: Select all

finish_frame:
    lda #1
    sta ppu_update_ready
    :
    lda ppu_update_ready
    bne :-
    rts
Post Reply