Parameters in sub routines

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Parameters in sub routines

Post by DRW »

I tried to google 6502 "subroutine parameters", but nothing useful came up. So, I'm just asking the question here:

Is there a way in 6502 Assembly to use parameters for subroutines?

Something like this:

Code: Select all

MySubRoutine:
  ; Parameter name: loopCounter
  LDX #$00
Loop:
  ; Do something here.
  INX
  CPX loopCounter
  BNE Loop
  RTS
And then you call the sub routine like one of these:

Code: Select all

  JSR MySubRoutine #$10
  ; Calls MySubRoutine and sets loopCounter to 16.

  JSR MySubRoutine $0500
  ; Calls MySubRoutine and sets loopCounter to the value at address $0500.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Parameters in sub routines

Post by thefox »

There's no standardized way to do this. Personally I came up with my own macro based solution, and I know others have done similar stuff.

My solution, called xparam, works like this:

1) In a header file, let's say foo.h, include xparam.h and declare the function(s):

Code: Select all

.include "xparam.h"

xdecl myFunction
  ; Parameters:
  foo .word
  bar .byte
  xlocals ; Local variables
    xyzzy .byte
  endxlocals
endxdecl
2) In the implementation file, say foo.s, define the function:

Code: Select all

.include "foo.h"

xproc myFunction
  ; Fetch an argument.
  lda param bar
  ; Store it in a local variable.
  sta local xyzzy
  rts
endxproc
3) Call the function from somewhere else, say main.s:

Code: Select all

.include "foo.h"

.code

somethingElse: .byte $69

.proc reset
  xinvoke myFunction, foo: #12345, bar: somethingElse
  jmp *
.endproc
In my implementation, all parameters and local variables are stored in the zero page. It's the programmer's duty to make sure the parameter/local areas of two different functions don't overlap.

And just to be clear, to use this stuff you need the xparam.h support header which hasn't been released (at least not yet).
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Parameters in sub routines

Post by Quietust »

Alternatively, 8-bit values could simply be pushed onto the stack, loaded in the subroutine using TSX : LDA $010#,X ($0103,X for the last thing you pushed, $0104,X for the second-last, and so on), then popped once you're done (caller cleanup, of course).
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
User avatar
Movax12
Posts: 541
Joined: Sun Jan 02, 2011 11:50 am

Re: Parameters in sub routines

Post by Movax12 »

That looks cool thefox. I'm not sure how you are doing that, I'm going to have to think about it. Looks like you are abusing some of the macro facilities a bit.
I am not completely happy with my macro parameter system, but it is similar to thefox's, and zeropage is the way to go. I have a constant amount of zeropage reserved for parameters and local variables. Using the stack is slow, and if you send a pointer, you can access it directly with zeropage. I find the 6502 hardware stack useful for saving and loading address only (jsr/rts).

For nested functions, I do have runtime verification that local variables/parameters are not overwritten via Lua with NintendulatorDX.
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Parameters in sub routines

Post by DRW »

Since there's no built-in solution, I guess the one with the stack is the right one for me.

So, please tell me if the following code snippets would be correct:

Code: Select all

FillSpecificMemoryWithValue:
; Parameters (put them to the stack in this order):
; 1. Length
; 2. Value
  TSX ; Loads the parameter "value" into X.
  TXA ; Copies X (i.e. parameter "value") into A.
  TSX ; Loads the parameter "length" into X.
Loop:
  STA $1000, X ; Stores "value" at address $1000 + X.
  DEX
  BNE Loop ; If X isn't 0 yet, repeat, otherwise, leave function.
  RTS

Code: Select all

  LDX #$10 ; The parameter "length".
  TXS
  LDX #$FF ; The parameter "value".
  TXS
  JSR FillSpecificMemoryWithValue
I still haven't really understood the stack, though: Where is it located? I thought that all variables are put into RAM. You write STA $0500 and the variable is put into that specific location. If some other function overwrites it, bad luck, you should have declared placeholder names with .res. But for some reason, you can push variables to a region called the stack and when you retreive it, it's automatically deleted from the stack again. Where does this happen in memory? And can I just directly write to that location, screwing up the stack?

And what do I have to do if there are more than three parameters that I can't just put into A, X and Y all at once? Wouldn't this again require some global variables to temporarily store those values?
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Parameters in sub routines

Post by thefox »

DRW wrote: So, please tell me if the following code snippets would be correct:

Code: Select all

  TSX ; Loads the parameter "value" into X.
This is wrong. TSX simply transfers the stack pointer to register X. You need to use LDA $0103,X etc to actually access the stack (like Quietust said).
I still haven't really understood the stack, though: Where is it located?
It's located at $100-$1FF in RAM. The stack pointer is 8 bits, OR it with $100 to get the address in RAM.
Last edited by thefox on Sun Sep 22, 2013 9:48 am, edited 1 time in total.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Parameters in sub routines

Post by rainwarrior »

I generally either put the parameters in the registers A/X/Y or where that's not appropriate, I use a ZP variable. These places are also good for return values. This makes a function call something like:

