| var mos6502 = require("./src/MOS6502.js"); | |||||
| var mos6502 = require("./src/chip/MOS6502"); | |||||
| var asm = new mos6502.Assembler(); | var asm = new mos6502.Assembler(); | ||||
| var src = "DEFINE TOPNT $44\n"; | var src = "DEFINE TOPNT $44\n"; |
| /* | |||||
| * 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 | |||||
| }; | |||||
| 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; |
| /* | |||||
| * 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; |
| module.exports = { | |||||
| CPU: require('./cpu.js'), | |||||
| Assembler: require('./assembler.js') | |||||
| }; |
| 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..."); | |||||
| }); | |||||
| }); |
| 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"); | |||||
| // ------------------------------------------------------------- | |||||
| }); | |||||
| }); | |||||
| const BIT = require('../../src/utils/bitman.js'); | |||||
| const BIT = require('../src/utils/bitman.js'); | |||||
| const expect = require('chai').expect; | const expect = require('chai').expect; | ||||
| describe("Testing utils/bitman ...", function(){ | describe("Testing utils/bitman ...", function(){ |