Program Counter
Program Counter
Can anyone tell me how to read the Program Counter? Is there a memory address somewhere that contains this in the 65C02? I would like to read the current Program Counter position, and store it to a memory address.
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
On the 6502, since JSR puts the return address on the stack, do a JSR to a routine that reads it off the stack and stores it elsewhere before returning. It's a little messy, but it can be done.
On the 65816, the single instruction PER pushes the current address plus an offset in the operand onto the stack, without affecting A, X, Y, or status. If you don't want an offset, just make the operand reflect that fact.
Tip #38 from my "Tip of the Day" column in the old Delphi forum uses this kind of thing to read the program counter. This is an example from a 6502 application I did in the late 1980's.
<begin quote from viewtopic.php?t=342&postdays=0&postorder=asc&start=38 >
Tip of the day, #38
Don't ignore your assembler' more advanced features. Macros especially make assembly code more concise, even hiding the ugly details of conditional assembly based on input parameters or other factors. Since there's no run-time overhead, macros don't slow the execution down like JSR and RTS do, and a macro may be worth defining even if you only use it once. The simple example
could display the message and then wait in a loop until the user presses the Yes/Continue key. The first line, "display immediate", indicates that the parameter is the actual string to display and not just an address. It might assemble
where the first line calls a subroutine, which uses the return address to pick up the next byte which tells how many bytes following make up the string that is to be displayed, gets the string, and advances the return address past the data to the next actual instruction. The WAIT_FOR_KEY line might assemble
Suddenly assembly is a higher-level language than you thought. A macro may replace hundreds of lines. A complex piece of code may be much more clear with certain portions pre-defined as macros with descriptive names. A macro's parameter list can be as long as you want, as long as it all fits on one line of usually 255 characters. Most assemblers allow nested macros. The assembler's list code output file shows exactly what got assembled.
On the 65816, the single instruction PER pushes the current address plus an offset in the operand onto the stack, without affecting A, X, Y, or status. If you don't want an offset, just make the operand reflect that fact.
Tip #38 from my "Tip of the Day" column in the old Delphi forum uses this kind of thing to read the program counter. This is an example from a 6502 application I did in the late 1980's.
<begin quote from viewtopic.php?t=342&postdays=0&postorder=asc&start=38 >
Tip of the day, #38
Don't ignore your assembler' more advanced features. Macros especially make assembly code more concise, even hiding the ugly details of conditional assembly based on input parameters or other factors. Since there's no run-time overhead, macros don't slow the execution down like JSR and RTS do, and a macro may be worth defining even if you only use it once. The simple example
Code: Select all
DISPLAY_IMM "Press YES key to continue"
WAIT_FOR_KEY YESKEY Code: Select all
JSR DISP_QUOTE
BYTE 25 ; (the string length)
BYTE "Press YES key to continue"Code: Select all
ReScan: JSR SCAN_KEYPAD
CMP #YESKEY
BNE ReScanGARTHWILSON wrote:
On the 6502, since JSR puts the return address on the stack, do a JSR to a routine that reads it off the stack and stores it elsewhere before returning. It's a little messy, but it can be done.
Code: Select all
TSX
INX
INX
LDA $100,X ; lo byte of return address
INX
LDY $100,X ; hi byte of return address
RTI
Code: Select all
HERE: BRK
.byte $xx ; any value ($00 to $FF) can be used here
An even more useful BRK handler is:
Code: Select all
CLC
TSX
INX
INX
ADC $100,X ; add lo byte of return address
INX
LDY $100,X ; hi byte of return address
BCC SKIP
INY
SKIP: RTI
The equivalent for JSR is:
Code: Select all
ENTRY1: CLC
ENTRY2: TSX
INX
ADC $100,X ; add lo byte of return address
INX
LDY $100,X ; hi byte of return address
BCC SKIP
INY
SKIP: RTS
Code: Select all
HERE: JSR ENTRY1
dclxvi wrote:
Code: Select all
TSX
INX
INX
LDA $100,X ; lo byte of return address
INX
LDY $100,X ; hi byte of return address
RTI
do you really need all those INX instructions?
couldn't you just build it into the address?
Code: Select all
TSX
LDA $102,X ; lo byte of return address
LDY $103,X ; hi byte of return address
RTI
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
If BRK puts the PC on the stack, can't I just PLA from both the hi byte and the lo byte off the stack and just store them to memory?
Code: Select all
PLA ;Pull Program Counter Low from stack
STA pchi ;store to pchi
PLA ;Pull Program Couter High from stack
STA pclo ;store to pclo
PHA ;Push pclo back on stack
LDA pchi
PHA ;Push pchi back on stack - GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
You could, if you later use that to get back to where the BRK routine was called; but you can't just do an RTI after you've removed the address from the stack. You would also have to use another instruction to clear the interrupt-disable bit, and probably also check to see if it even should be cleared.
I wouldn't use the BRK instruction though, for a couple of reasons. For one, it requires the interrupt handler to check the B flag to see if it was called by a software or hardware interrupt. (The 65816 does not have this problem as it has a separate BRK vector though.) That adds to the overhead, even for hardware interrupts. For another, BRK sets the interrupt-disable bit I, meaning that interrupts will get delayed unnecessarily, and then you again run into the I bit restoration business mentioned above.
I prefer to use JSR, with the TSX and LDA 102,X etc. in the subroutine which ends with RTS. I haven't ever used BRK since I was on the AIM-65 trainer computers in school over 25 years ago.
I wouldn't use the BRK instruction though, for a couple of reasons. For one, it requires the interrupt handler to check the B flag to see if it was called by a software or hardware interrupt. (The 65816 does not have this problem as it has a separate BRK vector though.) That adds to the overhead, even for hardware interrupts. For another, BRK sets the interrupt-disable bit I, meaning that interrupts will get delayed unnecessarily, and then you again run into the I bit restoration business mentioned above.
I prefer to use JSR, with the TSX and LDA 102,X etc. in the subroutine which ends with RTS. I haven't ever used BRK since I was on the AIM-65 trainer computers in school over 25 years ago.
DonnaD wrote:
If BRK puts the PC on the stack, can't I just PLA from both the hi byte and the lo byte off the stack and just store them to memory?
GARTHWILSON wrote:
I wouldn't use the BRK instruction though, for a couple of reasons.
The (slight) advantage of BRK over JSR is that with JSR, you have to know the address of the where-am-I routine beforehand (at assemble/compile time), which isn't the case for BRK. This probably won't be a major issue, though.
DonnaD,
Here is the Break Handler I use in my SBC-2 monitor.
ACC, Xreg, Yreg, Preg, PCH, PCL, SPtr are all storage locations in page 3 that the PrintReg routine uses to dump the registers after a break.
The code below is my PrintReg routine.
The code below is the IRQ handler that decides if its an IRQ or BRK.
Hope this helps...
Daryl
Here is the Break Handler I use in my SBC-2 monitor.
ACC, Xreg, Yreg, Preg, PCH, PCL, SPtr are all storage locations in page 3 that the PrintReg routine uses to dump the registers after a break.
Code: Select all
BRKroutine sta ACC ; save A Monitor"s break handler
stx Xreg ; save X
sty Yreg ; save Y
pla ;
sta Preg ; save P
pla ; PCL
plx ; PCH
sec ;
sbc #$02 ;
sta PCL ; backup to BRK cmd
bcs Brk2 ;
dex ;
Brk2 stx PCH ; save PC
TSX ; get stack pointer
stx SPtr ; save stack pointer
jsr Bell ; Beep speaker
jsr PrintReg ; dump register contents
ldx #$FF ;
txs ; clear stack
cli ; enable interrupts again
jmp Monitor ; start the monitor
Code: Select all
RegData .byte" PC= A= X= Y= S= P= (NVRBDIZC)="
;
PrintReg Jsr Print_CR ; Lead with a CR
ldx #$ff ;
ldy #$ff ;
Printreg1 iny ;
lda Regdata,y ;
jsr Output ;
cmp #$3D ; "="
bne Printreg1 ;
Printreg2 inx ;
cpx #$07 ;
beq Printreg3 ; done with first 6
lda PCH,x ;
jsr Print1Byte ;
cpx #$00 ;
bne Printreg1 ;
bra Printreg2 ;
Printreg3 dex ;
lda PCH,x ; get Preg
ldx #$08 ;
Printreg4 rol ;
tay ;
lda #$31 ;
bcs Printreg5 ;
dec ;
Printreg5 jsr Output ;
tya ;
dex ;
bne Printreg4 ;
; fall into the print CR routine
Print_CR PHA ; Save Acc
LDA #$0D ; "cr"
JSR OUTPUT ; send it
LDA #$0A ; "lf"
JSR OUTPUT ; send it
PLA ; Restore Acc
RTS ;
Code: Select all
Interrupt PHX ;
PHA ;
TSX ; get stack pointer
LDA $0103,X ; load INT-P Reg off stack
AND #$10 ; mask BRK
BNE BrkCmd ; BRK CMD
PLA ;
PLX ;
jmp (INTvector) ; let user routine have it
BrkCmd pla ;
plx ;
jmp (BRKvector) ; patch in user BRK routine
Daryl
Holy criminey, how easily people fall into the complexity trap.
This will store the address of the first PLA above, less one. If you're doing pointer arithmetic based on this, just remember to add one to your results to get a true pointer. If you're using the PC as a pointer via direct-page addressing, you can often avoid even this, by pre-loading Y with 1 first.
Code: Select all
JSR *+3
PLA
STA pclow
PLA
STA pchigh
Code: Select all
LDY #$01
LDA (pclow),Y
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
I like that! Yeah, I always got more complex, because the subroutines were called from many places in the code and did more than just get the address, like fetch variable-length data embedded in the code, handle appropriately, then calculate where to return to so as to skip the data and land on the next actual instruction.
Last edited by GARTHWILSON on Wed Feb 20, 2008 6:16 pm, edited 1 time in total.
Well, that's doable too. 
Code: Select all
jsr myPrimm ; the hello-world of inline data subroutines, I guess.
.byte "C:\WINDOWS\> RUN; RUN WINDOWS RUN; AWW, C'MON RUN",13,10,0
; one of MANY ways of implementing PRIMM.
.proc myPrimm
pla
sta rpc
pla
sta rpc+1
ldy #1
again:
lda (rpc),y
beq done
jsr chrout
iny
jmp again
done:
iny
tya
clc
adc rpc
sta rpc
lda rpc+1
adc #0
pha
lda rpc
pha
rts
.endproc
kc5tja wrote:
Holy criminey, how easily people fall into the complexity trap.
Code: Select all
JSR *+3
PLA
STA pclow
PLA
STA pchigh
If the location is known at assembly (or link) time, why wouldn't you just use:
Code: Select all
here: LDA #here
STA pclo
LDA #>here
STA pchi
The way I interpreted the original post is that the goal could be stated as: determining "where am I?" in an application, rather than a subroutine within the application determining "where was I called from?". However, what you're suggesting will work just fine for PRIMM, when overwriting A and the zp pointer are acceptable. If I've misinterpreted the question, then feel free to ignore my ramblings.