RESOLVED: I have found the error for what was going on below. There was one instruction (STA 0x91) where I was adding the Y register value only to the low byte (bLow + Y) instead of adding Y to the adjusted value ( ( bLow + bHigh << 8 ) + Y). Whoops!! That problem has been haunting me for months and months. Thanks to those who provided input! I can now play SMB!!
-------------------------------------------------------------------------------
I have had the issue shown in this video for quite some time:
http://www.youtube.com/watch?v=pgJXhy81Nb4
Several people have commented that I have a problem in my CPU code, so I was wondering if someone wouldn't mind looking over it to see if there were any obvious issues. It's not perfect and some of the comments in it are old, but I am able to pass all the nestest, nestress and the 'official_only.nes' CPU tests. It's still a work in progress.
The screen to the right in the video is the four name tables.
I'm a hobbyist programmer, so go easy on me.
Here is the CPU.cpp source file:
(link removed)
Thanks!
Add trace logging, then compare with another emulator's trace. Then see where they differ. I think it's probably the most painless way to diagnose such a problem.
Just register content tracing should be good enough (PC, A, X, Y, S, Flags). But if you're a fan of using diff software (like WinMerge), you'd need to make your trace match the format of another emulator's trace (or use a bunch of regular expressions to reformat the other emulator's trace).
Dwedit wrote:
Add trace logging, then compare with another emulator's trace. Then see where they differ. I think it's probably the most painless way to diagnose such a problem.
Just register content tracing should be good enough (PC, A, X, Y, S, Flags). But if you're a fan of using diff software (like WinMerge), you'd need to make your trace match the format of another emulator's trace (or use a bunch of regular expressions to reformat the other emulator's trace).
I actually already have some logging in my emu that outputs to a file. I just removed it (along with some other code) from the .cpp file I linked so others didn't have to sift through the non-cpu code.
Is there a particular emulator that outputs a nice log to a file?
My emu currently logs like this:
Code:
C5F5 A2 A:00 X:00 Y:00 P:24 SP:FD
But it's very easy to add/change/remove to make a diff easier. I usually just use Notepad++ with the Compare plugin.
Thanks
--------
FCEUX does it, it's called the "Trace Logger", under the debug menu.
FCEUX's trace logger lines look like this:
Code:
$F3B7:60 RTS A:00 X:00 Y:5E S:EB P:nvUbdiZc
$EACE:A5 3F LDA $003F = #$00 A:00 X:00 Y:5E S:ED P:nvUbdiZc
$EAD0:F0 F9 BEQ $EACB A:00 X:00 Y:5E S:ED P:nvUbdiZc
$EACB:20 97 F3 JSR $F397 A:00 X:00 Y:5E S:ED P:nvUbdiZc
$F397:A5 72 LDA $0072 = #$00 A:00 X:00 Y:5E S:EB P:nvUbdiZc
$F399:29 02 AND #$02 A:00 X:00 Y:5E S:EB P:nvUbdiZc
So some regular expressions, or a simple program could convert the logs to make them look the same.
IRQ/NMI timing might make logs look slightly different.
Also, it helps to turn on NTFS compression for the directory you save logs to, since logs quickly grow to 150+MB in size.
Dwedit wrote:
Also, it helps to turn on NTFS compression for the directory you save logs to, since logs quickly grow to 150+MB in size.
That or use zlib to compress logs in .gz format, which will work even on file systems other than NTFS. If you have PNG screenshot support, you're already linking in zlib anyway.
Did you try the CPU test roms to see if those report errors? Do other games experience weird problems or do they run ok?
MottZilla wrote:
Did you try the CPU test roms to see if those report errors? Do other games experience weird problems or do they run ok?
I tried these as I mentioned and maybe some other one-off tests:
"nestest, nestress and the 'official_only.nes'" <- They all pass
There are quite a few games that work fine and others that don't. But many of the others seems like minor timing issues. And none of the weirdness is that bad.
Dwedit wrote:
FCEUX does it, it's called the "Trace Logger", under the debug menu.
I changed my formatting to look similar *enough* to that of FCEUX. As I follow the logging in SMB, this is what I get:
Code:
$8000:78 SEI A:00 X:00 Y:00 S:00 P:nvubdIzc
$8001:D8 CLD A:00 X:00 Y:00 S:00 P:nvubdIzc
$8002:A9 10 LDA #$10 A:00 X:00 Y:00 S:00 P:nvubdIzc
$8004:8D 00 20 STA $2000 = #$00 A:10 X:00 Y:00 S:00 P:nvubdIzc
$8007:A2 FF LDX #$FF A:10 X:00 Y:00 S:00 P:nvubdIzc
$8009:9A TXS A:10 X:FF Y:00 S:00 P:NvubdIzc
$800A:AD 02 20 LDA $2002 = #$10 A:10 X:FF Y:00 S:FF P:NvubdIzc
$800D:10 FB BPL $800A A:10 X:FF Y:00 S:FF P:nvubdIzc
$800A:AD 02 20 LDA $2002 = #$10 A:10 X:FF Y:00 S:FF P:nvubdIzc
~25000 lines of waiting for VBLANK
$800F:AD 02 20 LDA $2002 = #$90 A:10 X:FF Y:00 S:FF P:nvubdIzc
$8012:10 FB BPL $800F A:90 X:FF Y:00 S:FF P:NvubdIzc
$8014:A0 FE LDY #$FE A:90 X:FF Y:00 S:FF P:NvubdIzc
$8016:A2 05 LDX #$05 A:90 X:FF Y:FE S:FF P:NvubdIzc
$8018:BD D7 07 LDA $07D7,X @ $07DC = #$FF A:90 X:05 Y:FE S:FF P:nvubdIzc <-- I read 0x00 here instead of 0xFF...
$801B:C9 0A CMP #$0A A:FF X:05 Y:FE S:FF P:NvubdIzc
$801D:B0 0C BCS $802B A:FF X:05 Y:FE S:FF P:NvubdIzC
$802B:20 CC 90 JSR $90CC A:FF X:05 Y:FE S:FF P:NvubdIzC
$90CC:A2 07 LDX #$07 A:FF X:05 Y:FE S:FD P:NvubdIzC
Why is there data at address $07DC? I was under the impression that when I read a rom, that the PRG contents are written to 0x8000-0xFFFF and the CHR contents are written into 0x0000-0x1FFF of VRAM (this is oversimplified, I know). Aside from that, I thought there was no data that was placed into memory. The readout from the CPU RAM of FCEUX has (0x00 0x00 0x00 0x00 0xFF 0xFF 0xFF 0xFF) repeating from address 0x0000-0x1FFF.
In memory, the PRG-ROM is read from 8000-FFFF. This is just reading from RAM. And what it's reading from in that is just RAM. RAM is data exactly like ROM except the program its self changes it.
3gengames wrote:
In memory, the PRG-ROM is read from 8000-FFFF. This is just reading from RAM. And what it's reading from in that is just RAM. RAM is data exactly like ROM except the program its self changes it.
I expect that, but there is nowhere before that point that the program has changed the data at that address. What I pasted is the entire contents of the program up until that point (with the exception of waiting for VBlank).
It appears that FCEXU initializes 0x0000 - 0x0800 to a repeating value of 00, 00, 00, 00, FF, FF, FF, FF. This really shouldn't matter however as I can't think of any games that rely on uninitialized RAM values and Super Mario Bros. should certainly work with everything initialized to 00.
They probably use that as a "warm start" or cold start" flag or something. Doesn't have to be written to during that beginning of the program, you ca always reset it. But considering no matter what that value is the same thing always happens, I don't know what the hell it's doing.
SMB1 checks if the high score consists of valid digits as its way of detecting a warm start. If it's a warm start, it keeps the high score, second quest flag, and continue world (A+START world).
But I'm far more interested in a trace log starting with an A button press from the ground, than from bootup. Sure, exact instructions executed will vary depending on time you started (music, etc...).
Or maybe just replay the demo, that means you can get something consistent.
Dwedit wrote:
SMB1 checks if the high score consists of valid digits as its way of detecting a warm start. If it's a warm start, it keeps the high score, second quest flag, and continue world (A+START world).
But I'm far more interested in a trace log starting with an A button press from the ground, than from bootup. Sure, exact instructions executed will vary depending on time you started (music, etc...).
Or maybe just replay the demo, that means you can get something consistent.
I took a break from logs to look at the RAM dumps from when the level starts. It is obvious looking at the dumps that the objects (blocks, pipes, etc.) are being written to a different place in RAM. They are off by 0x100.
The items in the blue squares are the bricks and question blocks at the beginning of the level. In both cases, I dumped the memory right after starting the game without moving.
Now I just need to figure out why they are off?!
Maybe there's a typo in you're code where it adds ram offsets or something.
3gengames wrote:
Maybe there's a typo in you're code where it adds ram offsets or something.
It's just odd because the other data is in the correct place. Like what is seen at 0x5B0.
Also, my reads and writes are very straight forward.
Code:
if (addr < 0x2000)
{
// Write
memCPU[addr & 0x07FF] = data;
}
if (addr < 0x2000)
{
// Read
return memCPU[addr & 0x07FF];
}
Not sure what is happening. But this gives me more to chew on.
-----
EDIT: This has been RESOLVED. See first post.
At first, this is what I thought: SMB1 decodes its maps to a 32x13-metatile sliding window. The left half is at $500-$5CF (which corresponds to PPU $2000-$23FF) and the right half at $5D0-$69F (which corresponds to PPU $2400-$27FF). It might decide which half to start at based on uninitialized memory; the results should be the same no matter which half it starts at. Why it uses $500 and $5D0 as opposed to $500 and $600, with the data between $5D0 and $5FF used for something else, is anybody's guess. It adds a cycle penalty to most accesses of the right side of the window.
But now that I've reread the hex editor screenshots and seen the working solution, I realize that they're off by $100, not $D0 as I originally thought when skimming the first posts.
tepples wrote:
At first, this is what I thought: SMB1 decodes its maps to a 32x13-metatile sliding window. The left half is at $500-$5CF (which corresponds to PPU $2000-$23FF) and the right half at $5D0-$69F (which corresponds to PPU $2400-$27FF). It might decide which half to start at based on uninitialized memory; the results should be the same no matter which half it starts at. Why it uses $500 and $5D0 as opposed to $500 and $600, with the data between $5D0 and $5FF used for something else, is anybody's guess. It adds a cycle penalty to most accesses of the right side of the window.
But now that I've reread the hex editor screenshots and seen the working solution, I realize that they're off by $100, not $D0 as I originally thought when skimming the first posts.
I can always count on someone here to know WAY too much about how a particular game works!
-----
Yeah, I could reproduce your bug in my emulator. It's "cool".
Anyway, you said that your emu gets a "Passed" in the CPU test ROM. Well, I get the error:
Code:
unknown opcode FF trapped at PC 0200!
Zepper wrote:
Yeah, I could reproduce your bug in my emulator. It's "cool".
Anyway, you said that your emu gets a "Passed" in the CPU test ROM. Well, I get the error:
Code:
unknown opcode FF trapped at PC 0200!
Which CPU test rom is that from?
tineras wrote:
Zepper wrote:
Yeah, I could reproduce your bug in my emulator. It's "cool".
Anyway, you said that your emu gets a "Passed" in the CPU test ROM. Well, I get the error:
Code:
unknown opcode FF trapped at PC 0200!
Which CPU test rom is that from?
That's probably from his log.
cpow wrote:
That's probably from his log.
Sometimes you make me feel a fool. Anyway, it's from
blargg_nes_cpu_test5.zip package. Quote from readme:
Quote:
NES CPU Tests
-------------
These test most of the instruction behavior fairly thoroughly, including unofficial instructions. Failing instructions are listed by their opcode and name. Serious errors in basic opcodes might cause massive erroneous errors.
cpu.nes Tests all instructions
official.nes Tests official only
Most instructions are tested by setting many combinations of input values for registers, flags, and memory, running the instruction under test, then updating a running checksum with the resulting values. After trying all interesting input combinations, the checksum is compared with the correct one (what a NES gives) to find if the instruction passed.
This approach makes it very easy to write the tests, since the instructions don't have to be each coded for separately; instead, only the different addressing modes need separate tests.