CPU-test: cpu_dummy_writes [DONE]
Moderator: Moderators
CPU-test: cpu_dummy_writes [DONE]
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
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.
*ahem* RockNES 5.07 gives error #10, "INC abs should do double-write".
Also... the timing diagram source.
Looks like the document is OK. I can't believe I bypassed it!
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
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
Zepper
RockNES author
RockNES author
Might one be able to exploit open bus to get the right value into INC $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.
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
I'd make this, but my PowerPak's open bus behavior can't be trusted.
Re: CPU-test request: cpu_dummy_writes
This is expected, see this thread.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.
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...
EDIT: I had to modify ASL, ROL, LSR and ROR w/ implicit addressing mode to take 2 cycles again. Everything's fine.
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
Last edited by Zepper on Wed Mar 21, 2012 9:48 am, edited 1 time in total.
Zepper
RockNES author
RockNES author
Re: CPU-test request: cpu_dummy_writes
The documentation on unofficial opcodes is piss-poor, and wildly inconsistent. Every unofficial opcode also has 5+ mnemonics, which doesn't help matters any.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)
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.
Re: unofficial opcodes
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:
ORA zx:
SLO zx:
--------
Similarly, opcode 8D, STA abs:
Opcode 8E, STX abs:
Opcode 8F, SAX abs:
--------
For a more complicated (and at first glance, irregular) example, consider the differences between these two official opcodes:
Opcode 95, STA zx (official):
Opcode 96, STX zy (official):
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):
Opcode 9E, STX absy (unofficial, alternative names: SHX/SXA/XAS)
Then the perfect synthesis opcode of these two becomes:
Opcode 9F, SAX absx (unofficial, alternative names: SHA/AHA/AHX)
--------
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.
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)'),
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)'),
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'),
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'),
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'),
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'),
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'),
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'),
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.
Re: CPU-test request: cpu_dummy_writes
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).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")
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.and gives exactly one community-preferred name for each mnemonic.
Is there something else for CPU emulation that could be tested? Such test was important here.
Zepper
RockNES author
RockNES author
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 ;-)
Video capture from my emulator:
http://bisqwit.iki.fi/kala/snap/nesemu1 ... te_oam.avi , http://bisqwit.iki.fi/kala/snap/nesemu1 ... ppumem.avi
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 ;-)
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.
It's time for implementing such unofficial opcodes. In fact, they are/were used to identify bad dumps and/or bugs in the emulation.
Zepper
RockNES author
RockNES author
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?
"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?
Thank you, but no, I think that description was verbose enough. Weird!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?
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.