Notes on 6502 Assembly
The NES runs a very slightly modified 6502 processor. What follows are some very introductory, and not at all exhaustive notes on 6502 Assembly, or ASM.
If you find this at all interesting, Easy 6502 is a really great introductory primer on 6502 Assembly that lets you get your hands dirty right from a web browser.
Numbers prefixed with one of the following:
$are hexadecimal format
#are literal numbers
Any other number without either of these prefixes refers to a memory location.
Loads the hex value
$01 into register
Registers and flags
There are 3 primary registers,
A is usually called the accumulator.
Each register holds a single byte
SP is the stack pointer, a register that is decremented every time a byte is pushed onto the stack and incremented whenever a byte is popped off the stack.
PC is the program counter.
PC is how the processor keeps track of where in the currently running program it is.
Each flag is 1 bit, so all 7 flags can live in a single byte
More info on registers and flags.
In 6502 Assembly instructions are like words in Forth, or functions in a higher order programming language. Every instruction takes 0 or 1 arguments.
An example of some instructions,
LDA #$c0 ; Load the hex value $c0 into the A register TAX ; Transfer the value in the A register to X INX ; Increment the value in the X register ADC #$c4 ; Add the hex value $c4 to the A register BRK ; Break - we're done
For a full list of 6502 ASM instructions see,
6502 ASM has a handful of branching instructions — they almost all rely on flags to determine what branch to follow.
The 6502 has 65536 bytes of available memory. These bytes are typically described using the HEX range $0000 - $ffff.
When the 6502 refers to addressing modes, it really means “What is the source of the data used in this instruction?”
The different modes are,
With absolute addressing, the full memory location is used as the argument to the instruction.
All instructions that support absolute addressing (with the exception of the jump instructions) also have the option to take a single-byte address. This type of addressing is called “zero page” - only the first page (the first 256 bytes) of memory is accessible. This is faster, as only one byte needs to be looked up, and takes up less space in the assembled code as well.
In this mode, a zero page address is given, and then the value of the
X register is added.
This is the equivalent of zero page,X, but can only be used with
Absolute,X and absolute,Y:
These are the absolute addressing versions of zero page,X and zero page,Y.
Immediate addressing doesn’t strictly deal with memory addresses - this is the mode where actual values are used. For example,
LDX #$01 loads the value
$01 into the
X register. This is very different to the zero page instruction
LDX $01 that loads the value at memory location
$01 into the
$c0 (or label)
Relative addressing is used for branching instructions. These instructions take a single byte, which is used as an offset from the following instruction.
Some instructions don’t deal with memory locations, for example,
INX - increment the
X register. These have implicit addressing because the argument is implied by the instruction.
Indirect addressing uses an absolute address to look up another address. The first address gives the least significant byte of the address, and the following byte gives the most significant byte.
This one’s kinda weird. It’s like a cross between zero page,X and indirect. Basically, you take the zero page address, add the value of the
X register to it, then use that to look up a two-byte address.
Indirect indexed is like indexed indirect, but instead of adding the
X register to the address before de-referencing, the zero page address is de-referenced, and the
Y register is added to the resulting address.
For more on the different modes of addressing,
The current depth of the stack is measured by the stack pointer, a special register. The stack lives in memory between
$01ff. The stack pointer is initially
$ff, which points to memory location
$01ff. When a byte is pushed onto the stack, the stack pointer becomes
$fe, or memory location
$01fe, and so on.
Jumping is like branching with two main differences:
- First, jumps are not conditionally executed
- Second, they take a two-byte absolute address
For small programs, this second detail isn’t important, as you’ll be using labels, and the assembler works out the correct memory location from the label. For larger programs though, jumping is the only way to move from one section of the code to another.
Because these are but the barest of minimum notes, here are some more resources for continued reference.