by Tom Hudson
Welcome to Boot Camp, the beginner's assembly language column. With this issue, we will have completed our introduction to the world of 6502 assembly operation codes. Starting next issue, we'll find out exactly how to apply these instructions in BASIC subroutines, games, utilities and other programs.
Fun with subroutines
Last issue's homework was for you to write a subroutine that would add the X and Y registers, placing the result in the accumulator. If the result of the add was greater than 255, you were to put the value $FF in the X register. If not, you were to set the X register to zero. Figure 1 shows one possible solution. Let's step through it and see how it works.
10 ;SUBROUTINE "ADDXY"
12 ;
14 ;ADDS X REGISTER TO Y REGISTER
16 ;PLACING RESULT IN ACCUMULATOR
18 ;IF RESULT > 255, X REG = $FF
20 ;IF RESULT <= 255, X REG = $00
22 ;
24 *= $0600
26 ADDXY CLD ;BINARY MATH
28 STX TEMP ;SAVE X REG.
30 TYA ;PUT Y IN ACC.
32 CLC ;CLEAR FOR ADD
34 ADC TEMP ;ADD X REGISTER
36 BCS GTR255 ;BRANCH IF > 255
38 LDX #$00 ;ZERO X REGISTER
40 RTS ;AND RETURN!
42 GTR255 LDX #$FF ;SET X REGISTER
44 RTS ;AND RETURN!
46 TEMP *=*+1
48 .END
- Lines 10-22 are the subroutine documentation lines. They tell what the subroutine does and how to use it. This can help refresh your memory if you need to change a program several years after you write it.
- Line 26 is the entry point for the subroutine. I have labeled this one ADDXY, for "Add X and Y registers." It's a good idea to use descriptive labels in your programs. I could have called the subroutine DOG, but this wouldn't help me remember what the subroutine does. This line clears the decimal mode, so that we're sure the subroutine is operating in binary math mode.
- Line 28 stores the X register at the location TEMP, a temporary hold area.
- Line 30 transfers the Y register to the accumulator with the TYA instruction. This is done because the 6502 add instruction (ADC) only works with the accumulator.
- Line 32 clears the carry flag for the add operation.
- Line 34 adds the accumulator (which now contains the Y-register value) to the location TEMP (which contains the X-register value). After this instruction executes, we have completed the first part of the homework, adding the X and Y registers with the result in the accumulator.
- Line 36 branches to the label GTR255 (Greater than 255) if the carry flag is set (BCS). If the carry is not set, execution continues at Line 38. Remember that the carry flag is set if the result of an add operation is greater than 255. Review the issue 17 Boot Camp if you're not sure of the carry flag's function.
- Line 38 places a zero in the X register if the add result was not greater than 255. The X register in this case is used as an indicator to tell the code which called the subroutine that the addition result fits in the accumulator. If the carry flag had been set, the result was greater than 255 and would not have fit in the 8-bit accumulator.
- Line 40 is an RTS instruction. This will return control to the code which called the subroutine.
- Line 42, labeled GTR255, is the code that will be executed if the add result is too large for the accumulator. It loads the X register with the value $FF. Once again, after the subroutine has been executed, the calling routine can test the X register. If the X register contains $FF, the calling routine can take the appropriate action.
- Line 44 is another RTS instruction, and will return control to the calling code.
- Line 46 defines a one-byte temporary storage location, labeled TEMP.
How would we use this subroutine? Figure 2 shows an example of the code necessary to call the subroutine ADDXY.
LDX ADD1 ;GET ADD #1
LDY ADD2 ;GET ADD #2
JSR ADDXY ;ADD X & Y
CPX #$00 ;ADD OK?
BNE BADADD ;NO!
STA RESULT ;ADD OK!
JMP OK ;JUMP ELSEWHERE
BADADD JMP NOTOK ;HANDLE ERROR
As you can see, this code first loads the X and Y registers with the desired add values, then JSRs to the subroutine.
The first instruction after the JSR tests the X register to see if it's zero. If not, the add was too large for the accumulator, and we branch to the label BADADD. If the add was okay, we store the accumulator in the location labeled RESULT and jump to another part of the program, labeled OK.
Of course, the use of the X register as an overflow flag was not really necessary in this problem. We could have simply tested the carry flag after the JSR and taken the appropriate action then. Still, I thought this would be a good time to introduce you to the technique of using subroutine result indicators.
So there you have it. Just one of the many ways in which the homework assignment can be solved. I'm sure most of you came up with other ways to accomplish the objective, and as long as they work it doesn't matter which approach you take. Just remember to thoroughly test each subroutine you write, to be sure they'll return the proper results.
Getting pushy
Up till now, all our stack usage has been handled by the 6502 itself, in the JSR and RTS instructions. Now we're going to find out how to use the stack for our own purposes.
The first two stack instructions we're going to investigate are the PHA (Push accumulator onto stack) and PLA (Pull accumulator from stack). The format of the PHA instruction is:
PHA (NO ADDRESSING)
The PHA instruction is used to place the accumulator on the "top" of the stack. It doesn't affect any status flags. Let's see what happens when a PHA instruction executes.
6502 stack <----
---------- |
$01FF | | |
|----------| |
| | | SP
|----------| | --
| | ---|00|
|----------| --
| |
Figure 3 shows how the stack looks when it's empty. The stack pointer (SP) contains $00. As you recall from the last two Boot Camp installments, the 6502 stack resides in the memory from $0100-01FF.
Let's assume the following two instructions are executed:
LDA #$40
PHA
The first instruction loads the accumulator with the value $40. The second instruction "pushes" this value onto the stack. The 6502 decrements the stack pointer (to $FF), then stores the accumulator's contents at the indicated memory location. Figure 4 shows how the stack looks after the PHA instruction.
6502 stack
----------
$01FF | 40 |<----
|----------| |
| | | SP
|----------| | --
| | ---|FF|
|----------| --
| |
If we like, we can push another value onto the stack. Let's push the value $6D onto the stack this time. Here's the code:
LDA #$6D
PHA
This time, the stack pointer will be decremented (to $FE), and the value $6D stored at the indicated location. Figure 5 shows how the stack looks now.
6502 stack
----------
$01FF | 40 |
|----------|
| 6D |<---- SP
|----------| | --
| | ---|FE|
|----------| --
| |
See how simple the PHA instruction is? No registers except the stack pointer are affected, and the numbers are sitting on the stack, ready for you to use them. How do we get them back? With the PLA instruction, of course!
Not like pulling teeth
Once you have numbers stored on the stack, they're incredibly easy to retrieve. We simply use the PLA instruction. Its format is:
PLA (NO ADDRESSING)
The PLA instruction takes the first number on the stack, places it in the accumulator, sets the SIGN and ZERO flags accordingly, and increments the stack pointer so that the next value is ready to be pulled from the stack. Let's see how this works with the numbers we placed on the stack earlier.
Figure 5 shows the stack as it appears now. We want to pull a value off the stack, so we write the following code:
PLA
The 6502 loads the accumulator from the indicated byte of the stack ($6D) and increments the stack pointer. At this point, the accumulator contains $6D, and the stack looks like Figure 6.
6502 stack
----------
$01FF | 40 |<----
|----------| |
| 6D | | SP
|----------| | --
| | ---|FF|
|----------| --
| |
Simple, right? We've just retrieved the last number placed on the stack. Let's do it again. We use the code:
PLA
When complete, the accumulator contains $40, and the stack looks like Figure 7.
6502 stack <----
---------- |
$01FF | 40 | |
|----------| |
| 6D | | SP
|----------| | --
| | ---|00|
|----------| --
| |
Now you see how easy stack usage is. All you need to do is push and pull the desired values, and the computer takes care of all necessary overhead. However, there are a few things you need to remember when using the stack.
Stack logic
The first thing you must remember about the stack is that it is a LIFO (Last-In, First-Out) structure. That is, the last number you place onto the stack will be the first number that you pull off. This sometimes takes getting used to, but you'll get the hang of it if you diagram your stack logic on paper first.
Second, the stack can only hold up to 256 numbers, and some space on the stack is used by the system. A good rule of thumb is to use the stack only when you need to, like in BASIC USR calls or when you're running out of memory (a PHA only takes one byte; an STA can take up to three bytes).
Using the stack
What can you use the stack for? Most people use it to store numbers temporarily or as a small table that automatically maintains pointers.
Here's an example of using the stack to save the accumulator's contents when a subroutine is executed. Remember that when a subroutine is executed, if it uses any registers, the values that were in those registers are lost.
Figure 8 shows how to save the accumulator so that you can be sure it is unchanged after a subroutine executes.
10 PHA ;SAVE ACCUMULATOR
20 JSR SUBTRN ;PERFORM SUBROUTINE
30 PLA ;RESTORE ACCUMULATOR
- Line 10 pushes the accumulator's contents onto the stack. Now, no matter what the subroutine does with the accumulator, we can always restore the accumulator to its original value.
- Line 20 calls the subroutine SUBRTN with the JSR instruction. We assume that the subroutine manipulates the accumulator, changing it to some unknown value.
- Line 30 pulls the old accumulator value off the stack, making sure that we have the accumulator restored to the desired value.
Unfortunately, the designers of the 6502 did not allow for the PUSHing of the X and Y registers, so we have to write a little extra code.
To push the X register, we use the code:
TXA ;MOVE X TO ACCUM.
PHA ;AND PUSH IT!
This transfers the X register to the accumulator, then pushes the value onto the stack.
Similarly, the Y value register can be pushed with the sequence:
TYA ;MOVE Y TO ACCUM.
PHA ;AND PUSH IT!
To pull the X or Y registers from the stack, use one of the following code sequences:
PLA ;PULL THE VALUE,
TAX ;AND PUT IN X!
PLA ;PULL THE VALUE,
TAY ;AND PUT IN Y!
These routines are simple enough, but you should remember that the accumulator will be lost in all of these operations unless you save it somewhere first.
Saving your status
Sometimes you'll want to save the processor status register before a subroutine or comparison operation so that you can test certain flags later. This can be done by using the PHP (Push processor status register onto stack) and PLP (Pull processor status register from stack) instructions. Their formats are:
PHP (NO ADDRESSING) PLP (NO ADDRESSING)
The PHP and PLP instructions work just like the PHA and PLA instructions,except that they push and pull the status flags instead of the accumulator.
The PHP instruction does not affect any flags, but the PLP instruction changes all the flags, since it is actually loading the flags from the stack.
We'll explore the use of the PHP in more detail later, when the need arises.
Which way to the stack?
Someday, you may need to know where the stack pointer is currently pointing, or you may need to change the stack pointer to point to a particular location. This is usually a rare occurrence, but I needed to do this in my debug utility, HBUG, in issue 18.
The 6502 has two instructions that will allow us to examine and change the stack pointer. These are TSX (Transfer stack pointer to X) and TXS (Transfer X to stack pointer). The formats of these instructions are:
TSX (NO ADDRESSING) TXS (NO ADDRESSING)
The TSX instruction simply loads the X register with whatever happens to be in the stack pointer at the time. The sign and zero flags reflect the result of the load.
Figure 9 shows an example of the use of the TSX instruction.
10 *= $0600
12 LDA #$F0 ;PUT # IN ACCUM.
14 TSX ;GET STACK PTR
16 STX STACK1 ;SAVE STACK #1
18 PHA ;PUSH ACCUM.
20 TSX ;GET STACK PTR
22 STX STACK2 ;SAVE STACK #2
24 PLA ;ACCUM.
26 TSX ;GET STACK PTR
28 STX STACK3 ;SAVE STACK #3
30 BRK ;ALL DONE!
32 STACK1 *=*+1
34 STACK2 *=*+1
36 STACK3 *=*+1
38 .END
Let's walk through this code and see what happens.
- Line 12 loads the accumulator with $F0.
- Line 14 transfers the current contents of the stack pointer to the X register.
- Line 16 stores the X register (which now contains the stack pointer value) in the location STACK1. This records the original stack location, so we can observe it later.
- Line 18 pushes the accumulator onto the stack. As we now know, the stack pointer will be decremented by 1 after this operation.
- Line 20 transfers the stack pointer to the X register again.
- Line 22 stores the X register (containing the stack pointer value) in the location STACK2. This will record the stack's position after the PHA instruction.
- Line 24 pulls the accumulator from the stack.
- Line 26 transfers the stack pointer to the X register a final time.
- Line 28 stores the stack pointer contained in the X register at the location STACK3.
- Line 30 stops the program's execution.
Type this program into your computer and assemble it. Note the locations of STACK1, STACK2 and STACK3 during the assembly. When the program is assembled, execute it.
After execution, examine the memory locations at STACK1, STACK2 and STACK3. STACK1 contains the stack's location at the beginning of the program. STACK2 contains the stack's location after the PHA instruction. Since the PHA decrements the stack pointer, STACK2 should be one less than STACK1.
STACK3 contains the stack pointer's contents after the PLA instruction. A PLA instruction increments the stack pointer, so STACK3 will be one more than STACK2.
The TXS instruction does the opposite of TSX. That is, you can move the contents of the X register to the stack pointer. To do this, you simply load the X register with the desired value and execute a TXS instruction, like so:
LDX #$40 ;STACK AT $0140
TXS ;POINT THERE!
I strongly suggest that you leave this instruction alone for the time being. Incorrect setting of the stack pointer can cause a system lockup, so hold on until we get a chance to use it safely in a Boot Camp program.
All for now
Well, we've covered all the major 6502 instructions, and were ready to learn some system-specific material. Starting next issue, we'll go full speed ahead into the world of the Atari's innards.
Send all letters to:
c/o ANALOG Computing
P.O. Box 23
Worcester, MA 01603
Digitized by Eric Petrich for CyberRoach’s Digital A.N.A.L.O.G. Archive; restored here with permission.
