base:decimal_mode_in_nmos_6500_series
no way to compare when less than two revisions
Differences
This shows you the differences between two versions of the page.
— | base:decimal_mode_in_nmos_6500_series [2015-04-17 04:31] (current) – created - external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== 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 | ||
+ | | ||
+ | AL, /* low nybble of accumulator */ | ||
+ | AH, /* high nybble of 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 | ||
+ | | ||
+ | |||
+ | 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 | ||
+ | | ||
+ | If everything goes well, it ends in RTS. | ||
+ | |||
+ | begin 600 dadc | ||
+ | M 0@9", | ||
+ | M*Q@(I? | ||
+ | ML ?)H) &"" | ||
+ | M9?S0!)@) J@8N/ | ||
+ | 0&& | ||
+ | |||
+ | 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 | ||
+ | | ||
+ | |||
+ | |||
+ | 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" | ||
+ | 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", | ||
+ | M09$KH$R1*XII:: | ||
+ | 5 .; | ||
+ | |||
+ | end | ||
+ | |||
+ | |||
+ | The only difference in SBC's operation in decimal mode from binary mode | ||
+ | is the result-fixup: | ||
+ | |||
+ | unsigned | ||
+ | | ||
+ | AL, /* low nybble of accumulator */ | ||
+ | AH, /* high nybble of 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", | ||
+ | M*S@(I? | ||
+ | M#ND/.+ )*+ &Z0^P NE? | ||
+ | 8_47]T)3F^]"> | ||
+ | |||
+ | 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. | ||
+ | |||
+ | begin 644 droradc | ||
+ | M`0@9", | ||
+ | M*S@(I? | ||
+ | ML`? | ||
+ | M9? | ||
+ | 2J1T892N%^ZD`92R%_*DX;/ | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | begin 644 dincsbc | ||
+ | M`0@9", | ||
+ | M*S@(I? | ||
+ | M# | ||
+ | :: | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | begin 644 dincsbc-deccmp | ||
+ | M`0@9", | ||
+ | M3Y$KH%R1*XII> | ||
+ | L"& | ||
+ | ` | ||
+ | end | ||
+ | </ | ||
base/decimal_mode_in_nmos_6500_series.txt · Last modified: 2015-04-17 04:31 by 127.0.0.1