1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
|
Register Preservation Using The Stack (and a BRK handler)
by Bruce Clark, 7 Jun 2004
__________________________________________________________________
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.
|