> I'm doing this on a 65802 in emulation mode, if that makes any difference.
I don't know if it makes any difference either. Read on and you can test it for yourself.
> If you can point out a pattern that's useful for something, I'd be glad to
> hear it. But as you can see from the above, it does not follow the same V
> flag rules that it does in binary mode.
There is one set of rules for binary mode and a separate set of rules for decimal mode. However, the decimal mode rules are consistent, even if they don't make much sense. In terms of usefulness, well...the decimal mode rules satiated my curiosity. Does that count?
SBC is easy. SBC NUMBER affects the V flag in the same way, regardless of the value of the D flag, or whether the two numbers being added are valid BCD numbers or not. To determine the V flag, just subtract the numbers as though they were binary numbers. Here is a program that checks this. It returns 1 in ERROR if the test passes, and it returns 0 in ERROR if the test fails. Conveniently, it stops at the first counterexample, leaving the V flag result from the decimal subtraction in DV. The V flag result of the binary subtraction can then be deduced, since the two flags are unequal. Basically, it just tests all 131072 cases (256 possible values of N1 * 256 possible values of N2 * 2 possible value of the carry upon entry). It takes about 8 seconds to complete (if it passes) at 1 MHz.
Code:
; CHECK SBC V FLAG IN DECIMAL MODE
;
; Returns with ERROR = 0 if the test passes, ERROR = 1 if the test fails
;
; Four additional memory locations are used: DV, ERROR, N1, and N2
; These may be located anywhere convenient in RAM
;
LDY #1 ; initialize Y (carry) to one
STY ERROR ; store 1 in error until all tests pass
LDA #0 ; initialize N1 and N2 to zero
STA N1
STA N2
LOOP SED
CPY #1 ; carry = 1 when Y = 1, carry = 0 when Y = 0
LDA N1 ; subtract in decimal mode
SBC N2
PHP
PLA
STA DV ; save decimal mode V flag
CLD
CPY #1 ; carry = 1 when Y = 1, carry = 0 when Y = 0
LDA N1 ; subtract in binary mode
SBC N2
PHP
PLA
EOR DV
AND #$40 ; mask the V flag
BNE DONE ; exit with ERROR = 1 if V flags are not equal
INC N1
BNE LOOP ; loop through all 256 values of N1
INC N2
BNE LOOP ; loop through all 256 values of N2
DEY
BPL LOOP ; loop through both values of Y (carry)
LDA #0 ; all tests pass, so store 0 in ERROR
STA ERROR
DONE RTS
If you change the AND #$40 to an AND #$01, you'll see that the same thing is true of the carry (for SBC). (You get the idea of how the whole formula testing program works. The V flag is one of the easier cases since it is the same on the 6502, 65C02, and 65816.)
ADC is more complicated. It's reassuring to know that all of your 65802 examples agree with what I've found. For the purposes of determining the V flag, the lower digits are added as though they were BCD digits, and the upper digits are added as though they are binary digits. More specifically, when adding N1 and N2 (all of the additions are performed as though they were binary, rather than decimal, numbers):
1. Let N1L = N1 AND $0F
2. Let N1H = N1 AND $F0
3. Let N2L = N2 AND $0F
4. Let N2H = N2 AND $F0
(L and H in this case are the low and high digits, not the low and high bytes.)
5. Let RESULTL = N1L + N2L + carry
6. The V flag is determined by the addition N1H + N2H when RESULTL <= 9
7. The V flag is determined by the addition N1H + N2H + $10 when RESULTL > 9
Here is a program which tests this. It also conveniently stops at the first counterexample. It's very similar to the SBC checking program, just modified for the ADC method. It takes about 10 seconds to complete (if it passes) at 1 MHz.
Code:
; CHECK ADC V FLAG IN DECIMAL MODE
;
; Returns with ERROR = 0 if the test passes, ERROR = 1 if the test fails
;
; Six additional memory locations are used: DV, ERROR, N1, N2, N2H, and N2L
; These may be located anywhere convenient in RAM
;
LDY #1 ; initialize Y (carry) to one
STY ERROR ; store 1 in error until all tests pass
LDA #0 ; initialize N1 and N2 to zero
STA N1
STA N2
LOOP1 LDA N2 ; split N2 into low and high digits
AND #$0F
STA N2L
LDA N2
AND #$F0
STA N2H
LOOP2 SED
CPY #1 ; carry = 1 when Y = 1, carry = 0 when Y = 0
LDA N1 ; add in decimal mode
ADC N2
PHP
PLA
STA DV ; save decimal mode V flag
CLD
CPY #1 ; carry = 1 when Y = 1, carry = 0 when Y = 0
LDA N1
AND #$0F ; get low digit of N1
ADC N2L ; add to low digit of N2
CMP #$0A
LDA N1
AND #$F0 ; get high digit of N1
BCC SKIP ; if low digit result < $0A, then add N1H + N2H
ORA #$0F ; since carry = 1, the addition will be N1H + N2H + $0F + 1
SKIP ADC N2H ; add high digits
PHP
PLA
EOR DV
AND #$40 ; mask the V flag
BNE DONE ; exit with ERROR = 1 if V flags are not equal
INC N1
BNE LOOP2 ; loop through all 256 values of N1
INC N2
BNE LOOP1 ; loop through all 256 values of N2
DEY
BPL LOOP1 ; loop through both values of Y (carry)
LDA #0 ; all tests pass, so store 0 in ERROR
STA ERROR
DONE RTS
> This excellent tutorial is now online:
The comparison tutorial isn't the tutorial I was referring to. There is a second tutorial titled "The V Flag Explained" that I sent a month ago or so. There is a bit of overlap between the two tutorials, but the V flag tutorial contains two appendices, the second of which deals with decimal mode and goes through a few ADC examples step by step. (It doesn't contain the programs above, though.)