Page 1 of 1

sprite0 problem

Posted: Tue Nov 13, 2018 12:33 am
by pubby
I have a loop which repeatedly finds the sprite0 hit and colors the screen green after it occurs:

Code: Select all

loop:
    ; Wait until sprite0 is cleared
:
    bit PPUSTATUS
    bvs :-

    ; Wait until sprite0 is set
:
    bit PPUSTATUS
    bvc :-

    ; Color the screen
    lda #PPUMASK_EMPHASIZE_G | PPUMASK_GREYSCALE
    sta PPUMASK
    jmp loop
When I test this in Nestopia and Nintendulator, it works for some frames, but other frames the screen is 100% green with rendering disabled. This seems to occur randomly.

What's going on?

I've tried setting breakpoints on PPUMASK, but I see absolutely no difference between frames. The split seems to occur at the same time each frame, but for some reason it doesn't display that way.

(If you're wondering, my NMI does nothing but set the PPU registers and returns)

Re: sprite0 problem

Posted: Tue Nov 13, 2018 12:52 am
by rainwarrior
My guess is that reading $2002 on the onset cycle of the NMI skips the NMI entirely? It's probably a conflict here to spin on a $2002 read loop while waiting for a pending NMI. Wait for a signal from the NMI to proceed first before you enter the wait loop for sprite 0 clear.

Might be worth posting the ROM for testing if you can, though, would maybe be interesting to compare this niche behaviour against emulators and hardware?

Re: sprite0 problem

Posted: Tue Nov 13, 2018 1:29 am
by pubby
rainwarrior wrote:My guess is that reading $2002 on the onset cycle of the NMI skips the NMI entirely? It's probably a conflict here to spin on a $2002 read loop while waiting for a pending NMI.
Hm, this seems likely, because if I pad the loops with NOPs, the glitch happens less frequently. But I don't think it's from the NMI handler being skipped (can that even happen?).

The documentation on that page says that if you read $2002 on the frame NMI starts, 0 is returned in bit 7. It doesn't mention bit 6, but if bit 6 also appears as 0 on that cycle then that could explain the behavior.
Wait for a signal from the NMI to proceed first before you enter the wait loop for sprite 0 clear.
Cool I found a way to make it work:

Code: Select all

    ; Wait until sprite0 is cleared
    bit PPUSTATUS
    bvc clear
    jsr wait_for_nmi
:
    bit PPUSTATUS
    bvs :-
clear:

    ; Wait until sprite0 is set
:
    bit PPUSTATUS
    bvc :-
It's a bit tricky, because I only wait for a NMI signal if the sprite0 bit is set to begin with. Waiting for a signal every time would incur lag frames (which is why I didn't do it to begin with).

Re: sprite0 problem

Posted: Tue Nov 13, 2018 5:07 am
by dougeff
If that's the code, then I'm suspicious of it.

wait for NMI should actually wait till the nmi code happens, and not be skipped for any reason.

The NMI code should set a flag or increase a counter, and "wait for NMI" should wait for that change.

Re: sprite0 problem

Posted: Tue Nov 13, 2018 5:54 am
by pubby
Ok here's a test ROM. I'd be interested to know what a real NES does, and if it differs on PAL vs NTSC.

Flashing screen = bad
No flashing = good

Re: sprite0 problem

Posted: Tue Nov 13, 2018 7:19 am
by dougeff
I had trouble playing it in several emulators, due to the illegal opcodes. I'm not sure which one to use.

On hardware it is flashing very quickly... perhaps skipping every other frame. And there is some text on the screen, which I think you did not intend.

CRT with original NES.
20181113_090920_resized.jpg
Flatscreen with toploader NES.
20181113_091043_resized.jpg

EDIT - Mesen emulates the flickering fairly well, but didn't emulate the random text on the screen.

Re: sprite0 problem

Posted: Tue Nov 13, 2018 11:54 am
by pubby
Hey thanks Doug! :beer:

Now to figure out why this is behaving this way. . .

Re: sprite0 problem

Posted: Tue Nov 13, 2018 12:24 pm
by tokumaru
Looking at the ROM, it seems that after the split, you immediately go back to polling $2002 waiting for the sprite hit flag to be cleared. Those $2002 reads are probably suppressing NMIs from time to time. Try not going back to polling right after the split. Instead, wait for the frame to end, for the NMI to run, and only then you should start polling again.

Re: sprite0 problem

Posted: Wed Nov 14, 2018 5:45 am
by pubby
Hmm so the glitch actually causes the handler to be skipped?

I had no idea. I thought it just affected the value read.

Re: sprite0 problem

Posted: Wed Nov 14, 2018 7:23 am
by dougeff
If NMIs are on, the signal will still fire regardless of what the program does. It will jump to the NMI code. I don't believe it will skip jumping to NMI by reading from 2002.

The vblank flag (bit 7 of 2002) clears when read. Sometimes it clears on the same cycle it is set, giving a false read of zero. Which is why you shouldn't rely on 2002 reads to time the start of vblank.

Use a flag that YOU set in the NMI handler to know when vblank has occurred.

Then poll for Sprite zero flag clear.

Then poll for Sprite zero flag set.

Re: sprite0 problem

Posted: Wed Nov 14, 2018 11:54 am
by tokumaru
dougeff wrote:If NMIs are on, the signal will still fire regardless of what the program does. It will jump to the NMI code. I don't believe it will skip jumping to NMI by reading from 2002.
That's not correct. From the wiki (https://wiki.nesdev.com/w/index.php/PPU_frame_timing):
Reading $2002 within a few PPU clocks of when VBL is set results in special-case behavior. Reading one PPU clock before reads it as clear and never sets the flag or generates NMI for that frame. Reading on the same PPU clock or one later reads it as set, clears it, and suppresses the NMI for that frame. Reading two or more PPU clocks before/after it's set behaves normally (reads flag's value, clears it, and doesn't affect NMI operation). This suppression behavior is due to the $2002 read pulling the NMI line back up too quickly after it drops (NMI is active low) for the CPU to see it. (CPU inputs like NMI are sampled each clock.)
So yeah, if you read $2002 too close to the time the vblank flag gets set, the CPU doesn't pick up the change in the NMI line and the NMI doesn't fire. In short: don't poll $2002 while waiting for vblank, even if you're testing the sprite hit flag rather than the vblank flag.