diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f11fd25 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2012 John McCann + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..ae20e63 --- /dev/null +++ b/index.html @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/javascript/assembler.js b/javascript/assembler.js new file mode 100644 index 0000000..395e9e8 --- /dev/null +++ b/javascript/assembler.js @@ -0,0 +1,81 @@ + +Tokenizer = { + + tokens: [ + { pattern: /^(;.*)/, type: "comment" }, + { pattern: /^\b(0x[0-9ABCDEF]+)\b/i, type: "hexidecimal" }, + { pattern: /^\b([0-9]+)\b/, type: "decimal" }, + { pattern: /^(\".*\")/, type: "string" }, + { pattern: /^(:[0-9A-Za-z_]+)/, type: "label_def" }, + { pattern: /^\b(POP|PUSH|PEEK|DAT)\b/i, type: "reserved_word" }, + { pattern: /^\b(SET|ADD|SUB|MUL|MLI|DIV|DVI|JSR)\b/i, + type: "command" }, + { pattern: /^\b([ABCXYZIJ]|SP|PC|EX)\b/i, type: "register" }, + { pattern: /^\b([0-9A-Za-z_]+)\b/, type: "label_ref" }, + { pattern: /^(\[)/, type: "open_bracket" }, + { pattern: /^(\])/, type: "close_bracket" }, + { pattern: /^(,)/, type: "comma" }, + { pattern: /^(\+|\-|\*)/, type: "operator" }, + { pattern: /^([\s]+)/, type: "space" }, + ], + + tokenize: function(input) { + + var lines = input.split("\n"); + var tokenizedLines = []; + for(var i = 0; i < lines.length; i++) { + var line = lines[i]; + var tokenizedLine = []; + + while(line != null && line.length > 0) { + + //console.log("tokenizing ", line); + + var lexeme = null; + var match = null; + var token = null; + + for(var p = 0; p < this.tokens.length; p++) { + token = this.tokens[p]; + match = token.pattern.exec(line); + if(match) break; + } + + if(match && match[1].length > 0) { + //console.log("token", match); + + lexeme = match[1]; + tokenizedLine.push( { lexeme: lexeme, type: token.type } ); + + line = line.substr(lexeme.length) + } + else { + throw ("Invlid token '" + line + "' on line " + (i+1)); + line = null; + } + } + if(tokenizedLine.length > 0) + tokenizedLines.push(tokenizedLine); + } + return tokenizedLines; + }, + + htmlFormatTokens: function(tokenizedLines) { + var html = ""; + for(var i = 0; i < tokenizedLines.length; i++) { + var tokenizedLine = tokenizedLines[i]; + for(var j = 0; j < tokenizedLine.length; j++) { + var token = tokenizedLine[j]; + html += this.formatToken(token); + } + html += "
"; + } + return html; + }, + + formatToken: function(token) { + var str = token.lexeme.replace(/ /g, " "); + return "" + str + ""; + } + +} \ No newline at end of file diff --git a/javascript/emulator.js b/javascript/emulator.js new file mode 100644 index 0000000..29b7fc2 --- /dev/null +++ b/javascript/emulator.js @@ -0,0 +1,842 @@ + +// http://pastebin.com/raw.php?i=Q4JvQvnM +// https://github.com/gibbed/0x10c-Notes + +function Register(_name, _value, _emulator) { + this.name = _name; + this.value = _value; + this.emulator = _emulator; + this.contents = 0; +} +Register.prototype.get = function() { return this.contents; } +Register.prototype.set = function(val) { this.contents = val; } + +function RegisterValue(_register) { + this.register = _register; + this.emulator = _register.emulator; +} +RegisterValue.prototype.get = function() { + return this.emulator.RAM[this.register.get()]; +} +RegisterValue.prototype.set = function(val) { + this.emulator.RAM[this.register.get()] = val; +} + +function RegisterPlusNextWord(_register) { + this.register = _register; + this.emulator = _register.emulator; +} +RegisterPlusNextWord.prototype.get = function() { + return this.emulator.RAM[this.register.get() + this.emulator.nextWord()]; +} +RegisterPlusNextWord.prototype.set = function(val) { + this.emulator.RAM[this.register.get() + this.emulator.nextWord()] = val; +} + + +function StackPointerValue(_emulator) { + this.emulator = _emulator +} +StackPointerValue.prototype.get = function() { + return this.emulator.Registers.SP.pop(); +} +StackPointerValue.prototype.set = function(val) { + this.emulator.Registers.SP.push(val); +} + +function Literal(_value) { + this.value = _value; +} +Literal.prototype.get = function() { return this.value; } +Literal.prototype.set = function(val) { } +Literals = { }; + +function Op(_emulator, _name, _value, _cycles, __exec, _set) { + this.emulator = _emulator; + this.name = _name; + this.value = _value; + this.cycles = _cycles; + this._exec = __exec; + _set = _set || this.emulator.OpSet; + _set[this.value] = this; +} +Op.prototype.exec = function(a, b) { + var valA = this.emulator.Values[new String(a)]; + var valB = this.emulator.Values[new String(b)]; + + if(!valA) throw new Error("Invalid 'a' value " + a); + if(!valB) throw new Error("Invalid 'b' value " + b); + + this._exec(valA, valB); + this.emulator.CPU_CYCLE += this.cycles; +}; + +// literals +for(var i = 0x20, literalVal = -1; i < 0x40; i++, literalVal++) { + Literals["L_" + literalVal] = i; +} + +// convenience constants +Values = { }; +Values.REGISTER_VALUE_OFFSET = 0x08; +Values.REGISTER_NEXT_WORD_OFFSET = 0x10; +Values.SP_OFFSET = 0x18; +Values.NEXT_WORD_VALUE = 0x1e; +Values.NEXT_WORD_LITERAL = 0x1f; +Values.SP = 0x1b; +Values.PC = 0x1c; +Values.EX = 0x1d; + +REGISTER_A = 0x00; +REGISTER_B = 0x01; +REGISTER_C = 0x02; +REGISTER_X = 0x03; +REGISTER_Y = 0x04; +REGISTER_Z = 0x05; +REGISTER_I = 0x06; +REGISTER_J = 0x07; +REGISTER_SP = 0x1b; +REGISTER_PC = 0x1c; +REGISTER_EX = 0x1d; + +OPERATION_SET = 0x01; +OPERATION_ADD = 0x02; +OPERATION_SUB = 0x03; +OPERATION_MUL = 0x04; +OPERATION_MLI = 0x05; +OPERATION_DIV = 0x06; +OPERATION_DVI = 0x07; +OPERATION_MOD = 0x08; +OPERATION_MDI = 0x09; +OPERATION_AND = 0x0a; +OPERATION_BOR = 0x0b; +OPERATION_XOR = 0x0c; +OPERATION_SHR = 0x0d; +OPERATION_ASR = 0x0e; +OPERATION_SHL = 0x0f; + +OPERATION_IFB = 0x10; +OPERATION_IFC = 0x11; +OPERATION_IFE = 0x12; +OPERATION_IFN = 0x13; +OPERATION_IFG = 0x14; +OPERATION_IFA = 0x15; +OPERATION_IFL = 0x16; +OPERATION_IFU = 0x17; + +OPERATION_ADX = 0x1a; +OPERATION_SBX = 0x1b; + +OPERATION_STI = 0x1e; +OPERATION_STD = 0x1f; + +OPERATION_JSR = 0x01; +OPERATION_INT = 0x08; +OPERATION_IAG = 0x09; +OPERATION_IAS = 0x0a; +OPERATION_RFI = 0x0b; +OPERATION_IAQ = 0x0c; + +OPERATION_HWN = 0x10; +OPERATION_HWQ = 0x11; +OPERATION_HWI = 0x12; + + + +Utils = { + to32BitSigned: function(val) { + if((val & 0x8000) > 0) { + return (((~val) + 1) & 0xffff) * -1; // two's complement + } + return val; + }, + + to16BitSigned: function(val) { + if(val < 0) { + //return ((~val) + 1) & 0xffff; // two's complement + return ((val & 0x7fff) | 0x8000); + } + return val & 0xffff; + }, + + roundTowardsZero: function(val) { + if(val < 0) + val = Math.ceil(val); + else + val = Math.floor(val); + return val; + }, + + makeInstruction: function(opcode, a, b) { + var instruction = opcode; + instruction |= (b << 5); + instruction |= (a << 10); + return instruction; + }, + + makeSpecialInstruction: function(opcode, a) { + var instruction = 0; + instruction |= (a << 10); + instruction |= (opcode << 5); + return instruction; + }, + + parseInstruction: function(instruction) { + return { + opcode: instruction & 0x001f, + b: (instruction & 0x03e0) >> 5, + a: (instruction & 0xfc00) >> 10 + } + }, + + parseSpecialInstruction: function(instruction) { + return { + a: (instruction & 0xfc00) >> 10, + opcode: (instruction & 0x03e0) >> 5, + b: 0 + } + }, + + hex: function(num) { + return "0x" + num.toString(16); + }, + + makeVideoCell: function(glyph, blink, bg, fg) { + var result = glyph & 0x7f; + result |= (blink & 0x1) << 7; + result |= (bg & 0xf) << 8; + result |= (fg & 0xf) << 12; + return result; + }, + + makeColor: function(d) { + var hex = Number(d).toString(16); + hex = "000000".substr(0, 6 - hex.length) + hex; + return "#" + hex; + } + +}; + + +function Emulator() { + + this.async = false; + + this.CPU_CYCLE = 0; + this.RAM = []; + + this.OpSet = { }; + this.SpecialOpSet = { }; + this.Registers = { + A: new Register("A", REGISTER_A, this), + B: new Register("B", REGISTER_B, this), + C: new Register("C", REGISTER_C, this), + X: new Register("X", REGISTER_X, this), + Y: new Register("Y", REGISTER_Y, this), + Z: new Register("Z", REGISTER_Z, this), + I: new Register("I", REGISTER_I, this), + J: new Register("J", REGISTER_J, this), + SP: new Register("SP", REGISTER_SP, this), + PC: new Register("PC", REGISTER_PC, this), + EX: new Register("EX", REGISTER_EX, this), + IA: new Register("IA", 0xffff, this), + }; + + + this.Registers.PC.inc = function() { + var v = this.get(); + this.set(v+1); + return v; + }; + this.PC = this.Registers.PC; + + this.Registers.SP.push = function(val) { + this.emulator.RAM[--this.contents] = val; + }; + this.Registers.SP.pop = function() { + return this.emulator.RAM[this.contents++]; + }; + + + this.Values = { } + this.Values[0x00] = this.Registers.A; + this.Values[0x01] = this.Registers.B; + this.Values[0x02] = this.Registers.C; + this.Values[0x03] = this.Registers.X; + this.Values[0x04] = this.Registers.Y; + this.Values[0x05] = this.Registers.Z; + this.Values[0x06] = this.Registers.I; + this.Values[0x07] = this.Registers.J; + this.Values[0x08] = new RegisterValue(this.Registers.A); + this.Values[0x09] = new RegisterValue(this.Registers.B); + this.Values[0x0a] = new RegisterValue(this.Registers.C); + this.Values[0x0b] = new RegisterValue(this.Registers.X); + this.Values[0x0c] = new RegisterValue(this.Registers.Y); + this.Values[0x0d] = new RegisterValue(this.Registers.Z); + this.Values[0x0e] = new RegisterValue(this.Registers.I); + this.Values[0x0f] = new RegisterValue(this.Registers.J); + this.Values[0x10] = new RegisterPlusNextWord(this.Registers.A); + this.Values[0x11] = new RegisterPlusNextWord(this.Registers.B); + this.Values[0x12] = new RegisterPlusNextWord(this.Registers.C); + this.Values[0x13] = new RegisterPlusNextWord(this.Registers.X); + this.Values[0x14] = new RegisterPlusNextWord(this.Registers.Y); + this.Values[0x15] = new RegisterPlusNextWord(this.Registers.Z); + this.Values[0x16] = new RegisterPlusNextWord(this.Registers.I); + this.Values[0x17] = new RegisterPlusNextWord(this.Registers.J); + this.Values[0x18] = new StackPointerValue(this); + this.Values[0x19] = new RegisterValue(this.Registers.SP); + this.Values[0x1a] = new RegisterPlusNextWord(this.Registers.SP); + this.Values[0x1b] = this.Registers.SP; + this.Values[0x1c] = this.Registers.PC; + this.Values[0x1d] = this.Registers.EX; + this.Values[0x1e] = { // next word value + emulator: this, + get: function() { return this.emulator.RAM[this.emulator.nextWord()]; }, + set: function(val) { this.emulator.RAM[this.emulator.nextWord()] = val; } + }; + this.Values[0x1f] = { // next word literal + emulator: this, + get: function() { return this.emulator.nextWord(); }, + set: function(val) { } + }; + + for(var i = 0x20, literalVal = -1; i < 0x40; i++, literalVal++) { + this.Values[i] = new Literal(literalVal); + } + + + this.BasicOperations = { + SET: new Op(this, "SET", OPERATION_SET, 1, function(a, b) { + b.set(a.get()); + }), + + ADD: new Op(this, "ADD", OPERATION_ADD, 2, function(a, b) { + var res = a.get() + b.get(); + b.set(res & 0xffff); + if((res & 0xffff0000) > 0) + this.emulator.Registers.EX.set(0x0001); + else + this.emulator.Registers.EX.set(0); + }), + + SUB: new Op(this, "SUB", OPERATION_SUB, 2, function(a, b) { + var aVal = a.get(); + var res = b.get() - aVal; + b.set(res & 0xffff); + if((res) < 0) + this.emulator.Registers.EX.set(0xffff); + else + this.emulator.Registers.EX.set(0); + + }), + + MUL: new Op(this, "MUL", OPERATION_MUL, 2, function(a, b) { + var res = a.get() * b.get(); + b.set(res & 0xffff); + this.emulator.Registers.EX.set((res >> 16) & 0xffff); + }), + + MLI: new Op(this, "MLI", OPERATION_MLI, 2, function(a, b) { + var aVal = Utils.to32BitSigned(a.get()), bVal = Utils.to32BitSigned(b.get()); + var res = bVal * aVal; + b.set(Utils.to16BitSigned(res)); + this.emulator.Registers.EX.set((res >> 16) & 0xffff); + }), + + DIV: new Op(this, "DIV", OPERATION_DIV, 3, function(a, b) { + var aVal = a.get(), bVal = b.get(); + if(aVal === 0) { + b.set(0); + this.emulator.Registers.EX.set(0); + } + else { + var res = Math.floor(bVal / aVal); + b.set(res & 0xffff); + this.emulator.Registers.EX.set(Math.floor(((bVal << 16) / aVal)) & 0xffff); + } + }), + + DVI: new Op(this, "DVI", OPERATION_DVI, 3, function(a, b) { + var aVal = Utils.to32BitSigned(a.get()), bVal = Utils.to32BitSigned(b.get()); + if(aVal === 0) { + b.set(0); + this.emulator.Registers.EX.set(0); + } + else { + var res = Utils.roundTowardsZero(bVal / aVal); + b.set(Utils.to16BitSigned(res)); + this.emulator.Registers.EX.set(Utils.roundTowardsZero(((bVal << 16) / aVal)) & 0xffff); + } + }), + + MOD: new Op(this, "MOD", OPERATION_MOD, 3, function(a, b) { + var aVal = a.get(), bVal = b.get(); + if(aVal === 0) + b.set(0); + else + b.set(bVal % aVal); + }), + + MDI: new Op(this, "MDI", OPERATION_MDI, 3, function(a, b) { + var aVal = Utils.to32BitSigned(a.get()), bVal = Utils.to32BitSigned(b.get()); + if(aVal === 0) + b.set(0); + else + b.set(Utils.to16BitSigned(bVal % aVal)); + }), + + AND: new Op(this, "AND", OPERATION_AND, 1, function(a, b) { + var aVal = a.get(), bVal = b.get(); + b.set(bVal & aVal); + }), + + BOR: new Op(this, "BND", OPERATION_BOR, 1, function(a, b) { + var aVal = a.get(), bVal = b.get(); + b.set(bVal | aVal); + }), + + XOR: new Op(this, "XOR", OPERATION_XOR, 1, function(a, b) { + var aVal = a.get(), bVal = b.get(); + b.set(bVal ^ aVal); + }), + + SHR: new Op(this, "SHR", OPERATION_SHR, 1, function(a, b) { + var aVal = a.get(), bVal = b.get(); + b.set(bVal >>> aVal); + this.emulator.Registers.EX.set(((bVal << 16 ) >> aVal) & 0xffff); + }), + + ASR: new Op(this, "ASR", OPERATION_ASR, 1, function(a, b) { + var aVal = a.get(), bVal = Utils.to32BitSigned(b.get()); + b.set((bVal >> aVal) & 0xffff); + this.emulator.Registers.EX.set(((bVal << 16) >>> aVal) & 0xffff); + }), + + SHL: new Op(this, "SHL", OPERATION_SHL, 1, function(a, b) { + var aVal = a.get(), bVal = b.get(); + b.set((bVal << aVal) & 0xffff); + this.emulator.Registers.EX.set(((bVal << aVal) >> 16) & 0xffff); + }), + + IFB: new Op(this, "IFB", OPERATION_IFB, 2, function(a, b) { + var aVal = a.get(), bVal = b.get(); + if((bVal & aVal) != 0) { } + else this.emulator.skipInstruction(); + + }), + + IFC: new Op(this, "IFC", OPERATION_IFC, 2, function(a, b) { + var aVal = a.get(), bVal = b.get(); + if((bVal & aVal) === 0) { } + else this.emulator.skipInstruction(); + + }), + + IFE: new Op(this, "IFE", OPERATION_IFE, 2, function(a, b) { + var aVal = a.get(), bVal = b.get(); + if(bVal === aVal) { } + else this.emulator.skipInstruction(); + }), + + IFN: new Op(this, "IFN", OPERATION_IFN, 2, function(a, b) { + var aVal = a.get(), bVal = b.get(); + if(bVal !== aVal) { } + else this.emulator.skipInstruction(); + }), + + IFG: new Op(this, "IFG", OPERATION_IFG, 2, function(a, b) { + var aVal = a.get(), bVal = b.get(); + if(bVal > aVal) { } + else this.emulator.skipInstruction(); + }), + + IFA: new Op(this, "IFA", OPERATION_IFA, 2, function(a, b) { + var aVal = Utils.to32BitSigned(a.get()), bVal = Utils.to32BitSigned(b.get()); + if(bVal > aVal) { } + else this.emulator.skipInstruction(); + }), + + IFL: new Op(this, "IFL", OPERATION_IFL, 2, function(a, b) { + var aVal = a.get(), bVal = b.get(); + if(bVal < aVal) { } + else this.emulator.skipInstruction(); + }), + + IFU: new Op(this, "IFU", OPERATION_IFU, 2, function(a, b) { + var aVal = Utils.to32BitSigned(a.get()), bVal = Utils.to32BitSigned(b.get()); + if(bVal < aVal) { } + else this.emulator.skipInstruction(); + }), + + + ADX: new Op(this, "ADX", OPERATION_ADX, 3, function(a, b) { + var res = a.get() + b.get() + this.emulator.Registers.EX.get(); + b.set(res & 0xffff); + this.emulator.Registers.EX.set(res > 0xffff ? 1 : 0); + }), + + SBX: new Op(this, "SBX", OPERATION_SBX, 3, function(a, b) { + var aVal = a.get(), bVal = b.get(); + var res = bVal - aVal + this.emulator.Registers.EX.get(); + b.set(res & 0xffff); + this.emulator.Registers.EX.set(res < 0 ? 0xffff : 0); + }), + + STI: new Op(this, "STI", OPERATION_STI, 2, function(a, b) { + var aVal = a.get(), bVal = b.get(); + b.set(aVal); + a.set(bVal); + this.emulator.Registers.I.set((this.emulator.Registers.I.get() + 1) & 0xffff); + this.emulator.Registers.J.set((this.emulator.Registers.J.get() + 1) & 0xffff); + }), + + STD: new Op(this, "STD", OPERATION_STD, 2, function(a, b) { + var aVal = a.get(), bVal = b.get(); + b.set(aVal); + a.set(bVal); + this.emulator.Registers.I.set((this.emulator.Registers.I.get() - 1) & 0xffff); + this.emulator.Registers.J.set((this.emulator.Registers.J.get() - 1) & 0xffff); + }), + + JSR: new Op(this, "JSR", OPERATION_JSR, 3, function(a) { + var aVal = a.get(); + this.emulator.Registers.SP.push(this.emulator.Registers.PC.get()); + this.emulator.Registers.PC.set(aVal); + }, this.SpecialOpSet), + + INT: new Op(this, "INT", OPERATION_INT, 4, function(a) { + var aVal = a.get(); + this.emulator.interruptQueue.push(aVal); + }, this.SpecialOpSet), + + IAG: new Op(this, "IAG", OPERATION_IAG, 1, function(a) { + a.set(this.emulator.Registers.IA.get()); + }, this.SpecialOpSet), + + IAS: new Op(this, "IAS", OPERATION_IAS, 1, function(a) { + this.emulator.Registers.IA.set(a.get()); + }, this.SpecialOpSet), + + RFI: new Op(this, "RFI", OPERATION_RFI, 3, function(a) { + this.emulator.interruptQueueingEnabled = false; + this.emulator.Registers.A.set(this.emulator.Registers.SP.pop()); + this.emulator.Registers.PC.set(this.emulator.Registers.SP.pop()); + + }, this.SpecialOpSet), + + IAQ: new Op(this, "IAQ", OPERATION_IAQ, 2, function(a) { + if(a === 0) + this.emulator.interruptQueueingEnabled = false; + else + this.emulator.interruptQueueingEnabled = true; + }, this.SpecialOpSet), + + HWN: new Op(this, "HWN", OPERATION_HWN, 2, function(a) { + a.set(this.emulator.devices.length); + }, this.SpecialOpSet), + + HWQ: new Op(this, "HWQ", OPERATION_HWQ, 4, function(a) { + var dev = this.emulator.devices[a.get()]; + if(dev) { + this.emulator.Registers.A.set(dev.id & 0xffff); + this.emulator.Registers.B.set((dev.id >> 16) & 0xffff); + this.emulator.Registers.C.set(dev.version & 0xffff); + this.emulator.Registers.X.set(dev.manufacturer & 0xffff); + this.emulator.Registers.Y.set((dev.manufacturer >> 16) & 0xffff); + } + + }, this.SpecialOpSet), + + HWI: new Op(this, "HWI", OPERATION_HWI, 4, function(a) { + var dev = this.emulator.devices[a.get()]; + if(dev) + dev.interrupt(); + }, this.SpecialOpSet), + }; + + + this.boot= function() { + console.log("--- DCPU-16 Emulator ---"); + + this.PC.set(0); + this.CPU_CYCLE = 0; + this.RAM = new Array(0x10000); + + for(var r in this.Registers) { + this.Registers[r].set(0); + } + this.Registers.SP.set(0xffff); + }; + + this.reboot= function() { this.boot(); }; + + this.run = function(_program) { + this.program = _program; + + console.log("Running program (" + this.program.length + " words)" ); + + if(!this.async) { + while(this.step()) { } + this.exit(); + } + else + this.stepAsync(); + + }; + + this.step = function() { + if(this.PC.get() < this.program.length) { + this.nextInstruction(); + + // process one interrupt if we have one + if(this.interruptQueueingEnabled == false && this.interruptQueue.length > 0) { + this.processInterrupt(this.interruptQueue.pop()); + } + + return true; + } + else return false; + }; + + var _this = this; + this.asyncSteps = 1; + this.stepAsync = function() { + + if(Math.floor(_this.CPU_CYCLE / 1000) > _this.asyncSteps) { + _this.asyncSteps++; + setTimeout(_this.stepAsync, 1); + } + else { + if(_this.step()) { + _this.stepAsync(); + } + else + _this.exit(); + } + }; + + this.nextInstruction = function() { + var data = this.program[this.PC.inc()]; + var instruction = Utils.parseInstruction(data); + var op; + if(instruction.opcode === 0) { + instruction = Utils.parseSpecialInstruction(data); + op = this.SpecialOpSet[instruction.opcode]; + } + else + op = this.OpSet[instruction.opcode]; + + + + if(!op) { + var err = "Invalid opcode " + instruction.opcode; + console.warn(err); + throw err; + } + +// console.log( +// Utils.hex(this.Registers.PC.get()) + "\t" + +// op.name + "\t(" + +// Utils.hex(instruction.a) + ",\t" + +// Utils.hex(instruction.b) + ")" +// ); + op.exec(instruction.a, instruction.b); + + }; + + this.nextWord = function() { + this.CPU_CYCLE++; + return this.program[this.Registers.PC.inc()]; + }; + + this.skipInstruction = function() { + var instruction = Utils.parseInstruction(this.program[this.PC.inc()]); + this.CPU_CYCLE++; + + if(instruction.opcode >= OPERATION_IFB && instruction.opcode <= OPERATION_IFU) { + // skip additional instruction at cost of an additional cycle + instruction = Utils.parseInstruction(this.program[this.PC.inc()]); + this.CPU_CYCLE++; + } + + }; + + this.interruptQueueingEnabled = false; + this.interruptQueue = []; + + this.processInterrupt = function(message) { + if(this.Registers.IA.get() != 0) { + this.interruptQueueingEnabled = true; + this.Registers.SP.push(this.Registers.PC.get()); // push PC onto the stack + this.Registers.SP.push(this.Registers.A.get()); // followed by pusing A to the stack + this.Registers.PC.set(this.Registers.IA.get()); // set PC to IA + this.Registers.A.set(message); // set A to the interrupt message + } + else { + } + }; + + this.interrupt = function(message) { + interruptQueue.push(message); + + if(interruptQueue.length > 256) { + // catch fire? + console.warn("DCUP-16 is on fire"); + throw "Too many interrupts"; + } + }; + + this.exit = function() { + console.log("Program completed in " + this.CPU_CYCLE + " cycles"); + }; + + this.devices = []; + + this.boot(); +}; + +// generic device used for unit tests +function Device(_id, _version, _manufacturer, _emulator) { + this.id = _id; + this.version = _version; + this.manufacturer = _manufacturer; + this.emulator = _emulator; +}; +Device.prototype.interrupt = function() { }; + + +// https://raw.github.com/gibbed/0x10c-Notes/master/hardware/clock.txt +function Clock(_emulator) { + this.id = 0x12d0b402; + this.version = 1; + this.manufacturer = 0x90099009; + this.emulator = _emulator; + + this.interruptsOn = false; + this.elapsed = 0; + this.interval = 0; + this.interruptMessage = 0; +}; + +Clock.prototype.interrupt = function() { + var aVal = this.emulator.Registers.A.get(); + var bVal = this.emulator.Registers.B.get(); + switch(aVal) { + case 0: + if(bVal != 0) + this.start(Math.round(bVal / 60 * 1000)); + else + this.stop(); + break; + + case 1: + this.emulator.Registers.C.set(this.elapsed); + break; + + case 2: + if(bVal != 0) { + this.interruptsOn = true; + this.interruptMessage = bVal; + } + else { + this.interruptsOn = false; + } + break; + } + +}; + +Clock.prototype.start = function(duration) { + this.stop(); + this.elapsed = 0; + this.interval = setInterval(this.tick, duration); +} + +Clock.prototype.stop = function() { + if(this.interval != 0) { + clearInterval(this.interval); + this.interval = 0; + } +} + +Clock.prototype.tick = function() { + this.elapsed = (this.elapsed + 1) & 0xffff; + + if(this.interruptsOn) + this.emulator.interrupt(this.interruptMessage); +} + +// https://raw.github.com/gibbed/0x10c-Notes/master/hardware/keyboard.txt +function Keyboard(_emulator) { + this.id = 0x30cf7406; + this.version = 1; + this.manufacturer = 0x90099009; + this.emulator = _emulator; + + this.interruptsOn = false; + this.interruptMessage = 0; + this.keys = []; + this.downKeys = {}; + + var _this = this; + document.body.onkeydown = function(event) { _this.keyDown(event); } + document.body.onkeyup = function(event) { _this.keyUp(event); } +} + +Keyboard.prototype.keyDown = function(event) { + var code = this.convert(event.keyCode); + this.downKeys[""+code] = true; + + this.keys.push(code); + + if(this.interruptsOn) + this.emulator.interrupt(this.interruptMessage); +} + +Keyboard.prototype.keyUp = function(event) { + var code = this.convert(event.keyCode); + this.downKeys[""+code] = false; +} + +Keyboard.prototype.convert = function(code) { + // TODO: convert key codes + return code; +} + +Keyboard.prototype.interrupt = function() { + var aVal = this.emulator.Registers.A.get(); + switch(aVal) { + case 0: + this.keys = []; + break; + + case 1: + var val = 0; + if(this.keys.length > 0) + val = this.keys.pop(); + this.emulator.Registers.C.set(val); + break; + + case 2: + if(this.downKeys[""+bVal]) + this.emulator.Registers.C.set(1); + else + this.emulator.Registers.C.set(0); + break; + + case 3: + if(bVal != 0) { + this.interruptsOn = true; + this.interruptMessage = bVal; + } + else { + this.interruptsOn = false; + } + break; + } + +}; + + + + diff --git a/javascript/lem1820.js b/javascript/lem1820.js new file mode 100644 index 0000000..435d02b --- /dev/null +++ b/javascript/lem1820.js @@ -0,0 +1,128 @@ +function Monitor(_emulator) { + this.id = 0x7349f615; + this.version = 0x1802; + this.manufacturer = 0x1c6c8b36; + this.emulator = _emulator; + + + + this.font = [ + 0xB79E, 0x388E, 0x722C, 0x75F4, 0x19BB, 0x7F8F, 0x85F9, 0xB158, 0x242E, 0x2400, 0x082A, 0x0800, 0x0008, 0x0000, 0x0808, 0x0808, + 0x00FF, 0x0000, 0x00F8, 0x0808, 0x08F8, 0x0000, 0x080F, 0x0000, 0x000F, 0x0808, 0x00FF, 0x0808, 0x08F8, 0x0808, 0x08FF, 0x0000, + 0x080F, 0x0808, 0x08FF, 0x0808, 0x6633, 0x99CC, 0x9933, 0x66CC, 0xFEF8, 0xE080, 0x7F1F, 0x0701, 0x0107, 0x1F7F, 0x80E0, 0xF8FE, + 0x5500, 0xAA00, 0x55AA, 0x55AA, 0xFFAA, 0xFF55, 0x0F0F, 0x0F0F, 0xF0F0, 0xF0F0, 0x0000, 0xFFFF, 0xFFFF, 0x0000, 0xFFFF, 0xFFFF, + 0x0000, 0x0000, 0x005F, 0x0000, 0x0300, 0x0300, 0x3E14, 0x3E00, 0x266B, 0x3200, 0x611C, 0x4300, 0x3629, 0x7650, 0x0002, 0x0100, + 0x1C22, 0x4100, 0x4122, 0x1C00, 0x1408, 0x1400, 0x081C, 0x0800, 0x4020, 0x0000, 0x0808, 0x0800, 0x0040, 0x0000, 0x601C, 0x0300, + 0x3E49, 0x3E00, 0x427F, 0x4000, 0x6259, 0x4600, 0x2249, 0x3600, 0x0F08, 0x7F00, 0x2745, 0x3900, 0x3E49, 0x3200, 0x6119, 0x0700, + 0x3649, 0x3600, 0x2649, 0x3E00, 0x0024, 0x0000, 0x4024, 0x0000, 0x0814, 0x2200, 0x1414, 0x1400, 0x2214, 0x0800, 0x0259, 0x0600, + 0x3E59, 0x5E00, 0x7E09, 0x7E00, 0x7F49, 0x3600, 0x3E41, 0x2200, 0x7F41, 0x3E00, 0x7F49, 0x4100, 0x7F09, 0x0100, 0x3E41, 0x7A00, + 0x7F08, 0x7F00, 0x417F, 0x4100, 0x2040, 0x3F00, 0x7F08, 0x7700, 0x7F40, 0x4000, 0x7F06, 0x7F00, 0x7F01, 0x7E00, 0x3E41, 0x3E00, + 0x7F09, 0x0600, 0x3E61, 0x7E00, 0x7F09, 0x7600, 0x2649, 0x3200, 0x017F, 0x0100, 0x3F40, 0x7F00, 0x1F60, 0x1F00, 0x7F30, 0x7F00, + 0x7708, 0x7700, 0x0778, 0x0700, 0x7149, 0x4700, 0x007F, 0x4100, 0x031C, 0x6000, 0x417F, 0x0000, 0x0201, 0x0200, 0x8080, 0x8000, + 0x0001, 0x0200, 0x2454, 0x7800, 0x7F44, 0x3800, 0x3844, 0x2800, 0x3844, 0x7F00, 0x3854, 0x5800, 0x087E, 0x0900, 0x4854, 0x3C00, + 0x7F04, 0x7800, 0x047D, 0x0000, 0x2040, 0x3D00, 0x7F10, 0x6C00, 0x017F, 0x0000, 0x7C18, 0x7C00, 0x7C04, 0x7800, 0x3844, 0x3800, + 0x7C14, 0x0800, 0x0814, 0x7C00, 0x7C04, 0x0800, 0x4854, 0x2400, 0x043E, 0x4400, 0x3C40, 0x7C00, 0x1C60, 0x1C00, 0x7C30, 0x7C00, + 0x6C10, 0x6C00, 0x4C50, 0x3C00, 0x6454, 0x4C00, 0x0836, 0x4100, 0x0077, 0x0000, 0x4136, 0x0800, 0x0201, 0x0201, 0x0205, 0x0200] + + this.palette = [ + 0x000000, 0x0000aa, 0x00aa00, 0x00aaaa, 0xaa0000, 0xaa00aa, 0xaa5500, 0xaaaaaa, + 0x555555, 0x5555ff, 0x55ff55, 0x55ffff, 0xff5555, 0xff55ff, 0xffff55, 0xffffff + ]; + + this.borderColor = 8; + + this.zoom = 2; + + this.canvas = document.createElement("canvas"); + this.canvas.width = this.zoom * 128; + this.canvas.height = this.zoom * 96; + this.canvas.style.backgroundColor = "#777777"; + this.canvas.className = "lem1820"; + this.setBorderColor(this.borderColor); + document.body.appendChild(this.canvas); + this.context = this.canvas.getContext('2d'); +} + +Monitor.prototype.interrupt = function() { + var aVal = this.emulator.Registers.A.get(); + var bVal = this.emulator.Registers.B.get(); + switch(aVal) { + case 0: + if(bVal === 0) + this.disconnect(); + else + this.memMapScreen(bVal); + break; + + case 1: + break; + + case 2: + break; + + case 3: + this.setBorderColor(bVal & 0xf); + break; + + case 4: + break; + + case 5: + break; + } +} + +Monitor.prototype.memMapScreen = function(offset) { + for(var y = 0; y < 12; y++) { + for(var x = 0; x < 32; x++) { + this.drawCell(x, y, this.emulator.RAM[offset + x + y*32]); + } + } +} + +Monitor.prototype.drawCell = function(x, y, word) { + var glyph = word & 0x7f; + var blink = (word & 0x80) >> 7; + var bg = (word & 0xf00) >> 8; + var fg = (word & 0xf000) >> 12; + this.drawGlyph(x, y, glyph, this.palette[fg], this.palette[bg], blink); +} + +Monitor.prototype.drawGlyph = function(x, y, glyph, fg, bg, blink) { + this.context.fillStyle = Utils.makeColor(bg); + this.context.fillRect(x * 4 * this.zoom, y * 8 * this.zoom, 4 * this.zoom, 8 * this.zoom); + + this.context.fillStyle = Utils.makeColor(fg); + + var cols = []; + glyph *= 2; + cols[0] = this.font[glyph] >> 8; + cols[1] = this.font[glyph] & 0xff; + cols[2] = this.font[glyph+1] >> 8; + cols[3] = this.font[glyph+1] & 0xff; + + for(var row = 0; row < 8; row++) { + for(var col = 0; col < 4; col++) { + var bit = (cols[col] >> row) & 0x01; + if(bit == 1) + this.context.fillRect((x*4 + col) * this.zoom, (y*8 + row) * this.zoom, this.zoom, this.zoom); + } + } + +} + +Monitor.prototype.disconnect = function() { + this.context.fillStyle = "#777777"; + this.context.fillRect(0, 0, 128, 96); +} + +Monitor.prototype.setBorderColor = function(color) { + this.borderColor = this.palette[color & 0xf]; + this.canvas.style.border = (4+this.zoom) + "px solid " + Utils.makeColor(this.borderColor); +} + +Monitor.prototype.getDOMElement = function() { + return this.canvas; +} + + diff --git a/stylesheets/styles.css b/stylesheets/styles.css new file mode 100644 index 0000000..278f7f1 --- /dev/null +++ b/stylesheets/styles.css @@ -0,0 +1,50 @@ +body { + font-family: courier, 'courier new', 'fixed-width'; + font-size: 12px; +} +.assembly { + background: #111111; + padding: 6px; + border: 1px dashed #cccccc; +} +.comment { + color: #777777; +} +.hexidecimal { + color: #007777; +} +.decimal { + color: #0077ff; +} +.string { + color: #007700 +} +.label_def { + color: #6666ff +} +.label_ref { + color: #aaaaff +} +.command { + color: #ffffff; + font-weight: bold; +} +.reserved_word { + color: #aaaa00; +} +.open_bracket { + color: #dddddd; +} +.close_bracket { + color: #dddddd; +} +.comma { + color: #dddddd; +} +.operator { + color: #dddddd; +} +.register { + color: #aa33aa; +} +.space { } \ No newline at end of file diff --git a/test/units.html b/test/units.html new file mode 100644 index 0000000..229e008 --- /dev/null +++ b/test/units.html @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + +

DCPU-16 Emulator QUnit Tests

+

+
+

+
    +
    test markup, will be hidden
    + + \ No newline at end of file diff --git a/test/units.js b/test/units.js new file mode 100644 index 0000000..fb047e6 --- /dev/null +++ b/test/units.js @@ -0,0 +1,638 @@ +function units() { + + module("opcodes Module"); + + test("SET Bad Programs", function() { + var e = new Emulator(); + + raises(function() { + e.run([ 0x18 ]); + }, "Test bad op code"); + + }); + + test("SET Test", function() { + //expect(8); + + var program = [ + // register tests + Utils.makeInstruction(OPERATION_SET, Literals.L_2, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_10, REGISTER_B), + Utils.makeInstruction(OPERATION_SET, REGISTER_B, REGISTER_C), + + // register + RAM tests + Utils.makeInstruction(OPERATION_SET, Literals.L_3, REGISTER_B + Values.REGISTER_VALUE_OFFSET), + Utils.makeInstruction(OPERATION_SET, REGISTER_B + Values.REGISTER_VALUE_OFFSET, REGISTER_I), + + // register + RAM + next word tests + Utils.makeInstruction(OPERATION_SET, Literals.L_4, REGISTER_A + Values.REGISTER_NEXT_WORD_OFFSET), 0x03, + Utils.makeInstruction(OPERATION_SET, REGISTER_A + Values.REGISTER_NEXT_WORD_OFFSET, REGISTER_J), 0x03, + + // next word tests + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_X), 0x2222, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_VALUE, REGISTER_Y), 0x0a, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, Values.NEXT_WORD_VALUE), 0x3333, 0x0b + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.A.get(), 2, "Register A set correctly"); + equal(e.Registers.B.get(), 10, "Register B set correctly"); + equal(e.Registers.C.get(), 10, "Register C set to value of B"); + + equal(e.RAM[0x0a], 3, "RAM at location 10 set to value of 3"); + equal(e.Registers.I.get(), e.RAM[0x0a], "Register I set to RAM at location 10"); + + equal(e.RAM[0x05], 4, "RAM at location 5 set to value of 4"); + equal(e.Registers.J.get(), e.RAM[0x05], "Register J set to RAM at location 5"); + + equal(e.Registers.X.get(), 0x2222, "Register X set to NEXT_WORD_LITERAL"); + equal(e.Registers.Y.get(), 3, "Register Y set to NEXT_WORD_VALUE"); + equal(e.RAM[0x0b], 0x3333, "RAM at NEXT_WORD_VALUE set to NEXT_WORD_LITERAL"); + + }); + + test("SP Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Literals.L_5, Values.SP_OFFSET), // push 5 + Utils.makeInstruction(OPERATION_SET, Literals.L_4, Values.SP_OFFSET), // push 4 + Utils.makeInstruction(OPERATION_SET, Literals.L_3, Values.SP_OFFSET), // push 3 + Utils.makeInstruction(OPERATION_SET, Literals.L_2, Values.SP_OFFSET), // push 2 + Utils.makeInstruction(OPERATION_SET, Literals.L_1, Values.SP_OFFSET), // push 1 + Utils.makeInstruction(OPERATION_SET, Literals.L_0, Values.SP_OFFSET), // push 0 + Utils.makeInstruction(OPERATION_SET, Literals["L_-1"], Values.SP_OFFSET), // push -1 + + Utils.makeInstruction(OPERATION_SET, Values.SP_OFFSET, REGISTER_A), // pop -1 + Utils.makeInstruction(OPERATION_SET, Values.SP_OFFSET+1, REGISTER_B), // peak 0 + Utils.makeInstruction(OPERATION_SET, Values.SP_OFFSET+2, REGISTER_C), 0x3,// pick 3 + Utils.makeInstruction(OPERATION_SET, Values.SP_OFFSET, REGISTER_I), // pop 0 + Utils.makeInstruction(OPERATION_SET, Values.SP_OFFSET, REGISTER_J), // pop 1 + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.A.get(), -1, "Register A is -1"); + equal(e.Registers.B.get(), 0, "Register B is 0"); + equal(e.Registers.C.get(), 3, "Register C is 3"); + equal(e.Registers.I.get(), 0, "Register I is 0"); + equal(e.Registers.J.get(), 1, "Register J is 1"); + equal(e.Registers.SP.get(), 0xfffb, "Register SP is 0xfffb"); + }); + + + + test("ADD Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Literals.L_2, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_4, REGISTER_B), + Utils.makeInstruction(OPERATION_ADD, REGISTER_A, REGISTER_B), + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_X), 0x8800, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_Y), 0x8801, + Utils.makeInstruction(OPERATION_ADD, REGISTER_X, REGISTER_Y) + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 6, "Register B is sum of A and B"); + equal(e.Registers.Y.get(), 0x1001, "Register Y is sum of 0x8800 and 0x8801"); + equal(e.Registers.EX.get(), 1, "Register EX shows overflow"); + }); + + + test("SUB Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Literals.L_2, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_4, REGISTER_B), + Utils.makeInstruction(OPERATION_SUB, REGISTER_A, REGISTER_B), + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_X), 0x8801, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_Y), 0x4800, + Utils.makeInstruction(OPERATION_SUB, REGISTER_X, REGISTER_Y) + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 2, "Register B is difference of B and A"); + equal(e.Registers.Y.get(), 0xbfff, "Register Y is difference of 0x4800 and 0x8801"); + equal(e.Registers.EX.get(), 0xffff, "Register EX shows overflow"); + }); + + test("MUL Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Literals.L_2, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_4, REGISTER_B), + Utils.makeInstruction(OPERATION_MUL, REGISTER_A, REGISTER_B), + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_X), 0x1010, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_Y), 0x0408, + Utils.makeInstruction(OPERATION_MUL, REGISTER_X, REGISTER_Y) + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 8, "Register B is product of B and A"); + equal(e.Registers.Y.get(), 0xc080, "Register Y is product of 0x1010 and 0x0400"); + equal(e.Registers.EX.get(), 0x0040, "Register EX shows overflow"); + }); + + test("MLI Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0xFFFB, // -5 + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x02, + Utils.makeInstruction(OPERATION_MLI, REGISTER_A, REGISTER_B), // B = -10 + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_X), 0xD8F0, // -10,000 + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_Y), 0xEB20, // -5,344 + Utils.makeInstruction(OPERATION_MLI, REGISTER_X, REGISTER_Y) // X = 0x6E00, EX=0x32F + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0xFFF6, "Register B is product of B and A"); + equal(e.Registers.Y.get(), 0x6E00, "Register Y is product of 0xD8F0 and 0xEB20"); + equal(e.Registers.EX.get(), 0x32F, "Register EX shows overflow"); + }); + + test("DIV Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x03, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x2c, // 44 + Utils.makeInstruction(OPERATION_DIV, REGISTER_A, REGISTER_B), // B = 14 + Utils.makeInstruction(OPERATION_SET, REGISTER_EX, REGISTER_I), + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_X), 0x0332, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_Y), 0xF445, // + Utils.makeInstruction(OPERATION_DIV, REGISTER_X, REGISTER_Y) // X = 0x4c, EX=0x723a + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0x0e, "Register B is quotient of B and A"); + equal(e.Registers.I.get(), 0xAAAA, "Register I shows overflow from first operation"); + equal(e.Registers.Y.get(), 0x4c, "Register Y is quotient of 0xD8F0 and 0xEB20"); + //equal(e.Registers.EX.get(), 0x723a, "Register EX shows overflow"); + }); + + test("DVI Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0xFFFD, // -3 + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x2c, // 44 + Utils.makeInstruction(OPERATION_DVI, REGISTER_A, REGISTER_B), // B = -14 + Utils.makeInstruction(OPERATION_SET, REGISTER_EX, REGISTER_I), + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_X), 0x0332, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_Y), 0xF445, // + Utils.makeInstruction(OPERATION_DVI, REGISTER_X, REGISTER_Y) // X = 0x4c, EX=0x723a + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0xFFF2, "Register B is quotient of B and A"); + equal(e.Registers.I.get(), 0x5556, "Register I shows overflow from first operation"); + equal(e.Registers.Y.get(), 0xFFFD, "Register Y is quotient of 0xD8F0 and 0xEB20"); + //equal(e.Registers.EX.get(), 0x723a, "Register EX shows overflow"); + }); + + test("MOD Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x0f, // 15 + Utils.makeInstruction(OPERATION_MOD, REGISTER_A, REGISTER_B), // B = 3 + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_X), 0x13, // + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_Y), 0x3452, // + Utils.makeInstruction(OPERATION_MOD, REGISTER_X, REGISTER_Y) // X = 0x12 + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 3, "Register B is result of B % A"); + equal(e.Registers.Y.get(), 0x12, "Register Y 0x3452 % 0x13"); + }); + + test("MDI Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x10, // -7 + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0xFFF9, // 16 + Utils.makeInstruction(OPERATION_MDI, REGISTER_A, REGISTER_B), // B = -7 + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0xFFF9, "Register B is result of B % A"); + }); + + test("AND Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0xff44, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x44ff, + Utils.makeInstruction(OPERATION_AND, REGISTER_A, REGISTER_B), + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0x4444, "Register B is result of B & A"); + }); + + test("BOR Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x4444, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x3333, + Utils.makeInstruction(OPERATION_BOR, REGISTER_A, REGISTER_B), + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0x7777, "Register B is result of B | A"); + }); + + test("XOR Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x3434, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x1111, + Utils.makeInstruction(OPERATION_XOR, REGISTER_A, REGISTER_B), + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0x2525, "Register B is result of B ^ A"); + }); + + test("SHR Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0xFF11, + Utils.makeInstruction(OPERATION_SHR, REGISTER_A, REGISTER_B), + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0x0FF1, "Register B is result of B >>> A"); + equal(e.Registers.EX.get(), 0x1000, "Register EX shows overflow"); + }); + + test("ASR Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0xFF11, + Utils.makeInstruction(OPERATION_ASR, REGISTER_A, REGISTER_B), + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0xFFF1, "Register B is result of B >> A"); + equal(e.Registers.EX.get(), 0x1000, "Register EX shows overflow"); + }); + + test("SHL Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0xFF11, + Utils.makeInstruction(OPERATION_SHL, REGISTER_A, REGISTER_B), + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0xF110, "Register B is result of B << A"); + equal(e.Registers.EX.get(), 0x0f, "Register EX shows overflow"); + }); + + // branching operations + test("IFB Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0xffee, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x01, + Utils.makeInstruction(OPERATION_IFB, REGISTER_B, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_0, REGISTER_B) + + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0xffee, "B is 0xffee, instruction was skipped"); + }); + + test("IFC Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0xffee, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_IFC, REGISTER_B, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_0, REGISTER_B) + + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0xffee, "B is 0xffee, instruction was skipped"); + }); + + test("IFE Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0xffee, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_SET, Literals.L_0, REGISTER_X), + Utils.makeInstruction(OPERATION_IFE, REGISTER_B, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_0, REGISTER_B), + Utils.makeInstruction(OPERATION_SET, Literals.L_1, REGISTER_I), + Utils.makeInstruction(OPERATION_IFE, Literals.L_4, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_2, REGISTER_J), + Utils.makeInstruction(OPERATION_IFE, Literals.L_5, REGISTER_A), + Utils.makeInstruction(OPERATION_IFE, Literals.L_6, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_7, REGISTER_X) + + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0xffee, "B is 0xffee, instruction was skipped"); + equal(e.Registers.I.get(), 1, "I is 1, instruction was not skipped"); + equal(e.Registers.J.get(), 2, "J is 2, instruction was not skipped"); + equal(e.Registers.X.get(), 0, "X is 0, instruction was skipped"); + }); + + test("IFN Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0xffee, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0xffee, + Utils.makeInstruction(OPERATION_IFN, REGISTER_B, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_0, REGISTER_B), + Utils.makeInstruction(OPERATION_IFN, Literals.L_5, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_7, REGISTER_J) + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0xffee, "B is 0xffee, instruction was skipped"); + equal(e.Registers.J.get(), 7, "J is 7, instruction was not skipped"); + }); + + test("IFG Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0xffee, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_IFG, REGISTER_B, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_0, REGISTER_B) + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0xffee, "B is 0xffee, instruction was skipped"); + }); + + test("IFA Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0xffee, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_IFA, REGISTER_B, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_0, REGISTER_B) + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0, "B is 0, instruction was not skipped"); + }); + + test("IFL Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x03, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_IFL, REGISTER_B, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_0, REGISTER_B) + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0x03, "B is 0x03, instruction was skipped"); + }); + + test("IFU Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x04, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0xff43, + Utils.makeInstruction(OPERATION_IFU, REGISTER_B, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_0, REGISTER_B) + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0, "B is 0, instruction was not skipped"); + }); + + test("ADX Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x05, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_EX), 0x06, + Utils.makeInstruction(OPERATION_ADX, REGISTER_A, REGISTER_B), + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_X), 0xee55, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_Y), 0xff44, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_EX), 0x06, + Utils.makeInstruction(OPERATION_ADX, REGISTER_X, REGISTER_Y), + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0x0f, "Register B is result of A + B + EX"); + equal(e.Registers.Y.get(), 0xED9F, "Register Y is result of X + Y + EX"); + equal(e.Registers.EX.get(), 0x01, "Register EX shows overflow"); + }); + + test("SBX Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x05, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_EX), 0x06, + Utils.makeInstruction(OPERATION_SBX, REGISTER_A, REGISTER_B), + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_X), 0xff44, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_Y), 0xee55, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_EX), 0x06, + Utils.makeInstruction(OPERATION_SBX, REGISTER_X, REGISTER_Y), + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.B.get(), 0x07, "Register B is result of B - A + EX"); + equal(e.Registers.Y.get(), 0xEF17, "Register Y is result of Y - X + EX"); + equal(e.Registers.EX.get(), 0xffff, "Register EX shows underflow"); + }); + + test("STI Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x05, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_I), 0x06, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_J), 0x07, + Utils.makeInstruction(OPERATION_STI, REGISTER_A, REGISTER_B), + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.A.get(), 0x05, "Register A is set to B"); + equal(e.Registers.B.get(), 0x04, "Register B is set to A"); + equal(e.Registers.I.get(), 0x07, "Register I has been incremented"); + equal(e.Registers.J.get(), 0x08, "Register J has been incremented"); + }); + + test("STD Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x05, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_I), 0x06, + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_J), 0x00, + Utils.makeInstruction(OPERATION_STD, REGISTER_A, REGISTER_B), + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.A.get(), 0x05, "Register A is set to B"); + equal(e.Registers.B.get(), 0x04, "Register B is set to A"); + equal(e.Registers.I.get(), 0x05, "Register I has been decremented"); + equal(e.Registers.J.get(), 0xffff, "Register J has been decremented"); + }); + + test("JSR Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x04, + Utils.makeSpecialInstruction(OPERATION_JSR, Values.NEXT_WORD_LITERAL), 0x08, // jump to 0x8 + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x05, + Utils.makeInstruction(OPERATION_IFE, Literals.L_5, REGISTER_A), + Utils.makeInstruction(OPERATION_SET, Literals.L_16, REGISTER_PC), // exit + + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_A), 0x03, // 0x8 + Utils.makeInstruction(OPERATION_SET, Values.SP_OFFSET, REGISTER_PC) // return + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.A.get(), 0x05, "Register A is set to 5"); + }); + + test("Interrupt Test", function() { + //expect(1); + + var program = [ + Utils.makeInstruction(OPERATION_SET, Values.NEXT_WORD_LITERAL, REGISTER_B), 0x06, + Utils.makeSpecialInstruction(OPERATION_IAS, REGISTER_B), + Utils.makeSpecialInstruction(OPERATION_INT, Values.NEXT_WORD_LITERAL), 0x08, + Utils.makeInstruction(OPERATION_SET, Literals.L_16, REGISTER_PC), // exit + Utils.makeInstruction(OPERATION_SET, Literals.L_0, REGISTER_C), // 0x06 + Utils.makeSpecialInstruction(OPERATION_IAG, REGISTER_Y), // Y = 6 + Utils.makeSpecialInstruction(OPERATION_IAS, REGISTER_C), // IA = 0 + Utils.makeInstruction(OPERATION_SET, REGISTER_A, REGISTER_X), // X = 8 (INT message) + Utils.makeSpecialInstruction(OPERATION_RFI, Literals.L_0), // return + + ]; + + var e = new Emulator(); + e.run(program); + + equal(e.Registers.IA.get(), 0x00, "Register IA is set to 0"); + equal(e.Registers.X.get(), 0x08, "Register X is set to 8"); + equal(e.Registers.Y.get(), 0x06, "Register Y is set to 6"); + }); + + test("Hardware Test", function() { + //expect(1); + + var program = [ + Utils.makeSpecialInstruction(OPERATION_HWN, REGISTER_I), + Utils.makeSpecialInstruction(OPERATION_HWQ, Literals.L_0), + Utils.makeSpecialInstruction(OPERATION_HWI, Literals.L_0), + ]; + + var e = new Emulator(); + e.devices.push(new Device(0xdeadbeef, 0x21, 0xfeeddeee)); + e.run(program); + + equal(e.Registers.I.get(), 1, "Register I is set to 1"); + equal(e.Registers.A.get(), 0xbeef, "Register A is set to 0xbeef"); + equal(e.Registers.B.get(), 0xdead, "Register B is set to 0xdead"); + equal(e.Registers.C.get(), 0x21, "Register C is set to 0x21"); + equal(e.Registers.X.get(), 0xdeee, "Register X is set to 0xdeee"); + equal(e.Registers.Y.get(), 0xfeed, "Register Y is set to 0xfeed"); + + }); + + +}; + + diff --git a/tokenizer.html b/tokenizer.html new file mode 100644 index 0000000..cbb0d16 --- /dev/null +++ b/tokenizer.html @@ -0,0 +1,25 @@ + + + + + + + + +
    + + +
    + +
    + + + + \ No newline at end of file