How do I jump to a subroutine using indirect addressing?

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
SecretServiceDude
Posts: 99
Joined: Wed Oct 22, 2008 3:49 pm
Location: Los Angeles, CA

How do I jump to a subroutine using indirect addressing?

Post by SecretServiceDude »

I'd like to be able to jump to certain subroutines (e.g. different enemy updates) in my game using indirect addressing in order to avoid wasteful if-else checks. So far, I've been using the JSR instruction to jump to subroutines; however, that instruction appears to support absolute addressing only.

I'm thinking it's possible to use the JMP instruction (which supports indirect addressing), but that would require pushing the address - 1 of the following instruction onto the stack in order to "play nice" with the RTS instruction that terminates the subroutine, wouldn't it?

I'm afraid of getting really nasty "off by one" errors if I push the wrong address onto the stack. What is the proper way to set up a correct jump?

Or is there a better method altogether?
User avatar
blargg
Posts: 3717
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

JSR to the JMP instruction.
User avatar
SecretServiceDude
Posts: 99
Joined: Wed Oct 22, 2008 3:49 pm
Location: Los Angeles, CA

Post by SecretServiceDude »

Hot damn that's slick. That's so much easier than what I was thinking!
Roth
Posts: 400
Joined: Wed Aug 03, 2005 3:15 pm
Contact:

Post by Roth »

SSD, I have recently heard about the technique your describing actually, what weird timing! I still don't understand how it works though. But I'm interested in what Blargg just said... what the? Can someone explains how that works?

EDIT: grammar blah
User avatar
SecretServiceDude
Posts: 99
Joined: Wed Oct 22, 2008 3:49 pm
Location: Los Angeles, CA

Post by SecretServiceDude »

Roth wrote:SSD, I have recently heard about the technique your describing actually, what weird timing! I still don't understand how it works though. But I'm interested in what Blargg just said... what the? Can someone explains how that works?
Let's say you have several different types of enemies in your game, and you want to update them all. One way is to check each enemy to see what type it is, then select the appropriate update routine. In pseudocode:

Code: Select all

if (enemyType == SOLDIER)
    JSR UpdateSoldier
else if (enemyType == NINJA)
    JSR UpdateNinja
else if (enemyType == BOSS)
    JSR UpdateBoss
etc...
This method is acceptable if you only have a few different enemies, but quickly gets out of hand as the number of enemy types increases. It's also pretty inefficient.

A better way is to store an array of addresses of the various update routines. Then use the enemy type as an index into that array. In pseudocode:

Code: Select all

SOLDIER = 0
NINJA = 1
BOSS = 2
etc...

; Initialization (performed once)
UpdateRoutine[SOLDIER] = UpdateSoldier
UpdateRoutine[NINJA] = UpdateNinja
UpdateRoutine[BOSS] = UpdateBoss
etc...

; Update (performed every frame)
JSR UpdateRoutine[enemyType]
By using the method above, you avoid all the if-else tests, and you can add as many different enemy types as you want.

I just wasn't sure how to pull that technique off in 6502 assembly. :wink:

Did that answer your question?
User avatar
blargg
Posts: 3717
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

JMP indirect doesn't seem all that useful for jump tables, since you have to copy the address to a fixed address first. Pushing the address (minus one!) on the stack and then returning to it is simplest. Note the subtlety: the caller JSRs to do_action, pushing the final return address on the stack. Then the table-based do_action routines push the address of the routine to "jump" to and "return" to that. At that point, one of the three action_n routines is being executed, with the original return address on the stack for it to return to when done.

Sorry for the length of the code below.

Code: Select all

; Example that does actions 0, 1, and 2
main:
        ; Loop through values of A
        lda #0
:       pha
        jsr do_action_a ; test one of the methods
        pla
        clc
        adc #1
        cmp #3
        bne :-
        rts

; The actions performed
action_0:
        print_str "0"
        rts

action_1:
        print_str "1"
        rts

action_2:
        print_str "2"
        rts

; Different ways to invoke action based on value of A

do_action_a:
        cmp #0
        bne :+
        jmp action_0

:       cmp #1
        bne :+
        jmp action_1
        
:       cmp #2
        bne :+
        jmp action_2
        
:       rts

do_action_b:
        asl a           ; x = a * 2
        tax
        lda table+1,x   ; push address to "jump" to
        pha
        lda table,x
        pha
        rts

table:
        .addr action_0-1, action_1-1, action_2-1

do_action_c:
        tax
        lda table_h,x   ; push address to "jump" to
        pha
        lda table_l,x
        pha
        rts

table_h:
        .byte >(action_0-1), >(action_1-1), >(action_2-1)
table_l:
        .byte <(action_0-1), <(action_1-1), <(action_2-1)
User avatar
SecretServiceDude
Posts: 99
Joined: Wed Oct 22, 2008 3:49 pm
Location: Los Angeles, CA

Post by SecretServiceDude »

That's pretty hardcore, dude! It'll take me a little while to digest all the nuances there, but I think I get the gist. Thanks for giving me another angle to consider. :D
Celius
Posts: 2159
Joined: Sun Jun 05, 2005 2:04 pm
Location: Minneapolis, Minnesota, United States
Contact:

Post by Celius »

In my game, my NMI routine looks like this:

Code: Select all

NMI:
 pha
 txa
 pha
 tya
 pha
