Learning 6502 assembly

This page by Dan Polansky guides the reader through first steps of learning 6502 assembly. The approach is this: first point the reader to good online sources describing the assembly language, then provide exercises.

We will use Easy 6502 website, which contains a nice introduction together with an online 6502 emulator written in JavaScript. In that environment, one can manipulate a screen of 32 x 32 pixels and 16 colors, starting at $200, one byte per pixel. Furthermore, pseudorandom 8-bit numbers can be obtained from address $fe. One can also get the ASCII code of the last key pressed from $ff. This environment is good to start practicing, although it does not offer ASCII output, which is inconvenient for, say, numerical exercises.

If you never programmed in assembly, the Easy 6502 web site is a good start as a read. The processor is also described in Wikibooks. You may either read Easy 6502 right now, or you can start with exercises and only consult the guide on an as needed basis.

The following exercises are meant for Easy 6502 online emulator. The sequence of exercises is significant, where the latter ones incrementally build on the earlier ones. To learn most from this page, you have to do the exercises yourself rather than merely reading the solutions.

Motivation: Get a feel for assembly programming with a simple processor that was much in use in many home computers (Apple II, Commodore 64, Atari 8-bit, etc.) and appreciate the convenience of high-level languages, C included, and cherish the ingenuity and talent of all those 6502 programmers who created the 8-bit games on very limited machines.

License: All code snippets here are released into the public domain.

Cheat sheet
Here is a quick cheat sheet. Exercises follow in the next sections.

Instructions:
 * LDA, STA, LDX, STX, LDY, STY
 * TAX, TAY, TXA, TYA, TXS, TSX
 * CMP, CPX, CPY
 * BEQ, BNE, BCC, BCS, BPL, BMI
 * JMP, JSR, RTS
 * PLA, PHA
 * ADC, SBC, INC, INX, INY, DEC, DEX, DEY
 * CLC, SEC
 * ASL, LSR, AND, ORA, EOR, BIT, ROL, ROR
 * Etc.

Addressing modes:
 * LDA #$80     ; Acc := $80; called immediate
 * LDA $80      ; Acc := Peek($80)
 * LDA $8000    ; Acc := Peek($8000)
 * LDA ($80), Y ; Acc := Peek(Deek($80)+Y) ; only for zero page
 * LDA ($80, X) ; Acc := Peek(Deek($80+X)) ; only for zero page

Numerical literals (exemplified in use with LDA immediate):
 * LDA #170      ;decimal
 * LDA #$AA      ;hexadecimal
 * LDA #%10101010 ;binary

Flags of the status register:
 * N: Negative
 * V: Overflow
 * -: ignored
 * B: Break
 * D: Decimal: use BCD
 * I: Interrupt
 * Z: Zero
 * C: Carry

Links:
 * 6502 Assembly, wikibooks.org
 * 6502 Instruction Set, masswerk.at

Limitations of the CPU
To get a better idea about the character of 6502 programming, let us look at its limitations compared e.g. to Motorola 68000 ("68k"):
 * There are only 3 main registers: A, X and Y. By contrast, 68k has 8 data registers and 8 address registers. As a consequence, 6502 programming needs to use memory location for loop indices and other temporary needs much more often.
 * All three main registers are 8-bit. Thus, even such a simple thing as making an x coordinage loop from 0 to 319 (typical for some 8-bit computers using the CPU) cannot be handled by incrementing a single register with no additional work. By contrast, 68k has 32-bit data and address registers. Even 16-bit registers would make a huge difference.
 * Some operations are only possible with the accumulator, not with the index registers X and Y. If one wants to use these operations on, say, X, one can use TXA, then operate, and then TAX, thereby losing the value that was in A.
 * 6502 has no integer multiplication and division instructions, only addition and subtraction. 68k has multiplication.

Small block of pixels without a loop
Assignment: fill a small block of pixels, say, 3 pixels, with colors 0 through 2.

Solution: LDA #0    ;Load accumulator STA $200  ;Store accumulator LDA #1 STA $201 LDA #2 STA $202

