package core.cpu;
import core.abstracts.InterruptHandler;
import core.abstracts.ProcessingUnit;
import static core.cpu.CPU.Dummy.*;
import core.cpu.memory.CPUMemory;
import core.exceptions.InvalidOperationException;
import core.ppu.PPU;
import core.system.NES;
/**
* The MOS 6502 processor which functions as the core processor of the Nintendo
* Entertainment System.
*
* @author Arson
*/
public final class CPU extends ProcessingUnit {
//Debug enabled
public static final boolean DEBUG = false;
//Processor flags locations
private static final int FLAG_C = 0;
private static final int FLAG_Z = 1;
private static final int FLAG_I = 2;
private static final int FLAG_D = 3;
private static final int FLAG_B = 4;
private static final int FLAG_U = 5;
private static final int FLAG_V = 6;
private static final int FLAG_S = 7;
//CPU decimal mode support, does nothing at the moment
public final boolean decimalModeEnabled = false;
//General purpose registers
private int A;
private int X;
private int Y;
//Processor flags
private int c;
private int z;
private int i;
private int d;
private int b;
private int u;
private int v;
private int s;
//Stack pointer
private int SP;
//Program counter
private int PC;
//Operation
private int OP;
//Total amount of cycles passed since start of the CPU (unused atm)
private long cycles;
//The memory of the CPU
private CPUMemory memory;
//The NMI handler for this CPU
private InterruptHandler nmiHandler;
public CPU(NES nes) {
super(nes);
memory = new CPUMemory(nes);
nmiHandler = new NMIHandler(nes);
}
@Override
public void initialize() {
A = 0;
X = 0;
Y = 0;
c = z = d = b = v = s = 0;
i = u = 1;
SP = 0xFD;
// PC = 0xC000; //For nestest in case of no PPU
PC = (read(0xFFFD) << 8) | (read(0xFFFC));
OP = read(PC); //Get address at PC without triggering PPU
cycles = 0;
}
@Override
public void reset() {
throw new UnsupportedOperationException("Not supported yet.");
}
public void executeInstruction() {
if (DEBUG) {
System.out.printf(
"PC: $%X OP: $%02X A: $%02X X: $%02X "
+ "Y: $%02X P: $%02X SP: $%02X CYC: %d "
+ "PPUCYC: %d Scanline: %d\n",
PC, OP, A, X, Y, getCPUStatus(), SP,
cycles, getNES().getPPU().getCycles(),
getNES().getPPU().getScanline());
}
//Have to increment PC after debug print, otherwise get wrong PC
PC++;
switch (OP) {
/*
* ADC (ADd with Carry).
*/
case 0x69:
adc(getImmediateAddress());
break;
case 0x65:
adc(getZeroAddress());
break;
case 0x75:
adc(getZeroAddress(X));
break;
case 0x6D:
adc(getAbsoluteAddress());
break;
case 0x7D:
adc(getAbsoluteAddress(X, ON_CROSSING));
break;
case 0x79:
adc(getAbsoluteAddress(Y, ON_CROSSING));
break;
case 0x61:
adc(getIndirectXAddress());
break;
case 0x71:
adc(getIndirectYAddress(ON_CROSSING));
break;
/*
* AND (bitwise AND with accumulator).
*/
case 0x29:
and(getImmediateAddress());
break;
case 0x25:
and(getZeroAddress());
break;
case 0x35:
and(getZeroAddress(X));
break;
case 0x2D:
and(getAbsoluteAddress());
break;
case 0x3D:
and(getAbsoluteAddress(X, ON_CROSSING));
break;
case 0x39:
and(getAbsoluteAddress(Y, ON_CROSSING));
break;
case 0x21:
and(getIndirectXAddress());
break;
case 0x31:
and(getIndirectYAddress(ON_CROSSING));
break;
/*
* ASL (Arithmetic Shift Left).
*/
case 0x0A:
aslA();
break;
case 0x06:
asl(getZeroAddress());
break;
case 0x16:
asl(getZeroAddress(X));
break;
case 0x0E:
asl(getAbsoluteAddress());
break;
case 0x1E:
asl(getAbsoluteAddress(X, ALWAYS));
break;
/*
* BIT (test BITs).
*/
case 0x24:
bit(getZeroAddress());
break;
case 0x2C:
bit(getAbsoluteAddress());
break;
/*
* BPL (Branch on PLus).
*/
case 0x10:
branch(s ^ 1, getRelativeAddress());
break;
/*
* BMI (Branch on MInus).
*/
case 0x30:
branch(s, getRelativeAddress());
break;
/*
* BVC (Branch on oVerflow Clear).
*/
case 0x50:
branch(v ^ 1, getRelativeAddress());
break;
/*
* BVS (Branch on oVerflow Set).
*/
case 0x70:
branch(v, getRelativeAddress());
break;
/*
* BCC (Branch on Carry Clear).
*/
case 0x90:
branch(c ^ 1, getRelativeAddress());
break;
/*
* BCS (Branch on Carry Set).
*/
case 0xB0:
branch(c, getRelativeAddress());
break;
/*
* BNE (Branch on Not Equal).
*/
case 0xD0:
branch(z ^ 1, getRelativeAddress());
break;
/*
* BEQ (Branch on EQual).
*/
case 0xF0:
branch(z, getRelativeAddress());
break;
/*
* BRK (BReaK).
*/
case 0x00:
brk();
break;
/*
* CMP (CoMPare).
*/
case 0xC9:
cmp(getImmediateAddress(), A);
break;
case 0xC5:
cmp(getZeroAddress(), A);
break;
case 0xD5:
cmp(getZeroAddress(X), A);
break;
case 0xCD:
cmp(getAbsoluteAddress(), A);
break;
case 0xDD:
cmp(getAbsoluteAddress(X, ON_CROSSING), A);
break;
case 0xD9:
cmp(getAbsoluteAddress(Y, ON_CROSSING), A);
break;
case 0xC1:
cmp(getIndirectXAddress(), A);
break;
case 0xD1:
cmp(getIndirectYAddress(ON_CROSSING), A);
break;
/*
* CPX (ComPare X).
*/
case 0xE0:
cmp(getImmediateAddress(), X);
break;
case 0xE4:
cmp(getZeroAddress(), X);
break;
case 0xEC:
cmp(getAbsoluteAddress(), X);
break;
/*
* CPY (ComPare Y).
*/
case 0xC0:
cmp(getImmediateAddress(), Y);
break;
case 0xC4:
cmp(getZeroAddress(), Y);
break;
case 0xCC:
cmp(getAbsoluteAddress(), Y);
break;
/*
* DEC (DECrement memory).
*/
case 0xC6:
dec(getZeroAddress());
break;
case 0xD6:
dec(getZeroAddress(X));
break;
case 0xCE:
dec(getAbsoluteAddress());
break;
case 0xDE:
dec(getAbsoluteAddress(X, ALWAYS));
break;
/*
* EOR (bitwise Exclusive OR).
*/
case 0x49:
eor(getImmediateAddress());
break;
case 0x45:
eor(getZeroAddress());
break;
case 0x55:
eor(getZeroAddress(X));
break;
case 0x4D:
eor(getAbsoluteAddress());
break;
case 0x5D:
eor(getAbsoluteAddress(X, ON_CROSSING));
break;
case 0x59:
eor(getAbsoluteAddress(Y, ON_CROSSING));
break;
case 0x41:
eor(getIndirectXAddress());
break;
case 0x51:
eor(getIndirectYAddress(ON_CROSSING));
break;
/*
* CLC (CLear Carry).
*/
case 0x18:
clc();
break;
/*
* SEC (SEt Carry).
*/
case 0x38:
sec();
break;
/*
* CLI (CLear Interrupt).
*/
case 0x58:
cli();
break;
/*
* SEI (SEt Interrupt).
*/
case 0x78:
sei();
break;
/*
* CLV (CLear oVerflow).
*/
case 0xB8:
clv();
break;
/*
* CLD (CLear Decimal).
*/
case 0xD8:
cld();
break;
/*
* SED (SEt Decimal).
*/
case 0xF8:
sed();
break;
/*
* INC (INCrement memory).
*/
case 0xE6:
inc(getZeroAddress());
break;
case 0xF6:
inc(getZeroAddress(X));
break;
case 0xEE:
inc(getAbsoluteAddress());
break;
case 0xFE:
inc(getAbsoluteAddress(X, ALWAYS));
break;
/*
* JMP (JuMP).
*/
case 0x4C:
jmp(getAbsoluteAddress());
break;
case 0x6C:
jmp(getIndirectAddress());
break;
/*
* JSR (Jump to SubRoutine).
*/
case 0x20:
jsr(getAbsoluteAddress());
break;
/*
* LDA (LoaD Accumulator).
*/
case 0xA9:
lda(getImmediateAddress());
break;
case 0xA5:
lda(getZeroAddress());
break;
case 0xB5:
lda(getZeroAddress(X));
break;
case 0xAD:
lda(getAbsoluteAddress());
break;
case 0xBD:
lda(getAbsoluteAddress(X, ON_CROSSING));
break;
case 0xB9:
lda(getAbsoluteAddress(Y, ON_CROSSING));
break;
case 0xA1:
lda(getIndirectXAddress());
break;
case 0xB1:
lda(getIndirectYAddress(ON_CROSSING));
break;
/*
* LDX (LoaD X register).
*/
case 0xA2:
ldx(getImmediateAddress());
break;
case 0xA6:
ldx(getZeroAddress());
break;
case 0xB6:
ldx(getZeroAddress(Y));
break;
case 0xAE:
ldx(getAbsoluteAddress());
break;
case 0xBE:
ldx(getAbsoluteAddress(Y, ON_CROSSING));
break;
/*
* LDY (LoaD Y register).
*/
case 0xA0:
ldy(getImmediateAddress());
break;
case 0xA4:
ldy(getZeroAddress());
break;
case 0xB4:
ldy(getZeroAddress(X));
break;
case 0xAC:
ldy(getAbsoluteAddress());
break;
case 0xBC:
ldy(getAbsoluteAddress(X, ON_CROSSING));
break;
/*
* LSR (Logical Shift Right).
*/
case 0x4A:
lsrA();
break;
case 0x46:
lsr(getZeroAddress());
break;
case 0x56:
lsr(getZeroAddress(X));
break;
case 0x4E:
lsr(getAbsoluteAddress());
break;
case 0x5E:
lsr(getAbsoluteAddress(X, ALWAYS));
break;
/*
* NOP (No OPeration).
*/
case 0xEA:
nop();
break;
/*
* ORA (bitwise OR with Accumulator).
*/
case 0x09:
ora(getImmediateAddress());
break;
case 0x05:
ora(getZeroAddress());
break;
case 0x15:
ora(getZeroAddress(X));
break;
case 0x0D:
ora(getAbsoluteAddress());
break;
case 0x1D:
ora(getAbsoluteAddress(X, ON_CROSSING));
break;
case 0x19:
ora(getAbsoluteAddress(Y, ON_CROSSING));
break;
case 0x01:
ora(getIndirectXAddress());
break;
case 0x11:
ora(getIndirectYAddress(ON_CROSSING));
break;
/*
* TAX (Transfer A to X).
*/
case 0xAA:
tax();
break;
/*
* TXA (Transfer X to A).
*/
case 0x8A:
txa();
break;
/*
* DEX (DECrease X).
*/
case 0xCA:
dex();
break;
/*
* INX (INcrease X).
*/
case 0xE8:
inx();
break;
/*
* TAY (Transfer A to Y).
*/
case 0xA8:
tay();
break;
/*
* TYA (Transfer Y to A).
*/
case 0x98:
tya();
break;
/*
* DEY (DEcrement Y).
*/
case 0x88:
dey();
break;
/*
* INY (INcrement Y).
*/
case 0xC8:
iny();
break;
/*
* ROL (ROtate Left).
*/
case 0x2A:
rolA();
break;
case 0x26:
rol(getZeroAddress());
break;
case 0x36:
rol(getZeroAddress(X));
break;
case 0x2E:
rol(getAbsoluteAddress());
break;
case 0x3E:
rol(getAbsoluteAddress(X, ALWAYS));
break;
/*
* ROR (ROtate Right).
*/
case 0x6A:
rorA();
break;
case 0x66:
ror(getZeroAddress());
break;
case 0x76:
ror(getZeroAddress(X));
break;
case 0x6E:
ror(getAbsoluteAddress());
break;
case 0x7E:
ror(getAbsoluteAddress(X, ALWAYS));
break;
/*
* RTI (ReTurn from Interrupt).
*/
case 0x40:
rti();
break;
/*
* RTS (ReTurn from Subroutine).
*/
case 0x60:
rts();
break;
/*
* SBC (SuBtract with Carry).
*/
case 0xE9:
sbc(getImmediateAddress());
break;
case 0xE5:
sbc(getZeroAddress());
break;
case 0xF5:
sbc(getZeroAddress(X));
break;
case 0xED:
sbc(getAbsoluteAddress());
break;
case 0xFD:
sbc(getAbsoluteAddress(X, ON_CROSSING));
break;
case 0xF9:
sbc(getAbsoluteAddress(Y, ON_CROSSING));
break;
case 0xE1:
sbc(getIndirectXAddress());
break;
case 0xF1:
sbc(getIndirectYAddress(ON_CROSSING));
break;
/*
* STA (STore Accumulator).
*/
case 0x85:
sto(getZeroAddress(), A);
break;
case 0x95:
sto(getZeroAddress(X), A);
break;
case 0x8D:
sto(getAbsoluteAddress(), A);
break;
case 0x9D:
sto(getAbsoluteAddress(X, ALWAYS), A);
break;
case 0x99:
sto(getAbsoluteAddress(Y, ALWAYS), A);
break;
case 0x81:
sto(getIndirectXAddress(), A);
break;
case 0x91:
sto(getIndirectYAddress(ALWAYS), A);
break;
/*
* TXS (Transfer X to Stack pointer).
*/
case 0x9A:
txs();
break;
/*
* TSX (Transfer Stack pointer to X).
*/
case 0xBA:
tsx();
break;
/*
* PHA (PusH Accumulator).
*/
case 0x48:
pha();
break;
/*
* PLA (PuLl Accumulator).
*/
case 0x68:
pla();
break;
/*
* PHP (PusH Processor status).
*/
case 0x08:
php();
break;
/*
* PLP (PuLl Processor status).
*/
case 0x28:
plp();
break;
/*
* STX (STore X register).
*/
case 0x86:
sto(getZeroAddress(), X);
break;
case 0x96:
sto(getZeroAddress(Y), X);
break;
case 0x8E:
sto(getAbsoluteAddress(), X);
break;
/*
* STY (STore Y regiter).
*/
case 0x84:
sto(getZeroAddress(), Y);
break;
case 0x94:
sto(getZeroAddress(X), Y);
break;
case 0x8C:
sto(getAbsoluteAddress(), Y);
break;
/*
* Unsupported operation by default.
*/
default:
throw new InvalidOperationException("Opcode $"
+ String.format("%X", OP) + " is invalid.");
}
if (!nmiHandler.isInterruptHandled()) {
nmiHandler.interrupt();
}
PC &= 0xFFFF;
//Fetch OP for next instruction
OP = _read(PC);
}
/**
* ADC (ADd with Carry).
*
* @param address the address from which the to-be-added data is fetched.
*/
public void adc(int address) {
int data = _read(address);
int result = A + data + c;
checkCarry(result);
checkZero(result);
checkADCOverflow(A, data, result);
checkSign(result);
A = result & 0xFF;
}
/**
* AND (AND with accumulator).
*
* @param address the address from which the to-be-anded data is fetched.
*/
public void and(int address) {
int data = _read(address);
int result = A & data;
checkZero(result);
checkSign(result);
A = result & 0xFF;
}
/**
* ASL (Arithmetic Shift Left with accumulator).
*
* This method is used for shifting the accumulator left.
*/
public void aslA() {
_read(PC + 1); //dummy read
A <<= 1;
checkCarry(A);
checkZero(A);
checkSign(A);
A &= 0xFF;
}
/**
* ASL (Arithmetic Shift Left).
*
* This method is used for shifting the fetched data left.
*
* @param address the address from which the to-be-shifted data is fetched.
*/
public void asl(int address) {
int data = _read(address);
_write(address, data);
data <<= 1;
checkCarry(data);
checkZero(data);
checkSign(data);
data &= 0xFF; //Memory write does this too, but just in case
_write(address, data);
}
/**
* BIT (test BITs).
*
* @param address the address from which the to-be-tested data is fetched.
*/
public void bit(int address) {
int data = _read(address);
checkZero(A & data);
checkSign(data);
v = (data & 0x40) >> FLAG_V;
}
/**
* Branch.
*
* Belongs to:
* - BPL (Branch on PLus)
* - BMI (Branch on MInus)
* - BVC (Branch on oVerflow Clear)
* - BVS (Branch on oVerflow Set)
* - BCC (Branch on Carry Clear)
* - BCS (Branch on Carry Set)
* - BNE (Branch on Not Equal)
* - BEQ (Branch on EQual)
*
* @param flag the flag which is supposed to be tested.
*/
public void branch(int flagStatus, int address) {
int offset = _read(address);
if ((flagStatus & 1) == 1) {
//Add cycle if branch is taken, just cycle or dummy read?
_read(PC);
int oldPC = PC;
PC = PC + (byte) offset;
if ((oldPC & 0xFF00) != (PC & 0xFF00)) {
//Add one more cycle if page crossed, just cycle or dummy read?
_read(PC);
// tickPPU();
}
}
}
/**
* BRK (BReaK).
*/
public void brk() {
PC++;
_read(PC); //dummy read
//Push PC high byte first
_push(PC >> 8);
_push(PC & 0xFF);
//Push processor flag register with U and B set
_push(getCPUStatus() | (1 << FLAG_U) | (1 << FLAG_B));
//Set PC to BRK vector
PC = (_read(0xFFFF) << 8) | _read(0xFFFE);
}
/**
* CMP (CoMPare).
*
* Belongs to:
* - CMP
* - CPX
* - CPY
*
* @param address the address from which the to-be-compared data is read.
* @param reg the register on which the comparison will be done.
*/
public void cmp(int address, int reg) {
int data = _read(address);
int result = reg - data;
c = reg >= data ? 1 : 0;
checkZero(result);
checkSign(result);
}
/**
* DEC (DECrement memory).
* checkZero(data);
* checkSign(data);
* data &= 0xFF;
* write(address, data);
*
* @param address the address from which the to-be-decremented data is read.
*/
public void dec(int address) {
int data = _read(address);
_write(address, data);
data--;
data &= 0xFF;
checkZero(data);
checkSign(data);
_write(address, data);
}
/**
* EOR (bitwise Exclusive OR).
*
* @param address the address from which the to-be-eor'd data is read.
*/
public void eor(int address) {
int data = _read(address);
int result = A ^ data;
checkZero(result);
checkSign(result);
A = result & 0xFF;
}
/**
* CLC (CLear Carry).
*/
public void clc() {
_read(PC + 1); //dummy read
c = 0;
}
/**
* SEC (SEt Carry).
*/
public void sec() {
_read(PC + 1); //dummy read
c = 1;
}
/**
* CLI (CLear Interrupt).
*/
public void cli() {
_read(PC + 1); //dummy read
i = 0;
}
/**
* SEI (SEt Interrupt).
*/
public void sei() {
_read(PC + 1); //dummy read
i = 1;
}
/**
* CLV (CLear oVerflow).
*/
public void clv() {
_read(PC + 1); //dummy read
v = 0;
}
/**
* CLD (CLear Decimal).
*/
public void cld() {
_read(PC + 1); //dummy read
d = 0;
}
/**
* SED (SEt Decimal).
*/
public void sed() {
_read(PC + 1); //dummy read
d = 1;
}
/**
* INC (INCrement memory).
*
* @param address the address from which the to-be-incremented data is read.
*/
public void inc(int address) {
int data = _read(address);
_write(address, data);
data++;
data &= 0xFF;
checkZero(data);
checkSign(data);
_write(address, data);
}
/**
* JMP (JuMP).
*
* @param address the address to be jumped to.
*/
public void jmp(int address) {
PC = address;
}
/**
* JSR (Jump to SubRoutine).
*
* @param address the address to be jumped to.
*/
public void jsr(int address) {
_read(SP); //dummy read (of SP?)
--PC;
_push(PC >> 8);
_push(PC & 0xFF);
PC = address;
}
/**
* LDA (LoaD Accumulator).
*
* @param address the address from which A is loaded.
*/
public void lda(int address) {
int data = _read(address);
checkZero(data);
checkSign(data);
A = data & 0xFF;
}
/**
* LDX (LoaD X register).
*
* @param address the address from which X is loaded.
*/
public void ldx(int address) {
int data = _read(address);
checkZero(data);
checkSign(data);
X = data;
}
/**
* LDY (LoaD Y register).
*
* @param address the address from which Y is loaded.
*/
public void ldy(int address) {
int data = _read(address);
checkZero(data);
checkSign(data);
Y = data;
}
/**
* LSRA (Logical Shift Right with Accumulator).
*
* This method is used for logically shifting the accumulator to the right.
*/
public void lsrA() {
_read(PC + 1); //dummy read
c = A & 1;
A >>>= 1;
checkZero(A);
checkSign(A);
}
/**
* LSR (Logical Shift Right).
*
* @param address the address which contains the data that needs to be
* logically shifted to the right.
*/
public void lsr(int address) {
int data = _read(address);
_write(address, data);
c = A & 1;
data >>>= 1;
checkZero(data);
checkSign(data);
_write(address, data);
}
/**
* NOP (No OPeration).
*/
public void nop() {
_read(PC + 1); //dummy read
}
/**
* ORA (bitwise OR with Accumulator).
*
* @param address the address from which the to-be-or'd data is fetched.
*/
public void ora(int address) {
int data = _read(address);
int result = A | data;
checkZero(result);
checkSign(result);
A = result & 0xFF;
}
/**
* TAX (Transfer A to X).
*/
public void tax() {
_read(PC + 1); //dummy read
X = A;
checkZero(A);
checkSign(A);
}
/**
* TXA (Transfer X to A).
*/
public void txa() {
_read(PC + 1); //dummy read
A = X;
checkZero(A);
checkSign(A);
}
/**
* DEX (DEcrement X).
*/
public void dex() {
_read(PC + 1); //dummy read
--X;
X &= 0xFF;
checkZero(X);
checkSign(X);
}
/**
* INX (INcrement X).
*/
public void inx() {
_read(PC + 1); //dummy read
++X;
X &= 0xFF;
checkZero(X);
checkSign(X);
}
/**
* TAY (Transfer A to Y).
*/
public void tay() {
_read(PC + 1); //dummy read
Y = A;
Y &= 0xFF;
checkZero(Y);
checkSign(Y);
}
/**
* TYA (Transfer Y to A).
*/
public void tya() {
_read(PC + 1); //dummy read
A = Y;
A &= 0xFF;
checkZero(A);
checkSign(A);
}
/**
* DEY (DEcrement Y).
*/
public void dey() {
_read(PC + 1); //dummy read
--Y;
Y &= 0xFF;
checkZero(Y);
checkSign(Y);
}
/**
* INY (INcrement Y).
*/
public void iny() {
_read(PC + 1); //dummy read
++Y;
Y &= 0xFF;
checkZero(Y);
checkSign(Y);
}
/**
* ROLA (ROtate Left with Accumulator).
*/
public void rolA() {
_read(PC + 1); //dummy read
A <<= 1;
A |= (c & 1);
checkCarry(A);
checkZero(A);
checkSign(A);
A &= 0xFF;
}
/**
* ROL (ROtate Left).
*
* @param address the address from which the to-be-rol'd data is fetched.
*/
public void rol(int address) {
int data = _read(address);
_write(address, data);
data <<= 1;
data |= (c & 1);
checkCarry(data);
checkZero(data);
checkSign(data);
data &= 0xFF;
_write(address, data);
}
/**
* RORA (ROtate Right with Accumulator).
*/
public void rorA() {
_read(PC + 1); //dummy read
//Shift carry into 8th bit before rotating to shift it into 7th
A |= (c & 1) << 8;
c = A & 1;
A >>>= 1;
checkZero(A);
checkSign(A);
A &= 0xFF;
}
/**
* ROR (ROtate Right).
*
* @param address the address from which the to-be-ror'd data is fetched.
*/
public void ror(int address) {
int data = _read(address);
_write(address, data);
data |= (c & 1) << 8;
c = data & 1;
data >>>= 1;
checkZero(data);
checkSign(data);
data &= 0xFF;
_write(address, data);
}
/**
* RTI (ReTurn from Interrupt).
*/
public void rti() {
_read(PC + 1); //dummy read
_read(SP); //dummy read... 2
setCPUStatus(_pull() | (1 << FLAG_U));
PC = _pull() | (_pull() << 8);
}
/**
* RTS (ReTurn from Subroutine).
*/
public void rts() {
_read(PC + 1); //dummy read
_read(SP); //dummy read... 2
PC = _pull() | (_pull() << 8);
_read(PC); //dummy read... 3?????
PC++;
}
/**
* SBC (SuBtract with Carry).
*
* @param address the address from which the to-be-subtracted data is
* fetched.
*/
public void sbc(int address) {
int data = _read(address);
int result = A - data - (c ^ 1);
checkBorrow(result);
checkZero(result);
checkSBCOverflow(A, data, result);
checkSign(result);
A = result & 0xFF;
}
/**
* STO (STOre register | Unofficial naming).
*
* Belongs to:
* - STA (STore Accumulator)
* - STX (STore X)
* - STY (STore Y)
*
* @param reg the register which is stored in the specified address.
* @param address the address in which the register is stored.
*/
public void sto(int address, int reg) {
_write(address, reg);
}
/**
* TXS (Transfer X to Stack pointer).
*/
public void txs() {
_read(PC + 1); //dummy read
SP = X;
}
/**
* TSX (Transfer Stack pointer to X).
*/
public void tsx() {
_read(PC + 1); //dummy read
X = SP;
checkZero(X);
checkSign(X);
}
/**
* PHA (PusH Accumulator).
*/
public void pha() {
_read(PC + 1);
_push(A);
}
/**
* PLA (PuLl Accumulator).
*/
public void pla() {
_read(PC + 1); //dummy read
_read(SP); //dummy read... 2
A = _pull();
checkZero(A);
checkSign(A);
}
/**
* PHP (PusH Processor status).
*/
public void php() {
_read(PC + 1); //dummy read
_push(getCPUStatus() | (1 << FLAG_U) | (1 << FLAG_B));
}
/**
* PLP (PuLl Processor status).
*/
public void plp() {
_read(PC + 1); //dummy read
_read(SP); //dummy read... 2
setCPUStatus((_pull() | (1 << FLAG_U)) & ~(1 << FLAG_B));
}
/**
* NMI (Non-Maskable Interrupt).
*
* Called from the PPU to tell the CPU that an NMI is supposed to happen.
*/
public void nmi() {
nmiHandler.setInterruptHandled(false);
}
/**
* Normal memory read.
*
* @param address the address to be read from.
* @return the read data.
*/
@Override
public int read(int address) {
return memory.read(address);
}
/**
* Normal memory write.
*
* @param address the addess to be written to.
* @param data the data to be written to the given address.
*/
@Override
public void write(int address, int data) {
memory.write(address, data);
}
/**
* Normal pull from stack.
*
* @return the pulled data.
*/
public int pull() {
return memory.read(0x100 + ++SP);
}
/**
* Normal push to stack.
*
* @param data the pushed data.
*/
public void push(int data) {
memory.write(0x100 + SP--, data);
}
/**
* Cycled memory read.
*
* Invokes the PPU's cycle method 3 times.
*
* @param address the address to be read from.
* @return the read data.
*/
public int _read(int address) {
int data = read(address);
tickPPU();
return data;
}
/**
* Cycled memory write.
*
* Invokes the PPU's cycle method 3 times.
*
* @param address the addess to be written to.
* @param data the data to be written to the given address.
*/
public void _write(int address, int data) {
write(address, data);
tickPPU();
}
/**
* Cycled pull from stack.
*
* Invokes the PPU's cycle method 3 times.
*
* @return the pulled data.
*/
public int _pull() {
return _read(0x100 + ++SP);
}
/**
* Cycled push to stack.
*
* Invokes the PPU's cycle method 3 times.
*
* @param data the pushed data.
*/
public void _push(int data) {
_write(0x100 + SP--, data);
}
public int getRelativeAddress() {
return PC++;
}
public int getImmediateAddress() {
return PC++;
}
public int getZeroAddress() {
return _read(PC++);
}
public int getZeroAddress(int reg) {
int lo = _read(PC++);
_read(lo); //dummy read
return (lo + reg) & 0xFF;
}
public int getAbsoluteAddress() {
return _read(PC++) | (_read(PC++) << 8);
}
public int getAbsoluteAddress(int reg, Dummy dummy) {
int address = _read(PC++) | (_read(PC++) << 8);
if ((address & 0xFF00) != ((address + reg) & 0xFF00)) {
//In some cases dummy read is exclusively when page crossing
if (dummy == ON_CROSSING) {
_read((address & 0xFF00) | (address + reg) & 0xFF);
}
}
//In other cases dummy read always happens
if (dummy == ALWAYS) {
_read((address & 0xFF00) | ((address + reg) & 0xFF));
}
address += reg;
address &= 0xFFFF;
return address;
}
public int getIndirectAddress() {
int address = _read(PC++) | (_read(PC++) << 8);
if ((address & 0xFF) == 0xFF) {
return _read(address)
| (_read(address & 0xFF00) << 8);
}
return _read(address)
| (_read(address + 1) << 8);
}
public int getIndirectXAddress() {
int lo = _read(PC++);
_read(lo); //dummy read
return _read((lo + X) & 0xFF)
| (_read((lo + X + 1) & 0xFF) << 8);
}
public int getIndirectYAddress(Dummy dummy) {
int lo = _read(PC++);
int address = _read(lo & 0xFF)
| (_read((lo + 1) & 0xFF) << 8);
if ((address & 0xFF00) != ((address + Y) & 0xFF00)) {
if (dummy == Dummy.ON_CROSSING) {
_read((address & 0xFF00) | ((address + Y) & 0xFF));
}
}
if (dummy == Dummy.ALWAYS) {
_read((address & 0xFF00) | ((address + Y) & 0xFF));
}
return (address + Y) & 0xFFFF;
}
public void checkADCOverflow(int reg, int data, int result) {
v = (((reg ^ data) & 0x80) == 0) && (((reg ^ result) & 0x80) != 0) ? 1 : 0;
}
public void checkSBCOverflow(int reg, int data, int result) {
v = (((reg ^ data) & 0x80) != 0) && (((reg ^ result) & 0x80) != 0) ? 1 : 0;
}
public void checkCarry(int result) {
c = (result >> 8) & 1;
}
public void checkBorrow(int result) {
c = ((result >> 8) & 1) ^ 1;
}
public void checkZero(int result) {
z = ((result & 0xFF) == 0) ? 1 : 0;
}
public void checkSign(int result) {
s = (result & 0x80) >> FLAG_S;
}
/**
* Dumps the processor status flag register.
*
* @return the CPU status flags in an integer.
*/
public int getCPUStatus() {
return (c << FLAG_C)
| (z << FLAG_Z)
| (i << FLAG_I)
| (d << FLAG_D)
| (b << FLAG_B)
| (u << FLAG_U)
| (v << FLAG_V)
| (s << FLAG_S);
}
/**
* Sets the processor status flags from an integer.
*
* @param p the integer which represents the CPU status register.
*/
public void setCPUStatus(int p) {
c = ((1 << FLAG_C) & p) >> FLAG_C;
z = ((1 << FLAG_Z) & p) >> FLAG_Z;
i = ((1 << FLAG_I) & p) >> FLAG_I;
d = ((1 << FLAG_D) & p) >> FLAG_D;
b = ((1 << FLAG_B) & p) >> FLAG_B;
u = ((1 << FLAG_U) & p) >> FLAG_U;
v = ((1 << FLAG_V) & p) >> FLAG_V;
s = ((1 << FLAG_S) & p) >> FLAG_S;
}
/**
* Invoke the PPU's cycle method 3 times.
*/
public void tickPPU() {
PPU ppu = getNES().getPPU();
ppu.cycle();
ppu.cycle();
ppu.cycle();
}
/**
* Enum for Dummy reads in certain addressing modes.
*
* Denotes whether a dummy read should always happen,
* or whether it should happen solely on carry set.
*/
public enum Dummy {
ON_CROSSING, ALWAYS;
}
/**
* Handler for NMIs.
*/
public class NMIHandler extends InterruptHandler {
public NMIHandler(NES nes) {
super(nes);
}
@Override
public void interrupt() {
push(PC >> 8);
push(PC & 0xFF);
push(getCPUStatus() & ~(1 << FLAG_B));
PC = read(0xFFFA) | (read(0xFFFB) << 8);
//Finish the interrupt
setInterruptHandled(true);
}
}
public int getA() {
return A;
}
public int getX() {
return X;
}
public int getY() {
return Y;
}
public int getP() {
return getCPUStatus();
}
public int getSP() {
return SP;
}
public int getPC() {
return PC;
}
}