6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Fri Sep 20, 2024 9:27 pm

All times are UTC




Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Subroutine parameters
PostPosted: Tue Mar 13, 2018 1:11 am 
Offline
User avatar

Joined: Tue Mar 13, 2018 12:48 am
Posts: 2
Hi,

Programming is nothing new to me, but 6502 assembler is kicking my ass right at the start.

I figured a minimum amount of infrastructure involves being able to pass parameters to a subroutine.
Some people call this inline parameter, immediate embedded data etc.
I've seen examples of people saying this is what they do, but never how they do it.

Basically, the simplest representation of what I want to do is:

Code:
doSomething:
   -magic to get the parameters in to accumulator-
   ...
rts

...

jsr doSomething
   !byte #$12,#$23,#$34,#$45

jsr doSomething
   !byte #$23,#$45,#$56,#$12


Extracting the address a parameter is on using the stack is no problem, but I can't figure out how to access the data in it because it's two bytes and lda ($FF),Y can only access the zero-page area.
I feel like there is a basic and important piece of the puzzle I'm missing.

Could somebody please show me an example of this that works?
It's not important if it's in ACME or tass or whatever, I just need to know how to execute the concept.


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 13, 2018 2:07 am 
Offline
User avatar

Joined: Tue Oct 25, 2016 8:56 pm
Posts: 362
Using 6502 I can't think of a "nice" way to do what I think you're asking to do; that is having data bytes immediately after the JSR call which are used as inputs to the subroutine called by the JSR.
The code below is my best attempt, though I'm sure other members of this forum will no doubt pick a million holes in it; they usually do.

Code:
RstVector:
    LDX     #$FF
    TXS                     ; S = $FF

main:
    JSR     doSomething     ; ($1FE.$1FF) = return address - 1, S = $FD
    BRA     ?paramSkip      ; skip over the parameter bytes on return
    db      $12             ; 1st parameter
    db      $23             ; 2nd parameter
?paramSkip:
    JMP     main

doSomething:
    TSX                     ; X = S = $FD
    LDA     $101, X         ; A = ($100+1+X) = low byte of return address
    STA     <$00            ; ($00) = low byte of return address
    LDA     $102, X         ; A = ($100+2+X) = high byte of return address
    STA     <$01            ; ($01) = high byte of return address
    LDY     #$03            ; We want the first parameter byte
                            ; but we also need to skip over the BRA
    LDA     ($00), Y        ; A = ($00.$01)+Y = $12
    ; ... do things
    INY                     ; Y = $04, 2nd parameter byte
    LDA     ($00), Y        ; A = ($00.$01)+Y = $23
    ; ... do things
    RTS

This clobbers both X and Y in the process of extracting the values, and requires two bytes of zero page to do the job.

An alternative might be:
Code:
RstVector:
    LDX     #$FF
    TXS                     ; S = $FF

main:
    LDA     #$12
    PHA                     ; ($1FF) = $12, S = $FE
    LDA     #$23
    PHA                     ; ($1FE) = $12, S = $FD
    JSR     doSomething     ; ($1FC.$1FD) = return address - 1, S = $FB
    JMP     main

doSomething:
    TSX                     ; X = S = $FB
    LDA     $104, X         ; A = ($100+4+X) = $12
    ; ... do things
    LDA     $103, X         ; A = ($100+3+X) = $23
    ; ... do things
    RTS

This code has the advantage of not clobbering Y, being in my opinion tidier, and generalizable to non-constant parameters. The downside is requiring more code at the call site.

_________________
Want to design a PCB for your project? I strongly recommend KiCad. Its free, its multiplatform, and its easy to learn!
Also, I maintain KiCad libraries of Retro Computing and Arduino components you might find useful.


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 13, 2018 3:29 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
The '02 is inefficient but not incapable at handling data as inlined parameters. Once again, the 6502 stacks treatise addresses it. The relevant page here is #7, "Inlining subroutine data", followed possibly by #13, "65816's instructions and capabilities relevant to stacks, and 65c02 code which partially synthesizes some of them.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 13, 2018 5:19 am 
Offline

Joined: Fri Apr 15, 2016 1:03 am
Posts: 139
There are several parameter passing options on the 6502, but no single obvious standard.


Here are some samples.
I assume a 2byte scratch variable "tmp" in zero page. As you've noticed, a pointer variable needs to be in zero page to use "lda (tmp),y".


Load the A, X, & Y registers with parameters before doing the jsr:
Code:
doSomething: ; expects a pointer in YA (hi byte in Y, lo byte in A)
   sta 0+tmp   ;save the pointer in a direct page temp variable
   sty 1+tmp

   ldy #0      ;access 1st byte of pointed-to data
   lda (tmp),y
   ...

   rts
