Introduction to 6502 Stack Operations
6502 Stack Operations are very useful in programming. You can temporarily store data that you need to retrieve later. Specifically, we might use the stack when calling subroutines in the 6502. We might back up register data that we need to retrieve later on. The stack is an area of memory residing between $0100h and $01FF. Remember, the zero page will reside at $0000 to $00FF, which allows for fast access of data. Since 0000 to $00FF, and $0100 to $01FF are already spoken for, our user programs will usually start at memory cell 0200h.
As we place data on the stack, it grows “upside-down”. In other words, your data will start at the top of the stack area, and work it’s way toward the bottom. Typically, we will remove data from the stack in the reverse order that we added it. To put it another way, the stack is a FILO (First In Last Out).
For this post, I’ll be using the PAL-1 computer. This is a clone of the KIM-1.
One of the 6502 registers is called the Stack Pointer (SP). This register is only 8 bits. The reason it’s only 8 bits is because the stack always resides at $0100 to $01FF. Therefore, we only need to store the least significant byte of the address that our stack points to. Remember, the stack starts at the top of the stack page. Each time we store data to the stack, the stack pointer decrements. Likewise, each time we pull a value from the stack, the stack pointer increments.
Storing Data to the Stack (6502 Stack Operations)
When we jump to a subroutine with the JSR instruction, the processor will place the address of the next instruction onto the stack. When we return from a subroutine with the RTS instruction, the processor removes the address from the stack, and jumps back to the next instruction after the JSR instruction that called the subroutine.
Additionally, we can push our own values onto the stack. It’s important to realize that if we push data onto the stack within a subroutine, we must pull it from the stack within the subroutine. Otherwise, the stack pointer will be at the wrong location for the RTS to know what address to jump back to when we return from the subroutine. Typically a subroutine needs to use the accumulator. For this reason, at the beginning of a subroutine, we might use a PHA to store the accumulator to the stack. Before we return from a subroutine with the RTS instruction, we probably want to restore the accumulator as well. We do this with the PLA instruction. Additionally, you can push and pull the processor status to and from the stack. In this case, you would use the PHP and PLP instructions.
At this point, let’s set up a simple subroutine. After that, we’ll see what our stack pointer is doing.
.ORG $0200 Start: TSX STX $0300 JSR MySubroutine TSX STX $0302 JMP $1C64 MySubroutine: TSX STX $0301 RTS
Obviously, this program originates at $0200. After that, we transfer the stack pointer to the X register with the TSX command. We’ll store X to $0300. At this point, the stack pointer should still be at the top of the stack, which is $FF. After that we jump to “MySubroutine”. The processor will store the address to the stack of where it came from. 64 bit addresses need two bytes. Therefore, after we store these two bytes, the stack pointer should decrease to $FD.. In our subroutine, we store the stack pointer to $0301. After we return from the subroutine, we store the stack pointer back to $0302. Since the processor has pulled the two bytes from the stack, our stack pointer should increase to $FF again. Let’s run our program, and take a look at addresses $0300 to $0302.
Let’s try this one more time. Except this time, we’ll push the accumulator onto the stack in our subroutine, and pull the accumulator back off before we return. Therefore, when we store the stack pointer in our subroutine, $301 should be $FC instead of $FD.
.ORG $0200 Start: TSX STX $0300 JSR MySubroutine TSX STX $0302 JMP $1C64 MySubroutine: PHA TSX STX $0301 PLA RTS
Stack Data in 6502 Stack Operations
Let’s do one last experiment. While we are in the subroutine, let’s simply take a snapshot of the stack data. We’ll load $FF, $FE, and $FD to $0302, $0301, and $0302 respectively. This should give us a snapshot view of the stack when the pointer is still at $FC.
.ORG $0200 Start: LDA #$55 JSR MySubroutine JMP $1C64 MySubroutine: PHA LDA $01FF STA $0302 LDA $01FE STA $0301 LDA $01FD STA $0300 PLA RTS
Now, let’s take a look at our snapshot of the stack.
As you can see, we placed $02, and $04 on the stack first. When the processor sees the RTS instruction, it will return back to $0204. This will be the JMP instruction. The lowest address has a value $55. This is because we loaded the accumulator with 55. Once we started the subroutine, we stored the accumulator to the stack with the PHA instruction. We just don’t want to forget to pull this accumulator back off the stack before executing the RTS.
For more information, visit the KIM-1 Category page!
— Ricky Bryce