First web implementation draft of the draft
This commit is contained in:
@@ -87,7 +87,7 @@ The lower nibble (bits 0 to 3) of the status can be modified from the applicatio
|
|||||||
|
|
||||||
## Sound output
|
## Sound output
|
||||||
|
|
||||||
ESOP supports monophonic sound output with a beeper controllable via `0x06` port. As long as the port value is non-zero, the beeper must emit a sound wave of the specified frequency. The frequency is specified in semitones relative to A4 (440 Hz), with A4 itself being located at `0x80` value.
|
ESOP supports monophonic sound output with a beeper controllable via `0x06` port. As long as the port value is non-zero, the beeper must emit a sound wave of the specified frequency. The frequency is specified in semitones relative to A4 (440 Hz), with A4 itself being located at `0x30` value.
|
||||||
|
|
||||||
## System calls
|
## System calls
|
||||||
|
|
||||||
@@ -107,6 +107,7 @@ The only system call required to be implemented in any ESOP-compatible runtime i
|
|||||||
|
|
||||||
- serial/debug port I/O;
|
- serial/debug port I/O;
|
||||||
- persistent/flash memory I/O (up to 16 MB), operating with 256-byte pages or individual bytes;
|
- persistent/flash memory I/O (up to 16 MB), operating with 256-byte pages or individual bytes;
|
||||||
|
- setting and getting datetime information;
|
||||||
- initiating and receiving GSM voice calls;
|
- initiating and receiving GSM voice calls;
|
||||||
- sending DTMF signals during active voice calls;
|
- sending DTMF signals during active voice calls;
|
||||||
- active voice call manipulation (terminate, hold/unhold, bridge);
|
- active voice call manipulation (terminate, hold/unhold, bridge);
|
||||||
@@ -130,8 +131,8 @@ ID |Params |Command|Meaning
|
|||||||
`0x03`|1s 3b [addr addr]|`0xa3` |Read a byte under the 3-byte flash address
|
`0x03`|1s 3b [addr addr]|`0xa3` |Read a byte under the 3-byte flash address
|
||||||
`0x04`|1b 3b [page addr]|`0x84` |Write a page under the 3-byte flash address
|
`0x04`|1b 3b [page addr]|`0x84` |Write a page under the 3-byte flash address
|
||||||
`0x05`|1b 3b [page addr]|`0x85` |Read a page under the 3-byte flash address
|
`0x05`|1b 3b [page addr]|`0x85` |Read a page under the 3-byte flash address
|
||||||
`0x06`|||(reserved)
|
`0x06`|1s [addr] |`0x46` |Read the current system time/date information (5 bytes/40 bits, Unix time) into the address
|
||||||
`0x07`|||(reserved)
|
`0x07`|1s [addr] |`0x47` |Set the current system time/date information (5 bytes/40 bits, Unix time) from the address
|
||||||
`0x08`|||(reserved)
|
`0x08`|||(reserved)
|
||||||
`0x09`|||(reserved)
|
`0x09`|||(reserved)
|
||||||
`0x0a`|||(reserved)
|
`0x0a`|||(reserved)
|
||||||
|
|||||||
+213
@@ -0,0 +1,213 @@
|
|||||||
|
/**
|
||||||
|
* ESOP: Essential Stack-Operated Phone
|
||||||
|
* JS implementation
|
||||||
|
* @version 0.0.1
|
||||||
|
* @author Luxferre
|
||||||
|
* @license Unlicense <https://unlicense.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
//esop-ext
|
||||||
|
//Extension setup according to the main spec
|
||||||
|
|
||||||
|
function ESOPExtensions() {
|
||||||
|
|
||||||
|
//system
|
||||||
|
function setupSys(vm, configObj) {
|
||||||
|
vm.config = configObj
|
||||||
|
vm.wst=0xfc00 //no effect for now
|
||||||
|
vm.rst=0xfd00 //no effect for now
|
||||||
|
vm.devMemOffset = 0xfff8
|
||||||
|
vm.videoMemOffset = 0xfe00
|
||||||
|
vm.videoMemSize = 504
|
||||||
|
|
||||||
|
//setup RNG
|
||||||
|
var getRandomByte = ('crypto' in window) ? function() {
|
||||||
|
return window.crypto.getRandomValues(new Uint8Array(1))[0]
|
||||||
|
} : function() {return Math.random()*256|0}
|
||||||
|
|
||||||
|
vm.tracePort = 0x04 //setup the trace/random port
|
||||||
|
vm.setReadHook(0x04, function() { //update the random byte port before the program consumes it
|
||||||
|
vm.setdev(0x04, getRandomByte())
|
||||||
|
})
|
||||||
|
|
||||||
|
//setup status port
|
||||||
|
vm.setReadHook(0x05, function() {
|
||||||
|
vm.setdev(0x05, 0xff) //emulate full brightness, full charge and charger plugged in
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//graphics
|
||||||
|
function setupScreen(vm, cnv) {
|
||||||
|
var width = 84,
|
||||||
|
height = 48,
|
||||||
|
vramOffset = vm.videoMemOffset,
|
||||||
|
vramSize = vm.videoMemSize,
|
||||||
|
ctx = cnv.getContext('2d', {alpha: false})
|
||||||
|
ctx.globalAlpha = 1
|
||||||
|
|
||||||
|
//setup screen renderer
|
||||||
|
|
||||||
|
function renderScreen() {
|
||||||
|
var vram = vm.getram(vramOffset, vramSize),
|
||||||
|
vdata = ctx.getImageData(0,0,width,height),
|
||||||
|
ppos, pval, vbval, i, j
|
||||||
|
for(i=0;i<vRamSize;i++) {
|
||||||
|
vbval = vram[i]
|
||||||
|
for(j=0;j<8;j++) { //iterate from highest to lowest
|
||||||
|
ppos = ((i<<3) + j) << 2
|
||||||
|
pval = (vbval>>>(7-j))&1 ? 255 : 0
|
||||||
|
vdata.data[ppos] = pval
|
||||||
|
vdata.data[ppos+1] = pval
|
||||||
|
vdata.data[ppos+2] = pval
|
||||||
|
vdata.data[ppos+3] = 255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.putImageData(vdata, 0,0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// also, we need to set up the screen vector
|
||||||
|
|
||||||
|
var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame
|
||||||
|
rAF(function vecframe() {
|
||||||
|
vm.triggerVector(0)
|
||||||
|
renderScreen()
|
||||||
|
if(vm.active)
|
||||||
|
rAF(vecframe)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//keypad input
|
||||||
|
|
||||||
|
var livemap = { //map keys to bit values (to be updated in one of 4 ports)
|
||||||
|
'0': 0x0,
|
||||||
|
'1': 0x1,
|
||||||
|
'2': 0x2,
|
||||||
|
'3': 0x3,
|
||||||
|
'4': 0x4,
|
||||||
|
'5': 0x5,
|
||||||
|
'6': 0x6,
|
||||||
|
'7': 0x7,
|
||||||
|
'8': 0x8,
|
||||||
|
'9': 0x9,
|
||||||
|
'*': 0xe,
|
||||||
|
'#': 0xf,
|
||||||
|
'ArrowUp': 0xa, //A - Advance
|
||||||
|
'ArrowDown': 0xb, //B - Back
|
||||||
|
'Backspace': 0xc, //C - Cancel
|
||||||
|
'Enter': 0xd //D - Do
|
||||||
|
}
|
||||||
|
|
||||||
|
//set KaiOS aliases if necessary
|
||||||
|
livemap['End'] = livemap['Backspace']
|
||||||
|
livemap['EndCall'] = livemap['End']
|
||||||
|
|
||||||
|
//now remap the keys for PC according to the spec recommendation
|
||||||
|
|
||||||
|
livemap['a'] = livemap['ArrowUp']
|
||||||
|
livemap['b'] = livemap['ArrowDown']
|
||||||
|
livemap['c'] = livemap['Backspace']
|
||||||
|
livemap['d'] = livemap['Enter']
|
||||||
|
|
||||||
|
function setupInput(vm) {
|
||||||
|
|
||||||
|
function keypress(e) {
|
||||||
|
var k = e.key
|
||||||
|
if(k in livemap) {
|
||||||
|
var keyval = livemap[k],
|
||||||
|
portnum = keyval>7 ? 3 : 2,
|
||||||
|
bitmask = 1<<(keyval&7)
|
||||||
|
vm.setdev(portnum, vm.getdev(portnum) | bitmask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function keyrelease(e) {
|
||||||
|
var k = e.key
|
||||||
|
if(k in livemap) {
|
||||||
|
var keyval = livemap[k],
|
||||||
|
portnum = keyval>7 ? 3 : 2,
|
||||||
|
bitmask = (~(1<<(keyval&7)))&255
|
||||||
|
vm.setdev(portnum, vm.getdev(portnum) & bitmask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.removeEventListener('keydown', keypress)
|
||||||
|
window.removeEventListener('keyup', keyrelease)
|
||||||
|
window.addEventListener('keydown', keypress)
|
||||||
|
window.addEventListener('keyup', keyrelease)
|
||||||
|
|
||||||
|
vm.setdev(2, 0)
|
||||||
|
vm.setdev(3, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
//keep sound frequency matrix for all 87 non-zero sound port values with A4 aligned to 0x30 position
|
||||||
|
var soundFreqs = [0,29.14,30.87,32.7,34.65,36.71,38.89,41.2,43.65,46.25,49,51.91,55,58.27,61.74,65.41,69.3,73.42,77.78,82.41,87.31,92.5,98,103.83,110,116.54,123.47,130.81,138.59,146.83,155.56,164.81,174.61,185,196,207.65,220,233.08,246.94,261.63,277.18,293.66,311.13,329.63,349.23,369.99,392,415.3,440,466.16,493.88,523.25,554.37,587.33,622.25,659.26,698.46,739.99,783.99,830.61,880,932.33,987.77,1046.5,1108.73,1174.66,1244.51,1318.51,1396.91,1479.98,1567.98,1661.22,1760,1864.66,1975.53,2093,2217.46,2349.32,2489.02,2637.02,2793.83,2959.96,3135.96,3322.44,3520,3729.31,3951.07,4186.01], sfl = soundFreqs.length
|
||||||
|
|
||||||
|
function setupAudio(vm, actx) {
|
||||||
|
var osc = null //keep the existing oscillator reference here
|
||||||
|
vm.setdev(6,0)
|
||||||
|
vm.setWriteHook(6, function(port, portval) {
|
||||||
|
if(osc && !portval) { //stop the note
|
||||||
|
osc.stop()
|
||||||
|
osc.disconnect()
|
||||||
|
osc = null
|
||||||
|
}
|
||||||
|
else if(portval < sfl) { //start the note
|
||||||
|
osc = actx.createOscillator()
|
||||||
|
osc.type = 'square'
|
||||||
|
osc.frequency = soundFreqs[portval]
|
||||||
|
osc.connect(actx.destination)
|
||||||
|
osc.start(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function runSyscall(vm, buf) {
|
||||||
|
var call = buf[0]
|
||||||
|
if(call === 0x00) //simulate writing a byte to the debug port
|
||||||
|
console.log(('[DBG] 00' + buf[1].toString(16)).slice(-2))
|
||||||
|
else if(call === 0x06) { //read datetime info into 5 bytes starting from addr
|
||||||
|
//not very optimal but a future-proof approach for now
|
||||||
|
var tm = ('00' + (0|(Date.now()/1000)).toString(16)).slice(-10).match(/.{2}/g).map(function(x){return parseInt(x, 16)}),
|
||||||
|
targetaddr = (buf[1]<<8)|buf[2]
|
||||||
|
vm.setram(targetaddr, tm[0])
|
||||||
|
vm.setram(targetaddr+1, tm[1])
|
||||||
|
vm.setram(targetaddr+2, tm[2])
|
||||||
|
vm.setram(targetaddr+3, tm[3])
|
||||||
|
vm.setram(targetaddr+4, tm[4])
|
||||||
|
}
|
||||||
|
else if(call === 0x1f) //halt call, required for all implementations
|
||||||
|
vm.active = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupSyscalls(vm) {
|
||||||
|
var syscallBuf = new Uint8Array(9), expectedParams = 0, paramId = 0
|
||||||
|
vm.setdev(7, 0)
|
||||||
|
vm.setWriteHook(7, function(port, portval) {
|
||||||
|
if(expectedParams) { //already expecting a parameter, push there
|
||||||
|
syscallBuf[paramId++] = portval
|
||||||
|
expectedParams--
|
||||||
|
} else if(portval) { //start constructing a new syscall
|
||||||
|
syscallBuf[0] = portval&31
|
||||||
|
expectedParams = portval >> 5
|
||||||
|
paramId = 1
|
||||||
|
}
|
||||||
|
if(!expectedParams && paramId) { //all parameters used up but the call didn't complete
|
||||||
|
runSyscall(vm, syscallBuf)
|
||||||
|
paramId = 0
|
||||||
|
expectedParams = 0
|
||||||
|
syscallBuf.fill(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
setup: function(vm, cfg) {
|
||||||
|
setupSys(vm, cfg)
|
||||||
|
setupScreen(vm, cfg.canvas)
|
||||||
|
setupInput(vm)
|
||||||
|
setupSyscalls(vm)
|
||||||
|
setupAudio(vm, cfg.audio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
//app part not related to ESOP JS implementation
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
var devCfg = {
|
||||||
|
audio: window.audioContext || window.webkitAudioContext,
|
||||||
|
canvas: document.getElementById('C') //JS-specific implementation canvas DOM reference
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('apprun').addEventListener('click', function() {
|
||||||
|
var fileObj = document.getElementById('appselect').files[0]
|
||||||
|
if(fileObj.name.endsWith('.eso')) {
|
||||||
|
var rdr = new FileReader()
|
||||||
|
rdr.onload = function() {
|
||||||
|
var vm = UxnCore()
|
||||||
|
var ext = ESOPExtensions()
|
||||||
|
vm.boot()
|
||||||
|
ext.setup(vm, devCfg)
|
||||||
|
vm.load(new Uint8Array(rdr.result))
|
||||||
|
console.log('VM started')
|
||||||
|
vm.start(0x100)
|
||||||
|
}
|
||||||
|
rdr.readAsArrayBuffer(fileObj)
|
||||||
|
}
|
||||||
|
else window.alert('Please select an .eso file to run on ESOP')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener('DOMContentLoaded', main)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>ESOP Web ref test</title>
|
||||||
|
<meta name=viewport content="width=device-width">
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#C {
|
||||||
|
width: 240px;
|
||||||
|
height: auto;
|
||||||
|
image-rendering: -moz-crisp-edges;
|
||||||
|
image-rendering: -webkit-crisp-edges;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Essential Stack-Operated Phone</h1>
|
||||||
|
<canvas id=C width=84 height=48></canvas>
|
||||||
|
<div>
|
||||||
|
<input type=file id=appselect><br>
|
||||||
|
<button type=button id=apprun>Run!</button>
|
||||||
|
</div>
|
||||||
|
<script src=uxncore.js></script>
|
||||||
|
<script src=esop-ext.js></script>
|
||||||
|
<script src=esop-web-app.js></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+191
@@ -0,0 +1,191 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
var vec = (mem.dev[0] << 8) | mem.dev[1]
|
||||||
|
if(vec)
|
||||||
|
uxnstart(vec)
|
||||||
|
else {
|
||||||
|
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) {
|
||||||
|
if(bs) {
|
||||||
|
mem.dev[x] = y>>8
|
||||||
|
if(x in devWriteHooks)
|
||||||
|
devWriteHooks[x](x, mem.dev[x])
|
||||||
|
mem.dev[x+1] = y&255
|
||||||
|
if((x+1) in devWriteHooks)
|
||||||
|
devWriteHooks[x+1](x+1, mem.dev[x+1])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mem.dev[x] = y
|
||||||
|
if(x in devWriteHooks)
|
||||||
|
devWriteHooks[x](x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// main eval loop
|
||||||
|
|
||||||
|
function uxnstart(startaddr, vmTrace) {
|
||||||
|
pc = startaddr & 65535
|
||||||
|
var instr, src, dst, a, b, c, tmpTrace = !!vmTrace
|
||||||
|
if(!pc || mem.dev[0x0f]) {
|
||||||
|
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, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user