Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- CHIP-JS8
- A CHIP-8 virtual machine/emulator for JavaScript
- by slacker' 2015
- ---
- TODO:
- -implement timers
- -implement keyboard
- -implement sprite drawing
- -future plan: write an assembly language instead of raw machine code
- */
- //globally scoped for debugging purposes
- var draw;
- var i;
- var j;
- var x;
- var y;
- var interval0;
- var interval1;
- var jumpflag;
- var ll;
- var vx;
- var vy;
- var addr;
- var l;
- var h;
- var hh;
- var opcode;
- var drawColor;
- function getRandomInt(min, max) {
- return Math.floor(Math.random() * (max - min + 1)) + min;
- }
- //complete virtual machine object
- var machine = {
- state: 0,
- memory: new Uint8Array(4096),
- callstack: [],
- callpointer: -1,
- displaywidth: 64,
- displayheight: 32,
- displayscale: 10,
- delaytimer: 0,
- soundtimer: 0,
- pc: 0x200,
- i: 0,
- register: new Uint8Array(16),
- rate: 1000,
- font: [
- 0xF0,
- 0x90,
- 0x90,
- 0x90,
- 0xF0,
- 0x20,
- 0x60,
- 0x20,
- 0x20,
- 0x70,
- 0xF0,
- 0x10,
- 0xF0,
- 0x80,
- 0xF0,
- 0xF0,
- 0x10,
- 0xF0,
- 0x10,
- 0xF0,
- 0x90,
- 0x90,
- 0xF0,
- 0x10,
- 0x10,
- 0xF0,
- 0x80,
- 0xF0,
- 0x10,
- 0xF0,
- 0xF0,
- 0x80,
- 0xF0,
- 0x90,
- 0xF0,
- 0xF0,
- 0x10,
- 0x20,
- 0x40,
- 0x40,
- 0xF0,
- 0x90,
- 0xF0,
- 0x90,
- 0xF0,
- 0xF0,
- 0x90,
- 0xF0,
- 0x10,
- 0xF0,
- 0xF0,
- 0x90,
- 0xF0,
- 0x90,
- 0x90,
- 0xE0,
- 0x90,
- 0xE0,
- 0x90,
- 0xE0,
- 0xF0,
- 0x80,
- 0x80,
- 0x80,
- 0xF0,
- 0xE0,
- 0x90,
- 0x90,
- 0x90,
- 0xE0,
- 0xF0,
- 0x80,
- 0xF0,
- 0x80,
- 0xF0,
- 0xF0,
- 0x80,
- 0xF0,
- 0x80,
- 0x80],
- display: document.getElementById("display"),
- input: document.getElementById("input"),
- init: function () {
- display.width = this.displaywidth * this.displayscale;
- display.height = this.displayheight * this.displayscale;
- draw = this.display.getContext("2d");
- draw.fillRect(0, 0, this.display.width, this.display.height, "#000");
- for (i = 0; i < this.font.length; i++) {
- this.memory[i] = this.font[i];
- }
- },
- doCycle: function () {
- l = this.memory[this.pc + 1].toString(16);
- h = this.memory[this.pc].toString(16);
- if (h.length == 1) {
- h = "0" + h;
- }
- if (l.length == 1) {
- l = "0" + l;
- }
- opcode = h + l;
- opcode = opcode.toUpperCase();
- hh = opcode.charAt(0);
- switch (hh) {
- case "0":
- ll = opcode.charAt(3);
- switch (ll) {
- case "0":
- draw.fillRect(0, 0, this.display.width, this.display.height, "#000");
- break;
- case "E":
- this.pc = this.callstack[this.callpointer];
- this.callpointer--;
- break;
- default:
- throw {
- name: "opcodeDecodeError",
- message: "unknown error decoding opcode " + opcode
- };
- }
- break;
- case "1":
- addr = parseInt(opcode.substring(1, 4), 16);
- this.pc = addr;
- jumpflag = true;
- break;
- case "2":
- addr = parseInt(opcode.substring(1, 4), 16);
- this.callpointer++;
- this.callstack[this.callpointer] = this.pc;
- this.pc = addr;
- jumpflag = true;
- break;
- case "3":
- vx = parseInt(opcode.charAt(1), 16);
- l = parseInt(opcode.substring(2, 4), 16);
- if (this.register[vx] == l) {
- this.pc += 2;
- }
- break;
- case "4":
- vx = parseInt(opcode.charAt(1), 16);
- l = parseInt(opcode.substring(2, 4), 16);
- if (this.register[vx] != l) {
- this.pc += 2;
- }
- break;
- case "5":
- vx = parseInt(opcode.charAt(1), 16);
- vy = parseInt(opcode.charAt(2), 16);
- if (this.register[vx] == this.register[vy]) {
- this.pc += 2;
- }
- break;
- case "6":
- vx = parseInt(opcode.charAt(1), 16);
- l = parseInt(opcode.substring(2, 4), 16);
- this.register[vx] = l;
- break;
- case "7":
- vx = parseInt(opcode.charAt(1), 16);
- l = parseInt(opcode.substring(2, 4), 16);
- this.register[vx] += l;
- break;
- case "8":
- ll = instruction.charAt(3);
- vx = parseInt(opcode.charAt(1), 16);
- vy = parseInt(opcode.charAt(2), 16);
- switch (ll) {
- case "0":
- this.register[vx] = this.register[vy];
- break;
- case "1":
- this.register[vx] = this.register[vx] | this.register[vy];
- break;
- case "2":
- this.register[vx] = this.register[vx] & this.register[vy];
- break;
- case "3":
- this.register[vx] = this.register[vx] ^ this.register[vy];
- break;
- case "4":
- this.register[vx] = this.register[vx] + this.register[vy];
- break;
- case "5":
- this.register[vx] = this.register[vx] - this.register[vy];
- if (vx > vy) {
- this.register[15] = 1;
- } else {
- this.register[15] = 0;
- }
- break;
- case "6":
- this.register[15] = this.register[vy] & 0xFFFE;
- this.register[vx] = this.register[vy] >>> 1;
- break;
- case "7":
- this.register[vx] = this.register[vy] - this.register[vx];
- if (vy > vx) {
- this.register[15] = 1;
- } else {
- this.register[15] = 0;
- }
- break;
- case "E":
- this.register[15] = (this.register[vy] >> 7) & 0xFFFE;
- this.register[vx] = this.register[vy] << 1;
- break;
- default:
- throw {
- name: "opcodeDecodeError",
- message: "unknown error decoding opcode " + opcode
- };
- }
- break;
- case "9":
- vx = parseInt(opcode.charAt(1), 16);
- vy = parseInt(opcode.charAt(2), 16);
- if (this.register[vx] != this.register[vy]) {
- this.pc += 2;
- }
- break;
- case "A":
- addr = parseInt(opcode.substring(1, 4), 16);
- this.i = addr;
- break;
- case "B":
- addr = parseInt(opcode.substring(1, 4), 16);
- this.pc = addr + this.register[0];
- jumpflag = true;
- break;
- case "C":
- vx = parseInt(opcode.charAt(1), 16);
- l = parseInt(opcode.substring(2, 4), 16);
- this.register[vx] = getRandomInt(0, 256) & l;
- break;
- case "D":
- vx = parseInt(opcode.charAt(1), 16);
- vy = parseInt(opcode.charAt(2), 16);
- ll = parseInt(opcode.charAt(3), 16);
- image = draw.getImageData(0, 0, this.display.width, this.display.height);
- for (i = 0; i < ll; i++) {
- linebyte = this.memory[this.i + i];
- for (j = 0; j < 8; j++) {
- bit = linebyte & (0x01 << j);
- cx = this.register[vx] * this.displayscale;
- cy = this.register[vy] * this.displayscale;
- data = imageDataReadPixel(cx, cy, image);
- if (bit){
- if (JSON.stringify(data) == JSON.stringify([0, 0, 0, 255])) {
- drawColor = 255;
- } else if (JSON.stringify(data) == JSON.stringify([255, 255, 255, 255])) {
- drawColor = 0;
- this.register[15] = 1;
- } else {
- throw {
- name: "imageDataError",
- message: "encountered error proccessing image data"
- };
- }
- draw.fillRect(cx, cy, this.displayscale, this.displayscale, "#" +
- drawColor.toString(16).repeat(3));
- }
- }
- }
- break;
- case "E":
- vx = parseInt(opcode.charAt(1), 16);
- l = parseInt(opcode.substring(2, 4), 16);
- switch (l) {
- case "9E":
- //EX9E
- //skip next instruction if key value in VX is pressed
- break;
- case "A1":
- //EXA1
- //skip next instruction if key value in VX is not pressed
- break;
- default:
- throw {
- name: "opcodeDecodeError",
- message: "unknown error decoding opcode " + opcode
- };
- }
- break;
- case "F":
- vx = parseInt(opcode.charAt(1), 16);
- l = opcode.substring(2, 4);
- switch (l) {
- case "07":
- //FX07
- //set VX to current value of delay timer
- break;
- case "0A":
- //FX0A
- //wait for keypress and store keycode in VX
- break;
- case "15":
- this.delaytimer = this.register[vx];
- break;
- case "18":
- this.delaytimer = this.register[vx];
- break;
- case "1E":
- this.i += this.register[vx];
- break;
- case "29":
- if ((this.register[vx] > 15) | (this.register[vx]) < 0) {
- throw {
- name: "characterFetchError",
- message: "Character code out of range"
- };
- }
- this.i = 5 * this.register[vx];
- break;
- case "33":
- var num = this.register[vx].toString(10);
- for (i = 0; i < 3; i++) {
- this.memory[this.i + i] = parseInt(num.charAt(i), 10);
- }
- break;
- case "55":
- for (i = 0; i <= vx; i++) {
- this.memory[this.i + i] = this.register[i];
- }
- this.i += i;
- break;
- case "65":
- for (i = 0; i <= vx; i++) {
- this.register[i] = this.memory[this.i + i];
- }
- this.i += i;
- break;
- default:
- throw {
- name: "opcodeDecodeError",
- message: "unknown error decoding opcode " + opcode
- };
- }
- break;
- default:
- throw {
- name: "opcodeDecodeError",
- message: "unknown error decoding opcode " + opcode
- };
- }
- if (!jumpflag) {this.pc += 2;} else {jumpflag = false;}
- return;
- },
- doTimers: function() {
- },
- load: function () {
- var text = this.input.value;
- var code = text.match(/^[0-9A-F]{4}$/gm);
- var h, l;
- for (i = 0; i < code.length; i++) {
- h = code[i].match(/^[0-9A-F]{2}/gm);
- l = code[i].match(/[0-9A-F]{2}$/gm);
- this.memory[i * 2 + 0x200] = parseInt(h, 16);
- this.memory[i * 2 + 0x200 + 1] = parseInt(l, 16);
- }
- return;
- }
- };
- //debugging object
- var debug = {
- dumpMemoryToConsole: function() {
- console.log("Memory: ");
- console.log(machine.memory);
- return;
- },
- dumpRegistersToConsole: function() {
- console.log("General registers:");
- for (i = 0; i < 16; i++) {
- console.log(i.toString(16) + ": " + machine.register[i]);
- }
- console.log("I register: " + machine.i);
- console.log("Program counter: " + machine.pc);
- return;
- },
- dumpStackToConsole: function () {
- console.log("Stack pointer: " + machine.callpointer);
- console.log("Call stack: ");
- for (i = machine.callpointer; i <= 0; i++) {
- console.log(i.toString(16) + ": " + machine.callstack[i]);
- }
- }
- };
- machine.init();
- loadButton = document.getElementById("loadButton");
- loadButton.addEventListener("click", function(){machine.load();});
- runButton = document.getElementById("runButton");
- runButton.addEventListener("click", function(){
- interval0 = setInterval(function(){
- try {
- machine.doCycle();
- } catch(err) {
- alert(err.name + "\n" + err.message);
- }
- }, 1000 / machine.rate);});
- pauseButton = document.getElementById("pauseButton");
- pauseButton.addEventListener("click", function(){clearInterval(interval0);});
- stepButton = document.getElementById("stepButton");
- stepButton.addEventListener("click", function(){machine.doCycle();debug.dumpRegistersToConsole();});
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement