.png)
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.
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.
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.
Your two values may be a few bytes higher or lower than these. Where the stack starts depends on what the machine was doing when your program was loaded. They will still be two apart.
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.
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
As before, only A, C and Z need to match yours.
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.
Subroutines bring two errors that read perfectly innocently. Both are worth producing deliberately once.
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.
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.
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.
RET between the CALL print and
the print: label. How many asterisks now, and why?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?CALL Z,yes to CALL NZ,yes. Which letters print now, and which of
the two calls is taken? Check each condition against the DEC above it
before you run it - the answer is not one of the two letters.LD A,'A' to LD A,200 and run the alphabet program. Codes above
127 are graphic characters rather than letters - see Appendix I.CALL inside sub to another
subroutine, and capture SP at the deepest point. What do you expect it to
read?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. Somewhere in Appendix I's graphics range there are corner
pieces and straight pieces. Find enough of them to draw a rectangle, then
write a program with a draw-a-row subroutine and draw one. Two warnings, both
of which are part of the exercise: you need a separate piece for each of the
four sides rather than one horizontal and one vertical, because a straight
piece sits at the top, bottom, left or right of its character square rather
than through the middle of it. And some codes in that range print nothing at
all on this screen. If you cannot close the rectangle neatly, the solid block
will always do it.
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.
| 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. |
CALL label runs a subroutine and remembers where to come back to. RET
goes back.CALL puts two bytes on, RET takes
them off, and SP tracks where the top is.RET will use it as an address.RET, or jumped over.CALL Z and CALL NZ call only when the condition holds.S8. Reading the Keyboard. Every program so far has been a monologue: it runs, it prints, it stops, and the player has no say in it. Next your programs start listening - which means two quite different ways of reading a key, and only one of them is any use in a game.
This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.