$0000–$00FF Zero page (fast access, 2-byte instructions)
$0100–$01FF Stack (grows downward from $01FF)
$0200–$1FFF System/free area
$2000–$3FFF User RAM (40 KB available)
$D000–$DFFF I/O ports (VIC-II, SID, CIA1, CIA2)
$E000–$FFFF Kernal ROM (can be banked out for more RAM)
All addresses relative to $D000 (e.g., $D010 = $D000 + $10).
$D000–$D00F Sprite X positions (16 bytes, 2 per sprite)
$D000 = Sprite 0 X (low byte)
$D010 = X high bits (bit 0 = Sprite 0 X bit 8, etc.)
$D001–$D00F Sprite Y positions (16 bytes, 2 per sprite)
$D001 = Sprite 0 Y
$D003 = Sprite 1 Y
... (every other byte)
$D015 Sprite enable ($00 = all off, $3F = all 6 on)
$D017 Sprite Y expand (1 = double height)
$D01D Sprite X expand (1 = double width)
$D025 Sprite 0 color
$D026 Sprite 1 color
... (one byte per sprite)
$D027 Sprite 0 multicolor (0–3 for multicolor mode)
$D028 Sprite 1 multicolor
...
$D01C Multicolor mode enable (bit = sprite index)
$D011 VIC control register 1
Bit 7: Raster interrupt request
Bit 6: Extended color text mode
Bit 5: Bitmap mode
Bit 4: Blank screen (1 = blank)
Bits 3–0: Scroll Y
$D012 Raster line (read for current, write for interrupt trigger)
$D016 VIC control register 2 (scroll X, multicolor, etc.)
$D018 Memory control (screen addr, bitmap addr)
Default: $14 (screen at $0400, bitmap at $2000)
$D020 Border color
$D021 Background color 0
$D022 Background color 1 (multicolor mode)
$D023 Background color 2 (multicolor mode)
$D024 Background color 3 (multicolor mode)
$DC00 Data direction register A (keyboard columns)
$DC01 Keyboard matrix input register
Bit 0 = Key Row
Bit 1 = Key Row, etc.
$DC02 Data direction register B
$DC03 Joystick / paddle input
$DC04–$DC05 Timer A (low/high)
$DC06–$DC07 Timer B (low/high)
$DC0E Control register A (timer, interrupt)
$DC0F Interrupt enable register
LDA #$00 ; Set port B for input
STA $DC02
LDA $DC01 ; Read joystick (port 2) or keyboard
; Bit pattern: 7=up, 6=down, 5=left, 4=right, 3=fire
LDA #$42 ; Load immediate
LDA $42 ; Load from zero page
LDA $4200 ; Load from absolute address
LDA $4200,X ; Indexed
LDA ($42,X) ; Indirect indexed
STA $42 ; Store
ADC #$10 ; Add with carry
SBC #$10 ; Subtract with carry
INC $42 ; Increment memory
DEC $42 ; Decrement memory
INX, INY, DEX, DEY ; Increment/decrement registers
BNE $4000 ; Branch if not equal (after CMP, etc.)
BEQ $4000 ; Branch if equal
BCS $4000 ; Branch if carry set
BCC $4000 ; Branch if carry clear
BPL $4000 ; Branch if plus (MSB = 0)
BMI $4000 ; Branch if minus (MSB = 1)
JMP $4000 ; Unconditional jump
JSR $4000 ; Jump to subroutine (push return address)
RTS ; Return from subroutine
CMP #$10 ; Compare A with immediate (sets flags)
CMP $42 ; Compare A with memory
CPX #$10 ; Compare X
CPY #$10 ; Compare Y
; Flags: Z=zero (equal), C=carry (unsigned <), N=minus
ASL ; Arithmetic shift left (A or M)
LSR ; Logical shift right
ROL ; Rotate left
ROR ; Rotate right
AND #$0F ; Bitwise AND
ORA #$F0 ; Bitwise OR
EOR #$FF ; Bitwise XOR
BIT $42 ; Test bits (sets N, V, Z)
PHA ; Push A
PLA ; Pull A
PHP ; Push status
PLP ; Pull status
; In initialization:
LDA #<IRQ_HANDLER ; Low byte of handler address
STA $0314
LDA #>IRQ_HANDLER ; High byte
STA $0315
CLI ; Clear interrupt disable flag
IRQ_HANDLER:
PHA ; Save registers
TXA
PHA
TYA
PHA
; Check if it's our interrupt (raster or timer)
LDA $D019 ; VIC interrupt status
BIT #$01 ; Test raster interrupt
BEQ NOT_VIC
; Handle VIC/raster interrupt
; (your game logic here)
LDA #$01 ; Acknowledge interrupt
STA $D019
NOT_VIC:
PLA ; Restore registers
TAY
PLA
TAX
PLA
RTI
C64 sprites are 24 pixels wide × 21 pixels high, stored as 63 bytes (3 bytes/line).
Location: Default at $3000–$3FFF (can be changed via $D018)
Format:
Bytes 0–2: Line 0 (pixel data)
Bytes 3–5: Line 1
...
Bytes 60–62: Line 20
Each 3-byte line = 24 pixels (bit pattern).
Example: Simple square
$38 $38 $00 (11000000 11000000 00000000 = two vertical bars)
$38 $38 $00
...
; Temporary registers (safe to overwrite)
TEMP0 = $F0
TEMP1 = $F1
TEMP2 = $F2
TEMP3 = $F3
; Loop counters
LOOP_X = $F4
LOOP_Y = $F5
LOOP_C = $F6
; Pointers (16-bit address)
PTR0 = $F8 ; PTR0_L = $F8, PTR0_H = $F9
PTR1 = $FA
WAIT_RASTER:
LDA #<RASTER_LINE ; e.g., $80 (line 128)
CMP $D012
BNE WAIT_RASTER
; Copy from $2000 to $4000, 256 bytes
LDX #$00
COPY_LOOP:
LDA $2000,X
STA $4000,X
INX
BNE COPY_LOOP
; Fill $2000–$27FF with $00
LDA #$00
LDX #$00
FILL_LOOP:
STA $2000,X
INX
BNE FILL_LOOP
INC $2001 ; Move to next page
CPX #$08 ; 8 pages × 256 bytes = 2048
BNE FILL_LOOP
| Mistake | Fix |
|---|---|
| Forgetting to acknowledge interrupt ($D019) | Add LDA #$01; STA $D019 before RTI |
| Sprite addresses hard-coded | Use $D018 to configure memory address |
| Not disabling interrupts when modifying $D018 | Use SEI before, CLI after |
| Assuming screen is at $0400 | Verify with $D018 or use absolute address |
| Overflow in 8-bit math (ADC/SBC) | Check carry flag after operation |
| Infinite loop in interrupt | Ensure interrupt flag is cleared and handler returns quickly |
Powered by TurnKey Linux.