6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Sep 21, 2024 3:06 am

All times are UTC




Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Program Counter
PostPosted: Mon Feb 18, 2008 9:53 pm 
Offline

Joined: Thu Jul 05, 2007 4:11 pm
Posts: 33
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.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Feb 18, 2008 10:41 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
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:
        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:
        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:
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.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Feb 19, 2008 4:26 am 
Offline
User avatar

Joined: Thu Mar 11, 2004 7:42 am
Posts: 362
GARTHWILSON 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.


You can also use BRK, since it pushes the program counter onto the stack. If the BRK handler is:

Code:
TSX
INX
INX
LDA $100,X ; lo byte of return address
INX
LDY $100,X ; hi byte of return address
RTI


then after:

Code:
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:
      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:
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:

Code:
HERE: JSR ENTRY1


returns HERE+2+A in A (low byte) and Y (high byte). (Using JSR ENTRY2 returns HERE+2+A+C).


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Feb 19, 2008 6:49 pm 
Offline

Joined: Tue Nov 18, 2003 8:41 pm
Posts: 250
dclxvi wrote:
Code:
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:
 TSX
 LDA $102,X ; lo byte of return address
 LDY $103,X ; hi byte of return address
 RTI


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Feb 19, 2008 9:20 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
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.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 3:56 am 
Offline

Joined: Thu Jul 05, 2007 4:11 pm
Posts: 33
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:
   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   


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 4:46 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
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.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 5:16 am 
Offline
User avatar

Joined: Thu Mar 11, 2004 7:42 am
Posts: 362
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?


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.

GARTHWILSON wrote:
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.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 5:16 am 
Offline

Joined: Thu Jul 05, 2007 4:11 pm
Posts: 33
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.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 5:31 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 9:02 pm
Posts: 1738
Location: Sacramento, CA
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:
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:
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:
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


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 6:30 am 
Offline

Joined: Thu Jul 05, 2007 4:11 pm
Posts: 33
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


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 6:54 am 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
Holy criminey, how easily people fall into the complexity trap.

Code:
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.

Code:
LDY #$01
LDA (pclow),Y


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 8:37 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
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.

Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 20, 2008 4:54 pm 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
Well, that's doable too. :-)

Code:
  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


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Feb 21, 2008 1:25 am 
Offline
User avatar

Joined: Thu Mar 11, 2004 7:42 am
Posts: 362
kc5tja wrote:
Holy criminey, how easily people fall into the complexity trap.

Code:
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:
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. :)


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 24 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: