← Back to Courses
module
7

Doing the same job from several places

Why this matters

Look back at the alphabet program from S6. The three lines that actually put a character on screen are:

        RST 8
        DEFB 158       ; ZOUTC

Two lines, and they appear in every program you have written. In a real game they would appear thirty times - and the day you want to change how printing works, you would have thirty places to edit and one of them you would miss.

A subroutine is a block of code you can run from anywhere and come back from. Write the printing once, use it everywhere.

CALL and RET

Two instructions.

CALL label goes to the label, like a jump - but it remembers where it came from.

RET goes back to wherever the last CALL came from.

        ORG 256

        LD A,'!'
        CALL print      ; go and print
        LD A,'?'
        CALL print      ; go and print again
        RET             ; finish the program

print:  RST 8
        DEFB 158       ; ZOUTC
        RET             ; go back to whichever CALL sent us here

The clever part is that print does not need to know who called it. The first CALL comes back to the LD A,'?' line; the second comes back to the RET that ends the program. Same subroutine, two different destinations, worked out for you.

If you have used BASIC, this is GOSUB and RETURN.

How it remembers: the stack

RET has to get its destination from somewhere, and that somewhere is the stack.

The stack is a region of memory the processor uses as a scratch pad, with one rule: the last thing put on it is the first thing taken off. CALL puts the address of the next instruction on it. RET takes an address off and jumps there.

There is a register that tracks it - SP, the stack pointer - and you can watch this happen. This program captures SP inside a subroutine and again after returning:

        ORG 256

        CALL sub
        LD HL,0
        ADD HL,SP       ; HL = SP back in the caller
        RET

sub:    LD HL,0
        ADD HL,SP       ; HL = SP inside the subroutine
        LD D,H
        LD E,L          ; keep a copy in DE
        RET

What you should see

 A  BC   DE   HL   PC   SZ-H-PNC
 0F 3D41 FCEB FCED 0769 10101000

DE is FCEB - the stack pointer inside the subroutine. HL is FCED - the stack pointer after the return. Two apart, which is the size of the address CALL put there and RET took away.

ADD HL,SP is the trick used here: SP cannot be printed directly, so the program zeroes HL, adds SP to it, and reads it out of HL instead.

Two things follow from this that matter later.

The stack is why RET works at all, including the RET at the end of your programs. Something put a return address on the stack before your program started, and your final RET uses it to hand control back. You have been relying on the stack since S3 without knowing it.

Anything you push, you must pop. The stack is shared. If a subroutine leaves something on it, the RET will pick that up instead of the return address and jump somewhere meaningless.

A subroutine in a loop

Put a subroutine together with S6's counted loop and you get the alphabet again, with the printing factored out:

        ORG 256

        LD C,26
        LD A,'A'

next:   CALL print
        INC A
        DEC C           ; the last thing before the jump
        JR NZ,next

        RET

print:  RST 8
        DEFB 158       ; ZOUTC
        RET

What you should see

>ABCDEFGHIJKLMNOPQRSTUVWXYZ
 A  BC   DE   HL   PC   SZ-H-PNC
 5B 3D00 1EFF FB3E 0769 01000010

A finishes at 5B, one past Z, because the last INC A ran after the final letter. C is 00. Exactly the same output as S6's version - the subroutine changed the structure, not the behaviour.

Note where DEC C sits: immediately before the jump. INC A is above it, so DEC C is the last thing to set the flags and the jump tests the counter. Swap those two lines and the loop tests the letter instead, which is a different program that happens to run.

Two mistakes worth making on purpose

Subroutines bring two errors that read perfectly innocently. Both are worth producing deliberately once.

Falling in through the front door

        ORG 256

        LD A,'*'
        CALL print

print:  RST 8
        DEFB 158       ; ZOUTC
        RET

This prints two asterisks.

CALL print runs the subroutine and comes back to the line after it - which is print. So it runs again, and this time the RET takes the address that was on the stack before your program started, and the program ends.

A subroutine has to sit somewhere execution cannot wander into. Put your subroutines after the RET that ends the main program, or jump over them.

