OAM reading isn't reliable

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

OAM reading isn't reliable

Post by blargg »

I've found some very weird OAM read behavior on my NTSC NES, and I'm pretty sure it occurs on the PAL one as well (I know it also acts weird, just haven't swapped boards back to try it yet). Basically, reading from OAM is broken for three out of the four PPU-CPU synchronizations that the system will be in after power or reset. I've been tearing my hair out trying to make sense of this, thinking my NTSC NES was damaged. Once I realized it was related to PPU-CPU synchronization, and that there were exactly four possible patterns of behavior, it started falling into place.

When SPRDATA reads are working correctly, they give you the contents of OAM at whatever SPRADDR is set to. When they're not, some/many/most addresses give you OAM at a different address. I haven't tested writing thoroughly, but I'm pretty sure that SPRDATA writes and SPRDMA work reliably all the time, explaining why this crazy behavior wouldn't have been a problem, as SPRDATA reads are rarely/never used.

I wrote a test program to determine what OAM byte is actually read for each address. It tries them from $00-$FF, with 16 per row. So when working correctly, we'd expect it to begin

00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
...

But let's say it's all correct as above, except that reading from address $04 really reads from $48:

00 01 02 03 48 05 06 07 08 09 0A 0B 0C 0D 0E 0F
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
...

Since it's difficult to easily see the incorrect entry, the test program prints -- for correct entries, so for the second example above, it prints

-- -- -- -- 48 -- -- -- -- -- -- -- -- -- -- --
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
...

So, here are the three insane mappings I get, randomly chosen at reset, but unchanging until I reset/power again:

Code: Select all

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
18 19 1A 1B 1C 1D 1E 1F -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
-- -- -- -- -- -- -- -- 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 
20 21 22 23 24 25 26 27 20 21 22 23 24 25 26 27 

-- 21 22 23 24 25 26 27 -- 21 22 23 24 25 26 27 
-- 21 22 23 24 25 26 27 -- 21 22 23 24 25 26 27 
-- -- -- -- -- -- -- -- 20 21 22 23 24 25 26 27 
-- 21 22 23 24 25 26 27 -- 21 22 23 24 25 26 27 
-- 21 22 23 24 25 26 27 -- 29 2A 23 2C 25 26 27 
-- 21 22 23 24 25 26 27 -- 39 0A 23 2C 25 26 27 
-- -- -- -- -- -- -- -- -- 61 62 63 64 65 66 67 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- -- 
-- -- 82 -- -- -- 26 A7 -- -- -- -- -- 8D 0E AF 
-- -- -- -- -- 25 26 27 -- A1 A2 A3 A4 25 26 27 
-- A1 A2 A3 A4 25 26 27 -- -- -- -- -- -- AE AF 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
The first two are stable. The first one simply reads $18-$1F when $20-$27 are read, nice and simple. The second one mirrors $20-$27 over the entire range! The last one is unstable, where the mappings change somewhat randomly, but still follow the above general pattern.

I'll probably be working more on this tomorrow, hopefully making the test program standalone so others can see how their NES behaves (I imagine each will be slightly different). Any thoughts on it?
bunnyboy
Posts: 449
Joined: Thu Oct 27, 2005 1:44 pm
Location: CA
Contact:

Post by bunnyboy »

This isn't at all helpful, but wasn't there at least one production game that read back OAM? Micro Machines? Maybe it just wrote OAM mid frame?

I would be interested in a test app. Will be interesting to see what clones do too. Would there be a separate way to identify which clock alignment is running, other than looking at the mapping results?
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

This could be related to the refresh bug I helped discover that caused pieces to flicker in LJ65. The working hypothesis was that there is an uncompleted 8-byte DRAM refresh transaction in the PPU's internal buffer, which gets sprayed somewhere else if you do "funny" things. Try displaying at least one full frame with rendering turned on before you fill and then read back OAM.
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

I'm getting a feeling that OAM was never meant to be read, that the read functionality was meant exactly for what Micro Machines does. That would explain why reading doesn't increment the address (so it wouldn't disturb when you read while PPU rendering is occurring). Still probing this behavior, right now writing a program to see whether OAM even works as expected on the one PPU-CPU alignment that seems normal.
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

tepples: enabling sprite rendering beforehand didn't seem to have any effect. This seems to be entirely based on the PPU-CPU synchronization. It'll be interesting to see how this behaves on PAL, now that I've got the cause figured out.

Here's a test ROM that visually shows what addresses don't read back normally (full ca65 source included):

oam_read.zip

It tests OAM reading ($2004), being sure it reads the byte from OAM at the current address in $2003. It scans OAM from 0 to $FF, testing each byte in sequence. It prints a '-' where it reads back from the current address, and '*' where it doesn't. Each row represents 16 bytes of OAM, 16 rows total.

On my NTSC front-loader NES, I get the following four general patterns at random after power/reset:

Image

I also wrote another test that thoroughly tests OAM:

oam_stress.zip

It randomly sets the address, then randomly either writes a random number of random bytes, or reads from the current address a random number of times and verifies that it matches what's expected. It does this for tens of seconds (refreshing OAM periodically so it doesn't fade). Once done, it verifies that all bytes in OAM match what's expected. This only passes for the left-hand pattern above. It also prints a pattern, but it's just based on what it's modified so far, so it might not exactly match the pattern from the first test.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Post by thefox »

I would've tried this on my PAL NES but couldn't get my piece of shit toasters to work. Gave up after 15 minutes of cycling power and adjusting the cart. I'm very interested in the results for PAL though.

I guess this would also explain why sprites were sometimes corrupted in some games when using my PowerPak save state mappers.

BTW oam_stress fails in Nintendulator, I wonder why?
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

I realized that having these tests fail isn't correct, since they should never fail on a NES. The system under test is merely failing to meet our desirable simple model of behavior.

How quickly does Nintendulator fail? That's surprising.

bunnyboy, sorry I ignored one of your questions. Yes, it's possible to determine the PPU-CPU synchronization separately from these tests. I did so and verified that each pattern corresponds to a particular synchronization. Or are you requesting a ROM to test that via the other means (VBL timing, sprite hit, overflow)?
jsr
Posts: 42
Joined: Thu Oct 07, 2004 2:47 am
Contact:

Post by jsr »

I tested oam_read on a PAL NES and famicom and got the following results:

PAL NES
Most of the reads on lines 3-8 always failed, it never passed completely. The errors started from midway on line 3 and ended on beginning of line 8, and all other lines passed without errors. The exact patterns on these lines was random each time after reset. So nothing like the NTSC results, and I'd test on my other PAL unit also but the controller port is broken. Would be interesting to see if it behaves similar or is completely random.

Famicom
The famicom always failed on all reads every time, seems like sprite reads doesn't work at all.
User avatar
Anders_A
Posts: 88
Joined: Mon Nov 27, 2006 11:56 pm
Location: Sollentuna, Sweden

Post by Anders_A »

blargg wrote:I'm getting a feeling that OAM was never meant to be read, that the read functionality was meant exactly for what Micro Machines does.
What exacly is it that Micro Machines does?
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

Very interesting that Famicom fails all the time. I'll have to write a test for you to run to see what exactly $2004 returns.

I still need to go back to PAL testing (it's tedious because all I have is a PAL nes board, but no case, so I have to pull my NTSC NES apart and put the PAL board in and hook everything up to it). I was seeing what you mentioned before, where they work except a certain part of the frame. I'm guessing that it's because the PPU-CPU synchronization on PAL constantly drifts around, rather than being frozen at power/reset as on NTSC.

Micro Machines is covered in previous posts. I'm not sure of the details. In one thread Disch and I discussed this and came up with some code to reliably use it in place of sprite #0 hit.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Post by thefox »

blargg wrote:How quickly does Nintendulator fail? That's surprising.
Very quickly.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

I had JSR run a further test on the Famicom and it seems that reading from $2004 is like reading from any other write-only PPU register, at least when rendering is disabled. We're going to see whether it behaves any differently when rendering is enabled.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

So that scanline detection method based on $2004 reads is history, right?
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

We have yet to see what $2004 reads back with rendering enabled on a Famicom. I also haven't tested that technique on my NTSC NES with the various PPU synchronizations. I'm betting it'll work fine on NTSC, that $2004 reading exists for this very purpose.

EDIT: Jsr pretty well verified that $2004 is a write-only register on Famicom in all cases, so yeah, I guess the $2004 read during rendering approach to scanline synchronization is dead, at least if you want Famicom compatibility.
6502freak
Posts: 92
Joined: Sun Dec 07, 2008 1:11 pm

Post by 6502freak »

blargg wrote:Very interesting that Famicom fails all the time.
On the Famicom, both PPU and CPU are connected to the reset circuit/switch, which implies that there might be no variations in cycle alignment at all on this system, since every reset brings both PPU and CPU in a defined state. On the NES, the PPU reset line is tied to VCC (deactivated), hence the picture stays stable when pushing reset, unlike the Famicom.
Post Reply