6502 Carry Flag

Basic and Machine Language

Moderator: Moderators

User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

6502 Carry Flag

Post by chysn »

Like everyone else, I've got quite a bit of social distancing time these days, so I decided to spend some of it studying aspects of 6502 programming that I've always ignored.

Subject number two: The Carry Flag!

Unlike my last subject, Decimal mode, which you can safely pretend doesn't exist and still write functional programs, the Carry Flag is ubiquitous. I spent my younger days of VIC-20 programming treating the Carry Flag in a pretty cavalier manner. If my ADC results weren't what I expected, I'd work around it somehow. I'd use scattershot methods to determine whether to test the Carry Flag for >= or <= operations. In my adult incarnation as a 6502 programmer, I resolved to specifically study the Carry Flag, and to write code that puts the theory into practice.

I can't make an exhaustive post about the Carry Flag here. But there are a few important sub-topics when studying the Carry Flag.

First, know which instructions affect the Carry Flag. These fall into a few categories:

(1) Instructions that affect the Carry Flag explicitly (SEC, CLC, PLP, RTI)
(2) Instructions that shift or rotate bits (ROR, ROL, LSR, ASL)
(3) Accumulator arithmetic instructions (ADC, SBC)
(4) Comparison instructions (CMP, CPX, CPY)

As a corollary, it's also important to know what instructions do not affect the Carry Flag. The increment and decrement instructions (INC, DEY, INX, etc.) do not affect Carry, which can be unexpected. Likewise, the load instructions leave the Carry Flag alone. All of these can be tested with the Zero Flag or Negative Flag.

Second--and this is a thing that I really used to miss--study the Carry Flag's use as an input. Aside from the obvious branch (BCC, BCS) instructions, the Carry flag does a lot of things in:

(1) ROR and ROL use Carry as an input and an output
(2) ADC and SBC use Carry as an input and an output

This dual-sided nature of the Carry Flag is a brilliant design, as it allows you to efficiently chain operations together without having to do intermediate arithmetic or explicit comparisons. For example, to rotate a 24-bit shift register left:

Code: Select all

LDA #$00     ; Used for ADC operand a few lines down
ASL REG_L
ROL REG_M
ROL REG_H
ADC REG_L
STA REG_L
Here we take advantage of the Carry Flag being set by ASL, used as an input by both ROL and ADC, possibly adding one to the addition with ADC to set bit 0 of the 24-bit register. You can have a shift register as long as you like at the cost of two or three additional bytes of code per eight bits!

Third, understand how the Carry Flag is set during comparisons. For me, it was helpful to first memorize and then internalize this:

Register < Operand: Z=0, C=0
Register == Operand: Z=1, C=1
Register > Operand: Z=0, C=1


Once you have this down, deriving whatever comparison you want (<=, for example) becomes more natural.

Fourth, understand how the Carry Flag affects arithmetic with ADC and SBC. The shift register example above shows that the Carry Flag will always be added to ADC results. The usual approach is to clear Carry (CLC) before addition. If you find that you're writing code that doesn't CLC before doing ADC, it should raise little alarm bells in your head. You might be handling the Carry Flag in a different manner, but you have to understand where its status comes from at any given point.

Likewise, the inverse of the Carry Flag is subtracted from SBC operations. So you'll usually see SEC before SBC. Again, alarm bells if you find yourself not doing that. A great demonstration of the use of the Carry Flag in a three-byte comparison operation is in the VIC-20 KERNAL's Jiffy counter routine. This is famously buggy code, but only because it uses the wrong number. The code itself is nice and instructive:

Code: Select all

LAB_F740
	SEC				; set carry for subtract
	LDA	LAB_A2		; get jiffy clock low byte
	SBC	#$01			; subtract $4F1A01 low byte
	LDA	LAB_A1		; get jiffy clock mid byte
	SBC	#$1A			; subtract $4F1A01 mid byte
	LDA	LAB_A0		; get jiffy clock high byte
	SBC	#$4F			; subtract $4F1A01 high byte
	BCC	LAB_F755		; branch if less than $4F1A01 jiffies
This is another demonstration of how the Carry Flag chains as an input and output across several operations, ending in a single conditional branch at the end.

One more thing I'll say about the Carry Flag. It makes a useful return value for a subroutine, in cases where you don't want to affect registers, or you've already used up other registers or flags. It's especially useful in cases where you need to make multiple comparisons to get your result. Consider some more code from VIC-20 KERNAL. This is the code that tests memory on start-up:

Code: Select all

LAB_FE91
	LDA	(LAB_C1),Y		; get existing RAM byte
	TAX				; copy to X
	LDA	#$55			; set first test byte
	STA	(LAB_C1),Y		; save to RAM
	CMP	(LAB_C1),Y		; compare with saved
	BNE	LAB_FEA4		; branch if fail

	ROR				; make byte $AA, carry is set here
	STA	(LAB_C1),Y		; save to RAM
	CMP	(LAB_C1),Y		; compare with saved
	BNE	LAB_FEA4		; branch if fail
	.byte	$A9			; makes next line LDA #$18

