Progress, of a sort. Flushed with success after cracking the intricacies of the ADC and SBC instructions, I decided to conduct a small experiment - using my Sharp6502 monitor alongside the VICE monitor to single-step through the VIC-20 startup sequence, and see how well they matched-up. Obviously, I'd be looking for a perfect score here, as any differences would indicate something wrong with my code - and although VICE isn't a perfect emulation (there are some very specific VIC-related issues it doesn't handle) it's generally accepted to be pretty reliable when it comes to 6502 instruction execution, which means that if my CPU core does something different, it's almost certainly my code that's wrong and not VICE. With all my 6502-based machines in storage at the moment, I can't do a 'real' hardware comparison, so VICE is a good second-best.
So, on Sunday evening I got both VICE and Sharp6502 sitting alongside each other on-screen, and started them both at the cold-start vector ($FD22) in the VIC-20 ROM. And we got precisely eight instructions into the code before I noticed something - the 'CMP Absolute,X' opcode at $FD44 set the Negative flag (also known as the 'sign' flag) differently in my code. VICE said N was set, and Sharp6502 said it was clear. The other two flags affected by CMP (Carry and Zero) were in agreement, but N was most definitely not. So I stopped the test, and had a look at the CMP implementation, which was one of the first I'd done ages ago - if you remember, I was implementing instructions as the ROM presented them, so this was a bit of code that dated back almost to the first version of the CPU core.
Well, I looked long and hard at that code, and couldn't for the life of me see how it was going wrong. In fact, I even started to doubt VICE at one point, because the preceding instruction is an LDA whose operand is #$CD, which of course sets the N flag, and I began to wonder if VICE was somehow forgetting to reset N when it did the CMP afterwards. I pulled-up all the documents I could find that talk about the CMP instruction, and it looked like I was doing exactly the right thing. An example of what various sources say about CMP is:
Y'see, it's like this. CMP is actually a pretty powerful instruction, because it does both magnitude and equality tests in one go; that is, on the basis of a single CMP you can tell whether the Accumulator contains a value the same as, bigger, or smaller than the value you're comparing it with. That's pretty clever, and is also where the devious little 'gotcha' lives that I'd not been cognisant of before this. The equality test sets (or clears) the Z flag, so that if the difference between the Accumulator and the test value is zero (i.e. they are equal) the flag is on. The 'bigger than' test sets the Carry flag, by just comparing the two values and setting the flag if the Accumulator is larger (or equal to, but that distinction is clarified by the Z flag).
But the N flag, which is the 'smaller than' test result indicator, is a tricksy little fiend that depends upon the natural ability of an 8-bit register to 'wrap around' when the value drops below zero. The key to this is recognising that little annotation in the instruction description for what it is, and what it means: [Z,C,N = A-M] tells us that the flags are set as a consequence of subtracting the memory (test) value from the Accumulator. And THAT means that although the Accumulator is left unchanged by CMP, it is actually doing an unsigned subtraction in the ALU to determine the result, and that means that subtracting a value of 2 from a 1 in the Accumulator, for example, gives us a result of 254 and not -1.
My result logic used an ordinary INT variable to do the calculation, which (using the 1-2 example just now) gave me -1 as the result, and of course Bit 7 wasn't set. Using a temporary 8-bit register as the result field, however, means that the wrap-around occurs and I get 254 as the answer, and Bit 7 IS set. Here's the code for 'CMP Absolute,X' - all the other addressing modes, as well as CPX and CPY, follow the same pattern:
So, on Sunday evening I got both VICE and Sharp6502 sitting alongside each other on-screen, and started them both at the cold-start vector ($FD22) in the VIC-20 ROM. And we got precisely eight instructions into the code before I noticed something - the 'CMP Absolute,X' opcode at $FD44 set the Negative flag (also known as the 'sign' flag) differently in my code. VICE said N was set, and Sharp6502 said it was clear. The other two flags affected by CMP (Carry and Zero) were in agreement, but N was most definitely not. So I stopped the test, and had a look at the CMP implementation, which was one of the first I'd done ages ago - if you remember, I was implementing instructions as the ROM presented them, so this was a bit of code that dated back almost to the first version of the CPU core.
Well, I looked long and hard at that code, and couldn't for the life of me see how it was going wrong. In fact, I even started to doubt VICE at one point, because the preceding instruction is an LDA whose operand is #$CD, which of course sets the N flag, and I began to wonder if VICE was somehow forgetting to reset N when it did the CMP afterwards. I pulled-up all the documents I could find that talk about the CMP instruction, and it looked like I was doing exactly the right thing. An example of what various sources say about CMP is:
CMP - Compare [Z,C,N = A-M]
This instruction compares the contents of the accumulator with another value
and sets the zero and carry flags as appropriate. Processor Status after use:
C Carry Flag Set if A >= M
Z Zero Flag Set if A = M
I Interrupt Disable Not affected
D Decimal Mode Flag Not affected
B Break Command Not affected
V Overflow Flag Not affected
N Negative Flag Set if bit 7 of the result is set
Seems pretty straightforward, right? What could possibly go wrong with that? Well, after 36 hours of painful research, experimentation, and banging of the head on the desk, I can tell you exactly what can go wrong with it.Y'see, it's like this. CMP is actually a pretty powerful instruction, because it does both magnitude and equality tests in one go; that is, on the basis of a single CMP you can tell whether the Accumulator contains a value the same as, bigger, or smaller than the value you're comparing it with. That's pretty clever, and is also where the devious little 'gotcha' lives that I'd not been cognisant of before this. The equality test sets (or clears) the Z flag, so that if the difference between the Accumulator and the test value is zero (i.e. they are equal) the flag is on. The 'bigger than' test sets the Carry flag, by just comparing the two values and setting the flag if the Accumulator is larger (or equal to, but that distinction is clarified by the Z flag).
But the N flag, which is the 'smaller than' test result indicator, is a tricksy little fiend that depends upon the natural ability of an 8-bit register to 'wrap around' when the value drops below zero. The key to this is recognising that little annotation in the instruction description for what it is, and what it means: [Z,C,N = A-M] tells us that the flags are set as a consequence of subtracting the memory (test) value from the Accumulator. And THAT means that although the Accumulator is left unchanged by CMP, it is actually doing an unsigned subtraction in the ALU to determine the result, and that means that subtracting a value of 2 from a 1 in the Accumulator, for example, gives us a result of 254 and not -1.
My result logic used an ordinary INT variable to do the calculation, which (using the 1-2 example just now) gave me -1 as the result, and of course Bit 7 wasn't set. Using a temporary 8-bit register as the result field, however, means that the wrap-around occurs and I get 254 as the answer, and Bit 7 IS set. Here's the code for 'CMP Absolute,X' - all the other addressing modes, as well as CPX and CPY, follow the same pattern:
case 221: // CMP Absolute,X
_tempResult = _mem[_mem[-++_PC.Contents] + _XR.Contents];
_TR8.Contents = _AC.Contents - _tempResult;
_SR[_BIT0_SR_CARRY] = _AC.Contents >= _tempResult ? 1 : 0;
_SR[_BIT1_SR_ZERO] = _TR8.Contents == 0 ? 1 : 0;
_SR[_BIT7_SR_NEGATIVE] = _TR8[_BIT7_SR_NEGATIVE];
_PC.Contents++;
break;
We put the test value into _tempResult because it's used twice, and I don't want to hit MemoryMap more than once. You can see that we set Carry by doing a simple magnitude test between the Accumulator and the test value. Having subtracted the test value from the Accumulator and stored the result in a temporary 8-bit register (_TR8) we then check to see if the answer was zero, and set Z accordingly. Finally, we simply set N to whatever Bit 7 of the temporary register contains, as it will have wrapped-around if the result dropped below zero.
To close, here's a snippet of text cribbed from www.6502.orgs' tutorial on the CMP functionality of the CPU - it turned out to be the definitive and most helpful document I could find on this subject, and of course it's well-worth a mooch around the rest of the site for more information on all things 6502. Enjoy!
To close, here's a snippet of text cribbed from www.6502.orgs' tutorial on the CMP functionality of the CPU - it turned out to be the definitive and most helpful document I could find on this subject, and of course it's well-worth a mooch around the rest of the site for more information on all things 6502. Enjoy!
The N flag contains most significant bit of the of the subtraction result. This is only occasionally useful. However, it is NOT the signed comparison result, as is sometimes claimed, as the following examples illustrate:
After:
LDA #$01 ; 1 (signed), 1 (unsigned)A = $01, C = 0, N = 0 (the subtraction result is $01 - $FF = $02), and Z = 0. The comparison results are:
CMP #$FF ; -1 (signed), 255 (unsigned)
- Equality comparison: false, since $01 <> $FF
- Signed comparison: 1 >= -1
- Unsigned comparison: 1 < 255
LDA #$7F ; 127 (signed), 127 (unsigned)A = $7F, C = 0, N = 1 (the subtraction result is $7F - $80 = $FF), and Z = 0. The comparison results are:
CMP #$80 ; -128 (signed), 128 (unsigned)
- Equality comparison: false, since $7F <> $80
- Signed comparison: 127 >= -128
- Unsigned comparison: 127 < 128

0 comments:
Post a Comment