189 lines
6.7 KiB
JavaScript
189 lines
6.7 KiB
JavaScript
/**
|
|
* Uxn core JS implementation for ESOP VM
|
|
* @version 0.0.1
|
|
* @author Luxferre
|
|
* @license Unlicense <https://unlicense.org>
|
|
*/
|
|
|
|
function UxnCore() {
|
|
|
|
function initMem() {
|
|
return {
|
|
ram: new Uint8Array(65536), //main RAM
|
|
wst: {ptr: 0, pk: 0, dat: new Uint8Array(256)}, //working stack
|
|
rst: {ptr: 0, pk: 0, dat: new Uint8Array(256)}, //return stack
|
|
dev: new Uint8Array(256) //device memory
|
|
}
|
|
}
|
|
|
|
var mem = null,
|
|
bs = 0, //byte/short flag
|
|
km = 0, //keep mode flag
|
|
pc = 0, //program counter
|
|
devWriteHooks = {},
|
|
devReadHooks = {}
|
|
|
|
//helper methods
|
|
|
|
function trapout(errcode) { //error reporting with vector execution
|
|
console.error('Error code: '+errcode+' at instruction #'+pc)
|
|
console.log('Working stack: ', mem.wst.dat)
|
|
console.log('Return stack: ', mem.rst.dat)
|
|
pc = 0
|
|
}
|
|
|
|
function rel(x) {return x > 0x80 ? x - 256 : x}
|
|
function PUSH8(s, x) {if(s.ptr === 255) trapout(2);s.dat[s.ptr++] = x}
|
|
function PUSH16(s, x) {PUSH8(s, x>>8);PUSH8(s, x&255)}
|
|
function PUSH(s, x) {if(bs) PUSH16(s, x);else PUSH8(s, x)}
|
|
function POP8(s) {
|
|
var ptr = km ? --s.pk : --s.ptr
|
|
if(ptr < 0) trapout(1)
|
|
return s.dat[ptr]
|
|
}
|
|
function POP16(s) {return POP8(s) | (POP8(s)<<8)}
|
|
function POP(s) {return bs ? POP16(s) : POP8 (s)}
|
|
function JUMP(x, pc) {return bs ? x : (pc + rel(x))}
|
|
function POKE(x, y) {
|
|
if(bs) {
|
|
mem.ram[x] = y>>8
|
|
mem.ram[x+1] = y&255
|
|
}
|
|
else mem.ram[x] = y
|
|
}
|
|
function PEEK16(x) {return (mem.ram[x]<<8) | mem.ram[x+1]}
|
|
function PEEK(x) {return bs ? PEEK16(x) : mem.ram[x]}
|
|
function DEVR(x) {if(x in devReadHooks) devReadHooks[x]();return bs ? (mem.dev[x] << 8)|mem.dev[x+1] : mem.dev[x]}
|
|
function DEVW(x, y) {
|
|
var p, p2
|
|
if(bs) {
|
|
p = mem.dev[x]
|
|
p2 = mem.dev[x+1]
|
|
mem.dev[x] = y>>8
|
|
if(x in devWriteHooks)
|
|
devWriteHooks[x](x, mem.dev[x], p)
|
|
mem.dev[x+1] = y&255
|
|
if((x+1) in devWriteHooks)
|
|
devWriteHooks[x+1](x+1, mem.dev[x+1], p2)
|
|
}
|
|
else {
|
|
p = mem.dev[x]
|
|
mem.dev[x] = y
|
|
if(x in devWriteHooks)
|
|
devWriteHooks[x](x, y, p)
|
|
}
|
|
}
|
|
|
|
// main eval loop
|
|
|
|
function uxnstart(startaddr, vmTrace) {
|
|
pc = startaddr & 65535
|
|
var instr, src, dst, a, b, c, tmpTrace = !!vmTrace
|
|
if(!pc) {
|
|
this.active = false
|
|
return 0
|
|
}
|
|
this.active = true
|
|
while(instr = mem.ram[pc++]) {
|
|
bs = (instr & 0x20) ? 1 : 0 //short mode
|
|
if(instr & 0x40) { //return mode
|
|
src = mem.rst
|
|
dst = mem.wst
|
|
} else {
|
|
src = mem.wst
|
|
dst = mem.rst
|
|
}
|
|
if(instr & 0x80) { //keep mode
|
|
km = 1
|
|
src.pk = src.ptr
|
|
dst.pk = dst.ptr
|
|
} else km = 0
|
|
// main instruction selector
|
|
switch(instr & 0x1f) {
|
|
// Stack
|
|
case 0x00: /* LIT */ PUSH(src, PEEK(pc)); pc += bs + 1; break;
|
|
case 0x01: /* INC */ PUSH(src, POP(src) + 1); break;
|
|
case 0x02: /* POP */ POP(src); break;
|
|
case 0x03: /* NIP */ a = POP(src); POP(src); PUSH(src, a); break;
|
|
case 0x04: /* SWP */ a = POP(src); b = POP(src); PUSH(src, a); PUSH(src, b); break;
|
|
case 0x05: /* ROT */ a = POP(src); b = POP(src); c = POP(src); PUSH(src, b); PUSH(src, a); PUSH(src, c); break;
|
|
case 0x06: /* DUP */ a = POP(src); PUSH(src, a); PUSH(src, a); break;
|
|
case 0x07: /* OVR */ a = POP(src); b = POP(src); PUSH(src, b); PUSH(src, a); PUSH(src, b); break;
|
|
// Logic
|
|
case 0x08: /* EQU */ a = POP(src); b = POP(src); PUSH8(src, 0|(b == a)); break;
|
|
case 0x09: /* NEQ */ a = POP(src); b = POP(src); PUSH8(src, 0|(b != a)); break;
|
|
case 0x0a: /* GTH */ a = POP(src); b = POP(src); PUSH8(src, 0|(b > a)); break;
|
|
case 0x0b: /* LTH */ a = POP(src); b = POP(src); PUSH8(src, 0|(b < a)); break;
|
|
case 0x0c: /* JMP */ pc = JUMP(POP(src), pc); break;
|
|
case 0x0d: /* JCN */ a = POP(src); if(POP8(src)) pc = JUMP(a, pc); break;
|
|
case 0x0e: /* JSR */ PUSH16(dst, pc); pc = JUMP(POP(src), pc); break;
|
|
case 0x0f: /* STH */ PUSH(dst, POP(src)); break;
|
|
// Memory
|
|
case 0x10: /* LDZ */ PUSH(src, PEEK(POP8(src))); break;
|
|
case 0x11: /* STZ */ POKE(POP8(src), POP(src)); break;
|
|
case 0x12: /* LDR */ PUSH(src, PEEK(pc + rel(POP8(src)))); break;
|
|
case 0x13: /* STR */ POKE(pc + rel(POP8(src)), POP(src)); break;
|
|
case 0x14: /* LDA */ PUSH(src, PEEK(POP16(src))); break;
|
|
case 0x15: /* STA */ POKE(POP16(src), POP(src)); break;
|
|
case 0x16: /* DEI */ PUSH(src, DEVR(POP8(src))); break;
|
|
case 0x17: /* DEO */ a = POP8(src); b = POP(src); if(a===this.tracePort) tmpTrace=true;else DEVW(a, b); break;
|
|
// Arithmetic
|
|
case 0x18: /* ADD */ a = POP(src); b = POP(src); PUSH(src, b + a); break;
|
|
case 0x19: /* SUB */ a = POP(src); b = POP(src); PUSH(src, b - a); break;
|
|
case 0x1a: /* MUL */ a = POP(src); b = POP(src); PUSH(src, b * a); break;
|
|
case 0x1b: /* DIV */ a = POP(src); b = POP(src); if(!a) trapout(3); PUSH(src, 0|(b / a)); break;
|
|
case 0x1c: /* AND */ a = POP(src); b = POP(src); PUSH(src, b & a); break;
|
|
case 0x1d: /* ORA */ a = POP(src); b = POP(src); PUSH(src, b | a); break;
|
|
case 0x1e: /* EOR */ a = POP(src); b = POP(src); PUSH(src, b ^ a); break;
|
|
case 0x1f: /* SFT */ a = POP8(src); b = POP(src); PUSH(src, b >> (a & 0x0f) << ((a & 0xf0) >> 4)); break;
|
|
}
|
|
if(vmTrace || tmpTrace) {
|
|
console.log('Step: ', pc, 'Instruction:', instr.toString(16), 'Base instr:', (instr&0x1f).toString(16), 'stackptr:', src.ptr, 'stack:', src.dat)
|
|
tmpTrace = vmTrace
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
active: false,
|
|
boot: function() {
|
|
this.active = false
|
|
this.tracePort = 0x0e
|
|
this.memDevOffset = 0
|
|
mem = initMem()
|
|
bs = 0
|
|
pc = 0
|
|
km = 0
|
|
devWriteHooks = {}
|
|
devReadHooks = {}
|
|
},
|
|
load: function(prog, addr) {
|
|
if(!addr) addr = 0x100
|
|
for(var i=0, l=prog.length;i<l;i++)
|
|
mem.ram[addr+i] = prog[i]
|
|
},
|
|
start: uxnstart,
|
|
setdev: function(port, val) {
|
|
mem.dev[port] = val
|
|
if(this.memDevOffset)
|
|
mem.ram[this.memDevOffset+port] = val
|
|
},
|
|
getdev: function(port) {return mem.dev[port]},
|
|
getram: function(start, length) {return mem.ram.subarray(start, start+length)},
|
|
setram: function(addr, bVal) {mem.ram[addr] = bVal},
|
|
setWriteHook: function(port, cb) {
|
|
if(!cb) cb = function(){}
|
|
devWriteHooks[port] = cb
|
|
},
|
|
setReadHook: function(devPort, cb) {
|
|
if(!cb) cb = function(){}
|
|
devReadHooks[devPort] = cb
|
|
},
|
|
triggerVector: function(devId) {
|
|
var basePos = (devId >> 4) << 4
|
|
var targetAddr = (mem.dev[basePos] << 8) | mem.dev[basePos + 1]
|
|
if(targetAddr) uxnstart(targetAddr)
|
|
}
|
|
}
|
|
}
|