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.
Making function calls in assembly code
Moderator: Moderators
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: Making function calls in assembly code
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:
(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.
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.
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.
Re: Making function calls in assembly code
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.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.
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
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
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
It's magic! And very useful nature of machine code.
So, exploit it, but don't reject.
Re: Making function calls in assembly code
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!