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

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
No NMI if main code reads PPUSTATUS ($2002) in a loop
by on (#180714)
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:
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:
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?
Re: NMI ignored if main code reads PPUSTATUS ($2002) in a lo
by on (#180715)
Well known by now and old news.
http://wiki.nesdev.com/w/index.php/NMI
Re: No NMI if main code reads PPUSTATUS ($2002) in a loop
by on (#180716)
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.
Re: No NMI if main code reads PPUSTATUS ($2002) in a loop
by on (#180718)
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:
  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.
Re: No NMI if main code reads PPUSTATUS ($2002) in a loop
by on (#180720)
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.)
Re: No NMI if main code reads PPUSTATUS ($2002) in a loop
by on (#180721)
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.
Re: No NMI if main code reads PPUSTATUS ($2002) in a loop
by on (#180722)
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.
Re: No NMI if main code reads PPUSTATUS ($2002) in a loop
by on (#180725)
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.
Re: No NMI if main code reads PPUSTATUS ($2002) in a loop
by on (#180730)
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.

Quote:
Anyway, I had removed that code and now I'm changing the order of OAM filling just on every frame.

That's better.