@@ -1,4 +1,4 @@ | |||
var mos6502 = require("./src/MOS6502.js"); | |||
var mos6502 = require("./src/chip/MOS6502"); | |||
var asm = new mos6502.Assembler(); | |||
var src = "DEFINE TOPNT $44\n"; |
@@ -1,739 +0,0 @@ | |||
/* | |||
* Emulate a basic 6502 (MOS) chip. | |||
*/ | |||
var Memory = require('./memory.js'); | |||
class CPU{ | |||
constructor(){ | |||
// Registers | |||
this.__PC = 0; // Program Counter (16 bit) | |||
this.__IRQ = 0; // IRQ interrupt address code (16 bit) | |||
this.__SR = 0; // Status Register (8 bit) | |||
this.__XR = 0; // X Register (8 bit) | |||
this.__YR = 0; // Y Register (8 bit) | |||
this.__AR = 0; // Accumulator Register (8 bit) | |||
// Variables to watch for Interrupts. | |||
this.__nmi = false; | |||
this.__irq = false; | |||
// Memory module or controller. | |||
this.__mem = null; // Must be explicitly attached. | |||
// Hold any created CLK instances. | |||
this.__clkfn = null; | |||
} | |||
set NMI(n){ | |||
this.__nmi = (n === true); | |||
} | |||
set IRQ(q){ | |||
// TODO: Verify this. | |||
// TODO: Do not set if the interrupt flag is off. | |||
this.__irq = (q === true); | |||
} | |||
reset(){ | |||
// TODO: Read memory address $FFFC - FFFD to find PC start location. | |||
// TODO: Reset status registers that get changed on a reset. | |||
} | |||
clk(){ | |||
if (this.__clkfn === null){ | |||
this.__clkfn = (function(){ | |||
// TODO: All the work!! | |||
}).bind(this); | |||
} | |||
return this.__clkfn; | |||
} | |||
memory(mem){ | |||
if (!(mem instanceof Memory)) | |||
throw new ValueError("Expected Memory instance object."); | |||
this.__mem = mem; | |||
} | |||
} | |||
// --------------------------------------------------------------------- | |||
// --------------------------------------------------------------------- | |||
function toHexString(v, l){ | |||
l = (typeof(l) === 'number' && (l === 8 || l === 16)) ? l : 8; | |||
var r = v.toString(16); | |||
if (r.length < 2) | |||
r = "0" + r; | |||
if (l === 16 && r.length < 4) | |||
r = ((r.length === 2) ? "00" : "0") + r; | |||
return r; | |||
} | |||
function tokenize(l){ | |||
var m = l.match(/([^()]+)|([()])/g); | |||
var ip = false; | |||
var t = []; | |||
m.forEach((mi)=>{ | |||
if (mi === "("){ | |||
ip = true; | |||
} else if (mi === ")"){ | |||
ip = false; | |||
} else { | |||
if (ip){ | |||
mi.split(/[\s,]+/).forEach((i)=>{ | |||
if (i !== "") | |||
t.push("+" + i); | |||
}); | |||
} else { | |||
mi.split(/[\s,]+/).forEach((i)=>{ | |||
if (i !== "") | |||
t.push(i); | |||
}); | |||
} | |||
} | |||
}); | |||
return t; | |||
} | |||
class Assembler{ | |||
constructor(initpc){ | |||
// Labels that hold variable values. | |||
this.__varlabels = {}; | |||
// Labels that hold jump/branch locations. | |||
this.__jmplabels = {}; | |||
// Inital value for program counter. Used by the reset() method. | |||
this.__initPC = (typeof(initpc) === 'number' && initpc >= 0) ? initpc : 0; | |||
// Program counter to track where in the code/memory a | |||
// operation is located. | |||
this.__PC = this.__initPC; | |||
this.__result = []; | |||
} | |||
get PC(){return this.__PC;} | |||
get variables(){ | |||
var v = {}; | |||
Object.keys(this.__varlabels).forEach((lbl)=>{ | |||
if (this.__varlabels.hasOwnProperty(lbl)) | |||
v[lbl] = this.__varlabels[lbl]; | |||
}); | |||
return v; | |||
} | |||
get jumplabels(){ | |||
var j = {}; | |||
Object.keys(this.__jmplabels).forEach((lbl)=>{ | |||
if (this.__jmplabels.hasOwnProperty(lbl)) | |||
j[lbl] = this.__jmplabels[lbl]; | |||
}); | |||
return j; | |||
} | |||
__StoreVarLabel(lbl, val){ | |||
if (lbl in this.__varlabels) | |||
throw new Error("Variable label '" + lbl + "' defined more than once."); | |||
if (lbl in this.__jmplabels) | |||
throw new Error("Variable label '" + lbl + "' matches already existing jump label."); | |||
let v = Number.NaN; | |||
if (val[0] === "$"){ | |||
if (val.length >= 2 && val.length <= 5) | |||
v = parseInt(val.substr(1), 16); | |||
} else if (val[0] === "#"){ | |||
if (val.startsWith("#b")){ | |||
v = parseInt(val.substr(2), 2); | |||
} else { | |||
v = parseInt(val); | |||
} | |||
} | |||
if (!isNaN(v)){ | |||
if (v < 0 || v > 65535) | |||
throw new Error("Variable '" + lbl + "' value is out of bounds for this processor."); | |||
this.__varlabels[lbl] = v; | |||
} else { | |||
throw new Error("Variable '" + lbl + "' value malformed."); | |||
} | |||
} | |||
__StoreJmpLabel(lbl){ | |||
if (lbl in this.__jmplabels) | |||
throw new Error("Jump label '" + lbl + "' defined more than once at program address " + toHexString(this.__PC)); | |||
if (lbl in this.__varlabels) | |||
throw new Error("Jump label '" + lbl + "' matches already existing variable name at program address " + toHexString(this.__PC)); | |||
this.__jmplabels[lbl] = this.__PC; | |||
} | |||
__AddrModeVal(tA, tB){ | |||
var mode = ""; | |||
var v = Number.NaN; | |||
if (tA[0] === '#'){ | |||
mode = "i"; // Immediate | |||
if (tA[1] === '$'){ | |||
v = parseInt(tA.substr(2), 16); | |||
} else if (tA[1] === 'b'){ | |||
v = parseInt(tA.substr(2), 2); | |||
} else { | |||
v = parseInt(tA.substr(1)); | |||
if (isNaN(v)){ | |||
let lbl = tA.substr(1); | |||
if (lbl in this.__varlabels){ | |||
if (this.__varlabels[lbl] < 256) | |||
v = this.__varlabels[lbl] | |||
} | |||
} | |||
} | |||
} else if (tA[0] === '$'){ | |||
v = parseInt(tA.substr(1), 16); | |||
if (tA.length === 3){ | |||
// Zero Page | |||
if (tB !== null){ | |||
if (tB === "X") | |||
mode = "zX"; | |||
if (tB === "Y") | |||
mode = "zY"; | |||
} else { | |||
mode = "z"; | |||
} | |||
} else if (tA.length === 5){ | |||
// Absolute | |||
if (tB !== null){ | |||
if (tB === "X"){ | |||
mode = "aX"; | |||
} else if (tB === "Y"){ | |||
mode = "aY"; | |||
} | |||
} else { | |||
mode = "a"; | |||
} | |||
} | |||
} else if (tA.startsWith("+")){ | |||
if (tB !== null){ | |||
if (tB === "+X"){ // The plus exists due to how tokenize() works. | |||
mode = "nX"; | |||
} else if (tB === "Y"){ | |||
mode = "nY"; | |||
} | |||
if (mode !== ""){ | |||
if (tA.startsWith("+$")){ | |||
v = parseInt(tA.substr(2), 16); | |||
} else { | |||
let lbl = tA.substr(1); | |||
if (lbl in this.__varlabels){ | |||
if (this.__varlabels[lbl] < 256) | |||
v = this.__varlabels[lbl]; | |||
} | |||
} | |||
} | |||
} | |||
} else { | |||
// We've been given a variable name. Need to look it up. | |||
// if the var is 1 byte, assume "Zero Page". If var 2 bytes, assume "Absolute" | |||
if (tA in this.__varlabel){ | |||
v = this.__varlabel[tA]; | |||
if (v < 256){ | |||
if (tB !== null){ | |||
if (tB === "X") | |||
mode = "zX"; | |||
} else { | |||
mode = "z"; | |||
} | |||
} else { | |||
if (tB !== null){ | |||
if (tB === "X"){ | |||
mode = "aX"; | |||
} else if (tB === "Y"){ | |||
mode = "aY"; | |||
} | |||
} else { | |||
mode = "a"; | |||
} | |||
} | |||
} | |||
} | |||
if (mode !== "" && ! isNaN(v)) | |||
return [mode, v]; | |||
throw new Error("Malformed op-code or value on program address " + toHexString(this.__PC)); | |||
} | |||
compile(src){ | |||
var op = []; | |||
src.split("\n").forEach((line)=>{ | |||
line = line.trim(); | |||
if (line === ""){return this;} | |||
if (line[0] !== ";"){ // Skip comment lines. | |||
line = line.split(";")[0].trim(); // Take out any trailing comments. | |||
// Checking for any jump labels! | |||
if (line.indexOf(":") >= 0){ | |||
let s = line.split(":"); | |||
let lbl = s[0].trim(); | |||
if (lbl.length <= 0) | |||
throw new Error("Malformatted jump label at program address " + toHexString(this.__PC)); | |||
this.__StoreJmpLabel(lbl); | |||
line = s[1].trim(); | |||
if (line.length <= 0){return this;} // Nothing left to process. | |||
} | |||
// Finally... tokenize the main command. | |||
var tokens = tokenize(line); | |||
if (tokens[0].toLowerCase() === '.define'){ | |||
// Variable label!! | |||
this.__StoreVarLabel(tokens[1], tokens[2]); | |||
/*} else if (tokens[0][tokens[0].length - 1] === ':'){ | |||
// Jump labels! | |||
this.__StoreJmpLabel(tokens[0].substr(0, tokens[0].length - 1));*/ | |||
} else if (tokens[0].toLowerCase() === '.bytes'){ | |||
// Compiler directive. Treat all proceeding tokens as values and store them raw. | |||
for(let i=1; i < tokens.length; i++){ | |||
let v = Number.NaN; | |||
if (tokens[i].startsWith("$")){ | |||
v = parseInt(tokens[i].substr(1), 16); | |||
} else if (tokens[i].startsWith("b")){ | |||
v = parseInt(tokens[i].substr(1), 2); | |||
} else { | |||
v = parseInt(tokens[i]); | |||
} | |||
if (isNaN(v) || v < 0 || v >= 256) | |||
throw new Error("Byte list value is malformed or out of bounds at program address " + toHexString(this.__PC)); | |||
op.push(v); | |||
this.__PC += 1; | |||
}; | |||
} else if (tokens[0].length === 3){ | |||
let StoreSingleOp = (code)=>{ | |||
if (tokens.length === 1){ | |||
op.push(code); | |||
this.__PC += 1; | |||
return false; | |||
} | |||
return true; | |||
}; | |||
let StoreBranchOp = (code) => { | |||
if (tokens.length === 2){ | |||
let v = Number.NaN; | |||
if (tokens[1][0] === '$'){ | |||
if (tokens[1].length === 3 || tokens[1].length === 5){ | |||
v = parseInt(token[1].substr(1), 16); | |||
} | |||
} else { | |||
if (tokens[1] in this.__jmplabels){ | |||
v = this.__jmplabels[tokens[1]]; | |||
} | |||
} | |||
if (!isNaN(v)){ | |||
if (v < (this.__PC+1) - 127 || v > (this.__PC+1) + 128) | |||
throw new Error("Branch exceeds maximum number of bytes on program address " + toHexString(this.__PC + 1)); | |||
v = v - (this.__PC + 1); | |||
op.push(code); | |||
op.push((v >= 0) ? v : v + 255); | |||
this.__PC += 2; | |||
return false; | |||
} | |||
} | |||
return true; | |||
}; | |||
let StoreOp = (codes, mint, maxt, zpm) => { | |||
zpm = (zpm === "zY") ? "zY" : "zX"; | |||
if (tokens.length >= mint && tokens.length <= maxt){ | |||
let mv = this.__AddrModeVal(tokens[1], (tokens.length === 3) ? tokens[2].toUpperCase() : null); | |||
let modena = true; | |||
switch(mv[0]){ | |||
case "i": | |||
modena = codes[0] === null; | |||
if (!modena){ | |||
op.push(codes[0]); | |||
op.push(mv[1]); | |||
this.__PC += 2; | |||
} break; | |||
case "z": | |||
modena = codes[1] === null; | |||
if (!modena){ | |||
op.push(codes[1]); | |||
op.push(mv[1]); | |||
this.__PC += 2; | |||
} break; | |||
case "zY": | |||
case "zX": | |||
modena = (mv[0] === zpm && codes[2] === null); | |||
if (!modena){ | |||
op.push(codes[2]); | |||
op.push(mv[1]); | |||
this.__PC += 2; | |||
} break; | |||
case "a": | |||
modena = codes[3] === null; | |||
if (!modena){ | |||
op.push(codes[3]); | |||
op.push(mv[1] & 0x000000FF); | |||
op.push((mv[1] & 0x0000FF00) >> 8); | |||
this.__PC += 3; | |||
} break; | |||
case "aX": | |||
modena = codes[4] === null; | |||
if (!modena){ | |||
op.push(codes[4]); | |||
op.push(mv[1] & 0x000000FF); | |||
op.push((mv[1] & 0x0000FF00) >> 8); | |||
this.__PC += 3; | |||
} break; | |||
case "aY": | |||
modena = codes[5] === null; | |||
if (!modena){ | |||
op.push(codes[5]); | |||
op.push(mv[1] & 0x000000FF); | |||
op.push((mv[1] & 0x0000FF00) >> 8); | |||
this.__PC += 3; | |||
} break; | |||
case "nX": | |||
modena = codes[6] === null; | |||
if (!modena){ | |||
op.push(codes[6]); | |||
op.push(mv[1]); | |||
this.__PC += 2; | |||
} break; | |||
case "nY": | |||
modena = codes[7] === null; | |||
if (!modena){ | |||
op.push(codes[7]); | |||
op.push(mv[1]); | |||
this.__PC += 2; | |||
} break; | |||
} | |||
if (modena) | |||
throw new Error("Op-code does not support implied mode on program address " + toHexString(this.__PC)); | |||
return false; | |||
} | |||
return true; | |||
}; | |||
let procFailed = false; | |||
let mv = null; | |||
// Possible op code. | |||
switch(tokens[0].toLowerCase()){ | |||
// --- ADC | |||
case 'adc': | |||
procFailed = StoreOp([0x69, 0x65, 0x75, 0x6D, 0x7D, 0x79, 0x61, 0x71], 2, 3); | |||
/* | |||
if (tokens.length >= 2 && tokens.length <= 3){ | |||
mv = addrModeVal(tokens[1], (tokens.length === 3) ? tokens[2].toUpperCase(), null); | |||
this.__PC += 2; | |||
switch(mv[0]){ | |||
case "i": | |||
op.push(0x69); | |||
op.push(mv[1]); | |||
break; | |||
case "z": | |||
case "zX": | |||
op.push((mv[0] === "z") ? 0x65 : 0x75); | |||
op.push(mv[1]); | |||
break; | |||
case "a": | |||
case "aX": | |||
case "aY": | |||
op.push((mv[0] === "a") ? 0x6D : ((mv[0] === "aX") ? 0x7D : 0x79)); | |||
op.push(mv[1] & 0x000000FF); | |||
op.push((mv[1] & 0x0000FF00) >> 8); | |||
this.__PC += 1; | |||
break; | |||
case "nX": | |||
case "nY": | |||
op.push((mv[0] === "nX") ? 0x61 : 0x71); | |||
op.push(mv[1]); | |||
break; | |||
} | |||
} else { procFailed = true; } | |||
*/ | |||
break; | |||
// --- AND | |||
case 'and': | |||
procFailed = StoreOp([0x29, 0x25, 0x35, 0x2D, 0x3D, 0x39, 0x21, 0x31], 2, 3); | |||
break; | |||
// --- ASL | |||
case 'asl': | |||
if (tokens.length === 2){ | |||
if (tokens[1].toUpperCase() === 'A'){ | |||
op.push(0x0A); | |||
this.__PC += 1; | |||
break; | |||
} | |||
} | |||
procFailed = StoreOp([null, 0x06, 0x16, 0x0E, 0x1E, null, null, null], 2, 3); | |||
break; | |||
// --- BCC | |||
case 'bcc': | |||
procFailed = StoreBranchOp(0x90); break; | |||
// --- BCS | |||
case 'bcs': | |||
procFailed = StoreBranchOp(0xB0); break; | |||
// --- BEQ | |||
case 'beq': | |||
procFailed = StoreBranchOp(0xF0); break; | |||
// --- BIT | |||
case 'bit': | |||
procFailed = StoreOp([null, 0x24, null, 0x2C, null, null, null, null], 2, 2); break; | |||
// --- BMI | |||
case 'bmi': | |||
procFailed = StoreBranchOp(0x30); break; | |||
// --- BNE | |||
case 'bne': | |||
procFailed = StoreBranchOp(0xD0); break; | |||
// --- BPL | |||
case 'bpl': | |||
procFailed = StoreBranchOp(0x10); break; | |||
// --- BRK | |||
case 'brk': | |||
procFailed = StoreSingleOp(0x00); break; | |||
// --- BVC | |||
case 'bvc': | |||
procFailed = StoreBranchOp(0x50); break; | |||
// --- BVS | |||
case 'bvs': | |||
procFailed = StoreBranchOp(0x70); break; | |||
// --- CLC | |||
case 'clc': | |||
procFailed = StoreSingleOp(0x18); break; | |||
// --- CLD | |||
case 'cld': | |||
procFailed = StoreSingleOp(0xD8); break; | |||
// --- CLI | |||
case 'cli': | |||
procFailed = StoreSingleOp(0x58); break; | |||
// --- CLV | |||
case 'clv': | |||
procFailed = StoreSingleOp(0xB8); break; | |||
// --- CMP | |||
case 'cmp': | |||
procFailed = StoreOp([0xC9, 0xC5, 0xD5, 0xCD, 0xDD, 0xD9, 0xC1, 0xD1], 2, 3); break; | |||
// --- CPX | |||
case 'cpx': | |||
procFailed = StoreOp([0xE0, 0xE4, null, 0xEC, null, null, null, null], 2, 2); break; | |||
// --- CPY | |||
case 'cpy': | |||
procFailed = StoreOp([0xC0, 0xC4, null, 0xCC, null, null, null, null], 2, 2); break; | |||
// --- DEC | |||
case 'dec': | |||
procFailed = StoreOp([null, 0xC6, 0xD6, 0xCE, 0xDE, null, null, null], 2, 3); break; | |||
// --- DEX | |||
case 'dex': | |||
procFailed = StoreSingleOp(0xCA); break; | |||
// --- DEY | |||
case 'dey': | |||
procFailed = StoreSingleOp(0x88); break; | |||
// --- EOR | |||
case 'eor': | |||
procFailed = StoreOp([0x49, 0x45, 0x55, 0x4D, 0x5D, 0x59, 0x41, 0x51], 2, 3); break; | |||
// --- INC | |||
case 'inc': | |||
procFailed = StoreOp([null, 0xE6, 0xF6, 0xEE, 0xFE, null, null, null], 2, 3); break; | |||
// --- INX | |||
case 'inx': | |||
procFailed = StoreSingleOp(0xE8); break; | |||
// --- INY | |||
case 'iny': | |||
procFailed = StoreSingleOp(0xC8); break; | |||
// --- JMP | |||
case 'jmp': | |||
if (tokens.length === 2){ | |||
let v = Number.NaN; | |||
let code = (tokens[1].startsWith("+")) ? 0x6C : 0x4C; | |||
if (tokens[1].startsWith("$")){ | |||
if (tokens[1].length === 5) | |||
v = parseInt(tokens[1].substr(1), 16); | |||
} else if (tokens[1].startsWith("+$")){ | |||
if (tokens[1].length === 6) | |||
v = parseInt(tokens[1].substr(2), 16); | |||
} else { | |||
let lbl = (tokens[1].startsWith("+")) ? tokens[1].substr(1) : tokens[1]; | |||
if (lbl in this.__jmplabels) | |||
v = this.__jmplabels[lbl]; | |||
} | |||
if (!isNaN(v)){ | |||
op.push(code); | |||
op.push(v & 0x000000FF); | |||
op.push((v & 0x0000FF00) >> 8); | |||
this.__PC += 3; | |||
} else { | |||
throw new Error("Malformed op-code or value on program address " + toHexString(this.__PC)); | |||
} | |||
} else { procFailed = true; } | |||
break; | |||
// --- JSR | |||
case 'jsr': | |||
if (tokens.length === 2){ | |||
let v = Number.NaN; | |||
if (tokens[1].startsWith("$")){ | |||
if (tokens[1].length === 5) | |||
v = parseInt(tokens[1].substr(1), 16); | |||
} else { | |||
if (tokens[1] in this.__jmplabels) | |||
v = this.__jmplabels[tokens[1]]; | |||
} | |||
if (!isNaN(v)){ | |||
op.push(0x20); | |||
op.push(v & 0x000000FF); | |||
op.push((v & 0x0000FF00) >> 8); | |||
this.__PC += 3; | |||
} else { | |||
throw new Error("Malformed op-code or value on program address " + toHexString(this.__PC)); | |||
} | |||
} else { | |||
procFailed = true; | |||
} | |||
break; | |||
// --- LDA | |||
case 'lda': | |||
procFailed = StoreOp([0xA9, 0xA5, 0xB5, 0xAD, 0xBD, 0xB9, 0xA1, 0xB1], 2, 3); break; | |||
// --- LDX | |||
case 'ldx': | |||
procFailed = StoreOp([0xA2, 0xA6, 0xB6, 0xAE, 0xBE, null, null, null], 2, 3, "zY"); break; | |||
// --- LDY | |||
case 'ldy': | |||
procFailed = StoreOp([0xA0, 0xA4, 0xB4, 0xAC, 0xBC, null, null, null], 2, 3); break; | |||
// --- LSR | |||
case 'lsr': | |||
if (tokens.length === 2){ | |||
if (tokens[1].toUpperCase() === 'A'){ | |||
op.push(0x4A); | |||
this.__PC += 1; | |||
break; | |||
} | |||
} | |||
procFailed = StoreOp([null, 0x46, 0x56, 0x4E, 0x5E, null, null, null], 2, 3); break; | |||
// --- NOP | |||
case 'nop': | |||
procFailed = StoreSingleOp(0xEA); break; | |||
// --- ORA | |||
case 'ora': | |||
procFailed = StoreOp([0x09, 0x05, 0x15, 0x0D, 0x1D, 0x19, 0x01, 0x11], 2, 3); break; | |||
// --- PHA | |||
case 'pha': | |||
procFailed = StoreSingleOp(0x48); break; | |||
// --- PHP | |||
case 'php': | |||
procFailed = StoreSingleOp(0x08); break; | |||
// --- PLA | |||
case 'pla': | |||
procFailed = StoreSingleOp(0x68); break; | |||
// --- PLP | |||
case 'plp': | |||
procFailed = StoreSingleOp(0x28); break; | |||
// --- ROL | |||
case 'rol': | |||
if (tokens.length === 2){ | |||
if (tokens[1].toUpperCase() === 'A'){ | |||
op.push(0x2A); | |||
this.__PC += 1; | |||
break; | |||
} | |||
} | |||
procFailed = StoreOp([null, 0x26, 0x36, 0x2E, 0x3E, null, null, null], 2, 3); break; | |||
// --- ROR | |||
case 'ror': | |||
if (tokens.length === 2){ | |||
if (tokens[1].toUpperCase() === 'A'){ | |||
op.push(0x6A); | |||
this.__PC += 1; | |||
break; | |||
} | |||
} | |||
procFailed = StoreOp([null, 0x66, 0x76, 0x6E, 0x7E, null, null, null], 2, 3); break; | |||
// --- RTI | |||
case 'rti': | |||
procFailed = StoreSingleOp(0x40); break; | |||
// --- RTS | |||
case 'rts': | |||
procFailed = StoreSingleOp(0x60); break; | |||
break; | |||
// --- SBC | |||
case 'sbc': | |||
procFailed = StoreOp([0xE9, 0xE5, 0xF5, 0xED, 0xFD, 0xF9, 0xE1, 0xF1], 2, 3); break; | |||
// --- SEC | |||
case 'sec': | |||
procFailed = StoreSingleOp(0x38); break; | |||
// --- SED | |||
case 'sed': | |||
procFailed = StoreSingleOp(0xF8); break; | |||
// --- SEI | |||
case 'sei': | |||
procFailed = StoreSingleOp(0x78); break; | |||
break; | |||
// --- STA | |||
case 'sta': | |||
procFailed = StoreOp([null, 0x85, 0x95, 0x8D, 0x9D, 0x99, 0x81, 0x91], 2, 3); break; | |||
// --- STX | |||
case 'stx': | |||
procFailed = StoreOp([null, 0x86, 0x96, 0x8E, null, null, null, null], 2, 3, "zY"); break; | |||
// --- STY | |||
case 'sty': | |||
procFailed = StoreOp([null, 0x84, 0x94, 0x8C, null, null, null, null], 2, 3); break; | |||
// --- TAX | |||
case 'tax': | |||
procFailed = StoreSingleOp(0xAA); break; | |||
// --- TAY | |||
case 'tay': | |||
procFailed = StoreSingleOp(0xA8); break; | |||
// --- TSX | |||
case 'tsx': | |||
procFailed = StoreSingleOp(0xBA); break; | |||
// --- TXA | |||
case 'txa': | |||
procFailed = StoreSingleOp(0x8A); break; | |||
// --- TXS | |||
case 'txs': | |||
procFailed = StoreSingleOp(0x9A); break; | |||
// --- TYA | |||
case 'tya': | |||
procFailed = StoreSingleOp(0x98); break; | |||
// --- --- | |||
// EOL | |||
// --- --- | |||
default: | |||
throw new Error("Unknown op-code '" + tokens[0].toUpperCase() + "' at program address " + toHexString(this.__PC)); | |||
} | |||
if (procFailed) | |||
throw new Error("Malformed op-code on program address " + toHexString(this.__PC)); | |||
} else { | |||
throw new Error("Failed to compile line '" + line + "' at program address " + toHexString(this.__PC)); | |||
} | |||
} | |||
}); | |||
this.__result = this.__result.concat(op); | |||
return this; | |||
//return new Uint8Array(op); | |||
} | |||
result(){ | |||
return new Uint8Array(this.__result); | |||
} | |||
reset(){ | |||
this.__PC = this.__initPC; | |||
this.__varlabels = {}; | |||
this.__jmplabels = {}; | |||
this.__result = []; | |||
return this; | |||
} | |||
} | |||
// --------------------------------------------------------------------- | |||
// --------------------------------------------------------------------- | |||
module.exports = { | |||
CPU: CPU, | |||
Assembler: Assembler | |||
}; | |||
@@ -0,0 +1,678 @@ | |||
function ToHexString(v, l){ | |||
l = (typeof(l) === 'number' && (l === 8 || l === 16)) ? l : 8; | |||
var r = v.toString(16); | |||
if (r.length < 2) | |||
r = "0" + r; | |||
if (l === 16 && r.length < 4) | |||
r = ((r.length === 2) ? "00" : "0") + r; | |||
return r; | |||
} | |||
function Tokenize(l){ | |||
var m = l.match(/([^()]+)|([()])/g); | |||
var ip = false; | |||
var t = []; | |||
m.forEach((mi)=>{ | |||
if (mi === "("){ | |||
ip = true; | |||
} else if (mi === ")"){ | |||
ip = false; | |||
} else { | |||
if (ip){ | |||
mi.split(/[\s,]+/).forEach((i)=>{ | |||
if (i !== "") | |||
t.push("+" + i); | |||
}); | |||
} else { | |||
mi.split(/[\s,]+/).forEach((i)=>{ | |||
if (i !== "") | |||
t.push(i); | |||
}); | |||
} | |||
} | |||
}); | |||
return t; | |||
} | |||
function StoreVarLabel(data, lbl, val){ | |||
if (lbl in data.varlabels) | |||
throw new Error("Variable label '" + lbl + "' defined more than once."); | |||
if (lbl in data.jmplabels) | |||
throw new Error("Variable label '" + lbl + "' matches already existing jump label."); | |||
let v = Number.NaN; | |||
if (val[0] === "$"){ | |||
if (val.length >= 2 && val.length <= 5) | |||
v = parseInt(val.substr(1), 16); | |||
} else if (val[0] === "#"){ | |||
if (val.startsWith("#b")){ | |||
v = parseInt(val.substr(2), 2); | |||
} else { | |||
v = parseInt(val); | |||
} | |||
} | |||
if (!isNaN(v)){ | |||
if (v < 0 || v > 65535) | |||
throw new Error("Variable '" + lbl + "' value is out of bounds for this processor."); | |||
data.varlabels[lbl] = v; | |||
} else { | |||
throw new Error("Variable '" + lbl + "' value malformed."); | |||
} | |||
} | |||
function StoreJmpLabel(data, lbl){ | |||
if (lbl in data.jmplabels) | |||
throw new Error("Jump label '" + lbl + "' defined more than once at program address " + ToHexString(data.PC)); | |||
if (lbl in data.varlabels) | |||
throw new Error("Jump label '" + lbl + "' matches already existing variable name at program address " + ToHexString(data.PC)); | |||
data.jmplabels[lbl] = data.PC; | |||
} | |||
function AddrModeVal(data, tA, tB){ | |||
var mode = ""; | |||
var v = Number.NaN; | |||
if (tA[0] === '#'){ | |||
mode = "i"; // Immediate | |||
if (tA[1] === '$'){ | |||
v = parseInt(tA.substr(2), 16); | |||
} else if (tA[1] === 'b'){ | |||
v = parseInt(tA.substr(2), 2); | |||
} else { | |||
v = parseInt(tA.substr(1)); | |||
if (isNaN(v)){ | |||
let lbl = tA.substr(1); | |||
if (lbl in this.__varlabels){ | |||
if (this.__varlabels[lbl] < 256) | |||
v = this.__varlabels[lbl] | |||
} | |||
} | |||
} | |||
} else if (tA[0] === '$'){ | |||
v = parseInt(tA.substr(1), 16); | |||
if (tA.length === 3){ | |||
// Zero Page | |||
if (tB !== null){ | |||
if (tB === "X") | |||
mode = "zX"; | |||
if (tB === "Y") | |||
mode = "zY"; | |||
} else { | |||
mode = "z"; | |||
} | |||
} else if (tA.length === 5){ | |||
// Absolute | |||
if (tB !== null){ | |||
if (tB === "X"){ | |||
mode = "aX"; | |||
} else if (tB === "Y"){ | |||
mode = "aY"; | |||
} | |||
} else { | |||
mode = "a"; | |||
} | |||
} | |||
} else if (tA.startsWith("+")){ | |||
if (tB !== null){ | |||
if (tB === "+X"){ // The plus exists due to how tokenize() works. | |||
mode = "nX"; | |||
} else if (tB === "Y"){ | |||
mode = "nY"; | |||
} | |||
if (mode !== ""){ | |||
if (tA.startsWith("+$")){ | |||
v = parseInt(tA.substr(2), 16); | |||
} else { | |||
let lbl = tA.substr(1); | |||
if (lbl in this.__varlabels){ | |||
if (this.__varlabels[lbl] < 256) | |||
v = this.__varlabels[lbl]; | |||
} | |||
} | |||
} | |||
} | |||
} else { | |||
// We've been given a variable name. Need to look it up. | |||
// if the var is 1 byte, assume "Zero Page". If var 2 bytes, assume "Absolute" | |||
if (tA in data.varlabel){ | |||
v = data.varlabel[tA]; | |||
if (v < 256){ | |||
if (tB !== null){ | |||
if (tB === "X") | |||
mode = "zX"; | |||
} else { | |||
mode = "z"; | |||
} | |||
} else { | |||
if (tB !== null){ | |||
if (tB === "X"){ | |||
mode = "aX"; | |||
} else if (tB === "Y"){ | |||
mode = "aY"; | |||
} | |||
} else { | |||
mode = "a"; | |||
} | |||
} | |||
} | |||
} | |||
if (mode !== "" && ! isNaN(v)) | |||
return [mode, v]; | |||
throw new Error("Malformed op-code or value on program address " + ToHexString(data.PC)); | |||
} | |||
// -------------------------------------------------------------------------------------------- | |||
// -------------------------------------------------------------------------------------------- | |||
function StoreSingleOp(data, cmp, code){ | |||
if (cmp.tokens.length === 1){ | |||
cmp.op.push(code); | |||
data.PC += 1; | |||
return false; | |||
} | |||
return true; | |||
}; | |||
function StoreBranchOp(data, cmp, code){ | |||
if (cmp.tokens.length === 2){ | |||
let v = Number.NaN; | |||
if (cmp.tokens[1][0] === '$'){ | |||
if (cmp.tokens[1].length === 3 || cmp.tokens[1].length === 5){ | |||
v = parseInt(cmp.token[1].substr(1), 16); | |||
} | |||
} else { | |||
if (cmp.tokens[1] in data.jmplabels){ | |||
v = data.jmplabels[cmp.tokens[1]]; | |||
} | |||
} | |||
if (!isNaN(v)){ | |||
if (v < (data.PC+1) - 127 || v > (data.PC+1) + 128) | |||
throw new Error("Branch exceeds maximum number of bytes on program address " + ToHexString(data.PC + 1)); | |||
v = v - (data.PC + 1); | |||
cmp.op.push(code); | |||
cmp.op.push((v >= 0) ? v : v + 255); | |||
data.PC += 2; | |||
return false; | |||
} | |||
} | |||
return true; | |||
}; | |||
function StoreOp(data, cmp, codes, mint, maxt, zpm){ | |||
let tokens = cmp.tokens; | |||
zpm = (zpm === "zY") ? "zY" : "zX"; | |||
if (cmp.tokens.length >= mint && cmp.tokens.length <= maxt){ | |||
let mv = AddrModeVal(data, cmp.tokens[1], (cmp.tokens.length === 3) ? cmp.tokens[2].toUpperCase() : null); | |||
let modena = true; | |||
switch(mv[0]){ | |||
case "i": | |||
modena = codes[0] === null; | |||
if (!modena){ | |||
cmp.op.push(codes[0]); | |||
cmp.op.push(mv[1]); | |||
data.PC += 2; | |||
} break; | |||
case "z": | |||
modena = codes[1] === null; | |||
if (!modena){ | |||
cmp.op.push(codes[1]); | |||
cmp.op.push(mv[1]); | |||
data.PC += 2; | |||
} break; | |||
case "zY": | |||
case "zX": | |||
modena = (mv[0] === zpm && codes[2] === null); | |||
if (!modena){ | |||
cmp.op.push(codes[2]); | |||
cmp.op.push(mv[1]); | |||
data.PC += 2; | |||
} break; | |||
case "a": | |||
modena = codes[3] === null; | |||
if (!modena){ | |||
cmp.op.push(codes[3]); | |||
cmp.op.push(mv[1] & 0x000000FF); | |||
cmp.op.push((mv[1] & 0x0000FF00) >> 8); | |||
data.PC += 3; | |||
} break; | |||
case "aX": | |||
modena = codes[4] === null; | |||
if (!modena){ | |||
cmp.op.push(codes[4]); | |||
cmp.op.push(mv[1] & 0x000000FF); | |||
cmp.op.push((mv[1] & 0x0000FF00) >> 8); | |||
data.PC += 3; | |||
} break; | |||
case "aY": | |||
modena = codes[5] === null; | |||
if (!modena){ | |||
cmp.op.push(codes[5]); | |||
cmp.op.push(mv[1] & 0x000000FF); | |||
cmp.op.push((mv[1] & 0x0000FF00) >> 8); | |||
data.PC += 3; | |||
} break; | |||
case "nX": | |||
modena = codes[6] === null; | |||
if (!modena){ | |||
cmp.op.push(codes[6]); | |||
cmp.op.push(mv[1]); | |||
data.PC += 2; | |||
} break; | |||
case "nY": | |||
modena = codes[7] === null; | |||
if (!modena){ | |||
cmp.op.push(codes[7]); | |||
cmp.op.push(mv[1]); | |||
data.PC += 2; | |||
} break; | |||
} | |||
if (modena) | |||
throw new Error("Op-code does not support implied mode on program address " + ToHexString(data.PC)); | |||
return false; | |||
} | |||
return true; | |||
}; | |||
// -------------------------------------------------------------------------------------------- | |||
// -------------------------------------------------------------------------------------------- | |||
class Assembler{ | |||
constructor(initpc){ | |||
// Inital value for program counter. Used by the reset() method. | |||
this.__initPC = (typeof(initpc) === 'number' && initpc >= 0) ? initpc : 0; | |||
this.__data = { | |||
// Labels that hold variable values. | |||
varlabels: {}, | |||
// Labels that hold jump/branch locations. | |||
jmplabels: {}, | |||
// Program counter to track where in the code/memory a | |||
// operation is located. | |||
PC: this.__initPC, | |||
// Currently compiled code. | |||
result: [] | |||
}; | |||
} | |||
get PC(){return this.__data.PC;} | |||
get variables(){ | |||
var v = {}; | |||
Object.keys(this.__data.varlabels).forEach((lbl)=>{ | |||
if (this.__data.varlabels.hasOwnProperty(lbl)) | |||
v[lbl] = this.__data.varlabels[lbl]; | |||
}); | |||
return v; | |||
} | |||
get jumplabels(){ | |||
var j = {}; | |||
Object.keys(this.__data.jmplabels).forEach((lbl)=>{ | |||
if (this.__data.jmplabels.hasOwnProperty(lbl)) | |||
j[lbl] = this.__data.jmplabels[lbl]; | |||
}); | |||
return j; | |||
} | |||
compile(src){ | |||
var cmp = { | |||
op: [], | |||
tokens: [] | |||
}; | |||
src.split("\n").forEach((line)=>{ | |||
line = line.trim(); | |||
if (line === ""){return this;} | |||
if (line[0] !== ";"){ // Skip comment lines. | |||
line = line.split(";")[0].trim(); // Take out any trailing comments. | |||
// Checking for any jump labels! | |||
if (line.indexOf(":") >= 0){ | |||
let s = line.split(":"); | |||
let lbl = s[0].trim(); | |||
if (lbl.length <= 0) | |||
throw new Error("Malformatted jump label at program address " + toHexString(this.__PC)); | |||
StoreJmpLabel(this.__data, lbl); | |||
line = s[1].trim(); | |||
if (line.length <= 0){return this;} // Nothing left to process. | |||
} | |||
// Finally... tokenize the main command. | |||
cmp.tokens = Tokenize(line); | |||
if (cmp.tokens[0].toLowerCase() === '.define'){ | |||
// Variable label!! | |||
StoreVarLabel(this.__data, cmp.tokens[1], cmp.tokens[2]); | |||
} else if (cmp.tokens[0].toLowerCase() === '.bytes'){ | |||
// Compiler directive. Treat all proceeding tokens as values and store them raw. | |||
for(let i=1; i < cmp.tokens.length; i++){ | |||
let v = Number.NaN; | |||
if (cmp.tokens[i].startsWith("$")){ | |||
v = parseInt(cmp.tokens[i].substr(1), 16); | |||
} else if (cmp.tokens[i].startsWith("b")){ | |||
v = parseInt(cmp.tokens[i].substr(1), 2); | |||
} else { | |||
v = parseInt(cmp.tokens[i]); | |||
} | |||
if (isNaN(v) || v < 0 || v >= 256) | |||
throw new Error("Byte list value is malformed or out of bounds at program address " + toHexString(this.__PC)); | |||
cmp.op.push(v); | |||
this.__data.PC += 1; | |||
}; | |||
} else if (cmp.tokens[0].length === 3){ | |||
let procFailed = false; | |||
let mv = null; | |||
// Possible op code. | |||
switch(cmp.tokens[0].toLowerCase()){ | |||
// --- ADC | |||
case 'adc': | |||
procFailed = StoreOp(this.__data, cmp, [0x69, 0x65, 0x75, 0x6D, 0x7D, 0x79, 0x61, 0x71], 2, 3); | |||
/* | |||
if (tokens.length >= 2 && tokens.length <= 3){ | |||
mv = addrModeVal(tokens[1], (tokens.length === 3) ? tokens[2].toUpperCase(), null); | |||
this.__PC += 2; | |||
switch(mv[0]){ | |||
case "i": | |||
op.push(0x69); | |||
op.push(mv[1]); | |||
break; | |||
case "z": | |||
case "zX": | |||
op.push((mv[0] === "z") ? 0x65 : 0x75); | |||
op.push(mv[1]); | |||
break; | |||
case "a": | |||
case "aX": | |||
case "aY": | |||
op.push((mv[0] === "a") ? 0x6D : ((mv[0] === "aX") ? 0x7D : 0x79)); | |||
op.push(mv[1] & 0x000000FF); | |||
op.push((mv[1] & 0x0000FF00) >> 8); | |||
this.__PC += 1; | |||
break; | |||
case "nX": | |||
case "nY": | |||
op.push((mv[0] === "nX") ? 0x61 : 0x71); | |||
op.push(mv[1]); | |||
break; | |||
} | |||
} else { procFailed = true; } | |||
*/ | |||
break; | |||
// --- AND | |||
case 'and': | |||
procFailed = StoreOp(this.__data, cmp, [0x29, 0x25, 0x35, 0x2D, 0x3D, 0x39, 0x21, 0x31], 2, 3); | |||
break; | |||
// --- ASL | |||
case 'asl': | |||
if (cmp.tokens.length === 2){ | |||
if (cmp.tokens[1].toUpperCase() === 'A'){ | |||
cmp.op.push(0x0A); | |||
this.__data.PC += 1; | |||
break; | |||
} | |||
} | |||
procFailed = StoreOp(this.__data, cmp, [null, 0x06, 0x16, 0x0E, 0x1E, null, null, null], 2, 3); | |||
break; | |||
// --- BCC | |||
case 'bcc': | |||
procFailed = StoreBranchOp(this.__data, cmp, 0x90); break; | |||
// --- BCS | |||
case 'bcs': | |||
procFailed = StoreBranchOp(this.__data, cmp, 0xB0); break; | |||
// --- BEQ | |||
case 'beq': | |||
procFailed = StoreBranchOp(this.__data, cmp, 0xF0); break; | |||
// --- BIT | |||
case 'bit': | |||
procFailed = StoreOp(this.__data, cmp, [null, 0x24, null, 0x2C, null, null, null, null], 2, 2); break; | |||
// --- BMI | |||
case 'bmi': | |||
procFailed = StoreBranchOp(this.__data, cmp, 0x30); break; | |||
// --- BNE | |||
case 'bne': | |||
procFailed = StoreBranchOp(this.__data, cmp, 0xD0); break; | |||
// --- BPL | |||
case 'bpl': | |||
procFailed = StoreBranchOp(this.__data, cmp, 0x10); break; | |||
// --- BRK | |||
case 'brk': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x00); break; | |||
// --- BVC | |||
case 'bvc': | |||
procFailed = StoreBranchOp(this.__data, cmp, 0x50); break; | |||
// --- BVS | |||
case 'bvs': | |||
procFailed = StoreBranchOp(this.__data, cmp, 0x70); break; | |||
// --- CLC | |||
case 'clc': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x18); break; | |||
// --- CLD | |||
case 'cld': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0xD8); break; | |||
// --- CLI | |||
case 'cli': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x58); break; | |||
// --- CLV | |||
case 'clv': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0xB8); break; | |||
// --- CMP | |||
case 'cmp': | |||
procFailed = StoreOp(this.__data, cmp, [0xC9, 0xC5, 0xD5, 0xCD, 0xDD, 0xD9, 0xC1, 0xD1], 2, 3); break; | |||
// --- CPX | |||
case 'cpx': | |||
procFailed = StoreOp(this.__data, cmp, [0xE0, 0xE4, null, 0xEC, null, null, null, null], 2, 2); break; | |||
// --- CPY | |||
case 'cpy': | |||
procFailed = StoreOp(this.__data, cmp, [0xC0, 0xC4, null, 0xCC, null, null, null, null], 2, 2); break; | |||
// --- DEC | |||
case 'dec': | |||
procFailed = StoreOp(this.__data, cmp, [null, 0xC6, 0xD6, 0xCE, 0xDE, null, null, null], 2, 3); break; | |||
// --- DEX | |||
case 'dex': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0xCA); break; | |||
// --- DEY | |||
case 'dey': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x88); break; | |||
// --- EOR | |||
case 'eor': | |||
procFailed = StoreOp(this.__data, cmp, [0x49, 0x45, 0x55, 0x4D, 0x5D, 0x59, 0x41, 0x51], 2, 3); break; | |||
// --- INC | |||
case 'inc': | |||
procFailed = StoreOp(this.__data, cmp, [null, 0xE6, 0xF6, 0xEE, 0xFE, null, null, null], 2, 3); break; | |||
// --- INX | |||
case 'inx': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0xE8); break; | |||
// --- INY | |||
case 'iny': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0xC8); break; | |||
// --- JMP | |||
case 'jmp': | |||
if (cmp.tokens.length === 2){ | |||
let v = Number.NaN; | |||
let code = (cmp.tokens[1].startsWith("+")) ? 0x6C : 0x4C; | |||
if (cmp.tokens[1].startsWith("$")){ | |||
if (cmp.tokens[1].length === 5) | |||
v = parseInt(cmp.tokens[1].substr(1), 16); | |||
} else if (cmp.tokens[1].startsWith("+$")){ | |||
if (cmp.tokens[1].length === 6) | |||
v = parseInt(cmp.tokens[1].substr(2), 16); | |||
} else { | |||
let lbl = (cmp.tokens[1].startsWith("+")) ? cmp.tokens[1].substr(1) : cmp.tokens[1]; | |||
if (lbl in this.__data.jmplabels) | |||
v = this.__data.jmplabels[lbl]; | |||
} | |||
if (!isNaN(v)){ | |||
cmp.op.push(code); | |||
cmp.op.push(v & 0x000000FF); | |||
cmp.op.push((v & 0x0000FF00) >> 8); | |||
this.__data.PC += 3; | |||
} else { | |||
throw new Error("Malformed op-code or value on program address " + ToHexString(this.__data.PC)); | |||
} | |||
} else { procFailed = true; } | |||
break; | |||
// --- JSR | |||
case 'jsr': | |||
if (cmp.tokens.length === 2){ | |||
let v = Number.NaN; | |||
if (cmp.tokens[1].startsWith("$")){ | |||
if (cmp.tokens[1].length === 5) | |||
v = parseInt(cmp.tokens[1].substr(1), 16); | |||
} else { | |||
if (cmp.tokens[1] in this.__data.jmplabels) | |||
v = this.__data.jmplabels[cmp.tokens[1]]; | |||
} | |||
if (!isNaN(v)){ | |||
cmp.op.push(0x20); | |||
cmp.op.push(v & 0x000000FF); | |||
cmp.op.push((v & 0x0000FF00) >> 8); | |||
this.__data.PC += 3; | |||
} else { | |||
throw new Error("Malformed op-code or value on program address " + ToHexString(this.__data.PC)); | |||
} | |||
} else { | |||
procFailed = true; | |||
} | |||
break; | |||
// --- LDA | |||
case 'lda': | |||
procFailed = StoreOp(this.__data, cmp, [0xA9, 0xA5, 0xB5, 0xAD, 0xBD, 0xB9, 0xA1, 0xB1], 2, 3); break; | |||
// --- LDX | |||
case 'ldx': | |||
procFailed = StoreOp(this.__data, cmp, [0xA2, 0xA6, 0xB6, 0xAE, 0xBE, null, null, null], 2, 3, "zY"); break; | |||
// --- LDY | |||
case 'ldy': | |||
procFailed = StoreOp(this.__data, cmp, [0xA0, 0xA4, 0xB4, 0xAC, 0xBC, null, null, null], 2, 3); break; | |||
// --- LSR | |||
case 'lsr': | |||
if (cmp.tokens.length === 2){ | |||
if (cmp.tokens[1].toUpperCase() === 'A'){ | |||
cmp.op.push(0x4A); | |||
this.__data.PC += 1; | |||
break; | |||
} | |||
} | |||
procFailed = StoreOp(this.__data, cmp, [null, 0x46, 0x56, 0x4E, 0x5E, null, null, null], 2, 3); break; | |||
// --- NOP | |||
case 'nop': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0xEA); break; | |||
// --- ORA | |||
case 'ora': | |||
procFailed = StoreOp(this.__data, cmp, [0x09, 0x05, 0x15, 0x0D, 0x1D, 0x19, 0x01, 0x11], 2, 3); break; | |||
// --- PHA | |||
case 'pha': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x48); break; | |||
// --- PHP | |||
case 'php': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x08); break; | |||
// --- PLA | |||
case 'pla': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x68); break; | |||
// --- PLP | |||
case 'plp': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x28); break; | |||
// --- ROL | |||
case 'rol': | |||
if (cmp.tokens.length === 2){ | |||
if (cmp.tokens[1].toUpperCase() === 'A'){ | |||
cmp.op.push(0x2A); | |||
this.__data.PC += 1; | |||
break; | |||
} | |||
} | |||
procFailed = StoreOp(this.__data, cmp, [null, 0x26, 0x36, 0x2E, 0x3E, null, null, null], 2, 3); break; | |||
// --- ROR | |||
case 'ror': | |||
if (cmp.tokens.length === 2){ | |||
if (cmp.tokens[1].toUpperCase() === 'A'){ | |||
cmp.op.push(0x6A); | |||
this.__data.PC += 1; | |||
break; | |||
} | |||
} | |||
procFailed = StoreOp(this.__data, cmp, [null, 0x66, 0x76, 0x6E, 0x7E, null, null, null], 2, 3); break; | |||
// --- RTI | |||
case 'rti': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x40); break; | |||
// --- RTS | |||
case 'rts': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x60); break; | |||
break; | |||
// --- SBC | |||
case 'sbc': | |||
procFailed = StoreOp(this.__data, cmp, [0xE9, 0xE5, 0xF5, 0xED, 0xFD, 0xF9, 0xE1, 0xF1], 2, 3); break; | |||
// --- SEC | |||
case 'sec': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x38); break; | |||
// --- SED | |||
case 'sed': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0xF8); break; | |||
// --- SEI | |||
case 'sei': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x78); break; | |||
break; | |||
// --- STA | |||
case 'sta': | |||
procFailed = StoreOp(this.__data, cmp, [null, 0x85, 0x95, 0x8D, 0x9D, 0x99, 0x81, 0x91], 2, 3); break; | |||
// --- STX | |||
case 'stx': | |||
procFailed = StoreOp(this.__data, cmp, [null, 0x86, 0x96, 0x8E, null, null, null, null], 2, 3, "zY"); break; | |||
// --- STY | |||
case 'sty': | |||
procFailed = StoreOp(this.__data, cmp, [null, 0x84, 0x94, 0x8C, null, null, null, null], 2, 3); break; | |||
// --- TAX | |||
case 'tax': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0xAA); break; | |||
// --- TAY | |||
case 'tay': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0xA8); break; | |||
// --- TSX | |||
case 'tsx': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0xBA); break; | |||
// --- TXA | |||
case 'txa': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x8A); break; | |||
// --- TXS | |||
case 'txs': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x9A); break; | |||
// --- TYA | |||
case 'tya': | |||
procFailed = StoreSingleOp(this.__data, cmp, 0x98); break; | |||
// --- --- | |||
// EOL | |||
// --- --- | |||
default: | |||
throw new Error("Unknown op-code '" + cmp.tokens[0].toUpperCase() + "' at program address " + ToHexString(this.__data.PC)); | |||
} | |||
if (procFailed) | |||
throw new Error("Malformed op-code on program address " + ToHexString(this.__data.PC)); | |||
} else { | |||
throw new Error("Failed to compile line '" + line + "' at program address " + ToHexString(this.__data.PC)); | |||
} | |||
} | |||
}); | |||
this.__data.result = this.__data.result.concat(cmp.op); | |||
return this; | |||
//return new Uint8Array(op); | |||
} | |||
result(){ | |||
return new Uint8Array(this.__data.result); | |||
} | |||
reset(){ | |||
this.__data.varlabels = {}; | |||
this.__data.jmplabels = {}; | |||
this.__data.PC = this.__initPC; | |||
this.__data.result = []; | |||
return this; | |||
} | |||
} | |||
module.exports = Assembler; |
@@ -0,0 +1,59 @@ | |||
/* | |||
* Emulate a basic 6502 (MOS) chip. | |||
*/ | |||
var Memory = require('../../common/memory.js'); | |||
class CPU{ | |||
constructor(){ | |||
// Registers | |||
this.__PC = 0; // Program Counter (16 bit) | |||
this.__IRQ = 0; // IRQ interrupt address code (16 bit) | |||
this.__SR = 0; // Status Register (8 bit) | |||
this.__XR = 0; // X Register (8 bit) | |||
this.__YR = 0; // Y Register (8 bit) | |||
this.__AR = 0; // Accumulator Register (8 bit) | |||
// Variables to watch for Interrupts. | |||
this.__nmi = false; | |||
this.__irq = false; | |||
// Memory module or controller. | |||
this.__mem = null; // Must be explicitly attached. | |||
// Hold any created CLK instances. | |||
this.__clkfn = null; | |||
} | |||
set NMI(n){ | |||
this.__nmi = (n === true); | |||
} | |||
set IRQ(q){ | |||
// TODO: Verify this. | |||
// TODO: Do not set if the interrupt flag is off. | |||
this.__irq = (q === true); | |||
} | |||
reset(){ | |||
// TODO: Read memory address $FFFC - FFFD to find PC start location. | |||
// TODO: Reset status registers that get changed on a reset. | |||
} | |||
clk(){ | |||
if (this.__clkfn === null){ | |||
this.__clkfn = (function(){ | |||
// TODO: All the work!! | |||
}).bind(this); | |||
} | |||
return this.__clkfn; | |||
} | |||
memory(mem){ | |||
if (!(mem instanceof Memory)) | |||
throw new ValueError("Expected Memory instance object."); | |||
this.__mem = mem; | |||
} | |||
} | |||
module.exports = CPU; |
@@ -0,0 +1,5 @@ | |||
module.exports = { | |||
CPU: require('./cpu.js'), | |||
Assembler: require('./assembler.js') | |||
}; |
@@ -1,478 +0,0 @@ | |||
const expect = require('chai').expect; | |||
const mos6502 = require('../src/MOS6502.js'); | |||
describe("Testing MOS6502 Systems...", function(){ | |||
describe("Testing MOS6502 Assembler...", function(){ | |||
var asm = new mos6502.Assembler(); | |||
it(".reset()", function(){ | |||
asm.compile("ADC $44"); | |||
asm.reset(); | |||
expect(asm.PC).to.equal(0); | |||
expect(Object.keys(asm.variables).length).to.equal(0); | |||
expect(Object.keys(asm.jumplabels).length).to.equal(0); | |||
expect(asm.result().length).to.equal(0); | |||
}); | |||
it(".result()", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $44"); | |||
expect(asm.result().length).to.be.gt(0); | |||
}); | |||
describe(".compile()", function(){ | |||
it(".define directive", function(){ | |||
asm.compile(".define TAG $44"); | |||
let vars = asm.variables; | |||
expect(vars).to.have.key('TAG'); | |||
expect(vars['TAG']).to.equal(0x44); | |||
}); | |||
it("Jump labels", function(){ | |||
let pc = asm.PC; | |||
asm.compile("loop:\nBNE loop"); | |||
let jmp = asm.jumplabels; | |||
expect(jmp).to.have.key('loop'); | |||
expect(jmp['loop']).to.equal(pc); | |||
}); | |||
it(".bytes directive", function(){ | |||
let res = asm.reset().compile(".BYTES $44, $55, $66, $77, b10110101, 128").result(); | |||
expect(res.length).to.equal(6); | |||
expect(res[0]).to.equal(0x44); | |||
expect(res[1]).to.equal(0x55); | |||
expect(res[2]).to.equal(0x66); | |||
expect(res[3]).to.equal(0x77); | |||
expect(res[4]).to.equal(parseInt('10110101', 2)); | |||
expect(res[5]).to.equal(128); | |||
}); | |||
it("Jump label before and after .byte directive", function(){ | |||
asm.reset(); | |||
let pc1 = asm.PC; | |||
asm.compile("bytes: .bytes $44, $55, $66, $77"); | |||
let pc2 = asm.PC; | |||
asm.compile("loop: BNE loop"); | |||
let jmp = asm.jumplabels; | |||
//expect(jmp).to.have.key('bytes'); | |||
expect(jmp['bytes']).to.equal(pc1); | |||
//expect(jmp).to.have.key('loop'); | |||
expect(jmp['loop']).to.equal(pc2); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(6); | |||
expect(res[0]).to.equal(0x44); | |||
expect(res[1]).to.equal(0x55); | |||
expect(res[2]).to.equal(0x66); | |||
expect(res[3]).to.equal(0x77); | |||
expect(res[4]).to.equal(0xD0); | |||
expect(res[5]).to.equal(0xFE); | |||
}); | |||
it("ADC Immediate", function(){ | |||
asm.reset(); | |||
asm.compile("ADC #$44"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(2); | |||
expect(res[0]).to.equal(0x69); | |||
expect(res[1]).to.equal(0x44); | |||
}); | |||
it("ADC Zero Page", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $44"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(2); | |||
expect(res[0]).to.equal(0x65); | |||
expect(res[1]).to.equal(0x44); | |||
}); | |||
it("ADC Zero Page, X", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $44, X"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(2); | |||
expect(res[0]).to.equal(0x75); | |||
expect(res[1]).to.equal(0x44); | |||
}); | |||
it("ADC Absolute", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $4400"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(3); | |||
expect(res[0]).to.equal(0x6D); | |||
expect(res[1]).to.equal(0x00); | |||
expect(res[2]).to.equal(0x44); | |||
}); | |||
it("ADC Absolute, X", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $4400,X"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(3); | |||
expect(res[0]).to.equal(0x7D); | |||
expect(res[1]).to.equal(0x00); | |||
expect(res[2]).to.equal(0x44); | |||
}); | |||
it("ADC Absolute, Y", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $4400, Y"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(3); | |||
expect(res[0]).to.equal(0x79); | |||
expect(res[1]).to.equal(0x00); | |||
expect(res[2]).to.equal(0x44); | |||
}); | |||
it("ADC Indirect, X", function(){ | |||
asm.reset(); | |||
asm.compile("ADC ($44,X)"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(2); | |||
expect(res[0]).to.equal(0x61); | |||
expect(res[1]).to.equal(0x44); | |||
}); | |||
it("ADC Indirect, Y", function(){ | |||
asm.reset(); | |||
asm.compile("ADC ($44), Y"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(2); | |||
expect(res[0]).to.equal(0x71); | |||
expect(res[1]).to.equal(0x44); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("AND Immediate"); | |||
it("AND Zero Page"); | |||
it("AND Zero Page, X"); | |||
it("AND Absolute"); | |||
it("AND Absolute, X"); | |||
it("AND Absolute, Y"); | |||
it("AND Indirect, X"); | |||
it("AND Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("ASL Accumulator"); | |||
it("ASL Zero Page"); | |||
it("ASL Zero Page, X"); | |||
it("ASL Absolute"); | |||
it("ASL Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("BIT Zero Page"); | |||
it("BIT Absolute"); | |||
// ------------------------------------------------------------- | |||
it("BPL"); | |||
it("BMI"); | |||
it("BVC"); | |||
it("BVS"); | |||
it("BCC"); | |||
it("BCS"); | |||
it("BNE"); | |||
it("BEQ"); | |||
// ------------------------------------------------------------- | |||
it ("BRK", function(){ | |||
asm.reset(); | |||
let res = asm.compile("BRK").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x00); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("CMP Immediate"); | |||
it("CMP Zero Page"); | |||
it("CMP Zero Page, X"); | |||
it("CMP Absolute"); | |||
it("CMP Absolute, X"); | |||
it("CMP Absolute, Y"); | |||
it("CMP Indirect, X"); | |||
it("CMP Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("CPX Immediate"); | |||
it("CPX Zero Page"); | |||
it("CPX Absolute"); | |||
// ------------------------------------------------------------- | |||
it("CPY Immediate"); | |||
it("CPY Zero Page"); | |||
it("CPY Absolute"); | |||
// ------------------------------------------------------------- | |||
it("DEC Zero Page"); | |||
it("DEC Zero Page, X"); | |||
it("DEC Absolute"); | |||
it("DEC Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("EOR Immediate"); | |||
it("EOR Zero Page"); | |||
it("EOR Zero Page, X"); | |||
it("EOR Absolute"); | |||
it("EOR Absolute, X"); | |||
it("EOR Absolute, Y"); | |||
it("EOR Indirect, X"); | |||
it("EOR Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("CLC", function(){ | |||
asm.reset(); | |||
let res = asm.compile("CLC").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x18); | |||
}); | |||
it("CLD", function(){ | |||
asm.reset(); | |||
let res = asm.compile("CLD").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xD8); | |||
}); | |||
it("CLI", function(){ | |||
asm.reset(); | |||
let res = asm.compile("CLI").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x58); | |||
}); | |||
it("CLV", function(){ | |||
asm.reset(); | |||
let res = asm.compile("CLV").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xB8); | |||
}); | |||
it("SEC", function(){ | |||
asm.reset(); | |||
let res = asm.compile("SEC").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x38); | |||
}); | |||
it("SED", function(){ | |||
asm.reset(); | |||
let res = asm.compile("SED").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xF8); | |||
}); | |||
it("SEI", function(){ | |||
asm.reset(); | |||
let res = asm.compile("SEI").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x78); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("INC Zero Page"); | |||
it("INC Zero Page, X"); | |||
it("INC Absolute"); | |||
it("INC Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("JMP Absolute"); | |||
it("JMP Indirect"); | |||
// ------------------------------------------------------------- | |||
it("JRS Absolute"); | |||
// ------------------------------------------------------------- | |||
it("LDA Immediate"); | |||
it("LDA Zero Page"); | |||
it("LDA Zero Page, X"); | |||
it("LDA Absolute"); | |||
it("LDA Absolute, X"); | |||
it("LDA Absolute, Y"); | |||
it("LDA Indirect, X"); | |||
it("LDA Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("LDX Immediate"); | |||
it("LDX Zero Page"); | |||
it("LDX Zero Page, Y"); | |||
it("LDX Absolute"); | |||
it("LDX Absolute, Y"); | |||
// ------------------------------------------------------------- | |||
it("LDY Immediate"); | |||
it("LDY Zero Page"); | |||
it("LDY Zero Page, X"); | |||
it("LDY Absolute"); | |||
it("LDY Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("LSR Accumulator"); | |||
it("LSR Zero Page"); | |||
it("LSR Zero Page, X"); | |||
it("LSR Absolute"); | |||
it("LSR Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("NOP", function(){ | |||
asm.reset(); | |||
let res = asm.compile("NOP").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xEA); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("ORA Immediate"); | |||
it("ORA Zero Page"); | |||
it("ORA Zero Page, X"); | |||
it("ORA Absolute"); | |||
it("ORA Absolute, X"); | |||
it("ORA Absolute, Y"); | |||
it("ORA Indirect, X"); | |||
it("ORA Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("DEX", function(){ | |||
asm.reset(); | |||
let res = asm.compile("DEX").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xCA); | |||
}); | |||
it("DEY", function(){ | |||
asm.reset(); | |||
let res = asm.compile("DEY").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x88); | |||
}); | |||
it("INX", function(){ | |||
asm.reset(); | |||
let res = asm.compile("INX").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xE8); | |||
}); | |||
it("INY", function(){ | |||
asm.reset(); | |||
let res = asm.compile("INY").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xC8); | |||
}); | |||
it("TAX", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TAX").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xAA); | |||
}); | |||
it("TAY", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TAY").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xA8); | |||
}); | |||
it("TXA", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TXA").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x8A); | |||
}); | |||
it("TYA", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TYA").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x98); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("ROL Accumulator"); | |||
it("ROL Zero Page"); | |||
it("ROL Zero Page, X"); | |||
it("ROL Absolute"); | |||
it("ROL Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("ROR Accumulator"); | |||
it("ROR Zero Page"); | |||
it("ROR Zero Page, X"); | |||
it("ROR Absolute"); | |||
it("ROR Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("RTI", function(){ | |||
asm.reset(); | |||
let res = asm.compile("RTI").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x40); | |||
}); | |||
it("RTS", function(){ | |||
asm.reset(); | |||
let res = asm.compile("RTS").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x60); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("SBC Immediate"); | |||
it("SBC Zero Page"); | |||
it("SBC Zero Page, X"); | |||
it("SBC Absolute"); | |||
it("SBC Absolute, X"); | |||
it("SBC Absolute, Y"); | |||
it("SBC Indirect, X"); | |||
it("SBC Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("STA Zero Page"); | |||
it("STA Zero Page, X"); | |||
it("STA Absolute"); | |||
it("STA Absolute, X"); | |||
it("STA Absolute, Y"); | |||
it("STA Indirect, X"); | |||
it("STA Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("PHA", function(){ | |||
asm.reset(); | |||
let res = asm.compile("PHA").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x48); | |||
}); | |||
it("PHP", function(){ | |||
asm.reset(); | |||
let res = asm.compile("PHP").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x08); | |||
}); | |||
it("PLA", function(){ | |||
asm.reset(); | |||
let res = asm.compile("PLA").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x68); | |||
}); | |||
it("PLP", function(){ | |||
asm.reset(); | |||
let res = asm.compile("PLP").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x28); | |||
}); | |||
it("TSX", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TSX").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xBA); | |||
}); | |||
it("TXS", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TXS").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x9A); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("STX Zero Page"); | |||
it("STX Zero Page, Y"); | |||
it("STX Absolute"); | |||
// ------------------------------------------------------------- | |||
it("STY Zero Page"); | |||
it("STY Zero Page, X"); | |||
it("STY Absolute"); | |||
// ------------------------------------------------------------- | |||
}); | |||
}); | |||
describe("Testing MOS6502 CPU...", function(){ | |||
it("Something here..."); | |||
}); | |||
}); |
@@ -0,0 +1,473 @@ | |||
const expect = require('chai').expect; | |||
const Assembler = require('../src/chip/MOS6502/assembler.js'); | |||
describe("Testing MOS6502 Assembler...", function(){ | |||
var asm = new Assembler(); | |||
it(".reset()", function(){ | |||
asm.compile("ADC $44"); | |||
asm.reset(); | |||
expect(asm.PC).to.equal(0); | |||
expect(Object.keys(asm.variables).length).to.equal(0); | |||
expect(Object.keys(asm.jumplabels).length).to.equal(0); | |||
expect(asm.result().length).to.equal(0); | |||
}); | |||
it(".result()", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $44"); | |||
expect(asm.result().length).to.be.gt(0); | |||
}); | |||
describe(".compile()", function(){ | |||
it(".define directive", function(){ | |||
asm.compile(".define TAG $44"); | |||
let vars = asm.variables; | |||
expect(vars).to.have.key('TAG'); | |||
expect(vars['TAG']).to.equal(0x44); | |||
}); | |||
it("Jump labels", function(){ | |||
let pc = asm.PC; | |||
asm.compile("loop:\nBNE loop"); | |||
let jmp = asm.jumplabels; | |||
expect(jmp).to.have.key('loop'); | |||
expect(jmp['loop']).to.equal(pc); | |||
}); | |||
it(".bytes directive", function(){ | |||
let res = asm.reset().compile(".BYTES $44, $55, $66, $77, b10110101, 128").result(); | |||
expect(res.length).to.equal(6); | |||
expect(res[0]).to.equal(0x44); | |||
expect(res[1]).to.equal(0x55); | |||
expect(res[2]).to.equal(0x66); | |||
expect(res[3]).to.equal(0x77); | |||
expect(res[4]).to.equal(parseInt('10110101', 2)); | |||
expect(res[5]).to.equal(128); | |||
}); | |||
it("Jump label before and after .byte directive", function(){ | |||
asm.reset(); | |||
let pc1 = asm.PC; | |||
asm.compile("bytes: .bytes $44, $55, $66, $77"); | |||
let pc2 = asm.PC; | |||
asm.compile("loop: BNE loop"); | |||
let jmp = asm.jumplabels; | |||
//expect(jmp).to.have.key('bytes'); | |||
expect(jmp['bytes']).to.equal(pc1); | |||
//expect(jmp).to.have.key('loop'); | |||
expect(jmp['loop']).to.equal(pc2); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(6); | |||
expect(res[0]).to.equal(0x44); | |||
expect(res[1]).to.equal(0x55); | |||
expect(res[2]).to.equal(0x66); | |||
expect(res[3]).to.equal(0x77); | |||
expect(res[4]).to.equal(0xD0); | |||
expect(res[5]).to.equal(0xFE); | |||
}); | |||
it("ADC Immediate", function(){ | |||
asm.reset(); | |||
asm.compile("ADC #$44"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(2); | |||
expect(res[0]).to.equal(0x69); | |||
expect(res[1]).to.equal(0x44); | |||
}); | |||
it("ADC Zero Page", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $44"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(2); | |||
expect(res[0]).to.equal(0x65); | |||
expect(res[1]).to.equal(0x44); | |||
}); | |||
it("ADC Zero Page, X", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $44, X"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(2); | |||
expect(res[0]).to.equal(0x75); | |||
expect(res[1]).to.equal(0x44); | |||
}); | |||
it("ADC Absolute", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $4400"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(3); | |||
expect(res[0]).to.equal(0x6D); | |||
expect(res[1]).to.equal(0x00); | |||
expect(res[2]).to.equal(0x44); | |||
}); | |||
it("ADC Absolute, X", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $4400,X"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(3); | |||
expect(res[0]).to.equal(0x7D); | |||
expect(res[1]).to.equal(0x00); | |||
expect(res[2]).to.equal(0x44); | |||
}); | |||
it("ADC Absolute, Y", function(){ | |||
asm.reset(); | |||
asm.compile("ADC $4400, Y"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(3); | |||
expect(res[0]).to.equal(0x79); | |||
expect(res[1]).to.equal(0x00); | |||
expect(res[2]).to.equal(0x44); | |||
}); | |||
it("ADC Indirect, X", function(){ | |||
asm.reset(); | |||
asm.compile("ADC ($44,X)"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(2); | |||
expect(res[0]).to.equal(0x61); | |||
expect(res[1]).to.equal(0x44); | |||
}); | |||
it("ADC Indirect, Y", function(){ | |||
asm.reset(); | |||
asm.compile("ADC ($44), Y"); | |||
let res = asm.result(); | |||
expect(res.length).to.equal(2); | |||
expect(res[0]).to.equal(0x71); | |||
expect(res[1]).to.equal(0x44); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("AND Immediate"); | |||
it("AND Zero Page"); | |||
it("AND Zero Page, X"); | |||
it("AND Absolute"); | |||
it("AND Absolute, X"); | |||
it("AND Absolute, Y"); | |||
it("AND Indirect, X"); | |||
it("AND Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("ASL Accumulator"); | |||
it("ASL Zero Page"); | |||
it("ASL Zero Page, X"); | |||
it("ASL Absolute"); | |||
it("ASL Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("BIT Zero Page"); | |||
it("BIT Absolute"); | |||
// ------------------------------------------------------------- | |||
it("BPL"); | |||
it("BMI"); | |||
it("BVC"); | |||
it("BVS"); | |||
it("BCC"); | |||
it("BCS"); | |||
it("BNE"); | |||
it("BEQ"); | |||
// ------------------------------------------------------------- | |||
it ("BRK", function(){ | |||
asm.reset(); | |||
let res = asm.compile("BRK").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x00); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("CMP Immediate"); | |||
it("CMP Zero Page"); | |||
it("CMP Zero Page, X"); | |||
it("CMP Absolute"); | |||
it("CMP Absolute, X"); | |||
it("CMP Absolute, Y"); | |||
it("CMP Indirect, X"); | |||
it("CMP Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("CPX Immediate"); | |||
it("CPX Zero Page"); | |||
it("CPX Absolute"); | |||
// ------------------------------------------------------------- | |||
it("CPY Immediate"); | |||
it("CPY Zero Page"); | |||
it("CPY Absolute"); | |||
// ------------------------------------------------------------- | |||
it("DEC Zero Page"); | |||
it("DEC Zero Page, X"); | |||
it("DEC Absolute"); | |||
it("DEC Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("EOR Immediate"); | |||
it("EOR Zero Page"); | |||
it("EOR Zero Page, X"); | |||
it("EOR Absolute"); | |||
it("EOR Absolute, X"); | |||
it("EOR Absolute, Y"); | |||
it("EOR Indirect, X"); | |||
it("EOR Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("CLC", function(){ | |||
asm.reset(); | |||
let res = asm.compile("CLC").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x18); | |||
}); | |||
it("CLD", function(){ | |||
asm.reset(); | |||
let res = asm.compile("CLD").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xD8); | |||
}); | |||
it("CLI", function(){ | |||
asm.reset(); | |||
let res = asm.compile("CLI").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x58); | |||
}); | |||
it("CLV", function(){ | |||
asm.reset(); | |||
let res = asm.compile("CLV").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xB8); | |||
}); | |||
it("SEC", function(){ | |||
asm.reset(); | |||
let res = asm.compile("SEC").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x38); | |||
}); | |||
it("SED", function(){ | |||
asm.reset(); | |||
let res = asm.compile("SED").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xF8); | |||
}); | |||
it("SEI", function(){ | |||
asm.reset(); | |||
let res = asm.compile("SEI").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x78); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("INC Zero Page"); | |||
it("INC Zero Page, X"); | |||
it("INC Absolute"); | |||
it("INC Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("JMP Absolute"); | |||
it("JMP Indirect"); | |||
// ------------------------------------------------------------- | |||
it("JRS Absolute"); | |||
// ------------------------------------------------------------- | |||
it("LDA Immediate"); | |||
it("LDA Zero Page"); | |||
it("LDA Zero Page, X"); | |||
it("LDA Absolute"); | |||
it("LDA Absolute, X"); | |||
it("LDA Absolute, Y"); | |||
it("LDA Indirect, X"); | |||
it("LDA Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("LDX Immediate"); | |||
it("LDX Zero Page"); | |||
it("LDX Zero Page, Y"); | |||
it("LDX Absolute"); | |||
it("LDX Absolute, Y"); | |||
// ------------------------------------------------------------- | |||
it("LDY Immediate"); | |||
it("LDY Zero Page"); | |||
it("LDY Zero Page, X"); | |||
it("LDY Absolute"); | |||
it("LDY Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("LSR Accumulator"); | |||
it("LSR Zero Page"); | |||
it("LSR Zero Page, X"); | |||
it("LSR Absolute"); | |||
it("LSR Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("NOP", function(){ | |||
asm.reset(); | |||
let res = asm.compile("NOP").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xEA); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("ORA Immediate"); | |||
it("ORA Zero Page"); | |||
it("ORA Zero Page, X"); | |||
it("ORA Absolute"); | |||
it("ORA Absolute, X"); | |||
it("ORA Absolute, Y"); | |||
it("ORA Indirect, X"); | |||
it("ORA Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("DEX", function(){ | |||
asm.reset(); | |||
let res = asm.compile("DEX").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xCA); | |||
}); | |||
it("DEY", function(){ | |||
asm.reset(); | |||
let res = asm.compile("DEY").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x88); | |||
}); | |||
it("INX", function(){ | |||
asm.reset(); | |||
let res = asm.compile("INX").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xE8); | |||
}); | |||
it("INY", function(){ | |||
asm.reset(); | |||
let res = asm.compile("INY").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xC8); | |||
}); | |||
it("TAX", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TAX").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xAA); | |||
}); | |||
it("TAY", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TAY").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xA8); | |||
}); | |||
it("TXA", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TXA").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x8A); | |||
}); | |||
it("TYA", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TYA").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x98); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("ROL Accumulator"); | |||
it("ROL Zero Page"); | |||
it("ROL Zero Page, X"); | |||
it("ROL Absolute"); | |||
it("ROL Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("ROR Accumulator"); | |||
it("ROR Zero Page"); | |||
it("ROR Zero Page, X"); | |||
it("ROR Absolute"); | |||
it("ROR Absolute, X"); | |||
// ------------------------------------------------------------- | |||
it("RTI", function(){ | |||
asm.reset(); | |||
let res = asm.compile("RTI").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x40); | |||
}); | |||
it("RTS", function(){ | |||
asm.reset(); | |||
let res = asm.compile("RTS").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x60); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("SBC Immediate"); | |||
it("SBC Zero Page"); | |||
it("SBC Zero Page, X"); | |||
it("SBC Absolute"); | |||
it("SBC Absolute, X"); | |||
it("SBC Absolute, Y"); | |||
it("SBC Indirect, X"); | |||
it("SBC Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("STA Zero Page"); | |||
it("STA Zero Page, X"); | |||
it("STA Absolute"); | |||
it("STA Absolute, X"); | |||
it("STA Absolute, Y"); | |||
it("STA Indirect, X"); | |||
it("STA Indirect, Y"); | |||
// ------------------------------------------------------------- | |||
it("PHA", function(){ | |||
asm.reset(); | |||
let res = asm.compile("PHA").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x48); | |||
}); | |||
it("PHP", function(){ | |||
asm.reset(); | |||
let res = asm.compile("PHP").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x08); | |||
}); | |||
it("PLA", function(){ | |||
asm.reset(); | |||
let res = asm.compile("PLA").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x68); | |||
}); | |||
it("PLP", function(){ | |||
asm.reset(); | |||
let res = asm.compile("PLP").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x28); | |||
}); | |||
it("TSX", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TSX").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0xBA); | |||
}); | |||
it("TXS", function(){ | |||
asm.reset(); | |||
let res = asm.compile("TXS").result(); | |||
expect(res.length).to.equal(1); | |||
expect(res[0]).to.equal(0x9A); | |||
}); | |||
// ------------------------------------------------------------- | |||
it("STX Zero Page"); | |||
it("STX Zero Page, Y"); | |||
it("STX Absolute"); | |||
// ------------------------------------------------------------- | |||
it("STY Zero Page"); | |||
it("STY Zero Page, X"); | |||
it("STY Absolute"); | |||
// ------------------------------------------------------------- | |||
}); | |||
}); | |||
@@ -1,4 +1,4 @@ | |||
const BIT = require('../../src/utils/bitman.js'); | |||
const BIT = require('../src/utils/bitman.js'); | |||
const expect = require('chai').expect; | |||
describe("Testing utils/bitman ...", function(){ |