NMI question

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
NMI question
by on (#120006)
The wiki says:

Code:
Two 1-bit registers inside the PPU control the generation of NMI signals. Frame timing and accesses to the PPU's PPUCTRL ($2000) and PPUSTATUS ($2002) registers change these registers as follows, regardless of whether rendering is enabled:
1.Start of vertical blanking: Set NMI_occurred in PPU to true.
2.End of vertical blanking, sometime in pre-render scanline: Set NMI_occurred to false.
3.Read $2002: Return old status of NMI_occurred in bit 7, then set NMI_occurred to false.
4.Write to $2000: Set NMI_output to bit 7.

The PPU pulls /NMI low if and only if both NMI_occurred and NMI_output are true. By toggling NMI_output ($2000 bit 7) during vertical blank without reading $2002, a program can cause /NMI to be pulled low multiple times, causing multiple NMIs to be generated.


Point 3: why return "old status" and not "current status"?
Re: NMI question
by on (#120007)
Because the "current status" is false, because you just cleared it by reading from $2002.
Re: NMI question
by on (#120009)
Quote:
Return old status of NMI_occurred in bit 7, then set NMI_occurred to false.

This specifically states that the clearing takes place after the status being returned, so I too don't get the "old" status thing. Does it mean the "soon to be old" status?
Re: NMI question
by on (#120010)
Yes. But I find "old status" apt because the $2002 race behavior is as if it output the value from previous dot. Here's the order of operations inside the PPU, as far as I can tell:
  1. If $2002 is being read, output the current value of the vblank flag.
  2. If this is the first dot of vblank, set the vblank flag to true.
  3. If $2002 is being read, clear the vblank flag to false.
A $2002 read activates steps 1 and 3, and the first dot of vblank activates step 2. But if a $2002 read occurs on the first dot of vblank, all three steps occur, and $2002 bit 7 is never read true during that vblank.
Re: NMI question
by on (#120021)
Im doing this in my code:
Its like:
Code:
data ReadPpuMem(addr)
{

    switch (addr)
    {
     //other cases...
     case 2002:
         //other things here
         data |= Ppu.on_vblank;
         Ppu.on_vblank = 0;
         break;
    }
    return data;
}


Is it bad?
Re: NMI question
by on (#120026)
= not |=. That will just set any bits that aren't already set.
Re: NMI question
by on (#120028)
No, his use of |= is correct.
see the bit about "other things here" where he presumably initializes data, and sets the overflow and sprite 0 flags.
Re: NMI question
by on (#120031)
What's missing is the special case when $2002 is read at the exact time the NMI flag gets set, which returns false AND clears the flag.
Re: NMI question
by on (#120121)
tokumaru wrote:
What's missing is the special case when $2002 is read at the exact time the NMI flag gets set, which returns false AND clears the flag.


I actually use this method for that:

Code:
data ReadPpuMem(addr)
{

    switch (addr)
    {
     //other cases...
     case 2002:
         //other things here
         data |= Ppu.on_vblank;
         Ppu.on_vblank = 0;
         
         //new code
         if (Ppu.scanline == 241 && Ppu.scanline_cycle == 1)
              Ppu.cant_setvblank = 1; 

       break;
    }
    return data;
}


Then in my ppu code:

Code:
...
if (Ppu.scanline == 241 && Ppu.scanline_cycle == 1)
    if (!Ppu.cant_setvblank)
        Ppu.on_vbank = 0x80;
    else
        Ppu.cant_setvblank = 0;
...


I know is not the best method, but passes Blargg's test "8) Reading 1 PPU clock before VBL should suppress setting".
Re: NMI question
by on (#120131)
Here's an example...

Code:
      case 0:
        if( (ppu->reg0 & 0x80) && !(data & 0x80) )
        {
           cpu_nmicancel();
        }
        else if( !(ppu->reg0 & 0x80) &&  (data & 0x80) && (ppu->status & 0x80) && (341 != ppucycle))
        {
           cpu_nmitrigger();
        }
Re: NMI question
by on (#120159)
Thanks Zepper!!
Re: NMI question
by on (#124630)
NMI circuit is tricky :D

The circuit itself is controlled by PPU control unit VSET / VCLR signals.

VBLANK flip/flop is cleared by VCLR or after reading $2002.

INT output goes directly to PPU /INT pad (NMI). This pad reflects current VBLANK flip/flop value, masked by VBLANK enable flag from $2000. Setting / clear VBLANK enable flag has nothing to do with current flip/flop value.

Reading $2002 first will output current VBLANK flip/flop value from intermediate D-latch and clear flip/flop at same time.