-----
   lda #<dat1
   ldy #>dat1
   jsr doSomething
   ...

dat1:   .byte $12,$23,$34,$45



Inline data after the jsr:
Code:
doSomething: ; expects inline data
   pla      ;tmp=rts_address popped from the stack
   sta 0+tmp   ;  the rts address points to the last byte of the jsr
   pla
   sta 1+tmp

;   tsx      ;or stack entries can be accessed withuot popping them
;   lda $101,x   ;tmp=rts_addr
;   sta 0+tmp
;   lda $102,x
;   sta 1+tmp

   ldy #1      ;access 1st byte of pointed-to data
   lda (tmp),y
   ...

   ; return, skipping the inline data
   clc
   lda 0+tmp
   adc #4
   tay
   lda 1+tmp
   adc #0
   pha
   tya
   pha
   rts
------
   jsr doSomething
   .byte $12,$23,$34,$45
   jsr doSomething
   .byte $23,$34,$45,$56



Pushing parameters on the stack before the jsr:
Code:
doSomething: ; expects a pointer on the stack
      ;stack layout:
      ;   .word param1
      ;   .word rts_addr
      ;  S -->
   tsx      ;tmp=param1
   lda $103,x
   sta 0+tmp
   lda $104,x
   sta 1+tmp

   lda #0      ;access 1st byte of data pointed-to by param1
   lda (tmp),y
   ...

   rts
-----
   lda #>dat1   ;push pointer to dat1
   pha
   lda #<dat1
   pha
   jsr doSomething
   pla      ;clean up stack
   pla
   ...
   lda #>dat2   ;push pointer to dat2
   pha
   lda #<dat2
   pha
   jsr doSomething
   pla      ;clean up stack
   pla
   ...

dat1:   .byte $12,$23,$34,$45
dat2:   .byte $23,$34,$45,$56



Using predefined variables:
Code:
doSomething: ; expects pointer in direct page location parm1
   ldy #0      ;access 1st byte of data pointed-to by param
   lda (parm1),y
   ...
   rts
-----
   lda #<dat1   ;parm1=&dat1
   sta 0+parm1
   lda #>dat1
   sta 1+parm1
   jsr doSomething
   ...
   lda #>dat2   ;parm1=&dat2
   sta 0+parm1
   lda #<dat2
   sta 1+parm1
   jsr doSomething
   ...

dat1:   .byte $12,$23,$34,$45
dat2:   .byte $23,$34,$45,$56



Using a parameter software stack (borrowed from Forth):
Code:
doSomething: ; expects a pointer to data on the parameter stack
   ldx ParmStkPtr
   lda 0+ParmStk,x   ;tmp=top of parameter stack
   sta 0+tmp
   lda 1+ParmStk,x
   sta 1+tmp
   inx      ;pop parameter stack
   inx
   stx ParmStkPtr

   ldy #0      ;access 1st byte of data pointed-to by param
   lda (tmp),y
   ...
   rts
-----
   lda #>dat1   ;push &dat1 on ParmStk
   ldy #<dat1
   ldx ParmStkPtr
   dex
   dex
   sta 0+ParmStk,x
   sty 1+ParmStk,x
   jsr doSomething
   ...
dat1:   .byte $12,$23,$34,$45



And I'm sure there are more possibilities.


Last edited by leepivonka on Wed Mar 14, 2018 3:45 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 13, 2018 1:42 pm 
Offline
User avatar

Joined: Tue Mar 13, 2018 12:48 am
Posts: 2
Thanks everyone!

The leepivonka examples were exactly what I needed to actually connect everything together,
and Alarm Siren's "A = ($00.$01)+Y = $12" comment gave me a great visualization for how to think about lda($ff),y without getting confused.

And I hadn't even considered you could read the stack directly so that's an extra bonus :)


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 13, 2018 4:33 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
BTW triangular, welcome to this excellent and friendly forum!


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 13, 2018 6:25 pm 
Offline

Joined: Sat Jan 02, 2016 10:22 am
Posts: 197
I've been digging around in the Acorn Atom disc rom recently, this is how Acorn handled inline text printing back in the day. There's no reason why the same process can't be extended to handle other data.

Code:
                     ;print zero/negative terminated inline text string
                     .instring
E016 : 68          : PLA
E017 : 85 EA       : STA &EA
E019 : 68          : PLA
E01A : 85 EB       : STA &EB          ;pull the return address and save in zero page
E01C : A0 00       : LDY #&00         ;clear the index as 6502 only has (zp),Y
                     .instrnext
E01E : E6 EA       : INC &EA          ;pre increment the pointer, as JSR pushes return address-1
E020 : D0 02       : BNE instr1
E022 : E6 EB       : INC &EB
                     .instr1
