Assembly 101: Subroutines

When developing in Assembly, you will definitely have to use a subroutine or two. JSR and RTS are the two main instructions that make subroutines work. What is a subroutine, how does it work and what are the possible pitfalls? Read all about it here.

What is a subroutine?

Whenever you’re writing code, you may (or rather, you will) have to reuse certain snippets of code across different sections of your game. For instance, drawing a sprite on screen takes quite a few lines of code, where you’ll have to write the position on screen, the character index number and its attributes to video RAM. Rewriting these lines every time you need to draw a sprite will eat up your precious bytes really fast. Rather than copying and pasting these lines all over your code, it’s better to write the code once and reference it throughout your game. This is where subroutines can help you out. You can consider a subroutine to be a function, that can be called multiple times without having to write redundant code.

Assembly time!

First, you write the code that needs to be executed multiple times across the game. Give that code a label, so you can reference it easier later on. Then, at the end of the code, you tell the code to return where it came from, so it can continue executing code along the way. The main instructions which will make this work, are JSR (Jump to SubRoutine) and RTS (ReTurn from Subroutine). As a simple example, we will write a subroutine that adds ten to the accumulator and stores the new value on a zero page memory address.

srAddTen:         ;; This is the label that defines the subroutine's name
    CLC           ;; Clear the carry to prevent overflow errors
    ADC #10       ;; Add ten to the accumulator
    STA $00       ;; Store the new value in the first byte of the zero page
    RTS           ;; Return from the subroutine

Add this subroutine somewhere in your code where it can be referenced, preferably in the static bank (you may use loadAllSubroutines.asm for this purpose). Then, to reference this subroutine, you simply jump to it using the JSR instruction:

    LDA #32       ;; Load a value of #32 into the accumulator
    JSR srAddTen  ;; Jump to the subroutine to add #10 to the accumulator and store it on the zero page

Now, whenever you need to add 10 to the accumulator and store it in the first zero page byte, you call this subroutine by adding JSR srAddTen to your code.

Make sure you don’t forget to end your subroutine with a RTS instruction, or all data after the subroutine is treated as code and executed accordingly, resulting in unexpected behaviour (and most likely crashes).

JSR vs. JMP

The difference between JSR (jump to subroutine) and JMP (jump to memory address) is the ability to return to where you’ve jumped from. JSR adds the current memory address to the stack*, so the code knows where to return later on. RTS retrieves the stored memory address from the stack and jumps to that address, essentially returning to where the code came from. JMP just jumps to the new memory address without an easy way to return.

*this is why it’s important to synchronize pushes and pulls to the stack: push or pull one byte too many and the code may jump to places in memory where it’s not supposed to go, crashing your game.

Subroutine vs. Macro

So why use a subroutine if we already have macros to handle reoccurring snippets of code? The simple answer is: prerservation of ROM space. When you use a macro, upon assembling/compiling your code, every instance of a called macro physically gets replaced with the contents of that macro, adding the macro script bytes every time it’s being referenced, with the possibility of filling up your ROM space up to a point where your bank may overflow. A subroutine gets handled after compilation, adding the code only once to your ROM, potentially saving a lot of space.

Honorable mention: RTI

There’s one more instruction associated with subroutines: ReTurn from Interrupt. I haven’t quite been able to wrap my head around the concept of interrupts, but as far as I understand, an interrupt can be called to break the flow of code execution, and react to events that require immediate attention, like updating the screen graphics and pallettes (dear reader, please correct me if I’m wrong here). When using NESmaker, you probably won’t really have to use this instruction, as it already is in place in every module available (within the NMI.asm or NMI_Blank.asm source code).

So there you have it! Save data, save time writing code, and use subroutines wherever applicable.