Code: Select all

    LDX #3
    LDY #7
    JSR multiply_xy
    ; at this line A contains 21
Yes, you can use the stack as mentioned, but I find having to move the stack pointer into X to retrieve them makes it a little inconvenient/inefficient. You don't really need to use the stack unless you have a recursive/re-entrant subroutine.

Most of my functions take a single parameter in A and put the return value in A.


If I misunderstood the question and you're looking for a simplified syntax, which seems to be what the other replies in this thread are about, once you've written your function it is easy to write an accompanying macro to set up the appropriate registers in a one-line statement:

Code: Select all

.macro MULTIPLY_XY a, b
    LDX a
    LDY b
    JSR multiply_xy
.endmacro

; called like this:
    MULTIPLY_XY #3, #7
User avatar
sonder
Posts: 116
Joined: Wed Jun 26, 2013 12:35 pm
Location: Baltimore
Contact:

Re: Parameters in sub routines

Post by sonder »

In the latest version of my 6502 Forth (which is subroutine-threaded, and I wasn't going to talk about it until my game was done but, hey...) I have a standardized parameter passing system. The X reg is an 8-bit pointer into a data stack on the zeropage, with A containing the top item. I use two macros, "dup" and "drop" to manipulate the data stack for passing parameters, consuming them, and returning them.

The beauty of this arrangement is it can be used completely divorced from the rest of the Forth language.

The stack grows downward, allowing you to put pointers on it intuitively (push high first, low second - additionally, 8-bit add/sub with no carry is straightforward). 0,x normally contains the 2nd item on the stack. Here are the macros:

Code: Select all

macro drop
   lda 0,x
   inx
endm

macro dup 
   dex
   sta 0,x
endm
To push something on the stack, you should first "dup" to effectively push A onto the stack freeing it to be LDA'd with something else. When a routine is done with a value, it should "drop" it. Or you can not "drop" it, changing the value of A to return something. Or push more values to return multiple values if desired.
sonder
User avatar
never-obsolete
Posts: 411
Joined: Wed Sep 07, 2005 9:55 am
Location: Phoenix, AZ
Contact:

Re: Parameters in sub routines

Post by never-obsolete »

I use the A, X, and Y registers first, but also have zero page variables that simulate MIPS registers:

Code: Select all

	.org $0000
a0:	.db 0	; argument registers
a1:	.db 0	;
a2:	.db 0	;
a3:	.db 0	;
a4:	.db 0	;
a5:	.db 0	;
t0:	.db 0	; temporary registers
t1:	.db 0	;
t2:	.db 0	;
t3:	.db 0	;
t4:	.db 0	;
t5:	.db 0	;
t6:	.db 0	;
t7:	.db 0	;
t8:	.db 0	;
t9:	.db 0	;
s0:	.db 0	; save registers
s1:	.db 0	;
s2:	.db 0	;
s3:	.db 0	;
s4:	.db 0	;
s5:	.db 0	;
v0:	.db 0	; return value registers
v1:	.db 0	;  (or more arguments)
i0:	.db 0	; temporary registers
i1:	.db 0	;  (for interrupts)
. That's just like, your opinion, man .
User avatar
sonder
Posts: 116
Joined: Wed Jun 26, 2013 12:35 pm
Location: Baltimore
Contact:

Re: Parameters in sub routines

Post by sonder »

DRW wrote:Since there's no built-in solution, I guess the one with the stack is the right one for me.

So, please tell me if the following code snippets would be correct:

Code: Select all

FillSpecificMemoryWithValue:
; Parameters (put them to the stack in this order):
; 1. Length
; 2. Value
  TSX ; Loads the parameter "value" into X.
  TXA ; Copies X (i.e. parameter "value") into A.
  TSX ; Loads the parameter "length" into X.
Loop:
  STA $1000, X ; Stores "value" at address $1000 + X.
  DEX
  BNE Loop ; If X isn't 0 yet, repeat, otherwise, leave function.
  RTS

Code: Select all

  LDX #$10 ; The parameter "length".
  TXS
  LDX #$FF ; The parameter "value".
  TXS
  JSR FillSpecificMemoryWithValue
I still haven't really understood the stack, though: Where is it located? I thought that all variables are put into RAM. You write STA $0500 and the variable is put into that specific location. If some other function overwrites it, bad luck, you should have declared placeholder names with .res. But for some reason, you can push variables to a region called the stack and when you retreive it, it's automatically deleted from the stack again. Where does this happen in memory? And can I just directly write to that location, screwing up the stack?

And what do I have to do if there are more than three parameters that I can't just put into A, X and Y all at once? Wouldn't this again require some global variables to temporarily store those values?
I only use the 6502 stack for subroutines and temporarily saving values. It's located at $100-$1ff. But because it grows downward, and usually never ever gets close to filling up, you can safely store as many variables there as you feel comfortable. Check out the technique I posted above, let me know if you have any questions and I'll be happy to explain :)
sonder
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Parameters in sub routines

Post by tokumaru »

Stacks (hardware or software) are cool and all, but most of the time not necessary in NES/6502 games. I often just use a ZP section for parameters... say, 16 bytes under the label "Scratchpad". Then, before each subroutine I have the following:

