Page 1 of 2
Program Counter
Posted: Mon Feb 18, 2008 9:53 pm
by DonnaD
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.
Posted: Mon Feb 18, 2008 10:41 pm
by GARTHWILSON
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
Code: Select all
DISPLAY_IMM "Press YES key to continue"
WAIT_FOR_KEY YESKEY
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
Code: Select all
JSR DISP_QUOTE
BYTE 25 ; (the string length)
BYTE "Press YES key to continue"
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
Code: Select all
ReScan: JSR SCAN_KEYPAD
CMP #YESKEY
BNE ReScan
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.
Posted: Tue Feb 19, 2008 4:26 am
by dclxvi
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.
You can also use BRK, since it pushes the program counter onto the stack. If the BRK handler is:
Code: Select all
TSX
INX
INX
LDA $100,X ; lo byte of return address
INX
LDY $100,X ; hi byte of return address
RTI
then after:
Code: Select all
HERE: BRK
.byte $xx ; any value ($00 to $FF) can be used here
A will contain the low byte of HERE+2 and Y will contain the high byte of HERE+2 (and X gets overwritten).
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
which returns HERE+2+A in A (low byte) and Y (high byte).
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
then:
returns HERE+2+A in A (low byte) and Y (high byte). (Using JSR ENTRY2 returns HERE+2+A+C).
Posted: Tue Feb 19, 2008 6:49 pm
by bogax
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
Posted: Tue Feb 19, 2008 9:20 pm
by GARTHWILSON
That will work as long as you initialized the stack pointer at startup so you won't have a situation where for example $103 + X can get you into page 2 instead of wrapping around to the other end of page 1 where the stack is.
Posted: Wed Feb 20, 2008 3:56 am
by DonnaD
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
Posted: Wed Feb 20, 2008 4:46 am
by GARTHWILSON
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.
Posted: Wed Feb 20, 2008 5:16 am
by dclxvi
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?
BRK pushes P onto the stack AFTER pushing the return address onto the stack, so P is on the top of the stack. You'd have to add a PLP to the beginning of your routine and a PHP to the end; it's a little slower than using $100,X, but it works.
I wouldn't use the BRK instruction though, for a couple of reasons.
I agree, but I would state this as: I wouldn't use both BRK and IRQ, unless neither one needs to be fast. I like BRK a lot, but I don't use IRQ much.
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.
Posted: Wed Feb 20, 2008 5:16 am
by DonnaD
I have a simple ml monitor that is the BRK handler. So the monitor is running as the BRK handler and I would like to get the PC from where the BRK was issued.
Posted: Wed Feb 20, 2008 5:31 am
by 8BIT
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.
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
The code below is my PrintReg routine.
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 ;
The code below is the IRQ handler that decides if its an IRQ or BRK.
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
Hope this helps...
Daryl
Posted: Wed Feb 20, 2008 6:30 am
by DonnaD
Thanks Daryl,
This helped a lot. Can I still use RTI at the end of the BRK routine? Or should I?
I just figured out I can CLI and then RTI..... thanks
Posted: Wed Feb 20, 2008 6:54 am
by kc5tja
Holy criminey, how easily people fall into the complexity trap.
Code: Select all
JSR *+3
PLA
STA pclow
PLA
STA pchigh
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.
Posted: Wed Feb 20, 2008 8:37 am
by GARTHWILSON
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.
Posted: Wed Feb 20, 2008 4:54 pm
by kc5tja
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
Posted: Thu Feb 21, 2008 1:25 am
by dclxvi
Holy criminey, how easily people fall into the complexity trap.
Code: Select all
JSR *+3
PLA
STA pclow
PLA
STA pchigh
Um, generally, the where-am-I routine is useful when the code is not at a known location until run time, and the code needs to know where it is so it can adjust accordingly. For example, if the OS only loads the application, but does not provide relocation, and it is up to the application to relocate itself, then the application needs some way to determine where it is, so that it can adjust the absolute addresses that it needs to. In this case, the JSR version of the where-am-I routine would have to be at a known location (e.g. in ROM, rather than being part of the application). However, the *+3 of JSR *+3 is an absolute address (since JSR uses an absolute, rather than a relative address) and would have to be adjusted before it is encountered.
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
(or here+constant). Any half-decent 6502 linker ought to be able to handle that.
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.
