Skip to content
Game 0 Unit 11 of 16 1 hr learning time

Call, Return, and a Stack You Can See

Package a job into a subroutine you can call from anywhere — and watch where the machine remembers its way back: the stack, sitting in plain memory you can read.

69% of Meet The Machine

Your programs are getting long enough that chunks of them want names. "Draw a short bar" is a job you'll want again and again — so write it once, give it a name, and call it whenever you need it.

That's a subroutine, and two instructions run it:

  • jsr fill8Jump to SubRoutine fill8, but remember where you came from.
  • rtsReTurn from Subroutine: go back to wherever the matching jsr was.

It's BASIC's GOSUB / RETURN — with one difference that matters for the rest of your life as a programmer: here you can see how it works. When you jsr, the machine writes the return address onto the stack — ordinary memory at page 1, $0100$01FF, with the SP register marking the top. rts reads it back off. The "magic" of how a subroutine knows the way home is just a number parked in RAM.

What you'll see by the end

Two short red bars of blocks, one on the top row and one on the row below.
Two bars from one subroutine: the bar-drawing code written once and called twice, each with a different starting cell.

Two short red bars — eight blocks on the top row, eight on the row below. The point is that we wrote the bar-drawing code once and called it twice, telling it a different starting cell each time. One definition, two uses.

One routine, called twice

; Meet the Machine - Unit 11: Call, Return, and a Stack You Can See
; Assemble with: acme -f cbm -o call.prg call.asm

*= $0801
!byte $0c,$08,$0a,$00,$9e,$32,$30,$36,$31,$00,$00,$00

*= $080d
        ldx #0          ; first run: start at cell 0  (top row)
        jsr fill8
        ldx #40         ; second run: start at cell 40 (the next row)
        jsr fill8
loop    jmp loop

; fill8 — write eight blocks (red) starting at screen cell X
fill8   ldy #8          ; Y counts the eight cells
f8loop  lda #$a0
        sta $0400,x     ; shape
        lda #2          ; red
        sta $d800,x     ; colour the same cell, same X
        inx
        dey
        bne f8loop
        rts

fill8 is a small loop — write eight blocks (shape and red colour, the same paired stores as the last two units) starting at cell X — given a name and an rts at the end. Before each jsr we set up its input: X says where to start. ldx #0 then jsr fill8 draws across the top row; ldx #40 (the start of the next row) then jsr fill8 draws the one below. Set up, call, return; set up, call, return.

The stack, in plain sight

Here's what jsr does: it writes the two-byte return address onto the stack — SP drops, and those two bytes of page-1 RAM now hold "come back here" — then jumps to the routine. rts reads those two bytes back, bumps SP up, and jumps to them. Open the memory view at page 1 ($0100$01FF) and you can watch the address go on at each jsr and come off at each rts.

This is worth seeing once, because the stack is real and therefore breakable — push something on and forget to take it off, and rts reads the wrong number and jumps somewhere wild. For now fill8 is balanced and well-behaved; just know the safety rail is made of plain RAM.

Assemble and run

acme -f cbm -o call.prg call.asm

Two short red bars, both painted by the same fill8.

Try this: a third bar

Add a third set-up-and-call before the loop — ldx #80 (the start of row 2), then jsr fill8. Three bars, and you didn't write the drawing code again. That's the whole point of a subroutine: name it once, use it freely.

Try this: watch the stack work

Load the program and open the memory view at page 1. Step through, and you'll see two bytes appear near the top of the stack at each jsr — the return address — and disappear at each rts. The thing BASIC hid inside GOSUB is right there in memory.

If it doesn't work

  • The screen garbles after the bars draw. An rts is missing, so fill8 runs straight on past its end into whatever bytes follow. Every subroutine needs its rts.
  • Both bars land on the same row. You forgot to change X between the calls, or both ldx lines use the same value. The second needs ldx #40.
  • It returns to the wrong place / crashes. Something inside the routine left the stack unbalanced — for now, fill8 should only jsr/rts cleanly and not push anything it doesn't pull back.

What you've learnt

jsr runs a named subroutine and pushes the return address onto the page-1 stack; rts pulls it and comes back — so you write a job once and call it from anywhere, passing what it needs in a register.

What's next

Your programs can now decide, repeat, and break into named pieces. A few pieces of everyday fluency remain before you build a game — and the first is the most ordinary thing of all. Next — Adding and Taking Away — doing maths on a value with inc, adc and sbc, and meeting the carry flag the 6510 makes you mind.