Code: Select all

.enum Scratchpad
	LocalVariable1 .dsb 1
	LocalVariable2 .dsb 2
	LocalVariable3 .dsb 1
.ende
And I just use these local variables for passing parameters. If a subroutine is called from another subroutine, I have to do something like this:

Code: Select all

.enum Scratchpad
	Subroutine1.LocalVariable1 .dsb 1
	Subroutine1.LocalVariable2 .dsb 2
	Subroutine1.LocalVariable3 .dsb 1
	Subroutine1.LocalEnd
.ende
Subroutine1:

(...)

.enum Subroutine1.LocalEnd
	Subroutine2.LocalVariable1 .dsb 1
	Subroutine2.LocalVariable2 .dsb 1
.ende
Subroutine2:
Now, if a subroutine is called from different subroutines, I'll either define it's locals after the last variable of the subroutine that uses the most scratchpad bytes or I'll say "fuck it" and use the stack if there's no other way.
User avatar
Movax12
Posts: 541
Joined: Sun Jan 02, 2011 11:50 am

Re: Parameters in sub routines

Post by Movax12 »

tokumaru wrote:Now, if a subroutine is called from different subroutines, I'll either define it's locals after the last variable of the subroutine that uses the most scratchpad bytes
Yeah that's an interesting problem I didn't want to think/worry about too much.

Code is my subs looks like:

Code: Select all

func myFunction

    params 3        ; start at offset 3, bytes 0..2 are in use
        foo .byte
        bar .byte
    endparams

    locals
        baz .byte
    endlocals

    ; code
    lda param::foo
    sta local::baz

    ; declare baz safe to overwrite:
    release local::baz
    call someOtherFunction

    ; mark it as in use again
    reserve local::baz
    ; do some stuff

    ; exit with baz in A, and release all local/param memory used
    return local::baz

endfunc
Any overwritten locations display an error at runtime.
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Parameters in sub routines

Post by DRW »

thefox wrote:This is wrong. TSX simply transfers the stack pointer to register X. You need to use LDA $0103,X etc to actually access the stack (like Quietust said).
Alright, now I understand. Even better. This means I can access these variables at will.
So, next try:

Code: Select all

FillSpecificMemoryWithValue:
; Parameters (put them to the stack in this order):
; 1. Length
; 2. Value

  TSX          ; Loads the current stack pointer.
  LDA $0103, X ; Loads "value" into A.
  LDX $0104, X ; Loads "length" into X.

Loop:
  STA $1000, X ; Stores "value" at address $1000 + X.
  DEX
  BNE Loop     ; If X isn't 0 yet, repeat, otherwise, leave function.

  PLA          ; Removes "value" from stack.
  PLA          ; Removes "length" from stack.

  RTS

Code: Select all

  LDA #$10 ; The parameter "length".
  PSA      ; "Length" is put into the stack.
  LDA #$FF ; The parameter "value".
  PSA      ; "Value" is put into the stack.
  JSR FillSpecificMemoryWithValue
Correct now?

If the stack starts at $0100, why do I have to get the most recent stack value with $0103 + X? I assume that the first value in the stack might be the stack pointer itself, so that the actual stack variables start at $0101. But why $0103?
rainwarrior wrote:Yes, you can use the stack as mentioned, but I find having to move the stack pointer into X to retrieve them makes it a little inconvenient/inefficient. You don't really need to use the stack unless you have a recursive/re-entrant subroutine.
Yeah, I guess in a real game I will have to see for each function individually which version I'll take. But in the moment, I'll try the stack just to get it to know.
rainwarrior wrote:If I misunderstood the question and you're looking for a simplified syntax
No, you understood it correctly. I was just looking for the technique itself, not for syntax tricks.

But thank you all for the example anyway.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Parameters in sub routines

Post by tepples »

never-obsolete wrote:zero page variables that simulate MIPS registers
I do something similar: $0000-$0007 for arguments and temporaries and $0008-$000F for caller-saved registers.

As for $0103: The top two values are the return address.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Parameters in sub routines

Post by thefox »

DRW wrote:

Code: Select all

  LDX $0104, X ; Loads "length" into X.[/quote]
This addressing mode doesn't exist, see http://www.obelisk.demon.co.uk/6502/reference.html#LDX

[quote]
If the stack starts at $0100, why do I have to get the most recent stack value with $010[u]3[/u] + X? I assume that the first value in the stack might be the stack pointer itself, so that the actual stack variables start at $0101. But why $0103?[/quote]
Stack pointer is not at $100, it's a register inside the CPU itself. As for why $103, consider this example:

Let's say stack pointer starts out as $FF, i.e. SP = $FF, i.e. it points to $1FF. Now let's say the caller pushes two values on the stack, so the bytes are placed at $1FF and $1FE and SP = $FD after. When you call a function, two more bytes (the return value) get pushed on the stack, so SP becomes $FB. And how do we get from the stack pointer value, $FB, to the parameter at $1FE? By adding $1FE - $FB = $103.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Post Reply