The stack turns out to be a data structure that is used constantly in computers at the machine level. We'll be spending quite a little while talking about the stack and how we use it.
Of course, the stack has to go in RAM. Following tradition, the stack starts from the top of RAM, and grows "down." This follows the memory map in just about every computer sytem I've ever seen:
What this figure tries to convey is that we allocated global variables and dynamic data structures in the RAM, starting at 0 and allocating each new value at a higher address. The stack is at the other end of RAM, and grows down to meet the globals. This way, we have maximum flexibility in using our tiny RAM; a program that needs more globals can use as much as possible for them, and so can the stack.
An obvious question is, what happens when the globals and the stack meet? They corrupt each other. Don't Do That. This isn't as horrible as it sounds; after all, we're already in a situation where we can try to put variables in EEPROM, try to put code in RAM... in general, we just plain don't have any memory protection, and this is just one more way we can cause ourselves grief if we aren't careful.
So, how do we actually use the stack? That's what comes next....
The stack is so important to the HC11 that there is a specific register dedicated to manipulating it, called SP (for Stack Pointer). The stack pointer is a 16 bit register, since it holds addresses (on the HC11E2 we could get away with an 8 bit stack pointer, of course. But there are other members of the family with more memory, which would need the full width of the stack pointer).
The stack pointer is actually a little bit weird, in that it doesn't actually point to the top of the stack: it points to the next location we are going to push to, tos-1. The reason for this is that it lets interrupts (which we haven't gotten to yet) happen a little bit quicker.
This is done with the lds instruction. We can initialize
it with lds #STACK, where STACK has
previously been equ'ed to $ff.
jsr, bsr, and
rtsTerminology note: early computer languages used the term "subroutine" to mean the same thing we now use "procedure" for. Assembly languages still tend to use "subroutine," and HC11 is no exception.
The first stack operations to talk about are for procedure call and return. As you know, procedure calls are extremely important in high level languages; it should come as no surprise that the machine level provides direct support for them. On the HC11, we have two instructions to call procedures, and one to return from them.
The jsr (jump to subroutine) and bsr (branch
to subroutine) instructions both perform procedure calls. The both
perform the following steps when executed:
jsr or bsr is put where SP is
currently pointing. This address is called the return
address.
Since jsr and bsr do exactly the same thing,
why do we have both instructions? Their relationship is exactly like
the relationship between jmp and bra:
jsr can call a procedure anywhere in the memory of the
machine, while bsr can only call a procedure near the
bsr instruction. But, jsr takes an extra
byte and an extra cycle. Use bsr when you can,
jsr when you have to.
The rts instruction is used to return from a procedure.
When it is executed, it
We don't need two forms of return instruction, since full return address is pushed on the stack no matter which procedure call instruction you use.
Procedure calls also map well to a stack: if procedure A calls procedure B, and B calls C, then C will return to B and B will return to A. The sequence of procedure calls and returns maps perfectly to pushes and pops on a stack. Because of this, virtually all processors provide support in the instruction set for stack operations.
The first piece of support the HC11 has for a stack is the stack pointer, SP. This is another 16 bit register, which is defined to always point to the top of the stack (as we'll see in a minute, this is a slight lie - it doesn't quite point to the top of the stack). There are a small number of instructions that directly manipulate SP:
lds | Load stack pointer |
sts | Store stack pointer |
des | Decrement stack pointer (allocates space on stack) |
ins | Increment stack pointer (deallocates space on stack) |
tsx | Transfer sp+1 to x |
txs | Transfer x-1 to sp |
tsy | Transfer sp+1 to y |
tys | Transfer y-1 to sp |
Motorola refers to pop as pul (that's
actually an old term). So the set of stack operations is:
psha | Push A |
pshb | Push B |
pshx | Push X |
pshy | Push Y |
pula | Pop A |
pulb | Pop B |
pulx | Pop X |
puly | Pop Y |
When the HC11 pushes something, it executes the following sequence of steps:
When it pops something, it does the exact opposite.
We always point the stack pointer at address $ff before
doing anything with it; that's because the stack ``grows down.''
jsr | Jump to Subroutine (and push return address) |
bsr | Branch to Subroutine (and push return address) |
rts | Return from Subroutine (Pop return address into PC) |