CPU-test: cpu_dummy_writes [DONE]

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Bisqwit
Posts: 249
Joined: Fri Oct 14, 2011 1:09 am

CPU-test: cpu_dummy_writes [DONE]

Post by Bisqwit »

There is a test called cpu_dummy_reads written by Blargg.

I created a logical counterpart called cpu_dummy_writes.

As I understand it, read-modify-write instructions, such as INC, issue two writes: Once for the unmodified value, and then for the modified value.

I tried my hand at making one (sans timings), and it is here, with source code: http://bisqwit.iki.fi/src/nes_tests/cpu ... writes.zip

It includes two versions. One depends on OAM reading being reliable, and the other depends on proper implementation of the PPU's open bus.

Test results for OAM version:

-- FCEUX, oldppu: OAM reading not supported (867 errors); fails test #3 (fail #2 given)
-- FCEUX, newppu: All opcodes pass, but 5 errors in OAM read test (fail #6)
-- BNES (BSNES 087): All official instructions pass, but emulator errors out at the first unofficial instruction (technically fail #9, but error message never gets displayed)
-- Nesemu1 (my emulator): All tests pass
-- Nintendulator 0.970: All opcodes pass, but 3 errors in OAM read test (fail #6)
-- Nestopia 1.40: All tests pass
-- NES: Random failures, more often than not. This suggests that OAM reading is reliable only on emulators. Though this test can help catch buggy CPU emulators, it does not suggest overall accuracy as it is now.

Test results for the PPU memory version:

-- FCEUX, oldppu: All tests pass
-- FCEUX, newppu: 150 open bus errors, opcodes worked fine (fail #10)
-- BNES (BSNES 087): 750 open bus errors, crash at unofficial instructions (technically fail #9, but error message never gets displayed), all official instructions report failure (but this is because open bus is not implemented properly).
-- Nesemu1 (my emulator): All tests pass
-- Nintendulator 0.970: All tests pass (nags about unofficial instructions)
-- Nestopia 1.40: 15 open bus errors, opcodes worked fine (fail #10)
-- NES: All tests pass
Last edited by Bisqwit on Sun Mar 25, 2012 2:43 pm, edited 14 times in total.
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Post by Zepper »

*ahem* RockNES 5.07 gives error #10, "INC abs should do double-write".

Code: Select all

#define INC(v) \
   z_readvalue(); \
   v += 1; \
   SET_SZ_FLAGS(v)

CPUOP(INC1)
  value = readvalue(offset);
  INC(value);
  writevalue(offset, value);
OPEND
Also... the timing diagram source.

Code: Select all

Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
                                     SLO, SRE, RLA, RRA, ISB, DCP)

        #  address R/W description
       --- ------- --- ------------------------------------------
        1    PC     R  fetch opcode, increment PC
        2    PC     R  fetch low byte of address, increment PC
        3    PC     R  fetch high byte of address, increment PC
        4  address  R  read from effective address
        5  address  W  write the value back to effective address,
                       and do the operation on it
        6  address  W  write the new value to effective address
Looks like the document is OK. I can't believe I bypassed it! :(
FHorse
Posts: 232
Joined: Sat May 08, 2010 9:31 am

Post by FHorse »

All test passed with puNES :)
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

Might one be able to exploit open bus to get the right value into INC $2005?

Code: Select all

 ldx #$1F
  inc $1FF5,x
  ; read #$FE = INC a,x
  ; read #$F5
  ; read #$1F
  ; read $1F05 = $0705
  ; read $2005 (open bus; hopefully returns value left there by reading $1F05)
  ; write back old value to $2005
  ; write back new value to $2005
$2006 (VRAM address) and $2007 (VRAM data) also have semantics for multiple reads and writes, and they may ignore writes too.

I'd make this, but my PowerPak's open bus behavior can't be trusted.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: CPU-test request: cpu_dummy_writes

Post by thefox »

Bisqwit wrote:-- NES, PowerPak: According to Kiddcade at #nesdev, the OAM write/read tests fail randomly; and when they succeed, the instruction tests fail randomly. However, as of this writing, he has not tried the newest version.
This is expected, see this thread.
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Post by Zepper »

Well, I did the necessary changes, but there's a problem. The APU test ROM (4017_timing.nes) fails regarding "delay after effective $4017 write: 0". Here's my new code anyways...

Code: Select all

#define INC(v) \
   v += 1; \
   SET_SZ_FLAGS(v)

CPUOP(INC1)
  value = readvalue(offset);
  writevalue(offset, value);
  INC(value);
  writevalue(offset, value);
OPEND
EDIT: I had to modify ASL, ROL, LSR and ROR w/ implicit addressing mode to take 2 cycles again. Everything's fine.
Last edited by Zepper on Wed Mar 21, 2012 9:48 am, edited 1 time in total.
Near
Founder of higan project
Posts: 1553
Joined: Mon Mar 27, 2006 5:23 pm

Re: CPU-test request: cpu_dummy_writes

Post by Near »

Bisqwit wrote:-- BNES (BSNES 087): All official instructions pass, but emulator errors out at the first unofficial instruction (technically fail #50, but error message never gets displayed)
The documentation on unofficial opcodes is piss-poor, and wildly inconsistent. Every unofficial opcode also has 5+ mnemonics, which doesn't help matters any.

I'd appreciate if there were a document that explains 100% of all behaviors (especially the ones that have "undefined behavior", which is coded speak for "I don't understand the actual interactions with past CPU actions/states"), and gives exactly one community-preferred name for each mnemonic.
Bisqwit
Posts: 249
Joined: Fri Oct 14, 2011 1:09 am

Re: unofficial opcodes

Post by Bisqwit »

SLO (ASL+ORA), RLA (ROL+AND), SRE (LSR+EOR), RRA (ROR+ADC), DCP (DEC+CMP), ISB (INC+SBC).

These six are very consistent and regular. They are (nearly) perfect synthesis of their component opcodes.
I am currently rewriting my CPU core, and from the rewritten core the combined synthesis of these opcodes becomes also rather obvious.
--------
Take opcode $17 (SLO zx) for example. It is a perfect synthesis of $16 (ASL zx) and 15 (ORA zx).
ASL zx:

Code: Select all

      //    address    write        recipient      code
      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.raw=data'),
      Array('ADDR.raw',false,      'data',        'ADDR.lo+=X'),
      Array('ADDR.raw',false,      'data',        't&=data; data=t'),
      Array('ADDR.raw',true,       'data',        'sb=0; reg.P_C = t&0x80; t<<=1$ t|=sb; data=t'),
      Array('ADDR.raw',true,       'data'), // Write new value
      Array('PC.raw',false,        'OPCODE',      '++PC.raw; reg.P_N=t&0x80; reg.P_Z=!u8(t)'),
ORA zx:

Code: Select all

      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.raw=data'),
      Array('ADDR.raw',false,      'data',        'ADDR.lo+=X'),
      Array('ADDR.raw',false,      'data',        't&=data; t2&=A'),
      Array('PC.raw',false,        'OPCODE',      '++PC.raw; t|=t2; A=t; reg.P_N=t&0x80; reg.P_Z=!u8(t)'),
SLO zx:

Code: Select all

      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.raw=data'),     IDENTICAL
      Array('ADDR.raw',false,      'data',        'ADDR.lo+=X'),                  IDENTICAL
      Array('ADDR.raw',false,      'data',        't&=data; t2&=A; data=t'),      INCLUDES EVERYTHING FROM BOTH COMPONENTS
      Array('ADDR.raw',true,       'data',        'sb=0; reg.P_C = t&0x80; t<<=1$ t|=sb; data=t'), THIS COMES FROM ASL
      Array('ADDR.raw',true,       'data'), // Write new value                    THIS FROM ASL
      Array('PC.raw',false,        'OPCODE',      '++PC.raw; t|=t2; A=t; reg.P_N=t&0x80; reg.P_Z=!u8(t)'), INCLUDES EVERYTHING FROM BOTH COMPONENTS
--------
Similarly, opcode 8D, STA abs:

Code: Select all

      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.lo=data'),
      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.hi=data; t&=A; data=t'),
      Array('ADDR.raw',true,       'data'),
      Array('PC.raw',false,        'OPCODE',      '++PC.raw'),
Opcode 8E, STX abs:

Code: Select all

      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.lo=data'),
      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.hi=data; t&=X; data=t'),
      Array('ADDR.raw',true,       'data'),
      Array('PC.raw',false,        'OPCODE',      '++PC.raw'),
Opcode 8F, SAX abs:

Code: Select all

      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.lo=data'),          IDENTICAL
      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.hi=data; t&=A; t&=X; data=t'), EVERYTHING FROM BOTH
      Array('ADDR.raw',true,       'data'),                                           IDENTICAL
      Array('PC.raw',false,        'OPCODE',      '++PC.raw'),                        IDENTICAL
--------
For a more complicated (and at first glance, irregular) example, consider the differences between these two official opcodes:
Opcode 95, STA zx (official):

Code: Select all

      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.raw=data'),
      Array('ADDR.raw',false,      'data',        'ADDR.lo+=X; t&=A; data=t'), // dummy read while the CPU is preparing data
      Array('ADDR.raw',true,       'data'),
      Array('PC.raw',false,        'OPCODE',      '++PC.raw'),
Opcode 96, STX zy (official):

Code: Select all

      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.raw=data'),
      Array('ADDR.raw',false,      'data',        'ADDR.lo+=Y; t&=X; data=t'),
      Array('ADDR.raw',true,       'data'),
      Array('PC.raw',false,        'OPCODE',      '++PC.raw'),
However, with absolute indexing, there is an extra cycle dedicated for fixing up the 16-bit address, and this conflicts with the indexing logic (the high-part of the address is loaded at the same time (I guess, in the same bus) as the target register, which is an index register), making 9E (STX absy) broken and therefore not part of the official set of instructions.

Opcode 9D, STA absx (official):

Code: Select all

      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.lo=data'),
      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.hi=data; ADDR.lo+=X; overflow=ADDR.lo<X'),
      Array('ADDR.raw',false,      'data',        'ADDR.hi += overflow; t&=A; data=t'),
      Array('ADDR.raw',true,       'data'),
      Array('PC.raw',false,        'OPCODE',      '++PC.raw'),
Opcode 9E, STX absy (unofficial, alternative names: SHX/SXA/XAS)

Code: Select all

      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.lo=data'),
      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.hi=data; ADDR.lo+=Y; overflow=ADDR.lo<Y'),
      Array('ADDR.raw',false,      'data',        't&=(ADDR.hi + overflow); t&=X; data=t'),
      Array('ADDR.raw',true,       'data'),
      Array('PC.raw',false,        'OPCODE',      '++PC.raw'),
Then the perfect synthesis opcode of these two becomes:
Opcode 9F, SAX absx (unofficial, alternative names: SHA/AHA/AHX)

Code: Select all

      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.lo=data'),
      Array('PC.raw',false,        'data',        '++PC.raw; ADDR.hi=data; ADDR.lo+=X; overflow=ADDR.lo<X'),
      Array('ADDR.raw',false,      'data',        't&=(ADDR.hi+overflow); t&=A; t&=X; data=t'),
      Array('ADDR.raw',true,       'data'),
      Array('PC.raw',false,        'OPCODE',      '++PC.raw'),
--------
At this cycle-level opcode analysis, the even the erradic behavior of the unofficial opcodes starts looking surprisingly regular.
True, there are some that are unpredictable, such as 8B (ANE #imm), which is a synthesis of 89 STA #imm + 82 STX #imm, both functionally NOPs, but combined does weird things.
--------
To answer your concern, what matters is not what people call these opcodes. What matters is what they do.
Last edited by Bisqwit on Wed Mar 21, 2012 2:17 pm, edited 6 times in total.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: CPU-test request: cpu_dummy_writes

Post by tepples »

byuu wrote:I'd appreciate if there were a document that explains 100% of all behaviors (especially the ones that have "undefined behavior", which is coded speak for "I don't understand the actual interactions with past CPU actions/states")
The behaviors behind instructions involving $11 or $EE is actually analog, as a page on visual6502 explains. The CPU is trying to read and write the special bus at the same time, where some of it is direct and the other goes through ADC's decimal mode circuitry. The timing on this differs from one lot of 6502s to another and possibly even by temperature and RFI. It also involves the RDY input, meaning the behavior might change around a DMC DMA request (which pulls RDY low for a few cycles).
and gives exactly one community-preferred name for each mnemonic.
For the stable opcodes, I'd pick whatever mnemonic ca65's 6502X mode uses. My article about practical uses of unofficial opcodes had mostly followed ca65's mnemonics, and today I changed SBX to AXS to conform fully.
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Post by Zepper »

Is there something else for CPU emulation that could be tested? Such test was important here.
crudelios
Posts: 24
Joined: Thu Feb 09, 2012 3:54 am

Post by crudelios »

Nice to see someone other than blargg creating some tests!

My emu (jaNES) passed both official and unnofficial tests :)
Bisqwit
Posts: 249
Joined: Fri Oct 14, 2011 1:09 am

Post by Bisqwit »

I created another version of this test, now using PPU memory for the double write testing. It also verifies whether the emulator emulates PPU's open bus features properly. I also updated the OAM version to do more exhaustive testing on the OAM.

I have updated the first post of the thread with test results.

The test(s) can be downloaded here: http://bisqwit.iki.fi/kala/cpu_dummy_writes.zip

I would appreciate it if someone could run this test on the real NES a few times and report carefully the results, preferably before people start using this test and before it becomes difficult to release updates to it.

Screenshots from Nestopia (1.40), BSNES (v087), Nintendulator (0.970), FCEUX (2.1.4a), RockNES (5.07), puNES (0.56), nesemu1, iNES 3.6, Famtasia, Nesticle ;-)
Image Image Image Image Image Image Image Image Image Image

Video capture from my emulator:
http://bisqwit.iki.fi/kala/snap/nesemu1 ... te_oam.avi , http://bisqwit.iki.fi/kala/snap/nesemu1 ... ppumem.avi
Last edited by Bisqwit on Sat Mar 24, 2012 7:32 pm, edited 4 times in total.
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Post by Zepper »

It's time for implementing such unofficial opcodes. In fact, they are/were used to identify bad dumps and/or bugs in the emulation.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

I ran the OAM test with my PowerPak in NovaYoshi's NES. Most of the time I got this:
"n FAILED READS; SPRDATA does not appear to be reliable RAM at all"
The most common values of n were 126 and 895, depending on PPU-CPU alignment at reset, but 6, 134, and 902 were seen more than once.

Occasionally I got this:
"INC abs should do double-write"

So I long-reset and did the PPU tests. Each line of open bus behavior was "0- 0- 00 0- 0- 0- 0- 00". Then it got glitchy as heck for a couple seconds. And then randomly depending on PPU-CPU alignment at reset, it would either pass or display a blank box below the "1B3B5B7BDBFB". Once I got it to say "Pas " with four spaces after "Pas". On the most part the result of a passed run looked like Nintendulator, except for orange horizontal lines running through most blank areas, and random characters scattered about the screen.

I don't have my video recording equipment with me at the moment. In a couple days, should I make videos covering about a dozen resets of each test?
Bisqwit
Posts: 249
Joined: Fri Oct 14, 2011 1:09 am

Post by Bisqwit »

tepples wrote:I don't have my video recording equipment with me at the moment. In a couple days, should I make videos covering about a dozen resets of each test?
Thank you, but no, I think that description was verbose enough. Weird!
The jumpy image was to be expected, it does that on emulators too. (Though on emulators, it jumps during the open bus testing, not during the opcode testing.)
I tried to minimize the times the screen is blanked, just in case an emulator crashes in the middle of the test and leaves nothing to be seen on the screen, but I may have cut it too close at times. I did include a vblank wait at relevant times, or so I think. I'll see if I can fix it more.
I'm not sure what to make of the "blank box". There was a blank box on the next screen in some earlier version, but the scrolling position is set with a vblank wait before the pass texts are output.

I will leave the OAM test like that though; I don't think I can improve it in any way except by adding more opcodes into the test or by making the opcode tests not depend on earlier tests, as is in the PPU memory version.

Good to know that the assumptions I made in making the open bus test were right.
Post Reply