--- name: 6510-assembly description: > Use this skill for any question about MOS 6510/6502 assembly language on the Commodore 64. Covers CPU registers, instruction set, addressing modes, writing and entering ML programs, using assemblers, the single-step simulator, and connecting ML to BASIC. Sources: The Machine Language Book for the C64, The Advanced Machine Language Book for the C64, The Anatomy of the C64, The Complete C64 ROM Disassembly. --- # 6510 Assembly Language for the Commodore 64 ## The 6510 CPU The Commodore 64 uses the **MOS 6510** processor, which is a slightly modified 6502 with an additional 8-bit I/O port at addresses $0000–$0001. It runs at: - **NTSC**: 1,022,727 Hz (~1 MHz) - **PAL**: 985,248 Hz (~0.985 MHz) ### CPU Registers | Register | Size | Description | |----------|------|-------------| | **A** (Accumulator) | 8-bit | Primary arithmetic/logic register | | **X** (Index X) | 8-bit | Index register, loop counter, offset | | **Y** (Index Y) | 8-bit | Index register, loop counter, offset | | **PC** (Program Counter) | 16-bit | Address of next instruction | | **SP** (Stack Pointer) | 8-bit | Points into stack ($0100–$01FF) | | **SR** (Status Register) | 8-bit | Processor flags | ### Status Register (SR) Flags ``` Bit 7 N Negative — Set if result bit 7 = 1 Bit 6 V Overflow — Set on signed arithmetic overflow Bit 5 - (always 1) Bit 4 B Break — Set when BRK instruction executed Bit 3 D Decimal — Decimal mode (BCD arithmetic) Bit 2 I IRQ Mask — 1 = IRQ disabled Bit 1 Z Zero — Set if result = 0 Bit 0 C Carry — Set on unsigned arithmetic carry/borrow ``` --- ## Addressing Modes | Mode | Syntax | Bytes | Example | Description | |------|--------|-------|---------|-------------| | Implied | — | 1 | `CLC` | Operand implied by opcode | | Accumulator | A | 1 | `LSR A` | Operand is accumulator | | Immediate | #$nn | 2 | `LDA #$41` | Literal value | | Zero Page | $nn | 2 | `LDA $FB` | Byte in zero page (fast!) | | Zero Page,X | $nn,X | 2 | `LDA $FB,X` | Zero page + X | | Zero Page,Y | $nn,Y | 2 | `LDX $FB,Y` | Zero page + Y | | Absolute | $nnnn | 3 | `LDA $C000` | 16-bit address | | Absolute,X | $nnnn,X | 3 | `LDA $C000,X` | 16-bit + X | | Absolute,Y | $nnnn,Y | 3 | `LDA $C000,Y` | 16-bit + Y | | Indirect | ($nnnn) | 3 | `JMP ($0314)` | Jump via pointer | | (Indirect,X) | ($nn,X) | 2 | `LDA ($FC,X)` | Zero-page ptr + X (pre-indexed) | | (Indirect),Y | ($nn),Y | 2 | `LDA ($FC),Y` | Zero-page ptr, then + Y (post-indexed) | | Relative | $nn | 2 | `BEQ $+5` | Branch offset (-128 to +127) | **Performance note**: Zero-page addressing is 1 cycle faster than absolute and uses 1 fewer byte. Use zero page for frequently accessed variables. --- ## Complete Instruction Set Reference ### Load/Store Instructions ``` LDA Load Accumulator N Z LDX Load X Register N Z LDY Load Y Register N Z STA Store Accumulator STX Store X Register STY Store Y Register ``` ### Transfer Instructions ``` TAX Transfer A to X N Z TAY Transfer A to Y N Z TXA Transfer X to A N Z TYA Transfer Y to A N Z TSX Transfer SP to X N Z TXS Transfer X to SP ``` ### Arithmetic Instructions ``` ADC Add with Carry N V Z C SBC Subtract with Carry N V Z C INC Increment Memory N Z INX Increment X N Z INY Increment Y N Z DEC Decrement Memory N Z DEX Decrement X N Z DEY Decrement Y N Z ``` **Important**: Always use `CLC` before `ADC` and `SEC` before `SBC` unless chaining multi-byte math. ### Logical Instructions ``` AND AND with Accumulator N Z ORA OR with Accumulator N Z EOR XOR with Accumulator N Z BIT Bit Test N V Z ``` ### Shift and Rotate ``` ASL Arithmetic Shift Left N Z C (multiply by 2) LSR Logical Shift Right N Z C (divide by 2) ROL Rotate Left N Z C (shift through carry) ROR Rotate Right N Z C (shift through carry) ``` ### Compare Instructions ``` CMP Compare A with Memory N Z C CPX Compare X with Memory N Z C CPY Compare Y with Memory N Z C ``` ### Branch Instructions (all relative, ±127 bytes) ``` BCC Branch if Carry Clear (C=0) BCS Branch if Carry Set (C=1) BEQ Branch if Equal (zero) (Z=1) BNE Branch if Not Equal (Z=0) BMI Branch if Minus (N=1) BPL Branch if Plus (N=0) BVC Branch if Overflow Clear (V=0) BVS Branch if Overflow Set (V=1) ``` ### Jump and Subroutine ``` JMP Jump (absolute or indirect) JSR Jump to Subroutine (pushes PC-1 to stack) RTS Return from Subroutine (pulls PC+1 from stack) RTI Return from Interrupt (pulls SR then PC from stack) BRK Software Break/Interrupt ``` ### Stack Instructions ``` PHA Push Accumulator to Stack PLA Pull Accumulator from Stack N Z PHP Push Status Register to Stack PLP Pull Status Register from Stack ``` ### Flag Instructions ``` CLC Clear Carry SEC Set Carry CLI Clear IRQ Mask (enable IRQs) SEI Set IRQ Mask (disable IRQs) CLD Clear Decimal Mode SED Set Decimal Mode CLV Clear Overflow NOP No Operation (2 cycles) ``` --- ## Multi-Byte Arithmetic ### 16-bit Addition ```asm ; Add 16-bit values at $FB/$FC to $FD/$FE ; Result in $FB/$FC CLC LDA $FB ADC $FD STA $FB LDA $FC ADC $FE ; carry propagates STA $FC ``` ### 16-bit Subtraction ```asm SEC LDA $FB SBC $FD STA $FB LDA $FC SBC $FE STA $FC ``` --- ## Entering ML Programs ### Method 1: DATA Statements (most portable) ```basic 10 FOR I=49152 TO 49152+N : READ D : POKE I,D : NEXT 20 SYS 49152 100 DATA 169,72,32,210,255,169,73,32,210,255,96 ``` ### Method 2: Using a ML Monitor (SUPERMON) ``` ; After loading SUPERMON: A C000 ; assemble at $C000 LDA #$41 JSR $FFD2 ; BSOUT - print char RTS ; Press RETURN on empty line to exit G C000 ; execute from $C000 ``` ### Method 3: Using the Built-in Monitor (from BASIC) ```basic SYS 1536 ; enter monitor (if SUPERMON at $0600) ``` --- ## Linking ML to BASIC ### Simple SYS call ```basic SYS 49152 ' call ML at $C000 SYS 49152,A,X,Y ' with register values (some monitors) ``` ### Passing parameters via memory ```basic POKE 251,LO : POKE 252,HI ' pass address in ZP SYS 49152 RESULT = PEEK(253) ' read result back ``` ### USR function hook ``` Location $0311-$0312 contains the USR vector (default: $FFB7) POKE 785, LO : POKE 786, HI ' point USR to your routine X = USR(VALUE) ' call from BASIC, float in FAC ``` --- ## Useful ML Idioms ### Clear accumulator quickly ```asm LDA #0 ; or: AND #0 or: EOR #$FF ; not #$FF ``` ### Test if A = 0 without changing A ```asm BEQ zero ; branch if A was 0 (after any instruction that sets Z) ``` ### Delay loop (~1 second at 1MHz, NTSC) ```asm DELAY LDX #$FF OUTER LDY #$FF INNER DEY BNE INNER DEX BNE OUTER RTS ``` ### Copy a block of memory ```asm ; Copy $C000-$C0FF to $D000 (100 bytes example — NOT I/O area in practice!) LDX #0 LOOP LDA $C000,X STA $CC00,X ; adjust destination INX BNE LOOP ; loop 256 times ``` ### Increment 16-bit pointer in zero page ($FB/$FC) ```asm INC $FB BNE SKIP INC $FC SKIP ... ``` --- ## Common Pitfalls 1. **Forgetting CLC/SEC before ADC/SBC** — results will be off by 1 2. **Branch out of range** — branches are ±127 bytes; use JMP workaround for longer distances 3. **Stack overflow** — only 256 bytes of stack; watch nested JSR depth 4. **I/O area at $D000** — reading/writing here when I/O is banked in hits chip registers, not RAM 5. **Decimal mode** — the 6510 supports BCD; make sure D flag is clear for normal math 6. **JMP ($nnFF) bug** — the 6502/6510 has a page-crossing bug with indirect JMP: high byte is fetched from $nn00, not ($nnFF)+1 7. **Forgetting RTI vs RTS** — IRQ handlers must use RTI; subroutines use RTS --- ## 6510-Specific: I/O Port ($0000/$0001) The 6510 has a built-in 8-bit I/O port multiplexed at $0000 (direction) and $0001 (data): ``` $0000 Direction: 1=output, 0=input (default: $2F = %00101111) $0001 Data port: Bit 0: LORAM — 0=BASIC ROM off, 1=on Bit 1: HIRAM — 0=Kernal ROM off, 1=on Bit 2: CHAREN — 0=Char ROM at $D000, 1=I/O at $D000 Bit 3: Cassette data output Bit 4: Cassette switch sense (input) Bit 5: Cassette motor control ``` To bank out ROMs and access full RAM: ```asm LDA #$34 ; %00110100 - RAM everywhere, I/O still visible STA $0001 ``` To restore normal operation: ```asm LDA #$37 ; %00110111 - BASIC+Kernal+I/O (normal) STA $0001 ```