E024 : B1 EA       : LDA (&EA),Y
E026 : 30 08       : BMI instrdone    ;invalid char exit and continue
E028 : F0 06       : BEQ instrdone    ;or zero means exit and error - as 0 is BRK
E02A : 20 F4 FF    : JSR OSWRCH       ;Acorn MOS call to sent A to the print stream
E02D : 4C 1E E0    : JMP instrnext    ;no branch always, so jump used.
                     .instrdone
E030 : 6C EA 00    : JMP (&00EA)      ;resume processing via indirect jump



Then later in the rom, the code is mostly called to print error messaged such as
Code:
                     .name_error
E0AB : 20 16 E0    : JSR instring
                     DS "NAME?"
E0B3 : 00            BRK


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 13, 2018 6:49 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
Here's a later effort by Acorn, from the MOS version 1.20 for the BBC Micro:
Code:
FA46   JSR &E7DC   ;check if free to print message
FA49   TAY         ;Y=A
FA4A   PLA         ;get back A
FA4B   STA &B8     ;&B8=8
FA4D   PLA         ;get back A
FA4E   STA &B9     ;&B9=A
FA50   TYA         ;A=Y
FA51   PHP         ;save flags on stack
FA52   INC &B8     ;
FA54   BNE &FA58   ;
FA56   INC &B9     ;
FA58   LDY #&00    ;Y=0
FA5A   LDA (&B8),Y ;get byte
FA5C   BEQ &FA68   ;if 0 Fa68
FA5E   PLP         ;get back flags
FA5F   PHP         ;save flags on stack
FA60   BEQ &FA52   ;if 0 FA52 to get next character
FA62   JSR OSASCI  ;else print
FA65   JMP &FA52   ;and do it again

FA68   PLP         ;get back flags
FA69   INC &B8     ;increment pointers
FA6B   BNE &FA6F   ;
FA6D   INC &B9     ;
FA6F   JMP (&00B8) ;and print error message so no error condition
                   ;occcurs


with a typical usage like this:
Code:
F947   JSR &FA46   ; print message following call

F94A   DB  'RECORD then RETURN';
F95C   BRK         ;


Top
 Profile  
Reply with quote  
PostPosted: Wed Mar 14, 2018 1:58 pm 
Offline

Joined: Thu Feb 10, 2011 3:14 am
Posts: 79
Inline parameter passing is common enough that I added support for it in C02. I haven't written any code that actually uses it, though.


Top
 Profile  
Reply with quote  
PostPosted: Wed Mar 14, 2018 3:25 pm 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1948
Location: Sacramento, CA, USA
Martin A wrote:
I've been digging around in the Acorn Atom disc rom recently, this is how Acorn handled inline text printing back in the day. There's no reason why the same process can't be extended to handle other data.

Code:
...
E024 : B1 EA       : LDA (&EA),Y
E026 : 30 08       : BMI instrdone    ;invalid char exit and continue
E028 : F0 06       : BEQ instrdone    ;or zero means exit and error - as 0 is BRK
...
                     .instrdone
E030 : 6C EA 00    : JMP (&00EA)      ;resume processing via indirect jump

... so, the string terminator is executed and therefore has to be a valid op-code? Kinky ...

Mike B.


Top
 Profile  
Reply with quote  
PostPosted: Wed Jan 09, 2019 2:01 pm 
Offline

Joined: Wed Jan 09, 2019 1:15 pm
Posts: 8
Here's one of the generic techniques I use for inline parameters. It's assumed there is no terminator, and the number of parameter bytes is variable (the subroutine handles that).

A subroutine that is expecting inline parameters begins with jsr ISetup. The subroutine needs to exit with an RTS and have the Y register set with the index position of the final byte of the parameter.

ISetup preserves the CPU registers at time of call, and makes them available in the ZP (zA$, etc) but the code (marked Store Registers and Restore Registers) can be removed if not needed.

Code:
; USAGE:
;
;        jsr i_Fn
;        byte 0,1,2,3
;        rts
;
; i_Fn   jsr ISetup
;        ldy #0
; Loop   lda (zFn_ParVec),Y
;        sta $0400,Y
;        iny
;        cpy #4
;        bne Loop
;        dey      ; Adjust rY for the return from subroutine
;        rts
;

zFn_ParVec = $F0
zA$ = 2 + zFn_ParVec
zX$ = 1 + zA$
zY$ = 1 + zX$
zP$ = 1 + zY$

ISetup   ; Store Registers
         sta zA$
         stx zX$
         sty zY$
         php
         pla
         sta zP$
         
         ; Access previous STK RTS address
         tsx
         inx
         inx
         txs

         ; Get addr of inline code -1 then put it into zFn_ParVec
         pla
         clc
         adc #1
         sta zFn_ParVec
         pla
         adc #0
         sta zFn_ParVec + 1

         ; Push @Exit to be the subroutine's return address