Testing the wrong result

next:   CALL print
        DEC C           ; sets the flags...
        INC A           ; ...and then this overwrites them
        JR NZ,next      ; so the jump tests INC A, not the counter

This is the S6 lesson in a new costume. The loop still runs, still prints, and still finishes - so it looks fine. It just ends when the character wraps to zero rather than when the counter runs out, and the count you asked for is never consulted.

The rule is unchanged: whatever you are testing goes immediately before the jump.

Conditional calls

CALL takes conditions, exactly like JR and JP:

        CALL Z,label    ; call only if the last result was zero
        CALL NZ,label   ; call only if it was not

Here both are exercised - the first condition is true, the second is not:

        ORG 256

        LD A,1
        DEC A           ; A = 0, so Z is set
        CALL Z,yes      ; taken

        LD A,2
        DEC A           ; A = 1, so Z is clear
        CALL Z,no       ; not taken

        RET

yes:    LD A,'Y'
        RST 8
        DEFB 158       ; ZOUTC
        RET

no:     LD A,'N'
        RST 8
        DEFB 158       ; ZOUTC
        RET

What you should see

>Y

One Y, and no N. This is the point at which your programs can start making decisions rather than just counting.

Change one thing

  • In the two-asterisk program above, add a RET between the CALL print and the print: label. How many asterisks now, and why?
  • In the alphabet program, delete the RET from the end of print. Predict what happens before you run it - and note that "it crashes" is a guess, not a prediction. What is actually on the other side of that subroutine?
  • Change CALL Z,yes to CALL NZ,yes. Which letter prints now?
  • Change LD A,'A' to LD A,200 and run the alphabet program. Codes above 127 are graphic characters rather than letters - see Appendix I.
  • In the stack program, add a second CALL inside sub to another subroutine, and capture SP at the deepest point. What do you expect it to read?

Exercises

7.1 - A newline subroutine. Write a subroutine that prints a line break - 13 then 10 - and use it between three printed words. Notice how much shorter the main program gets.

7.2 - Print a digit. Write a subroutine that takes a number 0-9 in A, converts it to its character, and prints it. Call it three times with different numbers. What does it leave in A, and does that matter to the caller?

7.3 - A box. Using Appendix I's graphics range, find four corner characters and two line characters, then write a program with a draw-a-row subroutine and use it to draw a rectangle on screen.

7.4 - Watch the stack. Write a subroutine that calls a second subroutine, which calls a third. Capture SP at each level and work out how much stack one call costs. Then predict SP at the fourth level before measuring it.

7.5 - Break it deliberately. Write a subroutine that pushes something onto the stack with PUSH BC and returns without a matching POP. You will need to look those two instructions up. Predict what RET will do before you run it, then check PC in the register line to see where it actually went.

When it goes wrong

What you see What it means
Everything printed twice Execution is falling into a subroutine from above. Move it below the main program's RET, or jump over it.
The program ends early, after one call A RET reached with nothing of yours on the stack hands control back to the system. Usually a subroutine placed where execution runs into it.
The loop runs the wrong number of times Something between the DEC and the jump reset the flags.
PC in the register line is an address nowhere in your program RET took something off the stack that was not a return address. Check for a PUSH without a POP.
error: unable to resolve reference: print The label and the CALL do not match - check spelling and capitals.

Summary

  • CALL label runs a subroutine and remembers where to come back to. RET goes back.
  • The return address lives on the stack. CALL puts two bytes on, RET takes them off, and SP tracks where the top is.
  • Whatever you push, pop before returning, or RET will use it as an address.
  • A subroutine must sit where execution cannot fall into it - after the main program's RET, or jumped over.
  • CALL Z and CALL NZ call only when the condition holds.
  • Factoring code into subroutines changes the structure and not the behaviour. The alphabet program prints the same 26 letters either way.

Next

S8. Keeping things in memory. Subroutines let you reuse code. The next problem is data - a message longer than one character, a score, a position that has to survive from one frame to the next - which means storing values somewhere and getting them back.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.