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
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
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.
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.
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.
reg6502 memory::read(int address) { return mem[address]; }
returns the value stored at address n.
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.
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.
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 |
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.
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.
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.
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.
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 |
#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.
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.
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; }
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.