6502 Registers and the Stack

8-Bit CPUs — 6502/6510 & Z80

Chapter 3 · 6502 Registers and the Stack

cpu8bit1-2 named A, X, and Y as the 6502's general-purpose registers, and left the rest for here. This chapter completes the picture — the special-purpose registers that make the chip actually work (PC, SP, P), a real hardware stack that's genuinely better than assembly1-7's manual LC-3 approach in one way and more limited in another, and the concrete reason the small register file forces the addressing-mode cleverness cpu8bit1-4 covers next.

The Full 6502 Register Set

RegisterWidthPurposeLC-3 equivalent
A8-bitThe accumulator — most arithmetic and logic instructions route through itNo direct equivalent — any of LC-3's R0–R7 could serve this role
X, Y8-bit eachIndex registers — indexed addressing (cpu8bit1-4) and loop countersLoosely comparable to a base register in assembly1-3's base+offset mode
PC16-bitAddress of the next instructionThe same role as LC-3's own PC (assembly1-2)
SP8-bitStack pointer — real hardware, but locked to one 256-byte page (below)LC-3 had no dedicated SP at all (assembly1-7) — the 6502 is a genuine improvement here
P8-bitProcessor status flags — richer than LC-3's N/Z/P (below)LC-3's N/Z/P (assembly1-4) — the 6502's version adds real capability LC-3 never needed

The Status Register — Richer Than N/Z/P

assembly1-4's N/Z/P condition codes were LC-3's own deliberately simplified teaching version of a real status register. The 6502's actual P register packs eight individual flag bits, several of which LC-3 simply never needed:

N
V
-
B
D
I
Z
C
  • N (Negative) and Z (Zero) — direct counterparts to two of LC-3's own three condition codes.
  • C (Carry) — LC-3 never had one, because LC-3's registers were a full 16 bits wide, matching its own word size, so ordinary addition never needed to signal "this overflowed the register." The 6502's 8-bit registers make Carry essential — see below.
  • V (Overflow) — a separate flag specifically for signed overflow, distinct from Carry's unsigned overflow signal — a distinction LC-3's simplified model never had to draw.
  • D (Decimal mode) — switches addition/subtraction into binary-coded decimal, useful for real-world calculations (early point-of-sale and calculator-style applications) where exact decimal results matter more than raw binary speed.
  • I (Interrupt disable) — a real hardware feature LC-3 never modeled at all.
  • B — only meaningful in the copy of P pushed to the stack during an interrupt, distinguishing a software-triggered BRK from a real hardware interrupt.

Why the Carry Flag Matters So Much Here

The 6502 is an 8-bit CPU — its registers and internal data paths handle one byte (0–255) at a time — but its address bus is 16 bits, giving it a full 64KB address space. That split matters: any arithmetic on a number bigger than 255 has to be built by hand, one byte at a time, chaining the result of one 8-bit addition into the next using Carry:

CLC              ; clear Carry before starting a multi-byte add
LDA NUM1_LOW
ADC NUM2_LOW    ; add with carry — low bytes
STA RESULT_LOW
LDA NUM1_HIGH
ADC NUM2_HIGH   ; carry from the low-byte add feeds into this one
STA RESULT_HIGH

LC-3's own 16-bit-wide registers meant a value never needed to be split across two registers just to add it — this entire pattern, and the Carry flag it depends on, simply had no reason to exist in assembly1. It's a direct, concrete consequence of the 6502 being genuinely 8-bit in a way LC-3, built purely for teaching, never was.

The Stack — Real Hardware, Genuinely Constrained

assembly1-7 had to build PUSH and POP entirely out of ordinary ADD/STR/LDR instructions, because LC-3 provides no dedicated stack-pointer register at all. The 6502 is a real improvement here: SP is a genuine, dedicated hardware register, and the chip provides native push/pull instructions:

PHA   ; push the accumulator onto the stack
PLA   ; pull the top of the stack back into the accumulator
PHP   ; push the status register
PLP   ; pull the status register back

Compare that single PHA against assembly1-7's own multi-instruction manual push sequence — this is real, dedicated hardware doing in one instruction what LC-3 needed three for.

The constraint: SP is only 8 bits wide, not 16. It doesn't hold a full address — it holds just the low byte of one, and the high byte is permanently fixed to $01 by the hardware. This locks the entire stack to a single 256-byte region, page 1 (addresses $0100$01FF), no matter what. The stack grows downward — SP decrements on every push, increments on every pull — the same downward-growing convention assembly1-7's own example already used.

Nothing stops a stack overflow
Because SP is just an ordinary 8-bit register being decremented and incremented, there's no hardware check preventing it from wrapping around. Push too many values without popping them and SP will silently wrap from $00 back to $FF, overwriting whatever was already sitting at the top of page 1 — with no error, no warning, just corrupted data. This is the same "the CPU doesn't stop you" theme assembly1 raised more than once, just showing up here as a real, documented 6502 hazard instead of a hypothetical one.

Why So Few Registers Forced Clever Addressing

Three general-purpose registers — A, X, Y — is genuinely little working space. Adding more wasn't an option without breaking cpu8bit1-2's own transistor budget. Instead, the 6502's designers found a different way to get fast, flexible access to values without paying for more physical registers: a special, quick-access region of memory that instructions can reach with shorter encodings than ordinary memory access requires. cpu8bit1-4 covers this technique — zero-page addressing — in full, as the chip's real answer to "we can't afford more registers."

TXS/TSX — SP is set indirectly, through X
You can't load an immediate value straight into SP the way you can into A, X, or Y. Setting up the stack pointer goes through X first: load the desired value into X, then use TXS (Transfer X to SP) to move it into place. Reading SP back out works the same way in reverse, via TSX. It's a small, real quirk — one more example of a register that isn't quite as general-purpose as it might first appear.

Hands-On Exercises

Exercise 1

Using this chapter's own reasoning, explain why LC-3 never needed a Carry flag, but the 6502 genuinely does. Tie your answer to the specific difference in register width between the two architectures.

📄 View solution
Exercise 2

Compare the 6502's stack to LC-3's own stack from assembly1-7: name one specific way the 6502's is a genuine improvement, and one specific way it's more constrained. Use this chapter's own PHA/PLA example and the page-1 limitation in your answer.

📄 View solution
Exercise 3

A program pushes 260 values onto the 6502 stack without ever popping any of them, starting from SP's default reset value of $FF. Using this chapter's own warn-box, explain what happens to SP and to the data sitting in page 1 as a result — and why the hardware never stops it.

📄 View solution

Chapter 3 Quick Reference

  • A — the accumulator · X, Y — index registers · PC — 16-bit instruction pointer · SP — 8-bit stack pointer · P — 8-bit status flags (N V - B D I Z C)
  • Carry (C) exists because the 6502 is genuinely 8-bit — multi-byte math is chained by hand across ADC instructions, something LC-3's full 16-bit registers never required
  • Overflow (V) is a separate signed-overflow flag, distinct from Carry's unsigned signal
  • Decimal mode (D) and Interrupt disable (I) have no LC-3 equivalent at all
  • PHA/PLA/PHP/PLP — real, dedicated hardware push/pull, a genuine improvement over assembly1-7's manual LC-3 approach
  • SP is only 8 bits — the stack is permanently locked to page 1 ($0100–$01FF), with no hardware protection against overflow/wraparound
  • SP can't be loaded directly — set it via X and TXS
  • Only 3 general-purpose registers is exactly why zero-page addressing (cpu8bit1-4) exists