Reset $2003 at scanline 238

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

FHorse
Posts: 231
Joined: Sat May 08, 2010 9:31 am

Reset $2003 at scanline 238

Post by FHorse » Fri Jul 08, 2011 10:46 am

Testing my new MMC1 implementation, I found some problems with "Dusty Diamond's All-Star Softball (U) [!]. nes". After the initial choices the rom hangs in an infinite loop. Tested with other emulators I noticed that there are the same problem. I understood what was the problem and now I know what you think. The rom does something like this:
1) writes 0x00 in $2003
2) writes 4 bytes in the OAM through the $2004 (setting sprite 0)
3) in the next video frame update the entire OAM using $4014 starting to write, however, from the the sprite 1 instead of 0.
In this way, the sprite 0 will always have a Y coordinate "0xFF" and then never gets hit and the game goes into an infinite loop waiting that bit 6 of $2002 is set.
It seems that before the writing of the $4014, the $2003 should be cleaned.
Following this logic, I tried to reset the $2003 at scanline 238 after the last evaluated sprite and the games started to work.
They also started to run even the chinese MMC3 rom that not working properly such as:
Aladdin 2 (Unl). Nes
Bing Kuang Ji Dan Zi - flighty Chicken (Ch). Nes
and many others

User avatar
cpow
NESICIDE developer
Posts: 1097
Joined: Mon Oct 13, 2008 7:55 pm
Location: Minneapolis, MN
Contact:

Re: Reset $2003 at scanline 238

Post by cpow » Fri Jul 08, 2011 12:48 pm

Doesn't the OAM address get reset by the PPU somewhere in each scanline? I thought it used it during the sprite evaluation process which happens during pixels 0-255?

User avatar
Zepper
Formerly Fx3
Posts: 3221
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Re: Reset $2003 at scanline 238

Post by Zepper » Fri Jul 08, 2011 6:59 pm

FHorse wrote:It seems that before the writing of the $4014, the $2003 should be cleaned. Following this logic, I tried to reset the $2003 at scanline 238 after the last evaluated sprite and the games started to work.
Interesting. Cleaning the sprite address ($2003) before the VBlank was already discussed, but nobody got a definitive "yes" or a definitive "no".

There's something regarding using the upper $2003 bits to write sprites #0 and #1 data, but I'm unsure of it.

EDIT: here's the old discussion (sort of).

User avatar
James
Posts: 429
Joined: Sat Jan 22, 2005 8:51 am
Location: Chicago, IL
Contact:

Post by James » Sat Jul 09, 2011 5:24 am

These games work with nemulator; I never reset $2003.
get nemulator
http://nemulator.com

Drag
Posts: 1327
Joined: Mon Sep 27, 2004 2:57 pm
Contact:

Post by Drag » Sat Jul 09, 2011 11:53 am

Just purely incidentally, after reading this, I ran across this page on the wiki, which mentions $2003 being cleared.

I guess there's still some controversy as to how $2003 behaves during rendering?

User avatar
kevtris
Posts: 504
Joined: Sat Oct 29, 2005 2:09 am
Location: Indianapolis
Contact:

Post by kevtris » Mon Jul 11, 2011 9:54 am

Drag wrote:Just purely incidentally, after reading this, I ran across this page on the wiki, which mentions $2003 being cleared.

I guess there's still some controversy as to how $2003 behaves during rendering?
I discovered this long ago and thought it was well known... guess not? I clear it at the end of rendering on scanline 239. There are a few games that rely on this or else there's some subtle sprite errors.

I cannot remember which game it is now, but one game does strange stuff to 2003 so that sprite 0 is moved around (i.e. OAM entry 0 is NOT sprite 0). Huge Insect does this too. This is why it does not work on many emulators. The symptoms are the game starts fine, and the game screen works and everything... but no insects ever show up. This is because sprite 0 never hits, and that is caused by sachen "moving" sprite 0 by writing something to 2003 before sprite DMA'ing from what I recall.

