Computing History · Microprocessor Revolution
Zero Page Addressing and the Economics of Eight-Bit Instructions
Placing the first 256 bytes of memory at the centre of the instruction set traded address range for instruction length, and shaped how eight-bit code was written.
An eight-bit opcode occupies one byte and has 256 possible values. That budget must cover every operation the processor performs, every addressing mode each operation supports, and enough combinations left over for the instructions that do nothing but change a register pair or a flag. There is no room to also carry a sixteen-bit address. Every instruction that names a memory location either gives up bytes from the opcode, or gives up address range, and the choices made here are visible in the resulting code size and execution speed.
The arithmetic of an opcode byte
The 6502 has no separate mode field. Three bits in the middle of the opcode byte select the addressing mode, and the remaining five bits carry the operation, so a single operation has a different opcode value for each mode it supports. Adding two numbers from memory and adding two numbers from the accumulator are different opcodes, not one opcode with a flag. The consequence is a table of 256 opcode values in which the mode is implicit in the value, and an assembler’s table for the processor has 256 entries with no mode bits to decode.
That regularity makes the mode cost explicit. An instruction that names a location in the first 256 bytes of memory is two bytes: an opcode and one address byte. An instruction that names any of the 65,536 locations is three bytes. The saving is one byte per reference, and on a processor where a typical routine is a few dozen instructions, the saving is a meaningful fraction of the total program.
The Z80 takes a different route to the same economy. Its instruction set is an extension of the 8080’s, and it has no zero page. Instead it has short forms that name the location indirectly through a register pair, such as a load that uses the address held in HL. Those instructions are one byte and need no immediate operand, but they require the program to have loaded the address into HL first, which costs three bytes of its own. For a single access, the indirect form is larger in total. For a loop that touches the same location repeatedly, the address is loaded once and the loop body is one byte per access.
Both designs are spending the same currency. A short instruction is fast and small, but it needs a way to name a location without carrying sixteen bits. The 6502’s way is to make the low 256 bytes special. The Z80’s way is to use a register as a pointer.
What the first 256 bytes became
On a 6502 machine the zero page is not merely an optimisation. It is where the processor’s most useful indirect addressing modes demand their pointers live. The indirect indexed mode reads a sixteen-bit address from two consecutive zero-page locations and then adds the Y register to it. The indexed indirect mode reads a pointer from zero page plus the X register. Neither mode can take its pointer from anywhere else in memory. A program that wants to walk a buffer with a pointer must therefore keep the pointer in zero page, and the page’s 256 bytes become a scarce, manually allocated resource, with part of the page claimed by whatever system software the machine runs.
The scarcity is visible in the operating conventions of the era. System software claimed a fixed block of the zero page for its own pointers, and applications had to fit within what remained. A program using a pointer per open file, per buffer, and per device consumed two bytes each, and the allocation was planned on paper the way a modern program plans registers. Because the 6502 has no stack-relative addressing for locals, subroutine parameters and temporaries also tended to land in zero page, and recursion was impractical for the same reason.
The performance difference is not only about instruction length. A two-byte instruction is fetched in two bus cycles; a three-byte instruction takes three. On a processor whose cycle count is dominated by instruction fetch, the saving in bytes is also a saving in cycles, and the relationship is direct enough that the byte count is a fair proxy for the time. The same reasoning appears in reverse in the design of the bus itself: an instruction fetch that must read an extra byte also occupies the bus for an extra cycle, which matters on machines where the video circuitry takes the bus away for part of each line.
The page boundary as an edge case
Zero-page addressing is not free of hazards. The indexed forms wrap within the page. An instruction that reads from zero page plus the X register, with X holding a value that pushes the sum past 255, reads from the start of the zero page rather than from the next page. That wrap is documented and useful, because it makes a circular buffer of 256 bytes free of any boundary check, but it is also a source of bugs when a program assumes the index is bounded and it is not.
The other modes have a related but different behaviour. Absolute indexed addressing crosses page boundaries without wrapping, but pays an extra cycle when it does. The processor computes the sixteen-bit address, detects that the high byte changed, and inserts a cycle to correct the read. A program that places a frequently accessed table so that it straddles a page boundary pays that cycle on the entries past the boundary, and the fix is to align the table.
The 6502’s indirect jump has a documented defect of the same family: the instruction reads its target address from two bytes, and when those bytes straddle a page boundary the processor does not carry into the high byte of the address. It reads the low byte from the last location of the page and the high byte from the first location of the same page, wrapping the pointer fetch rather than the pointer value. Code that places a jump table across a page boundary gets a target that points into the wrong page. Assemblers and linkers for the platform were expected to align such tables, and hand-written code that computed an indirect jump address dynamically had to arrange the same alignment itself.
; 6502: a jump table must not straddle a page boundary.
; If it does, the high byte of the target is read from the wrong page.
.org $0300 ; page-aligned so the table cannot straddle
table: .word routine_a
.word routine_b
.word routine_c
; dispatch on X = 0, 2, 4
LDA table,X ; low byte of target
STA ptr
LDA table+1,X ; high byte
STA ptr+1
JMP (ptr)
The Z80’s equivalent modes have their own boundary rules. Its indexed addressing adds a signed displacement to a register pair, and the displacement is a single byte interpreted as a signed value, so the reachable range is 127 bytes forward or 128 back. A structure larger than that cannot be reached with one indexed instruction, which pushes programmers toward register pairs holding base addresses and explicit arithmetic to compute offsets.
Why the trade is not reversible
A later designer looking at these instruction sets might ask why the short form was tied to a fixed region rather than to a mode bit that could name any page. The answer is that a mode bit costs opcode space, and the opcode space was the scarce resource. The 6502 uses 256 opcodes and has no spare encodings to spend on a page selector. The cost of the zero page is paid in address-map complexity, in the scarcity of 256 bytes, and in the manual allocation that every nontrivial program had to perform.
The benefit is measurable in a way the alternatives are not. A subroutine that manipulates a handful of pointers and counters can be written entirely in two-byte instructions, and the resulting code is compact enough to fit in the small ROMs of the period. The same routine written with absolute addressing is larger by a byte per reference, and the difference accumulates across an entire program in a way that decided whether a product shipped with one ROM or two. The same pressure appears in the framebuffer budget, where every byte given to the display is a byte the program cannot use.
The mechanism generalises past the specific processor. Any instruction set that wants short encodings must find some way to imply high-order address bits, whether by dedicating a region of memory, as here, or by dedicating a register, as the Z80 does, or by making the instruction stream itself relative to a base, as later designs did. The zero page is the most literal version of the idea: the high byte of the address is not stored anywhere, because it is defined to be zero. Later processors with sixteen-bit registers could afford a mode bit instead, which is one reason segmented addressing computes its addresses from two registers rather than reserving a page for pointers.
References
- MCS6500 Family Programming Manual — MOS Technology, 1976
- Zilog Z80 CPU User Manual (UM008) — Zilog
- MCS-80/85 Family User Manual — Intel Corporation