| @@ -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(){ | |||