Boot Camp

by Tom Hudson


Well, for the last week or so I've been receiving your solutions to the 5 times 27 multiply problem, and it looks like everybody's got the hang of it. Some people tried to cheat by multiplying 27 by 5. This is a much simpler operation, but we'll see later why this type of shortcut is not always possible.

What happened?!!

Figure 6 from last issue's column was a simple BASIC program that looked like this:

10 GOSUB 10
20 END

I told you to execute it and see if you could determine what went wrong. If you look at the code, you'll see that the program places itself in an infinite loop with the GOSUB 10 statement. If you let the program run for a few minutes, you'll eventually see an ERROR 2 message. What happened? Let's step through the program and find out.

Line 10 executes a GOSUB 10 statement. The next executable statement is Line 20, so the line number 20 is placed on the stack. The program then branches to Line 10. The stack now looks like this:

BASIC stack
 ---------
|    20   |
|---------|
|         | <- POINTER
|---------|
|         |
|---------|
|         |

Line 10 executes GOSUB 10 again, with the same results as above. The line number 20 is placed on the stack again, and execution continues at Line 10 again. Now the stack looks like this:

BASIC stack
 ---------
|    20   |
|---------|
|    20   |
|---------|
|         | <- POINTER
|---------|
|         |

Line 10 performs the same set of operations again, and you can see that the program is in an infinite loop. Each time the GOSUB 10 statement is executed, the BASIC stack gets larger and larger... until there is no more memory available. When this happens, the computer stops with the ERROR 2 AT LINE 10 message.

Obviously, one must take care that all subroutines are terminated by a RETURN. Each subroutine must contain at least one RETURN statement, otherwise you'll find yourself running out of memory far faster than you ever dreamed!

Assembly subroutines

Last issue, as you recall, we found out what a stack is and how BASIC uses a stack to execute subroutines. There is a lot of "housekeeping" done by the system to keep track of subroutines, and we don't want to write all those routines ourselves, do we?

Luckily for us, the 6502 microprocessor has its own set of subroutine instructions. They are: JSR (jump to subroutine), which corresponds to the BASIC GOSUB statement; and RTS (return from subroutine), which performs the same function as the BASIC RETURN statement.

The format of the JSR instruction is:

JSR nn (ABSOLUTE)

The operand of the JSR instruction can be any address, such as JSR $4000, or a program label, such as JSR PRINT.

When the JSR instruction executes, things happen a little differently than they did in our BASIC example, last issue. Instead of a line number being placed on the stack, a two-byte address is used. More on that in a moment.

The format of the RTS instruction is:

RTS

Like the RETURN statement in BASIC, the RTS instruction will continue execution at the instruction following the JSR which called the subroutine.

Let's took at an assembly program which uses the JSR and RTS instructions. For purposes of illustration, we'll duplicate the function of the BASIC program we used last time. Figure 1 is a listing of the assembly program, with the addresses and hex codes of the instructions shown to the left of the line numbers. The corresponding BASIC statements are shown in the comment fields.

0000        10         *=   $0600
0600 D8     15         CLD
0601 200506 20         JSR  SUB1    ;GOSUB 100
0604 00     25         BRK          ;END
0605 200906 30 SUB1    JSR  SUB2    ;GOSUB 200
0608 60     35         RTS          ;RETURN
0609 AD1306 40 SUB2    LDA  VARA    ;VARA=VARA+1
060C 18     45         CLC
060D 6901   50         ADC  #1
060F 8D1306 55         STA  VARA
0612 60     60         RTS          ;RETURN
0613        65 VARA    *=   *+1
0614        70         .END
Figure 1

Let's walk through this program and watch what happens to the stack. Remember, the 6502 does all the stack handling for us, and this walk-through is just to familiarize you with what's happening inside the machine.

Line 15 clears the decimal mode for the binary arithmetic the program will do later. At the start of the program, the stack pointer will be at some arbitrary location. We'll assume that it's set to $00 for this demonstration. The stack at this point looks like this:

       6502 stack <----
       ----------      |
$01FF |          |     |
      |----------|     |
      |          |     |    SP
      |----------|     |    --
      |          |      ---|00|
      |----------|          --
      |          |
      |----------|
      |          |
      |----------|
      |          |

Line 20 performs a JSR to the location labeled SUB1. Before going to the subroutine, the 6502 must save the return address on the stack. The next instruction after the JSR is at $0604, so the 6502 takes this address and subtracts 1 from it, resulting in a return address of $0603. The stack pointer is decremented by 1, and contains $FF. The high byte of the return address ($06) is placed at location $01FF. The stack pointer is decremented again, and now contains $FE. Now the 6502 stores the low byte of the return address ($03) on the stack at location $01FE. The return address is now properly stored, and execution, continues at location $0605, the address of SUB1. At this point, the stack looks like this:

       6502 stack
       ----------
$01FF |    06    |
      |----------|
      |    03    |<----     SP
      |----------|     |    --
      |          |      ---|FE|
      |----------|          --
      |          |
      |----------|
      |          |
      |----------|
      |          |

