Branches and wrap-arounds problem?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Branches and wrap-arounds problem?
by on (#106409)
Hey guys, I currently have my 6502 emulator passing all the nestest tests without any errors.

However, when I was fixing bugs I came across a problem with one of the BNE instructions in nestest. I figured out what was happening: When I was "jumping" I was adding the immediate directly to the program counter (PC += immediateByte).

I realized that it was wrong because the highest significant bit would wrap-around, so I fixed the bug this way:

Code:
this.PC = (UInt16)((this.PC & 0xFF00) | ((this.PC + immediateByte) & 0xFF));


And after that it worked all right... So I replicated that line in all the other Branches and removed the old (PC+=immediateByte) code.

But when I do this to all the other branches, nestest starts failing. :/ So I kept it only at the BNE. Today I loaded another NES ROM and it was also messing up with the jumps, so I also replaced the BPL to do the wrap-around.

Now, obvisouly there is something REALLY wrong here. :/ Do you guys have any ideas? Am I missing something about the branch instructions? Oh by the way, when I say "PC" I mean the PC at the beggining of the instruction.

Thank you guys!
Re: Branches and wrap-arounds problem?
by on (#106413)
I'm surprised your emulator passed all the CPU tests, as I'm sure there's one that catches this bug. You should simply be adding the operand byte interpreted as a signed 8-bit value. Since a (two's complement) byte simply has a negative weight for the top bit (-128), you can portably convert an unsigned to signed with (n^0x80)-0x80.
Re: Branches and wrap-arounds problem?
by on (#106414)
For a taken branch, you need to compute both the address that you'd get from 16-bit addition to the sign-extended value and the address that you'd get from 8-bit wraparound. If the two addresses match, the branch is 3 cycles; otherwise, it's 4 cycles, with one of the cycles being an unused fetch from the 8-bit wraparound address. (Not taken branches are always 2 cycles.) Either way, execution resumes at the address resulting from 16-bit addition.
Re: Branches and wrap-arounds problem?
by on (#106416)
Thanks a lot guys!!!! :) It works fine now!

It broke up here:

Code:
C729  CA        DEX                             A:CB X:05 Y:4F P:6D SP:F1 CYC:276 SL:108
C72A  D0 E0     BNE $C70C                       A:CB X:04 Y:4F P:6D SP:F1 CYC:282 SL:108
C70C  68        PLA                             A:CB X:04 Y:4F P:6D SP:F1 CYC:291 SL:108


But somehow... my wrap-around solution "fixed" the issue and passed the test. Still can't really understand how, but ok...
Re: Branches and wrap-arounds problem?
by on (#106427)
If you were branching to the wrong place sometimes, it would have been easy for those wrong branches to produce no noticeable effects even though they were messing something up. At the very least, they were probably skipping some tests.
Re: Branches and wrap-arounds problem?
by on (#106441)
Yes, but somehow, that didn't happen and it would jump to the right line... which is odd, probably just "coincidence".

You see, I created my own "test function". I made a function to parse nestest.log that generates a similar file containing only the registers in each line (removing scanlines, disasm, etc), so I compare nestest log line by line with the output of my cpu. When the strings are different, I output them both, and the line number in nestest.log. I could reach the end, even with that bug.

Still no PPU, though. Also haven't tested the instructions timing yet.

Thanks again! :)
Re: Branches and wrap-arounds problem?
by on (#106447)
So at each line you compared program counter and AXYPS. That's similar to what I did. When you removed scanlines, did you keep the CYC (which refers to dots, where 3 dots = 1 CPU cycle)?
Re: Branches and wrap-arounds problem?
by on (#106450)
Yep, I removed CYC. I know I have some bugs in timing though. Actually I'm working at it right now.

By the way, I still don't know much about the PPU - when you say "dots", what do you mean exacly in terms of PPU functionality?

Thanks!
Re: Branches and wrap-arounds problem?
by on (#106451)
Assuming Famicom, NTSC NES, or PlayChoice:

First see blargg's post about clocks, pixels, and cycles. The PPU produces a new pixel, or dot, every four master clocks. One CPU cycle is twelve master clocks. CYC in the nestest log counts dots. So every time CYC increases by three, the CPU performs one cycle of work.
Re: Branches and wrap-arounds problem?
by on (#106457)
Thank you very much! :) I think I got the cycles working great now. I'll check that link later today. Now heading off to a "pleasant" exam in college.