Table of Contents

MOS 6502

Address Modes:


A …. Accumulator OPC A operand is AC
abs …. absolute OPC $HHLL operand is address $HHLL
abs,X …. absolute, X-indexed OPC $HHLL,X operand is address incremented by X with carry
abs,Y …. absolute, Y-indexed OPC $HHLL,Y operand is address incremented by Y with carry
# …. immediate OPC #$BB operand is byte (BB)
impl …. implied OPC operand implied
ind …. indirect OPC ($HHLL) operand is effective address; effective address is value of address
X,ind …. X-indexed, indirect OPC ($BB,X) operand is effective zeropage address; effective address is byte (BB) incremented by X without carry
ind,Y …. indirect, Y-indexed OPC ($LL),Y operand is effective address incremented by Y with carry; effective address is word at zeropage address
rel …. relative OPC $BB branch target is PC + offset (BB), bit 7 signifies negative offset
zpg …. zeropage OPC $LL operand is of address; address hibyte = zero ($00xx)
zpg,X …. zeropage, X-indexed OPC $LL,X operand is address incremented by X; address hibyte = zero ($00xx); no page transition
zpg,Y …. zeropage, Y-indexed OPC $LL,Y operand is address incremented by Y; address hibyte = zero ($00xx); no page transition


Instructions by Name:


ADC …. add with carry
AND …. and (with accumulator)
ASL …. arithmetic shift left
BCC …. branch on carry clear
BCS …. branch on carry set
BEQ …. branch on equal (zero set)
BIT …. bit test
BMI …. branch on minus (negative set)
BNE …. branch on not equal (zero clear)
BPL …. branch on plus (negative clear)
BRK …. interrupt
BVC …. branch on overflow clear
BVS …. branch on overflow set
CLC …. clear carry
CLD …. clear decimal
CLI …. clear interrupt disable
CLV …. clear overflow
CMP …. compare (with accumulator)
CPX …. compare with X
CPY …. compare with Y
DEC …. decrement
DEX …. decrement X
DEY …. decrement Y
EOR …. exclusive or (with accumulator)
INC …. increment
INX …. increment X
INY …. increment Y
JMP …. jump
JSR …. jump subroutine
LDA …. load accumulator
LDY …. load X
LDY …. load Y
LSR …. logical shift right
NOP …. no operation
ORA …. or with accumulator
PHA …. push accumulator
PHP …. push processor status (SR)
PLA …. pull accumulator
PLP …. pull processor status (SR)
ROL …. rotate left
ROR …. rotate right
RTI …. return from interrupt
RTS …. return from subroutine
SBC …. subtract with carry
SEC …. set carry
SED …. set decimal
SEI …. set interrupt disable
STA …. store accumulator
STX …. store X
STY …. store Y
TAX …. transfer accumulator to X
TAY …. transfer accumulator to Y
TSX …. transfer stack pointer to X
TXA …. transfer X to accumulator
TXS …. transfer X to stack pointer
TYA …. transfer Y to accumulator



Registers:


PC …. program counter (16 bit)
AC …. accumulator (8 bit)
X …. X register (8 bit)
Y …. Y register (8 bit)
SR …. status register [NV-BDIZC] (8 bit)
SP …. stack pointer (8 bit)



SR Flags (bit 7 to bit 0):


N …. Negative
V …. Overflow
- …. ignored
B …. Break
D …. Decimal (use BCD for arithmetics)
I …. Interrupt (IRQ disable)
Z …. Zero
C …. Carry

Branches

Branch instructions are in cycles, it depends on the bits of flag when the op-code is runed. Below is the branch instruction sets and hex code


MNEMONIC ——————– HEX
BPL (Branch on PLus) — $10
BMI (Branch on MInus) – $30
BVC (Branch on oVerflow Clear)- $50
BVS (Branch on oVerflow Set) - $70
BCC (Branch on Carry Clear) - $90
BCS (Branch on Carry Set) - $B0
BNE (Branch on Not Equal) - $D0
BEQ (Branch on EQual) - $F0

Shift

Mutiplex

XST supports different description styles for multiplexers, such as If-Then-Else or Case. When writing MUXs, using a Case statement, and you do not specify all values of the selector, you may get latches instead of a multiplexer.

Notes

Memory

memory() - Constructor