What we have learned: we can use LDA and STA to manipulate memory. There is the immediate addressing mode indicated using the hash character (#) and the other addressing mode. The string character ($) indicates the value is hexadecimal; it is decimal without it.

Block of pixels using a loop
Assignment: fill a 256-long block of pixels (spanning mutliple lines on the 32x32 screen) with color 1.

Hint: use indirect addressing mode with register Y, using Y as a loop variable.

Solution: LDA #1 LDY #0 LOOP: STA $200, Y ;Indexed addressing with Y; store to $200+Y INY BNE LOOP ;Branch on not equal (here implicitly not equal to zero)

What we have learned:
 * The increment instruction affects the zeor flag.

Fill screen with vertical stripes
Assignment: fill the 32x32 screen with vertical color stripes. Which colors are to be used is left unspecified.

Hint: we may build on the ideas from previous exercises. We can no longer do with using Y as index register alone since we need to cover 32x32 = 4 * 256 bytes.

Solution: LDA #00 STA $80 LDA #02 STA $81 LDA #1 LDY #0 LDX #4   ;Four pages to process LOOP: TYA STA ($80), Y INY BNE LOOP INC $81 DEX BNE LOOP

What we have learned:
 * We can use the indirect addressing indexed with Y for the purpose. The base address needs to be stored at zero page ($0-$FF). We picked $80 arbitrarily. We note that 6502 uses little endian addressing: the lower byte is stored at $80 and the upper byte in $81 rather than the other way around.
 * There is TYA (A := Y) and similar for X and the other way around.

Fill screen with horizontal stripes
Assignment: Fill screen with horizontal stripes.

Solution: LDA #00 STA $80 LDA #02 STA $81 LDA #0 LDY #0 LDX #4 LOOP: STA ($80), Y STA $82 ;Temporary color store INY TYA AND #$1F BNE KEEPCOLOR LDA $82 CLC ADC #1 STA $82 KEEPCOLOR: LDA $82 CPY #0 BNE LOOP INC $81 DEX BNE LOOP What we have learned: there is no increment instruction for accumulator, but we can emulate it with CLC (clear the carry bit) followed by ADC (add with carry). We need to use additional memory to store values given how few registers we have.

Fill screen with random color
Assignment: fill the 32x32 screen with random color. This is a minor variation on filling the screen with a fixed color.

Hint: obtain a random byte from address $fe.

LDA #00 STA $80 LDA #02 STA $81 LDY #0 LDX #4 LOOP: LDA $FE STA ($80), Y INY BNE LOOP INC $81 DEX BNE LOOP

Plot a secondary diagonal
Assignment: plot a secondary diagonal using color 1. That is, start at upper righthand corner and end at lower lefthand corner. (Plotting the main diagonal is a bit easier and is left out.)

Solution: LDA #00 STA $80 LDA #02 STA $81 LDA #1 LDY #31 LDX #32  ;Pixel count LOOP: LDA #1 STA ($80), Y TYA CLC ADC #31 TAY BCC SKIP ;Branch on carry clear INC $81 SKIP: DEX BNE LOOP What we have learned: we cannot do addition on Y, only on A. The carry bit set by ADC is not erased by TAY.

Keyboard controlled pixels
Assignment: read key events and when a key is pressed, place random color to pixel that is offset from the screen beginning by the ASCII code of the key pressed. Read the ASCII code of the last key (or key combination, e.g. "a" with shift) from $ff. When no key is being pressed, keep setting random color to pixel with offset zero.

Solution: LOOP: LDY $ff ;Last key pressed LDA $fe ;Random byte STA $200, Y CPY #0 BEQ LOOP LDY #0 STY $FF ;Erase last key pressed or else it will stick BEQ LOOP What we have learned: How to read key events and erase last key pressed. How the keys map to codes, visually; thus, e.g. "a" is $61, and other lowercase letters alphabetically follow.

Keyboard-controlled moving dot
Assignment: implement a keyboard-controlled moving dot representing a person, represented as a single pixel of color 1 (white). Use color 5 (green) to first fill the screen as a background. Mark the screen boundary using color 8 (brown). As the player moves around, leave empty space (color 0, black) behind. Do not allow the player to enter the screen boundary filled with brown. Use keys A, S, D, and W to control the player.

Note: This was inspired by the snake game at Easy 6502. Like in the snake, one has to figure out how to receive keyboard events. However, it is simpler than the snake, not requiring keeping track of the locations of the snake body.

Solution: LDA #00 STA $80 LDA #02 STA $81 LDA #5 LDY #0 LDX #4   ;Four pages to process LOOP1: STA ($80), Y INY BNE LOOP1 INC $81 DEX BNE LOOP1 LDA #02 STA $81 LDA #8 LDY #0 LDX #32 LOOP2: STA ($80), Y INY DEX BNE LOOP2 LDA #$E0 ; Let $80 start at $200 + 31 * 32 = 5E0 STA $80 LDA #$05 STA $81 LDA #8 LDY #0 LDX #32 LOOP3: STA ($80), Y INY DEX BNE LOOP3 LDA #00 STA $80 LDA #02 STA $81 LDY #0 LDX #32 ;Pixel count LOOP4: LDA #8 STA ($80), Y TYA CLC ADC #32 TAY BCC SKIP INC $81 SKIP: DEX BNE LOOP4 LDA #00 STA $80 LDA #02 STA $81 LDY #31 LDX #32 ;Pixel count LOOP5: LDA #8 STA ($80), Y TYA CLC ADC #32 TAY BCC SKIP2 INC $81 SKIP2: DEX BNE LOOP5 LDA #$EF STA $82  ;Player position as pixel address; $200 + 15 * 32 + 15 STA $84 LDA #$03 STA $83 STA $85 LOOP6: LDA $84 STA $82 LDA $85 STA $83 LOOP7: LDA #1 LDY #0 STA ($82), Y LDY $FF  ;Last key pressed CPY #$61 ;"a", left BNE TESTD LDA #0 STA $FF LDA $82 STA $84 LDA $83 STA $85 DEC $84 LDA $84 CMP #$FF BNE SKIP3 DEC $85 SKIP3: LDY #0 LDA ($84), Y CMP #8 ;brown BEQ LOOP7 LDA #0 STA ($82), Y CLC BCC LOOP6 TESTD: LDY $FF  ;Last key pressed CPY #$64 ;"d", right BNE TESTW LDA #0 STA $FF LDA $82 STA $84 LDA $83 STA $85 INC $84 LDA $84 CMP #0 BNE SKIP4 INC $85 SKIP4: LDY #0 LDA ($84), Y CMP #8 ;brown BEQ LOOP7 LDA #0 STA ($82), Y CLC BCC LOOP6 TESTW: LDY $FF  ;Last key pressed CPY #$77 ;"w", up BNE TESTS LDA #0 STA $FF LDA $82 STA $84 LDA $83 STA $85 LDA $84 SEC SBC #32 STA $84 BCS SKIP5 DEC $85 SKIP5: LDY #0 LDA ($84), Y CMP #8 ;brown BEQ LOOP7C LDA #0 STA ($82), Y JMP LOOP6 LOOP7C: JMP LOOP7 TESTS: LDY $FF  ;Last key pressed CPY #$73 ;"s", down BNE LOOP7C LDA #0 STA $FF LDA $82 STA $84 LDA $83 STA $85 LDA $84 CLC ADC #32 STA $84 BCC SKIP7 INC $85 SKIP7: LDY #0 LDA ($84), Y CMP #8 ;brown BEQ LOOP7C LDA #0 STA ($82), Y JMP LOOP6
 * Memory variables
 * $80-81: screen address
 * $82-83: player position as pixel address on the screen
 * $84-85: temporary copy of the above to be modified and conditionally copied to the actual position
 * Fill screen with color 5, green
 * Plot boundary with color 8, brown
 * Player control and key event loop

What we have learned:
 * Far jumps are not possible using relative branch instructions Bxx; one has to use e.g. JMP.
 * To subtract the value V using SBC #V, one has to make sure the carry flag is set (via SEC) rather than cleared (via CLC).

Online assemblers
One can asseble the 6502 assembly source code using online assemblers:
 * Easy 6502, skilldrick.github.io
 * virtual 6502 Assembler, masswerk.at

The Masswerk assembler is too sensitive in that it does not compile "LDA ($80), Y" because of the space after the comma. Moreover, it assembles "LDA ($80),X", which is invalid, into "LDA $80,X". Easy 6502 seems better in those two regards.