Skip to content

Instantly share code, notes, and snippets.

@stanleykerr
Last active September 12, 2017 19:37
Show Gist options
  • Select an option

  • Save stanleykerr/cf4d0ed93221af2f5a73292456c0c3ec to your computer and use it in GitHub Desktop.

Select an option

Save stanleykerr/cf4d0ed93221af2f5a73292456c0c3ec to your computer and use it in GitHub Desktop.
lol i'm never gonna finish this
package com.stanley.nesemu;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CPU {
private Rom rom;
private Ram ramObj;
private PPU ppu;
private APU apu;
public CPURam ram;
public int pc, x, y, a, s, pb; // pb = 1 if goes past page boundary
private Status statusReg;
private boolean running;
private final byte[] CPI = {7,6,1,8,3,3,5,5,3,2,2,2,4,4,6,6,3,5,1,8,4,4,6,6,2,4,2,7,4,4,7,7,6,6,1,8,3,3,5,5,4,2,2,2,4,4,6,6,2,5,1,8,4,4,6,6,2,4,2,7,4,4,7,7,6,6,1,8,3,3,5,5,3,2,2,2,3,4,6,6,3,5,1,8,4,4,6,6,2,4,2,7,4,4,7,7,6,6,1,8,3,3,5,5,4,2,2,2,5,4,6,6,2,5,1,8,4,4,6,6,2,4,2,7,4,4,7,7,2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,3,6,1,6,4,4,4,4,2,5,2,5,5,5,5,5,2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,2,5,1,5,4,4,4,4,2,4,2,4,4,4,4,4,2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,3,5,1,8,4,4,6,6,2,4,2,7,4,4,7,7,2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,2,5,1,8,4,4,6,6,2,4,2,7,4,4,7,7};
private int cycles;
public CPU(Ram ram, PPU ppu, APU apu, Rom rom) {
this.ramObj = ram;
this.ppu = ppu;
this.rom = rom;
this.running = false;
this.ram = new CPURam(rom, ppu, ram, apu);
}
public void startup() {
this.statusReg = new Status();
// TODO: Implement memory sets
for(int i = 0; i < 0x800; ++i)
ram.write(i, 0xFF);
ram.write(0x0008, 0xF7);
ram.write(0x0009, 0xEF);
ram.write(0x000A, 0xDF);
ram.write(0x000F, 0xBF);
for(int i = 0x4000; i <= 0x400F; ++i)
ram.write(i, 0x00);
a = 0;
x = 0;
y = 0;
s = 0xFD;
pc = ram.read(0xFFFD) * 256 + ram.read(0xFFFC);
}
public void runRom(Rom rom) {
// Load rom
this.rom = rom;
// Program counter stores current execution point
this.pc = Rom.HEADER_SIZE;
// Run program
this.running = true;
while(this.running) {
runCycle();
}
}
private static enum Modifier {
INDX(0x01, 0x03, 0x21, 0x23, 0x41, 0x43, 0x61, 0x63, 0x81, 0x83, 0xA1, 0xA2, 0xA3, 0xC1, 0xC3, 0xE1, 0xE3),
IMM(0x09, 0x0B, 0x29, 0x2B, 0x49, 0x4B, 0x69, 0x6B, 0x80, 0x82, 0x89, 0xA0, 0xA2, 0xA9, 0xAB, 0xAB, 0xC0, 0xC2, 0xC9, 0xCB, 0xE0, 0xE2, 0xE9, 0xEB),
ZPG_PLAIN(0x04, 0x05, 0x06, 0x07, 0x24, 0x25 ,0x26, 0x27, 0x44, 0x45, 0x46, 0x47, 0x64, 0x65, 0x66, 0x67, 0x84, 0x85, 0x86, 0x87, 0xA4, 0xA5, 0xA6, 0xA7, 0xC4, 0xC5, 0xC6, 0xC7, 0xE4, 0xE5, 0xE6, 0xE7),
ZPG_X(0x14, 0x15, 0x16, 0x17, 0x34, 0x35, 0x36, 0x37, 0x54, 0x55, 0x56, 0x57, 0x74, 0x75, 0x76, 0x77, 0x94, 0x95, 0xB4, 0xB5, 0xD4, 0xD5, 0xD6, 0xD7, 0xF4, 0xF5, 0xF6, 0xF7),
ZPG_Y(0xB7, 0xB6, 0x97, 0x96),
ABS_PLAIN(0x0C, 0x0D, 0x0E, 0x0F, 0x20, 0x2C, 0x2D, 0x2E, 0x2F, 0x4D, 0x4E, 0x4F, 0x6D, 0x6E, 0x6F, 0x8C, 0x8D, 0x8E, 0x8F, 0xAC, 0xAD, 0xAE, 0xAF, 0xCC, 0xCD, 0xCE, 0xCF, 0xEC, 0xED, 0xEE, 0xEF),
ABSX_ONCARRY(0x1C, 0x1D, 0x3C, 0x3D, 0x5C, 0x5D, 0x7C, 0x7D, 0xBC, 0xBD, 0xDC, 0xDD, 0xFC, 0xFD),
ABSX_ALWAYS(0x1E, 0x1F, 0x3E, 0x3F, 0x5E, 0x5F, 0x7E, 0x7F, 0x9C, 0x9D, 0xDE, 0xDF, 0xFE, 0xFF),
ABSY_ONCARRY(0x19, 0x39, 0x59, 0x79, 0xB9, 0xBB, 0xBE, 0xBF, 0xD9, 0xF9),
ABSY_ALWAYS(0x1B, 0x3B, 0x5B, 0x7B, 0x99, 0x9B, 0x9E, 0x9F, 0xDB, 0xFB),
INDY_ONCARRY(0x11, 0x31, 0x51, 0x71, 0xB1, 0xB3, 0xD1, 0xF1),
INDY_ALWAYS(0x13, 0x33, 0x53, 0x73, 0x91, 0x93, 0xD3, 0xF3),
NONE;
private Map<Integer> addressMap = new HashMap<>();
private Modifier(int... addresses) {
for(int addr : addresses)
addressMap.put(addr, this);
}
public static int get(int addr) {
Modifier modifier = addressMap.getOrDefault(addr, NONE);
switch(modifier) {
case INDX: return indX();
case IMM: return imm();
case ZPG_PLAIN: return zpg();
case ZPG_X: return zpg(x);
case ZPG_Y: return zpg(y);
case ABS_PLAIN: return abs();
case ABS_XONCARRY: return abs(x, Dummy.ONCARRY);
case ABS_XALWAYS: return abs(x, Dummy.ALWAYS);
case ABS_YONCARRY: return abs(y, Dummy.ONCARRY);
case ABS_YALWAYS: return abs(y, Dummy.ALWAYS);
case INDY_ONCARRY: return indY(Dummy.ONCARRY);
case INDY_ALWAYS: return indY(Dummy.ALWAYS);
case NONE:
default: return -1;
}
}
}
private static enum IType {
ADC(0x69, 0x65, 0x75, 0x6D, 0x7D, 0x79, 0x61, 0x71), AND(0x29, 0x25, 0x35, 0x2D, 0x3D, 0x39, 0x21, 0x31), ASL(0x0A, 0x06, 0x16, 0x0E, 0x1E),
BCC(0x90), BCS(0xB0), BEQ(0xF0), BIT(0x24, 0x2C), BMI(0x30), BNE(0xD0), BPL(0x10), BRK(0x00), BVC(0x50), BVS(0x70),
CLC(0x18), CLD(0xD8), CLI(0x58), CLV(0xB8), CMP(0xC1, 0xC5, 0xC9, 0xCD, 0xD1, 0xD5, 0xD9, 0xDD), CPX(0xE0, 0xE4, 0xEC), CPY(0xC0, 0xC4, 0xCC),
DEC(0xC6, 0xD6, 0xCE, 0xDE), DEX(0xCA), DEY(0x88),
EOR(0x49, 0x45, 0x55, 0x4D, 0x5D, 0x59, 0x41, 0x51),
INC(0xE6, 0xF6, 0xEE, 0xFE), INX(0xE8), INY(0xC8),
JMP(0x4C, 0x6C), JSR(0x20),
LDA(0xA9, 0xA5, 0xB5, 0xAD, 0xBD, 0xB9, 0xA1, 0xB1), LDX(0xA2, 0xA6, 0xB6, 0xAE, 0xBE), LDY(0xA0, 0xA4, 0xB4, 0xAC, 0xBC), LSR(0x4A, 0x46, 0x56, 0x4E, 0x5E),
NOP(0x1A, 0x3a, 0x5a, 0x7a, 0xda, 0xea, 0xfa, 0x80, 0x82, 0xc2, 0xe2, 0x89, 0x04, 0x44, 0x64, 0x14, 0x34, 0x54, 0x74, 0xd4, 0xf4, 0x0C, 0x1c, 0xc3, 0x5c, 0x7c, 0xdc, 0xfc),
ORA(0x09, 0x05, 0x15, 0x15, 0x0d, 0x1d, 0x19, 0x01, 0x11),
PHA(0x48), PHP(0x08), PLA(0x68), PLP(0x28),
ROL(0x2A, 0x26, 0x36, 0x2E, 0x3E), ROR(0x6A, 0x66, 0x76, 0x6E, 0x7E), RTI(0x40), RTS(0x60),
SBC(0xE9, 0xE5, 0xF5, 0xED, 0xFD, 0xF9, 0xE1, 0xF1), SEC(0x38), SED(0xF8), SEI(0x78), SLO(0x03, 0x07, 0x0f, 0x13, 0x17, 0x1b, 0x1f), STA(0x85, 0x95, 0x8D, 0x9D, 0x99, 0x81, 0x91), STX(0x86, 0x96, 0x8E), STY(0x84, 0x94, 0x8C),
TAX(0xAA), TAY(0xA8), TSX(0xBA), TXA(0x8A), TXS(0x9A), TYA(0x98),
UNREGISTERED;
private static Map<Integer, IType> addressMap = new HashMap<>();
private IType(int... addresses) {
for(int addr : addresses)
addressMap.put(addr, this);
}
public static IType get(int addr) {
return addressMap.getOrDefault(addr, UNREGISTERED);
}
}
private static enum Dummy {
ONCARRY, ALWAYS;
}
private int interrupt = 0;
public void runCycle() {
if(cycles-- > 0) return;
if(interrupt > 0) {
if(!statusReg.isInterrupt() && !interruptDelay) {
interrupt();
cycles += 7;
return;
} else if(interruptDelay) {
interruptDelay = false;
if(!previntflag) {
interrupt();
cycles += 7;
return;
}
}
} else {
interruptDelay = false;
}
int instr = this.rom.read(this.pc++);
IType type = IType.get(instr);
int toUse = Modifier.get(instr);
/* DEBUG OUTPUT: String op = String.format(Util.opcodes()[instr],
ram.read(pc),
ram.read(pc + 1),
pc + (byte) (ram.read(pc)) + 1);
System.out.println(Util.toHex(pc - 1) + " " + Util.toHex(instr)
+ String.format(" %-14s ", op));*/
switch(type) {
case ADC:
adc(toUse);
break;
case AND:
and(toUse);
break;
case ASL:
if(instr == 0x0A) aslA();
else asl(toUse);
break;
case BCC:
branch(!statusReg.isCarry());
break;
case BCS:
branch(statusReg.isCarry());
break;
case BEQ:
branch(statusReg.isZero());
break;
case BIT:
bit(toUse);
break;
case BMI:
branch(statusReg.isNegative());
break;
case BNE:
branch(!statusReg.isZero());
break;
case BPL:
branch(!statusReg.isNegative());
break;
case BRK:
breakinterrupt();
break;
case BVC:
branch(!statusReg.isOverflow());
break;
case BVS:
branch(statusReg.isOverflow());
break;
case CLC:
statusReg.setCarry(false);
break;
case CLD:
statusReg.setDecimal(false);
break;
case CLI:
delayInterrupt();
statusReg.setInterrupt(false);
break;
case CLV:
statusReg.setOverflow(false);
break;
case CMP:
cmp(a, toUse);
break;
case CPX:
cmp(x, toUse);
break;
case CPY:
cmp(y, toUse);
break;
case DEC:
dec(toUse);
break;
case DEX:
x--;
x &= 0xFF;
setflags(x);
break;
case DEY:
y--;
y &= 0xFF;
setflags(y);
break;
case EOR:
eor(toUse);
break;
case INC:
inc(toUse);
break;
case INX:
x++;
x &= 0xff;
setflags(x);
break;
case INY:
y++;
y &= 0xff;
setflags(y);
break;
case JMP:
int tempPC = pc;
pc = instr == 0x4cc ? abs() : ind();
if(pc == (tempPC - 1))
// TODO: idle = true;
break;
case JSR:
jsr(toUse);
break;
case LDA:
ld(toUse, a);
break;
case LDX:
ld(toUse, x);
break;
case LDY:
ld(toUse, y);
break;
case LSR:
if(instr == 0x4A) lsrA();
else lsr(toUse);
break;
case NOP:
break;
case ORA:
ora(toUse);
break;
case PHA:
ram.read(pc + 1); //dummy fetch
push(a);
break;
case PHP:
ram.read(pc + 1); //dummy fetch
push(flagstobyte() | 16);
break;
case PLA:
ram.read(pc + 1); //dummy fetch
a = pop();
setflags(a);
break;
case PLP:
delayInterrupt();
ram.read(pc + 1); //dummy fetch
bytetoflags(pop());
break;
case ROL:
if(instr == 0x2A) rolA();
else rol(toUse);
break;
case ROR:
if(instr == 0x6A) rorA();
else ror(toUse);
break;
case RTI:
rti();
break;
case RTS:
rts();
break;
case SBC:
sbc(toUse);
break;
case SEC:
statusReg.setCarry(true);
break;
case SED:
statusReg.setDecimal(true);
break;
case SEI:
delayInterrupt();
statusReg.setInterrupt(true);
break;
case SLO:
slo(toUse);
break;
case STA:
st(toUse, a);
break;
case STX:
st(toUse, x);
break;
case STY:
st(toUse, y);
break;
case TAX:
x = a;
setflags(a);
break;
case TAY:
y = a;
setflags(a);
break;
case TSX:
s = x;
setflags(x);
break;
case TXA:
a = x;
setflags(a);
break;
case TXS:
s = x;
break;
case TYA:
a = y;
setflags(a);
break;
case UNREGISTERED:
default:
System.err.println("Instruction not found! (" + Util.toHex(instr) + ")");
System.exit(1);
break;
}
cycles += CPI[instr];
}
private boolean interruptDelay, previntflag;
private void delayInterrupt() {
interruptDelay = true;
previntflag = statusReg.isInterrupt();
}
private void rol(final int addr) {
int data = ram.read(addr);
ram.write(addr, data);
data = (data << 1) | (statusReg.isCarry() ? 1 : 0);
statusReg.setCarry((data & 256) != 0);
data &= 0xFF;
setflags(data);
ram.write(addr, data);
}
private void rolA() {
a = a << 1 | (statusReg.isCarry() ? 1 : 0);
statusReg.setCarry((a & 256) != 0);
a &= 0xff;
setflags(a);
}
private void ror(final int addr) {
int data = ram.read(addr);
ram.write(addr, data);
final boolean tmp = statusReg.isCarry();
statusReg.setCarry((data & 1) != 0);
data = ((data >> 1) & 0x7F) | (tmp ? 0x80 : 0);
setflags(data);
ram.write(addr, data);
}
private void rorA() {
final boolean tmp = statusReg.isCarry();
statusReg.setCarry((a & 1) != 0);
a >>= 1;
a &= 0x7F;
a |= (tmp ? 128 : 0);
setflags(a);
}
private void rts() {
ram.read(pc++);
pc = (pop() & 0xff) | (pop() << 8);
pc++;
}
private void rti() {
ram.read(pc++);
bytetoflags(pop());
pc = (pop() & 0xff) | (pop() << 8);
}
private int pop() {
++s;
s &= 0xff;
return ram.read(0x100 + s);
}
public void push(final int byteToPush) {
ram.write((0x100 + (s & 0xff)), byteToPush);
--s;
s &= 0xff;
}
private void branch(final boolean isTaken) {
if(!isTaken) {
rel();
return;
}
final int pcprev = pc + 1; // previous pc
pc = rel();
// page boundary penalty
if((pcprev & 0xff00) != (pc & 0xff00))
pb = 2;
//else cycles++;
//if((pcprev - 2) == pc)
//idle = true;
}
private void interrupt() {
push(pc >> 8);
push(pc & 0xFF);
push(flagstobyte() & ~16);
pc = ram.read(0xFFFE) + (ram.read(0xFFFF) << 8);
statusReg.setInterrupt(true);
}
private void breakinterrupt() {
// same as interrupt but BRK flag is turned on
ram.read(pc++); // dummy fetch
push(pc >> 8); // high bit 1st
push(pc & 0xFF); // check that this pushes right address
push(flagstobyte() | 16 | 32); // push byte w/bits 4+5 set
pc = ram.read(0xFFFE) + (ram.read(0xFFFF) << 8);
statusReg.setInterrupt(true);
}
private void lsr(final int addr) {
int data = ram.read(addr);
ram.write(addr, data);
statusReg.setCarry((data & 1) != 0);
data >>= 1;
data &= 0x7F;
ram.write(addr, data);
setflags(data);
}
private void lsrA() {
statusReg.setCarry((a & 1) != 0);
a >>= 1;
a &= 0x7F;
setflags(a);
}
private boolean decimalModeEnable = false;
private void ld(final int addr, int v) {
v = ram.read(addr);
setflags(v);
}
private void st(final int addr, int v) {
ram.write(addr, v);
}
private void setflags(final int result) {
statusReg.setZero(result == 0);
statusReg.setNegative((result & 128) != 0);
}
private void inc(final int addr) {
int tmp = ram.read(addr);
ram.write(addr, tmp);
++tmp;
tmp &= 0xff;
ram.write(addr, tmp);
setflags(tmp);
}
private void dec(final int addr) {
int tmp = ram.read(addr);
ram.write(addr, tmp);
--tmp;
tmp &= 0xff;
ram.write(addr, tmp);
setflags(tmp);
}
private void adc(final int addr) {
final int value = ram.read(addr);
int result;
if(statusReg.isDecimal() && decimalModeEnable) {
int al = (a & 0xF) + (value & 0xF) + (statusReg.isCarry() ? 1 : 0);
if(al >= 0x0A)
al = ((al + 0x6) & 0xF) + 0x10;
result = (a & 0xF0) + (value & 0xF0) + al;
if(result >= 0xA0)
result += 0x60;
} else result = value + a + (statusReg.isCarry() ? 1 : 0);
statusReg.setCarry(result >> 8 != 0);
statusReg.setOverflow(((a ^ value) & 0x80) == 0 && (((a ^ result) & 0x80)) != 0);
a = result & 0xff;
setflags(a);
}
private void sbc(final int addr) {
final int value = ram.read(addr);
int result;
if(statusReg.isDecimal() && decimalModeEnable) {
int al = (a & 0xF) - (value & 0xF) + (statusReg.isCarry() ? 1 : 0);
if(al < 0)
al = ((al - 0x6) & 0xF) - 0x10;
result = (a & 0xF0) + (value & 0xF0) + al;
if(result < 0)
result -= 0x60;
} else result = a - value - (statusReg.isCarry() ? 0 : 1);
statusReg.setCarry(result >> 8 == 0);
statusReg.setOverflow((((a ^ value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0));
a = result & 0xff;
setflags(a);
}
private void and(final int addr) {
a &= ram.read(addr);
setflags(a);
}
private void asl(final int addr) {
int data = ram.read(addr);
ram.write(addr, data);
statusReg.setCarry((data & 128) != 0);
data <<= 1;
data &= 0xff;
setflags(data);
ram.write(addr, data);
}
private void aslA() {
statusReg.setCarry((a & 128) != 0);
a <<= 1;
a &= 0xff;
setflags(a);
}
private void cmp(final int regVal, final int addr) {
final int result = regVal - ram.read(addr);
statusReg.setNegative(result == 0 ? false : (result & 128) != 0);
statusReg.setCarry(result >= 0);
statusReg.setZero(result == 0);
}
private void eor(final int addr) {
a ^= ram.read(addr);
a &= 0xff;
setflags(a);
}
private void ora(final int addr) {
a |= ram.read(addr);
a &= 0xff;
setflags(a);
}
private void bit(final int addr) {
final int data = ram.read(addr);
statusReg.setZero((data & a) == 0);
statusReg.setNegative((data & 128) != 0);
statusReg.setOverflow((data & 64) != 0);
}
private void jsr(final int addr) {
pc--;
ram.read(pc);
push(pc >> 8);
push(pc & 0xFF);
pc = addr;
}
private int imm() {
return pc++;
}
// Zero page mode
private int zpg() {
return ram.read(pc++);
}
// Zero page added to register (modulus page boundary)
private int zpg(final int reg) {
return (ram.read(pc++) + reg) & 0xff;
}
// Returns actual value of PC, not memory location to look at
// because only branches use this
private int rel() {
return ((byte) ram.read(pc++)) + pc;
}
// Absolute mode
private int abs() {
return ram.read(pc++) + (ram.read(pc++) << 8);
}
// Absolute + value from register
private int abs(final int reg, final Dummy dummy) {
final int addr = (ram.read(pc++) | (ram.read(pc++) << 8));
if(addr >> 8 != (addr + reg) >> 8) pb = 1;
if(((addr & 0xFF00) != ((addr + reg) & 0xFF00) && dummy == Dummy.ONCARRY) || dummy == Dummy.ALWAYS)
ram.read((addr & 0xFF00) | ((addr + reg) & 0xFF));
return (addr + reg) & 0xFFFF;
}
/* [ Only used by JMP ]
* If reading from the last byte in a page, high bit of address
* Taken from first byte on the page, not first byte on NEXT page.
*/
private int ind() {
final int addr = abs();
return ram.read(addr) + (ram.read(((addr & 0xff) == 0xff) ? addr - 0xff : addr + 1) << 8);
}
// Indirect mode
// (Doesn't suffer from same bug as jump indirect)
private int indX() {
final int arg = ram.read(pc++);
return ram.read((arg + x) & 0xff) + (ram.read((arg + 1 + x) & 0xff) << 8);
}
private void slo(int addr) {
asl(addr);
ora(addr);
}
private int indY(final Dummy dummy) {
final int arg = ram.read(pc++);
final int addr = (ram.read((arg) & 0xff) | (ram.read((arg + 1) & 0xff) << 8));
if(addr >> 8 != (addr + y) >> 8) pb = 1;
if(((addr & 0xFF00) != ((addr + y) & 0xFF00) && dummy == Dummy.ONCARRY) || dummy == Dummy.ALWAYS)
ram.read((addr & 0xFF00) | ((addr + y) & 0xFF));
return (addr + y) & 0xffff;
}
public final int flagstobyte() {
return ((statusReg.isNegative() ? 128 : 0) | (statusReg.isOverflow() ? 64 : 0) | 32
| (statusReg.isDecimal() ? 8 : 0) | (statusReg.isInterrupt() ? 4 : 0)
| (statusReg.isZero() ? 2 : 0) | (statusReg.isCarry() ? 1 : 0));
}
private void bytetoflags(final int sb) {
statusReg.setNegative((sb & 128) != 0);
statusReg.setOverflow((sb & 64) != 0);
statusReg.setDecimal((sb & 8) != 0);
statusReg.setInterrupt((sb & 4) != 0);
statusReg.setZero((sb & 2) != 0);
statusReg.setCarry((sb & 1) != 0);
}
public Status getStatusReg() {
return statusReg;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment