Skip to content

Programming by Design

If you're not prepared to be wrong, you'll never come up with anything original. – Sir Ken Robinson

  • About
  • Java-PbD
  • C-PbD
  • ASM-PbD
  • Algorithms
  • Other

Chapter 6502-2 – Basic CPU Architecture

Posted on June 7, 2024January 19, 2025 By William Jojo
AsmBook

(Updated July 16, 2024)

Table of Contents

    Physical Details
    Registers
    Instructions
    Addressing Modes

Physical Details

The MOS 6502 was originally released as a 40-pin dual-inline package (DIP-40) in both plastic and ceramic.

MOS6502 CPU Image pinout

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.

Historical Note!
In 1999, the IEC (International Electrotechnical Commission) released a new standard for quantities and units, specifically, IEC 80000-13:2008. The original JEDEC standard states that 1 kilobyte (KB) is 1024 bytes. The IEC Standard says 1 kilobyte (KB) is 1000 bytes and 1 kibibyte (KiB) is 1024 bytes.

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.

Important Note!
Each instruction is a link to some exceptionally well written documentation by Norbert Landsteiner at mass:werk.

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.

Post navigation

❮ Previous Post: Chapter 6502-1 – The IDE
Next Post: Chapter 6502-3 – The Assembler ❯

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Copyright © 2018 – 2025 Programming by Design.