More about the 6502
You don't need any of this to start writing programs — the syntax page is enough. But a handful of facts about how the 6502 is built make CodeLab's odd-looking addresses and panels click into place. Here's the short tour: what's inside the chip, how it finds your program, why 16-bit values look backwards in memory, and where everything lives.
What's inside the chip — the registers
The 6502 is tiny. Everything it can hold at once lives in six registers — small named slots, each just a byte or two. Think of them as the chip's hands: it can only grip a few things at a time, so most of what a program does is shuttling values between these registers and memory. You can watch all six change, live, in CodeLab's CPU panel.
| Reg | Name | Size | How often — and can you set it? |
|---|---|---|---|
A | Accumulator | byte (8-bit) | Everyday. The value you compute with — load it, do math/logic, store it. |
X | Index X | byte (8-bit) | Everyday. Loop counters and indexed addressing (table,X). |
Y | Index Y | byte (8-bit) | Everyday. A second index; the (zp),Y mode uses it. |
SP | Stack pointer | byte (8-bit) | Rarely direct. Subroutine calls and interrupts move it for you; reach it only via TSX / TXS. |
PC | Program counter | word (16-bit) | Never direct. You can't load or store it — only JMP, branches, JSR / RTS and interrupts change it. |
P | Status flags | byte (8-bit) | Automatic. Set as side effects of other instructions, tested by branches; whole-byte access only via PHP / PLP. |
In practice you spend almost all your time in A,
X and Y — the three you actually load, change
and store. SP, PC and P mostly
move on their own. Note the sizes: every register is a single
byte (8 bits, $00–$FF)
except PC, which is a 16-bit word — it
has to be, since it holds a full memory address. That byte-vs-word
split is exactly why addresses are stored as two bytes — in a
specific order, which the little-endian section below
explains.
The status flags (P)
P is special: you almost never load it directly. Instead,
ordinary instructions set its bits as a side effect — nearly
every operation updates Z and N from its
result — and the branch instructions then read one bit and jump on it.
That's the whole loop: do something, a flag records how it went, a
branch acts on it. CodeLab's CPU panel shows P as a hex
byte; these are the bits inside it, from bit 7 down to bit 0.
| Bit | Flag | Name | Set when… | Act on it with |
|---|---|---|---|---|
7 | N | Negative | The last result's high bit (bit 7) was 1. | BMI / BPL |
6 | V | Overflow | A signed add/subtract overflowed (also set by BIT). | BVS / BVC |
5 | — | (unused) | No flag here — this bit always reads as 1. | — |
4 | B | Break | Marks a BRK vs a hardware IRQ in the copy pushed to the stack — not a bit you toggle. | (interrupts lesson) |
3 | D | Decimal | BCD-math mode. SED / CLD flip it, but CodeLab does math in binary only — no effect here. | SED / CLD |
2 | I | IRQ disable | When set, maskable interrupts are ignored. | SEI / CLI |
1 | Z | Zero | The last result was exactly zero. | BEQ / BNE |
0 | C | Carry | Carry out of an add / no borrow on subtract; the bit shifted out by shifts & rotates; set by compares. | BCC / BCS · CLC / SEC |
(Because bit 5 is always 1, a "cleared" P still
shows one bit set in the CPU panel — that's normal, not a stuck
flag.)
The reset vector — the 6502's main()
In most languages a program begins at main() — something
finds it by name and calls it. A 6502 has nothing that clever. The
6502 can address 64 KB of memory: every location has an
address from $0000 to $FFFF. On power-on (or
reset) the chip does one thing: it reads the two bytes at
$FFFC and $FFFD, glues them into a 16-bit
address — in a particular byte order we'll get to in a moment — and
jumps there. Whatever lives at that address is your program's
first instruction.
Those two bytes are called the reset vector. Think of it as a fixed mailbox the chip always checks first: it doesn't know where your code is, so it looks up the address from a place that never moves. (Two more mailboxes sit right beside it — for interrupts. You'll open those in the interrupts lesson.)
Little-endian: addresses look backwards
Here's the part that trips everyone up the first time. A 16-bit address is two bytes — a low half and a high half. The 6502 always stores them low byte first, high byte second. That convention is called little-endian.
So the address $E000 is laid down in memory as the byte
$00 followed by the byte $E0 — at
$FFFC/$FFFD the reset vector reads 00 E0, not
E0 00. Glance at a hex dump and a familiar address can
look reversed; it isn't wrong, it's just stored low-then-high.
Once you expect it, it stops surprising you — because it's
consistent. The reset vector, the target of a
JMP or JSR, an address baked into an
instruction, a .word you write — every 16-bit value the
6502 touches follows the same low-then-high rule. When you read the
hex dump panel and an address seems backwards, that's
this, working exactly as designed.
Why your program lives high in memory
The reset vector is at $FFFC/$FFFD — right at the very top
of the address space. For the chip to start cleanly, the memory up
there must be dependable: it has to hold a real address the moment
power comes on, before anything has had a chance to run. So CodeLab's
machine puts the top 8 KB — $E000 through
$FFFF (that's $2000 = 8192 bytes) — in
ROM: read-only memory that's simply there at
power-on. Your program goes in that ROM, and the reset vector points
into it.
You never write any of this yourself — that's the Layer-0 deal from the syntax page. CodeLab takes your handful of instructions, places them in ROM, and fills in the reset vector so it points at your first line. That expanded, complete program is exactly what the hex dump and disassembly panels show you — the long run of empty bytes is the rest of the ROM your tiny program didn't fill.
The stack lives at $0100
The 6502 has a built-in stack, and it isn't anywhere
you choose — it's hard-wired to the 256 bytes from $0100
to $01FF. When you call a subroutine (JSR)
the chip pushes the return address onto that stack; RTS
pops it back. Interrupts push there too. You rarely touch it directly,
but it's a real, fixed part of memory — which is one reason a 6502
system needs working RAM down at the low addresses, starting from
$0000.
So where is the stack pointer itself? Not in memory — it's a
dedicated register inside the CPU, right alongside A,
X, Y and PC. You can watch it
live as SP in CodeLab's CPU panel. It
holds just one byte — an offset — and the actual stack address is
always $0100 + SP (the high half is hard-wired to
$01, which is exactly why the stack can never leave
page 1). After reset it starts at $FD, so the first
push lands near the top of page 1 and the stack grows
downward from there. The only way to read or set it as a
value is TSX (copy SP→X) and
TXS (copy X→SP); everything
else — JSR, RTS, interrupts, push/pull —
moves it for you.
That fixed size is a hard limit: 256 bytes, and not one
more. The stack pointer is an 8-bit register; every push
writes a byte and counts it down, every pop counts back up. Nothing
checks it. Recurse too deeply — or just keep calling without
returning — and the pointer runs off the end and wraps around,
and new pushes start overwriting the oldest frames still sitting in
page 1. There is no error, no trap, no "stack overflow" message.
The damage is silent: a clobbered return address means the next
RTS pops a garbage value and the CPU jumps to a more or
less random place. The program doesn't stop — it derails,
running whatever bytes it lands in. (This is why CodeLab's park /
idle detector exists: a derailed program usually ends up spinning,
and CodeLab notices.)
The flip side of "nothing protects the stack" is "nothing stops you
driving it yourself." The stack pointer is just a register you can
read and set, and page 1 is ordinary memory. So you don't have to
use JSR/RTS as given: jumping into a routine
with JMP instead of calling it skips pushing a return
frame entirely — the hand-rolled version of a tail call. You
can also reset the pointer to throw away frames, or hand-write return
addresses to build jump tables. The 6502 gives you the mechanism and
no guard rails; what you do with it is your calling convention.
Zero page is special — and fast
The very first 256 bytes, $0000–$00FF, are
called zero page. Many instructions have a shorter,
faster form when the address they touch is in zero page — it takes one
less byte and one less clock cycle than a full 16-bit address. A few
addressing modes only work through zero page at all. The
practical takeaway: when you need a handful of scratch locations,
reach for $00–$FF first. It's the closest,
cheapest shelf in the workshop.
CodeLab's memory map
Putting it together — here's where things live in the machine the labs run on:
| Address | What | Why it matters |
|---|---|---|
$0000–$00FF | Zero page | The first 256 bytes — your fastest scratch space. |
$0100–$01FF | The stack | Hardware stack. Subroutine calls and interrupts use it for you. |
$0200–$1FFF | RAM | General read/write memory for your data. |
$B000 | ViaBase | The VIA — storing a byte here lights the 8 LEDs. |
$E000–$FFFF | ROM | Read-only: your program + the reset vector live here. |
Everything else in the 64 KB is simply unused by these labs — that's fine, real machines have gaps too.
The 6522 VIA — how a byte becomes light
That $B000 row is its own chip. The
6522 VIA — short for Versatile Interface
Adapter — is a separate part, the 6502 CPU's classic companion
for talking to the outside world. It sits on
the same address bus as the RAM and ROM, but instead of storing bytes
it has pins: two 8-bit parallel ports (A and B) and a
couple of hardware timers.
In this machine the VIA answers at $B000 (the labs call
it ViaBase), and Port B's eight output pins are
wired straight to the eight LEDs. So the CPU never "knows" about LEDs
at all — it just stores a byte to an address. The VIA turns that byte
into voltage on eight pins, and the pins light the lights. That's the
whole trick behind talking to hardware on a 6502, and it's the same
chip whose Timer 1 the timer lesson uses to keep
time without a delay loop.
- The assembly syntax — every instruction, number and label you can write here.
- How CodeLab is built — the open-source layers, the two CPUs, why it runs at 1 kHz.
- The lessons — the hands-on series, start to finish.
- Standalone CodeLab — the lab on its own, no lesson around it.