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
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
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 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)