-
 jmp ($00)
 jmp ($02)
 jmp ($04)
 ...
 jmp ($1C)
 jmp ($1E)
 lda #<-
 sta $20
 pla
 tay
 pla
 tax
 pla
 rti

Return:
 clc
 lda $20
 adc #3
 sta $20
 jmp ($20)
$20/$21 hold the address of "-" initially. The routine is fixed so it never goes beyond a page boundary, so I don't have to do 16-bit additions. Anyways, basically I have all the stuff I want to do in the NMI routine in a list. This list is made up of the addresses of the routines I want to go to, and they're stored in $0-$1F, so that's 16 different routines I can go to in the NMI. Instead of doing an "RTS" at the end of each routine, I go "JMP Return". $20/$21 basically holds the position of where the program last was. Though I have to add 3 to it to get past the instruction it was just at. I designed this specifically for like what you were saying. I hated all those ugly If/Else statements. And I also designed this for heavy gear-shifting in the code. By that I mean like when you switch from going into game-mode where you're jumping around killing enemies to going to the menu, where the engine will be doing very different things.

Oh, and if any routine in that list isn't doing anything, it just jumps directly to "Return". I have it like this for my game loop, too. Though it's a bit different.
User avatar
SecretServiceDude
Posts: 99
Joined: Wed Oct 22, 2008 3:49 pm
Location: Los Angeles, CA

Post by SecretServiceDude »

@Celius: I really like your idea of having callbacks in the NMI routine. Right now I'm working on the transition from the title screen to the main game. Since those two sections handle graphics quite differently, having callbacks greatly simplifies some of the logic.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

In order to have vastly different NMIs through my game, I have the NMI vector point to RAM. I reserved 3 bytes of RAM for it, and usually I have a "JMP $XXXX" instruction there, and I change the address depending on what section of the game is being executed. Sometimes I place an RTI in place of the JMP, to "disable" NMIs without touching the PPU registers.

In order to avoid the catastrophic case of an NMI firing while the address is partially updated (something that would crash the program), I first change the JMP to an RTI, and then I change the address and restore the JMP.
Celius
Posts: 2159
Joined: Sun Jun 05, 2005 2:04 pm
Location: Minneapolis, Minnesota, United States
Contact:

Post by Celius »

Yeah, that's one thing I have to consider when using my method. In general, the NMI and Game Loop sharing sections of RAM is dangerous. For example, I didn't even think about it until a little while ago. In my NMI I need to use different temporary variables than I do in the game loop. If the NMI is fired and destroys those values, my game = worse catastrophe than when the meteor killed the dinosaurs. Anyways, I rarely change the routines in that list, so I disable the NMI before I update them, and turn it on right after that. Though it's pretty impractical sometimes.
User avatar
BMF54123
Posts: 410
Joined: Mon Aug 28, 2006 2:52 am
Contact:

Post by BMF54123 »

Celius wrote:If the NMI is fired and destroys those values, my game = worse catastrophe than when the meteor killed the dinosaurs.
Yeah. I've dealt with exactly that issue before...in a commercial game.

(granted, the game was never released, but that doesn't excuse some of the amateur programming errors they made...)
User avatar
Bregalad
Posts: 8036
Joined: Fri Nov 12, 2004 2:49 pm
Location: Caen, France

Post by Bregalad »

In order to have vastly different NMIs through my game, I have the NMI vector point to RAM. I reserved 3 bytes of RAM for it, and usually I have a "JMP $XXXX" instruction there, and I change the address depending on what section of the game is being executed. Sometimes I place an RTI in place of the JMP, to "disable" NMIs without touching the PPU registers.

In order to avoid the catastrophic case of an NMI firing while the address is partially updated (something that would crash the program), I first change the JMP to an RTI, and then I change the address and restore the JMP.
That's exactly how Final Fantasy 3 works.

Celius, your idea of a jump table is great, but could be optimised.
You could place the NMI in RAM so that the jump table would be a jsr table, and you just have to replace the argument of the jsr code to change the routine called. You could also call a routine that stores the content of A,X and Y into variables (instead of pushing them) so that even that step can be done from RAM (and is fully optionnal), and you can change the initial jsr to a rti to disable NMIs if needed. That way the routines called in the NMI can simply rts and don't have to do a jmp Return. Also, the exact same routine can be called from the main code if needed.

Alternatively, if having the jump table in RAM bothers you because you have to interleave the addresses with "rts" you could do the following thing :

Code: Select all

JumpToNMISubroutine
    lda $20
    inc $20
    asl A
    tax
    lda $00,X
    pha
    lda $01,X
    pha
    rts
And the NMI code would just be a loop that calls for that subroutine (or a stream of calls to be faster). In other world the work done by "Return" is done before the routine is started, and not after it is executed, but otherwise it's exactly the same as you posted (you'll have to substract one from routine's adress, if you don't want to just use jmp indirect instead of pha, pha, rts.
Useless, lumbering half-wits don't scare us.
Celius
Posts: 2159
Joined: Sun Jun 05, 2005 2:04 pm
Location: Minneapolis, Minnesota, United States
Contact:

Post by Celius »

Hey, that's actually a really great idea. I could definitely just have somewhere in RAM a list of 16 JSR $XXXX in RAM, and obviously only change the addresses. That would save a lot of time, actually. Though that would require me to do probably a lot of optimization at this point.
Post Reply