Meet the Altair · Saving registers
Keeping a value
across a call.
A routine can find its way back and still leave your registers changed. How can it borrow the message pointer, then give back the value its caller needs?
Begin here: Step four instructions. CALL has saved return address 0009, and PUSH H has saved the pointer 0100 in a second pair of stack bytes. HL is still 0100. Nothing has been printed yet.
Loaded register-saving program · reference card All addresses and bytes here are hexadecimal
| Address | Reference | RAM | Check |
|---|
0000: LXI SP,0200H chooses the stack starting point. 0003: LXI H,0100H chooses the message. 0006 and 0009: CALL 000DH call the printing routine, saving 0009 and 000C respectively. 000C: HLT finishes.
000D: PUSH H saves H and L. 000E: MOV A,M, 000F: CPI 0, and 0011: JZ 001AH check for NUL. 0014: OUT 01H sends A; 0016: INX H advances HL; 0017: JMP 000EH repeats.
001A: POP H restores L and H from RAM. 001B: RET reads the next stack pair into PC. Restoring HL lets the second call start at the same message without another LXI H.
The lesson checks all twenty-eight program bytes. Message and stack data can be edited while stopped. PUSH, POP, and RET act on real RAM; the viewer keeps no hidden copy of HL.
This lesson connects an 8080, 65,536 bytes of RAM, and a teaching output device at port 1. It calls one printing routine twice, then halts. Start again restores program and message bytes, clears the stack RAM, registers, flags, halted state, output, counts, and history, and stops at PC 0000. It is not the hardware RESET switch.
Borrow the pointer.
In the previous lessons, the printing routine left HL pointing at NUL. RET brought execution back to the caller, but it left HL changed. The routine had to set HL to 0100 again each time it began.
Here the caller chooses the message: its LXI H puts 0100 into HL once, before either call. The printing routine starts reading wherever HL points. As it prints, INX H still advances the pointer.
We want the routine to preserve the caller's starting pointer, so both calls can print the same message. Two new instructions make that possible: PUSH H before the loop and POP H after it.
Save two registers in two bytes.
The CALL at 0006 saves 0009 at 01FE–01FF and leaves SP at 01FE. The routine's first instruction is PUSH H. In this instruction's name, H selects the H–L register pair: both bytes are saved.
PUSH decreases SP and writes H, 01, at 01FD. It decreases SP again and writes L, 00, at 01FC. These bytes form the saved pointer 0100. The return address in the next pair is untouched.
PUSH copies the value into RAM. It does not empty the registers or move the pointer: HL remains 0100, ready for the loop to use. PC advances to the next instruction at 000E.
The CPU does not tag these bytes as “pointer” or “return address.” The table's labels explain how our program uses the locations. Which instruction reads them determines what happens next.
Use the pointer, then restore it.
The familiar loop prints HELLO and LF. HL advances to 0106, while its original value stays in the saved bytes. When MOV reads NUL and CPI sets Z, the conditional jump selects POP H at 001A.
POP H reads the byte at SP into L, then the next byte into H. HL becomes 0100 again and SP advances to 01FE. The bytes remain in RAM.
POP advances PC to the following instruction, RET at 001B. It has restored the pointer, but has not returned to the caller. RET then reads 0009 from the next stack pair into PC and advances SP to 0200.
Make a promise the caller can use.
The second call now starts with the same HL as the first, without another LXI H. It saves and restores that pointer in the same two locations. The complete run prints two HELLO lines in 89 instructions, leaving HL at 0100 and SP at 0200.
Our routine's promise is specific: HL is preserved. A and the flags still hold the values left by reading and testing NUL. PUSH H and POP H do not save or restore them.
This promise comes from the instructions we wrote. CALL and RET do not automatically preserve registers. A routine can preserve selected values itself, or its caller can save values it needs before calling.
Change the saved pointer before it is restored
Start again and step four instructions. EXAMINE 01FC, set the switches to 0001, and DEPOSIT. You have changed the saved L, making the stored pointer 0101. HL is still 0100. EXAMINE 000E to restore PC, then RUN. The first call prints HELLO as usual; POP H restores the edited pointer, so the second call prints ELLO. The captured PUSH explanation still reports the original 0100.
Finish in reverse order.
CALL saved a return address first; PUSH saved HL afterward. To finish correctly, POP must read the saved HL before RET reads the return address. This is the same last-in, first-out rule as nested calls.
What happens if RET runs before POP?
Start again and step until PC is 001A after the first HELLO line. EXAMINE 001B to skip POP, then step RET. SP still points at the saved pointer, so RET reads 0100 into PC. It leaves HL at 0106 and advances SP only to 01FE. The lesson pauses because 0100 is not an instruction start in its reference program. A real CPU does not check whether these bytes were intended as a return address.
Start again to restore the normal path. We now have the pieces for a small interactive program: input, a buffer, decisions, and routines that return with the values their callers need.