Assembly Language Fundamentals

Intermediate
Version:
1.0

The Stack

The stack is a special area of memory used for temporary storage of data, especially return addresses and register values. It follows a Last In, First Out (LIFO) order, meaning the most recently pushed value is the first to be popped off.

On the Z80, the stack grows downward — each new value is stored at a lower memory address than the last. The stack pointer (SP) always points to the top of the stack.

Why Does This Matter for Game Development?

Games involve lots of fast, nested behaviour — player actions, enemy AI, collisions, rendering, and more. These often rely on:

Understanding how the stack works allows you to:

How the Z80 Uses the Stack During a CALL

When your program calls a subroutine with the instruction:

CALL address

the Z80 needs to remember where to come back to after the subroutine finishes. It does this by automatically saving the return address (the next instruction’s location) onto the stack. Here’s what happens step by step:

  1. The Program Counter (PC) moves to the next instruction after CALL. This address is what the CPU will return to later.
  2. The Z80 pushes this return address onto the stack:
    • It first stores the high byte (MSB) of the PC at the memory location pointed to by the Stack Pointer (SP).
    • It then decrements SP by 1.
    • Next, it stores the low byte (LSB) of the PC and decrements SP again.
  3. With the return address now safely on the stack, the CPU loads the target subroutine address into the PC.
  4. Execution continues from the start of the subroutine.

What Happens on RET

When the subroutine reaches a RET instruction, the CPU retrieves the return address from the stack so it can resume where it left off:

  1. The Z80 reads the low byte (LSB) of the saved address from the stack and increments SP.
  2. It then reads the high byte (MSB) and increments SP again.
  3. The combined address is loaded into the PC. Execution continues from the instruction after the original CALL.

In short: CALL pushes the return address onto the stack so the CPU can come back after the subroutine, and RET pops it off again to resume execution.

Nested Subroutines and the Stack

Because return addresses are saved on the stack, the Z80 can handle nested subroutines — that is, a subroutine that calls another subroutine. Each return address is pushed onto the stack in turn. As each RET is encountered, the correct return address is popped and execution continues at the right place.

The stack must be large enough to handle these return addresses. On the Tatung Einstein, more than 256 bytes are typically available, which is usually sufficient.

PUSH and POP Instructions

To store or retrieve register values on the stack manually, use PUSH and POP.

PUSH

PUSH qq

This decrements SP twice and stores the register pair at the new stack location.

POP

POP qq

Example – Save and Restore HL

       LD HL, 9        ; Load 9 into HL
       PUSH HL         ; Save HL to stack

       LD HL, 0        ; Overwrite HL

       POP HL          ; Restore original HL from stack

       LD A, L         ; Copy low byte (9) into A
       ADD A, 48       ; Convert 9 into ASCII '9'
       RST 8           ; Print '9'

       DEFB 158        ; End marker for monitor
       RET

Example – Subroutine With Temporary Register Storage

       CALL DrawPlayer    ; Call a routine that uses HL and DE

DrawPlayer:
        PUSH HL            ; Save HL (we'll use it temporarily)
        PUSH DE            ; Save DE as well
       
; ... drawing code here ...
        POP DE             ; Restore original DE
        POP HL             ; Restore original HL
        RET

This technique is essential when writing reusable subroutines that won't accidentally overwrite important registers — especially in game loops or input handling routines.

Summary

Exercises

Exercise 23.1 – Observe the Stack

Write a small program that:

Exercise 23.2 – Trace CALL and RET

Write a program with a main routine and a subroutine. Use print statements to observe the stack pointer before and after the call and return.

Exercise 23.3 – Save Registers in a Drawing Routine

Write a DrawTile subroutine that temporarily uses DE and HL. Use PUSH and POP to protect these registers and test that the values are restored correctly after the call.

Previous Module
Next Module