The other game I'm thinking of (but unfortunately cannot remember the name of) does this too, but the game still works fine if you do not implement this. The problem is many sprites disappear due to them having more than 8 on a scanline. They did it to cycle the sprites without actually moving around the OAM entries like most other games did.

It appears that whatever sprite 2003 is pointing at will be sprite 0 as far as the rendering hardware is concerned. Since 2003 is reset at the end of rendering (or thereabouts), it usually is OAM entry 0. I do not know what happens if you set a non-MOD 4 value into 2003 before rendering though. i.e. if you load 01h 02h, or 03h into 2003. It's possible that the lower 2 bits are cleared before evaluation occurs but I don't know.

The actual OAM counter is composed of TWO separate counters on the PPU. The lower 2 bits is one counter, and the upper 6 are another counter. This is known because of how the OAM sprite overflow flag bug works. The state machine accidentally increments both counters at the same time instead of just the upper one after 8 sprites are rendered.
/* this is a comment */

User avatar
James
Posts: 429
Joined: Sat Jan 22, 2005 8:51 am
Location: Chicago, IL
Contact:

Post by James » Mon Jul 11, 2011 12:18 pm

kevtris wrote:Huge Insect does this too. This is why it does not work on many emulators. The symptoms are the game starts fine, and the game screen works and everything... but no insects ever show up.
Huge Insect works fine on nemulator too. I wonder if I'm doing something else incorrectly...
get nemulator
http://nemulator.com

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Post by thefox » Mon Jul 11, 2011 7:00 pm

The OAM address does get cleared *somewhere*, so it seems that the myth is a myth. I'm not sure if it's at the end of the scanline, or at the end of the frame, but here's a simple test I wrote:

Code: Select all

#include <knes.h>

#pragma bss-name(push, "ZEROPAGE")
#pragma data-name(push, "ZEROPAGE")

void hang(void)
{
    while ( 1 );
}

const char* text;
void write_text(void)
{
    poll_vblank();
    // Just to make sure the writes won't occur outside vblank...
    PPU.mask = BGREND_OFF;
    // Copy the text to $21C4.
    PPU_ADDR( 0x21C4 );
    while ( *text )
    {
        PPU.data = *text++;
    }
    // Hopefully we're still in vblank. :)
    PPU_SCROLL( 0, 0 );
    PPU.mask = BGREND_ON;
}

byte hexnum;
void write_hexnum(void)
{
    static const char* hex_lut = "0123456789ABCDEF";
    
    poll_vblank();
    // Just to make sure the writes won't occur outside vblank...
    PPU.mask = BGREND_OFF;
    // Copy the text to $21E4.
    PPU_ADDR( 0x21E4 );
    PPU.data = hex_lut[ hexnum >> 4  ];
    PPU.data = hex_lut[ hexnum & 0xF ];
    // Hopefully we're still in vblank. :)
    PPU_SCROLL( 0, 0 );
    PPU.mask = BGREND_ON;
}

void set_palette(void)
{
    // Palette is initially initialized to black by KNES.

    poll_vblank();
    
    // Set the second palette color to white.
    PPU_ADDR( PPU_BG_PALETTE + 1 );
    PPU.data = 0x30; // White
}

void test1(void)
{
    // Test #1:
    //   Test if $2003 is cleared at the end of the frame.
    
    // Disable rendering.
    PPU.mask = 0;
    
    // Initialize the OAM to numbers 0-255.
    {
        byte i = 0;
        byte *p = ( byte * )OAM;
        do
        {
            *p++ = i;
        }
        while ( ++i != 0 );
        poll_vblank();
        OAM_DMA();
    }
    
    // Set the OAM address.
    poll_vblank();
    PPU.oam_addr = 0x4;
    
    // Enable rendering.
    PPU.mask = BGREND_ON;
    
    // Wait for the vblank (assuming the flag gets cleared during the frame).
    poll_vblank();
    
    // If the hypothesis is true, $2003 should now be 0.
    // We can determine the value by reading from OAM.
    hexnum = PPU.oam_data; write_hexnum();
    
    text = "TEST 1 READY"; write_text();
}

