It is currently Mon Nov 20, 2017 2:12 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 9 posts ] 
Author Message
PostPosted: Wed Oct 12, 2016 5:31 am 
Offline

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


Last edited by VEG on Wed Oct 12, 2016 6:22 am, edited 8 times in total.

Top
 Profile  
 
PostPosted: Wed Oct 12, 2016 5:34 am 
Offline
User avatar

Joined: Fri Nov 19, 2004 7:35 pm
Posts: 3950
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!


Top
 Profile  
 
PostPosted: Wed Oct 12, 2016 5:52 am 
Offline

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


Top
 Profile  
 
PostPosted: Wed Oct 12, 2016 7:12 am 
Online

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


Top
 Profile  
 
PostPosted: Wed Oct 12, 2016 7:27 am 
Offline
User avatar

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


Top
 Profile  
 
PostPosted: Wed Oct 12, 2016 7:45 am 
Offline

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


Top
 Profile  
 
PostPosted: Wed Oct 12, 2016 8:15 am 
Online

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


Top
 Profile  
 
PostPosted: Wed Oct 12, 2016 9:07 am 
Offline

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


Top
 Profile  
 
PostPosted: Wed Oct 12, 2016 11:01 am 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10114
Location: Rio de Janeiro - Brazil
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.


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: MLX and 12 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