LAB_FEA4
	CLC				; flag test failed
	TXA				; get original byte back
	STA	(LAB_C1),Y		; restore original byte
	RTS
The code wants to non-destructively test all eight bits in the byte indexed by Y. The caller checks the Carry Flag upon return. But to test each bit, the subroutine needs to check two values, 01010101 and 10101010, so it would less efficient to rely on the Zero flag for the result. So the first comparison clears Carry if the memory is bad, and the second does the same thing.

(The ersatz LDA at FEA3 might be too clever for its own good, as BEQ $FEA5 would do the same job for one less byte, with Carry set by a successful comparison above. However... never expecting this test to fail (hopefully), their way does save a cycle per byte tested, so they could probably be credited with saving several seconds of your life. Edit well, no, I forgot that the fake LDA takes cycles. I’m going to throw up my hands in surrender on this one)
Last edited by chysn on Tue May 05, 2020 7:26 am, edited 1 time in total.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: 6502 Carry Flag

Post by Mike »

chysn wrote:([...] Edit well, no, I forgot that the fake LFA takes cycles. I’m going to throw up my hands in surrender on this one)
Anyway they could have saved much more cycles if they'd inlined that code snippet (it's only called once, from within RAMTAS) instead of wasting 12 cycles with JSR/RTS. ;)

CHRGET/CHRGOT is another prominent example, which cleverly utilizes the C flag on its output:

Code: Select all

.0073 CHRGET INC $7A
.0075        BNE $0079
.0077        INC $7B
.0079 CHRGOT LDA $EA60 ; <- self-modified address
.007C        CMP #$3A
.007E        BCS $008A
.0080        CMP #$20
.0082        BEQ $0073
.0084        SEC
.0085        SBC #$30
.0087        SEC
.0088        SBC #$D0
.008A        RTS
The somewhat strange construction at the end, $0084..$0088 - in conjunction with the CMP/BCS at $007C/$007E - prepares the C flag, so C=0 when a digit 0..9 has been scanned, and C=1 otherwise.
nippur72
de Lagash
Posts: 574
Joined: Thu Sep 07, 2006 8:35 am

Re: 6502 Carry Flag

Post by nippur72 »

@chysin, very well written! A very clear picture of the carry flag, covering all the black spots. You should also tell about how carry it's used along with ADC for 16 bit addition.
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: 6502 Carry Flag

Post by chysn »

nippur72 wrote: Fri May 08, 2020 8:36 am @chysin, very well written! A very clear picture of the carry flag, covering all the black spots. You should also tell about how carry it's used along with ADC for 16 bit addition.
Thanks! Yeah, I can go into that more deeply. Since my medium is mostly unexpanded VIC-20, my approach to 16-bit addition is based on what I need to do, and no more, to save precious bytes. For example, if I need to add an 8-bit number in the accumulator to a 16-bit number in memory, I'll do this:

Code: Select all

add CLC
    ADC low
    STA low
    BCC out
    INC high
out BRK
The INC of the high byte is sufficient in the case of adding an 8-bit number, and saves a ton of overhead versus the "proper" way*. The more proper way is to add a low-byte number to a low-byte number, and a high-byte number to a high-byte number with carry from the low-byte addition, like this:

Code: Select all

add CLC
    LDA addend1_low
    ADC addend2_low
    STA result_low
    LDA addend1_high
    ADC addend2_high
    STA result_high
    BCS maybe_handle_this
    BRK
This is the same kind of chaining seen in the original post. You certainly don't need to stop at 16-bit numbers. And you may or may not need or want to handle the carry flag at the very end, as shown above.

* But remember that INC doesn't set the Carry flag ever, so you have to be okay with your result possibly rolling over like a 1980s video game score or Chevette odometer. Use BEQ or BNE if you really need to check for that.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: 6502 Carry Flag

Post by chysn »

I've been pounding on a small subroutine that will probably take me into my next 6502 topic, but it's also somewhat related to the Carry flag.

I wanted code that moves a reference around screen memory efficiently. So it needs to be able to add and subtract an 8-bit value to and from a 16-bit screen location. So I'm doing the arithmetic like this:

Code: Select all

north = $EA              ; 2s-complement -22
east  = $01
south = $16
west  = $FF              ; 2s-complement -1

move   PHA
       AND #$80          ; Extend the sign to 16 bits
       BEQ sign          ; ,,
       ORA #$FF          ; ,,
sign   TAY               ; ,,
       PLA               ; Add original passed A
       CLC
       ADC position_low
       STA position_low
       TYA               ; Observe the sign of the passed A
       ADC position_high
       STA position_high
       RTS
Now we can do things like

Code: Select all

       LDA #north
       JSR move
and the screen memory location of whatever you're keeping track of will be updated based on the specified cardinal direction.

If you haven't yet guessed, my current topic of study (and my next planned post) is signed arithmetic.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
malcontent
Vic 20 Hobbyist
Posts: 129
Joined: Sun Dec 26, 2010 1:51 pm

Re: 6502 Carry Flag

Post by malcontent »

I do that with 16 bit arguments instead, then you don’t have to worry about sign.