int main(void)
{
    set_palette();
    
    text = "READY"; write_text();
    
    test1();
    
    // Wait for the judgement day.
    hang();
    
    return 0;
}
ROM: http://kkfos.aspekt.fi/downloads/2003-test.zip

Correct output (based on my PAL NES) is:

Code: Select all

TEST 1 READY
00
Nintendulator prints "04", Nestopia prints "00" and FCEUX prints "88" (doesn't support OAM readback?)

EDIT: Forgot to initialize "i" to 0 in the OAM init code, however this shouldn't change the behaviour because the reset code initializes the RAM.
Last edited by thefox on Mon Jul 11, 2011 11:16 pm, edited 2 times in total.

User avatar
cpow
NESICIDE developer
Posts: 1097
Joined: Mon Oct 13, 2008 7:55 pm
Location: Minneapolis, MN
Contact:

Post by cpow » Mon Jul 11, 2011 7:12 pm

thefox wrote:ROM: http://kkfos.aspekt.fi/downloads/2003-test.zip

Correct output (based on my PAL NES) is:

Code: Select all

TEST 1 READY
00
Weird, this is what I get too, yet I hang at the Akira(J) Start/Continue screen and I don't get any insects in Huge Insect (I waited at least five minutes for one to show up).

User avatar
Zepper
Formerly Fx3
Posts: 3221
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Post by Zepper » Mon Jul 11, 2011 7:24 pm

RockNES prints 04 too.

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Post by thefox » Mon Jul 11, 2011 9:06 pm

I also forgot that OAM data readback isn't reliable, however this shouldn't matter on PAL NES as the OAM bytes 0-31 seem to be stable (based on blargg's earlier tests). It might produce strange results on NTSC NES though, depending on CPU-PPU synchronization. Can anybody try it out?
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Post by thefox » Mon Jul 11, 2011 11:09 pm

I'm getting some *very* strange results when testing this on my PAL NES. Remember that PAL VBL is 70 scanlines long.

If I write a value to OAM addr, and read back (using $2004) during the first ~20 or so scanlines of the vblank, it returns the expected value. However, if I read after that point, the OAM addr seems to have changed (and the value depends on how many cycles after that point I read it). It's almost as if the PAL PPU starts evaluating sprites while still in vblank?

Also in the test I posted earlier, if I change the oam_addr init value to something like 0x69, it ends up as 0x80 after the end of the frame instead of 0. So it seems to be incorrect to say (per se) that the OAM addr is cleared at some point in the frame. It's more likely that it gets cleared (under some conditions only!) as a result of the scanline sprite evaluation logic (which uses the same register).

Take this with a grain of salt, there are so many factors in play (like OAM readback corruption) that it's easy to make mistakes. I need to run some more tests later. And finally get me an NTSC NES.

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Post by thefox » Tue Jul 19, 2011 1:14 am

thefox wrote:If I write a value to OAM addr, and read back (using $2004) during the first ~20 or so scanlines of the vblank, it returns the expected value. However, if I read after that point, the OAM addr seems to have changed (and the value depends on how many cycles after that point I read it). It's almost as if the PAL PPU starts evaluating sprites while still in vblank?
I've confirmed this. Doing OAM DMA on PAL NES more than ~20 or so (didn't time this exactly) scanlines into vblank will *not* copy the sprites over properly.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

tepples
Posts: 22161
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples » Tue Jul 19, 2011 5:08 am

So it might not be 70 lines of vblank and 1 line of pre-render as much as 20 lines of vblank and 51 lines of pre-render. Does this newly discovered OAM copy failure also happen if you have forced blank on?

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Post by thefox » Tue Jul 19, 2011 5:43 am

tepples wrote:Does this newly discovered OAM copy failure also happen if you have forced blank on?
Yes.
Also in the test I posted earlier, if I change the oam_addr init value to something like 0x69, it ends up as 0x80 after the end of the frame instead of 0.
This I couldn't reproduce for whatever reason. Strange.

Post Reply