by Tom Hudson
It's hard to believe, but here we are in the seventh installment of Boot Camp. We've only got a few more 6502 operation codes to cover before we begin writing full-scale programs, so hang in there! The best is yet to come.
Last issue's assignment asked you to solve eight bit-manipulation problems. You were given before-and-after bit patterns and asked to find what operation codes and operands were used to get the results. Figure 1 shows the completed assignment. Some of the problems had two possible answers. These are so noted, with both solutions.
BYTE 1 OPN BYTE 2 RESULT ANS -------- --- -------- -------- --- 01000011 AND 01000001 01000001 (1) 01000011 EOR 00000010 01000001 (2) 11001011 EOR 01101001 10100010 11110000 AND 01000000 01000000 (1) 11110000 EOR 10110000 01000000 (2) 01010101 ORA 10101010 11111111 (1) 01010101 EOR 10101010 11111111 (2) 11001000 EOR 10110100 01111100 11111111 AND 11110001 11110001 (1) 11111111 EOR 00001110 11110001 (2) 00100100 EOR 10011100 10111000 01000111 EOR 01010011 00010010
Clever readers have probably noticed that the fourth problem actually has far more than two possible answers. In fact, by using the ORA instruction, BYTE 2 could be any value with bits 1, 3, 5 and 7 set! Try it yourself with a short program.
As you may recall from issue 13's Boot Camp, by shifting a binary number left one bit, we effectively multiply it by two. Shifting it left two bits multiplies it by four. This principle is very handy, allowing us to multiply integers quickly and easily.
How do we perform this left-shift operation in 6502 assembly language? With the ASL (Arithmetic Shift Left) instruction, of course. This operation shifts the contents of the accumulator or a selected memory byte left one bit, and has the following formats:
ASL A | (ACCUMULATOR) |
ASL nn | (ABSOLUTE) |
ASL n | (ZERO PAGE) |
ASL n,X | (ZERO PAGE INDEXED X) |
ASL nn,X | (INDEXED X) |
When an ASL instruction is executed, the accumulator or memory byte is shifted one bit to the left. Figure 2 shows how the operation is handled internally.
BEFORE 0 0 0 1 1 0 0 0 1 CARRY BYTE=49 AFTER 0 <- 0 1 1 0 0 0 1 0 <- 0 CARRY BYTE=98
As you can see from the before and after images in Figure 2, each bit of the selected byte is shifted to the left one place. Since bit 7 has no other place to go, it is shifted into the 6502 CARRY flag. This is done to allow for multiple-byte shifts, which we'll look at in a moment. A zero is shifted into the 1 bit. As you can see, the value of the byte has been multiplied by two!
As long as the results of your shift-multiples do not exceed 255 decimal, you will find the ASL instruction works fine. Problems begin, though, when you get into multiple-byte values.
BEFORE 0 0 1 1 0 1 0 0 1 0 0 1 1 1 1 0 VALUE = 13470 AFTER 0 1 1 0 1 0 0 1 <- 0 0 1 1 1 1 0 0 <- 0 VALUE = 26940
Figure 3 shows an example of a multiple-byte shift. As you can see, the contents of bit 7 of the low byte must shift into bit 0 of the high byte. In order to do this, we must use the LSR instruction to shift the low byte, and a new instruction, ROL (Rotate left through carry), for the high byte. ROL has the following formats:
ROL A | (ACCUMULATOR) |
ROL nn | (ABSOLUTE) |
ROL n | (ZERO PAGE) |
ROL n,X | (ZERO PAGE INDEXED X) |
ROL nn,X | (INDEXED X) |
The ROL instruction performs the same function as ASL, except that it puts the contents of the carry flag in the low-order bit instead of a zero.
Both ASL and ROL set the SIGN, ZERO and CARRY flags according to the result of the operation.
10 *= $0600
20 LDA #$07 ;PLACE 7 IN ACCUM.
30 ASL A ;TIMES 2
40 ASL A ;TIMES 4
50 ASL A ;TIMES 8
60 STA TIMES8 ;SAVE RESULT
70 BRK ;AND STOP!
80 TIMES8 *=*+1
90 .END
Let's took at a few examples of multiplication using the ASL and ROL instructions.
Figure 4 shows an example of single-byte multiplication using the ASL instruction. In this example, we're multiplying the contents of the accumulator (7) by eight and storing the result in the location labeled TIMES8.
The above example shows how easy the ASL instruction makes it to multiply a number by a power of two, but what if you want to multiply a number by five?
In such cases, it's good to break the multiplier down into "bite-sized" pieces. For example, a multiply by five can be broken down into:
(number * 4) + (number ) ------------ (number * 5)
The 6502 code required for this operation is shown in Figure 5.
10 *= $0600
15 LDA #23 ;PLACE 23 IN ACCUM.
20 ASL A ;TIMES 2
25 ASL A ;TIMES 4
30 CLC ;CLEAR CARRY FOR ADD
35 ADC #23 ;ADD 23 = TIMES 5!
40 STA TIMES5 ;AND STORE RESULT
45 BRK ;ALL DONE!
50 TIMES5 *=*+1
55 .END
Similarly, a multiply by 10 can be broken down to:
(number * 8) + (number * 2) ------------- (number * 10)
With its 6502 code shown in Figure 6.
10 *= $0600
15 LDA #23 ;PLACE 23 IN ACCUM.
20 ASL A ;TIMES 2
25 STA TIMES2 ;SAVE *2 VALUE
30 ASL A ;TIMES 4
35 ASL A ;TIMES 8
40 CLC ;CLEAR CARRY FOR ADD
45 ADC TIMES2 ;*8 + *2 = *10!
50 STA TIMES10 ;SAVE TIMES 10
55 BRK ;AND STOP!
60 TIMES2 *=*+1
65 TIMES10 *=*+1
70 .END
As you can see, you can multiply a number by almost any value, through a combination of left shifts and add/subtract operations. It's just a matter of careful planning when writing a program.
Now that we've looked at single-byte multiplication, we can go on to bigger and better things, such as multiplying two-byte values. Figure 7 shows the procedure for multiplying the two-byte value TOTAL by sixteen. Note that the low-order byte is always SHIFTed, and the high byte is always ROTATED.
10 *= $0600
15 LDA #$02 ;PLACE 02...
20 STA TOTAL+1 ;IN TOTAL HI BYTE
25 LDA #$4F ;PLACE 4F...
30 STA TOTAL ;IN TOTAL LO BYTE
35 ASL TOTAL ;SHIFT LOW,
40 ROL TOTAL+1 ;ROTATE HI = TIMES 2
45 ASL TOTAL ;SHIFT LOW,
50 ROL TOTAL+1 ;ROTATE HI = TIMES 4
55 ASL TOTAL ;SHIFT LOW,
60 ROL TOTAL+1 ;ROTATE HI = TIMES 8
65 ASL TOTAL ;SHIFT LOW
70 ROL TOTAL+1 ;ROTATE HI = TIMES 16
75 BRK ;ALL DONE!
80 TOTAL *=*+2
85 .END
These examples show the basics of 6502 multiplication, but don't stop here. Study the above code and try creating your own programming puzzles. I've given you the ball, now run with it!
Now that we've covered simple multiplication, let's look at basic division. You know how bit-shifting works, so picking up the finer points of binary division should be easy.
Remember how shifting the value 49 decimal (00110001 binary) left one bit gave us 98 (01100010 binary)? What happens if we shift the value RIGHT one bit? Figure 8 gives us the answer.
BEFORE 0 0 1 1 0 0 0 1 0 BYTE=49 CARRY AFTER 0 -> 0 0 0 1 1 0 0 0 -> 1 BYTE=24 CARRY
As you can see, we've just discovered the first limitation of binary division -- we can't handle decimals! Using real numbers instead of integers, 49/2 = 24.5. Shifting the value 49 right one bit divided it by two, all right, but we lost the decimal portion of the result. We'll look at real number division in later installments of Boot Camp, but for now the loss of the precision does not matter. I mentioned the problem because it's good for you to be aware of this limitation.
In the 6502 instruction set, the operation which performs this right shift is the LSR (Logical shift right) instruction. Its formats are:
LSR A | (ACCUMULATOR) |
LSR nn | (ABSOLUTE) |
LSR n | (ZERO PAGE) |
LSR n,X | (ZERO PAGE INDEXED X) |
LSR nn,X | (INDEXED X) |
As Figure 8 shows, the LSR instruction shifts all the bits of the indicated byte right one position. A zero is placed in the high-order, or 128 bit. The low-order, or 1 bit is shifted into the carry flag. This allows us to perform multi-byte right shifts, similar to multi-byte left-shifts.
Before we look at multiple-byte division, let's look at a single-byte example.
10 *= $0600
20 LDA #184 ;PUT 184 IN ACCUM.
30 LSR A ;DIVIDE BY 2
40 LSR A ;DIVIDE BY 4
50 LSR A ;DIVIDE BY 8
60 STA DIV8 ;SAVE RESULT
70 BRK ;AND STOP!
80 DIV8 *=*+1
90 .END
Figure 9 shows an example of dividing a single-byte value by eight. Like multiplication by eight, this operation requires three shifts, but in the opposite direction. In this example, we divide the number 184 decimal by eight, placing the result in the location DIV8.
Now you see how simple single-byte division is. If you want to divide any integer up to 255 by a power of two, this process works fine.
Up till now, we've limited ourselves to simple, single-byte division. Now let's see how we do it with more than one byte.
BEFORE 0 1 1 0 1 1 0 1 0 1 1 0 1 0 0 0 VALUE = 28008 AFTER 0 -> 0 0 1 1 0 1 1 0 -> 1 0 1 1 0 1 0 0 VALUE = 14004
Figure 10 shows the division of the two-byte value 28008 by two. As you can easily calculate, the result is 14004. If you compare this example with the multi-byte multiplication shown in Figure 3, you will notice an interesting difference.
In multiplication, the LOW byte is shifted and the HIGH byte(s) is (are) rotated. This is because the bit shift proceeds from right to left.
In division, however, things are reversed. Since we are shifting all the bits to the right, the HIGHEST byte is shifted, and the remaining bytes are rotated. This allows the low-order bits of the bytes being divided to shift into the lower-order bytes.
Let's look at an example of the three-byte value SCORE being divided by four. The code necessary is shown in Figure 11.
10 *= $0600
15 LDA #$49 ;SET UP...
20 STA SCORE+2 ;3-BYTE...
25 LDA #$23 ;VALUE...
30 STA SCORE+1 ;IN SCORE...
35 LDA #$F8 ;=$4923F8
40 STA SCORE
45 LSR SCORE+2 ;DIVIDE...
50 ROR SCORE+1 ;SCORE...
55 ROR SCORE ;BY 2
60 LSR SCORE+2 ;DIVIDE...
65 ROR SCORE+1 ;SCORE...
70 ROR SCORE ;BY 4
75 BRK ;AND STOP!
80 SCORE *=*+3
85 .END
Well, now you have the basics of integer binary multiplication and division under your belt. The principle is simple, you just have to work with it until you feel comfortable. In order to do that, create your own problems to solve. If you run into difficulty, write me and I'll help out. After all, you may not be the only person with a particular question, and your query could help others understand more, too.
For those of you who need some prompting to get started with problems, here's one that shouldn't be too hard if you've read carefully.
Write a program that multiplies the value 5 by 27. Use any of the techniques we have discussed so far. There are several possible solutions to this problem, so give it your best shot. When you solve it, I'd like to see the technique you used. Send listings of your solutions to:
Boot Camp
Next issue, we'll look at a couple of possible solutions. We'll also find out what the stack is and how it helps us write subroutines.