Code: Select all

Table !word -40, 1, 40, -1

asl
tax
lda table,x
adc zp
sta zp
lda table+1,x
adc zp+1
sta zp+1
rts
Enter with direction 0-3 in A, the ASL clears the carry
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: 6502 Carry Flag

Post by chysn »

malcontent wrote: Sun May 10, 2020 11:17 pm I do that with 16 bit arguments instead, then you don’t have to worry about sign.

Code: Select all

Table !word -40, 1, 40, -1

asl
tax
lda table,x
adc zp
sta zp
lda table+1,x
adc zp+1
sta zp+1
rts
Enter with direction 0-3 in A, the ASL clears the carry
Very nice!
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: 6502 Carry Flag

Post by chysn »

I was reading through some code by Steve Wozniak last night, and learned another Carry flag trick that's simple, but worth mentioning here for posterity:

Code: Select all

    TXA
    LSR
    BCC x_is_even
    BCS x_is_odd
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
wimoos
Vic 20 Afficionado
Posts: 348
Joined: Tue Apr 14, 2009 8:15 am
Website: http://wimbasic.webs.com
Location: Netherlands
Occupation: farmer

Re: 6502 Carry Flag

Post by wimoos »

This is where I used the carry flag to convert screen codes to PETSCII. The conversion is:

b5 -> b5
/b5 -> b6
b6 -> b7

Code: Select all

LC03E	LDA  ($D1),Y	; get screen code
	ASL  A		; shift b7 into carry, shift in '0'
	ASL  A		; shift b6 into carry, shift in '0'
	ADC  #$00	; put b6 on in-shifted '0', carry is '0', sign is b5
	BMI  LC04C	; when b5 is '1' we have complement ('0') in the carry
	SEC		; set carry to '1' to have complement in case of b5='0'
LC04C	ROR  A		; shift complemented b5 in, b6 is now in the carry
	ROR  A		; shift b6 in, carry is now '0'.
	JSR  CBM_PRINTCHAR     	
Regards,

Wim.
VICE; selfwritten 65asmgen; tasm; maintainer of WimBasic
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: 6502 Carry Flag

Post by chysn »

wimoos wrote: Tue May 12, 2020 1:01 am This is where I used the carry flag to convert screen codes to PETSCII. The conversion is:
Useful and elegant, thanks for sharing!
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
R'zo
Vic 20 Nerd
Posts: 514
Joined: Fri Jan 16, 2015 11:48 pm

Re: 6502 Carry Flag

Post by R'zo »

thank you so much for this. it helped my fix a couple of bugs i've been struggling with and understand why my adc commands didn't seem to be working right.
R'zo
I do not believe in obsolete...
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: 6502 Carry Flag

Post by chysn »

R'zo wrote: Mon May 25, 2020 2:19 pm thank you so much for this. it helped my fix a couple of bugs i've been struggling with and understand why my adc commands didn't seem to be working right.
Thank you for your comment, I'm glad you found it useful. I find that it's important for me to go back to fundamentals every once in a while, so I'm happy to have found a forum where people let me dive deep into things that are probably really basic for others.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: 6502 Carry Flag

Post by chysn »

For some reason--don't ask me why because I don't know--I woke up this morning with the question, "What's the most efficient way to invert the Carry Flag?" That is, set it when it's unset and unset it when it's set. I did not wake up with any practical uses for inverting the Carry Flag, only the question.

One way takes five bytes, and affects only the Carry Flag (but note that it also affects N, V, and Z on a 65C02, on which opcode $34 is BIT ZP,X):

Code: Select all

    bcs -@
    sec
    :34 ; Skip byte
-@  clc
Another way takes four bytes and affects the Accumulator, along with C, N, and Z:

Code: Select all

    ror           rol
    eor #$80 ~or~ eor #$01
    rol           ror
Also, there's this, but it's kind of ridiculous in light of the other ways:

Code: Select all

    php
    plp
    eor #$01
    pha
    plp
I don't think it can be done in fewer than four bytes.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
malcontent
Vic 20 Hobbyist
Posts: 129
Joined: Sun Dec 26, 2010 1:51 pm

Re: 6502 Carry Flag

Post by malcontent »

One thing I just figured out that may be a minor point, is that after using a lsr as a div 2, the carry is actually the remainder. So if I want to divide an odd integer and split it between 2 variables, I can come out with a combination that isn't one less than the input.
User avatar
Noizer
Vic 20 Devotee
Posts: 297
Joined: Tue May 15, 2018 12:00 pm
Location: Europa

Re: 6502 Carry Flag

Post by Noizer »

chysn wrote: Mon May 11, 2020 8:47 am I was reading through some code by Steve Wozniak last night, and learned another Carry flag trick that's simple, but worth mentioning here for posterity:

Code: Select all

    TXA
    LSR
    BCC x_is_even
    BCS x_is_odd
Also this trick can be applied to all common used Branche commands, when the next opcode is within range. This can save a lot of bytes, instead of using JMP
Valid rule today as earlier: 1 Byte = 8 Bits
-._/classes instead of masses\_.-
Post Reply