memory::memory()
{
	for (int address = 0; address < 65536; address++)
	{
		for (int bit = 1; bit < 9; bit++)
			mem[address].set(0, bit);
	}
}

A nested for loop cycles through each memory address and as it cycles through each one, sets each bit in the address to 0.

write(int, reg6502)

void memory::write(int address, reg6502 value)
{
	for (bit = 1; bit < 9; bit++)
		mem[address].set(value.getQ(bit), bit);
}

A single for loop gets the value of bit n from one reg6502 object and sets the same bit in a memory address to that value.

read(int)

reg6502 memory::read(int address)
{
	return mem[address];
}

returns the value stored at address n.

dump()

void memory::dump()
{
	for (int address = 0; address < 65536; address++)
	{
		for (int bit = 1; bit < 9; bit++)
			printf("%d", mem[address].getQ(bit));
 
		printf("\n");
	}
}

A nested for loop, first, cycles through each address and then prints out all 8 bits of that address. Thus printing out every value stored in memory.

Registers

Accumulator (A Register)

8 bit primary register. Just about any arithmetic operations will be done in here, along with local operations. When two numbers are added together, the first number is pulled from memory into the accumulator. The second number gets added to this and that result is then stored in accumulator.The accumulator has more addressing modes than any of the other 6502 registers. This is because the accumulator is the 6502's primary register.

Processor Status (P Register)

An 8 bit register which represent flags which describe the status of the processor. This contains the following flags:

Flag When Set Bit
n (negative) 1 = Negative 7
v (overflow) 1 = Overflow 6
b (break instruction) 1 = Break caused an interrupt 4
d (decimal mode) 1 = Decimal Mode on 3
i (IRQ Disable) 1 = IRQ Disabled 2
z (zero) 1 = Result of Zero 1
c (carry) 1 = Carry 0

X and Y Index Registers

These are the general purpose registers. With a single instruction the x and y registers can be incremented or decremented. This makes them ideal for being used as loop counters, moving memory, or accessing successive table locations.

Stack Pointer (S Register)

Data structure implemented in hardware known as a stack. The stack in the 6502 is limited to 256 bytes. This is located in memory between $100 and $1FF. These memory locations are accessed via push and pull instructions. A push instructions takes data from a specified register and adds it to a location on the stack pointed to by the stack pointer.

Program Counter (PC Register)

The Program Counter contains the address of the next byte in the instruction sequence. When a program begins its execution, the program counter is set to the program's entry point, the address where the program was loaded.

Addressing Modes

The 6502 has 14 different addressing modes:

Addressing Mode Opcode Operand
Implied Dex
Accumulator ASL A
Immediate LDA #55
Absolute LDA $2000
Program Counter Relative BEQ LABEL12
Stack PHA
Zero Stack LDA $81
Absolute Indexed With X LDA $2000,X
Absolute Indexed With Y LDA $2000,Y
Zero Page Indexed With X LDA $55,X
Zero Page Indexed With Y LDA $55,Y
Absolute Indirect JMP ($1020)
Zero Page Indirect Indexed With Y (Postindexed) LDA ($55),Y
Zero Page Indexed Indirect With X (Preindexed) LDA ($55,X)

Combining these addressing modes with the 6502's 56 operation mnemonics, gives a total of 151 possible programming instructions.

Instructions

Mnemonic Description
LDA load the accumulator
LDX load X register
LDY load Y register
STA store the accumulator
STX store X register
STY store Y register
—————-
PHA push the accumulator
PHP push the status registers
PHX push the X index register
PHY push the Y index register
PHB push data bank register
PHK push program bank register
PHD push direct page register

ToDo

Who is doing what

Documentation

0x0A Arithmetic Shift Left

#include"r6502.h"
 
void ins0x0A(reg6502& A, reg6502& status)
{
	bool isNonZero = false;
 
	status.set(A.getQ(7), CARRY);

First we create a boolean variable to check the result if the result of the shift is 0. Then we shift the first bit in the accumulator to the carry in the status register.

	for (int x = 6; x >= 0; x--)
	{
		A.set(A.getQ(x), x + 1);
		isNonZero = isNonZero | A.getQ(x);
	}

Next we use a for loop to shift each bit to it's immediate left and then check the bit for a zero.

	if (isNonZero == true)
		status.set(false, ZERO);
	else
		status.set(true, ZERO);

We do our check for zero in the accumulator and if we find zero turn on the ZERO flag.

	if (A.getQ(NEGATIVE) == true)
		status.set(true, NEGATIVE);
	else
		status.set(false, NEGATIVE);
 
}	

