6502SimDesktop/app/assembler.js

2661 lines
66 KiB
JavaScript
Raw Permalink Normal View History

2017-03-10 20:41:50 +00:00
/*
* 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
*/
2017-03-29 21:40:22 +00:00
/* eslint no-use-before-define: 0 */ // --> OFF
2017-03-10 20:41:50 +00:00
'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);
2018-03-02 03:30:29 +00:00
$node.find('.hexdumpButton').click(assembler.hexdump).trigger('keydown', {which: 65});
2017-03-10 20:41:50 +00:00
$node.find('.disassembleButton').click(assembler.disassemble);
2017-03-30 01:33:28 +00:00
$node.find('.debug').on('click', function () {
$(this).toggleClass('active');
var debug = $(this).hasClass('active');
2017-03-10 20:41:50 +00:00
if (debug) {
ui.debugOn();
simulator.enableDebugger();
} else {
ui.debugOff();
simulator.stopDebugger();
}
});
2017-03-30 01:33:28 +00:00
$node.find('.monitoring').on('click', function () {
$(this).toggleClass('active');
if($(this).hasClass('active')) {
2018-03-02 03:30:29 +00:00
2017-03-30 01:33:28 +00:00
}
2017-03-10 20:41:50 +00:00
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() {
2017-03-30 01:33:28 +00:00
$node.find('.monitor-container').toggle();
2017-03-10 20:41:50 +00:00
}
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,
2018-03-02 03:30:29 +00:00
caretEnd = this.selectionEnd,
currentValue = this.value;
2017-03-10 20:41:50 +00:00
2018-03-02 03:30:29 +00:00
this.value = currentValue.substring(0, caretStart) + '\t' + currentValue.substring(caretEnd);
2017-03-10 20:41:50 +00:00
// 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 = [
2018-03-02 03:30:29 +00:00
'#000000', '#ffffff', '#880000', '#aaffee',
'#cc44cc', '#00cc55', '#0000aa', '#eeee77',
'#dd8855', '#664400', '#ff7777', '#333333',
'#777777', '#aaff66', '#0088ff', '#bbbbbb'
2017-03-10 20:41:50 +00:00
];
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() {
2018-03-02 03:30:29 +00:00
ctx.fillStyle = 'black';
2017-03-10 20:41:50 +00:00
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) {
2018-03-02 03:30:29 +00:00
if (x > 0) { html += '\n'; }
2017-03-10 20:41:50 +00:00
n = (start + x);
html += num2hex(((n >> 8) & 0xff));
html += num2hex((n & 0xff));
2018-03-02 03:30:29 +00:00
html += ': ';
2017-03-10 20:41:50 +00:00
}
html += num2hex(memory.get(start + x));
2018-03-02 03:30:29 +00:00
html += ' ';
2017-03-10 20:41:50 +00:00
}
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;
2018-03-02 03:30:29 +00:00
throw new Error('Interrupts not implemented');
2017-03-10 20:41:50 +00:00
//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;
2018-03-02 03:30:29 +00:00
throw new Error('Interrupts not implemented');
2017-03-10 20:41:50 +00:00
//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 () {
2018-03-02 03:30:29 +00:00
message('Address $' + addr2hex(regPC) + ' - unknown opcode');
2017-03-10 20:41:50 +00:00
codeRunning = false;
}
};
function stackPush(value) {
memory.set((regSP & 0xff) + 0x100, value & 0xff);
regSP--;
if (regSP < 0) {
regSP &= 0xff;
2018-03-02 03:30:29 +00:00
message('6502 Stack filled! Wrapping...');
2017-03-10 20:41:50 +00:00
}
}
function stackPop() {
var value;
regSP++;
if (regSP >= 0x100) {
regSP &= 0xff;
2018-03-02 03:30:29 +00:00
message('6502 Stack emptied! Wrapping...');
2017-03-10 20:41:50 +00:00
}
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();
2018-03-02 03:30:29 +00:00
message('Program end at PC=$' + addr2hex(regPC - 1));
2017-03-10 20:41:50 +00:00
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'),
2018-03-02 03:30:29 +00:00
$length = $node.find('.length'),
start = parseInt($start.val(), 16),
length = parseInt($length.val(), 16),
end = start + length - 1;
2017-03-10 20:41:50 +00:00
$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) {
2018-03-02 03:30:29 +00:00
execute(true);
2017-03-10 20:41:50 +00:00
//}
updateDebugInfo();
}
function updateDebugInfo() {
2018-03-02 03:30:29 +00:00
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--) {
2017-03-10 20:41:50 +00:00
html += regP >> i & 1;
}
$node.find('.minidebugger').html(html);
updateMonitor();
}
// gotoAddr() - Set PC to address (or address of label)
function gotoAddr() {
2018-03-02 03:30:29 +00:00
var inp = prompt('Enter address or label', '');
2017-03-10 20:41:50 +00:00
var addr = 0;
if (labels.find(inp)) {
addr = labels.getPC(inp);
} else {
if (inp.match(/^0x[0-9a-f]{1,4}$/i)) {
2018-03-02 03:30:29 +00:00
inp = inp.replace(/^0x/, '');
2017-03-10 20:41:50 +00:00
addr = parseInt(inp, 16);
} else if (inp.match(/^\$[0-9a-f]{1,4}$/i)) {
2018-03-02 03:30:29 +00:00
inp = inp.replace(/^\$/, '');
2017-03-10 20:41:50 +00:00
addr = parseInt(inp, 16);
}
}
if (addr === 0) {
2018-03-02 03:30:29 +00:00
message('Unable to find/parse given address/label');
2017-03-10 20:41:50 +00:00
} 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);
2018-03-02 03:30:29 +00:00
message('\nStopped\n');
2017-03-10 20:41:50 +00:00
}
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)) {
2018-03-02 03:30:29 +00:00
message('**Label already defined at line ' + (i + 1) + ':** ' + lines[i]);
2017-03-10 20:41:50 +00:00
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) {
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// 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+:/)) {
2018-03-02 03:30:29 +00:00
var label = input.replace(/(^\w+):.*$/, '$1');
2017-03-10 20:41:50 +00:00
if (symbols.lookup(label)) {
2018-03-02 03:30:29 +00:00
message('**Label ' + label + 'is already used as a symbol; please rename one of them**');
2017-03-10 20:41:50 +00:00
return false;
}
2018-03-02 03:30:29 +00:00
return push(label + '|' + currentPC);
2017-03-10 20:41:50 +00:00
}
return true;
}
// Push label to array. Return false if label already exists.
function push(name) {
if (find(name)) {
return false;
}
2018-03-02 03:30:29 +00:00
labelIndex.push(name + '|');
2017-03-10 20:41:50 +00:00
return true;
}
// Returns true if label exists.
function find(name) {
var nameAndAddr;
for (var i = 0; i < labelIndex.length; i++) {
2018-03-02 03:30:29 +00:00
nameAndAddr = labelIndex[i].split('|');
2017-03-10 20:41:50 +00:00
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++) {
2018-03-02 03:30:29 +00:00
nameAndAddr = labelIndex[i].split('|');
2017-03-10 20:41:50 +00:00
if (name === nameAndAddr[0]) {
2018-03-02 03:30:29 +00:00
labelIndex[i] = name + '|' + addr;
2017-03-10 20:41:50 +00:00
return true;
}
}
return false;
}
// Get address associated with label
function getPC(name) {
var nameAndAddr;
for (var i = 0; i < labelIndex.length; i++) {
2018-03-02 03:30:29 +00:00
nameAndAddr = labelIndex[i].split('|');
2017-03-10 20:41:50 +00:00
if (name === nameAndAddr[0]) {
return (nameAndAddr[1]);
}
}
return -1;
}
function displayMessage() {
2018-03-02 03:30:29 +00:00
var str = 'Found ' + labelIndex.length + ' label';
2017-03-10 20:41:50 +00:00
if (labelIndex.length !== 1) {
2018-03-02 03:30:29 +00:00
str += 's';
2017-03-10 20:41:50 +00:00
}
2018-03-02 03:30:29 +00:00
message(str + '.');
2017-03-10 20:41:50 +00:00
}
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 */
2018-03-02 03:30:29 +00:00
['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]
2017-03-10 20:41:50 +00:00
];
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// Assembles the code into memory
function assembleCode() {
var BOOTSTRAP_ADDRESS = 0x600;
wasOutOfRangeBranch = false;
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
simulator.reset();
labels.reset();
defaultCodePC = BOOTSTRAP_ADDRESS;
$node.find('.messages code').empty();
var code = $node.find('.code').val();
2018-03-02 03:30:29 +00:00
code += '\n\n';
var lines = code.split('\n');
2017-03-10 20:41:50 +00:00
codeAssembledOK = true;
2018-03-02 03:30:29 +00:00
message('Preprocessing ...');
2017-03-10 20:41:50 +00:00
var symbols = preprocess(lines);
2018-03-02 03:30:29 +00:00
message('Indexing labels ...');
2017-03-10 20:41:50 +00:00
defaultCodePC = BOOTSTRAP_ADDRESS;
if (!labels.indexLines(lines, symbols)) {
return false;
}
labels.displayMessage();
defaultCodePC = BOOTSTRAP_ADDRESS;
2018-03-02 03:30:29 +00:00
message('Assembling code ...');
2017-03-10 20:41:50 +00:00
codeLen = 0;
for (var i = 0; i < lines.length; i++) {
if (!assembleLine(lines[i], i, symbols)) {
codeAssembledOK = false;
break;
}
}
if (codeLen === 0) {
codeAssembledOK = false;
2018-03-02 03:30:29 +00:00
message('No code to run.');
2017-03-10 20:41:50 +00:00
}
if (codeAssembledOK) {
ui.assembleSuccess();
memory.set(defaultCodePC, 0x00); //set a null byte at the end of the code
} else {
2018-03-02 03:30:29 +00:00
var str = lines[i].replace('<', '&lt;').replace('>', '&gt;');
2017-03-10 20:41:50 +00:00
if(!wasOutOfRangeBranch) {
2018-03-02 03:30:29 +00:00
message('**Syntax error line ' + (i + 1) + ': ' + str + '**');
2017-03-10 20:41:50 +00:00
} else {
message('**Out of range branch on line ' + (i + 1) + ' (branches are limited to -128 to +127): ' + str + '**');
}
ui.initialize();
return false;
}
2018-03-02 03:30:29 +00:00
message('Code assembled successfully, ' + codeLen + ' bytes.');
2017-03-10 20:41:50 +00:00
return true;
}
// Sanitize input: remove comments and trim leading/trailing whitespace
function sanitize(line) {
// remove comments
2018-03-02 03:30:29 +00:00
var no_comments = line.replace(/^(.*?);.*/, '$1');
2017-03-10 20:41:50 +00:00
// trim line
2018-03-02 03:30:29 +00:00
return no_comments.replace(/^\s+/, '').replace(/\s+$/, '');
2017-03-10 20:41:50 +00:00
}
function preprocess(lines) {
var table = [];
2018-03-02 03:30:29 +00:00
var PREFIX = '__'; // Using a prefix avoids clobbering any predefined properties
2017-03-10 20:41:50 +00:00
function lookup(key) {
if (table.hasOwnProperty(PREFIX + key)) return table[PREFIX + key];
else return undefined;
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
function add(key, value) {
var valueAlreadyExists = table.hasOwnProperty(PREFIX + key)
if (!valueAlreadyExists) {
table[PREFIX + key] = value;
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// 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]));
2018-03-02 03:30:29 +00:00
lines[i] = ''; // We're done with this preprocessor directive, so delete it
2017-03-10 20:41:50 +00:00
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// Callers will only need the lookup function
return {
lookup: lookup
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// 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+:/)) {
2018-03-02 03:30:29 +00:00
label = input.replace(/(^\w+):.*$/, '$1');
2017-03-10 20:41:50 +00:00
if (input.match(/^\w+:[\s]*\w+.*$/)) {
2018-03-02 03:30:29 +00:00
input = input.replace(/^\w+:[\s]*(.*)$/, '$1');
command = input.replace(/^(\w+).*$/, '$1');
2017-03-10 20:41:50 +00:00
} else {
2018-03-02 03:30:29 +00:00
command = '';
2017-03-10 20:41:50 +00:00
}
} else {
2018-03-02 03:30:29 +00:00
command = input.replace(/^(\w+).*$/, '$1');
2017-03-10 20:41:50 +00:00
}
// Nothing to do for blank lines
2018-03-02 03:30:29 +00:00
if (command === '') {
2017-03-10 20:41:50 +00:00
return true;
}
command = command.toUpperCase();
if (input.match(/^\*\s*=\s*\$?[0-9a-f]*$/)) {
// equ spotted
2018-03-02 03:30:29 +00:00
param = input.replace(/^\s*\*\s*=\s*/, '');
if (param[0] === '$') {
param = param.replace(/^\$/, '');
2017-03-10 20:41:50 +00:00
addr = parseInt(param, 16);
} else {
addr = parseInt(param, 10);
}
if ((addr < 0) || (addr > 0xffff)) {
2018-03-02 03:30:29 +00:00
message('Unable to relocate code outside 64k memory');
2017-03-10 20:41:50 +00:00
return false;
}
defaultCodePC = addr;
return true;
}
if (input.match(/^\w+\s+.*?$/)) {
2018-03-02 03:30:29 +00:00
param = input.replace(/^\w+\s+(.*?)/, '$1');
2017-03-10 20:41:50 +00:00
} else if (input.match(/^\w+$/)) {
2018-03-02 03:30:29 +00:00
param = '';
2017-03-10 20:41:50 +00:00
} else {
return false;
}
2018-03-02 03:30:29 +00:00
param = param.replace(/[ ]/g, '');
2017-03-10 20:41:50 +00:00
2018-03-02 03:30:29 +00:00
if (command === 'DCB') {
2017-03-10 20:41:50 +00:00
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; }
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
return false; // Unknown syntax
}
function DCB(param) {
var values, number, str, ch;
2018-03-02 03:30:29 +00:00
values = param.split(',');
2017-03-10 20:41:50 +00:00
if (values.length === 0) { return false; }
for (var v = 0; v < values.length; v++) {
str = values[v];
if (str) {
ch = str.substring(0, 1);
2018-03-02 03:30:29 +00:00
if (ch === '$') {
number = parseInt(str.replace(/^\$/, ''), 16);
2017-03-10 20:41:50 +00:00
pushByte(number);
2018-03-02 03:30:29 +00:00
} else if (ch >= '0' && ch <= '9') {
2017-03-10 20:41:50 +00:00
number = parseInt(str, 10);
pushByte(number);
} else {
return false;
}
}
}
return true;
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// 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;
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
var value;
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// 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);
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// Validate range
if (value >= 0 && value <= 0xff) {
return value;
} else {
2018-03-02 03:30:29 +00:00
return -1;
2017-03-10 20:41:50 +00:00
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// 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;
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
var value;
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// 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);
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// 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) {
2018-03-02 03:30:29 +00:00
wasOutOfRangeBranch = true;
return false;
2017-03-10 20:41:50 +00:00
}
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; }
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
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;
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
// Label lo/hi
if (param.match(/^#[<>]\w+$/)) {
2018-03-02 03:30:29 +00:00
label = param.replace(/^#[<>](\w+)$/, '$1');
hilo = param.replace(/^#([<>]).*$/, '$1');
2017-03-10 20:41:50 +00:00
pushByte(opcode);
if (labels.find(label)) {
addr = labels.getPC(label);
switch(hilo) {
2018-03-02 03:30:29 +00:00
case '>':
pushByte((addr >> 8) & 0xff);
return true;
case '<':
pushByte(addr & 0xff);
return true;
default:
return false;
2017-03-10 20:41:50 +00:00
}
} else {
pushByte(0x00);
return true;
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
return false;
}
// Check if param is indirect and push value
function checkIndirect(param, opcode, symbols) {
var value;
if (opcode === null) { return false; }
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
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; }
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
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; }
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
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
2018-03-02 03:30:29 +00:00
if (param !== '' && param !== 'A') { return false; }
2017-03-10 20:41:50 +00:00
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;
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
return false;
}
// Check if param is ABSX and push value
function checkAbsoluteX(param, opcode, symbols) {
var number, value, addr;
if (opcode === null) { return false; }
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
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)) {
2018-03-02 03:30:29 +00:00
param = param.replace(/,X$/i, '');
2017-03-10 20:41:50 +00:00
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; }
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
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)) {
2018-03-02 03:30:29 +00:00
param = param.replace(/,Y$/i, '');
2017-03-10 20:41:50 +00:00
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; }
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
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;
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
return false;
}
// Check if param is ZPY and push value
function checkZeroPageY(param, opcode, symbols) {
var number, value;
if (opcode === null) { return false; }
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
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;
}
}
2018-03-02 03:30:29 +00:00
2017-03-10 20:41:50 +00:00
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);
}
2017-03-29 21:40:22 +00:00
function openDiv(content, title) {
var w = document.querySelector('.banner');
var ele = document.createElement('div');
// var html = "<link href='style.css' rel='stylesheet' type='text/css' />";
// html += "<title>" + title + "</title></head><body>";
// html += "<pre><code>";
// html += content;
// html += "</code></pre></body></html>";
ele.innerHTML = '<code>' + content + '</code>';
w.appendChild(ele);
2017-03-10 20:41:50 +00:00
}
// Dump binary as hex to new window
function hexdump() {
2017-03-29 21:40:22 +00:00
openDiv(memory.format(0x600, codeLen), 'Hexdump');
2017-03-10 20:41:50 +00:00
}
// 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');
2017-03-29 21:40:22 +00:00
openDiv(html, 'Disassembly');
2017-03-10 20:41:50 +00:00
}
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) {
2018-03-02 03:30:29 +00:00
var str = '0123456789abcdef';
2017-03-10 20:41:50 +00:00
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) {
2018-03-02 03:30:29 +00:00
if (text.length > 1)
2017-03-10 20:41:50 +00:00
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);
});
});