Skip to content
Game 0 Unit 16 of 18 1 hr learning time

Bigger Than a Byte

A byte stops at 255 — too small for an address. The 6502 has no 16-bit registers, so you hold a big number as two bytes and add it by hand, low byte first — and the carry flag becomes the bridge between them.

89% of Meet The Machine

One byte holds 0 to 255. But a screen address runs up to 65535 — far past what a byte can carry. You've been using numbers that big all along: $2000 (the nametable), $3F00 (the palette), $8000 (where your code lives) are all sixteen-bit. So how does the machine hold them?

The 6502 has no sixteen-bit registers. A, X and Y are each eight bits, full stop. So a sixteen-bit number is kept as two bytes — a low byte and a high byte — like the units and the "hundreds" of a number, except each digit counts to 255 before it rolls over. $2FF0 is high byte $2F, low byte $F0.

To add to a sixteen-bit value, you add byte by byte, low first — and here the carry flag from last unit finally shows what it's for. Add the low bytes; if they overflow, the carry comes on. Add the high bytes without clearing it, and that carry flows in — the "carry the one" from school arithmetic, in silicon.

What you'll see by the end

The NES screen filled with white.
A 16-bit add by hand: $28 added to $2FF0 carries into the high byte, climbing $2F to $30 (white), with nothing added to it directly.

A white screen — colour $30. We held the value $2FF0, added $28 to it, and showed the high byte. It was $2F. It came out $30 — white. The high byte climbed by one, and nothing added directly to it: the carry from the low byte did it.

Adding two bytes, low first

; ============================================================================
; Meet the Machine (NES) - Unit 16: Bigger Than a Byte
;
; A byte stops at 255, but addresses run to 65535. The 6502 has no 16-bit
; registers, so a big number is two bytes - low and high - added low-first, and
; the carry is the bridge that climbs from one to the other.
; ============================================================================

.segment "HEADER"
    .byte "NES", $1a
    .byte 2
    .byte 1
    .byte $00, $00

.segment "CODE"

reset:
    sei
    cld
    ldx #$40
    stx $4017
    ldx #$ff
    txs
    inx
    stx $2000
    stx $2001
    stx $4010
warm1:
    bit $2002
    bpl warm1
warm2:
    bit $2002
    bpl warm2

    ; ----------------------------------------------------------- YOUR CODE START
    ; hold the 16-bit value $2FF0 as two bytes: low in $10, high in $11
    lda #$f0
    sta $10                 ; low byte
    lda #$2f
    sta $11                 ; high byte

    ; add $28 to it - 16 bits, low byte first
    clc
    lda $10
    adc #$28                ; $F0 + $28 = $118 -> keeps $18, SETS the carry
    sta $10
    lda $11
    adc #$00                ; high + 0 + the carry that just climbed up
    sta $11                 ; $2F becomes $30

    lda $11                 ; show the high byte: it was $2F, now $30 (white)
    ; ------------------------------------------------------------- YOUR CODE END

    sta $00
    bit $2002
    lda #$3f
    sta $2006
    lda #$00
    sta $2006
    lda $00
    sta $2007

forever:
    jmp forever

nmi:
    rti
irq:
    rti

.segment "VECTORS"
    .word nmi
    .word reset
    .word irq

.segment "CHARS"
    .byte $00,$00,$00,$00,$00,$00,$00,$00
    .byte $00,$00,$00,$00,$00,$00,$00,$00
    .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff
    .byte $00,$00,$00,$00,$00,$00,$00,$00
    .res 8192 - 32, $00

We park $2FF0 in two zero-page boxes, $10 (low) and $11 (high). Then: clc, add $28 to the low byte — $F0 + $28 is $118, too big for a byte, so it keeps $18 and sets the carry. Now add the high byte with adc #$00 — zero, plus that carry — and $2F becomes $30. No clc between the two adds: the whole point is to let the carry climb from the low byte to the high one. Show the high byte and the screen goes white.

Assemble and run

ca65 wide.asm -o wide.o && ld65 -C nes.cfg wide.o -o wide.nes

The high byte ticks from $2F to $30 — white — carried up from below.

Try this: a step that doesn't carry

Change the starting low byte to lda #$00 (so the value is $2F00). Now $00 + $28 is $28 — no overflow, carry stays clear — and the high byte stays $2F. ($2F is one of the NES's blank codes, so the screen comes up black — a stark "nothing carried" against the white.) Adding only moves the high byte when the low byte overflows into it. Same code; this time the carry has nothing to climb.

Try this: show the low byte instead

Change the last lda $11 to lda $10 (the low byte). With the original $2FF0 start you'll see $18's colour instead — the other half of the sixteen-bit answer. Two bytes, one number.

A pointer is the next step — but not here

A sixteen-bit value like this is how you hold a screen address — and stepping it is how a game walks through the nametable or scrolls a level. But to write through an address you compute, not one baked into the instruction, needs a different addressing mode — the indirect, indexed pointer ((ptr),y). That's among the first things the first game teaches, exactly when you need it. Here, the arithmetic; there, the pointer.

If it doesn't work

  • The high byte didn't change. Either the low add didn't overflow (check the start is $F0), or you slipped a clc between the two adds and wiped out the carry before it could climb. The low add gets the clc; the high add must not.
  • The answer is off by 256-ish. You added to the wrong byte, or in the wrong order. Low byte first, then high.
  • ca65 complains about $10. Zero-page boxes $10/$11 are free RAM to borrow; check there's no #lda $11 reads the box, lda #$11 would load the number.

What you've learnt

The 6502 has no 16-bit registers, so a number bigger than a byte is two bytes — low and high — and you add it low-first, letting the carry chain into the high byte. The carry flag is the bridge between them.

What's next

That's the last of the machine's everyday fluency with numbers. One capability is still untouched — the one you hear. Next — The Machine Speaks Back — the APU, the sound chip inside the 2A03: set a few registers and a pulse channel holds a note on its own, while your code does nothing at all.