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.
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 fill8— Jump to SubRoutinefill8, but remember where you came from.rts— ReTurn from Subroutine: go back to wherever the matchingjsrwas.
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 — 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
rtsis missing, sofill8runs straight on past its end into whatever bytes follow. Every subroutine needs itsrts. - Both bars land on the same row. You forgot to change
Xbetween the calls, or bothldxlines use the same value. The second needsldx #40. - It returns to the wrong place / crashes. Something inside the routine left the stack unbalanced — for now,
fill8should onlyjsr/rtscleanly 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.