Now we check the sign bit and set the NEGATIVE flag accordingly.

0x69 Instruction Add with Carry

 bool isNonZero = false;
 bool Asign = A.getQ(NEGATIVE);
 reg6502 value;
 int carry;

Code declarations. tmp is used to check for 0s in all 8 bits. tmp is ORed with every bit in the accumulator. If there is a one somewhere in the accumulator, it will be the result of the OR operation, and then never change. So if tmp is set to true, the number is non-zero. Asign is the value of the high bit in the accumulator. value is a generic register. carry is used as the carry out of the full adder.

for (int x = 0; x < 8; x++)
{
  adder.set(A.getQ(x), value.getQ(x), carry);
  A.set(adder.getSum());
  carry = adder.getCarryOut();
  isNonZero = isNonZero | A.getQ(x);
}

The for loop goes through the accumulator and value, sending each bit from both as inputs to the full adder along with a carry out. The addition is carried out and the last line in the for loop performs the OR operation to check for a zero value in the accumulator.

if (isNonZero == true)
  status.set(false, ZERO)
else
  status.set(true, ZERO);

After the OR operation, if isNonZero == true the zero flag in the status register is set.

if (A.getQ(NEGATIVE) == true)
  status.set(true, NEGATIVE);
else
  status.set(false, NEGATIVE);

It checks the sign bit of the accumulator for 1. If it finds one, the negative flag in the status register is set.

if (carry == true)
  status.set(true, CARRY);
else
  status.set(false, CARRY);

Checks the result of the final carry for a 1. If it finds ones, the carry bit in the status register is set.

if (Asign == true && value.getQ(NEGATIVE) == true)
{
  if (A.getQ(NEGATIVE) == true)
    status.set(false, OVERFLOW);
}
else if (Asign == false && value.getQ(NEGATIVE) == false)
{
  if (A.getQ(NEGATIVE) == false)
    status.set(false, OVERFLOW);
}
else
  status.set(true, OVERFLOW);

It, first, checks the sign of the two input values. If they are the same sign, and the result contains the opposite sign, an overflow has occurred. Then the overflow bit of the status register is set.

0x29 Instruction AND with accumulator immediate

Parameters: reg6502 A(accumulator), reg6502 status(status register), reg6502 immd (immediate value)

Function heading/declarations

void ins0x29()
{
	bool isNonZero = false; //Checks for a value of zero
	bool immediate[8];      //immediate value

A for loop is used to first AND each bit of the accumulator with each bit of the immediate value. The next line performs an OR between the tmp variable and the accumulator to check weather the accumulator contains a value of zero.

for (int x = 0; x < 8; x++)
{
  accum.set(accum.getQ(x) & immediate[x], x);
  isNonZero = isNonZero | accum.getQ(x);
}

Next we set the appropriate flags (N, Z). If isNonZero turns out to be true, because any non zero value in the accumulator will set isNonZero to true and keep it there, we set the zero flag in the status register to false. Otherwise we sat the zero flag to true. The negative flag is checked by looking at the sign bit of the accumulator and if the sign bit is true, set the negative flag to true, otherwise set it to false.

    if (isNonZero == false)
        status.set(true, ZERO);
    else
        status.set(false, ZERO);
 
    if (A.getQ(NEGATIVE) == true)
        status.set(true, NEGATIVE);
    else
        status.set(false, NEGATIVE);
 
    return;
}

Code Examples

Here's a fairly primitive loop. There are much better ways but this is what I've discovered so far.

    .ORG $8000 ;Directive so the assembler knows where to start your program
    LDA #$00   ;Load 0x00 into the accumulator
    STA $50    ;Store the accumulator contents into memory location 0x50
    LDA #$0A   ;Load 0x0A into the accumulator
    STA $51    ;Store the accumulator contents into memory location 0x51
LOOP INC $50   ;Create a lable "LOOP" & Increment memory location 0x50 by one
     DEC $51   ;Decrement memory location 0x51 by one
     BNE LOOP  ;Check zero flag and if not zero branch to LOOP
     BRK       ;Stop program execution

The problem with this is that the execution of the loop depends on whether or not the zero flag is tripped and not whether you've actually counted up to $0A. I'll see what other things I can figure out/dig up.

References