Famitone skips ahead
Moderator: Moderators
Famitone skips ahead
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]
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]
Re: Famitone woes?
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.
Re: Famitone woes?
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
That was easy. Good instinct! haha
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
Re: Famitone skips ahead
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
- rainwarrior
- Posts: 8734
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: Famitone skips ahead
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.
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.
Re: Famitone skips ahead
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 )
/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 )
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
Re: Famitone skips ahead
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.
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
Re: Famitone skips ahead
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?
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?
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
Re: Famitone skips ahead
Are you saying precisely what I just posted above, here?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.
Re: Famitone skips ahead
I just leave NMIs on the whole time.
Re: Famitone skips ahead
Yes, if you don't turn NMIs off, you won't have this problem.GradualGames wrote:Are you saying precisely what I just posted above, here?
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
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
Re: Famitone skips ahead
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.thefox wrote:Yes, if you don't turn NMIs off, you won't have this problem.GradualGames wrote:Are you saying precisely what I just posted above, here?
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.
- rainwarrior
- Posts: 8734
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: Famitone skips ahead
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:
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
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
Re: Famitone skips ahead
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.
- rainwarrior
- Posts: 8734
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: Famitone skips ahead
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