Famicom Keyboard picture
20A01 — Famicom Keyboard

Assembly(6502) is the language used to program the famicom microprocessor.

This page is a collection of notes on the basics of 6502 assembly, assembled from various guides and tutorial. Understanding these notes require prior understanding of binary numbers.


The 6502 handles data in its registers, each of which holds one byte(8-bits) of data. There are a total of three general use and two special purpose registers:

Note: When you use X it adds the value of X to the memory address and uses the 16-bit value at that address to do the write. Whereas when you use Y it adds the value of Y to the address stored in the memory address it's reading from instead. 6502 is little-endian, so $0200 is stored as $00 $02 in memory.

NES System Architecture

The NES screen resolution is 256x240.


The 6502 has 9 major(13 in total) addressing modes, or ways of accessing memory.

Immediate#aaThe value given is a number to be used immediately by the instruction. For example, LDA #$99 loads the value $99 into the accumulator.
AbsoluteaaaaThe value given is the address (16-bits) of a memory location that contains the 8-bit value to be used. For example, STA $3E32 stores the present value of the accumulator in memory location $3E32.
Zero PageaaThe first 256 memory locations ($0000-00FF) are called "zero page". The next 256 instructions ($0100-01FF) are page 1, etc. Instructions making use of the zero page save memory by not using an extra $00 to indicate the high part of the address.
ImpliedMany instructions are only one byte in length and do not reference memory. These are said to be using implied addressing. For example, CLC, DEX & TYA.
Indirect Absolute(aaaa)Only used by JMP (JuMP). It takes the given address and uses it as a pointer to the low part of a 16-bit address in memory, then jumps to that address. For example, JMP ($2345) or, jump to the address in $2345 low and $2346 high
Absolute Indexed,X/Yaaaa,XThe final address is found by taking the given address as a base and adding the current value of the X or Y register to it as an offset. So, LDA $F453,X where X contains 3 Load the accumulator with the contents of address $F453 + 3 = $F456.
Zero Page Indexed,X/Yaa,XSame as Absolute Indexed but the given address is in the zero page thereby saving a byte of memory.
Indexed Indirect(aa,X)Find the 16-bit address starting at the given location plus the current X register. The value is the contents of that address. For example, LDA ($B4,X) where X contains 6 gives an address of $B4 + 6 = $BA. If $BA and $BB contain $12 and $EE respectively, then the final address is $EE12. The value at location $EE12 is put in the accumulator.
Indirect Indexed(aa),YFind the 16-bit address contained in the given location ( and the one following). Add to that address the contents of the Y register. Fetch the value stored at that address. For example, LDA ($B4),Y where Y contains 6 If $B4 contains $EE and $B5 contains $12 then the value at memory location $12EE + Y (6) = $12F4 is fetched and put in the accumulator.

6502 Processor Overview

$0000-0800Internal RAM, 2KB chip in the NES
$2000-2007PPU access ports
$4000-4017Audio and controller access ports
$6000-7FFFOptional WRAM inside the game cart
$8000-FFFFGame cart ROM


To make graphics on the screen you must write graphic data to the PPU memory, but you can't write directly to PPU memory, you have to use PPU ports $2006 and $2007. By using $2006 you declare the address of PPU memory then by using $2007 you write the desired value to that address, PPU Memory addresses are 16bit starting from $0000~$3FFF(0000-1fff = tiles & 2000-23ff = nametable 0).

Hexhigh byte($4A)low byte($0F)

So you need to write twice to $2006 to declare it's address, the first write declares high byte of address, the second write declares the low byte of address. Each time you write a value to $2007, the PPU address is automatically adjusted to the next address, so you don't need to declare the PPU address with $2006 for sequential PPU memory addresses.

  LDA #$20   ; high byte
  STA $2006
  LDA #$00   ; low byte
  STA $2006
  LDA #$04   ; sprite-id
  STA $2007

Calculate at what address to draw it: $2000 plus 32 times the vertical position of the tile (in 8-pixel units) plus the horizontal position of the tile (in 8-pixel units), write the high byte of the address to $2006: this is usually values $20 to $23, and write the low byte of the address to $2006. In other words, calculate the tile offset (TileY * 32 + TileX) and then add the base address. This will give you a pointer you can use to access any part of the map.


Directives are commands you send to the assembler to do things like locating code in memory. They start with . and are indented. This sample directive tells the assembler to put the code starting at memory location $8000, which is inside the game ROM area:

  .org $8000


The label is aligned to the far left and has a : at the end. The label is just something you use to organize your code and make it easier to read. The assembler translates the label into an address. Sample label:

  .org $8000


The opcode is the instruction that the processor will run, and is indented like the directives. In this sample, JMP is the opcode that tells the processor to jump to the MyFunction label:

  .org $8000
  JMP MyFunction


The operands are additional information for the opcode. Opcodes have between one and three operands. In this example the #$FF is the operand:

  .org $8000
  LDA #$FF
  JMP MyFunction


Comments are to help you understand in English what the code is doing. When you write code and come back later, the comments will save you. You do not need a comment on every line, but should have enough to explain what is happening. Comments start with a ; and are completely ignored by the assembler. They can be put anywhere horizontally, but are usually spaced beyond the long lines.

  .org $8000
