It is currently Sun Feb 18, 2018 6:49 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Sun Sep 22, 2013 3:57 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1570
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:
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:
  JSR MySubRoutine #$10
  ; Calls MySubRoutine and sets loopCounter to 16.

  JSR MySubRoutine $0500
  ; Calls MySubRoutine and sets loopCounter to the value at address $0500.

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 4:25 am 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 3008
Location: Tampere, Finland
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:
.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:
.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:
.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: kkfos.aspekt.fi


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 6:21 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1403
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.


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 7:17 am 
Offline
User avatar

Joined: Sun Jan 02, 2011 11:50 am
Posts: 522
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.


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 7:24 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1570
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:
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:
  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?

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 7:46 am 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 3008
Location: Tampere, Finland
DRW wrote:
So, please tell me if the following code snippets would be correct:
Code:
  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).

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

_________________
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: kkfos.aspekt.fi


Last edited by thefox on Sun Sep 22, 2013 9:48 am, edited 1 time in total.

Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 8:04 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6033
Location: Canada
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:
    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:
.macro MULTIPLY_XY a, b
    LDX a
    LDY b
    JSR multiply_xy
.endmacro

; called like this:
    MULTIPLY_XY #3, #7


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 9:21 am 
Offline
User avatar

Joined: Wed Jun 26, 2013 12:35 pm
Posts: 116
Location: Baltimore
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:
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


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 9:33 am 
Offline
User avatar

Joined: Wed Sep 07, 2005 9:55 am
Posts: 312
Location: Phoenix, AZ
I use the A, X, and Y registers first, but also have zero page variables that simulate MIPS registers:

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


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 9:40 am 
Offline
User avatar

Joined: Wed Jun 26, 2013 12:35 pm
Posts: 116
Location: Baltimore
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:
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:
  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


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 9:42 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10285
Location: Rio de Janeiro - Brazil
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:
.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:
.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.


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 11:06 am 
Offline
User avatar

Joined: Sun Jan 02, 2011 11:50 am
Posts: 522
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:
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.


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 11:37 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1570
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:
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:
  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.

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 11:45 am 
Online

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19633
Location: NE Indiana, USA (NTSC)
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.


Top
 Profile  
 
PostPosted: Sun Sep 22, 2013 12:04 pm 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 3008
Location: Tampere, Finland
DRW wrote:
[code]
LDX $0104, X ; Loads "length" into X.

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 $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?

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: kkfos.aspekt.fi


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

All times are UTC - 7 hours


Who is online

Users browsing this forum: Google [Bot], lidnariq and 7 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:  
Powered by phpBB® Forum Software © phpBB Group