Overview

A stack is a general data structure which stores data in a last-in, first-out structure. Many processors, including the Z80, use a stack to facilitate function calls (and by extension interrupts) and temporary data storage for user code.

Data may be added to the stack, which is known as a push, or removed from it (pop).

The Z80 stack

The stack is defined by the stack pointer register sp. This contains the memory address of the current "top" of the stack. When a 16-bit value (two bytes) is pushed onto the stack, sp is decremented by two, and the data is written to the memory location it now points to. When a value is popped, the 16-bit value is retrieved from the currently pointed location and the stack pointer is then incremented by two. Thus, the stack is an area of memory extending from one byte before the initial sp value downwards through the address space.

For user code, only regular register pairs (af, bc, de, hl, ix and iy) can be pushed onto the stack. Other opcodes and operations implicitly using the stack (call, rst, hardware interrupts, ret/reti/retn) push/pop the program counter (pc) register.

Uses of the stack

Register preservation

The most simple use is to save the value in a register for later use. A contrived example is:

 ld a,1     // register a is now 1
 push af
   ld a,2
   ...      // register a is now 2
 pop af
 ...        // register a is now 1

It is common to do this when you need to access a particular register, but it contains an important unknown value which cannot be preserved in another register.

An unoptimised function implementation might push all registers at its start, and pop them all at the end, to make itself reusable, but efficiency might require that this is not always done. For interrupt handlers, it is sometimes necessary.

Copying register values

If an operation can only be performed on certain registers, push/pop can be used to transfer values; however, it is generally slower than a normal register copy.

 push de
 pop bc
 add hl,bc

slower than

 ld b,d
 ld c,e
 add hl,bc

Delays

Push and pop opcodes are relatively slow and can be used to provide delays. For example,

 push ix
 pop ix

will use 29 cycles in 4 bytes and has no effect on the system.

Functions and interrupts

Any call or rst will push the pc onto the stack. Due to the way the Z80 operates, this will have been pre-incremented so it points to the next instruction after the call. A hardware interrupt will have a similar effect, except that it may occur mid-instruction (for instructions like ldir) but it is all handled by the Z80 so it works correctly.

On returning from a function/interrupt, pc is popped from the stack and execution carries on as expected.

Thus, to use functions and interrupts, it is necessary to define the stack correctly.

Parameter passing

It is common practice on many systems to pass function parameters on the stack in the form:

 push <space for result>
 push <parameters>
 call <function>
 pop <parameters into a don't care register>
 pop <result>

(or variants of this, according to the calling convention in use.)

The function is responsible for extracting the parameters without losing the return address, so more advanced stack manipulation (such as dealing directly with the value of sp) is needed.

In general, this is not recommended in Z80 code. It takes more space and is far slower than passing parameters in registers. If you need to pass in more parameters than can fit into the available registers, consider using a memory structure instead.

Stack overflow

If your code contains deeply recursive function calls, or contains some mis-matched pushes, the stack will grow and grow until it exceeds the space available to it. This will result in it overwriting other memory, and eventually it may run out of RAM altogether. Thus, don't have deeply recursive function calls (if you can help it; all recursions can be made into iterations) or mis-matched pushes (at all).

The effect on the stack of a recursive function can be alleviated by optimising it to require less stack space per recursion.

How to initialise the stack "properly" on the SMS

The stack pointer sp has to be set before any interrupts can happen, because they will attempt to use it. Thus, in any program's startup code, it has to perform a ld sp,nnnn operation as early as possible - usually, immediately after the initial di and im 1 instructions.

Because the stack grows downwards in RAM, it is common practice to start it at the highest available address, because then it grows into the "unused space" above the "regular" memory area, which is conventionally allocated starting at the lowest address. However, on the SMS, the very highest area of RAM is affected by various common hardware register writes - writes to $fffc-$ffff for paging, $fff8-$fffb for the 3D glasses, and certain other registers from $fff0 for official Sega development hardware - which are mirrored in RAM at $dff0-$dfff. Thus, it is common practice to initialise the stack pointer to $dff0:

 ld sp,$dff0



Return to top
0.146s