by Tom Hudson
I hope all Boot Camp readers have been practicing their addition, subtraction and X-Y register manipulations, because we're moving on to bigger and better things. We'll be dabbling with comparisons, branching and indexing this month, giving you even more tools to work with in assembly language.
Last month, I gave you a simple data manipulation problem:
PROBLEM: Write a program which starts with A=$03, X=$07 and Y=$14. Then write the code necessary to change these registers so that when the program ends, the registers are A=$07, X=14 and Y=$03.
As most readers know, there are hundreds of ways to solve any programming problem, and this one is no exception. The objective is not just to solve the problem, but to do it in the most efficient way possible. I'll show you two ways to solve the above problem, and discuss the pros and cons of each.
10 STA AHOLD 20 STX XHOLD 30 STY YHOLD 40 LDA XHOLD 50 LDX YHOLD 60 LDY AHOLD 70 BRK 80 AHOLD *=*+1 90 XHOLD *=*+1 0100 YHOLD *=*+1 0110 .END
Figure 1 shows an easy-to-understand, straightforward solution to our problem. It stores each register in hold areas, then loads the registers from the appropriate hold area. Lines 10-60 perform the register exchange function, and Lines 80-100 set up the one byte storage areas.
This solution is very easy to understand by simply looking at it, and is a solution that most beginners would probably use. However, from a memory usage standpoint, this routine requires 22 bytes. We can do the same exchange in only 10 bytes with the routine in Figure 2.
10 STY HOLD 20 TAY 30 TXA 40 LDX HOLD 50 BRK 60 HOLD *=*+1 70 .END
As you can see, this code uses two of the transfer instructions, TAY and TXA, to eliminate two of the temporary storage areas used in Figure 1. Since the transfer instructions use only one byte versus the six bytes for a LDA and STA instruction, this version of the exchange code uses less than half the memory of Figure 1.
Although we gain memory savings by using the code in Figure 2, we do lose some readability. Let's say you use the routine in Figure 1 in a program and don't look at the program for a year. If you need to make a change, it's easy to see what the routine does. The code in Figure 2 may not be so easy to decipher. Since you never know when you'll have to make a change to a program, it's a very good idea to COMMENT your code heavily, in order to let yourself know what you were doing.
The great thing about computers is that they can perform calculations very quickly. Without the ability to make decisions, though, a computer would be almost useless.
For this reason, the 6502 microprocessor in your Atari is equipped with 14 comparison instructions. These instructions are designed to test the values contained in the Accumulator, X and Y registers. Each of these instructions compares the desired register with the memory byte specified in the operand and sets the 6502 status flags accordingly.
The Accumulator comparison instructions are:
CMP #n (IMMEDIATE) CMP nn (ABSOLUTE) CMP n (ZERO PAGE) CMP (n,X) (PRE-INDEXED INDIRECT) CMP (n),Y (POST-INDEXED INDIRECT) CMP n,X (ZERO PAGE INDEXED X) CMP nn,X (INDEXED X) CMP nn,Y (INDEXED Y)
The X register comparison instructions are:
CPX #n (IMMEDIATE) CPX nn (ABSOLUTE) CPX n (ZERO PAGE)
The Y register comparison instructions are:
CPY #n (IMMEDIATE) CPY nn (ABSOLUTE) CPY n (ZERO PAGE)
All comparison instructions affect only three status flags. These are the SIGN, ZERO and CARRY flags.
What happens in a comparison? Internally, the computer will subtract the operand byte from the register contents, set the status flags just like a subtract, but will NOT alter the register. Simple, right? Let's look at a few examples.
Assume the accumulator contains $45, and we execute the instruction:
CMP #$31
Inside the computer, the faithful 6502 would subtract $31 from. $45 and obtain the following result:
$45 = 0 1 0 0 0 1 0 1 $31 = 0 0 1 1 0 0 0 1 _______________ 0 0 0 1 0 1 0 0 = $14
Since the result is not zero, the ZERO flag is set to 0. The SIGN flag is set to bit 7 of the result, which is 0. The CARRY flag is set to 1, since no borrow was required. The CARRY flag is always the inverse of the borrow status.
By looking at the result of this comparison, we can say that the accumulator is NOT EQUAL to $31, since the result of the compare was not zero. We can also say that the accumulator is GREATER THAN $31, since the CARRY flag is set.
Assume the X register contains $7F and we want to compare it with $7F. We would use the following instruction:
CPX #$7F
The subtract operation inside the 6502 would look like:
$7F = 0 1 1 1 1 1 1 1 $7F = 0 1 1 1 1 1 1 1 _______________ 0 0 0 0 0 0 0 0 = $00
The result is zero, so the ZERO flag is set to 1. The 7 bit of the result is 0, so the SIGN flag is set to 0. No borrow was required, so the CARRY flag is set to 1.
After this comparison is complete, we can conclude that the register is EQUAL to $7F because the ZERO flag is set.
Assume the Y register contains $12 and we want to compare it to $4E. We would use the following instruction:
CPY #$4E
The subtract operation inside the 6502 would look like:
$12 = 0 0 0 1 0 0 1 0 $4E = 0 1 0 0 1 1 1 0 _______________ 1 1 0 0 0 1 0 0 = $C4
Before you get confused with the above binary operation, remember how subtraction works in base 10. If the number being subtracted (minuend) is larger than the subtrahend, a BORROW is necessary from the next higher digit. This case of the compare requires a borrow.
In this case, the ZERO flag will be set to zero, indicating a non-zero result. The SIGN flag will be set to the contents of bit 7 of the result, which is a 1. The CARRY flag will be set to 0, the inverse of the borrow status.
From these flags, we can conclude that the Y register is less than $4E because the CARRY flag is cleared (0).
That's all there is to using the compare instructions. They work the same way, regardless of the addressing mode.
Comparisons are just about worthless without the ability to do something based on the result of a comparison, so next we'll look at the 6502 branch- on-condition instructions.
So far, the only means of transferring program execution we've looked at has been the JMP (JUMP TO LOCATION) instruction. Now we'll look at the 8 branch-on-condition instructions used by the 6502. The 8 formats are:
BCS n (BRANCH IF CARRY = 1) BCC n (BRANCH IF CARRY = 0) BEQ n (BRANCH IF ZERO = 1) BNE n (BRANCH IF ZERO = 0) BMI n (BRANCH IF SIGN = 1) BPL n (BRANCH IF SIGN = 0) BVC n (BRANCH IF OFLOW = 0) BVS n (BRANCH IF OFLOW = 1)
Observant readers may note that the operand of the branch instructions consists of only one byte. As you may recall, the JMP instruction was able to jump to any memory location because its operand consisted of two bytes. Branches are another story altogether.
With only one byte in their operands, branch instructions are only able to branch backward 128 bytes or forward 127 bytes. This is known as RELATIVE addressing. Fortunately, most assemblers will calculate the distance of a branch for you. However, if a branch distance is more than the branch limit, you'll have to restructure your branch by using a JMP or multiple branch instructions.
Let's look at a few typical branch applications. Figure 3 shows the comparison/branch structure for the condition:
IF X = 7 THEN GOTO START
CPX #7 BEQ START . . . START
As you can see, the CPY instruction is followed by a branch instruction. In this case, if the X register is EQUAL TO 7, the program will go to the location labeled START.
For the condition:
IF A <> 52 THEN GOTO POINTA
we would use the code in Figure 4.
CMP #52 BNE POINTA . . . POINTA
Multiple conditions may require some extra effort, such as the condition:
IF Y <= 242 THEN GOTO MAIN
The code for this condition is shown in Figure 5.
CPY #242 BEQ MAIN BMI MAIN . . . MAIN
These multiple conditions are really quite easy, you just have to use the instructions provided.
The nice thing about branch instructions is that you don't have to use them after a compare instruction. You can place them anywhere in a program. For example, in addition or subtraction instructions, which set the status flags just like a compare, a zero result in an operation will set the proper branch flags. Look at the following code:
LDA BYTE1 SEC SBC BYTE2 CMP #0 BEQ ZERO
The CMP #0 instruction is not necessary, since the SBC operation sets the flags for us! The optimized code would look like:
LDA BYTE1 SEC SBC BYTE2 BEQ ZERO
Remember, branches can be done anywhere the status flags are altered, giving you incredible flexibility in program design.
Now we can start combining some of our new programming tools to do meaningful work. With the added function of branching, we can start using the X and Y registers as counters and indexes.
Indexing was discussed in the second installment of Boot Camp in ANALOG #14, so I won't repeat all the basics. The first example I'll show is the use of the X and Y registers as counters.
Let's say we want to execute a section of code ten times. Since the program uses the Accumulator and X register in the loop, we'll use the Y register as a counter to control the loop.
In order to use the X and Y registers as indexes, we have been given four instructions:
INX (INCREMENT X BY 1) INY (INCREMENT Y BY 1) DEX (DECREMENT X BY 1) DEY (DECREMENT Y BY 1)
These four instructions simply add or subtract one from the X or Y registers, allowing you to use the registers as indexes easily. These registers affect the ZERO and SIGN flags.
Figure 6 shows the code necessary to perform a loop ten times.
LDY #10 LOOP . . . DEY BNE LOOP
This is a very simple counter example. Note that, in this case, we have set up the Y register as a countdown counter, from 10 to 0. After the DEY instruction is executed, we BNE LOOP. If the Y register decremented to zero, the program will not take the branch, and the loop is finished. No CPY #0 instruction was needed, since the DEY instruction set the zero flag for us.
We could have used the Y register as a count-up counter, from 0 to 10, as in Figure 7.
LDY #0 LOOP . . . INY CPY #10 BNE LOOP
Note that in the count-up example an extra compare is needed (CPY #10) to see the Y register has reached ten yet. If it has not, the program will take the BNE LOOP branch to continue looping.
Using the X and Y registers for indexing is similar to using them for counters. The main difference is that the register is used inside the loop to point to varying places in memory. Figure 8 shows an example of indexing that will copy the six bytes of TABLEl into TABLE2.
10 LDX #5 20 COPY LDA TABLE1,X 30 STA TABLE2,X 40 DEX 50 BPL COPY 60 BRK 70 TABLE1 .BYTE 10,20,30,40,50,60 80 TABLE2 *=*+6 90 .END
The program in Figure 8 begins with the X register set at 5. Remember, when referencing individual elements in a table, the indexes for the elements range from zero to one less than the number elements. In this case, the element numbers range from 0-5. As the loop (labeled COPY) executes, each byte of TABLE1 will be moved to TABLE2. This looping will continue until the X register is decremented past zero, where it will equal 255 due to wraparound. At this point, the SIGN flag will be 1, indicating a negative number. When this happens, the BPL COPY instruction will be ignored and the looping will end. Try assembling this routine into memory and tracing its execution.
What if we want to copy TABLE1 into TABLE2 in REVERSE ORDER? This is a nifty little problem that will help you understand X-Y indexing more thoroughly. Try writing the code necessary, using as many memory locations as necessary. Next issue, I'll show a way to do this with only three changes to Figure 8.
I had wanted to cover multi-byte math this issue, but due to space limitations I'll have to delay this until next issue. Until then, play around with comparisons and branching, and try to find a solution to the above problem.