1
0
forked from sent/waves
waves/public/assets/g/commodoreclicker/js/c64/cpu.js
2025-04-09 17:11:14 -05:00

1229 lines
48 KiB
JavaScript

define(function() {
return {
VECTOR_INT: 0xFFFE,
VECTOR_RESET: 0xFFFC,
VECTOR_NMI: 0xFFFA,
clock: null,
printedTo: null,
curOp: [],
curCycle: null,
reg: null,
halted: null,
signalled: null,
util: {
setFlag: function(flag, cond) {
if (cond) {
this.reg.P |= flag;
} else {
this.reg.P &= (255 - flag);
}
},
setNZ: function(val) {
if (val & 128) {
this.reg.P |= this.flags.N;
} else {
this.reg.P &= (255 - this.flags.N);
}
if (val == 0) {
this.reg.P |= this.flags.Z;
} else {
this.reg.P &= (255 - this.flags.Z);
}
},
branch: function(flag, val) {
if ((this.reg.P & flag) == val) {
this.reg.PC = this.reg.addr;
if (!this.reg.tmp4) {
this.reg.tmp4 = 1;
return false;
} else if ((this.reg.addr & 0xFF00) != (this.reg.tmp1 & 0xFF00)) {
if (this.reg.tmp4 == 1) {
this.reg.tmp4 = 2;
return false;
}
}
}
return true;
},
push: function(val) {
this.owner.MMU.w(this.reg.S + 0x0100, val);
this.reg.S = (this.reg.S - 1) & 0x00FF;
},
pop: function() {
this.reg.S = (this.reg.S + 1) & 0x00FF;
return this.owner.MMU.r(this.reg.S + 0x0100);
},
interrupt: function(vector, brk_flag, reset_flag) {
// This gets a bit messy, handling all the int types:
// BRK changes PC, and sets the B flag
// RESET doesn't push PC, and doesn't set I
switch (this.reg.tmp4) {
case null:
this.reg.tmp4 = 1;
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
return false;
case 1:
this.reg.tmp4 = reset_flag ? 3 : 2;
if (!brk_flag) {
// Rewind the changes made in previous cycles
this.reg.PC = (this.reg.PC - 2) & 0xFFFF;
}
if (!reset_flag) {
this.util.push.call(this, this.reg.PC >> 8);
}
return false;
case 2:
this.reg.tmp4 = 3;
if (!reset_flag) {
this.util.push.call(this, this.reg.PC & 255);
}
return false;
case 3:
this.reg.tmp4 = 4;
if (reset_flag) {
return false;
}
if (brk_flag) {
this.reg.P |= this.flags.B;
} else {
this.reg.P &= (255 - this.flags.B);
}
this.util.push.call(this, this.reg.P);
if (reset_flag) {
this.reg.P &= (255 - this.flags.I);
} else {
this.reg.P |= this.flags.I;
}
return false;
case 4:
this.reg.tmp4 = 5;
this.reg.addr = this.owner.MMU.r(vector);
return false;
case 5:
this.reg.addr += (this.owner.MMU.r(vector + 1) << 8);
this.reg.PC = this.reg.addr;
}
return true;
}
},
ops: {
ADC: function() {
var res = this.reg.operand + this.reg.A + ((this.reg.P & this.flags.C) ? 1 : 0);
if (this.reg.P & this.flags.D) {
this.util.setFlag.call(this, this.flags.Z, (res & 255) == 0);
if ((this.reg.operand & 15) + (this.reg.A & 15) + ((this.reg.P & this.flags.C) ? 1 : 0) > 9) {
res += 6;
}
this.util.setFlag.call(this, this.flags.N, res & 128);
this.util.setFlag.call(this, this.flags.V,
(!((this.reg.A ^ this.reg.operand) & 128)) &&
((this.reg.A ^ res) & 128)
);
if (res > 0x99) {
res += 0x60;
}
this.util.setFlag.call(this, this.flags.C, res > 0x99);
} else {
this.util.setNZ.call(this, res & 255);
this.util.setFlag.call(this, this.flags.V,
(!((this.reg.A ^ this.reg.operand) & 128)) &&
((this.reg.A ^ res) & 128)
);
this.util.setFlag.call(this, this.flags.C, res > 255);
}
this.reg.A = res & 255;
return true;
},
AHX: function() {
this.reg.writeflag = true;
// TODO: Undocumented
return true;
},
ALR: function() {
// TODO: Undocumented
return true;
},
ANC: function() {
// TODO: Undocumented
return true;
},
AND: function() {
this.reg.A = (this.reg.A & this.reg.operand) & 255;
this.util.setNZ.call(this, this.reg.A);
return true;
},
ARR: function() {
// TODO: Undocumented
return true;
},
ASL: function() {
this.reg.writeflag = true;
this.util.setFlag.call(this, this.flags.C, this.reg.operand & 128);
this.reg.operand = (this.reg.operand << 1) & 255;
this.util.setNZ.call(this, this.reg.operand);
return true;
},
AXS: function() {
// TODO: Undocumented
return true;
},
BCC: function() {
return this.util.branch.call(this, this.flags.C, 0);
},
BCS: function() {
return this.util.branch.call(this, this.flags.C, this.flags.C);
},
BEQ: function() {
return this.util.branch.call(this, this.flags.Z, this.flags.Z);
},
BIT: function() {
this.util.setFlag.call(this, this.flags.N, this.reg.operand & 128);
this.util.setFlag.call(this, this.flags.V, this.reg.operand & 64);
this.util.setFlag.call(this, this.flags.Z, (this.reg.operand & this.reg.A) == 0);
return true;
},
BMI: function() {
return this.util.branch.call(this, this.flags.N, this.flags.N);
},
BNE: function() {
return this.util.branch.call(this, this.flags.Z, 0);
},
BPL: function() {
return this.util.branch.call(this, this.flags.N, 0);
},
BRK: function() {
return this.util.interrupt.call(this, this.VECTOR_INT, true);
},
BVC: function() {
return this.util.branch.call(this, this.flags.V, 0);
},
BVS: function() {
return this.util.branch.call(this, this.flags.V, this.flags.V);
},
CLC: function() {
this.reg.P &= (255 - this.flags.C);
return true;
},
CLD: function() {
this.reg.P &= (255 - this.flags.D);
return true;
},
CLI: function() {
this.reg.P &= (255 - this.flags.I);
return true;
},
CLV: function() {
this.reg.P &= (255 - this.flags.V);
return true;
},
CMP: function() {
var res = (this.reg.A - this.reg.operand) & 511;
this.util.setFlag.call(this, this.flags.C, res < 256);
this.util.setNZ.call(this, res & 255);
return true;
},
CPX: function() {
var res = (this.reg.X - this.reg.operand) & 511;
this.util.setFlag.call(this, this.flags.C, res < 256);
this.util.setNZ.call(this, res & 255);
return true;
},
CPY: function() {
var res = (this.reg.Y - this.reg.operand) & 511;
this.util.setFlag.call(this, this.flags.C, res < 256);
this.util.setNZ.call(this, res & 255);
return true;
},
DCP: function() {
this.reg.writeflag = true;
// TODO: Undocumented
return true;
},
DEC: function() {
this.reg.writeflag = true;
this.reg.operand = (this.reg.operand - 1) & 255;
this.util.setNZ.call(this, this.reg.operand);
return true;
},
DEX: function() {
this.reg.X = (this.reg.X - 1) & 255;
this.util.setNZ.call(this, this.reg.X);
return true;
},
DEY: function() {
this.reg.Y = (this.reg.Y - 1) & 255;
this.util.setNZ.call(this, this.reg.Y);
return true;
},
EOR: function() {
this.reg.A = (this.reg.A ^ this.reg.operand) & 255;
this.util.setNZ.call(this, this.reg.A);
return true;
},
HLT: function() {
this.halted = true;
return true;
},
INC: function() {
this.reg.writeflag = true;
this.reg.operand = (this.reg.operand + 1) & 255;
this.util.setNZ.call(this, this.reg.operand);
return true;
},
INT: function() {
return this.util.interrupt.call(this, this.VECTOR_INT);
},
INX: function() {
this.reg.X = (this.reg.X + 1) & 255;
this.util.setNZ.call(this, this.reg.X);
return true;
},
INY: function() {
this.reg.Y = (this.reg.Y + 1) & 255;
this.util.setNZ.call(this, this.reg.Y);
return true;
},
ISC: function() {
this.reg.writeflag = true;
// TODO: Undocumented
return true;
},
JMP: function() {
this.reg.PC = this.reg.addr;
return true;
},
JSR: function() {
switch (this.reg.tmp4) {
case null:
this.reg.tmp4 = 1;
this.reg.tmp3 = (this.reg.PC - 1) & 0xFFFF;
this.util.push.call(this, this.reg.tmp3 >> 8);
return false;
case 1:
this.reg.tmp4 = 2;
this.util.push.call(this, this.reg.tmp3 & 255);
return false;
case 2:
this.reg.PC = this.reg.addr;
}
return true;
},
LAS: function() {
// TODO: Undocumented
return true;
},
LAX: function() {
// TODO: Undocumented
return true;
},
LDA: function() {
this.reg.A = this.reg.operand;
this.util.setNZ.call(this, this.reg.A);
return true;
},
LDX: function() {
this.reg.X = this.reg.operand;
this.util.setNZ.call(this, this.reg.X);
return true;
},
LDY: function() {
this.reg.Y = this.reg.operand;
this.util.setNZ.call(this, this.reg.Y);
return true;
},
LSR: function() {
this.reg.writeflag = true;
this.util.setFlag.call(this, this.flags.C, this.reg.operand & 1);
this.reg.operand >>= 1;
this.util.setNZ.call(this, this.reg.operand);
return true;
},
NMI: function() {
return this.util.interrupt.call(this, this.VECTOR_NMI);
},
NOP: function() {
// lol
return true;
},
ORA: function() {
this.reg.A = (this.reg.A | this.reg.operand) & 255;
this.util.setNZ.call(this, this.reg.A);
return true;
},
PHA: function() {
if (!this.reg.tmp3) {
this.reg.tmp3 = 1;
return false;
}
this.util.push.call(this, this.reg.A);
return true;
},
PHP: function() {
if (!this.reg.tmp3) {
this.reg.tmp3 = 1;
return false;
}
this.util.push.call(this, this.reg.P);
return true;
},
PLA: function() {
switch (this.reg.tmp4) {
case null:
this.reg.tmp4 = 1;
return false;
case 1:
this.reg.tmp4 = 2;
return false;
case 2:
this.reg.A = this.util.pop.call(this);
this.util.setNZ.call(this, this.reg.A);
}
return true;
},
PLP: function() {
switch (this.reg.tmp4) {
case null:
this.reg.tmp4 = 1;
return false;
case 1:
this.reg.tmp4 = 2;
return false;
case 2:
this.reg.P = this.util.pop.call(this);
}
return true;
},
RLA: function() {
this.reg.writeflag = true;
// TODO: Undocumented
return true;
},
ROL: function() {
this.reg.writeflag = true;
this.reg.operand <<= 1;
if (this.reg.P & this.flags.C) {
this.reg.operand |= 1;
}
this.util.setFlag.call(this, this.flags.C, this.reg.operand & 256);
this.reg.operand &= 255;
this.util.setNZ.call(this, this.reg.operand);
return true;
},
ROR: function() {
this.reg.writeflag = true;
if (this.reg.P & this.flags.C) {
this.reg.operand |= 256;
}
this.util.setFlag.call(this, this.flags.C, this.reg.operand & 1);
this.reg.operand >>= 1;
this.util.setNZ.call(this, this.reg.operand);
return true;
},
RRA: function() {
this.reg.writeflag = true;
// TODO: Undocumented
return true;
},
RST: function() {
return this.util.interrupt.call(this, this.VECTOR_RESET, false, true);
},
RTI: function() {
switch (this.reg.tmp4) {
case null:
this.reg.tmp4 = 1;
return false;
case 1:
this.reg.tmp4 = 2;
return false;
case 2:
this.reg.tmp4 = 3;
this.reg.P = this.util.pop.call(this);
return false;
case 3:
this.reg.tmp4 = 4;
this.reg.addr = this.util.pop.call(this);
return false;
case 4:
this.reg.addr += (this.util.pop.call(this) << 8);
this.reg.PC = this.reg.addr;
}
return true;
},
RTS: function() {
switch (this.reg.tmp4) {
case null:
this.reg.tmp4 = 1;
return false;
case 1:
this.reg.tmp4 = 2;
return false;
case 2:
this.reg.tmp4 = 3;
this.reg.addr = this.util.pop.call(this);
return false;
case 3:
this.reg.tmp4 = 4;
this.reg.addr += (this.util.pop.call(this) << 8);
return false;
case 4:
this.reg.PC = (this.reg.addr + 1) & 0xFFFF;
}
return true;
},
SAX: function() {
// TODO: Undocumented
return true;
},
SBC: function() {
var res = this.reg.A - this.reg.operand - ((this.reg.P & this.flags.C) ? 0 : 1);
this.util.setNZ.call(this, res & 255);
this.util.setFlag.call(this, this.flags.V,
((this.reg.A ^ this.reg.operand) & 128) &&
((this.reg.A ^ res) & 128)
);
if (this.reg.P & this.flags.D) {
if ((this.reg.A & 15) - ((this.reg.P & this.flags.C) ? 0 : 1) < (this.reg.operand & 15)) {
res -= 6;
}
if (res > 0x99) {
res -= 0x60;
}
}
this.util.setFlag.call(this, this.flags.C, !(res & 256));
this.reg.A = res & 255;
return true;
},
SEC: function() {
this.reg.P |= this.flags.C;
return true;
},
SED: function() {
this.reg.P |= this.flags.D;
return true;
},
SEI: function() {
this.reg.P |= this.flags.I;
return true;
},
SHX: function() {
this.reg.writeflag = true;
// TODO: Undocumented
return true;
},
SHY: function() {
this.reg.writeflag = true;
// TODO: Undocumented
return true;
},
SLO: function() {
this.reg.writeflag = true;
// TODO: Undocumented
return true;
},
SRE: function() {
this.reg.writeflag = true;
// TODO: Undocumented
return true;
},
STA: function() {
this.reg.writeflag = true;
this.reg.writeonly = true;
this.reg.operand = this.reg.A;
return true;
},
STX: function() {
this.reg.writeflag = true;
this.reg.writeonly = true;
this.reg.operand = this.reg.X;
return true;
},
STY: function() {
this.reg.writeflag = true;
this.reg.writeonly = true;
this.reg.operand = this.reg.Y;
return true;
},
TAS: function() {
// TODO: Undocumented
return true;
},
TAX: function() {
this.reg.X = this.reg.A;
this.util.setNZ.call(this, this.reg.X);
return true;
},
TAY: function() {
this.reg.Y = this.reg.A;
this.util.setNZ.call(this, this.reg.Y);
return true;
},
TSX: function() {
this.reg.X = this.reg.S;
this.util.setNZ.call(this, this.reg.X);
return true;
},
TXA: function() {
this.reg.A = this.reg.X;
this.util.setNZ.call(this, this.reg.A);
return true;
},
TXS: function() {
this.reg.S = this.reg.X;
return true;
},
TYA: function() {
this.reg.A = this.reg.Y;
this.util.setNZ.call(this, this.reg.A);
return true;
},
XAA: function() {
// TODO: Undocumented
return true;
}
},
addr: {
imp: function() {
// Nothing to do except suck up 1 cycle
return true;
},
imp_w: function() {
return true;
},
acc: function() {
this.reg.operand = this.reg.A;
return true;
},
acc_w: function() {
this.reg.A = this.reg.operand;
return true;
},
imm: function() {
if (this.curOp.length == 1) {
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.operand = this.curOp[1];
}
return true;
},
imm_w: function() {
return true;
},
z: function() {
switch (this.curOp.length) {
case 1:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.addr = this.curOp[1];
return false;
case 2:
if (this.reg.operand === null) {
this.reg.operand = this.owner.MMU.r(this.reg.addr);
}
return true;
}
},
z_w: function() {
if (!this.reg.writeonly && this.curCycle < 5) {
return false;
}
this.owner.MMU.w(this.reg.addr, this.reg.operand);
return true;
},
zx: function() {
switch (this.curOp.length) {
case 1:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp1 = this.curOp[1];
return false;
case 2:
if (this.reg.addr === null) {
this.reg.tmp1 += this.reg.X;
this.reg.addr = this.reg.tmp1 & 0xFF;
return false;
}
if (this.reg.operand === null) {
this.reg.operand = this.owner.MMU.r(this.reg.addr);
}
return true;
}
},
zx_w: function() {
if (!this.reg.writeonly && this.curCycle < 6) {
return false;
}
this.owner.MMU.w(this.reg.addr, this.reg.operand);
return true;
},
zy: function() {
switch (this.curOp.length) {
case 1:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp1 = this.curOp[1];
return false;
case 2:
if (this.reg.addr === null) {
this.reg.tmp1 += this.reg.Y;
this.reg.addr = this.reg.tmp1 & 0xFF;
return false;
}
if (this.reg.operand === null) {
this.reg.operand = this.owner.MMU.r(this.reg.addr);
}
return true;
}
},
zy_w: function() {
if (!this.reg.writeonly && this.curCycle < 6) {
return false;
}
this.owner.MMU.w(this.reg.addr, this.reg.operand);
return true;
},
abs: function() {
switch (this.curOp.length) {
case 1:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp1 = this.curOp[1];
return false;
case 2:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.addr = (this.curOp[2] << 8) + this.reg.tmp1;
if (
this.map[this.curOp[0]][0] == 'JMP' ||
this.map[this.curOp[0]][0] == 'JSR'
) {
// Operand not required
return true;
}
return false;
case 3:
if (this.reg.operand === null) {
this.reg.operand = this.owner.MMU.r(this.reg.addr);
}
return true;
}
},
abs_w: function() {
if (!this.reg.writeonly && this.curCycle < 6) {
return false;
}
this.owner.MMU.w(this.reg.addr, this.reg.operand);
return true;
},
abx: function() {
switch (this.curOp.length) {
case 1:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp1 = this.curOp[1];
return false;
case 2:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp2 = (this.curOp[2] << 8) + this.reg.tmp1;
return false;
case 3:
if (this.reg.addr === null) {
this.reg.addr = (this.reg.tmp2 + this.reg.X) & 0xFFFF;
if ((this.reg.tmp2 & 0xFF00) != (this.reg.addr & 0xFF00)) {
// Page boundary, add a cycle
return false;
}
}
if (this.reg.operand === null) {
this.reg.operand = this.owner.MMU.r(this.reg.addr);
}
return true;
}
},
abx_w: function() {
if (this.curCycle < (this.reg.writeonly ? 5 : 7)) {
return false;
}
this.owner.MMU.w(this.reg.addr, this.reg.operand);
return true;
},
aby: function() {
switch (this.curOp.length) {
case 1:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp1 = this.curOp[1];
return false;
case 2:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp2 = (this.curOp[2] << 8) + this.reg.tmp1;
return false;
case 3:
if (this.reg.addr === null) {
this.reg.addr = (this.reg.tmp2 + this.reg.Y) & 0xFFFF;
if ((this.reg.tmp2 & 0xFF00) != (this.reg.addr & 0xFF00)) {
// Page boundary, add a cycle
return false;
}
}
if (this.reg.operand === null) {
this.reg.operand = this.owner.MMU.r(this.reg.addr);
}
return true;
}
},
aby_w: function() {
if (this.curCycle < (this.reg.writeonly ? 5 : 7)) {
return false;
}
this.owner.MMU.w(this.reg.addr, this.reg.operand);
return true;
},
ind: function() {
switch (this.curOp.length) {
case 1:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp1 = this.curOp[1];
return false;
case 2:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp2 = (this.curOp[2] << 8) + this.reg.tmp1;
return false;
case 3:
if (this.reg.tmp3 === null) {
this.reg.tmp3 = this.owner.MMU.r(this.reg.tmp2);
return false;
}
if (this.reg.addr === null) {
this.reg.addr = (this.owner.MMU.r(this.reg.tmp2 + 1) << 8) + this.reg.tmp3;
}
return true;
}
},
ind_w: function() {
// No such thing as a write here
return true;
},
rel: function() {
if (this.curOp.length == 1) {
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp1 = this.reg.PC;
return false;
}
this.reg.operand = this.curOp[1];
this.reg.addr = (this.curOp[1] & 128)
? -(256 - this.curOp[1])
: this.curOp[1];
this.reg.addr += this.reg.tmp1;
return true;
},
rel_w: function() {
// No such thing as a write here
return true;
},
izx: function() {
switch (this.curOp.length) {
case 1:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp1 = this.curOp[1];
return false;
case 2:
if (this.reg.tmp2 === null) {
// Redundant fetch
this.reg.tmp2 = this.owner.MMU.r(this.reg.tmp1);
this.reg.tmp1 = (this.reg.tmp1 + this.reg.X) & 0xFF;
return false;
}
if (this.reg.tmp3 === null) {
this.reg.tmp3 = this.owner.MMU.r(this.reg.tmp1);
return false;
}
if (this.reg.addr === null) {
this.reg.addr = (this.owner.MMU.r((this.reg.tmp1 + 1) & 0xFF) << 8) + this.reg.tmp3;
return false;
}
if (this.reg.operand === null) {
this.reg.operand = this.owner.MMU.r(this.reg.addr);
}
return true;
}
},
izx_w: function() {
if (this.curCycle < (this.reg.writeonly ? 6 : 8)) {
return false;
}
this.owner.MMU.w(this.reg.addr, this.reg.operand);
return true;
},
izy: function() {
switch (this.curOp.length) {
case 1:
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.reg.tmp1 = this.curOp[1];
return false;
case 2:
if (this.reg.tmp2 === null) {
this.reg.tmp2 = this.owner.MMU.r(this.reg.tmp1);
return false;
}
if (this.reg.tmp3 === null) {
this.reg.tmp3 = (this.owner.MMU.r((this.reg.tmp1 + 1) & 0xFF) << 8) + this.reg.tmp2;
return false;
}
if (this.reg.addr === null) {
this.reg.addr = (this.reg.tmp3 + this.reg.Y) & 0xFFFF;
if ((this.reg.tmp3 & 0xFF00) != (this.reg.addr & 0xFF00)) {
// Page boundary crossed
return false;
}
}
if (this.reg.operand === null) {
this.reg.operand = this.owner.MMU.r(this.reg.addr);
}
return true;
}
},
izy_w: function() {
if (this.curCycle < (this.reg.writeonly ? 6 : 8)) {
return false;
}
this.owner.MMU.w(this.reg.addr, this.reg.operand);
return true;
}
},
disasmHandlers: {
imp: function(op) {
return '';
},
acc: function(op) {
return 'a';
},
imm: function(op) {
return '#$' + this.pad(op[1], '0', 2);
},
z: function(op) {
return '$' + this.pad(op[1], '0', 2);
},
zx: function(op) {
return '$' + this.pad(op[1], '0', 2) + ',x';
},
zy: function(op) {
return '$' + this.pad(op[1], '0', 2) + ',y';
},
abs: function(op) {
return '$' + this.pad(op[1] + (op[2] << 8), '000', 4);
},
abx: function(op) {
return '$' + this.pad(op[1] + (op[2] << 8), '000', 4) + ',x';
},
aby: function(op) {
return '$' + this.pad(op[1] + (op[2] << 8), '000', 4) + ',y';
},
ind: function(op) {
return '($' + this.pad(op[1] + (op[2] << 8), '000', 4) + ')';
},
rel: function(op) {
if (this.reg.addr) {
return '$' + this.pad(this.reg.addr, '000', 4);
} else {
if (op[1] & 128) {
return '-' + (((~op[1]) + 1) & 127);
} else {
return '+' + op[1];
}
}
},
izy: function(op) {
return '($' + this.pad(op[1], '0', 2) + '),Y';
},
izx: function(op) {
return '($' + this.pad(op[1], '0', 2) + ',X)';
}
},
map: [
['BRK','imp',1,7],['ORA','izx',2,6],['HLT','imp',1,2],['SLO','izx',2,8],
['NOP','z', 2,3],['ORA','z', 2,3],['ASL','z', 2,5],['SLO','z', 2,5],
['PHP','imp',1,3],['ORA','imm',2,2],['ASL','acc',1,2],['ANC','imm',2,2],
['NOP','abs',3,4],['ORA','abs',3,4],['ASL','abs',3,6],['SLO','abs',3,6],
['BPL','rel',2,2],['ORA','izy',2,5],['HLT','imp',1,2],['SLO','izy',2,8],
['NOP','zx', 2,4],['ORA','zx', 2,4],['ASL','zx', 2,6],['SLO','zx', 2,6],
['CLC','imp',1,2],['ORA','aby',3,4],['NOP','imp',1,2],['SLO','aby',3,7],
['NOP','abx',3,4],['ORA','abx',3,4],['ASL','abx',3,7],['SLO','abx',3,7],
['JSR','abs',3,6],['AND','izx',2,6],['HLT','imp',1,2],['RLA','izx',2,8],
['BIT','z' ,2,3],['AND','z' ,2,3],['ROL','z', 2,5],['RLA','z', 2,5],
['PLP','imp',1,4],['AND','imm',2,2],['ROL','acc',1,2],['ANC','imm',2,2],
['BIT','abs',3,4],['AND','abs',3,4],['ROL','abs',3,6],['RLA','abs',3,6],
['BMI','rel',2,2],['AND','izy',2,5],['HLT','imp',1,2],['RLA','izy',2,8],
['NOP','zx', 2,4],['AND','zx', 2,4],['ROL','zx', 2,6],['RLA','zx', 2,6],
['SEC','imp',1,2],['AND','aby',3,4],['NOP','imp',1,2],['RLA','aby',3,7],
['NOP','abx',3,4],['AND','abx',3,4],['ROL','abx',3,7],['RLA','abx',3,7],
['RTI','imp',1,6],['EOR','izx',2,6],['HLT','imp',1,2],['SRE','izx',2,8],
['NOP','z', 2,3],['EOR','z', 2,3],['LSR','z', 2,5],['SRE','z', 2,5],
['PHA','imp',1,3],['EOR','imm',2,2],['LSR','acc',1,2],['ALR','imm',2,2],
['JMP','abs',3,3],['EOR','abs',3,4],['LSR','abs',3,6],['SRE','abs',3,6],
['BVC','rel',2,2],['EOR','izy',2,5],['HLT','imp',1,2],['SRE','izy',2,8],
['NOP','zx', 2,4],['EOR','zx', 2,4],['LSR','zx', 2,6],['SRE','zx', 2,6],
['CLI','imp',1,2],['EOR','aby',3,4],['NOP','imp',1,2],['SRE','aby',3,7],
['NOP','abx',3,4],['EOR','abx',3,4],['LSR','abx',3,7],['SRE','abx',3,7],
['RTS','imp',1,6],['ADC','izx',2,6],['HLT','imp',1,2],['RRA','izx',2,8],
['NOP','z', 2,3],['ADC','z', 2,3],['ROR','z', 2,5],['RRA','z', 2,5],
['PLA','imp',1,4],['ADC','imm',2,2],['ROR','acc',1,2],['ARR','imm',2,2],
['JMP','ind',3,5],['ADC','abs',3,4],['ROR','abs',3,6],['RRA','abs',3,6],
['BVS','rel',2,2],['ADC','izy',2,5],['HLT','imp',1,2],['RRA','izy',2,8],
['NOP','zx', 2,4],['ADC','zx', 2,4],['ROR','zx', 2,6],['RRA','zx', 2,6],
['SEI','imp',1,2],['ADC','aby',3,4],['NOP','imp',1,2],['RRA','aby',3,7],
['NOP','abx',3,4],['ADC','abx',3,4],['ROR','abx',3,7],['RRA','abx',3,7],
['NOP','imm',2,2],['STA','izx',2,6],['NOP','imm',2,2],['SAX','izx',2,6],
['STY','z', 2,3],['STA','z', 2,3],['STX','z', 2,3],['SAX','z', 2,3],
['DEY','imp',1,2],['NOP','imm',2,2],['TXA','imp',1,2],['XAA','imm',2,2],
['STY','abs',3,4],['STA','abs',3,4],['STX','abs',3,4],['SAX','abs',3,4],
['BCC','rel',2,2],['STA','izy',2,6],['HLT','imp',1,2],['AHX','izy',2,6],
['STY','zx', 2,4],['STA','zx', 2,4],['STX','zy', 2,4],['SAX','zy', 2,4],
['TYA','imp',1,2],['STA','aby',3,5],['TXS','imp',1,2],['TAS','aby',3,5],
['SHY','abx',3,5],['STA','abx',3,5],['SHX','abx',3,5],['AHX','abx',3,5],
['LDY','imm',2,2],['LDA','izx',2,6],['LDX','imm',2,2],['LAX','izx',2,6],
['LDY','z', 2,3],['LDA','z', 2,3],['LDX','z', 2,3],['LAX','z', 2,3],
['TAY','imp',1,2],['LDA','imm',2,2],['TAX','imp',1,2],['LAX','imm',2,2],
['LDY','abs',3,4],['LDA','abs',3,4],['LDX','abs',3,4],['LAX','abs',3,4],
['BCS','rel',2,2],['LDA','izy',2,5],['HLT','imp',1,2],['LAX','izy',2,5],
['LDY','zx', 2,4],['LDA','zx', 2,4],['LDX','zy', 2,4],['LAX','zy', 2,4],
['CLV','imp',1,2],['LDA','aby',3,4],['TSX','imp',1,2],['LAS','aby',3,4],
['LDY','abx',3,4],['LDA','abx',3,4],['LDX','aby',3,4],['LAX','aby',3,4],
['CPY','imm',2,2],['CMP','izx',2,6],['NOP','imm',2,2],['DCP','izx',2,8],
['CPY','z', 2,3],['CMP','z', 2,3],['DEC','z', 2,5],['DCP','z', 2,5],
['INY','imp',1,2],['CMP','imm',2,2],['DEX','imp',1,2],['AXS','imm',2,2],
['CPY','abs',3,4],['CMP','abs',3,4],['DEC','abs',3,6],['DCP','abs',3,6],
['BNE','rel',2,2],['CMP','izy',2,5],['HLT','imp',1,2],['DCP','izy',2,8],
['NOP','zx', 2,4],['CMP','zx', 2,4],['DEC','zx', 2,6],['DCP','zx', 2,6],
['CLD','imp',1,2],['CMP','aby',3,4],['NOP','imp',1,2],['DCP','aby',3,7],
['NOP','abx',3,4],['CMP','abx',3,4],['DEC','abx',3,7],['DCP','abx',3,7],
['CPX','imm',2,2],['SBC','izx',2,6],['NOP','imm',1,2],['ISC','izx',2,8],
['CPX','z', 2,3],['SBC','z', 2,3],['INC','z', 2,5],['ISC','z', 2,5],
['INX','imp',1,2],['SBC','imm',2,2],['NOP','imp',1,2],['SBC','imm',2,2],
['CPX','abs',3,4],['SBC','abs',3,4],['INC','abs',3,6],['ISC','abs',3,6],
['BEQ','rel',2,2],['SBC','izy',2,5],['HLT','imp',1,2],['ISC','izy',2,8],
['NOP','zx', 2,4],['SBC','zx', 2,4],['INC','zx', 2,6],['ISC','zx', 2,6],
['SED','imp',1,2],['SBC','aby',3,4],['NOP','imp',1,2],['ISC','aby',3,7],
['NOP','abx',3,4],['SBC','abx',3,4],['INC','abx',3,7],['ISC','abx',3,7],
['INT','imp',1,7],['RST','imp',1,6],['NMI','imp',1,7]
],
flags: {
N: 128,
V: 64,
B: 16,
D: 8,
I: 4,
Z: 2,
C: 1
},
interruptSignals: {
'INT': 256,
'RST': 257,
'NMI': 258
},
step: function() {
this.clock++;
var op; // [opcode, addr, size, time]
if (this.halted) {
return;
}
if (this.owner.MMU.busLock) {
if (this.owner.game.debug) {
console.log('BUS LOCK: ' + this.owner.MMU.busLock);
}
this.owner.MMU.busLock--;
return;
}
if (this.clock == 1) {
this.signal('RST');
}
this.curCycle++;
if (this.curOp.length == 0) {
this.reg.origPC = this.reg.PC;
if (this.signalled && !(this.reg.P & this.flags.I)) {
this.curOp.push(this.signalled);
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
this.signalled = null;
} else {
this.curOp.push(this.owner.MMU.r(this.reg.PC));
this.reg.PC = (this.reg.PC + 1) & 0xFFFF;
}
} else {
op = this.map[this.curOp[0]];
if (this.addr[op[1]].call(this)) {
if (!this.reg.operated) {
this.reg.operated = this.ops[op[0]].call(this);
}
if (this.reg.operated) {
if (this.owner.game.debug && !this.reg.printed) {
if (this.clock > this.printedTo) {
this.reg.printed = true;
console.log(this.debugString());
this.printedTo = this.clock;
}
}
if (this.reg.writeflag) {
if (this.addr[op[1] + '_w'].call(this)) {
this.resetOp();
}
} else {
this.resetOp();
}
}
}
}
},
disasm: function(pc, len) {
len = len || 64;
var i = 0, j, op = [], prevPC;
do {
prevPC = pc;
op.length = 0;
op.push(this.owner.MMU.r(pc++));
for (j = 1; j < this.map[op[0]][2]; j++) {
op.push(this.owner.MMU.r(pc++));
}
i += op.length;
console.warn([
this.pad(prevPC, '000', 4),
this.map[op[0]][0],
this.disasmHandlers[this.map[op[0]][1]].call(this, op).toUpperCase()
].join(' '));
} while (i < len);
},
debugString: function() {
var i, opcodes = '',
operand = this.disasmHandlers[this.map[this.curOp[0]][1]].call(this, this.curOp).toUpperCase();
for (i = 0; i < this.curOp.length; i++) {
opcodes += this.pad(this.curOp[i], '0', 2, true);
opcodes += ' ';
}
while (opcodes.length < 12) {
opcodes += ' ';
}
while (operand.length < 10) {
operand += ' ';
}
return [
'.C:' + this.pad(this.reg.origPC, '000', 4),
' ' + opcodes,
this.map[this.curOp[0]][0], operand, '-',
"A:" + this.pad(this.reg.A, '0', 2, true),
"X:" + this.pad(this.reg.X, '0', 2, true),
"Y:" + this.pad(this.reg.Y, '0', 2, true),
"S:" + this.pad(this.reg.S, '0', 2),
(
((this.reg.P & this.flags.N) ? 'N' : '.') +
((this.reg.P & this.flags.V) ? 'V' : '.') +
'-' +
((this.reg.P & this.flags.B) ? 'B' : '.') +
((this.reg.P & this.flags.D) ? 'D' : '.') +
((this.reg.P & this.flags.I) ? 'I' : '.') +
((this.reg.P & this.flags.Z) ? 'Z' : '.') +
((this.reg.P & this.flags.C) ? 'C' : '.')
), ' ',
this.clock
].join(' ');
},
pad: function(val, padder, len, upper) {
return (padder + val.toString(16))[upper ? 'toUpperCase' : 'toLowerCase']().slice(-len);
},
signal: function(line) {
this.signalled = this.interruptSignals[line] || null;
},
getState: function() {
return {
clock: this.clock,
curOp: this.curOp.slice(0),
curCycle: 0 + this.curCycle,
reg: $.extend({}, this.reg)
};
},
setState: function(state) {
this.clock = state.clock;
this.curOp = state.curOp.slice(0);
this.curCycle = state.curCycle;
this.reg = $.extend({}, state.reg);
},
reset: function() {
this.reg = {
PC: 0,
A: 0,
X: 0,
Y: 0,
S: 0,
P: 0x20,
// Might be useful for intermediate addressing steps
operated: false,
writeflag: false,
writeonly: false,
printed: false,
operand: null,
origPC: null,
addr: null,
tmp1: null,
tmp2: null,
tmp3: null,
tmp4: null
};
this.clock = 0;
this.printedTo = 0;
this.halted = false;
this.resetOp();
},
resetOp: function() {
this.curOp.length = 0;
this.curCycle = 0;
this.reg.operated = false;
this.reg.writeflag = false;
this.reg.writeonly = false;
this.reg.printed = false;
this.reg.operand = null;
this.reg.origPC = null;
this.reg.addr = null;
this.reg.tmp1 = null;
this.reg.tmp2 = null;
this.reg.tmp3 = null;
this.reg.tmp4 = null;
},
init: function() {
this.reset();
}
};
});