Making function calls in assembly code

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
User avatar
zanto
Posts: 57
Joined: Sun Mar 07, 2021 11:15 pm
Location: Rio de Janeiro, Brazil

Making function calls in assembly code

Post by zanto » Fri Mar 12, 2021 11:59 pm

I imagine this is a silly question, so it's a little embarrassing, but I've been struggling with this for a while...
I'm trying to organize my code a little and I'd like to use functions that are only executed when they are called from another part of the code. If I just add the functions to the .asm file, the program will execute the function like any other code. To avoid that, I've been adding a JMP instruction before the function leading to the instruction after the function is over. That sounded weird to me, so I tried looking at other examples, but I just couldn't really figure out how they work. I tried messing around with the .proc directive, but I couldn't get it to work right, even after reading the cc65 manual. Could anyone help me understanding how to use functions? These functions could be in the same .asm file from where they are called or a different one.

unregistered
Posts: 1146
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: Making function calls in assembly code

Post by unregistered » Sat Mar 13, 2021 12:28 am

hi zanto,

In asm, on the NES, a game begins processing, after being turned on or reset, at one of the 3 memory locations pointed to at the very end $FFFA through $FFFF. The 1st address is at $FFFA and $FFFB, the 2nd at $FFFC and $FFFD, and the 3rd is at $FFFE and $FFFF. Those three addresses, all stored low byte first, point to either vblank, irq, or reset type functions; though, I don’t remember that order now.

So, your game will start processing at the reset type address. Then, it could run into your main loop and the rest of its progress is up to you, the programmer.

Functions are usually called into action with jsr. Make a note now that there is no way for a function to run without being called into action, other then if you mistakenly include that function somewhere in your main-loop/vblank.


My game looks something like:

Code: Select all

;header

;variables declared


reset:


rti


MainLoop:

jsr functionC

jmp MainLoop


;other functions here... like
 
scroll_screen:
 
 
rts


functionB:


rts


vblank:

jsr scroll_screen

rti


irq: rti


functionC:


rts


.pad $FFFA
;.pad is asm6’s way of filling
;addresses with 0s until reaching the
;specified address

;$FFFA, $FFFB, $FFFC, $FFFD, $FFFE, $FFFF
;maybe(?) this order would work
.dw vblank, irq, reset
;.dw is asm6’s way of declaring word(s) 
;(16bit address(es))


;vblank’s low byte of its 16bit address
;would be stored at either $FFFA, $FFFC,
;or $FFFE.
(It’s always the same order of those address purposes at the end; I just can’t remember what that order is right now. Sorry :( the nesdev wiki would definitely know. :)


p.s. functionB can’t possibly run bc it is never called into action. :)


Note: The order of the functions in your game does NOT matter since, all 3 of the control-functions’ 16bit addresses are recorded in the last 6 bytes of your bank ($FFFA through $FFFF in this example). Therefore, the game will know where to start your vblank, irq, and reset at, every time they are needed. :)

User avatar
aa-dav
Posts: 159
Joined: Tue Apr 14, 2020 9:45 pm
Location: Russia

Re: Making function calls in assembly code

Post by aa-dav » Sat Mar 13, 2021 4:16 am

zanto wrote:
Fri Mar 12, 2021 11:59 pm
I imagine this is a silly question, so it's a little embarrassing, but I've been struggling with this for a while...
I'm trying to organize my code a little and I'd like to use functions that are only executed when they are called from another part of the code. If I just add the functions to the .asm file, the program will execute the function like any other code. To avoid that, I've been adding a JMP instruction before the function leading to the instruction after the function is over. That sounded weird to me, so I tried looking at other examples, but I just couldn't really figure out how they work. I tried messing around with the .proc directive, but I couldn't get it to work right, even after reading the cc65 manual. Could anyone help me understanding how to use functions? These functions could be in the same .asm file from where they are called or a different one.
First of all just place all code except starting one in procedures - there is almost no need in code outside procedures. So there will be no problem you described.
As for 'falling into procedure' - it is absolutely ok in assembler if you don't forget to insert 'rts' ('ReTurn from Subroutine') instruction at the end of functions.
Anyway functions are nothing but just labels which can be target of instructions like jmp or call/jsr.
.proc directive is useful because it starts new scope and labels in different scopes can have the same names, so you need no to make labels like 'loop_in_proc_some_name7'.
But technically procedures are just labels of their start addresses and you can get profit of it. You can make them just using regular labels. .proc is useful for enclosing scopes.
For example you can make different procedures using the same code:

Code: Select all

.proc mul_by_10
  lda #10
.endproc ; note - there is no rts!
.proc mul_by_a ; a is first multiplicand
   ...
   rts ; return from subroutine to the caller
.endproc
Here you gain profit from 'falling into next procedure' behaviour and save space in code.
And we can rewrite it as:

Code: Select all

mul_by_10:
  lda #10
.proc mul_by_a ; a is first multiplicand
   ...
   rts ; return from subroutine to the caller
.endproc
...and get the same result (first label just need no scope).
Also you can get profit from jumping to another procedure instead of call it:

Code: Select all

.proc mul_by_20
  lda #20
  jmp mul_by_a ; no need in jsr+rts!
.endproc
.proc mul_by_10
  lda #10
.endproc ; note - there is no ret!
.proc mul_by_a ; a is first multiplicand
   ...
   rts
.endproc
Here mul_by_20 need no to jsr to mul_by_a and then rts because it just jumps to it and then mul_by_a returns it will return to caller of mul_by_20.
It's magic! :D And very useful nature of machine code.
So, exploit it, but don't reject.

User avatar
zanto
Posts: 57
Joined: Sun Mar 07, 2021 11:15 pm
Location: Rio de Janeiro, Brazil

Re: Making function calls in assembly code

Post by zanto » Sat Mar 13, 2021 8:12 pm

I see! I guess the only thing .proc does is define a local scope for the labels you create in it. I got it to work by putting the functions after the NMI interrupt code. Thank you, people! :D

Post Reply