## Meta

base:decimal_mode_in_nmos_6500_series

# Decimal mode in NMOS 6500 series

Most sources claim that the NMOS 6500 series sets the N, V and Z flags unpredictably when performing addition or subtraction in decimal mode. Of course, this is not true. While testing how the flags are set, I also wanted to see what happens if you use illegal BCD values.

ADC works in Decimal mode in a quite complicated way. It is amazing how it can do that all in a single cycle. Here's a C code version of the instruction:

``` [ Warning: this code is NOT accurate. ]

unsigned
A,  /* Accumulator */
AL, /* low nybble of accumulator */
AH, /* high nybble of accumulator */

C,  /* Carry flag */
Z,  /* Zero flag */
V,  /* oVerflow flag */
N,  /* Negative flag */

s;  /* value to be added to Accumulator */

AL = (A & 15) + (s & 15) + C;         /* Calculate the lower nybble. */

AH = (A >> 4) + (s >> 4) + (AL > 15); /* Calculate the upper nybble. */

Z = ((A + s + C) & 255 != 0);         /* Zero flag is set just
like in Binary mode. */

if (AL > 9) AL += 6;                  /* BCD fixup for lower nybble. */

/* Negative and Overflow flags are set with the same logic than in
Binary mode, but after fixing the lower nybble. */

N = (AH & 8 != 0);
V = ((AH << 4) ^ A) & 128 && !((A ^ s) & 128);

if (AH > 9) AH += 6;                  /* BCD fixup for upper nybble. */

/* Carry is the only flag set after fixing the result. */

C = (AH > 15);
A = ((AH << 4) | (AL & 15)) & 255;

The C flag is set as the quiche eaters expect, but the N and V flags
are set after fixing the lower nybble but before fixing the upper one.
They use the same logic than binary mode ADC. The Z flag is set before
any BCD fixup, so the D flag does not have any influence on it.

Proof: The following test program tests all 131072 ADC combinations in
Decimal mode, and aborts with BRK if anything breaks this theory.
If everything goes well, it ends in RTS.

M 0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@   'BI&*  A/N\$_\$B@+)\$KH(V1
M*Q@(I?PI#X7]I?LI#V7]R0J0 FD%J"D/A?VE^RGP9?PI\ C \$) ":0^JL @H
ML ?)H) &""@X:5\X!?V%_0AH*3W@ ! ""8"HBD7[\$ JE^T7\, 28"4"H**7[
M9?S0!)@) J@8N/BE^V7\V A%_= G:(3]1?W0(.;[T(?F_-"#:\$D8\ )88*D=
0&&4KA?NI &4LA?RI.&S[  A%

end

Decimal Mode
AC  +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f +10 +11

59  60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e  69 70
5a  61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f  70 71
5b  62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 60  71 72
5c  62 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 60 61  72 73
5d  64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 60 61 62  73 74
5e  65 66 67 68 69 6a 6b 6c 6d 6e 6f 60 61 62 63  74 75
5f  66 67 68 69 6a 6b 6c 6d 6e 6f 60 61 62 63 64  75 76
60  61 62 63 64 65 66 67 68 69 70 71 72 73 74 75  70 71

Table 1: Sample results
The triangular area (5b+f, 5f+b, 5f+f) with significantly smaller
results is due to the fact that Carry cannot reach value of "2".

All programs in this chapter have been successfully tested on a Vic20
and a Commodore 64 and a Commodore 128D in C64 mode. They should run on
C16, +4 and on the PET series as well. If not, please report the problem
to Marko M"akel"a. Each test in this chapter should run in less than a
minute at 1 MHz.

SBC is much easier. Just like CMP, its flags are not affected by
the D flag.

Proof:

begin 600 dsbc-cmp-flags
M 0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@   'B@ (3[A/RB XH8:66HL2N@
M09\$KH\$R1*XII::BQ*Z!%D2N@4)\$K^#BXI?OE_-@(:(7].+BE^^7\"&A%_? !
5 .;[T./F_-#?RA"_8!@X&#CEY<7%

end

The only difference in SBC's operation in decimal mode from binary mode
is the result-fixup:

unsigned
A,  /* Accumulator */
AL, /* low nybble of accumulator */
AH, /* high nybble of accumulator */

C,  /* Carry flag */
Z,  /* Zero flag */
V,  /* oVerflow flag */
N,  /* Negative flag */

s;  /* value to be added to Accumulator */

AL = (A & 15) - (s & 15) - !C;        /* Calculate the lower nybble. */

if (AL & 16) AL -= 6;                 /* BCD fixup for lower nybble. */

AH = (A >> 4) - (s >> 4) - (AL & 16); /* Calculate the upper nybble. */

if (AH & 16) AH -= 6;                 /* BCD fixup for upper nybble. */

/* The flags are set just like in Binary mode. */

C = (A - s - !C) & 256 != 0;
Z = (A - s - !C) & 255 != 0;
V = ((A - s - !C) ^ s) & 128 && (A ^ s) & 128;
N = (A - s - !C) & 128 != 0;

A = ((AH << 4) | (AL & 15)) & 255;

Again Z flag is set before any BCD fixup. The N and V flags are set
at any time before fixing the high nybble. The C flag may be set in any
phase.

Decimal subtraction is easier than decimal addition, as you have to
make the BCD fixup only when a nybble overflows. In decimal addition,
you had to verify if the nybble was greater than 9. The processor has
an internal "half carry" flag for the lower nybble, used to trigger
the BCD fixup. When calculating with legal BCD values, the lower nybble
cannot overflow again when fixing it.
So, the processor does not handle overflows while performing the fixup.
Similarly, the BCD fixup occurs in the high nybble only if the value
overflows, i.e. when the C flag will be cleared.

Because SBC's flags are not affected by the Decimal mode flag, you
could guess that CMP uses the SBC logic, only setting the C flag
first. But the SBX instruction shows that CMP also temporarily clears
the D flag, although it is totally unnecessary.

The following program, which tests SBC's result and flags,
contains the 6502 version of the pseudo code example above.

begin 600 dsbc
M 0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@   'BI&*  A/N\$_\$B@+)\$KH':1
M*S@(I?PI#X7]I?LI#^7]L /I!1@I#ZBE_"GPA?VE^RGP"#CE_2GPL KI7RBP
M#ND/.+ )*+ &Z0^P NE?A/T%_87]*+BE^^7\"&BH.+CXI?OE_-@(1?W0FVB\$
8_47]T)3F^]">YOS0FFA)&- \$J3C0B%A@

end

Obviously the undocumented instructions RRA (ROR+ADC) and ISB
(INC+SBC) have inherited also the decimal operation from the official
instructions ADC and SBC. The program droradc proves this statement
for ROR, and the dincsbc test proves this for ISB. Finally,
dincsbc-deccmp proves that ISB's and DCP's (DEC+CMP) flags are not
affected by the D flag.

M`0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@```'BI&*``A/N\$_\$B@+)\$KH(V1
M*S@(I?PI#X7]I?LI#V7]R0J0`FD%J"D/A?VE^RGP9?PI\`C`\$)`":0^JL`@H
ML`?)H)`&""@X:5\X!?V%_0AH*3W@`!`""8"HBD7[\$`JE^T7\,`28"4"H**7[
M9?S0!)@)`J@XN/BE^R;\9_S8"\$7]T"=HA/U%_=`@YOO0A>;\T(%H21CP`EA@
2J1T892N%^ZD`92R%_*DX;/L`
`
end

begin 644 dincsbc
M`0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@```'BI&*``A/N\$_\$B@+)\$KH':1
M*S@(I?PI#X7]I?LI#^7]L`/I!1@I#ZBE_"GPA?VE^RGP"#CE_2GPL`KI7RBP
M#ND/.+`)*+`&Z0^P`NE?A/T%_87]*+BE^^7\"&BH.+CXI?O&_.?\V`A%_="9
::(3]1?W0DN;[T)SF_-"8:\$D8T`2I.-"&6&#\
`
end

begin 644 dincsbc-deccmp
M`0@9",D'GL(H-#,IJC(U-JS"*#0T*:HR-@```'B@`(3[A/RB`XH8:7>HL2N@
M3Y\$KH%R1*XII>ZBQ*Z!3D2N@8)\$KBFE_J+\$KH%61*Z!BD2OX.+BE^^;\Q_S8
L"&B%_3BXI?OF_,?\"&A%_?`!`.;[T-_F_-#;RA"M8!@X&#CFYL;&Q\?GYP#8
`
end```
base/decimal_mode_in_nmos_6500_series.txt · Last modified: 2015-04-17 04:31 (external edit)