Save X and Y. By Lee Davison. |
The code.Not something that comes up very often. I needed some code that would run on an NMOS 6502, not destroy X or Y, return a value in A and was re-entrant, i.e. it could call itself without overwriting any values.
Update.First we save all the registers. We're not really interested in the value of A but we need a byte on the stack.
PHA ; make space for A TYA ; copy Y PHA ; save Y TXA ; copy X PHA ; save XNow we have to restore the registers. Because we don't have PHX/PLX or PHY/PLY we must use A to restore X and Y but we also don't want to overwrite A, we can't just push A onto the stack as that makes getting X and Y off something of a problem. What we do is to overwrite the value for A that is already on the stack and then just pop the registers as we normally would.
TSX ; copy stack pointer STA $0103,X ; write result to stacked A PLA ; pull stacked X TAX ; restore X PLA ; pull stacked Y TAY ; restore Y PLA ; get result into AThis code is also usefull if you don't know which type of 6502 the code will be run on and you need it to work on all of them.
.. need it to work on all of them.Or not as it turns out. There can be a case when the above code doesn't work and that is if the stack is nearly full when the code is entered. While this won't happen very often it could happen and code that works nearly all the time can be frustrating.
Let's look at what happens when it fails, first the code as it saves the registers ..
S Address PHA $02 $0102 ; make space for A TYA $01 ..... ; copy Y PHA $01 $0101 ; save Y TXA $00 ..... ; copy X PHA $00 $0100 ; save X <more code> $FF $01FF ; subroutine codeIn this case A was pushed to address $0102 but after pushing X and Y the stack is full and the stack pointer now points back to the top of the stack. Now when the code tries to save A and exit the following happens ..
X Address TSX $FF ..... ; copy stack pointer STA $0103,X $FF $0202 ; write result to stacked A S Address PLA $00 $0100 ; pull stacked X TAX $01 ..... ; restore X PLA $01 $0101 ; pull stacked Y TAY $02 ..... ; restore Y PLA $02 $0102 ; get result into AOops! We just failed to return the value in A and trashed $0202 as well. The solution is to make X equal the stack pointer when A was first pushed and use this as the offset from the base of the stack page.
TSX ; copy stack pointer INX ; increment X to .. INX ; .. equal stack pointer .. INX ; .. when A was pushed STA $0100,X ; write result to stacked A PLA ; pull stacked X TAX ; restore X PLA ; pull stacked Y TAY ; restore Y PLA ; get result into ANow going through the code showing the register values ..
X Address TSX $FF ..... ; copy stack pointer INX $00 ..... ; increment X to .. INX $01 ..... ; .. equal stack pointer .. INX $02 ..... ; .. when A was pushed STA $0100,X $02 $0102 ; write result to stacked A S Address PLA $00 $0100 ; pull stacked X TAX $01 ..... ; restore X PLA $01 $0101 ; pull stacked Y TAY $02 ..... ; restore Y PLA $02 $0102 ; get result into AThis costs a few bytes and some clock cycles but will work even if the stack pointer should wrap as above.
Last page update: 1st February, 2005. | e-mail me |