Jim Butterfield, Associate Editor
There's a quick method of generating decimal output on the 6502. It's a notable departure from conventional methods, and it would be worthwhile to lay down a few general ideas.
Suppose we have two bytes, OLD and NEW. OLD contains a value, and NEW contains zero. We want to transfer the contents of OLD to NEW, and set OLD to zero. That's not hard by conventional coding (a couple of Load and Store commands), but we're going to look at another method.
Suppose that we shift each bit out of OLD, and then shift it into NEW. Using a left shift, we would code: ASL OLD (arithmetic shift left), which puts the extra bit into the carry; and then ROL NEW, which slides the carry bit into the new byte. If we repeat this eight times, OLD will have moved to NEW, bit by bit. It seems like a slow way of doing it, but it does indeed achieve what we want.
The same method, of course, would move a two-byte OLD to NEW, or as many bytes as we need. Each bit shift would consist of one ASL followed by several ROL commands until the job is done.
A New Way To Rotate
The ROL (Rotate Left) command is compact and handy. It takes the contents of the Carry flag and moves it into the low-order bit position of the operand; all other bits move over to make room, and the high-order bit falls out into the Carry. Now let's do the same thing without using the ROL command.
The ROL command might be considered the same as multiplying by two plus adding a carry, if necessary. We often use the left Shift and Rotate commands for multiplication. But there's another way to multiply: we can use repeated addition.
We can do exactly the same as ROL NEW by coding: LDA NEW: ADC NEW: STA NEW. The original number is doubled, which gives the left shift, and the carry is automatically added in. A new carry condition is generated. All we seem to have done is use three instructions where one would have done.
But here's the gimmick: we can make the ADC instruction add in a different manner by switching to decimal mode. In decimal mode, addition automatically produces BCD numbers. And BCD numbers can be printed as if they were hexadecimal, which greatly simplifies the output calculation.
Let's work this out in principle. First, a warning: on many machines, decimal mode is poisonous to the operating system and to the interrupt routines. Remember to restore binary mode when you're finished; and if your machine uses interrupt, lock it out for the duration of your calculation.
Let's look at simple coding to change a one-byte OLD to NEW:
LDA #$00 STA NEW (clear NEW) LDX #$07 (eight bits) ASL OLD (grab a bit) LDA NEW (slip it..) ADC NEW (..into..) STA NEW (...NEW) DEX (count down) BPL BIT (next bit) ...
If we are in binary mode, the above routine will copy OLD to NEW unchanged. But if we switch to decimal mode, OLD will be converted to BCD as it is moved to NEW.
A warning here: the result might not fit. A one-byte binary number might need to be converted to three decimal digits (for example, 250). In this case, we'd need to have two bytes available in NEW to hold the result, since BCD holds only two decimal digits per byte. Be sure your coding provides for sufficient space for the answer.
Let's write the outline of a routine to convert a series of 16-bit numbers to decimal and output them. We'll write the code in compact form so as to emphasize the logic flow.
Set up Y to reach several numbers:
Copy a number into the work area:
A: LDA TABLE, Y : STA WORK : LDA TABLE + 1,Y : STA WORK1
Move Y to reach the next number, clear output area:
INY : INY : LDA #0 : STA OUT1 : STA OUT2 : STA OUT3 : STA ZSUP
Get ready to move 16 bits from WORK to OUT:
Move bit out of WORK:
B: ASL WORK : ROL WORK1
Switch to decimal mode:
SEI : SED
Move bit (decimally) into OUT:
LDA OUT1 : ADC OUT1 : STA OUT1 LDA OUT2 : ADC OUT2 : STA OUT2 LDA OUT3 : ADC OUT3 : STA OUT3
Clear decimal mode:
CLD : CLI
Repeat for next bit:
DEX : BPL B
Prepare to output three bytes (six digits):
Get bytes, high order first, for output:
C: LDA OUT1, Y : PHA
Output high order digit:
PHA : LSR : LSR : LSR : LSR : JSR PUT : PLA
Output low order digit:
AND #$0F : JSR PUT
Go for next byte:
DEX : BPL C
LDA #$0D : JSR PRINT
Go back for another number:
CPY #10 : BCC A
Zero suppress output subroutine:
PUT : CMP ZSUP : BNE D
Fill with space:
LDA #$20 : BNE E
Convert numeric, kill zero suppression:
D: ORA #$30 : STA ZSUP
Print and return:
E: JMP $FFD2
Let's put the above into a PET/CBM/VIC/C64 environment to see it work:
100 DATA 160, 0, 185, 80, 3, 141, 64, 3 110 DATA 185, 81, 3, 141, 65, 3, 200, 200 120 DATA 169, 0, 141, 66, 3, 141, 67, 3, 141, 68, 3 130 DATA 141, 69, 3, 162, 15, 14, 64, 3, 46, 65, 3, 120, 248 140 DATA 173, 66, 3, 109, 66, 3, 141, 66, 3, 173, 67, 3 150 DATA 109, 67, 3, 141, 67, 3, 173, 68, 3 160 DATA 109, 68, 3, 141, 68, 3, 216, 88, 202, 16, 216 170 DATA 162, 2, 189, 66, 3, 72, 74, 74, 74, 74 180 DATA 32, 184, 3, 104, 41, 15, 32, 184, 3, 202, 16, 236, 169, 13, 32, 210, 255 190 DATA 192, 10, 144, 155, 96, 205, 69, 3, 208, 4, 169, 32 195 DATA 208, 5, 9, 48, 141, 69, 3, 76, 210, 255 200 FOR J = 848 TO 968 : READ X 210 T = T + X : POKE J, X 220 NEXT J 230 IF T<>10738 THEN STOP 300 SYS 848
The numbers that are printed won't have any special meaning, but you'll see that conversion is taking place, and that zero suppression works nicely.
Converting binary numbers to BCD in preparation for output isn't really a gimmick. It's a sensible way to do an otherwise difficult job.