MyFunction:        ; loads FF into accumulator
  LDA #$FF
  JMP MyFunction

Common Opcodes

Load/Store opcodes
LDA #$0ALoaD the value 0A into the accumulator A. The number part of the opcode can be a value or an address. If the value is zero, the zero flag will be set.
LDX $0000LoaD the value at address $0000 into the index register X. If the value is zero, the zero flag will be set.
LDY #$FF LoaD the value $FF into the index register Y. If the value is zero, the zero flag will be set.
STA $2000STore the value from accumulator A into the address $2000. The number part must be an address.
STX $4016STore value in X into $4016. The number part must be an address.
STY $0101STore Y into $0101. The number part must be an address.
TAX Transfer the value from A into X. If the value is zero, the zero flag will be set.
TAY Transfer A into Y. If the value is zero, the zero flag will be set.
TXA Transfer X into A. If the value is zero, the zero flag will be set.
TYA Transfer Y into A. If the value is zero, the zero flag will be set.
Math opcodes
ADC #$01 ADd with Carry. A = A + $01 + carry. If the result is zero, the zero flag will be set
SBC #$80 SuBtract with Carry. A = A - $80 - (1 - carry). If the result is zero, the zero flag will be set
CLC CLear Carry flag in status register. Usually this should be done before ADC
SEC SEt Carry flag in status register. Usually this should be done before SBC
INC $0100INCrement value at address $0100. If the result is zero, the zero flag will be set
DEC $0001DECrement $0001. If the result is zero, the zero flag will be set
INY INcrement Y register. If the result is zero, the zero flag will be set
INX INcrement X register. If the result is zero, the zero flag will be set
DEY DEcrement Y. If the result is zero, the zero flag will be set
DEX DEcrement X. If the result is zero, the zero flag will be set
ASL A Arithmetic Shift Left. Shift all bits one position to the left. This is a multiply by 2. If the result is zero, the zero flag will be set
LSR $6000Logical Shift Right. Shift all bits one position to the right. This is a divide by 2. If the result is zero, the zero flag will be set
Comparison opcodes
CMP #$01 CoMPare A to the value $01. This actually does a subtract, but does not keep the result. Instead you check the status register to check for equal, . Less than, or greater than
CPX $0050ComPare X to the value at address $0050
CPY #$FF ComPare Y to the value $FF
Control-Flow opcodes
JMP $8000JuMP to $8000, continue running code there
BEQ $FF00Branch if EQual, contnue running code there. First you would do a CMP, which clears or sets the zero flag. Then the BEQ will check the zero flag. If zero is set (values were equal) the code jumps to $FF00 and runs there. If zero is clear (values not equal) there is no jump, runs next instruction
BNE $FF00Branch if Not Equal - opposite above, jump is made when zero flag is clear


The compare instructions set or clear three of the status flags (Carry, Zero, and Negative) that can be tested with branch instructions, without altering the contents of the operand. There are three types of compare instructions:

CMPCompare Memory and Accumulator
CPXCompare Memory and IndexX
CPYCompare Memory and Index Y

The CMP instruction supports eight different addressing modes, the same ones supported by the ADC and SBC instructions. Since the X and Y registers function primarily as counters and indexes, the CPX and CPY instructions do not require this elaborate addressing capability and operate with just three addressing modes (immediate, absolute, and zero page).

The compare instructions subtract (without carry) an immediate value or the contents of a memory location from the addressed register, but do not save the result in the register. The only indications of the results are the states of the three status flags: Negative (N), Zero (Z), and Carry (C). The combination of these three flags indicate whether the register contents are less than, equal to (the same as), or greater than the operand "data" (the immediate value or contents of the addressed memory location. The table below summarizes the result indicators for the compare instructions.

Compare Result NZC
A, X, or Y < Memory *00
A, X, or Y = Memory 011
A, X, or Y > Memory *01

The compare instructions serve only one purpose; they provide information that can be tested by a subsequent branch instruction. For example, to branch if the contents of a register are less than an immediate or memory value, you would follow the compare instruction with a Branch on Carry Clear (BCC) instruction, as shown by the following:

Comparing Memory to the Accumulator

  CMP  $20    ; Accumulator less than location $20?
  ; No, continue execution here.
  ; Execute this if Accumulator is less than location $20.

Use of Branch Instructions with Compare

To Branch IfFollow compare instruction with
For unsigned numbersFor signed numbers
Register is less than dataBCC THEREBMI THERE
Register is equal to dataBEQ THEREBEQ THERE
Register is greater than dataBEQ HERE
Register is less than or equal to dataBCC THERE
Register is greater than or equal to dataBCS THEREBPL THERE

Controller Ports

The controllers are accessed through memory port addresses $4016 and $4017. First you have to write the value $01 then the value $00 to port $4016. This tells the controllers to latch the current button positions. Then you read from $4016 for first player or $4017 for second player. The buttons are sent one at a time, in bit 0. If bit 0 is 0, the button is not pressed. If bit 0 is 1, the button is pressed.

Button status for each controller is returned in the following order: A, B, Select, Start, Up, Down, Left, Right.

Palette Codes

00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F

Last update on 20F05, edited 13 times. +25/66fh