This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

CPX... Flags in relation to which value?

CPX... Flags in relation to which value?
by on (#42420)
I'm reading through the commodore programmers manual (http://nesdev.com/6502.txt) and it states that CPX (Compare Memory and Index X) changes the Negative, Zero, and Carry flag.

I'm a little confused how this works, espically with the carry flag (I understand the carry flag, but since nothing is being added/subtracted theres no overflow). Also, are the flags set being influenced by the value at the memory address or in X?


For example:

If X = 5 and the value at the memory address = 0:
Would no flag be set? Or is it the other way around and the Z flag would be set?

Can someone also provide an example of how the carry flag would even fall into all this? Thanks.

by on (#42421)
The carry flag is set if the value in X is greather or equal than the value in memory (ungisned), and clear if the value in X is smaller than the value in memory.

Think as if "memory" was substracted from X as if it was a SBC opcode (also it doesn't substract one more even if C was set prior to the compare), but the resuls is discared, only the flags are taken over
Re: CPX... Flags in relation to which value?
by on (#42442)
JohnPublic wrote:
I'm reading through the commodore programmers manual (http://nesdev.com/6502.txt) and it states that CPX (Compare Memory and Index X) changes the Negative, Zero, and Carry flag.

I'm a little confused how this works, espically with the carry flag (I understand the carry flag, but since nothing is being added/subtracted theres no overflow). Also, are the flags set being influenced by the value at the memory address or in X?


For example:

If X = 5 and the value at the memory address = 0:
Would no flag be set? Or is it the other way around and the Z flag would be set?

Can someone also provide an example of how the carry flag would even fall into all this? Thanks.


First off, the Z flag and C flag are independent of each other in the compare operation. Either one or the other, or both will be set on the result of the compare.

To compare something, you take the difference between A and B. So compare is a straight forward subtraction.

You can think of the C flag the same as with normal math:

Code:
 (1)6
   -9
 -----
    7

A carry would have been needed because 9 is larger than 6. In decimal math, you carry/borrow from the next place depending on the result. In the above example, the 'ones' place would have needed a 'borrow' from the 'tens' place. On the cpu, there is no negative number. Instead, it 'wraps'. $01-$ff=$02. $10-$11=$ff

X=9, memory=6. The result is 7. This result is thrown away and the flags are set/cleared accordingly. Which flags? The Z=0 was *cleared* because the result of the subtraction didn't end up being 0. The C flag was *set* (C=1) because a borrow would have been needed. If a borrow was needed, then you know the number in register X is larger than in memory(or immediate or ZP - whatever the operand is).

Code:
 ldx #$08
 cpx #$09
                ;c=0, z=0

 ldx #$08
 cpx #$09
               ;c=1, z=0

 ldx #$09
 cpx #$09
              ;c=1,z=1

See that last one? You would think that C would be cleared because 9 is not larger than 9, so no borrow would have been needed. The explanation for this is a little complicated and not really necessary to understand for you to use it. It' easier just to remember that the C flag is also set if A=B or A-B=0. Looking at an example with branching makes it easy to understand.


Code:
 ldx #$08
 cpx #$09     ;c=0,z=0
*bcc less_than
 beq equal
 bcs equal_or_greater_than

 
 ldx #$09
 cpx #$08    ;c=1, z=0
 bcc less_than
 beq equal
*bcs equal_or_greater_than


 ldx #$09
 cpx #$09    ;c=1,z=1
 bcc less_than
*beq equal
 bcs equal_or_greater_than

The * represents which branch is taken. If you don't know your conditional branchs, BCC = branch if carry is clear, BEQ = branch if zero is set, BCS = branch if carry is set. If the condition of a corresponding flag of a conditional branch is not met, the cpu 'falls' through to the next instruction.

edit: updated.

by on (#42443)
Bregalad wrote:
The carry flag is set if the value in X is greather or equal than the value in memory (ungisned), and clear if the value in X is smaller than the value in memory.

Think as if "memory" was substracted from X as if it was a SBC opcode (also it doesn't substract one more even if C was set prior to the compare), but the resuls is discared, only the flags are taken over


Consider the following:

Code:
loadpal:                ; this is a freaky loop
        lda tilepal, x  ; that gives 32 numbers
        sta $2007       ; to $2007, ending when
        inx             ; X is 32, meaning we
        cpx #32         ; are done.
        bne loadpal     ; if X isn't =32, goto "loadpal:" line.


bne will keep branching back to loadpal until the Z flag is 1. You stated that the carry flag is set when #32 = X. X will keep increasing until it equals 32. The carry flag however wont influence bne. How would it work in this case?

by on (#42444)
Quote:
Consider the following:

Code:
loadpal: ; this is a freaky loop
lda tilepal, x ; that gives 32 numbers
sta $2007 ; to $2007, ending when
inx ; X is 32, meaning we
cpx #32 ; are done.
bne loadpal ; if X isn't =32, goto "loadpal:" line.


bne will keep branching back to loadpal until the Z flag is 1. You stated that the carry flag is set when #32 = X. X will keep increasing until it equals 32. The carry flag however wont influence bne. How would it work in this case?


Since the BNE only looks at the Z flag, the C flag isn't going to effect anything in that code example. If you wanted to use the C flag instead, then:

Code:
 loadpal:               ; this is a freaky loop
        lda tilepal, x  ; that gives 32 numbers
        sta $2007       ; to $2007, ending when
        inx             ; X is 32, meaning we
        cpx #32         ; are done.
        bcc loadpal     ; if X isn't =32, goto "loadpal:" line.


Alternatively, you can

Code:
        ldx #$e0
 loadpal:                     ; this is a freaky loop
        lda tilepal-$e0, x    ; that gives 32 numbers
        sta $2007             ; to $2007, ending when
        inx                   ; X is 32, meaning we
        bne loadpal           ; if X isn't =32, goto "loadpal:" line.

by on (#42445)
Ok it makes perfect sense now, I had no idea that a CMP was really doing a subtraction the whole time. Thank you very much!

by on (#42455)
Quote:
X=8, memory=9. The result is 7. This result is thrown away and the flags are set/cleared accordingly. Which flags? The Z=0 was *cleared* because the result of the subtraction didn't end up being 0.


8-9 = 7? Huh?

I thought it would be -1?

Also, there is a contridiction:

Quote:
"The C flag was *set* (C=1) because a borrow would have been needed."

X=8, memory=9.
ldx #$08
cpx #$09
;c=0, z=0

by on (#42460)
Yes, definitely 8-9 can't equal 7 in all existing bases.

Here if you do something like that :
ldx #$08
cmp #$09

The 6502 will internally substract 9 from 8, and the result will be $ff, because it's a 8-bit CPU. So you know the Z flag is clear ($ff is not zero), the C flag is clear (the substraction has done an underflow), and the N flag is set (because $ff is negative). The result $ff is of course discarded, but the flag remains. For some reason I never use the N flag after a cmp/cpx/cpy instruction because it's often not usefull in practice, there is most certainly cases where it is usefull but I haven't found any.

by on (#42470)
Haha. I was pretty tired when I wrote that <_<

It was supposed to be
Code:
 (1)6
   -9
 -----
    7


The one's representing LSB and the tens MSB, and the idea of wrapping.

Quote:
The 6502 will internally substract 9 from 8, and the result will be $ff


Is correct. Don't forget the V flag.

by on (#42473)
CMP doesn't affect the V flag. Also, I think internally the 6502 never subtracts, and implements CMP by setting the carry and complementing (XORing with $FF) the operand before adding. SBC simply complements the operand before adding, which is why you must set the carry first, so that you get two's complement negation (complement then add one). This is why the carry is opposite of what you'd expect, and how it's done on other processors like the GB-Z80. So on them carry set after a CMP would mean the register is less than the operand, whereas it means greater or equal on the 6502.

by on (#42476)
Quote:
This is why the carry is opposite of what you'd expect, and how it's done on other processors like the GB-Z80.

In fact I found it much more logical and convenient to do it the way the 6502 does it. A carry means there is an overflow in the addition. No carry means there is an underflow in the substraction. This perfectly make sense. Also I often write things like that :
Code:
  lda Value
  sec
  sbc #$05
  sta Value
  bcc _ok
  sbc #$10
_ok
  sta Value

This code will substract 5 to value, but the result never goes into the $f0-$ff range. This is very usefull to deal with vertical scrolling on the NES for example, but there is many other situations too where this is usefull. If the carry were handled the backwards way like on some other processors, you would end up with much less optimised code.

by on (#42533)
Here's some GB-Z80 code that does the same, where carry is handled the opposite way. No less optimized:
Code:
    ld  a,(Value)   ; a = byte at Value
    sub $05         ; a = a - $05
    jr  c,_ok       ; if carry, jump to _ok
    sub $10         ; a = a - $10
_ok ld  (Value),a   ; byte at Value = a

If the 6502 did it the same way, you'd have
Code:
  lda Value
  clc
  sbc #$05
  bcs _ok
  sbc #$10
_ok
  sta Value

Also no less optimal. BTW, your example doesn't make sense to me:
Code:
Input: F8 F9 FA FB FC FD FE FF 00 01 02 03 04 05 06 07 08 09 0A
Output:E3 E4 E5 E6 E7 E8 E9 EA FB FC FD FE FF F0 F1 F2 F3 F4 F5

Bregalad wrote:
Quote:
This is why the carry is opposite of what you'd expect, and how it's done on other processors like the GB-Z80.

In fact I found it much more logical and convenient to do it the way the 6502 does it. A carry means there is an overflow in the addition. No carry means there is an underflow in the substraction. This perfectly make sense.

The opposite makes more sense to me, since I think in terms of the addition/subtraction being performed using 9 bits, with the carry being the 9th bit of the result. The left shows normal addition, the middle normal subtraction. But the 6502 does the subtraction as on the right, using addition.
Code:
                                             000000001 ($001)
  011111111 ($0FF)     000000001 ($001)    + 000000001 ($001)
+ 000000010 ($002)   - 000000011 ($003)      011111100 ($0FC)
------------------   ------------------ -> ------------------
  100000001 ($101)     111111110 ($1FE)      011111110 ($0FE)
thus, carry is set   thus, carry is set    thus, carry is clear
and result is $01     and result is $FE     and result is $FE


EDIT: "Output:" above was not what the code actually generated. Corrected.

by on (#42540)
You are right I completely screwed up in my example. The actual thing should look more like that :
Code:
  lda Value
  sec
  sbc #$02
  cmp #$f0
  bcc _ok
  sbc #$10
_ok
  sta Value

There, it removes 2 to value and ensure the result is never in $f0-$ff ranges, which is usefull for vertical scrolling on the NES for example. Tt really show of how usefull it is to have the carry to be handled how the 6502 does. If it did the other way I'd have to do something like :

Code:
  lda Value
  clc
  sbc #$05
  cmp #$f0
  bcc _ok
  clc
  sbc #$10
_ok
  sta Value

OK nothing too terrible, but the added clc removes all the elegancy of the code.
Also when doing an inverse substraction where for some reason you have the value being substracted in A and don't want to use temporary storage :

Code:
  jsr DoSomeCalculation
  eor #$ff
  sec
  adc Value
  sta Value       ;Value = Value-SomeCalculation

Here you see that in fact it's a substraction rather than an addition thanks to the "sec" opcode. It is quite elegant, but again it would be the same if it were handled the Intel/Zilog way.

by on (#42543)
Bregalad wrote:
[It] really show of how usefull it is to have the carry to be handled how the 6502 does. If it did the other way I'd have to do something like :
[code for hypothetical 6502 whose carry acts the opposite for SBC/CMP]
Code:
  lda Value
  clc
  sbc #$05
  cmp #$f0
  bcc _ok
  clc
  sbc #$10
_ok
  sta Value

If it were reverse, carry would be set when the value was less than $F0, so you'd do BCS _ok, and then no need to clear it. So it wouldn't require any extra instructions. However, even if that weren't the case, if you knew carry was always going to be set before the SBC #n you'd simply adjust the value you were subtracting. For example (on a real 6502 now), if you wanted the values $F0-$FF unchanged, and the others to have $10 subtracted from them, you could do
Code:
  lda Value
  cmp #$f0
  bcs greater_or_equal
  sbc #$10-1 ; instead of SEC + SBC $10
  sta Value
greater_or_equal: