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.
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!
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
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.
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.
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
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!
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
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!
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
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: