On the 65C02, a subroutine can save the A, X, and Y registers without altering them by using:
PHA PHX PHY
The subroutine then restores A, X, and Y by exiting with:
PLY PLX PLA RTS
The PHX/PLX and PHY/PLY instructions are not available on the 6502. On the 6502, A, X, and Y are often saved using:
PHA TXA PHA TYA PHA
which preserves X and Y, but overwrites A. The subroutine then restores A, X, and Y by exiting with:
PLA TAY PLA TAX PLA RTS
One way to preserve A (to allow it to be passed as a parameter to the subroutine) -- as well as X and Y -- is to use:
STA TEMP PHA TXA PHA TYA PHA LDA TEMP
which takes 4 additional bytes and 6 additional cycles if TEMP is a zero page location, and 6 additional bytes and 8 additional cycles if TEMP is an absolute memory location.
This is usually sufficient, but if, for example, the subroutine is used by the main program and by an interrupt routine, interrupts must be disabled so that TEMP will not be in use when the interrupt occurs.
An alternative is to use:
PHA TXA TSX PHA TYA PHA INX LDA $100,X ; get A from the stack
which takes 5 additional bytes and 8 additional cycles (as compared to the the first 6502 example, not the previous example) but interrupts need not be disabled since it gets A from the stack rather than requiring a special memory location. This could be used in a subroutine called by an NMI (since an NMI can't be disabled). Remember, NMIs are dangerous, and should be used with extreme caution if at all.
Notice that the idea is to get the stack pointer into X as soon as possible so that the number INX instructions needed can be minimized.
The code above works correctly regardless of the value of the stack pointer. As long as the stack pointer is not $FF, the last two instructions could be replaced with a LDA $101,X instruction, which would save 1 byte and two cycles. Ordinarily, this is not a problem since the stack pointer is usually initialized to $FF (and will therefore be less than $FF after the PHA). It goes without saying that the software should be fully debugged before even considering the LDA $101,X change.
The code above preserves A, but overwrites X. To preserve A, X, and Y, use:
PHA TXA TSX PHA TYA PHA INX LDA $100,X ; get A from the stack PHA DEX LDA $100,X ; get X from the stack TAX PLA
which is 7 bytes and 15 cycles longer than the previous example. This may be more trouble than it is worth, but it could come in handy for subroutines that are used for debugging (where the idea is to be as independent as possible and to stay as far out of the way of the main program as possible).
The preceding techniques can also be applied to the BRK/IRQ interrupt handler. For example, on the 6502,
PHA TXA TSX PHA INX INX LDA $100,X ; get the status register from the stack AND #$10 ; mask B flag BNE BREAK BEQ IRQ
can be used to determine whether the interrupt was caused by a BRK instruction or an IRQ. Ordinarily, of course, the code would simply fall through to the IRQ handler, eliminating the BEQ instruction. The BRK and IRQ handlers would then exit with:
PLA TAX PLA RTI
Alternatively, A and X could be restored immediately after branching (or falling through) to the BRK or IRQ interrupt handler.
On the 65C02, with PHX and PLX available,
PHX TSX PHA INX INX LDA $100,X ; get the status register from the stack AND #$10 ; mask B flag BNE BREAK BEQ IRQ
can be used, which takes 1 fewer byte and 2 fewer cycles than the 6502 example. The BRK and IRQ handlers would then exit with:
PLA PLX RTI
which also takes 1 fewer byte and 2 fewer cycles than the 6502 example.
Note that A and X are saved in the opposite order that they were in the 6502 example!
The B (Break) flag, bit 4 of the P (Processor status) register, is a frequent source of confusion on the 6502. The sole purpose of this flag is to distinguish a BRK from a IRQ. However, the behavior of BRK and IRQ (and how to distinguish between the two) can be described without even mentioning the B flag.
After the return address has been pushed onto the stack, a BRK instruction pushes the value of the P register ORed with $10, then transfers control to the BRK/IRQ interrupt handler.
After the return address has been pushed onto the stack, an IRQ interrupt pushes the value of the P register ANDed with $EF, then transfers control to the BRK/IRQ interrupt handler.
This means that the value of the P register that was pushed onto the stack must be used to distinguish a BRK from a IRQ, not the value of the P register upon entry to the BRK/IRQ interrupt handler. Specifically,
PHA PHP PLA AND #$10 ; mask B flag BNE BREAK BEQ IRQ
does NOT properly distinguish a BRK from an IRQ!
Chelly adds: At the end proper checking of the B flag is discussed. My feedback is that it would be simpler to explain that there is no B flag in the processor status register; that bit is simply unused. When pushing the status register on the stack, that bit is set to a fixed value based on the instruction/event (set for PHP and BRK, clear for NMI). This is much simpler to explain and leads to no incorrect assumption that there is a B flag in the status register that can be checked.
Last page update: November 11, 2006.