NEWS, EDITORIALS, REFERENCE
6502 / 6510 Instruction Set
Every Commodore 64 programmer should have the 6502/6510 instruction set at their fingertips. Although there are many reference texts like this, I find them to be lacking.
My goal is to provide the fastest, easiest-to-use and best organized 6502/6510 instruction set reference document on the internet. It's not just for you, it's for me too. If you have ideas for how I can improve this, let me know in the comments.
Special thanks to 6502.org for the source of the raw content.Last Updated: September 2021
|A... is for Add|
|B... is for Branch|
|C... is for Clear|
|D... is for Decrement|
|E... is for Exclusive|
|I... is for Increment|
|J... is for Jump|
|L... is for Load|
|N... is for No|
|O... is for Or|
|P... is for Push|
|R... is for Roll|
|S... is for Subtract|
|T... is for Transfer|
The Processor flags are the bits of the 6502's 8-bit processor status register. The behavior of many instructions is influenced by the state of one or more processor flags at the time of execution. Additionally, some instructions may change the state of one or more flags. Following the syntax table for each instruction, you will find a block of processor flags such as the one below. The highlighted flags are those which are potentially modified by the instruction.
Each flag has a letter symbol for easier reference. Here are the flags and what they mean:
|Negative||N||b7||Set when an operation results in a negative number|
|Overflow||V||b6||Set when a signed addition or subtraction results in an overflow|
|Unused||—||b5||This bit of the processor status register is not used|
|Break||B||b4||Set when a BRK instruction is executed|
|Decimal Mode||D||b3||When set, certain instructions operate in decimal rather than binary mode|
|Interrupt Mask||I||b2||When set, interrupt requests are ignored|
|Zero||Z||b1||Set when an operation results in a zero|
|Carry||C||b0||Set when an unsigned addition or subtraction results in an overflow|
See: Flag Instructions
The Interrupt Flag (I)
The interrupt flag is used to mask (prevent) maskable interrupts (aka IRQs) from occurring. When the flag is set (SEI) interrupts are masked. When it is clear (CLI) interrupts are allowed to occur. This flag does not signal the presence or absence of an interrupt condition.
The interrupt flag is automatically set in response to an interrupt, to prevent the interruption of an interrupt service routine in progress. The state of the interrupt flag is restored to its prior state by the return from interrupt (RTI) instruction. If you want an interrupt service routine to be able to be interrupted, you must clear the interrupt flag (CLI) within that routine.The Decimal Flag (D)
The decimal flag controls how the 6502 adds and subtracts. If set, arithmetic is carried out in packed binary coded decimal. This flag is unchanged by interrupts and is unknown on power-up. If you are uncertain of what mode you are in, you should explicitly clear (CLD) or set (SED) decimal mode to ensure expected behavior.The Overflow Flag (V)
The overflow flag is generally misunderstood and therefore under-utilised. After addition (ADC) or subtraction (SBC), the overflow flag is set if the two's complement result is less than -128 or greater than +127, otherwise the flag is cleared.
In twos complement, $80 through $FF represent -128 through -1, and $00 through $7F represents 0 through +127. Thus, after:
CLC LDA #$7F ; 127 ADC #$01 ; + 1
The overflow flag is set, because in signed arithmetic (127 + 1 = -128). And after:
SEC LDA #$80 ; -128 SBC #$01 ; - 1
The overflow flag is set, because in signed arthmetic (-128 -1 = 127).
The overflow flag is not affected by increments, decrements, shifts and logical operations. It is only affected by: ADC, SBC, BIT, CLV, PLP and RTI. Unlike the decimal flag and the carry flag which have pairs of instructions for clearing and setting (CLD/SED and CLC/SEC), there is no instruction to set the overflow flag. A workaround is to BIT any byte whose bit6 is set. A common technique is to BIT an RTS instruction.
For a more thorough explanation of the overflow flag, see: The Overflow (V) Flag Explained.
Op code execution times are measured in machine cycles; one machine cycle equals one clock cycle. Many instructions require one extra cycle for execution if a page boundary is crossed; these are indicated by a + following the time values shown.
When the 6502 is ready for the next instruction it increments the program counter before fetching the instruction. Once it has the op code, it increments the program counter by the length of the operand, if any. This must be accounted for when calculating branches or when pushing bytes to create a false return address (i.e. jump table addresses are made up of addresses-1 when it is intended to use an RTS rather than a JMP).
The program counter is loaded least significant byte first. Therefore the most significant byte must be pushed first when creating a false return address.
When calculating branches a forward branch of 6 skips the following 6 bytes so, effectively the program counter points to the address that is 8 bytes beyond the address of the branch opcode; and a backward branch of $FA (256-6) goes to an address 4 bytes before the branch instruction.
See: Branch Instructions
Use caution with indexed zero page operations as they are subject to wrap-around. For example, if the X register holds $FF and you execute LDA $80,X you will not access $017F as you might expect; instead you access $7F i.e. $80-1. This characteristic can be used to advantage but make sure your code is well commented.
It is possible, however, to access $017F when X = $FF by using the Absolute,X addressing mode of LDA $80,X. That is, instead of:
LDA $80,X ; ZeroPage,X - the resulting object code is: B5 80
which accesses $007F when X=$FF, use:
LDA $0080,X ; Absolute,X - the resulting object code is: BD 80 00
which accesses $017F when X = $FF (a at cost of one additional byte and one additional cycle). All of the ZeroPage,X and ZeroPage,Y instructions except STX ZeroPage,Y and STY ZeroPage,X have a corresponding Absolute,X and Absolute,Y instruction. Unfortunately, a lot of 6502 assemblers don't have an easy way to force Absolute addressing, i.e. most will assemble a LDA $0080,X as B5 80. One way to overcome this is to insert the bytes using the .BYTE pseudo-op (on some 6502 assemblers this pseudo-op is called DB or DFB, consult the assembler documentation) as follows:
.BYTE $BD,$80,$00 ; LDA $0080,X (absolute,X addressing mode)
The comment is optional, but highly recommended for clarity.
In cases where you are writing code that will be relocated you must consider wrap-around when assigning dummy values for addresses that will be adjusted. Both zero and the semi-standard $FFFF should be avoided for dummy labels. The use of zero or zero page values will result in assembled code with zero page opcodes when you wanted absolute codes. With $FFFF, the problem is in addresses+1 as you wrap around to page 0.
See: Memory Instructions
Bitwise AND with Accumulator
|Zero Page||AND $44||$25||2||3|
|Zero Page,X||AND $44,X||$35||2||4|
Bitwise Exclusive-OR with Accumulator
|Zero Page||EOR $44||$45||2||3|
|Zero Page,X||EOR $44,X||$55||2||4|
Bitwise OR with Accumulator
|Zero Page||ORA $44||$05||2||3|
|Zero Page,X||ORA $44,X||$15||2||4|
Arithmetic Shift Left
ASL shifts all bits left one position.
0 is shifted into bit 0 and the original bit 7 is shifted into the Carry.
|Zero Page||ASL $44||$06||2||5|
|Zero Page,X||ASL $44,X||$16||2||6|
Logical Shift Right
LSR shifts all bits right one position.
0 is shifted into bit 7 and the original bit 0 is shifted into the Carry.
|Zero Page||LSR $44||$46||2||5|
|Zero Page,X||LSR $44,X||$56||2||6|
ROL shifts all bits left one position.
The Carry is shifted into bit 0 and the original bit 7 is shifted into the Carry.
|Zero Page||ROL $44||$26||2||5|
|Zero Page,X||ROL $44,X||$36||2||6|
ROR shifts all bits right one position.
The Carry is shifted into bit 7 and the original bit 0 is shifted into the Carry.
|Zero Page||ROR $44||$66||2||5|
|Zero Page,X||ROR $44,X||$76||2||6|
All branches are relative mode and have a length of two bytes. Syntax is "Bxx Displacement" or (better) "Bxx Label". Branches are dependant on the status of the flag bits when the opcode is encountered.
See the notes on the Program Counter for more on displacements.
|BPL||Branch on Plus||$10||2||2++|
|BMI||Branch on Minus||$30||2||2++|
|BVC||Branch on Overflow Clear||$50||2||2++|
|BVS||Branch on Overflow Set||$70||2||2++|
|BCC||Branch on Carry Clear||$90||2||2++|
|BCS||Branch on Carry Set||$B0||2||2++|
|BNE||Branch on Not Equal||$D0||2||2++|
|BEQ||Branch on Equal||$F0||2||2++|
A branch not taken requires 2 machine cycles.
A branch requires 3 cycles if taken, plus 1 cycle if the branch cross a page boundary.
There is no BRA (Branch Always) instruction but it can be easily emulated by branching on the basis of a known condition. One of the best flags to use for this purpose is the Overflow flag which is unchanged by all but the addition and subtraction operations.
A page boundary crossing occurs when the branch destination is on a different page than the instruction AFTER the branch instruction. For example:
SEC BCS LABEL NOP
A page boundary crossing occurs (i.e. the BCS takes 4 cycles) when (the address of) LABEL and the NOP are on different pages. This means that
CLV BVC LABEL LABEL NOP
the BVC instruction will take 3 cycles no matter what address it is located at.
Compare sets processor flags as if a subtraction had been carried out.
If the accumulator and the compared value are equal, the result of the subtraction is zero and the Zero (Z) flag is set. If the accumulator is equal or greater than the compared value, the Carry (C) flag is set.
|Zero Page||CMP $44||$C5||2||3|
|Zero Page,X||CMP $44,X||$D5||2||4|
Compare X Register
Operation and flag results are identical to equivalent mode accumulator CMP operations.
|Zero Page||CPX $44||$E4||2||3|
Compare Y Register
Operation and flag results are identical to equivalent mode accumulator CMP operations.
|Zero Page||CPY $44||$C4||2||3|
BIT sets the Z flag as though the value in the address tested were ANDed with the accumulator.
The N and V flags are set equal to bits 7 and 6 respectively of the value in the tested address.
|Zero Page||BIT $44||$24||2||3|
BIT is often used to skip one or two following bytes as in:
CLOSE1 LDX #$10 ; If entered here, we .BYTE $2C ; effectively perform CLOSE2 LDX #$20 ; a BIT test on $20A2, .BYTE $2C ; another one on $30A2, CLOSE3 LDX #$30 ; and end up with the X CLOSEX LDA #12 ; register still at $10 STA ICCOM,X ; upon arrival here.
Beware: a BIT instruction used in this way as a NOP does have effects: the flags may be modified, and the read of the absolute address, if it happens to access an I/O device, may cause an unwanted action.
Instructions that change Processor Status Flags
Clear Carry, Set Carry
Clear Interrupt, Set Interrupt
JMP loads the program counter with the absolute address, or the address stored at the memory location of the indirect address. Program execution proceeds from the new program counter value.
Note that there is no carry associated with the indirect jump so, an indirect JMP must never use a vector beginning on the last byte of a page.
For example, JMP ($30FF) will read the vector low byte from $30FF, but it will read the vector high byte from $3000, NOT from $4000 as you might expect. Unless the vector is intentionally laid out like this, it will likely lead to a crash.
Jump Saving Return
JSR pushes the address-1 of the next operation to the stack before transferring the value of the argument to the program counter. JSR behaves just like a JMP, but saves the return address to the stack first, thus creating a subroutine.
Subroutines are normally terminated by an RTS instruction.
Return to Saved
RTS pulls the top two bytes off the stack (low byte first) and transfers them to the program counter. The program counter is incremented by one and then execution proceeds from there.
RTS is typically used in combination with a JSR which saves the return address-1 to the stack.
RTS can also be used to implement a jump table where addresses-1 are pushed onto the stack and accessed via RTS.
In this example, the X register is used to select which routine will be executed.
LDX #1 JSR EXEC ... RTS EXEC LDA HIBYTE,X PHA LDA LOBYTE,X PHA RTS LOBYTE .BYTE <ROUTINE0-1,<ROUTINE1-1 .BYTE <ROUTINE2-1,<ROUTINE3-1 HIBYTE .BYTE >ROUTINE0-1,>ROUTINE1-1 .BYTE >ROUTINE2-1,>ROUTINE3-1
Return from Interrupt
RTI retrieves the Processor Status byte and Program Counter from the stack in that order. Interrupts push the program counter first and then the processor status.
Unlike RTS, the return address on the stack is the actual address rather than the address-1.
Add with Carry
ADC behavior depends on the state of the decimal flag. In decimal mode, the values upon which the addition is performed are interpreted as packed BCD (Binary Coded Decimal).
|Zero Page||ADC $44||$65||2||3|
|Zero Page,X||ADC $44,X||$75||2||4|
There is no way to add without the carry.
Subtract with Carry
SBC behavior depends on the state of the decimal flag. In decimal mode, the values upon which the subtraction is performed are interpreted as packed BCD (Binary Coded Decimal).
|Zero Page||SBC $44||$E5||2||3|
|Zero Page,X||SBC $44,X||$F5||2||4|
There is no way to subtract without the carry.
The carry works as an inverse borrow. i.e., to subtract, first set the carry. If the carry is cleared by the operation, a borrow has occurred.
|Zero Page||LDA $44||$A5||2||3|
|Zero Page,X||LDA $44,X||$B5||2||4|
Load X Register
|Zero Page||LDX $44||$A6||2||3|
|Zero Page,Y||LDX $44,Y||$B6||2||4|
Load Y Register
|Zero Page||LDY $44||$A4||2||3|
|Zero Page,X||LDY $44,X||$B4||2||4|
|Zero Page||STA $44||$85||2||3|
|Zero Page,X||STA $44,X||$95||2||4|
Store X Register
|Zero Page||STX $44||$86||2||3|
|Zero Page,Y||STX $44,Y||$96||2||4|
Store Y Register
|Zero Page||STY $44||$84||2||3|
|Zero Page,X||STY $44,X||$94||2||4|
|Zero Page||DEC $44||$C6||2||5|
|Zero Page,X||DEC $44,X||$D6||2||6|
|Zero Page||INC $44||$E6||2||5|
|Zero Page,X||INC $44,X||$F6||2||6|
These instructions are implied mode, have a length of one byte and require two machine cycles.
|TAX||Transfer A to X||$AA||1||2|
|TAY||Transfer A to Y||$A8||1||2|
|TXA||Tranfer X to A||$8A||1||2|
|TYA||Transfer Y to A||$98||1||2|
These instructions are implied mode, have a length of one byte and require machine cycles as indicated. The "PULL" operations are known as "POP" on most other microprocessors.
With the 6502, the stack is always on page $01 ($0100-$01FF) and works top down. i.e., when you push to the stack the stack pointer is decremented. When you pull from the stack the stack pointer is incremented.
|PHP||Push Processor Status||$08||1||3|
|TXS||Transfer X to Stack Pointer||$9A||1||2|
|TSX||Transfer Stack Pointer to X||$BA||1||2|
|PLP||Pull Processor Status||$28||1||4|
BRK sets the B flag, and then generates a forced interrupt. The Interrupt flag is ignored and the CPU goes through the normal interrupt process. In the interrupt service routine, the state of the B flag can be used to distinguish a BRK from a standard interrupt.
BRK causes a non-maskable interrupt and increments the program counter by one. Therefore an RTI will go to the address of the BRK +2 so that BRK may be used to replace a two-byte instruction for debugging and the subsequent RTI will be correct.
A NOP takes 2 machine cycles to execute, but it has no effect on any register, memory location, or processor flag. Thus, it takes up time and space but performs no operation.
NOP can be used to reserve space for future modifications or to remove existing code without changing the memory locations of code that follows it.
NOP can also be used in tightly timed code, to idly take up 2 cycles without having any other side effects.
The original source for the above content is: http://6502.org/tutorials/6502opcodes.html
In case there are any errors in my rendition, or in the 6502.org source, here is the relevant section from the official C64 Programmer's Reference Guide: Chapter 5: Basic to Machine Language (PDF)
Do you like what you see?
You've just read one of my high-quality, long-form, weblog posts, for free! First, thank you for your interest, it makes producing this content feel worthwhile. I love to hear your input and feedback in the forums below. And I do my best to answer every question.
I'm creating C64 OS and documenting my progress along the way, to give something to you and contribute to the Commodore community. Please consider purchasing one of the items I am currently offering or making a small donation, to help me continue to bring you updates, in-depth technical discussions and programming reference. Your generous support is greatly appreciated.
Greg Naçu — C64OS.com