Line 30 -- Execution continues here after the JSR process is complete. This is another JSR, this time to the subroutine labeled SUB2. As in the previous JSR, the return address minus 1 ($0607 this time) is stored in the next two stack locations, and execution continues at the subroutine. The stack pointer now contains $FC, and the stack looks like this:

       6502 stack
       ----------
$01FF |    06    |
      |----------|
      |    03    |          SP
      |----------|          --
      |    06    |      ---|FC|
      |----------|     |    --
      |    07    |<----
      |----------|
      |          |
      |----------|
      |          |

Lines 40-55 add 1 to the contents of location VARA, placing the result back into VARA. The stack is unchanged by this operation.

Line 60 -- Now we encounter our first RTS instruction. It functions almost like the BASIC RETURN statement, but with a small difference. When executed, the RTS gets the byte from the stack location indicated by the stack pointer and places it in the low byte of the program counter. Remember that the program counter is where the 6502 stores the address of the instruction that is currently being executed. The stack pointer is then incremented (to $FD), the next byte in the stack is placed in the high byte of the program counter, and the stack pointer is incremented again (to $FE). At this point, the program counter contains the return address minus 1, so the program counter is incremented by 1 to get the proper return address. In this case, the return address is $0608, and the program continues there (Line 35). After this instruction executes, the stack will took like this:

       6502 stack
       ----------
$01FF |    06    |
      |----------|
      |    03    |<----     SP
      |----------|     |    --
      |    06    |      ---|FE|
      |----------|          --
      |    07    |
      |----------|
      |          |
      |----------|
      |          |

Line 35 executes another RTS instruction. This time, the program will return to location $0604 (1 byte higher than the location in the last two bytes of the stack). The stack pointer will be incremented twice, and when the program is complete, the stack pointer will contain $00. After this RTS, execution continues at Line 25, and the stack looks like this:

       6502 stack <----
       ----------      |
$01FF |    06    |     |
      |----------|     |
      |    03    |     |    SP
      |----------|     |    --
      |    06    |      ---|00|
      |----------|          --
      |    07    |
      |----------|
      |          |
      |----------|
      |          |

Line 25 stops the execution of the program with the BRK instruction. The stack is unchanged.

Don't panic!

Remember, the 6502 performs all of the stack maintenance functions for you. Writing a subroutine in assembly is just as easy as writing one in BASIC. I've just explained the details of the stack, so that you'll be prepared for next issue's stack-manipulation instructions.

Later on, when you're more comfortable with assembly language and the stack, wall see how we can use the stack for some fancy control structures.

Simple subroutines

Right now, let's see how simple assembly subroutines can be. Let's write a subroutine that will add 1 to a two-byte counter for us.

Let's assume the counter is labeled COUNTL (low byte) and COUNTH (high byte). The normal code we' use to add 1 to this two-byte counter is shown in Figure 2.

LDA COUNTL    ;GET LO BYTE
CLC           ;CLEAR CARRY
ADC #1        ;ADD 1
STA COUNTL    ;SAVE LO BYTE
LDA COUNTH    ;GET HI BYTE
ADC #0        ;ADD WITH CARRY
STA COUNTH    ;SAVE HI BYTE
Figure 2

