MACHINE LANGUAGE
Jim Butterfield, Associate Editor
Decimal Mode
Part 1
The 6502 has an option which affects only the add (ADC) and subtract (SBC) instructions: decimal mode.
Decimal mode is invoked with the Set Decimal (SED) command, and canceled with Clear Decimal (CLD). It may be affected by stack activities that pull the status register—PLP for Pull Processor status, and RTI—but this is unusual. In most computer environments you can assume that decimal mode is not in force when your program is invoked; but if you're not sure, it won't hurt to give a CLD.
Decimal mode is intended to help with certain types of numbers: Binary Coded Decimal (BCD) numbers. You might want to use this type of number system when the values are used mostly for input and output with little calculation involved.
Binary numbers—the computer's usual numeric values—are good for advanced calculations. Multiplication and division are easy to do in binary, and more advanced calculations can readily be developed. The only problem with binary numbers is this: They must be converted to decimal at the time of input or output.
Decimal numbers, or more accurately BCD numbers, are easy to input and output since they are held in the same decimal notation as was entered or will be seen by the user. With decimal mode, we may add or subtract these numbers without converting them to binary. But if we want to do more advanced mathematics, we'll certainly go to binary.
Accounting programs often use decimal mode. Similarly, many games keep scores in decimal format, since the only activities are adding points as they are scored and displaying the results.
What Is BCD?
The easiest way to describe a number held in Binary Coded Decimal is this: When you display it in hexadecimal format, you see the correct decimal value. Let's explain this with a few examples.
A value of 9 is held within a byte as binary 00001001. This is true whether you are using binary or BCD numbering. If we print the contents of this byte in hexadecimal, it is displayed as 09. Now, this not only represents the value nine, it looks like nine.
If we are in binary mode and add one to the above value, we'll get 00001010. The value is ten but the number displays in hex as 0A. This doesn't look like ten to those of us who are not trained to read hex. Worse: If we add six, we'll get a value of 16, which prints as hex value 10. This doesn't look like 16—if we didn't know it was a hexadecimal number, we might think it was ten.
Let's go back to our original value of nine, but switch to decimal mode. If we add one, using the ADC instruction, we'll end up with binary 00010000. We know that the value must represent ten, and when we print the hexadecimal it shows up as 10—which looks like ten. We must ignore the usual binary rules, which would tell us that binary 00010000 is equivalent to decimal 16. In BCD, this binary number has a value of 10. If we add a six in decimal mode, we'll get 00010110 which has a value of 16 and prints out as hexadecimal 16.
We've decided to use the bits in a different way. The four high bits—the high nybble, as it's sometimes called—represent a tens digit; the four low bits, or low nybble, represent units. Each nybble may have a value from 0 to 9, but the six highest combinations corresponding to hex A, B, C, D, E, and F will never be used.
This makes BCD less efficient than binary for storing numbers. The highest BCD number that we can store within a single byte is 99, as compared to 255 for binary. We can use several bytes together to hold larger numbers, but BCD always holds less: A two-byte BCD number can go from 0000 to 9999, compared to a two-byte unsigned binary number which can range from 0 to 65535.
But it's convenient. When we wish to output such a number, we extract each digit, convert it to ASCII with an ORA #$30, and print it. (We get the left digit by using four LSR instructions, and the right digit with AND #$0F.) An equivalent binary number would need a divide-by-ten routine before it could be output.
Similarly, input is a snap. As each ASCII digit arrives, it has its high bits stripped (with AND #$0F) and gets packed together with another digit to generate the two-to-a-byte BCD value.
An Example
Here's a sample program to show the power of BCD numbers and ease of programming with them. We'll have the computer (PET, VIC, or 64) output a table of multiples of the number 142857. This is a favorite peculiar number of mine; you'll see why when we print the table.
;set value to zero 033C A2 00 LDX #$00 033E 8E 90 03 STX LOW 0341 8E 91 03 STX MED 0344 8E 92 03 STX HIGH ;do the addition 0347 18 LOOP CLC 0348 78 SEI 0349 F8 SED 034A AD 90 03 LDA LOW 034D 69 57 ADC #$57 034F 8D 90 03 STA LOW 0352 Ad 91 03 LDA MED 0355 69 28 ADC #$28 0357 8D 91 03 STA MED 035a AD 92 03 LDA HIGH 035D 69 14 ADC #$14 0362 D8 CLD 0363 58 CLI ;print the number 0364 A0 02 LDY #$02 0366 B9 90 03 LP LDA LOW, Y 0369 4A LSR A 036A 4A LSR A 036B 4A LSR A 036C 4A LSR A 036D 09 30 ORA #$30 036F 20 D2 FF JSR $FFD2 0372 B9 90 03 LDA LOW,Y 0375 29 0F AND #$0F 0377 09 30 ORA #$30 0379 20 D2 FF JSR $FFD2 037C 88 DEY 037D 10 E7 BPL LP ;print RETURN and loop 0381 A9 0D LDA #$0D 0381 20 D2 FF JSR $FFD2 0384 E8 INX 0385 E0 07 CPX #$07 0389 60 RTS
Note that we hold the value we are calculating in three bytes; called LOW, MED, and HIGH; we add starting at the low byte and working up. The Carry flag works the same way as is usual for addition. While we're in decimal mode, we lock out the interrupt so that the interrupt routines won't do their arithmetic in the wrong mode. The addition sequences could have been written as a loop; for the sake of clarity, it was done using "straight line" coding.
For printing, we start from the high byte, of course. The output routine for BCD is simple compared to what we would need to do with binary values.
If you'd rather enter the program from BASIC, here's the same program in DATA statements. It will work on all Commodore machines.
100 DATA 162, 0, 142, 144, 3, 142, 145, 3 110 DATA 142, 146, 3, 24, 120, 248, 173, 144, 3 120 DATA 105, 87, 141, 144, 3, 173, 145, 3 130 DATA 105, 40, 141, 145, 3, 173, 146, 3 140 DATA 105, 20, 141, 146, 3, 216, 88, 160, 2 150 DATA 185, 144, 3, 74, 74, 74, 74, 9, 48 160 DATA 32, 210, 255, 185, 144, 3, 41, 15, 9, 48 170 DATA 32, 210, 255, 136, 16, 231, 169, 13 180 DATA 32, 210, 255, 232, 224, 7, 208, 190, 96 200 FOR J = 828 TO 905 210 READ X: T = T + X 220 POKE J, X 230 NEXT J 240 SYS 828
You might like to examine the output of the program to see what's so special about the first seven multiples of the number 142857.
Next month, we'll discuss special features and wrinkles of decimal mode.