(Updated July 16, 2024)
Table of Contents
Physical Details
The MOS 6502 was originally released as a 40-pin dual-inline package (DIP-40) in both plastic and ceramic.
The pins connect to locations inside the packaging and onto the CPU die with tiny filaments. Each pin represents a signal in the form of voltage going into or coming out of the CPU. We won’t get into the details of each pin, but some of these pins may make immediate sense as we describe the inner workings of the CPU.
Category | Width in Bits | Size/Range |
---|---|---|
Data Bus (D0-D7) | 8 | 0 -> 255 (-128 -> 127) |
Address Bus (A0-A15) | 16 | 0 -> 65536 |
Stack | 8 (16-bits fixed to page 1) | 256 |
The table shows a few categories of information related to data representation. The 6502 can only work with 8 bits of data at a time. It also has the limitation of a 16-bit address bus, giving only 65536 possible addresses or 64KiB.
All of the storage measurements in this book follow the IEC standard. You can find more details here as well.
Of these possible addresses, there are a few that are considered special.
Memory | Size | Range |
---|---|---|
Page 0 | 256 bytes | $00 -> $FF |
Page 1 (Stack) | 256 bytes | $0100 -> $01FF |
NMI (pin 4) Handler | 2 bytes | $FFFA-$FFFB |
RESET (pin 40) Handler | 2 bytes | $FFFC-$FFFD |
BRK/IRQ (pin 6) Handler | 2 bytes | $FFFE-$FFFF |
Page 0 is special because an addressing mode specifically uses these 256 locations. We will discuss this further in the addressing modes section. This page is a precious commodity often used for long-lived storage. Operating systems written for the 6502 often monopolized this space, leaving very little to the programmer.
Page 1 is special because this is where the stack lives. The 6502 only has 256 bytes for the stack, which is rather small. This is used for subroutine calls and temporary storage. This cannot be used as long-lived storage.
The handlers are little-endian addresses in memory that the CPU will go to when these events occur.
Registers
There are three special purpose and three general purpose registers on the 6502. These are shown below:
8 bits 8 bits |----------------|----------------| | | | Program Counter (PC) |----------------|----------------| | $01 | | Stack Pointer (S) |----------------|----------------| | NV1BDIZC | Flags/Processor Status (P) |----------------| | | Accumulator (A) |----------------| | | X Index Register (X) |----------------| | | Y Index Register (Y) |----------------|
General-purpose registers are like internal variables for the CPU. They can only hold one value at a time, and it changes frequently. These registers are used to marshal data to and from memory. Arithmetic operations can only happen within the accumulator, so data must be brought from memory to the CPU to accomplish any work.
The index registers can be used to access sequential locations in memory. This helps to facilitate data structures like arrays, strings, lookup tables, etc.
Program Counter
The program counter points to the next instruction to be executed. During sequential execution of code, it will advance one, two, or three bytes depending on the nature of the instruction. Further, the program counter can also change via conditional branching, unconditional jumps, and calling subroutines.
Stack Pointer
The stack pointer is 8 bits wide, but since it is located within page one and cannot be relocated, we show it with a presumed $01 as the most significant byte of the pointer. The value of S begins at $FF (255). Pushing a value adds it to the stack and decrements the stack pointer by one. Pulling a value removes it and increments the stack pointer by one. Stacks generally grow toward lower memory.
Flags
This register is constantly changing. Each bit is set or cleared based on the current instruction’s action. Each bit represents something about the recent data operation or direct manipulation of the flags.
N - Negative - This flag indicates that the last operation resulted in a value in A, X, or Y whose most significant bit is set. V - oVerflow - This flag is affected by arithmetic operations and the instructions PLP and BIT. This bit is set when the result of signed arithmetic would result in an overflow. Signed byte values are in the range -128 through 127. For example, adding 85 to 45 yields 130, which is -126. So, the addition has overflowed. 1 - UNUSED - This flag has no defined purpose and is presumed always to be 1. B - Break - Recall that memory locations $FFFE-$FFFF are for the BRK/IRQ Handler. When this bit is set, it helps distinguish when a hardware IRQ (Interrupt ReQuest) signal results from executing the BRK instruction. D - Decimal - This flag indicates that decimal mode is in effect. When set (using SED), all arithmetic operations are performed with Binary Coded Decimal (BCD) instead of two's complement. This is cleared with CLD. I - Interrupt disable - When this flag is set, the CPU will not process the IRQ signal. This can happen when explicitly set with the SEI instruction or when the processor is already handling an IRQ signal. Z - Zero - This flag behaves similar to N. This flag indicates that the last operation resulted in a zero value in A, X, or Y. Otherwise, the flag is cleared. C - Carry - This flag is a busy one. All arithmetic, comparisons, and bit rotations/shifts affect this flag. Primarily, this flag indicates when a 9th bit was necessary to either carry for addition or borrow for subtraction.
Accumulator
This register is where all arithmetic happens and accumulates the result. It is the only register connected to the Arithmetic and Logic Unit (ALU).
X Index Register
The X
register is a general-purpose register that can also be used for indexing. It can be used as an additive quantity to specific operations to manage an array or lookup table. There is one addressing mode that only X
can be used, called indexed-indirect.
This register can be used to manipulate the stack pointer (S).
Y Index Register
The Y
register is a general-purpose register that can also be used for indexing. It can be used as an additive quantity to specific operations to manage an array or lookup table. There is one addressing mode that only Y
can be used, called indirect-indexed.
Instructions
The instruction set for the 6502 offers 56 instructions in multiple categories, which we have broken down into their basic functional models.
Load and Store
Instructions to move data between memory and registers.
Mnemonic | Purpose | Flags Affected |
---|---|---|
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 |
Register Transfer
The X and Y registers can transfer data to and from the accumulator; there is no direct transfer between index registers.
Mnemonic | Purpose | Flags Affected |
---|---|---|
TAX | Transfer accumulator to X | N,Z |
TAY | Transfer accumulator to Y | N,Z |
TXA | Transfer X to accumulator | N,Z |
TYA | Transfer Y to accumulator | N,Z |
Stack
Only the flags and accumulator can be pushed to or pulled from the stack. The stack pointer can only
Mnemonic | Purpose | Flags Affected |
---|---|---|
PHA | Push accumulator on stack | |
PHP | Push processor status on stack | |
PLA | Pull accumulator from stack | N,Z |
PLP | Pull processor status from stack | All |
TSX | Transfer stack pointer to X | N,Z |
TXS | Transfer X to stack pointer |
Logical Operators
Operations are performed on the accumulator.
Mnemonic | Purpose | Flags Affected |
---|---|---|
AND | Logical AND | N,Z |
BIT | Bit Test | N,V,Z |
EOR | Exclusive OR | N,Z |
ORA | Logical Inclusive OR | N,Z |
Arithmetic Operators
Mnemonic | Purpose | Flags Affected |
---|---|---|
ADC | Add with Carry | N,V,Z,C |
CMP | Compare accumulator | N,Z,C |
CPX | Compare X regsiter | N,Z,C |
CPY | Compare Y regsiter | N,Z,C |
SBC | Subtract with Carry | N,V,Z,C |
Increment/Decrement
Increment/Decrement memory or an index register. You cannot perform this operation on the accumulator.
Mnemonic | Purpose | Flags Affected |
---|---|---|
DEC | Decrement memory location | N,Z |
DEX | Decrement X register | N,Z |
DEY | Decrement Y register | N,Z |
INC | Increment memory location | N,Z |
INX | Increment X register | N,Z |
INY | Increment X register | N,Z |
Shift/Rotate
These instructions cause the bits in memory or the accumulator to be shifted one position to the left or right. Rotates pull the carry flag into the vacant bit, replacing the carry with the bit that was rotated out.
Mnemonic | Purpose | Flags Affected |
---|---|---|
ASL | Arithmetic Shift Left | N,Z,C |
LSR | Logical Shift Right | N,Z,C |
ROL | Rotate Left | N,Z,C |
ROR | Rotate Right | N,Z,C |
Jumps and Calls
These instructions change the program counter to unconditionally alter execution. JMP
does not alter the stack. JSR
pushes the current PC on the stack before jumping to the new address. RTS
pulls the former PC from the stack and makes it the current PC, effectively returning from the subroutine.
Mnemonic | Purpose | Flags Affected |
---|---|---|
JMP | Jump to new location | |
JSR | Jump to a subroutine | |
RTS | Return from subroutine |
Conditional Branching
These instructions alter the program flow by
Mnemonic | Purpose | Flags Affected |
---|---|---|
BCC | Branch if carry clear | |
BCS | Branch if carry set | |
BEQ | Branch if zero set | |
BMI | Branch if negative set | |
BNE | Branch if zero clear | |
BPL | Branch if negative clear | |
BVC | Branch if overflow clear | |
BVS | Branch if overflow set |
Flag Manipulation
These instructions intentionally alter the state of the bits in the process
Mnemonic | Purpose | Flags Affected |
---|---|---|
CLC | Decrement memory location | N,Z |
CLD | Decrement X register | N,Z |
CLI | Decrement Y register | N,Z |
CLV | Increment memory location | N,Z |
SEC | Increment X register | N,Z |
SED | Increment X register | N,Z |
SEI | Increment X register | N,Z |
System Functions
Mnemonic | Purpose | Flags Affected |
---|---|---|
BRK | Force an interrupt | B |
NOP | No Operation | |
RTI | Return from Interrupt | All |
Addressing Modes
There are 13 addressing modes. But don’t worry; not all can be applied to every instruction. Most of them are pretty straightforward to understand. Several have been combined into their respective categories since they are technically one-offs from the basic model.
Many of the examples will use hexadecimal notation to represent values. Most documentation will use similar notation. Those unfamiliar with hexadecimal, other numeric systems, or bitwise operations and two’s complement now have many links to follow.
Implied/Implicit
This mode is for instructions whose action is implied by the mnemonic name. For example, CLC
, which clears the carry flag, or DEX
, which decrements the X register, requires no other arguments or details to make the instruction work.
DEX ; decrement X
DEY ; decrement Y
CLC ; reset carry
RTS ; return
Accumulator
While some of these instructions are implicit, a few also allow you to provide an operand of A
. This is not a requirement for our assembler, but it is good to know if you come across it in the wild.
LSR ; shift accumulator
is the same as
LSR A ; shift accumulator
Immediate
The immediate addressing mode allows using an 8-bit constant using the #
symbol.
LDA #$A0 ; load 160 into A
LDX #$00 ; set X to zero
LDY #100 ; set Y to 100 (note the missing $)
Zero Page
Zero-page addressing involves using bytes from the first page of memory. Since these are from the first page, all addresses can be represented at 8-bit quantities instead of the typical 16-bit addresses as $00 is presumed to be the first byte. This was especially useful for two reasons:
- Fewer bytes were necessary, thereby shrinking the code size.
- Instructions ran slightly faster than absolute addressing since only half an address was needed.
LDA $10 ; Load accumulator with value at zero page address $10
STA $11 ; Store accumulator at zero page address $11
The X
and Y
registers can also be used as an additive quantity known as indexing. We take the address and add the value of the index register, which provides an effective address.
LDY $10, X ; load Y with $10+X
STY $40, X ; store Y at $40+X
or
LDX $10, Y ; load X with $10+Y
STX $40, Y ; store X at $40+Y
This is rather limited since there are only 256 possible zero-page addresses. If the effective address exceeds $FF, it will wrap around, yielding an 8-bit value.
Relative
Relative addressing is used with branches. All branch instructions react to the state of the flags. Branches are limited in how far they may go from the current PC. The value after the branch instruction is an 8-bit two’s-complement quantity. The destination must be within 127 bytes forward or -128 from the current PC.
This is calculated by the assembler, but it’s important to know the distance limitation with branch instructions.
LDA $10 ; get value from $10
CMP $11 ; check with $11
BEQ NO ; skip if equal
INC $10 ; increment $10
NO: RTS ; leave
Absolute
Absolute is very similar to zero-page addressing except that the addresses are a full 16-bit value.
LDA $A310 ; Load accumulator with value at address $A310
STA $A311 ; Store accumulator at address $A311
Again, like zero-page, the X
and Y
registers can also be used as an additive quantity known as indexing. We take the address and add the value of the index register, which provides an effective address.
LDY $A310, X ; load Y with $A310+X
STY $B340, X ; store Y at $B340+X
or
LDX $A310, Y ; load X with $A310+Y
STX $B340, Y ; store X at $B340+Y
Indirect
Indirect addressing comes in three forms.
- The first, indirect, involves the
JMP
instruction. - The next, indexed indirect, involves a zero page location and the
X
register. - The last, indirect indexed, involves a zero page location and the
Y
register.
Indirect
Indirect addressing allows us to look up an address in another location and use that as the destination. Parentheses are used to indicate that the target address is located at the stated address.
JMP ($4000) ; Jump to location stored at $4000
Let’s say the address $FFD2 is in $4000 and $4001 (as $D2 and $FF). The JMP
instruction will ultimately jump to $FFD2.
Again, the X
and Y
registers can also be used as an additive quantity known as indexing. However, these only work with zero page addresses. The effective address is calculated differently depending on the form used.
Indexed Indirect
Using the following hex dump as a table of addresses in zero page
0010: 00 40 C0 40 F8 50 40 A0
We can use the following to get a value from the table.
LDX #$04
LDY ($10, X) ; load Y with with value at ($10+X) or $50F8
The key to remember here is:
- The addresses in the table are in little-endian.
- The least significant byte is pointed to by ($10+X), which in this case is ($14), and the value there is $F8.
- We are pulling the effective address from $14 and $15.
So, the bytes in question are $F8 followed by $50, and Y
is loaded with the value at the address $50F8. This is rather limited since there are only 256 possible zero-page addresses.
Indirect Indexed
This involves zero-page and the Y register. This is the more commonly used form over Indexed Indirect. In this model we assume there is only one address of interest in zero page. The address in parenthesis is used to fetch a 16-bit address, and then Y
is added to it, forming the effective address (with wrap-around).
Using the following hex dump as a table of addresses in zero page
0010: 00 40 C0 40 F8 50 40 A0
We can use the following to get a value from the table.
LDY #$20
LDX ($10), Y ; load X with value at ($10)+Y or $4020
The key to remember here is:
- The addresses in the table are in little-endian.
- The least significant byte is pointed to by ($10), and the value is $00.
- We are pulling the effective address from $10 and $11.
The address is $4000 + $20, which is $4020. This is rather limited since there are only 256 possible zero-page addresses.