operator calc
         lda #>@Exit - 1
         pha
         lda #<@Exit - 1
         pha
operator hilo

         ; Access topmost STK RTS address
         dex
         dex
         txs
         rts      ; Call subroutine

         ; The subroutine returns to this routine. rY + zFn_ParVec = end address of inline data.
@Exit    tya
         clc
         adc zFn_ParVec
         tax
         lda #0
         adc zFn_ParVec + 1
         pha
         txa
         pha

         ; Restore Registers
         lda zP$
         pha
         lda zA$
         ldx zX$
         ldy zY$
         plp
         rts


Top
 Profile  
Reply with quote  
PostPosted: Wed Jan 09, 2019 2:29 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10938
Location: England
(Welcome, CronicBadger!)


Top
 Profile  
Reply with quote  
PostPosted: Mon Jan 14, 2019 5:55 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1467
Location: Scotland
Oldish thread resurrection, but another in-line data coder here: I've been using the in-line, pull address of the stack thing for a very long time on several different systems - both 6502 and 8080/z80 (not used them for a very long time though) and recently in some pdp-8 code too. I've found that on the 6502 it's more space efficient to jsr to the string print for anything more than 2 characters (and it's equal at 2). I've also used this technique to load static data into things like a font table where you know how many bytes you're transferring. A down-side I've found though is that I tend to be more liberal with text messages and what should have been a tiny "monitor" living in under one K became nearly 3... Basically, I wanted code to look like:

Code:
PRINT "Hello, world"


rather than some sort of load X & Y with address of string, held pages (or paper) away in the listing, then call the string print code.

Here is the fairly simple version I'm using in my little 6502 SBC system: (I'm using ca65, so note the .byte and .asciiz variants for storing text)

Code:
; _strout:
;       Output a zero terminated string to the host. The string is in-line with
;       the code and is used like:
;
;       jsr strout
;       .asciiz "Hello, world"
;  or
;       jsr strout
;       .byte   "Hello, world",13,10,0
;
; Note: This uses self-modifying code, so has to run in RAM.
;
;       Destroys A
;********************************************************************************

.export _strout
.proc   _strout
        pla
        sta     strX+1
        pla
        sta     strX+2

strout1:
        inc     strX+1
        bne     strX
        inc     strX+2
strX:
        lda     $FFFF           ; This word modified.
        beq     stroutEnd
        jsr     putchar
        bra     strout1

stroutEnd:
        lda     strX+2
        pha
        lda     strX+1
        pha
        rts
.endproc


-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 17, 2019 12:02 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1467
Location: Scotland
Just another note on this - I've been writing some sweet16 code in the past couple of days and that's another example of what's effectively in-lining data and using the stack return address to access it. You could say that the subroutine is the sweet16 interpreter and the parameter is the program... (slightly extreme, maybe!)

Code:
jsr sweet16
... sweet16 opcodes
RTN
; back to 6502 code


-Gordon
(Brought to you from the one mans data is another mans code department)

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Sun Jan 20, 2019 2:52 am 
Offline

Joined: Wed Jan 09, 2019 1:15 pm
Posts: 8
drogon wrote:
... I've been using the in-line, pull address of the stack thing for a very long time ...


I'm a relatively new convert to inlining. Now I try to use it wherever fast execution time isn't required. The benefits I find are code size and readability.

Another cool trick (to my mind) is to declare a decision table inline. It's easier to modify a table on-the-fly than code (optimising via scoring, etc).

Below is a small, simple example of one I use to convert C64 Petscii text code to C64 screen code:

Evaluation is line-by-line. This table has four rules comprising a Condition (a Range, defined in two columns) and an Action. The first line of data is not the table, but the initialiser - it sets the table processing mode to Single Shot (execution ends after first successful evaluation), the Condition's shared operation type is numeric Range, the Action's shared conversion is a Subtraction from the input.

The first Rule is, in plain language, "if the text character code is within the Pet_Graphics2 code range then subtract 64 from it". And so on.

Code:
; Pet To Screen Uppercase
jsr i_DecisionTable
         ; Initialiser       
         byte 21, OM_SingleShot, MOP_Ran, AOP_Sub, 0, 0
         ; Table Data       
         byte Pet_Graphics2Lo, Pet_Graphics2Hi, 64
         byte Pet_Graphics1Lo, Pet_Graphics1Hi, 32
         byte Pet_PuncNumLo,   Pet_PuncNumHi,    0
         byte Pet_AlphaLo,     Pet_AlphaHi,     64


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

All times are UTC


Who is online

Users browsing this forum: No registered users and 1 guest


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: