/* * 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 */ /* eslint no-use-before-define: 0 */ // --> OFF '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).trigger('keydown', {which: 65}); $node.find('.disassembleButton').click(assembler.disassemble); $node.find('.debug').on('click', function () { $(this).toggleClass('active'); var debug = $(this).hasClass('active'); if (debug) { ui.debugOn(); simulator.enableDebugger(); } else { ui.debugOff(); simulator.stopDebugger(); } }); $node.find('.monitoring').on('click', function () { $(this).toggleClass('active'); if($(this).hasClass('active')) { } 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-container').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) + ' SP=$' + num2hex(regSP) + ' PC=$' + addr2hex(regPC) + ' 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 openDiv(content, title) { var w = document.querySelector('.banner'); var ele = document.createElement('div'); // var html = ""; // html += "" + title + ""; // html += "
";
      // html += content;
      // html += "
"; ele.innerHTML = '' + content + ''; w.appendChild(ele); } // Dump binary as hex to new window function hexdump() { openDiv(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'); openDiv(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); }); });