Subroutine parameters

Programming the 6502 microprocessor and its relatives in assembly and other languages.
User avatar
Triangular
Posts: 2
Joined: 13 Mar 2018

Subroutine parameters

Post by Triangular »

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: Select all

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.
User avatar
Alarm Siren
Posts: 363
Joined: 25 Oct 2016

Re: Subroutine parameters

Post by Alarm Siren »

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: Select all

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: Select all

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.
User avatar
GARTHWILSON
Forum Moderator
Posts: 8774
Joined: 30 Aug 2002
Location: Southern California
Contact:

Re: Subroutine parameters

Post by GARTHWILSON »

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?
leepivonka
Posts: 168
Joined: 15 Apr 2016

Re: Subroutine parameters

Post by leepivonka »

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: Select all

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: Select all

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: Select all

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: Select all

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: Select all

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.
User avatar
Triangular
Posts: 2
Joined: 13 Mar 2018

Re: Subroutine parameters

Post by Triangular »

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 :)
User avatar
BigEd
Posts: 11464
Joined: 11 Dec 2008
Location: England
Contact:

Re: Subroutine parameters

Post by BigEd »

BTW triangular, welcome to this excellent and friendly forum!
Martin A
Posts: 197
Joined: 02 Jan 2016

Re: Subroutine parameters

Post by Martin A »

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: Select all

                     ;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: Select all

                     .name_error
E0AB : 20 16 E0    : JSR instring
                     DS "NAME?"
E0B3 : 00            BRK
User avatar
BigEd
Posts: 11464
Joined: 11 Dec 2008
Location: England
Contact:

Re: Subroutine parameters

Post by BigEd »

Here's a later effort by Acorn, from the MOS version 1.20 for the BBC Micro:

Code: Select all

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: Select all

F947	JSR &FA46   ; print message following call

F94A	DB  'RECORD then RETURN';
F95C	BRK         ;
CurtisP
Posts: 79
Joined: 10 Feb 2011

Re: Subroutine parameters

Post by CurtisP »

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.
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Subroutine parameters

Post by barrym95838 »

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: Select all

...
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.
CronicBadger
Posts: 8
Joined: 09 Jan 2019

Re: Subroutine parameters

Post by CronicBadger »

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: Select all

; 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
User avatar
BigEd
Posts: 11464
Joined: 11 Dec 2008
Location: England
Contact:

Re: Subroutine parameters

Post by BigEd »

(Welcome, CronicBadger!)
User avatar
drogon
Posts: 1671
Joined: 14 Feb 2018
Location: Scotland
Contact:

Re: Subroutine parameters

Post by drogon »

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: Select all

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: Select all

; _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/
User avatar
drogon
Posts: 1671
Joined: 14 Feb 2018
Location: Scotland
Contact:

Re: Subroutine parameters

Post by drogon »

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: Select all

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/
CronicBadger
Posts: 8
Joined: 09 Jan 2019

Re: Subroutine parameters

Post by CronicBadger »

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: Select all

; 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
Post Reply