No NMI if main code reads PPUSTATUS ($2002) in a loop

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
VEG
Posts: 53
Joined: Mon Nov 11, 2013 2:55 pm
Location: Minsk, Belarus

No NMI if main code reads PPUSTATUS ($2002) in a loop

Post by VEG »

I have encountered a very strange behavior. I'm reading PPUSTATUS ($2002) in main code (reset vector) in a loop, and for some reason sometimes NMI isn't called in this case. It depends on ticks between reads from the $2002.

For example this code:

Code: Select all

RESET:

	; ... basic init code is skipped

	LDA #PPU_CTRL_NMI_ON
	STA PPU_CTRL

@loop:

	LDA PPU_STATUS
	NOP
	NOP
	JMP @loop
Causes that NMI will not be called at all in Dendy (Hybrid) mode of the Nintendulator or puNES.

My NMI does nothing but increment nmi_counter:

Code: Select all

NMI:

	PHA
	TXA
	PHA
	TYA
	PHA

	INC nmi_counter

	PLA
	TAY
	PLA
	TAX
	PLA
	RTI
How to reproduce:
1. Download this example: http://veg.by/files/nes/nonmibug.7z
2. Open Nintendulator, set Hybrid PPU mode, open demo.nes from the archive
3. Open debugger, press Add breakpoint, choose NMI, press Add
4. Press run.

You will see that NMI is called only 3 times. After it only infinite loop in the main code will be executed.

What's the cause of this?
Last edited by VEG on Wed Oct 12, 2016 6:22 am, edited 8 times in total.
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: NMI ignored if main code reads PPUSTATUS ($2002) in a lo

Post by Dwedit »

Well known by now and old news.
http://wiki.nesdev.com/w/index.php/NMI
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
VEG
Posts: 53
Joined: Mon Nov 11, 2013 2:55 pm
Location: Minsk, Belarus

Re: No NMI if main code reads PPUSTATUS ($2002) in a loop

Post by VEG »

No, it is a different problem. Please read the code in my message and you will see that it is an absolutely different situation. NMI vector is called just 3 times, and then it will not be called at all. Just because of this loop with two nops and reading from the $2002.

I've encountered this this problem in other code, and it skips some NMIs (not all) in other modes also, in Nintendulator, Nestopia and puNES. Possibly, it even happens on a real hardware, but I'm not sure. Only FCEUX doesn't skip NMIs at all. Depending on timings it is possible to achieve skipping of all NMIs. So, my example (with two NOPs) skips absolutely all NMI's in Dendy mode. It looks like NMI is disabled, but it is not true. You can remove LDA PPU_STATUS from the main code, and it will fix the problem with NMI. I would like to understand why reading from the $2002 blocks execution of the NMI.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: No NMI if main code reads PPUSTATUS ($2002) in a loop

Post by tepples »

This comment is brought to you by the letters NMI and the number eleven.

NMI is skipped if the NMI occurred flag gets set and acknowledged in the same cycle. Reading $2002 acknowledges the NMI. But if $2002 is read exactly on the same cycle that the PPU enters vblank, the logic that pulls the /NMI line low never has a chance to activate long enough that the 6502 core sees it.

The rest of my answer depends on whether you're making a game or a test ROM.

In a game
The easiest workaround is not to read $2002 in a tight loop after your init code. Instead, have your NMI handler increment a variable in RAM and your main thread test for changes in that variable:

Code: Select all

  lda nmi_counter
@loop
  cmp nmi_counter
  beq @loop
In a test ROM
On Dendy, each frame is 341*312/3 = 35464 CPU cycles long, which factors into prime powers as 2^3 * 11 * 13 * 31. Your polling loop is 11 cycles long (LDA aaaa = 4, NOP = 2, NOP = 2, JMP = 3), which divides the frame length (35464 cycles), making exactly 3224 iterations of your loop per frame. So once your program has locked on to the "set and acknowledged in the same cycle" state, it will stay that way.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: No NMI if main code reads PPUSTATUS ($2002) in a loop

Post by thefox »

Maybe the wiki article in question should be modified to clarify that an ill-timed $2002 read in fact suppresses the NMI in addition to affecting the $2002 return value. (I know it says it indirectly, but it might not be immediately obvious.)
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
VEG
Posts: 53
Joined: Mon Nov 11, 2013 2:55 pm
Location: Minsk, Belarus

Re: No NMI if main code reads PPUSTATUS ($2002) in a loop

Post by VEG »

Yes, it would be nice to mention it in the wiki.

I've tried to catch sprite overflow flag in the main code while waiting of changing of the value of the nmi_counter, but it was the cause of losing of some NMI calls. I've tried to use this flag as an event for changing direction of outputting of sprites :) It changes direction when last frame had sprite overflow. I know about bug with this flag when there is exactly 8 sprites on a line. It wasn't very important for this case. But as the result I had lost some NMIs. Fortunately, I can just change the direction every frame, so it is not a problem. But it is interesting how to easily detect if last frame had sprite overflow.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: No NMI if main code reads PPUSTATUS ($2002) in a loop

Post by tepples »

I'm not sure it applies to the Dendy's UA6538 PPU, but at least on the authentic NTSC 2A03, the sprite overflow flag has both false positives and false negatives due to an increment bug. If the ninth sprite directly follows the eighth in OAM, it's always set, and if seven or fewer sprites are on a line, it isn't set. But otherwise, it reads Y coordinates from OAM in a strange diagonal pattern.

In any case, the sprite overflow flag remains on until the pre-render line, which gives you all of the vertical blanking period starting at NMI to read it.
VEG
Posts: 53
Joined: Mon Nov 11, 2013 2:55 pm
Location: Minsk, Belarus

Re: No NMI if main code reads PPUSTATUS ($2002) in a loop

Post by VEG »

So, I can check this flag (for the previous frame) directly in my NMI handler, at the top of its code? Thank you for this information.
Anyway, I had removed that code and now I'm changing the order of OAM filling just on every frame.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: No NMI if main code reads PPUSTATUS ($2002) in a loop

Post by tokumaru »

VEG wrote:So, I can check this flag (for the previous frame) directly in my NMI handler, at the top of its code?
Yes. All flags (sprite 0 hit, sprite overflow and vblank) are automatically cleared at the end of vblank, so any time before that is OK for you to read them.
Anyway, I had removed that code and now I'm changing the order of OAM filling just on every frame.
That's better.
Post Reply