Lecture 06

6502 Assembly on the Apple II

BASIC is a comfortable living room; assembly is the engine bay. This lecture gets your hands dirty: the built-in Monitor, entering machine code byte by byte, calling ROM routines, and writing programs that run hundreds of times faster than BASIC.

1. Meet the Monitor

Every Apple II ships with the Monitor — a tiny debugger in ROM. From BASIC, enter it with:

]CALL -151
*█

The * prompt means you're talking to the Monitor. Its language is terse but powerful:

You typeIt does
300Show the byte at $0300
300.30FShow memory from $0300 to $030F
300: A9 C1Write bytes $A9 $C1 starting at $0300
300GGo: execute the code at $0300
300LList: disassemble 20 instructions from $0300
F800.F8FFRead the ROM itself — nothing is secret
Ctrl+C then ReturnBack to BASIC (or 3D0G with DOS)

2. Your first machine-language program

We'll print a letter using a ROM routine. The Monitor ROM contains COUT at $FDED: "output the character in A". Character codes want the high bit set, so 'A' is $C1. The program:

0300: A9 C1     LDA #$C1     ; A = 'A' (high-bit set)
0302: 20 ED FD  JSR $FDED    ; call COUT — print it
0305: 60        RTS          ; return to caller

Enter and run it from the Monitor:

*300: A9 C1 20 ED FD 60
*300G
A
*█

Six bytes. You just wrote machine code the way people did in 1978 — and called a subroutine Woz's team wrote into the ROM. From BASIC, the same program runs with CALL 768 (768 = $0300).

NOTE: RTS at the end is what makes it a well-behaved subroutine: it returns to whoever called it — the Monitor, or BASIC's CALL. Forget it and the CPU marches on into whatever garbage follows your code.

3. The ROM is your standard library

These entry points have been stable since the 1970s — every Apple II programmer knew them by heart:

AddressNameWhat it does
$FDEDCOUTPrint character in A (screen code, high bit set)
$FD0CRDKEYWait for a keypress, return it in A
$FC58HOMEClear the text screen, cursor to top-left
$FDDAPRBYTEPrint A as two hex digits
$FCA8WAITDelay for a time set by A (bigger = longer)
$FBDDBELL1Beep the speaker
$F864SETCOLSet lo-res color from A (low nibble)
$F800PLOTLo-res plot: A = row, Y = column

4. A real example: HELLO with a loop

Printing a whole string introduces loops, labels, indexed addressing, and flags working together:

        ORG $300           ; assemble at $0300
COUT    EQU $FDED

START   LDX #$00           ; X = string index
LOOP    LDA MSG,X          ; fetch MSG[X]
        BEQ DONE           ; byte $00 marks the end → quit
        JSR COUT           ; print it
        INX                ; X = X + 1
        BNE LOOP           ; loop (always taken here)
DONE    RTS

MSG     HEX C8C5CCCCCF     ; "HELLO" with high bits set
        HEX 8D00           ; carriage return, terminator

Hand-assembled, ready to paste into the Monitor:

*300: A2 00 BD 0E 03 F0 06 20 ED FD E8 D0 F5 60
*30E: C8 C5 CC CC CF 8D 00
*300G
HELLO
*█

Walk through what the CPU does: LDA MSG,X is the absolute,X addressing mode from Lecture 02 — the table walk. BEQ watches the Z flag, which LDA sets whenever it loads a zero. This load–test–branch pattern is the assembly equivalent of a while loop, and you will write it a thousand times.

5. Why bother? Speed.

Filling the whole text screen with a character:

BASIC:  10 FOR I = 1024 TO 2039 : POKE I, 42 : NEXT
        (~4 seconds — you can watch it paint)

ASM:    LDA #$AA        ; screen code for '*'
        LDX #$00
FILL    STA $0400,X     ; four STA,X instructions cover
        STA $0500,X     ;   the whole $0400-$07FF page
        STA $0600,X
        STA $0700,X
        INX
        BNE FILL        ; 256 iterations
        RTS
        (~9 milliseconds — appears instantly)

That's a factor of several hundred. Every arcade-style Apple II game, every smooth animation, every music routine is assembly for exactly this reason. (This fill writes 1024 bytes, which includes the 64 screen-hole bytes — harmless for a screen fill, and cheaper than avoiding them.)

6. From hand-assembly to real tools

Nobody ships software by typing hex. The upgrade path:

  1. The Monitor's mini-assembler (present on the IIe/IIc, and on the original II's Integer ROM): type ! then mnemonics like 300:LDA #$C1, and it assembles in place. Good for experiments.
  2. Native assemblers of the era: Merlin, LISA, the DOS Toolkit assembler — full editors running on the Apple II itself.
  3. Cross-development (the modern way): write source on your PC/Mac in an editor, assemble with ca65 or Merlin32, copy onto a disk image, run in an emulator. Fast, comfortable, version-controllable. Getting Started walks you through this exact setup.

7. Mixing BASIC and assembly

The classic architecture of a polished Apple II program: BASIC for the top level (menus, logic, file handling), assembly for the hot paths (drawing, sound). The glue:

10  REM POKE THE ROUTINE INTO $300 FROM DATA
20  FOR I = 0 TO 13 : READ B : POKE 768 + I, B : NEXT
30  DATA 162,0,189,14,3,240,6,32,237,253,232,208,245,96
40  REM ... POKE THE STRING AT $30E TOO, THEN:
50  CALL 768

READ/DATA + POKE + CALL: machine code delivered inside a BASIC listing — exactly how magazines distributed hybrid programs in the 1980s.

Check your understanding

Q1. In the Monitor, what does 300: A9 C1 do?

Q2. Why does the HELLO loop end when LDA MSG,X reads a $00 byte?

Q3. How does BASIC hand control to a machine-language routine at $0300?

← Lecture 05: Applesoft BASIC Next: build your own programs →