Clearly, this is just a simple two-byte add operation (if you have problems with addition, review issue 17's Boot Camp).

Let's say you're writing a program which needs to increment this counter in several different places. You could re-type the addition code each time you need it, but this would waste quite a bit of memory. Luckily, you know all about the 6502 JSR and RTS instructions, so you write a simple subroutine to do the job. Figure 3 shows the code necessary.

INCCTR LDA COUNTL    ;GET LO BYTE
       CLC           ;CLEAR CARRY
       ADC #1        ;ADD 1
       STA COUNTL    ;SAVE LO BYTE
       LDA COUNTH    ;GET HI BYTE
       ADC #0        ;ADD WITH CARRY
       STA COUNTH    ;SAVE HI BYTE
       RTS           ;RETURN!
Figure 3

If you look at the subroutine closely, you'll see only two changes from Figure 1! The first line of the subroutine contains the label INCCTR (INCrement CounTeR). This allows us to reference the subroutine with an easy-to-remember name. The other change is the addition of an RTS instruction at the end of the routine. See? Writing ass6mbly subroutines isn't so hard, after all.

To call this subroutine, all we need is the statement:

JSR INCCTR

I'm sure you'll agree that this is much easier than retyping the addition code each time you need to increment the counter. Figure 4 shows a complete program which uses the subroutine in three places.

10          *=  $0600
20          CLD        ;BINARY MATH
30          LDA #0     ;ZERO OUT...
40          STA COUNTL ;COUNTER LO
50          STA COUNTH ;COUNTER HI
60          JSR INCCTR ;INC COUNTER
70          LDX #4     ;5 TIMES...
80 LOOP1 JSR INCCTR    ;INC COUNTER
90          DEX        ;NEXT X
0100        BPL LOOP1  ;LOOP IF POS.
0110        LDA #$50   ;GET # IN ACC.
0120        JSR INCCTR ;INC COUNTER
0130        STA ACCUM  ;SAVE ACCUM.
0140        BRK        ;ALL DONE!
0150 INCCTR LDA COUNTL ;GET LO BYTE
0160        CLC        ;CLEAR CARRY
0170        ADC #1     ;ADD 1
0180        STA COUNTL ;SAVE LO BYTE
0190        LDA COUNTH ;GET HI BYTE
0200        ADC #0     ;ADD W/CARRY
0210        STA COUNTH ;SAVE HI BYTE
0220        RTS        ;RETURN!
0230 COUNTL *=*+1
0240 COUNTH *=*+1
0250 ACCUM  *=*+1
0260        .END
Figure 4

Flexible subroutines

The INCCTR subroutine showed how a subroutine could be written to perform the same function each time. Now we're going to write a subroutine that will perform a function on a value passed to the subroutine in one of the registers. We'll use another familiar routine, multiplication by 27.

We'll write a subroutine which will multiply the contents of the accumulator by 27 and return with the value times 27 in the accumulator.

Those people who took the multiply 27 by 5 shortcut are in for a little surprise! In order for this subroutine to work, the multiply by 27 approach must be used. Take that!

Figure 5 shows the subroutine necessary to multiply the accumulator by 27 and return the result in the accumulator. Only the accumulator is altered; the X and Y registers are untouched. The subroutine requires three one-byte storage locations, TIMES1, TIMES2 and TIMES8.

MULT27 STA TIMES1 ;SAVE NUMBER
       ASL A      ;* 2
       STA TIMES2 ;SAVE # TIMES 2
       ASL A      ;* 4
       ASL A      ;* 8
       STA TIMES8 ;SAVE # TIMES 8
       ASL A      ;* 16
       CLC        ;CLEAR CARRY
       ADC TIMES8 ;*16 + *8 = *24
       CLC        ;CLEAR CARRY
       ADC TIMES2 ;*24 + *2 = *26
       CLC        ;CLEAR CARRY
       ADC TIMES1 ;*26 + *1 = *27
       RTS        ;ALL DONE!
Figure 5

This routine is essentially the same as the multiply by 27 solution shown last issue. The accumulator is assumed to contain the number to be multiplied upon entry into the subroutine. After the multiply is complete, the result is left in the accumulator. The RTS instruction at the end of the routine lets us know that this is a subroutine. The subroutine is labeled MULT27 and is called with the statement:

JSR MULT27

Let's put this subroutine to work, using a program which will multiply the numbers 3, 7 and 9 by 27. We will place the results in locations labeled THREE, SEVEN and NINE, respectively. Figure 6 shows one possible solution.

10          *=  $0600
20          CLD        ;BINARY MATH
30          LDA #3     ;GET 3,
40          JSR MULT27 ;MULT BY 27,
50          STA THREE  ;SAVE RESULT
60          LDA #7     ;GET 7,
70          JSR MULT27 ;MULT BY 27,
80          STA SEVEN  ;SAVE RESULT
90          LDA #9     ;GET 9,
0100        JSR MULT27 ;MULT BY 27
0110        STA NINE   ;SAVE RESULT
0120        BRK        ;AND STOP!
0130 MULT27 STA TIMES1 ;SAVE NUMBER
0140        ASL A      ;* 2
0150        STA TIMES2 ;SAVE # TIMES 2
0160        ASL A      ;* 4
0170        ASL A      ;* 8
0180        STA TIMES8 ;SAVE # TIMES 8
0190        ASL A      ;* 16
0200        CLC        ;CLEAR CARRY
0210        ADC TIMES8 ;*16 + *8 = *24
0220        CLC        ;CLEAR CARRY
0230        ADC TIMES2 ;*24 + *2 = *26
0240        CLC        ;CLEAR CARRY
0250        ADC TIMES1 ;*26 + *1 = *27
0260        RTS        ;ALL DONE!
0270 TIMES1 *=*+1
0280 TIMES2 *=*+1
0290 TIMES8 *=*+1
0300 THREE  *=*+1      ;3*27 RESULT
0310 SEVEN  *=*+1      ;7*27 RESULT
0320 NINE   *=*+1      ;9*27 RESULT
0330        .END
Figure 6

Homework

Now you know how to write subroutines in 6502 assembly language. Subroutines are a powerful programming technique, and open doors into the Atari operating system (OS). Future installments of Boot Camp will show how to access these OS. routines.

Until next time, write a subroutine that will add the X register to the Y register, placing the result in the accumulator. If the result of the add is greater than 255 (carry flag set), put the value $FF in the X register. Otherwise, set the X register to $00. Good luck!

Send all letters to:

Boot Camp
c/o ANALOG Computing
P.O. Box 23
Worcester, MA 01603


Previous | Contents | Next

Original text copyright 1984 by ANALOG Computing. Reprinted with permission by the Digital ANALOG Archive.