6502 Addressing Modes — Zero Page as "Extra Registers"

8-Bit CPUs — 6502/6510 & Z80

Chapter 4 · 6502 Addressing Modes — Zero Page as "Extra Registers"

cpu8bit1-3 closed with a promise: with only three general-purpose registers, the 6502 needed another way to get fast, flexible access to values. This chapter delivers on it — zero page, the chip's real, concrete answer to a transistor budget that couldn't afford more registers, plus the rest of the addressing modes that make real 6502 programs practical.

Zero Page — The Chip's Real Answer

Addresses $0000$00FF — the first 256 bytes of memory — get special treatment on the 6502. Because every address in that range fits in a single byte, an instruction accessing zero page only needs to encode one address byte instead of two. That shorter encoding means less to fetch and fewer internal cycles to compute the effective address — zero page is measurably faster than ordinary memory access, not just shorter to type.

The practical effect: with 256 available slots, zero page behaves like a bank of pseudo-registers, vastly larger than the 3 real ones (A, X, Y) the chip actually has — at a real cost (an extra memory access instead of a truly free register read), but a far smaller cost than physical registers would have added to the transistor budget cpu8bit1-2 already established as the chip's central constraint.

Absolute Addressing — Reaching the Full 64KB

Absolute addressing embeds a full, two-byte address directly in the instruction, reaching anywhere across the 6502's entire 64KB space. This is worth contrasting directly with assembly1-3's own PC-relative LD: LC-3 had to compute an address from an offset specifically because its instructions were a fixed 16 bits wide, with no room left over for a full standalone address. The 6502 doesn't have that problem — its instructions are variable length (1 to 3 bytes), so a full 2-byte address simply costs one extra byte in the instruction stream, not a fight for bits within a single fixed-width word.

LDA $0050    ; zero page — 2 bytes, address fits in one byte
LDA $1050    ; absolute — 3 bytes, a full two-byte address

Indexed Addressing — Zero Page,X and Absolute,X/Y

Adding X or Y to a base address turns either mode into an indexed one — the mechanism behind iterating over an array, with the index register playing a role similar to assembly1-3's own base+offset LDR, except the "offset" here is a whole register that changes every loop iteration rather than a fixed 6-bit constant baked into the instruction.

LDA $0050,X   ; zero page,X — base $0050 plus X
LDA $1050,X   ; absolute,X — base $1050 plus X, reaches the full 64KB
Zero page,X wraps — it never spills into page 1
LDA $FF,X with X = $02 does not read from $0101. The addition wraps within zero page itself, landing at $01 — the high byte of the address stays permanently zero no matter how far the addition would otherwise carry. Absolute,X has no such limit; only the zero-page indexed modes wrap this way. It's a real, well-documented 6502 quirk, and a genuine source of bugs for programmers who assume it behaves like absolute,X.

(Zero Page),Y — Indirect-Indexed: A Real Pointer Dereference

This is the 6502's most powerful addressing mode, and the direct answer to a problem assembly1-3's own indirect mode (LDI/STI) never fully solved: accessing an array whose starting address isn't known until the program actually runs.

A zero-page location holds a full 16-bit pointer — the low byte at the zero-page address itself, the high byte at the next one over. (zp),Y reads that pointer, then adds Y to the pointer's value to get the final effective address:

; zero page $10/$11 holds a pointer to $3000
LDA #$00
STA $10        ; low byte of the pointer
LDA #$30
STA $11        ; high byte of the pointer — together, $10/$11 hold $3000

LDY #$00
LDA ($10),Y   ; reads from $3000 + Y — a real runtime pointer dereference

This is genuinely more capable than LC-3's own LDI, which could follow a pointer but never combine that with an index in the same instruction. (zp),Y does both at once — exactly what's needed to walk through an array whose address is only known at runtime.

A related, less commonly needed mode, (zero page,X) — indexed-indirect — works the other way around: X is added to the zero-page address first (wrapping within zero page, the same way zp,X does), and whatever pointer sits at the resulting location is used directly, with no further offset. It shows up most often selecting between several fixed pointers — a jump table, for instance — rather than walking through an array.

Real Cycle-Cost Comparison

ModeBytesCycles (LDA)When to reach for it
Immediate22A known constant
Zero page23A frequently-used variable — the "extra register" case
Zero page,X24An array indexed by X, with its base in zero page
Absolute34A variable located anywhere in the 64KB space
Absolute,X / ,Y34 (+1 on page crossing)An array indexed by X/Y, with its base anywhere in memory
(Zero page),Y25 (+1 on page crossing)Dereferencing a runtime pointer, then indexing into it
(Zero page,X)26Selecting between several fixed pointers — jump tables

Zero page's own 3-cycle cost against absolute's 4 is the concrete, measurable version of "extra registers" this chapter opened with — a real, repeatable speed advantage for any value accessed often enough to be worth keeping in the first 256 bytes.

Assemblers pick zero page for you automatically
A real 6502 assembler doesn't require choosing between LDA $0050 and a longer absolute form manually — if the target address fits in a single byte, it assembles as zero page by default, since there's essentially never a reason not to take the faster, shorter encoding when it's available. The two forms in this chapter's own absolute-addressing example look almost identical in source code, but assemble to genuinely different instructions underneath.

Hands-On Exercises

Exercise 1

Using this chapter's own tip-box, explain why LDA $0050 assembles as a 2-byte, 3-cycle zero-page instruction while LDA $1050 assembles as a 3-byte, 4-cycle absolute instruction, even though both look like a similar "load from an address" instruction in source code.

📄 View solution
Exercise 2

Zero page address $10 holds the byte $00, and $11 holds the byte $30. Y currently holds $05. Trace this chapter's own (zp),Y formula to determine the effective address of LDA ($10),Y, and state what that address actually is.

📄 View solution
Exercise 3

X holds $02. Using this chapter's own warn-box, determine exactly where LDA $FF,X actually reads from, and explain why a programmer expecting it to behave like absolute,X would be wrong.

📄 View solution

Chapter 4 Quick Reference

  • Zero page ($0000–$00FF) — shorter encoding, faster access, the chip's real substitute for more physical registers
  • Absolute — a full 2-byte address embedded directly, reaching the whole 64KB space, possible because 6502 instructions are variable-length
  • Zero page,X / absolute,X,Y — indexed addressing for arrays; zero page,X wraps within page zero, absolute,X does not
  • (Zero page),Y — indirect-indexed: dereference a runtime pointer stored in zero page, then add Y — genuinely more capable than LC-3's own LDI
  • (Zero page,X) — indexed-indirect: index into zero page first, then follow the pointer found there — used for jump-table-style selection
  • Zero page (3 cycles) beats absolute (4 cycles) for the same LDA — a real, measurable "extra register" speed advantage
  • Real assemblers automatically choose zero-page encoding whenever the target address fits in one byte