hack-emulator-js
Version:
a hack emulator for the nand2tetris project
434 lines (376 loc) • 10.3 kB
JavaScript
/**
* As the hack computer is quite small
* Everything is placed in this file
* CPU, RAM, ROM, Screen, Keyboard
*/
var assembler = require('hack-assembler')
function Hack() {
this.ROM = new Array(32767 + 1) // 0x0000 to 0x8000
this.RAM = new Array(24576 + 1) // 0x0000 to 0x6000
this.RAM.fill(0)
this.KBD = 24576 // Keyboard position in 0x6000
this.PC = 0
this.DRegister = 0
this.ARegister = 0
this.comp = {
// '0101010': '0',
'0101010': () => {
return 0
},
// '0111111': '1',
'0111111': () => {
return 1
},
// '0111010': '-1',
'0111010': () => {
return -1
},
// '0001100': 'D',
'0001100': () => {
return this.DRegister
},
// '0110000': 'A',
'0110000': () => {
return this.ARegister
},
// '0001101': '!D',
'0001101': () => {
return ~this.DRegister
},
// '0110001': '!A',
'0110001': () => {
return ~this.ARegister
},
// '0001111': '-D',
'0001111': () => {
return -this.DRegister
},
// '0110011': '-A',
'0110011': () => {
return -this.ARegister
},
// '0011111': 'D+1',
'0011111': () => {
return this.DRegister + 1
},
// '0110111': 'A+1',
'0110111': () => {
return this.ARegister + 1
},
// '0001110': 'D-1',
'0001110': () => {
return this.DRegister - 1
},
// '0110010': 'A-1',
'0110010': () => {
return this.ARegister - 1
},
// '0000010': 'D+A',
'0000010': () => {
return this.DRegister + this.ARegister
},
// '0010011': 'D-A',
'0010011': () => {
return this.DRegister - this.ARegister
},
// '0000111': 'A-D',
'0000111': () => {
return this.ARegister - this.DRegister
},
// '0000000': 'D&A',
'0000000': () => {
return this.DRegister & this.ARegister
},
// '0010101': 'D|A',
'0010101': () => {
return this.DRegister | this.ARegister
},
// '1110000': 'M',
'1110000': () => {
return this.RAM[this.ARegister]
},
// '1110001': '!M',
'1110001': () => {
return ~this.RAM[this.ARegister]
},
// '1110011': '-M',
'1110011': () => {
return -this.RAM[this.ARegister]
},
// '1110111': 'M+1',
'1110111': () => {
return this.RAM[this.ARegister] + 1
},
// '1110010': 'M-1',
'1110010': () => {
return this.RAM[this.ARegister] - 1
},
// '1000010': 'D+M',
'1000010': () => {
return this.DRegister + this.RAM[this.ARegister]
},
// '1010011': 'D-M',
'1010011': () => {
return this.DRegister - this.RAM[this.ARegister]
},
// '1000111': 'M-D',
'1000111': () => {
return this.RAM[this.ARegister] - this.DRegister
},
// '1000000': 'D&M',
'1000000': () => {
return this.DRegister & this.RAM[this.ARegister]
},
// '1010101': 'D|M'
'1010101': () => {
return this.DRegister | this.RAM[this.ARegister]
}
}
// Destination
this.dest = {
// '000': '0',
'000': (val) => {
// Do nothing
},
// '001': 'M',
'001': (val) => {
this.setRAM(val)
},
// '010': 'D',
'010': (val) => {
this.DRegister = val
},
// '011': 'MD',
'011': (val) => {
this.DRegister = val
this.setRAM(val)
},
// '100': 'A',
'100': (val) => {
this.ARegister = val
},
// '101': 'AM',
'101': (val) => {
this.setRAM(val)
this.ARegister = val
},
// '110': 'AD',
'110': (val) => {
this.DRegister = val
this.ARegister = val
},
// '111': 'AMD'
'111': (val) => {
this.DRegister = val
this.setRAM(val)
this.ARegister = val
}
}
this.jump = {
// '000': '0',
'000': (val) => {
return false
},
// '001': 'JGT',
'001': (val) => {
if (val > 0) {
return true
}
},
// '010': 'JEQ',
'010': (val) => {
if (val == 0) {
return true
}
},
// '011': 'JGE',
'011': (val) => {
if (val >= 0) {
return true
}
},
// '100': 'JLT',
'100': (val) => {
if (val < 0) {
return true
}
},
// '101': 'JNE',
'101': (val) => {
if (val != 0) {
return true
}
},
// '110': 'JLE',
'110': (val) => {
if (val <= 0) {
return true
}
},
// '111': 'JMP'
'111': (val) => {
return true
}
}
// Screen
this.SIZE_BITS = 512 * 256
this.SIZE_WORDS = this.SIZE_BITS / 16
this.SCREEN_RAM = 16384
this.CANVAS = null
this.CANVAS_CTX = null
this.CANVAS_DATA = null
function getBinVal(i) {
var bin = i.toString(2)
while (bin.length < 32) {
bin = "0" + bin
}
return bin
}
// Used for the screen
// Screen operates bit patterns
// 1 is on 0 is off
function dec2bin(dec) {
var bin = (dec >>> 0).toString(2)
var bit32 = getBinVal(bin)
return bit32.substring(16, 32)
}
// Get X and Y position of the word on the image
// According to the position in RAM
// RAM[16384 + r*32 + c%16]
this.getImageRowColumn = function () {
var numWord = this.ARegister - this.SCREEN_RAM
var y = Math.floor(numWord * 16 / 512)
var x = numWord * 16 % 512
var xy = { x: x, y: y }
return xy
}
// Draw pixel on canvas
this.drawPixel = function (x, y, r, g, b, a) {
var index = (x + y * this.CANVAS.width) * 4;
this.CANVAS_DATA.data[index + 0] = r;
this.CANVAS_DATA.data[index + 1] = g;
this.CANVAS_DATA.data[index + 2] = b;
this.CANVAS_DATA.data[index + 3] = a;
}
this.updateImageData = function (val) {
// get bin val
var rowColumn = this.getImageRowColumn()
var x = rowColumn.x
var y = rowColumn.y
var binVal = dec2bin(val)
var binAry = binVal.split('')
binAry.forEach((elem, i) => {
if (elem == 1) {
this.drawPixel(x + 16 - i, y, 0, 0, 0, 255)
} else {
this.drawPixel(x + 16 - i, y, 255, 255, 255, 0)
}
})
}
this.updateCanvas = function () {
this.CANVAS_CTX.putImageData(this.CANVAS_DATA, 0, 0);
}
// Just in order to turn off screen
this.screen = 1
// Set screen RAM for fast access
// As screen is updated often
this.setRAM = function (val) {
this.RAM[this.ARegister] = val
if (this.ARegister >= this.SCREEN_RAM && this.ARegister < this.KBD) {
if (this.screen) {
this.updateImageData(val)
}
}
}
// Instruction: ixxaccccccdddjjj
// Get opcode
// 0 = C instruction
// 1 = A instruction
this.getOpcode = function (ins) {
return ins.substring(0, 1)
}
this.getComp = function (ins) {
return ins.substring(3, 10)
}
this.getDest = function (ins) {
return ins.substring(10, 13)
}
this.getJump = function (ins) {
return ins.substring(13, 16)
}
this.debugCycle = function (ins) {
if (!this.debug) {
return
}
var opcode = this.getOpcode(ins)
console.log('Opcode ', opcode)
if (opcode == 0) {
console.log('At ', parseInt(ins, 2))
console.log('At (value) ', this.RAM[parseInt(ins, 2)])
}
console.log('Ins ', ins)
console.log('PC ', this.PC)
console.log('After Parse')
console.log('ALUOut', this.ALUOut)
console.log('AReg ', this.ARegister)
console.log('DReg ', this.DRegister)
console.log(this.RAM.slice(0, 16))
console.log('---')
}
this.cyclesDone = 0
this.debug = 0
this.cycle = function () {
this.currentPC = this.PC
if (typeof this.ROM[this.PC] == 'undefined') {
return
}
var ins = this.ROM[this.PC]
var opcode = this.getOpcode(ins)
if (opcode == 1) {
this.cycleC(ins)
} else {
this.cycleA(ins)
}
this.cyclesDone++
if (this.cyclesDone % 100000 == 0) {
if (this.debug) {
console.log(this.cyclesDone)
}
}
}
this.cycleC = function (ins) {
var comp = this.getComp(ins)
var dest = this.getDest(ins)
var jump = this.getJump(ins)
var jumped = false
var ALUOut = this.comp[comp]()
this.ALUOut = ALUOut
if (dest != '000') {
this.dest[dest](ALUOut)
}
if (jump != '000') {
if (this.jump[jump](ALUOut)) {
this.PC = this.ARegister
jumped = true
}
}
if (!jumped) {
this.PC++
}
this.debugCycle(ins)
}
this.cycleA = function (ins) {
this.ARegister = parseInt(ins, 2)
this.PC++
this.debugCycle(ins)
}
this.loadROM = function (str) {
var ass = new assembler(str)
var bin = ass.getAssembledCode()
var program = bin.split('\n')
for (i = 0; i < program.length; i++) {
this.ROM[i] = program[i];
}
}
}
module.exports = Hack