A subroutine is a portion of programming that you need again and again in different places in your program, so instead of having another copy of it in each place you need it, you write it once, and tell all those places that need it to just run the one subroutine. The program counter goes to that subroutine, then needs to come back to resume execution in the calling routine when it's done with the subroutine. In order to know where to come back to, the routine that called it needs to have put the return address on the stack.
That's what JSR is all about— "Jump, Saving Return address." (JSR is
usually thought of as "Jump to SubRoutine" which works just as well but is not quite as accurate.) Now suppose that that subroutine
needs to call other ones. No problem, as each JSR puts the return address on the stack, and the return
addresses needed first will be the last ones put on. Nesting is then automatically accommodated. Nesting is when one
subroutine calls another, which calls another, and so on. If you didn't need the nesting, you could put the return address in a
variable or a register; but you can see that the first time you have any nesting, the variable would get overwritten and the first return
address would be lost. The stack comes to the rescue.
Consider this subroutine nesting scenario (the numbers being addresses):
In the diagram above, the stack will contain the following hex numbers at points a, b, and c (not including what might have already been on the stack before a.):
a. b. c. top of stack (TOS) --> $1915 $2632 $2F43 $1915 $2632 $1915What might look wrong there in the table is that at a., the next instruction's address is $1916, not $1915. The RTS (Return From Subroutine) instruction will increment it though, and program execution will resume at $1916. (This is not true of RTI, the ReTurn-from-Interrupt instruction though. This, and the reason for the difference will be covered later. * * * * *)
Since the 6502's data bus and memory are only 8-bit, each address above requires two locations in the
stack. When JSR pushes the return address (minus 1) onto the stack, it first pushes the high byte, then
the low byte. (Remember the stack grows down, and the stack pointer is getting decremented as bytes are pushed
onto the stack, so the resulting byte order is low-byte in lowest memory address, the same as other addresses in 6502.) So let's
suppose for example that the stack pointer (register S) was at $EF before the code entered the diagram and stack chart above. That
would make the contents of actual addresses in the stack's memory space to be like the following chart. (We don't know what was in
$EF to $FF inclusive, so we won't chart those bytes.)
---------stack memory location---------- S $1EF $1EE $1ED $1EC $1EB $1EA $1E9 start: $EF ? ? ? ? ? ? ? at a: $ED $19 $15 ? ? ? ? ? at b: $EB $19 $15 $26 $32 ? ? ? at c: $E9 $19 $15 $26 $32 $2F $43 ?
Now what happens as you exit the subroutines and get back to the main routine? Note that as the addresses are pulled off the stack
to return to, they are not erased; so the chart might then look like this:
---------stack memory location---------- S $1EF $1EE $1ED $1EC $1EB $1EA $1E9 start: $EF ? ? ? ? ? ? ? at a: $ED $19 $15 ? ? ? ? ? at b: $EB $19 $15 $26 $32 ? ? ? at c: $E9 $19 $15 $26 $32 $2F $43 ? after 1st RTS: $EB $19 $15 $26 $32 ($2F) ($43) ? after 2nd RTS: $ED $19 $15 ($26) ($32) ($2F) ($43) ? after 3rd RTS: $EF ($19) ($15) ($26) ($32) ($2F) ($43) ?
The numbers in parentheses are still there, but that part of the stack memory is freed up. Pulling something off the stack just means the processor reads it and then increments the stack pointer. So can you use those numbers, since they're still there? If you have interrupts (which are discussed in the next section), the answer is no, because an interrupt could hit at any time and overwrite them. It violates the LIFO protocol, and interrupts demand that the protocol be followed. Otherwise you could, but it would not be good programming practice, and I can't imagine any valid reason to do it.
As you get more and more deeply nested, ie, going toward the right in the diagram, the stack will become deeper. As you return from subroutines, ie, go toward the left in the diagram, the stack will become shallower.
Here's an animation of the same thing. It draws the stack vertically, which is actually a more accurate representation:
A subroutine can be called by another routine or subroutine any number of times. For example, in the left column of the diagram near the top of the page, the JSR $2604 could be repeated several times before we reach the bottom, ie, the end of the routine that starts at $190A. (Only one of the JSR $2604 calls will reside at address $1913 though.)
So... Why the stack? Couldn't the return address be kept by the subroutine itself? Well, not really. I understand some of the the 60's/70's minicomputers did that; but there are too many problems with it:
Recursion is when a subroutine calls itself over and over until the required exit conditions are met so it can start executing RTS's instead of JSR's. It will be discussed later, in section 15. A possible application is successive-approximation searches.
Note that any given subroutine could be called by many different places in the code, and the nesting will not be to the same depth
every time it is called, and the stack pointer S may be different; so the address its RTS returns to will
not always be stored in the same page-1 locations. It's not a problem.
1. Intro: stack basics <--Previous | Next--> 3. interrupts
last updated Sep 18, 2015