/**
* This file is a part of JaC64 - a Java C64 Emulator
* Main Developer: Joakim Eriksson (Dreamfabric.com)
* Contact: joakime@sics.se
* Web: http://www.dreamfabric.com/c64
* ---------------------------------------------------
*
*
*/
package com.dreamfabric.jac64;
import java.io.*;
/**
* MOS6510Core "implements" the 6510 processor in java code.
* Other classes are intended to implement the specific
* write/read from memory for correct emulation of RAM/ROM/IO
* handling
*
* @author Joakim Eriksson (joakime@sics.se)
* @author Jan Blok (jblok@profdata.nl)
* @version $Revision: $
* $Date: $
*/
public abstract class MOS6510Core extends MOS6510Ops {
protected int memory[];
protected boolean debug = false;
public static final int NMI_DELAY = 2;
public static final int IRQ_DELAY = 2;
public static final int NMI_INT = 1;
public static final int IRQ_INT = 2;
// Needed by ...
protected PatchListener list;
protected ExtChip chips = null;
protected IMonitor monitor;
public String codebase;
// -------------------------------------------------------------------
// Interrup signals
// -------------------------------------------------------------------
public boolean checkInterrupt = false;
public boolean NMILow = false;
public boolean NMILastLow = false;
private boolean IRQLow = false;
public int lastInterrupt = 0;
public boolean busAvailable = true;
public long baLowUntil = 0;
// The processor flags
boolean sign = false;
boolean zero = false;
boolean overflow = false;
boolean carry = false;
boolean decimal = false;
boolean brk = false;
boolean resetFlag = false;
// registers
protected int acc = 0;
protected int x = 0;
protected int y = 0;
protected int s = 0xff; // The stackpointer ??? ff = top?
protected long nmiCycleStart = 0;
protected long irqCycleStart = 0;
protected EventQueue scheduler = new EventQueue();
private String[] debugInfo;
public MOS6510Core(IMonitor m, String cb) {
monitor = m;
codebase = cb;
}
public abstract String getName();
public int[] getMemory() {
return memory;
}
public void jump(int pc) {
jumpTo = pc;
checkInterrupt = true;
}
public long getCycles() {
return cycles;
}
public void setIRQLow(boolean low) {
if (!IRQLow && low) {
// If low -> will trigger an IRQ!
checkInterrupt = true;
irqCycleStart = cycles + IRQ_DELAY;
}
IRQLow = low;
}
public void setNMILow(boolean low) {
if (!NMILow && low) {
// If going from "high" to low -> will trigger an NMI!
checkInterrupt = true;
nmiCycleStart = cycles + NMI_DELAY;
//System.out.println("*** NMI Goes low!");
}
NMILow = low;
// If setting to non-low - both low and lastLow can be set?
if (!low) {
NMILastLow = low;
//System.out.println("*** NMI Goes hi!");
}
}
protected int jumpTo = -1;
public long cycles = 0;
protected long lastMillis = 0;
// Some temporary and other variables...
protected long nr_ins = 0;
protected long nr_irq = 0;
protected long start = System.currentTimeMillis();
protected int pc;
protected int interruptInExec = 0;
protected boolean disableInterupt = false;
// Used for actual address...
protected int rindex = 0;
protected int lastReadOP = 0;
public int getSP() {
return s;
}
private final void doInterrupt(int adr, int status) {
// System.out.println("Doing Interrupt disableInterrupt before: " +
// disableInterupt);
fetchByte(pc);
fetchByte(pc + 1);
push((pc & 0xff00) >> 8); // HI ??
push(pc & 0x00ff); // LOW ??
push(status);
interruptInExec++;
pc = (fetchByte(adr + 1) << 8);
pc += fetchByte(adr);
}
protected final int getStatusByte() {
return
((carry ? 0x01 : 0) + (zero ? 0x02 : 0) + (disableInterupt ? 0x04 : 0) +
(decimal ? 0x08 : 0) + (brk ? 0x10 : 0) + 0x20 +
(overflow ? 0x40 : 0) + (sign ? 0x80 : 0));
}
private final void setStatusByte(int status) {
carry = (status & 0x01) != 0;
zero = (status & 0x02) != 0;
disableInterupt = (status & 0x04) != 0;
decimal = (status & 0x08) != 0;
brk = (status & 0x10) != 0;
overflow = (status & 0x40) != 0;
sign = (status & 0x80) != 0;
}
// Memory handling - both methods always add 1 to cycles!!!
protected abstract int fetchByte(int adr);
protected abstract void writeByte(int adr, int data);
private final void setZS(int data) {
zero = data == 0;
sign = data > 0x7f;
}
private final void setCarry(int data) {
carry = data > 0x7f;
}
// -------------------------------------------------------------------
// Old m4 macros as methods... should be replaced some day (with above)
// -------------------------------------------------------------------
// Stack operations...
//can access array directly 's' is filed with one byte
private final int pop() {
int r = fetchByte((s = (s + 1) & 0xff) | 0x100);
return r;
}
//can access array directly 's' is filed with one byte
private final void push(int data) {
writeByte((s & 0xff) | 0x100, data);
s = (s - 1) & 0xff;
}
private final void opADCimp(int data) {
int tmp = data + acc + (carry ? 1 : 0);
zero = (tmp & 0xff) == 0; // not valid in decimal mode
if (decimal) {
tmp = (acc & 0xf) + (data & 0xf) + (carry ? 1 : 0);
if (tmp > 0x9)
tmp += 0x6;
if (tmp <= 0x0f)
tmp = (tmp & 0xf) + (acc & 0xf0) + (data & 0xf0);
else
tmp = (tmp & 0xf) + (acc & 0xf0) + (data & 0xf0) + 0x10;
overflow = (((acc ^ data) & 0x80) == 0) &&
(((acc ^ tmp) & 0x80) != 0);
sign = (tmp & 0x80) > 0;
if ((tmp & 0x1f0) > 0x90)
tmp += 0x60;
carry = tmp > 0x99;
} else {
overflow = (((acc ^ data) & 0x80) == 0) &&
(((acc ^ tmp) & 0x80) != 0);
carry = tmp > 0xff;
sign = (tmp & 0x80) > 0;
}
acc = tmp & 0xff;
}
private final void branch(boolean branch, int adr, int cycDiff) {
if (branch) {
int oldPC = pc;
pc = adr;
/* correct branch */
if (cycDiff == 1) {
fetchByte(pc);
} else {
if (pc < oldPC)
fetchByte(pc + 0x100);
else
fetchByte(pc - 0x100);
fetchByte(pc); // Should be fwd or backwd...
}
}
}
private final void opSBCimp(int data) {
int tmp = acc - data - (carry ? 0 : 1);
boolean nxtcarry = (tmp >= 0);
tmp = tmp & 0x1ff; // Carry is set!
sign = (tmp & 0x80) == 0x80; // Invalid in decimal mode??
zero = ((tmp & 0xff) == 0);
overflow = (((acc ^ tmp) & 0x80) != 0) && (((acc ^ data) & 0x80) != 0);
if (decimal) {
tmp = (acc & 0xf) - (data & 0xf) - (carry ? 0 : 1);
if ((tmp & 0x10) > 0)
tmp = ((tmp - 6) & 0xf) |
((acc & 0xf0) - (data & 0xf0) - 0x10);
else
tmp = (tmp & 0xf) | ((acc & 0xf0) - (data & 0xf0));
if ((tmp & 0x100) > 0)
tmp -= 0x60;
}
acc = tmp & 0xff;
carry = nxtcarry;
}
public void emulateOp() {
// Before executing an operation - check for interrupts!!!
if (checkInterrupt) {
// Trigger on negative edge!
if ((NMILow && !NMILastLow) && (cycles >= nmiCycleStart)) {
log("NMI interrupt at " + cycles);
lastInterrupt = NMI_INT;
doInterrupt(0xfffa, getStatusByte() & 0xef);
disableInterupt = true;
//prevent irq during nmi,RTI will clear by poping status back
//checkInterrupt = false;
// Remember last NMI state in order to check on next...
NMILastLow = NMILow;
// Just the interrupt handling... do nothing more...
return;
} else if ((IRQLow && cycles >= irqCycleStart) || brk) {
if (!disableInterupt) {
log("IRQ interrupt > " + IRQLow + " BRK: " + brk);
lastInterrupt = IRQ_INT;
//checkInterrupt = false; //does not make sense to leave more
int status = getStatusByte();
if (brk) {
status |= 0x10;
pc++;
}
else status &= 0xef;
doInterrupt(0xfffe, status);
disableInterupt=true;
//prevent irq during irq, RTI will clear by poping status back
brk = false;
// Just the interrupt handling... do nothing more...
// Remember last NMI state in order to check on next...
// NMILastLow = NMILow;
return;
} else {
brk = false;
checkInterrupt = (NMILow && !NMILastLow);
}
} else if (resetFlag) {
doReset();
} else if (jumpTo != -1) {
pc = jumpTo;
jumpTo = -1;
}
}
// Ok no interrupts, execute instruction
// fetch instruction!
int data = INSTRUCTION_SET[fetchByte(pc++)];
int op = data & OP_MASK;
int addrMode = data & ADDRESSING_MASK;
boolean read = (data & READ) != 0;
boolean write = (data & WRITE) != 0;
int adr = 0;
int tmp = 0;
boolean nxtcarry = false;
lastReadOP = rindex;
// System.out.println("AddrMode:" + Hex.hex2(addrMode) +
// " op: " + Hex.hex2(op)
// + " data: " + Hex.hex2(data));
// fetch first argument (always fetched...?) - but not always pc++!!
int p1 = fetchByte(pc);
// Fetch addres, and read if it should be done!
switch (addrMode) {
// never any address when immediate
case IMMEDIATE:
pc++;
data = p1;
break;
case ABSOLUTE:
pc++;
adr = (fetchByte(pc++) << 8) + p1;
if (read) {
data = fetchByte(adr);
}
break;
case ZERO:
pc++;
adr = p1;
if (read) {
data = fetchByte(adr);
}
break;
case ZERO_X:
case ZERO_Y:
pc++;
// Read from wrong address first...
fetchByte(p1);
if (addrMode == ZERO_X)
adr = (p1 + x) & 0xff;
else
adr = (p1 + y) & 0xff;
if (read) {
data = fetchByte(adr);
}
break;
case ABSOLUTE_X:
case ABSOLUTE_Y:
pc++;
// Fetch hi byte!
adr = fetchByte(pc++) << 8;
// add x/y to low byte & possibly faulty fetch!
if (addrMode == ABSOLUTE_X)
p1 += x;
else
p1 += y;
data = fetchByte(adr + (p1 & 0xff));
adr += p1;
// If read - a fifth cycle patches the incorrect address...
// Always done if RMW!
if (read && (p1 > 0xff || write)) {
data = fetchByte(adr);
}
break;
case RELATIVE:
pc++;
adr = pc + (byte) p1;
if (((adr ^ pc) & 0xff00) > 0) {
// loose one cycle since adr is on another page...
tmp = 2;
} else {
tmp = 1;
}
break;
case ACCUMULATOR:
data = acc;
write = false;
break;
case INDIRECT_X:
pc++;
// unneccesary read... fetchByte(p1);
fetchByte(p1);
tmp = (p1 + x) & 0xff;
adr = (fetchByte(tmp + 1) << 8);
adr |= fetchByte(tmp);
if (read) {
data = fetchByte(adr);
}
break;
case INDIRECT_Y:
pc++;
// Fetch hi and lo
adr = (fetchByte(p1 + 1) << 8);
p1 = fetchByte(p1);
p1 += y;
data = fetchByte(adr + (p1 & 0xff));
adr += p1;
// If read - a sixth cycle patches the incorrect address...
// Always done if RMW!
if (read && (p1 > 0xff || write)) {
data = fetchByte(adr);
}
break;
case INDIRECT:
pc++;
// Fetch pointer
adr = (fetchByte(pc) << 8) + p1;
// Calculate address
tmp = (adr & 0xfff00) | ((adr + 1) & 0xff);
// fetch the real address
adr = fetchByte(adr);
adr += (fetchByte(tmp) << 8);
break;
}
// -------------------------------------------------------------------
// Addressing handled! now on to instructions in order of appearance
// -------------------------------------------------------------------
// If RMW - it will write before proceeding
if (read && write) {
writeByte(adr, data);
}
switch(op) {
case BRK:
brk = true;
checkInterrupt = true;
break;
case AND:
acc = acc & data;
setZS(acc);
break;
case ADC:
opADCimp(data);
break;
case SBC:
opSBCimp(data);
break;
case ORA:
acc = acc | data;
setZS(acc);
break;
case EOR:
acc = acc ^ data;
setZS(acc);
break;
case BIT:
sign = data > 0x7f;
overflow = (data & 0x40) > 0;
zero = (acc & data) == 0;
break;
case LSR:
carry = (data & 0x01) != 0;
data = data >> 1;
zero = (data == 0);
sign = false;
break;
case ROL:
data = (data << 1) + (carry ? 1 : 0);
carry = (data & 0x100) != 0;
data = data & 0xff;
setZS(data);
break;
case ROR:
nxtcarry = (data & 0x01) != 0;
data = (data >> 1) + (carry ? 0x80 : 0);
carry = nxtcarry;
setZS(data);
break;
case TXA:
acc = x;
setZS(acc);
break;
case TAX:
x = acc;
setZS(x);
break;
case TYA:
acc = y;
setZS(acc);
break;
case TAY:
y = acc;
setZS(y);
break;
case TSX:
x = s;
setZS(x);
break;
case TXS:
s = x & 0xff;
break;
case DEC:
data = (data - 1) & 0xff;
setZS(data);
break;
case INC:
data = (data + 1) & 0xff;
setZS(data);
break;
case INX:
x = (x + 1) & 0xff;
setZS(x);
break;
case DEX:
x = (x - 1) & 0xff;
setZS(x);
break;
case INY:
y = (y + 1) & 0xff;
setZS(y);
break;
case DEY:
y = (y - 1) & 0xff;
setZS(y);
break;
// Jumps
case JSR:
pc++;
adr = (fetchByte(pc) << 8) + p1;
fetchByte(s | 0x100);
push((pc & 0xff00) >> 8); // HI
push(pc & 0x00ff); // LOW
pc = adr;
break;
case JMP:
pc = adr;
break;
case RTS:
fetchByte(s | 0x100);
pc = pop() + (pop() << 8);
pc++;
fetchByte(pc);
break;
case RTI:
fetchByte(s | 0x100);
tmp = pop();
setStatusByte(tmp);
pc = pop() + (pop() << 8);
brk = false;
interruptInExec--;
// Need to check for interrupts
checkInterrupt = true;
break;
case TRP:
monitor.info("TRAP Instruction executed");
break;
case NOP:
break;
case ASL:
setCarry(data);
data = (data << 1) & 0xff;
setZS(data);
break;
case PHA:
push(acc);
break;
case PLA:
fetchByte(s | 0x100);
acc = pop();
setZS(acc);
break;
case PHP:
brk = true;
push(getStatusByte());
brk = false;
break;
case PLP:
tmp = pop();
setStatusByte(tmp);
brk = false;
checkInterrupt = true;
break;
case ANC:
acc = acc & data;
setZS(acc);
carry = (acc & 0x80) != 0;
break;
case CMP:
data = acc - data;
carry = data >= 0;
setZS((data & 0xff));
break;
case CPX:
data = x - data;
carry = data >= 0;
setZS((data & 0xff));
break;
case CPY:
data = y - data;
carry = data >= 0;
setZS((data & 0xff));
break;
// Branch instructions
case BCC:
branch(!carry, adr, tmp);
break;
case BCS:
branch(carry, adr, tmp);
break;
case BEQ:
branch(zero, adr, tmp);
break;
case BNE:
branch(!zero, adr, tmp);
break;
case BVC:
branch(!overflow, adr, tmp);
break;
case BVS:
branch(overflow, adr, tmp);
break;
case BPL:
branch(!sign, adr, tmp);
break;
case BMI:
branch(sign, adr, tmp);
break;
// Modify flags
case CLC:
carry = false;
break;
case SEC:
carry = true;
break;
case CLD:
decimal = false;
break;
case SED:
decimal = true;
break;
case CLV:
overflow = false;
break;
case SEI:
disableInterupt = true;
break;
case CLI:
disableInterupt = false;
checkInterrupt = true;
log(getName() + " Enabled interrupts: IRQ: " + chips.getIRQFlags() + " IRQLow: " + IRQLow);
break;
// Load / Store instructions
case LDA:
acc = data;
setZS(data);
break;
case LDX:
x = data;
setZS(data);
break;
case LDY:
y = data;
setZS(data);
break;
case STA:
data = acc;
break;
case STX:
data = x;
break;
case STY:
data = y;
break;
// -------------------------------------------------------------------
// Undocumented ops
// -------------------------------------------------------------------
case ANE:
acc = p1 & x & (acc | 0xee);
setZS(acc);
break;
case ARR: // ARR = AND + ROR ??? - not???
// A'la frodo
tmp = p1 & acc;
acc = (carry ? (tmp >> 1) | 0x80 : tmp >> 1);
if (!decimal) {
setZS(acc);
carry = (acc & 0x40) != 0;
overflow = ((acc & 0x40) ^ ((acc & 0x20) << 1)) != 0;
} else {
sign = carry;
zero = acc == 0;
overflow = ((tmp ^ acc) & 0x40) != 0;
if ((tmp & 0x0f) + (tmp & 0x01) > 5)
acc = acc & 0xf0 | (acc + 6) & 0x0f;
if (carry = ((tmp + (tmp & 0x10)) & 0x1f0) > 0x50)
acc += 0x60;
}
break;
case ASR: // AND + LSR
acc = acc & data;
nxtcarry = (acc & 0x01) != 0;
acc = (acc >> 1);
carry = nxtcarry;
setZS(acc);
break;
case DCP:
data = (data - 1) & 0xff;
setZS(data);
tmp = acc - data;
carry = tmp >= 0;
setZS((tmp & 0xff));
break;
case ISB:
data = (data + 1) & 0xff;
// SBC PART!
opSBCimp(data);
break;
case LAX:
acc = x = data;
setZS(acc);
break;
case LAS: // A,X,S:={adr}&S
acc = x = s = (data & s);
setZS(acc);
break;
case LXA:
x = acc = (acc | 0xee) & p1;
setZS(acc);
break;
case RLA:
data = (data << 1) + (carry ? 1 : 0);
carry = (data & 0x100) != 0;
data = data & 0xff;
// AND PART
acc = acc & data;
zero = (acc == 0);
sign = (acc > 0x7f);
break;
case RRA: // RRA ROR + ADC
nxtcarry = (data & 0x01) != 0;
data = (data >> 1) + (carry ? 0x80 : 0);
carry = nxtcarry;
// ADC PART!
opADCimp(data);
break;
case SBX:
x = ((acc & x) - p1);
carry = x >= 0;
x = x & 0xff;
setZS(x);
break;
case SHA:
data = acc & x & ((adr >> 8) + 1);
break;
case SHS:
data = acc & x & ((adr >> 8) + 1);
s = acc & x;
break;
case SHX:
data = x & ((adr >> 8) + 1);
break;
case SHY:
data = y & ((adr >> 8) + 1);
break;
case SAX:
data = acc & x;
break;
case SRE:
carry = (data & 0x01) != 0;
data = data >> 1;
// EOR PART
acc = acc ^ data;
setZS(acc);
break;
case SLO:
// ASL
setCarry(data);
data = (data << 1) & 0xff;
// Written later...
// THE ORA PART
acc = acc | data;
setZS(acc);
break;
default:
unknownInstruction(pc, op);
}
if (write) {
writeByte(adr, data);
} else if (addrMode == ACCUMULATOR) {
acc = data;
}
}
public void unknownInstruction(int pc, int op) {
System.out.println("Unknown instruction: " + op);
}
public void init(ExtChip scr) {
super.init();
installROMS();
chips = scr;
}
protected abstract void installROMS();
protected abstract void patchROM(PatchListener list);
public void hardReset() {
for (int i = 0; i < 0x10000; i++) {
memory[i] = 0;
}
reset();
}
private void doReset() {
sign = false;
zero = false;
overflow = false;
carry = false;
decimal = false;
brk = false;
disableInterupt = false;
interruptInExec = 0;
rindex = 0;
checkInterrupt = false;
NMILow = false;
NMILastLow = false;
IRQLow = false;
log("Set IRQLOW to false...");
resetFlag = false;
scheduler.empty();
chips.reset();
pc = fetchByte(0xfffc) + (fetchByte(0xfffd) << 8);
log("Reset to: " + pc);
if (list != null)
patchROM(list);
}
// Reset the MOS6510Core!!!
// This can be called with any thread!!!
public void reset() {
// Clear and copy!
// The processor flags
NMILow = false;
brk = false;
IRQLow = false;
log("Set IRQLOW to false...");
resetFlag = true;
checkInterrupt = true;
}
public void setDebug(int adr, String msg) {
if (debugInfo == null) {
debugInfo = new String[0x10000];
}
debugInfo[adr & 0xffff] = msg;
}
public String getDebug(int adr) {
if (debugInfo != null)
return debugInfo[adr & 0xffff];
return null;
}
protected void loadROM(InputStream ins, int startMem, int len) {
try {
BufferedInputStream stream = new BufferedInputStream(ins);
if (stream != null) {
byte[] charBuf = new byte[len];
int pos = 0;
int t;
try {
while((t = stream.read(charBuf, pos, len - pos)) > 0) {
pos += t;
}
monitor.info("Installing rom at :" + Integer.toString(startMem,16) + " size:" + pos);
for (int i = 0; i < charBuf.length; i++) {
memory[i + startMem] = ((int)charBuf[i]) & 0xff;
}
} catch (Exception e) {
monitor.error("Problem reading rom file ");
e.printStackTrace();
} finally {
try {
stream.close();
} catch (Exception e2) {}
}
}
} catch(Exception e) {
monitor.error("Error loading resource" + e);
}
}
void log(String s) {
if (debug)
monitor.info(getName() + " : " + s);
}
public boolean getIRQLow() {
return IRQLow;
}
}