diff --git a/app/assembler.js b/app/assembler.js new file mode 100644 index 0000000..33ff326 --- /dev/null +++ b/app/assembler.js @@ -0,0 +1,2659 @@ +/* + * 6502 assembler and simulator in Javascript + * (C)2006-2010 Stian Soreng - www.6502asm.com + * + * Adapted by Nick Morgan + * https://github.com/skilldrick/6502js + * + * Released under the GNU General Public License + * see http://gnu.org/licenses/gpl.html + */ + +'use strict'; + +function SimulatorWidget(node) { + var $node = $(node); + var ui = UI(); + var display = Display(); + var memory = Memory(); + var labels = Labels(); + var simulator = Simulator(); + var assembler = Assembler(); + + function initialize() { + stripText(); + ui.initialize(); + display.initialize(); + simulator.reset(); + + $node.find('.assembleButton').click(function () { + assembler.assembleCode(); + }); + $node.find('.runButton').click(simulator.runBinary); + $node.find('.runButton').click(simulator.stopDebugger); + $node.find('.resetButton').click(simulator.reset); + $node.find('.hexdumpButton').click(assembler.hexdump); + $node.find('.disassembleButton').click(assembler.disassemble); + $node.find('.debug').change(function () { + var debug = $(this).is(':checked'); + if (debug) { + ui.debugOn(); + simulator.enableDebugger(); + } else { + ui.debugOff(); + simulator.stopDebugger(); + } + }); + $node.find('.monitoring').change(function () { + ui.toggleMonitor(); + simulator.toggleMonitor(); + }); + $node.find('.start, .length').blur(simulator.handleMonitorRangeChange); + $node.find('.stepButton').click(simulator.debugExec); + $node.find('.gotoButton').click(simulator.gotoAddr); + $node.find('.notesButton').click(ui.showNotes); + + var editor = $node.find('.code'); + + editor.on('keypress input', simulator.stop); + editor.on('keypress input', ui.initialize); + editor.keydown(ui.captureTabInEditor); + + $(document).keypress(memory.storeKeypress); + + simulator.handleMonitorRangeChange(); + } + + function stripText() { + //Remove leading and trailing space in textarea + var text = $node.find('.code').val(); + text = text.replace(/^\n+/, '').replace(/\s+$/, ''); + $node.find('.code').val(text); + } + + function UI() { + var currentState; + + var start = { + assemble: true, + run: [false, 'Run'], + reset: false, + hexdump: false, + disassemble: false, + debug: [false, false] + }; + var assembled = { + assemble: false, + run: [true, 'Run'], + reset: true, + hexdump: true, + disassemble: true, + debug: [true, false] + }; + var running = { + assemble: false, + run: [true, 'Stop'], + reset: true, + hexdump: false, + disassemble: false, + debug: [true, false] + }; + var debugging = { + assemble: false, + reset: true, + hexdump: true, + disassemble: true, + debug: [true, true] + }; + var postDebugging = { + assemble: false, + reset: true, + hexdump: true, + disassemble: true, + debug: [true, false] + }; + + + function setState(state) { + $node.find('.assembleButton').attr('disabled', !state.assemble); + if (state.run) { + $node.find('.runButton').attr('disabled', !state.run[0]); + $node.find('.runButton').val(state.run[1]); + } + $node.find('.resetButton').attr('disabled', !state.reset); + $node.find('.hexdumpButton').attr('disabled', !state.hexdump); + $node.find('.disassembleButton').attr('disabled', !state.disassemble); + $node.find('.debug').attr('disabled', !state.debug[0]); + $node.find('.debug').attr('checked', state.debug[1]); + $node.find('.stepButton').attr('disabled', !state.debug[1]); + $node.find('.gotoButton').attr('disabled', !state.debug[1]); + currentState = state; + } + + function initialize() { + setState(start); + } + + function play() { + setState(running); + } + + function stop() { + setState(assembled); + } + + function debugOn() { + setState(debugging); + } + + function debugOff() { + setState(postDebugging); + } + + function assembleSuccess() { + setState(assembled); + } + + function toggleMonitor() { + $node.find('.monitor').toggle(); + } + + function showNotes() { + $node.find('.messages code').html($node.find('.notes').html()); + } + + function captureTabInEditor(e) { + // Tab Key + if(e.keyCode === 9) { + + // Prevent focus loss + e.preventDefault(); + + // Insert tab at caret position (instead of losing focus) + var caretStart = this.selectionStart, + caretEnd = this.selectionEnd, + currentValue = this.value; + + this.value = currentValue.substring(0, caretStart) + "\t" + currentValue.substring(caretEnd); + + // Move cursor forwards one (after tab) + this.selectionStart = this.selectionEnd = caretStart + 1; + } + } + + return { + initialize: initialize, + play: play, + stop: stop, + assembleSuccess: assembleSuccess, + debugOn: debugOn, + debugOff: debugOff, + toggleMonitor: toggleMonitor, + showNotes: showNotes, + captureTabInEditor: captureTabInEditor + }; + } + + + function Display() { + var displayArray = []; + var palette = [ + "#000000", "#ffffff", "#880000", "#aaffee", + "#cc44cc", "#00cc55", "#0000aa", "#eeee77", + "#dd8855", "#664400", "#ff7777", "#333333", + "#777777", "#aaff66", "#0088ff", "#bbbbbb" + ]; + var ctx; + var width; + var height; + var pixelSize; + var numX = 32; + var numY = 32; + + function initialize() { + var canvas = $node.find('.screen')[0]; + width = canvas.width; + height = canvas.height; + pixelSize = width / numX; + ctx = canvas.getContext('2d'); + reset(); + } + + function reset() { + ctx.fillStyle = "black"; + ctx.fillRect(0, 0, width, height); + } + + function updatePixel(addr) { + ctx.fillStyle = palette[memory.get(addr) & 0x0f]; + var y = Math.floor((addr - 0x200) / 32); + var x = (addr - 0x200) % 32; + ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); + } + + return { + initialize: initialize, + reset: reset, + updatePixel: updatePixel + }; + } + + function Memory() { + var memArray = new Array(0x600); + + function set(addr, val) { + return memArray[addr] = val; + } + + function get(addr) { + return memArray[addr]; + } + + function getWord(addr) { + return get(addr) + (get(addr + 1) << 8); + } + + // Poke a byte, don't touch any registers + function storeByte(addr, value) { + set(addr, value & 0xff); + if ((addr >= 0x200) && (addr <= 0x5ff)) { + display.updatePixel(addr); + } + } + + // Store keycode in ZP $ff + function storeKeypress(e) { + var value = e.which; + memory.storeByte(0xff, value); + } + + function format(start, length) { + var html = ''; + var n; + + for (var x = 0; x < length; x++) { + if ((x & 15) === 0) { + if (x > 0) { html += "\n"; } + n = (start + x); + html += num2hex(((n >> 8) & 0xff)); + html += num2hex((n & 0xff)); + html += ": "; + } + html += num2hex(memory.get(start + x)); + html += " "; + } + return html; + } + + return { + set: set, + get: get, + getWord: getWord, + storeByte: storeByte, + storeKeypress: storeKeypress, + format: format + }; + } + + function Simulator() { + var regA = 0; + var regX = 0; + var regY = 0; + var regP = 0; + var regPC = 0x600; + var regSP = 0xff; + var codeRunning = false; + var debug = false; + var monitoring = false; + var executeId; + + // Set zero and negative processor flags based on result + function setNVflags(value) { + if (value) { + regP &= 0xfd; + } else { + regP |= 0x02; + } + if (value & 0x80) { + regP |= 0x80; + } else { + regP &= 0x7f; + } + } + + function setCarryFlagFromBit0(value) { + regP = (regP & 0xfe) | (value & 1); + } + + function setCarryFlagFromBit7(value) { + regP = (regP & 0xfe) | ((value >> 7) & 1); + } + + function setNVflagsForRegA() { + setNVflags(regA); + } + + function setNVflagsForRegX() { + setNVflags(regX); + } + + function setNVflagsForRegY() { + setNVflags(regY); + } + + var ORA = setNVflagsForRegA; + var AND = setNVflagsForRegA; + var EOR = setNVflagsForRegA; + var ASL = setNVflags; + var LSR = setNVflags; + var ROL = setNVflags; + var ROR = setNVflags; + var LDA = setNVflagsForRegA; + var LDX = setNVflagsForRegX; + var LDY = setNVflagsForRegY; + + function BIT(value) { + if (value & 0x80) { + regP |= 0x80; + } else { + regP &= 0x7f; + } + if (value & 0x40) { + regP |= 0x40; + } else { + regP &= ~0x40; + } + if (regA & value) { + regP &= 0xfd; + } else { + regP |= 0x02; + } + } + + function CLC() { + regP &= 0xfe; + } + + function SEC() { + regP |= 1; + } + + + function CLV() { + regP &= 0xbf; + } + + function setOverflow() { + regP |= 0x40; + } + + function DEC(addr) { + var value = memory.get(addr); + value--; + value &= 0xff; + memory.storeByte(addr, value); + setNVflags(value); + } + + function INC(addr) { + var value = memory.get(addr); + value++; + value &= 0xff; + memory.storeByte(addr, value); + setNVflags(value); + } + + function jumpBranch(offset) { + if (offset > 0x7f) { + regPC = (regPC - (0x100 - offset)); + } else { + regPC = (regPC + offset); + } + } + + function overflowSet() { + return regP & 0x40; + } + + function decimalMode() { + return regP & 8; + } + + function carrySet() { + return regP & 1; + } + + function negativeSet() { + return regP & 0x80; + } + + function zeroSet() { + return regP & 0x02; + } + + function doCompare(reg, val) { + if (reg >= val) { + SEC(); + } else { + CLC(); + } + val = (reg - val); + setNVflags(val); + } + + function testSBC(value) { + var tmp, w; + if ((regA ^ value) & 0x80) { + setOverflow(); + } else { + CLV(); + } + + if (decimalMode()) { + tmp = 0xf + (regA & 0xf) - (value & 0xf) + carrySet(); + if (tmp < 0x10) { + w = 0; + tmp -= 6; + } else { + w = 0x10; + tmp -= 0x10; + } + w += 0xf0 + (regA & 0xf0) - (value & 0xf0); + if (w < 0x100) { + CLC(); + if (overflowSet() && w < 0x80) { CLV(); } + w -= 0x60; + } else { + SEC(); + if (overflowSet() && w >= 0x180) { CLV(); } + } + w += tmp; + } else { + w = 0xff + regA - value + carrySet(); + if (w < 0x100) { + CLC(); + if (overflowSet() && w < 0x80) { CLV(); } + } else { + SEC(); + if (overflowSet() && w >= 0x180) { CLV(); } + } + } + regA = w & 0xff; + setNVflagsForRegA(); + } + + function testADC(value) { + var tmp; + if ((regA ^ value) & 0x80) { + CLV(); + } else { + setOverflow(); + } + + if (decimalMode()) { + tmp = (regA & 0xf) + (value & 0xf) + carrySet(); + if (tmp >= 10) { + tmp = 0x10 | ((tmp + 6) & 0xf); + } + tmp += (regA & 0xf0) + (value & 0xf0); + if (tmp >= 160) { + SEC(); + if (overflowSet() && tmp >= 0x180) { CLV(); } + tmp += 0x60; + } else { + CLC(); + if (overflowSet() && tmp < 0x80) { CLV(); } + } + } else { + tmp = regA + value + carrySet(); + if (tmp >= 0x100) { + SEC(); + if (overflowSet() && tmp >= 0x180) { CLV(); } + } else { + CLC(); + if (overflowSet() && tmp < 0x80) { CLV(); } + } + } + regA = tmp & 0xff; + setNVflagsForRegA(); + } + + var instructions = { + i00: function () { + codeRunning = false; + //BRK + }, + + i01: function () { + var zp = (popByte() + regX) & 0xff; + var addr = memory.getWord(zp); + var value = memory.get(addr); + regA |= value; + ORA(); + }, + + i05: function () { + var zp = popByte(); + regA |= memory.get(zp); + ORA(); + }, + + i06: function () { + var zp = popByte(); + var value = memory.get(zp); + setCarryFlagFromBit7(value); + value = value << 1; + memory.storeByte(zp, value); + ASL(value); + }, + + i08: function () { + stackPush(regP | 0x30); + //PHP + }, + + i09: function () { + regA |= popByte(); + ORA(); + }, + + i0a: function () { + setCarryFlagFromBit7(regA); + regA = (regA << 1) & 0xff; + ASL(regA); + }, + + i0d: function () { + regA |= memory.get(popWord()); + ORA(); + }, + + i0e: function () { + var addr = popWord(); + var value = memory.get(addr); + setCarryFlagFromBit7(value); + value = value << 1; + memory.storeByte(addr, value); + ASL(value); + }, + + i10: function () { + var offset = popByte(); + if (!negativeSet()) { jumpBranch(offset); } + //BPL + }, + + i11: function () { + var zp = popByte(); + var value = memory.getWord(zp) + regY; + regA |= memory.get(value); + ORA(); + }, + + i15: function () { + var addr = (popByte() + regX) & 0xff; + regA |= memory.get(addr); + ORA(); + }, + + i16: function () { + var addr = (popByte() + regX) & 0xff; + var value = memory.get(addr); + setCarryFlagFromBit7(value); + value = value << 1; + memory.storeByte(addr, value); + ASL(value); + }, + + i18: function () { + CLC(); + }, + + i19: function () { + var addr = popWord() + regY; + regA |= memory.get(addr); + ORA(); + }, + + i1d: function () { + var addr = popWord() + regX; + regA |= memory.get(addr); + ORA(); + }, + + i1e: function () { + var addr = popWord() + regX; + var value = memory.get(addr); + setCarryFlagFromBit7(value); + value = value << 1; + memory.storeByte(addr, value); + ASL(value); + }, + + i20: function () { + var addr = popWord(); + var currAddr = regPC - 1; + stackPush(((currAddr >> 8) & 0xff)); + stackPush((currAddr & 0xff)); + regPC = addr; + //JSR + }, + + i21: function () { + var zp = (popByte() + regX) & 0xff; + var addr = memory.getWord(zp); + var value = memory.get(addr); + regA &= value; + AND(); + }, + + i24: function () { + var zp = popByte(); + var value = memory.get(zp); + BIT(value); + }, + + i25: function () { + var zp = popByte(); + regA &= memory.get(zp); + AND(); + }, + + i26: function () { + var sf = carrySet(); + var addr = popByte(); + var value = memory.get(addr); + setCarryFlagFromBit7(value); + value = value << 1; + value |= sf; + memory.storeByte(addr, value); + ROL(value); + }, + + i28: function () { + regP = stackPop() | 0x30; // There is no B bit! + //PLP + }, + + i29: function () { + regA &= popByte(); + AND(); + }, + + i2a: function () { + var sf = carrySet(); + setCarryFlagFromBit7(regA); + regA = (regA << 1) & 0xff; + regA |= sf; + ROL(regA); + }, + + i2c: function () { + var value = memory.get(popWord()); + BIT(value); + }, + + i2d: function () { + var value = memory.get(popWord()); + regA &= value; + AND(); + }, + + i2e: function () { + var sf = carrySet(); + var addr = popWord(); + var value = memory.get(addr); + setCarryFlagFromBit7(value); + value = value << 1; + value |= sf; + memory.storeByte(addr, value); + ROL(value); + }, + + i30: function () { + var offset = popByte(); + if (negativeSet()) { jumpBranch(offset); } + //BMI + }, + + i31: function () { + var zp = popByte(); + var value = memory.getWord(zp) + regY; + regA &= memory.get(value); + AND(); + }, + + i35: function () { + var addr = (popByte() + regX) & 0xff; + regA &= memory.get(addr); + AND(); + }, + + i36: function () { + var sf = carrySet(); + var addr = (popByte() + regX) & 0xff; + var value = memory.get(addr); + setCarryFlagFromBit7(value); + value = value << 1; + value |= sf; + memory.storeByte(addr, value); + ROL(value); + }, + + i38: function () { + SEC(); + }, + + i39: function () { + var addr = popWord() + regY; + var value = memory.get(addr); + regA &= value; + AND(); + }, + + i3d: function () { + var addr = popWord() + regX; + var value = memory.get(addr); + regA &= value; + AND(); + }, + + i3e: function () { + var sf = carrySet(); + var addr = popWord() + regX; + var value = memory.get(addr); + setCarryFlagFromBit7(value); + value = value << 1; + value |= sf; + memory.storeByte(addr, value); + ROL(value); + }, + + i40: function () { + regP = stackPop() | 0x30; // There is no B bit! + regPC = stackPop() | (stackPop() << 8); + //RTI + }, + + i41: function () { + var zp = (popByte() + regX) & 0xff; + var value = memory.getWord(zp); + regA ^= memory.get(value); + EOR(); + }, + + i45: function () { + var addr = popByte() & 0xff; + var value = memory.get(addr); + regA ^= value; + EOR(); + }, + + i46: function () { + var addr = popByte() & 0xff; + var value = memory.get(addr); + setCarryFlagFromBit0(value); + value = value >> 1; + memory.storeByte(addr, value); + LSR(value); + }, + + i48: function () { + stackPush(regA); + //PHA + }, + + i49: function () { + regA ^= popByte(); + EOR(); + }, + + i4a: function () { + setCarryFlagFromBit0(regA); + regA = regA >> 1; + LSR(regA); + }, + + i4c: function () { + regPC = popWord(); + //JMP + }, + + i4d: function () { + var addr = popWord(); + var value = memory.get(addr); + regA ^= value; + EOR(); + }, + + i4e: function () { + var addr = popWord(); + var value = memory.get(addr); + setCarryFlagFromBit0(value); + value = value >> 1; + memory.storeByte(addr, value); + LSR(value); + }, + + i50: function () { + var offset = popByte(); + if (!overflowSet()) { jumpBranch(offset); } + //BVC + }, + + i51: function () { + var zp = popByte(); + var value = memory.getWord(zp) + regY; + regA ^= memory.get(value); + EOR(); + }, + + i55: function () { + var addr = (popByte() + regX) & 0xff; + regA ^= memory.get(addr); + EOR(); + }, + + i56: function () { + var addr = (popByte() + regX) & 0xff; + var value = memory.get(addr); + setCarryFlagFromBit0(value); + value = value >> 1; + memory.storeByte(addr, value); + LSR(value); + }, + + i58: function () { + regP &= ~0x04; + throw new Error("Interrupts not implemented"); + //CLI + }, + + i59: function () { + var addr = popWord() + regY; + var value = memory.get(addr); + regA ^= value; + EOR(); + }, + + i5d: function () { + var addr = popWord() + regX; + var value = memory.get(addr); + regA ^= value; + EOR(); + }, + + i5e: function () { + var addr = popWord() + regX; + var value = memory.get(addr); + setCarryFlagFromBit0(value); + value = value >> 1; + memory.storeByte(addr, value); + LSR(value); + }, + + i60: function () { + regPC = (stackPop() | (stackPop() << 8)) + 1; + //RTS + }, + + i61: function () { + var zp = (popByte() + regX) & 0xff; + var addr = memory.getWord(zp); + var value = memory.get(addr); + testADC(value); + //ADC + }, + + i65: function () { + var addr = popByte(); + var value = memory.get(addr); + testADC(value); + //ADC + }, + + i66: function () { + var sf = carrySet(); + var addr = popByte(); + var value = memory.get(addr); + setCarryFlagFromBit0(value); + value = value >> 1; + if (sf) { value |= 0x80; } + memory.storeByte(addr, value); + ROR(value); + }, + + i68: function () { + regA = stackPop(); + setNVflagsForRegA(); + //PLA + }, + + i69: function () { + var value = popByte(); + testADC(value); + //ADC + }, + + i6a: function () { + var sf = carrySet(); + setCarryFlagFromBit0(regA); + regA = regA >> 1; + if (sf) { regA |= 0x80; } + ROR(regA); + }, + + i6c: function () { + regPC = memory.getWord(popWord()); + //JMP + }, + + i6d: function () { + var addr = popWord(); + var value = memory.get(addr); + testADC(value); + //ADC + }, + + i6e: function () { + var sf = carrySet(); + var addr = popWord(); + var value = memory.get(addr); + setCarryFlagFromBit0(value); + value = value >> 1; + if (sf) { value |= 0x80; } + memory.storeByte(addr, value); + ROR(value); + }, + + i70: function () { + var offset = popByte(); + if (overflowSet()) { jumpBranch(offset); } + //BVS + }, + + i71: function () { + var zp = popByte(); + var addr = memory.getWord(zp); + var value = memory.get(addr + regY); + testADC(value); + //ADC + }, + + i75: function () { + var addr = (popByte() + regX) & 0xff; + var value = memory.get(addr); + testADC(value); + //ADC + }, + + i76: function () { + var sf = carrySet(); + var addr = (popByte() + regX) & 0xff; + var value = memory.get(addr); + setCarryFlagFromBit0(value); + value = value >> 1; + if (sf) { value |= 0x80; } + memory.storeByte(addr, value); + ROR(value); + }, + + i78: function () { + regP |= 0x04; + throw new Error("Interrupts not implemented"); + //SEI + }, + + i79: function () { + var addr = popWord(); + var value = memory.get(addr + regY); + testADC(value); + //ADC + }, + + i7d: function () { + var addr = popWord(); + var value = memory.get(addr + regX); + testADC(value); + //ADC + }, + + i7e: function () { + var sf = carrySet(); + var addr = popWord() + regX; + var value = memory.get(addr); + setCarryFlagFromBit0(value); + value = value >> 1; + if (sf) { value |= 0x80; } + memory.storeByte(addr, value); + ROR(value); + }, + + i81: function () { + var zp = (popByte() + regX) & 0xff; + var addr = memory.getWord(zp); + memory.storeByte(addr, regA); + //STA + }, + + i84: function () { + memory.storeByte(popByte(), regY); + //STY + }, + + i85: function () { + memory.storeByte(popByte(), regA); + //STA + }, + + i86: function () { + memory.storeByte(popByte(), regX); + //STX + }, + + i88: function () { + regY = (regY - 1) & 0xff; + setNVflagsForRegY(); + //DEY + }, + + i8a: function () { + regA = regX & 0xff; + setNVflagsForRegA(); + //TXA + }, + + i8c: function () { + memory.storeByte(popWord(), regY); + //STY + }, + + i8d: function () { + memory.storeByte(popWord(), regA); + //STA + }, + + i8e: function () { + memory.storeByte(popWord(), regX); + //STX + }, + + i90: function () { + var offset = popByte(); + if (!carrySet()) { jumpBranch(offset); } + //BCC + }, + + i91: function () { + var zp = popByte(); + var addr = memory.getWord(zp) + regY; + memory.storeByte(addr, regA); + //STA + }, + + i94: function () { + memory.storeByte((popByte() + regX) & 0xff, regY); + //STY + }, + + i95: function () { + memory.storeByte((popByte() + regX) & 0xff, regA); + //STA + }, + + i96: function () { + memory.storeByte((popByte() + regY) & 0xff, regX); + //STX + }, + + i98: function () { + regA = regY & 0xff; + setNVflagsForRegA(); + //TYA + }, + + i99: function () { + memory.storeByte(popWord() + regY, regA); + //STA + }, + + i9a: function () { + regSP = regX & 0xff; + //TXS + }, + + i9d: function () { + var addr = popWord(); + memory.storeByte(addr + regX, regA); + //STA + }, + + ia0: function () { + regY = popByte(); + LDY(); + }, + + ia1: function () { + var zp = (popByte() + regX) & 0xff; + var addr = memory.getWord(zp); + regA = memory.get(addr); + LDA(); + }, + + ia2: function () { + regX = popByte(); + LDX(); + }, + + ia4: function () { + regY = memory.get(popByte()); + LDY(); + }, + + ia5: function () { + regA = memory.get(popByte()); + LDA(); + }, + + ia6: function () { + regX = memory.get(popByte()); + LDX(); + }, + + ia8: function () { + regY = regA & 0xff; + setNVflagsForRegY(); + //TAY + }, + + ia9: function () { + regA = popByte(); + LDA(); + }, + + iaa: function () { + regX = regA & 0xff; + setNVflagsForRegX(); + //TAX + }, + + iac: function () { + regY = memory.get(popWord()); + LDY(); + }, + + iad: function () { + regA = memory.get(popWord()); + LDA(); + }, + + iae: function () { + regX = memory.get(popWord()); + LDX(); + }, + + ib0: function () { + var offset = popByte(); + if (carrySet()) { jumpBranch(offset); } + //BCS + }, + + ib1: function () { + var zp = popByte(); + var addr = memory.getWord(zp) + regY; + regA = memory.get(addr); + LDA(); + }, + + ib4: function () { + regY = memory.get((popByte() + regX) & 0xff); + LDY(); + }, + + ib5: function () { + regA = memory.get((popByte() + regX) & 0xff); + LDA(); + }, + + ib6: function () { + regX = memory.get((popByte() + regY) & 0xff); + LDX(); + }, + + ib8: function () { + CLV(); + }, + + ib9: function () { + var addr = popWord() + regY; + regA = memory.get(addr); + LDA(); + }, + + iba: function () { + regX = regSP & 0xff; + LDX(); + //TSX + }, + + ibc: function () { + var addr = popWord() + regX; + regY = memory.get(addr); + LDY(); + }, + + ibd: function () { + var addr = popWord() + regX; + regA = memory.get(addr); + LDA(); + }, + + ibe: function () { + var addr = popWord() + regY; + regX = memory.get(addr); + LDX(); + }, + + ic0: function () { + var value = popByte(); + doCompare(regY, value); + //CPY + }, + + ic1: function () { + var zp = (popByte() + regX) & 0xff; + var addr = memory.getWord(zp); + var value = memory.get(addr); + doCompare(regA, value); + //CPA + }, + + ic4: function () { + var value = memory.get(popByte()); + doCompare(regY, value); + //CPY + }, + + ic5: function () { + var value = memory.get(popByte()); + doCompare(regA, value); + //CPA + }, + + ic6: function () { + var zp = popByte(); + DEC(zp); + }, + + ic8: function () { + regY = (regY + 1) & 0xff; + setNVflagsForRegY(); + //INY + }, + + ic9: function () { + var value = popByte(); + doCompare(regA, value); + //CMP + }, + + ica: function () { + regX = (regX - 1) & 0xff; + setNVflagsForRegX(); + //DEX + }, + + icc: function () { + var value = memory.get(popWord()); + doCompare(regY, value); + //CPY + }, + + icd: function () { + var value = memory.get(popWord()); + doCompare(regA, value); + //CPA + }, + + ice: function () { + var addr = popWord(); + DEC(addr); + }, + + id0: function () { + var offset = popByte(); + if (!zeroSet()) { jumpBranch(offset); } + //BNE + }, + + id1: function () { + var zp = popByte(); + var addr = memory.getWord(zp) + regY; + var value = memory.get(addr); + doCompare(regA, value); + //CMP + }, + + id5: function () { + var value = memory.get((popByte() + regX) & 0xff); + doCompare(regA, value); + //CMP + }, + + id6: function () { + var addr = (popByte() + regX) & 0xff; + DEC(addr); + }, + + id8: function () { + regP &= 0xf7; + //CLD + }, + + id9: function () { + var addr = popWord() + regY; + var value = memory.get(addr); + doCompare(regA, value); + //CMP + }, + + idd: function () { + var addr = popWord() + regX; + var value = memory.get(addr); + doCompare(regA, value); + //CMP + }, + + ide: function () { + var addr = popWord() + regX; + DEC(addr); + }, + + ie0: function () { + var value = popByte(); + doCompare(regX, value); + //CPX + }, + + ie1: function () { + var zp = (popByte() + regX) & 0xff; + var addr = memory.getWord(zp); + var value = memory.get(addr); + testSBC(value); + //SBC + }, + + ie4: function () { + var value = memory.get(popByte()); + doCompare(regX, value); + //CPX + }, + + ie5: function () { + var addr = popByte(); + var value = memory.get(addr); + testSBC(value); + //SBC + }, + + ie6: function () { + var zp = popByte(); + INC(zp); + }, + + ie8: function () { + regX = (regX + 1) & 0xff; + setNVflagsForRegX(); + //INX + }, + + ie9: function () { + var value = popByte(); + testSBC(value); + //SBC + }, + + iea: function () { + //NOP + }, + + i42: function () { + //WDM -- pseudo op for emulator: arg 0 to output A to message box + var value = popByte(); + if (value == 0) + message(String.fromCharCode(regA)); + }, + + iec: function () { + var value = memory.get(popWord()); + doCompare(regX, value); + //CPX + }, + + ied: function () { + var addr = popWord(); + var value = memory.get(addr); + testSBC(value); + //SBC + }, + + iee: function () { + var addr = popWord(); + INC(addr); + }, + + if0: function () { + var offset = popByte(); + if (zeroSet()) { jumpBranch(offset); } + //BEQ + }, + + if1: function () { + var zp = popByte(); + var addr = memory.getWord(zp); + var value = memory.get(addr + regY); + testSBC(value); + //SBC + }, + + if5: function () { + var addr = (popByte() + regX) & 0xff; + var value = memory.get(addr); + testSBC(value); + //SBC + }, + + if6: function () { + var addr = (popByte() + regX) & 0xff; + INC(addr); + }, + + if8: function () { + regP |= 8; + //SED + }, + + if9: function () { + var addr = popWord(); + var value = memory.get(addr + regY); + testSBC(value); + //SBC + }, + + ifd: function () { + var addr = popWord(); + var value = memory.get(addr + regX); + testSBC(value); + //SBC + }, + + ife: function () { + var addr = popWord() + regX; + INC(addr); + }, + + ierr: function () { + message("Address $" + addr2hex(regPC) + " - unknown opcode"); + codeRunning = false; + } + }; + + function stackPush(value) { + memory.set((regSP & 0xff) + 0x100, value & 0xff); + regSP--; + if (regSP < 0) { + regSP &= 0xff; + message("6502 Stack filled! Wrapping..."); + } + } + + function stackPop() { + var value; + regSP++; + if (regSP >= 0x100) { + regSP &= 0xff; + message("6502 Stack emptied! Wrapping..."); + } + value = memory.get(regSP + 0x100); + return value; + } + + // Pops a byte + function popByte() { + return(memory.get(regPC++) & 0xff); + } + + // Pops a little-endian word + function popWord() { + return popByte() + (popByte() << 8); + } + + // Executes the assembled code + function runBinary() { + if (codeRunning) { + // Switch OFF everything + stop(); + ui.stop(); + } else { + ui.play(); + codeRunning = true; + executeId = setInterval(multiExecute, 15); + } + } + + function multiExecute() { + if (!debug) { + // use a prime number of iterations to avoid aliasing effects + + for (var w = 0; w < 97; w++) { + execute(); + } + } + updateDebugInfo(); + } + + + function executeNextInstruction() { + var instructionName = popByte().toString(16).toLowerCase(); + if (instructionName.length === 1) { + instructionName = '0' + instructionName; + } + var instruction = instructions['i' + instructionName]; + + if (instruction) { + instruction(); + } else { + instructions.ierr(); + } + } + + // Executes one instruction. This is the main part of the CPU simulator. + function execute(debugging) { + if (!codeRunning && !debugging) { return; } + + setRandomByte(); + executeNextInstruction(); + + if ((regPC === 0) || (!codeRunning && !debugging)) { + stop(); + message("Program end at PC=$" + addr2hex(regPC - 1)); + ui.stop(); + } + } + + function setRandomByte() { + memory.set(0xfe, Math.floor(Math.random() * 256)); + } + + function updateMonitor() { + if (monitoring) { + var start = parseInt($node.find('.start').val(), 16); + var length = parseInt($node.find('.length').val(), 16); + + var end = start + length - 1; + + var monitorNode = $node.find('.monitor code'); + + if (!isNaN(start) && !isNaN(length) && start >= 0 && length > 0 && end <= 0xffff) { + monitorNode.html(memory.format(start, length)); + } else { + monitorNode.html('Cannot monitor this range. Valid ranges are between $0000 and $ffff, inclusive.'); + } + } + } + + function handleMonitorRangeChange() { + + var $start = $node.find('.start'), + $length = $node.find('.length'), + start = parseInt($start.val(), 16), + length = parseInt($length.val(), 16), + end = start + length - 1; + + $start.removeClass('monitor-invalid'); + $length.removeClass('monitor-invalid'); + + if(isNaN(start) || start < 0 || start > 0xffff) { + + $start.addClass('monitor-invalid'); + + } else if(isNaN(length) || end > 0xffff) { + + $length.addClass('monitor-invalid'); + } + } + + // Execute one instruction and print values + function debugExec() { + //if (codeRunning) { + execute(true); + //} + updateDebugInfo(); + } + + function updateDebugInfo() { + var html = "A=$" + num2hex(regA) + " X=$" + num2hex(regX) + " Y=$" + num2hex(regY) + "
"; + html += "SP=$" + num2hex(regSP) + " PC=$" + addr2hex(regPC); + html += "
"; + html += "NV-BDIZC
"; + for (var i = 7; i >=0; i--) { + html += regP >> i & 1; + } + $node.find('.minidebugger').html(html); + updateMonitor(); + } + + // gotoAddr() - Set PC to address (or address of label) + function gotoAddr() { + var inp = prompt("Enter address or label", ""); + var addr = 0; + if (labels.find(inp)) { + addr = labels.getPC(inp); + } else { + if (inp.match(/^0x[0-9a-f]{1,4}$/i)) { + inp = inp.replace(/^0x/, ""); + addr = parseInt(inp, 16); + } else if (inp.match(/^\$[0-9a-f]{1,4}$/i)) { + inp = inp.replace(/^\$/, ""); + addr = parseInt(inp, 16); + } + } + if (addr === 0) { + message("Unable to find/parse given address/label"); + } else { + regPC = addr; + } + updateDebugInfo(); + } + + + function stopDebugger() { + debug = false; + } + + function enableDebugger() { + debug = true; + if (codeRunning) { + updateDebugInfo(); + } + } + + // reset() - Reset CPU and memory. + function reset() { + display.reset(); + for (var i = 0; i < 0x600; i++) { // clear ZP, stack and screen + memory.set(i, 0x00); + } + regA = regX = regY = 0; + regPC = 0x600; + regSP = 0xff; + regP = 0x30; + updateDebugInfo(); + } + + function stop() { + codeRunning = false; + clearInterval(executeId); + message("\nStopped\n"); + } + + function toggleMonitor() { + monitoring = !monitoring; + } + + return { + runBinary: runBinary, + enableDebugger: enableDebugger, + stopDebugger: stopDebugger, + debugExec: debugExec, + gotoAddr: gotoAddr, + reset: reset, + stop: stop, + toggleMonitor: toggleMonitor, + handleMonitorRangeChange: handleMonitorRangeChange + }; + } + + + function Labels() { + var labelIndex = []; + + function indexLines(lines, symbols) { + for (var i = 0; i < lines.length; i++) { + if (!indexLine(lines[i], symbols)) { + message("**Label already defined at line " + (i + 1) + ":** " + lines[i]); + return false; + } + } + return true; + } + + // Extract label if line contains one and calculate position in memory. + // Return false if label already exists. + function indexLine(input, symbols) { + + // Figure out how many bytes this instruction takes + var currentPC = assembler.getCurrentPC(); + assembler.assembleLine(input, 0, symbols); //TODO: find a better way for Labels to have access to assembler + + // Find command or label + if (input.match(/^\w+:/)) { + var label = input.replace(/(^\w+):.*$/, "$1"); + + if (symbols.lookup(label)) { + message("**Label " + label + "is already used as a symbol; please rename one of them**"); + return false; + } + + return push(label + "|" + currentPC); + } + return true; + } + + // Push label to array. Return false if label already exists. + function push(name) { + if (find(name)) { + return false; + } + labelIndex.push(name + "|"); + return true; + } + + // Returns true if label exists. + function find(name) { + var nameAndAddr; + for (var i = 0; i < labelIndex.length; i++) { + nameAndAddr = labelIndex[i].split("|"); + if (name === nameAndAddr[0]) { + return true; + } + } + return false; + } + + // Associates label with address + function setPC(name, addr) { + var nameAndAddr; + for (var i = 0; i < labelIndex.length; i++) { + nameAndAddr = labelIndex[i].split("|"); + if (name === nameAndAddr[0]) { + labelIndex[i] = name + "|" + addr; + return true; + } + } + return false; + } + + // Get address associated with label + function getPC(name) { + var nameAndAddr; + for (var i = 0; i < labelIndex.length; i++) { + nameAndAddr = labelIndex[i].split("|"); + if (name === nameAndAddr[0]) { + return (nameAndAddr[1]); + } + } + return -1; + } + + function displayMessage() { + var str = "Found " + labelIndex.length + " label"; + if (labelIndex.length !== 1) { + str += "s"; + } + message(str + "."); + } + + function reset() { + labelIndex = []; + } + + return { + indexLines: indexLines, + find: find, + getPC: getPC, + displayMessage: displayMessage, + reset: reset + }; + } + + + function Assembler() { + var defaultCodePC; + var codeLen; + var codeAssembledOK = false; + var wasOutOfRangeBranch = false; + + var Opcodes = [ + /* Name, Imm, ZP, ZPX, ZPY, ABS, ABSX, ABSY, IND, INDX, INDY, SNGL, BRA */ + ["ADC", 0x69, 0x65, 0x75, null, 0x6d, 0x7d, 0x79, null, 0x61, 0x71, null, null], + ["AND", 0x29, 0x25, 0x35, null, 0x2d, 0x3d, 0x39, null, 0x21, 0x31, null, null], + ["ASL", null, 0x06, 0x16, null, 0x0e, 0x1e, null, null, null, null, 0x0a, null], + ["BIT", null, 0x24, null, null, 0x2c, null, null, null, null, null, null, null], + ["BPL", null, null, null, null, null, null, null, null, null, null, null, 0x10], + ["BMI", null, null, null, null, null, null, null, null, null, null, null, 0x30], + ["BVC", null, null, null, null, null, null, null, null, null, null, null, 0x50], + ["BVS", null, null, null, null, null, null, null, null, null, null, null, 0x70], + ["BCC", null, null, null, null, null, null, null, null, null, null, null, 0x90], + ["BCS", null, null, null, null, null, null, null, null, null, null, null, 0xb0], + ["BNE", null, null, null, null, null, null, null, null, null, null, null, 0xd0], + ["BEQ", null, null, null, null, null, null, null, null, null, null, null, 0xf0], + ["BRK", null, null, null, null, null, null, null, null, null, null, 0x00, null], + ["CMP", 0xc9, 0xc5, 0xd5, null, 0xcd, 0xdd, 0xd9, null, 0xc1, 0xd1, null, null], + ["CPX", 0xe0, 0xe4, null, null, 0xec, null, null, null, null, null, null, null], + ["CPY", 0xc0, 0xc4, null, null, 0xcc, null, null, null, null, null, null, null], + ["DEC", null, 0xc6, 0xd6, null, 0xce, 0xde, null, null, null, null, null, null], + ["EOR", 0x49, 0x45, 0x55, null, 0x4d, 0x5d, 0x59, null, 0x41, 0x51, null, null], + ["CLC", null, null, null, null, null, null, null, null, null, null, 0x18, null], + ["SEC", null, null, null, null, null, null, null, null, null, null, 0x38, null], + ["CLI", null, null, null, null, null, null, null, null, null, null, 0x58, null], + ["SEI", null, null, null, null, null, null, null, null, null, null, 0x78, null], + ["CLV", null, null, null, null, null, null, null, null, null, null, 0xb8, null], + ["CLD", null, null, null, null, null, null, null, null, null, null, 0xd8, null], + ["SED", null, null, null, null, null, null, null, null, null, null, 0xf8, null], + ["INC", null, 0xe6, 0xf6, null, 0xee, 0xfe, null, null, null, null, null, null], + ["JMP", null, null, null, null, 0x4c, null, null, 0x6c, null, null, null, null], + ["JSR", null, null, null, null, 0x20, null, null, null, null, null, null, null], + ["LDA", 0xa9, 0xa5, 0xb5, null, 0xad, 0xbd, 0xb9, null, 0xa1, 0xb1, null, null], + ["LDX", 0xa2, 0xa6, null, 0xb6, 0xae, null, 0xbe, null, null, null, null, null], + ["LDY", 0xa0, 0xa4, 0xb4, null, 0xac, 0xbc, null, null, null, null, null, null], + ["LSR", null, 0x46, 0x56, null, 0x4e, 0x5e, null, null, null, null, 0x4a, null], + ["NOP", null, null, null, null, null, null, null, null, null, null, 0xea, null], + ["ORA", 0x09, 0x05, 0x15, null, 0x0d, 0x1d, 0x19, null, 0x01, 0x11, null, null], + ["TAX", null, null, null, null, null, null, null, null, null, null, 0xaa, null], + ["TXA", null, null, null, null, null, null, null, null, null, null, 0x8a, null], + ["DEX", null, null, null, null, null, null, null, null, null, null, 0xca, null], + ["INX", null, null, null, null, null, null, null, null, null, null, 0xe8, null], + ["TAY", null, null, null, null, null, null, null, null, null, null, 0xa8, null], + ["TYA", null, null, null, null, null, null, null, null, null, null, 0x98, null], + ["DEY", null, null, null, null, null, null, null, null, null, null, 0x88, null], + ["INY", null, null, null, null, null, null, null, null, null, null, 0xc8, null], + ["ROR", null, 0x66, 0x76, null, 0x6e, 0x7e, null, null, null, null, 0x6a, null], + ["ROL", null, 0x26, 0x36, null, 0x2e, 0x3e, null, null, null, null, 0x2a, null], + ["RTI", null, null, null, null, null, null, null, null, null, null, 0x40, null], + ["RTS", null, null, null, null, null, null, null, null, null, null, 0x60, null], + ["SBC", 0xe9, 0xe5, 0xf5, null, 0xed, 0xfd, 0xf9, null, 0xe1, 0xf1, null, null], + ["STA", null, 0x85, 0x95, null, 0x8d, 0x9d, 0x99, null, 0x81, 0x91, null, null], + ["TXS", null, null, null, null, null, null, null, null, null, null, 0x9a, null], + ["TSX", null, null, null, null, null, null, null, null, null, null, 0xba, null], + ["PHA", null, null, null, null, null, null, null, null, null, null, 0x48, null], + ["PLA", null, null, null, null, null, null, null, null, null, null, 0x68, null], + ["PHP", null, null, null, null, null, null, null, null, null, null, 0x08, null], + ["PLP", null, null, null, null, null, null, null, null, null, null, 0x28, null], + ["STX", null, 0x86, null, 0x96, 0x8e, null, null, null, null, null, null, null], + ["STY", null, 0x84, 0x94, null, 0x8c, null, null, null, null, null, null, null], + ["WDM", 0x42, 0x42, null, null, null, null, null, null, null, null, null, null], + ["---", null, null, null, null, null, null, null, null, null, null, null, null] + ]; + + // Assembles the code into memory + function assembleCode() { + var BOOTSTRAP_ADDRESS = 0x600; + + wasOutOfRangeBranch = false; + + simulator.reset(); + labels.reset(); + defaultCodePC = BOOTSTRAP_ADDRESS; + $node.find('.messages code').empty(); + + var code = $node.find('.code').val(); + code += "\n\n"; + var lines = code.split("\n"); + codeAssembledOK = true; + + message("Preprocessing ..."); + var symbols = preprocess(lines); + + message("Indexing labels ..."); + defaultCodePC = BOOTSTRAP_ADDRESS; + if (!labels.indexLines(lines, symbols)) { + return false; + } + labels.displayMessage(); + + defaultCodePC = BOOTSTRAP_ADDRESS; + message("Assembling code ..."); + + codeLen = 0; + for (var i = 0; i < lines.length; i++) { + if (!assembleLine(lines[i], i, symbols)) { + codeAssembledOK = false; + break; + } + } + + if (codeLen === 0) { + codeAssembledOK = false; + message("No code to run."); + } + + if (codeAssembledOK) { + ui.assembleSuccess(); + memory.set(defaultCodePC, 0x00); //set a null byte at the end of the code + } else { + + var str = lines[i].replace("<", "<").replace(">", ">"); + + if(!wasOutOfRangeBranch) { + message("**Syntax error line " + (i + 1) + ": " + str + "**"); + } else { + message('**Out of range branch on line ' + (i + 1) + ' (branches are limited to -128 to +127): ' + str + '**'); + } + + ui.initialize(); + return false; + } + + message("Code assembled successfully, " + codeLen + " bytes."); + return true; + } + + // Sanitize input: remove comments and trim leading/trailing whitespace + function sanitize(line) { + // remove comments + var no_comments = line.replace(/^(.*?);.*/, "$1"); + + // trim line + return no_comments.replace(/^\s+/, "").replace(/\s+$/, ""); + } + + function preprocess(lines) { + var table = []; + var PREFIX = "__"; // Using a prefix avoids clobbering any predefined properties + + function lookup(key) { + if (table.hasOwnProperty(PREFIX + key)) return table[PREFIX + key]; + else return undefined; + } + + function add(key, value) { + var valueAlreadyExists = table.hasOwnProperty(PREFIX + key) + if (!valueAlreadyExists) { + table[PREFIX + key] = value; + } + } + + // Build the substitution table + for (var i = 0; i < lines.length; i++) { + lines[i] = sanitize(lines[i]); + var match_data = lines[i].match(/^define\s+(\w+)\s+(\S+)/); + if (match_data) { + add(match_data[1], sanitize(match_data[2])); + lines[i] = ""; // We're done with this preprocessor directive, so delete it + } + } + + // Callers will only need the lookup function + return { + lookup: lookup + } + } + + // Assembles one line of code. + // Returns true if it assembled successfully, false otherwise. + function assembleLine(input, lineno, symbols) { + var label, command, param, addr; + + // Find command or label + if (input.match(/^\w+:/)) { + label = input.replace(/(^\w+):.*$/, "$1"); + if (input.match(/^\w+:[\s]*\w+.*$/)) { + input = input.replace(/^\w+:[\s]*(.*)$/, "$1"); + command = input.replace(/^(\w+).*$/, "$1"); + } else { + command = ""; + } + } else { + command = input.replace(/^(\w+).*$/, "$1"); + } + + // Nothing to do for blank lines + if (command === "") { + return true; + } + + command = command.toUpperCase(); + + if (input.match(/^\*\s*=\s*\$?[0-9a-f]*$/)) { + // equ spotted + param = input.replace(/^\s*\*\s*=\s*/, ""); + if (param[0] === "$") { + param = param.replace(/^\$/, ""); + addr = parseInt(param, 16); + } else { + addr = parseInt(param, 10); + } + if ((addr < 0) || (addr > 0xffff)) { + message("Unable to relocate code outside 64k memory"); + return false; + } + defaultCodePC = addr; + return true; + } + + if (input.match(/^\w+\s+.*?$/)) { + param = input.replace(/^\w+\s+(.*?)/, "$1"); + } else if (input.match(/^\w+$/)) { + param = ""; + } else { + return false; + } + + param = param.replace(/[ ]/g, ""); + + if (command === "DCB") { + return DCB(param); + } + for (var o = 0; o < Opcodes.length; o++) { + if (Opcodes[o][0] === command) { + if (checkSingle(param, Opcodes[o][11])) { return true; } + if (checkImmediate(param, Opcodes[o][1], symbols)) { return true; } + if (checkZeroPage(param, Opcodes[o][2], symbols)) { return true; } + if (checkZeroPageX(param, Opcodes[o][3], symbols)) { return true; } + if (checkZeroPageY(param, Opcodes[o][4], symbols)) { return true; } + if (checkAbsoluteX(param, Opcodes[o][6], symbols)) { return true; } + if (checkAbsoluteY(param, Opcodes[o][7], symbols)) { return true; } + if (checkIndirect(param, Opcodes[o][8], symbols)) { return true; } + if (checkIndirectX(param, Opcodes[o][9], symbols)) { return true; } + if (checkIndirectY(param, Opcodes[o][10], symbols)) { return true; } + if (checkAbsolute(param, Opcodes[o][5], symbols)) { return true; } + if (checkBranch(param, Opcodes[o][12])) { return true; } + } + } + + return false; // Unknown syntax + } + + function DCB(param) { + var values, number, str, ch; + values = param.split(","); + if (values.length === 0) { return false; } + for (var v = 0; v < values.length; v++) { + str = values[v]; + if (str) { + ch = str.substring(0, 1); + if (ch === "$") { + number = parseInt(str.replace(/^\$/, ""), 16); + pushByte(number); + } else if (ch >= "0" && ch <= "9") { + number = parseInt(str, 10); + pushByte(number); + } else { + return false; + } + } + } + return true; + } + + // Try to parse the given parameter as a byte operand. + // Returns the (positive) value if successful, otherwise -1 + function tryParseByteOperand(param, symbols) { + if (param.match(/^\w+$/)) { + var lookupVal = symbols.lookup(param); // Substitute symbol by actual value, then proceed + if (lookupVal) { + param = lookupVal; + } + } + + var value; + + // Is it a hexadecimal operand? + var match_data = param.match(/^\$([0-9a-f]{1,2})$/i); + if (match_data) { + value = parseInt(match_data[1], 16); + } else { + // Is it a decimal operand? + match_data = param.match(/^([0-9]{1,3})$/i); + if (match_data) { + value = parseInt(match_data[1], 10); + } + } + + // Validate range + if (value >= 0 && value <= 0xff) { + return value; + } else { + return -1; + } + } + + // Try to parse the given parameter as a word operand. + // Returns the (positive) value if successful, otherwise -1 + function tryParseWordOperand(param, symbols) { + if (param.match(/^\w+$/)) { + var lookupVal = symbols.lookup(param); // Substitute symbol by actual value, then proceed + if (lookupVal) { + param = lookupVal; + } + } + + var value; + + // Is it a hexadecimal operand? + var match_data = param.match(/^\$([0-9a-f]{3,4})$/i); + if (match_data) { + value = parseInt(match_data[1], 16); + } else { + // Is it a decimal operand? + match_data = param.match(/^([0-9]{1,5})$/i); + if (match_data) { + value = parseInt(match_data[1], 10); + } + } + + // Validate range + if (value >= 0 && value <= 0xffff) { + return value; + } else { + return -1; + } + } + + // Common branch function for all branches (BCC, BCS, BEQ, BNE..) + function checkBranch(param, opcode) { + var addr; + if (opcode === null) { return false; } + + addr = -1; + if (param.match(/\w+/)) { + addr = labels.getPC(param); + } + if (addr === -1) { pushWord(0x00); return false; } + pushByte(opcode); + + var distance = addr - defaultCodePC - 1; + + if(distance < -128 || distance > 127) { + wasOutOfRangeBranch = true; + return false; + } + + pushByte(distance); + return true; + } + + // Check if param is immediate and push value + function checkImmediate(param, opcode, symbols) { + var value, label, hilo, addr; + if (opcode === null) { return false; } + + var match_data = param.match(/^#([\w\$]+)$/i); + if (match_data) { + var operand = tryParseByteOperand(match_data[1], symbols); + if (operand >= 0) { + pushByte(opcode); + pushByte(operand); + return true; + } + } + + // Label lo/hi + if (param.match(/^#[<>]\w+$/)) { + label = param.replace(/^#[<>](\w+)$/, "$1"); + hilo = param.replace(/^#([<>]).*$/, "$1"); + pushByte(opcode); + if (labels.find(label)) { + addr = labels.getPC(label); + switch(hilo) { + case ">": + pushByte((addr >> 8) & 0xff); + return true; + case "<": + pushByte(addr & 0xff); + return true; + default: + return false; + } + } else { + pushByte(0x00); + return true; + } + } + + return false; + } + + // Check if param is indirect and push value + function checkIndirect(param, opcode, symbols) { + var value; + if (opcode === null) { return false; } + + var match_data = param.match(/^\(([\w\$]+)\)$/i); + if (match_data) { + var operand = tryParseWordOperand(match_data[1], symbols); + if (operand >= 0) { + pushByte(opcode); + pushWord(operand); + return true; + } + } + return false; + } + + // Check if param is indirect X and push value + function checkIndirectX(param, opcode, symbols) { + var value; + if (opcode === null) { return false; } + + var match_data = param.match(/^\(([\w\$]+),X\)$/i); + if (match_data) { + var operand = tryParseByteOperand(match_data[1], symbols); + if (operand >= 0) { + pushByte(opcode); + pushByte(operand); + return true; + } + } + return false; + } + + // Check if param is indirect Y and push value + function checkIndirectY(param, opcode, symbols) { + var value; + if (opcode === null) { return false; } + + var match_data = param.match(/^\(([\w\$]+)\),Y$/i); + if (match_data) { + var operand = tryParseByteOperand(match_data[1], symbols); + if (operand >= 0) { + pushByte(opcode); + pushByte(operand); + return true; + } + } + return false; + } + + // Check single-byte opcodes + function checkSingle(param, opcode) { + if (opcode === null) { return false; } + // Accumulator instructions are counted as single-byte opcodes + if (param !== "" && param !== "A") { return false; } + pushByte(opcode); + return true; + } + + // Check if param is ZP and push value + function checkZeroPage(param, opcode, symbols) { + var value; + if (opcode === null) { return false; } + + var operand = tryParseByteOperand(param, symbols); + if (operand >= 0) { + pushByte(opcode); + pushByte(operand); + return true; + } + + return false; + } + + // Check if param is ABSX and push value + function checkAbsoluteX(param, opcode, symbols) { + var number, value, addr; + if (opcode === null) { return false; } + + var match_data = param.match(/^([\w\$]+),X$/i); + if (match_data) { + var operand = tryParseWordOperand(match_data[1], symbols); + if (operand >= 0) { + pushByte(opcode); + pushWord(operand); + return true; + } + } + + // it could be a label too.. + if (param.match(/^\w+,X$/i)) { + param = param.replace(/,X$/i, ""); + pushByte(opcode); + if (labels.find(param)) { + addr = labels.getPC(param); + if (addr < 0 || addr > 0xffff) { return false; } + pushWord(addr); + return true; + } else { + pushWord(0xffff); // filler, only used while indexing labels + return true; + } + } + + return false; + } + + // Check if param is ABSY and push value + function checkAbsoluteY(param, opcode, symbols) { + var number, value, addr; + if (opcode === null) { return false; } + + var match_data = param.match(/^([\w\$]+),Y$/i); + if (match_data) { + var operand = tryParseWordOperand(match_data[1], symbols); + if (operand >= 0) { + pushByte(opcode); + pushWord(operand); + return true; + } + } + + // it could be a label too.. + if (param.match(/^\w+,Y$/i)) { + param = param.replace(/,Y$/i, ""); + pushByte(opcode); + if (labels.find(param)) { + addr = labels.getPC(param); + if (addr < 0 || addr > 0xffff) { return false; } + pushWord(addr); + return true; + } else { + pushWord(0xffff); // filler, only used while indexing labels + return true; + } + } + return false; + } + + // Check if param is ZPX and push value + function checkZeroPageX(param, opcode, symbols) { + var number, value; + if (opcode === null) { return false; } + + var match_data = param.match(/^([\w\$]+),X$/i); + if (match_data) { + var operand = tryParseByteOperand(match_data[1], symbols); + if (operand >= 0) { + pushByte(opcode); + pushByte(operand); + return true; + } + } + + return false; + } + + // Check if param is ZPY and push value + function checkZeroPageY(param, opcode, symbols) { + var number, value; + if (opcode === null) { return false; } + + var match_data = param.match(/^([\w\$]+),Y$/i); + if (match_data) { + var operand = tryParseByteOperand(match_data[1], symbols); + if (operand >= 0) { + pushByte(opcode); + pushByte(operand); + return true; + } + } + + return false; + } + + // Check if param is ABS and push value + function checkAbsolute(param, opcode, symbols) { + var value, number, addr; + if (opcode === null) { return false; } + + var match_data = param.match(/^([\w\$]+)$/i); + if (match_data) { + var operand = tryParseWordOperand(match_data[1], symbols); + if (operand >= 0) { + pushByte(opcode); + pushWord(operand); + return true; + } + } + + // it could be a label too.. + if (param.match(/^\w+$/)) { + pushByte(opcode); + if (labels.find(param)) { + addr = (labels.getPC(param)); + if (addr < 0 || addr > 0xffff) { return false; } + pushWord(addr); + return true; + } else { + pushWord(0xffff); // filler, only used while indexing labels + return true; + } + } + return false; + } + + // Push a byte to memory + function pushByte(value) { + memory.set(defaultCodePC, value & 0xff); + defaultCodePC++; + codeLen++; + } + + // Push a word to memory in little-endian order + function pushWord(value) { + pushByte(value & 0xff); + pushByte((value >> 8) & 0xff); + } + + function openPopup(content, title) { + var w = window.open('', title, 'width=500,height=300,resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no'); + + var html = ""; + html += ""; + html += "" + title + ""; + html += "
";
+
+      html += content;
+
+      html += "
"; + w.document.write(html); + w.document.close(); + } + + // Dump binary as hex to new window + function hexdump() { + openPopup(memory.format(0x600, codeLen), 'Hexdump'); + } + + // TODO: Create separate disassembler object? + var addressingModes = [ + null, + 'Imm', + 'ZP', + 'ZPX', + 'ZPY', + 'ABS', + 'ABSX', + 'ABSY', + 'IND', + 'INDX', + 'INDY', + 'SNGL', + 'BRA' + ]; + + var instructionLength = { + Imm: 2, + ZP: 2, + ZPX: 2, + ZPY: 2, + ABS: 3, + ABSX: 3, + ABSY: 3, + IND: 3, + INDX: 2, + INDY: 2, + SNGL: 1, + BRA: 2 + }; + + function getModeAndCode(byte) { + var index; + var line = Opcodes.filter(function (line) { + var possibleIndex = line.indexOf(byte); + if (possibleIndex > -1) { + index = possibleIndex; + return true; + } + })[0]; + + if (!line) { //instruction not found + return { + opCode: '???', + mode: 'SNGL' + }; + } else { + return { + opCode: line[0], + mode: addressingModes[index] + }; + } + } + + function createInstruction(address) { + var bytes = []; + var opCode; + var args = []; + var mode; + + function isAccumulatorInstruction() { + var accumulatorBytes = [0x0a, 0x4a, 0x2a, 0x6a]; + if (accumulatorBytes.indexOf(bytes[0]) > -1) { + return true; + } + } + + function isBranchInstruction() { + return opCode.match(/^B/) && !(opCode == 'BIT' || opCode == 'BRK'); + } + + //This is gnarly, but unavoidably so? + function formatArguments() { + var argsString = args.map(num2hex).reverse().join(''); + + if (isBranchInstruction()) { + var destination = address + 2; + if (args[0] > 0x7f) { + destination -= 0x100 - args[0]; + } else { + destination += args[0]; + } + argsString = addr2hex(destination); + } + + if (argsString) { + argsString = '$' + argsString; + } + if (mode == 'Imm') { + argsString = '#' + argsString; + } + if (mode.match(/X$/)) { + argsString += ',X'; + } + if (mode.match(/^IND/)) { + argsString = '(' + argsString + ')'; + } + if (mode.match(/Y$/)) { + argsString += ',Y'; + } + + if (isAccumulatorInstruction()) { + argsString = 'A'; + } + + return argsString; + } + + return { + addByte: function (byte) { + bytes.push(byte); + }, + setModeAndCode: function (modeAndCode) { + opCode = modeAndCode.opCode; + mode = modeAndCode.mode; + }, + addArg: function (arg) { + args.push(arg); + }, + toString: function () { + var bytesString = bytes.map(num2hex).join(' '); + var padding = Array(11 - bytesString.length).join(' '); + return '$' + addr2hex(address) + ' ' + bytesString + padding + opCode + + ' ' + formatArguments(args); + } + }; + } + + function disassemble() { + var startAddress = 0x600; + var currentAddress = startAddress; + var endAddress = startAddress + codeLen; + var instructions = []; + var length; + var inst; + var byte; + var modeAndCode; + + while (currentAddress < endAddress) { + inst = createInstruction(currentAddress); + byte = memory.get(currentAddress); + inst.addByte(byte); + + modeAndCode = getModeAndCode(byte); + length = instructionLength[modeAndCode.mode]; + inst.setModeAndCode(modeAndCode); + + for (var i = 1; i < length; i++) { + currentAddress++; + byte = memory.get(currentAddress); + inst.addByte(byte); + inst.addArg(byte); + } + instructions.push(inst); + currentAddress++; + } + + var html = 'Address Hexdump Dissassembly\n'; + html += '-------------------------------\n'; + html += instructions.join('\n'); + openPopup(html, 'Disassembly'); + } + + return { + assembleLine: assembleLine, + assembleCode: assembleCode, + getCurrentPC: function () { + return defaultCodePC; + }, + hexdump: hexdump, + disassemble: disassemble + }; + } + + + function addr2hex(addr) { + return num2hex((addr >> 8) & 0xff) + num2hex(addr & 0xff); + } + + function num2hex(nr) { + var str = "0123456789abcdef"; + var hi = ((nr & 0xf0) >> 4); + var lo = (nr & 15); + return str.substring(hi, hi + 1) + str.substring(lo, lo + 1); + } + + // Prints text in the message window + function message(text) { + if (text.length>1) + text += '\n'; // allow putc operations from the simulator (WDM opcode) + $node.find('.messages code').append(text).scrollTop(10000); + } + + initialize(); +} + +$(document).ready(function () { + $('.widget').each(function () { + SimulatorWidget(this); + }); +}); diff --git a/app/es5-shim.js b/app/es5-shim.js new file mode 100644 index 0000000..d768f9e --- /dev/null +++ b/app/es5-shim.js @@ -0,0 +1,1105 @@ +// vim: ts=4 sts=4 sw=4 expandtab +// -- kriskowal Kris Kowal Copyright (C) 2009-2011 MIT License +// -- tlrobinson Tom Robinson Copyright (C) 2009-2010 MIT License (Narwhal Project) +// -- dantman Daniel Friesen Copyright (C) 2010 XXX TODO License or CLA +// -- fschaefer Florian Schäfer Copyright (C) 2010 MIT License +// -- Gozala Irakli Gozalishvili Copyright (C) 2010 MIT License +// -- kitcambridge Kit Cambridge Copyright (C) 2011 MIT License +// -- kossnocorp Sasha Koss XXX TODO License or CLA +// -- bryanforbes Bryan Forbes XXX TODO License or CLA +// -- killdream Quildreen Motta Copyright (C) 2011 MIT Licence +// -- michaelficarra Michael Ficarra Copyright (C) 2011 3-clause BSD License +// -- sharkbrainguy Gerard Paapu Copyright (C) 2011 MIT License +// -- bbqsrc Brendan Molloy (C) 2011 Creative Commons Zero (public domain) +// -- iwyg XXX TODO License or CLA +// -- DomenicDenicola Domenic Denicola Copyright (C) 2011 MIT License +// -- xavierm02 Montillet Xavier Copyright (C) 2011 MIT License +// -- Raynos Jake Verbaten Copyright (C) 2011 MIT Licence +// -- samsonjs Sami Samhuri Copyright (C) 2010 MIT License +// -- rwldrn Rick Waldron Copyright (C) 2011 MIT License +// -- lexer Alexey Zakharov XXX TODO License or CLA + +/*! + Copyright (c) 2009, 280 North Inc. http://280north.com/ + MIT License. http://github.com/280north/narwhal/blob/master/README.md +*/ + +// Module systems magic dance +(function (definition) { + // RequireJS + if (typeof define == "function") { + define(definition); + // CommonJS and + + + + +0