As I've been learning the 65816 instruction set for this project, I thought I knew what I was getting into... but as usual, things have a way of becoming twice as interesting as I expected. I'm learning a bunch, however, and I thought that I'd throw out some discoveries and tips as I learn the processor and discover how to use the ISA to build software for the Foenix.
Today's note is about the stack and the interesting hybrid 8/16-bit nature of the 65816 processor.
So the first thing to know is that when you're working with the 65816, it can run in 65C02 emulation mode, with near perfect compatibility with the published 6502 and 65C02 instruction sets. However, the Foenix is going to run in Native mode, which allows the CPU to access 16MB of RAM and store 16-bit values in the accumulator, X, and Y registers.
This is where things get fun. You can switch the registers back and forth between 8 and 16 bit mode on the fly by setting the M and X flags in the P register. The SEP and REP instructions are helpful with this. If you see SEP $20 in a code sequence, that's the programmer setting the accumulator to 8 bit mode.
Which is where the fun comes in. If you're writing public API's, such as kernel code, you need to remember the previous state of the registers when exiting your subroutine.
For example, in the LOCATE API, which moves the cursor around on the screen, I use the accumulator in 16-bit mode to store the memory address the cursor sits on top of. If the screen starts at $1000, and the cursor is on row 5, column 12, that means I need to add 5*80+12 to figure out which location in memory matches the character cell under the cursor.
But here's the thing... when in 16-bit mode, the CPU reads and writes two bytes when using the LDA and STA instructions. Since I'm working with byte-oriented screen memory, I need to set A to 8-bit mode so I can write a single character to the screen. Oh, and did I mention I need to remember what A was before I started the LOCATE routine?
That's where the stack comes in. You can store the value of A on the stack, then pull it back out later. But here's the important part: when A is set to 8 bit mode, only one byte gets pushed onto the stack. And when it's in 16-bit mode, two bytes get pushed. And you don't always know the state of A when you start.
So the key is to remember the width of the register, so that you can set that back when you're done. The way to do that is to push P, the Flags, then recover it before pulling A back off again.
Enter code pattern 1: Preserving Registers
When entering a subroutine, preserve registers that you intend to modify. You can do this by placing them on the stack. And any time you push an unknown value onto the stack, push the flags on afterward. When you reach the end of your routine, pull the flags back off first, then the registers you saved. This ensures that the flags are in the state they were in before your subroutine was called.
Here's one way to preserve the registers.
PHA
PHX
PHY
PHP
do stuff
PLP
PLY
PLX
PLA
However, it's missing something...when you switch A to 8 bit mode, the top 8 bytes are actually preserved in the register. They're just not used most of the time.
So here's another pattern to consider:
PHP ; Preserve the flags
REP #$30 ; Set all registers to 16 bit mode
PHA
PHX
PHY
do stuff.
REP #$30
PLY
PLX
PLA ; Retrieve A
PLP ; Retrieve the flags
This captures the full accumulator, making sure to grab the hidden B value.
Should you use the second pattern? It's up to you. There are arguments to be made for brevity and simplicity vs completeness and preserving calling programs' data. I'd love to hear what experienced '816 developers have to say.
QUOTE:"But here's the thing... when in 16-bit mode, the CPU reads and writes two bytes when using the LDA and STA instructions. Since I'm working with byte-oriented screen memory, I need to set A to 8-bit mode so I can write a single character to the screen. Oh, and did I mention I need to remember what A was before I started the LOCATE routine? "
Can't you just PAD the upper 8 bits with ZEROs ?? $0030h instead of $30h ?
Dan