Suppose we want to evaluate
(1 + 5) - (2 + 3)
Saying how to do this out loud (and being very wordy about it!), we'd
probably say something like:
1
to 5
and stash the result someplace''2
to 3
and stash the result someplace''1
to 5
, and
2
to 3
than how to add the last two
results. So here's another description of how to do it:
1
on a piece of paper and put it on the
table''
5
on a piece of paper and put it on top
of the last piece of paper''
2
on a piece of paper and put it on top
of the last piece of paper''
3
on a piece of paper and put it on top
of the last piece of paper''
This is an example of using a stack. A stack is a natural
data structure to use in applications where you need to retrieve data
from the structure in the reverse of the order in which it is
inserted (like we just did). The classic example is in expression
evaluation like we did above (in fact
there have been many, many pocket calculators on the market that use
stack-based operations. HP is best known, but far from alone). The
stack has two fundamental operations: push
and
pop
. push
puts data on the stack, and
pop
gets it back off (there are a variety of definitions
of stack, with extra operations defined as needed. We only need these
two).
We can do it like this:
push(1)
push(5)
push(pop() + pop())
push(2)
push(3)
push(pop() + pop())
push(-pop()+ pop())
There is actually an alternative notation for arithmetic, developed by
Lukasiewicz, called Polish notation. There are two varieties of
Polish notation; Polish (also called Polish Prefix) and Reverse Polish
(also called Polish Postfix). We'd write the expression
up above as
1 5 + 2 3 + -
in reverse Polish.
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.''
So here's what that arithmetic example from the start of the day would look like on an HC11:
* Code to perform the following algebric equation:
* (1 + 5) - (2 + 3)
RAM equ $0010
STACK equ $00ff
EEPROM equ $f800
RESET equ $fffe
org EEPROM
start lds #STACK * set the stack pointer
ldaa #1 * push(1)
psha
ldaa #5 * push(5)
psha
pulb * push(pop() + pop())
pula
aba
psha
ldaa #2 * push(2)
psha
ldaa #3 * push(3)
psha
pulb * push(pop() + pop())
pula
aba
psha
pulb * push (-pop() + pop())
pula
sba
psha
eloop bra eloop
org RESET
fdb start
(as you simulate this code, you'll be able to watch the data getting put on the stack, but when a pop() happens you won't see the data disappear. Actually taking the time to erase the data from the stack would take extra time, and there's no need to actually do it, but you must never try to access data once it's been popped. It turns out interrupts make use of the stack also, and so the data above the stack pointer can get corrupted at any moment).