Commodore Free Magazine, Issue 80 - Part 15
From
Stephen Walsh@39:901/280 to
All on Sat May 17 18:24:12 2014
next higher address. But as many things
computer, it's exactly the opposite, so I prefer to view it this way: the
top of the Stack is the next position at which the data we "push" will go,
and each successive push is made to the next lower address within the range $0100-$01FF. The value of the S Register (the Stack Pointer) keeps track
of this position. Conversely, each pull, which targets data already placed
on the Stack, will read from the next position in an upward direction (in memory). We will discuss pulls in a moment because they are handled
slightly differently, but for right now let's picture the push.
If you have the chance, take a look at a disassembly of the Commodore 64
ROM, specifically the RESET routine at $FCE2. The very first thing the ROM does (after disabling interrupts) is to set the Stack Pointer to the value
$FF, which means the position in memory the next pushed byte will be placed
at is $0100+$FF, or $01FF. Recall that the Stack Pointer is actually an
offset from $0100. Once we push a byte onto the Stack, the Stack Pointer automatically decrements to $FE, so our next push will be to $0100+$FE, or $01FE. With another push the Stack Pointer decrements once more to $FD,
which means the next byte pushed will be placed at $0100+$FD, or $01FD.
This process continues indefinitely, right? Well, not exactly.
What happens when we start with a Stack Pointer value of $FF and push 256
bytes on the stack? The Stack Pointer is an 8-bit register with an implied high byte of $01, so it decrements until it reaches $00 and, if once again decremented, it actually wraps back to $FF (keeping in mind an implied high byte of $01). But what about the first push we made to $01FF? The truth
is that the data we placed with that first push is still in memory at
$01FF, and we can assume the data is important to us, or we wouldn't have
put it there in the first place. So what happens if we inadvertently
"wrap" the Stack, causing the Stack Pointer to once again have a value of
$FF, meaning we placed data to all 256 locations in the range $0100-$01FF? Generally speaking, when this happens we can sum it up with just one word - crash! You can imagine the scenario. But I'll explain it now because it
gives us the opportunity to discuss what a Stack pull is.
THE STACK PULL
By now we have an idea what happens when we place data on the Stack, and we know the function of the Stack Pointer. Armed with this knowledge it is logical to assume we should match every push with a pull in order to
maintain Stack equilibrium. This assumption is correct and the tell-tale
sign you have not achieved equilibrium is the crash. Before getting into retrieving Stack data, it should be mentioned again that any data you do
place on the Stack is never actually removed. Data in any given location
may be over-written, but the Stack is, after all, just regular memory with
a very special hardware pointer keeping track of the relative position of
our next push. It is the Stack Pointer register value which changes
through all of this activity, so as a programmer utilizing the Stack you address data in relative terms as opposed to absolute or indirect
addressing as with normal instructions.
To recap, after pushing data on the Stack the Stack Pointer is
automatically decremented. Pulling data from the Stack is handled in the reverse order. The Stack Pointer is first incremented, then the data is retrieved. The data is not physically removed; it is merely "read" from
the memory at the address pointed to by the Stack Pointer relative to
$0100. It is this concept which can be difficult for the novice to
understand. In fact, if you push data to the Stack and do not over-write
that data with something else it remains right where it is, unaltered, and could, in theory, be retrieved anytime during the life of a program - and
even after a program has finished its execution.
THE SUBROUTINE
We have established that the Stack Pointer is incremented by the CPU before
a pull occurs, and that for subroutine calls the CPU places the return
address onto the Stack (so that the CPU can later find its way back). The
CPU places the address of the last byte of the JSR instruction onto the
Stack, not the actual return address itself, which would be one byte later
in the code stream. So, for the instruction JSR $1234 (stored in memory as $20, $34, $12) the address placed onto the Stack is that which contains the
$12 byte. 65x family processors always store multi-byte data with the low
byte in the lower address, the high byte in the next higher address, so the address $C002 would be placed onto the Stack in reverse order as $02, $C0.
In plain language this means the CPU pushes the high byte of the return
address first, then the low byte. So now the question becomes, "If we come back (return via RTS) to the $12 byte, how do we get to the next
instruction that follows?"
The answer lies with the RTS instruction, which first increments the
multi-byte value it retrieves from the Stack (hopefully it is the return address!). When the return address is pulled (lower addressed byte first)
one is added to the value before it is sent to the Program Counter (it is pre-incremented). An internal carry flag (not the one we use) is cleared
or set based on the result of this first addition. Then the value of the internal carry flag (which is either 0 or 1) is added to the next retrieved byte, or high byte of the return address. So that's it. One is added to
the two-byte return address before it is sent to the Program Counter in the exact same way we add multi-byte numbers with the ADC instruction. Now the Program Counter contains the correct address to continue execution - the address directly after the jump to subroutine instruction.
STACK INSTRUCTIONS
As mentioned, special instructions are used to access the Stack, and the
6502 has a very simple, limited lineup. Pushing data on the Stack is accomplished with the PHA instruction, which reads PusHA Register. This instruction pushes the contents of the A Register, or Accumulator, onto the Stack, and decrements the Stack Pointer. Pulling (retrieving) data
requires the PLA instruction, or PulLA Register. In this case the Stack Pointer is first incremented; then the retrieved data is placed into the Accumulator. As with all data transfers, certain Status Register flags (in this case Z and N) are cleared or set based on the effect the transfer has
on the destination register. The same is not true of push instructions as
no destination register is involved.
Another type of Stack instruction involves getting or setting the value of
the Stack Pointer itself. We will see in Part Two just how valuable these
next two instructions can be when setting up what is known as a Stack
Frame, which is just a fancy name for a place we can store temporary local data. The first instruction is TSX, or Transfer Stack Pointer to X
Register, and it does exactly what it sounds like it does. Its
complementary sibling, TXS, does exactly the opposite. If you managed
earlier to pull out your copy of the Kernal RESET routine you saw this one
in action. It Transfers the contents of the X Register to the Stack
Pointer.
A final Stack instruction pair is PHP/PLP. These instruction push or pull
the Status, or S Register, respectively. PHP pushes the values of all
status flags, while PLP fills the Status Register with the value it pulls
from the Stack. Both instructions, while not used frequently, are
invaluable because they allow you to preserve the Status Register flags,
which is extremely important during interrupt handling.
IN CONCLUSION
Today was all about the 6502 Hardware Stack, but there is so much more to
tell, so we'll pick this up again next time when I will show you how to get tricky with some nifty techniques. Did you know advanced programmers
sometimes place certain data on the stack to make programs auto-run? Or
that the Stack is actually a
--- MBSE BBS v1.0.01 (GNU/Linux-i386)
* Origin: Dragon's Lair ---:- bbs.vk3heg.net -:--- (39:901/280)