Created
June 24, 2016 07:00
-
-
Save Bananattack/e1fff4f84ae74fec25540c3a97c3e264 to your computer and use it in GitHub Desktop.
p80 - A virtual machine for pico8. Mainly just a proof of concept to test ideas to get around pico-8 size limitations.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; Example: move a sprite with buttons (drawn externally during yield) | |
b0 EQU $0000 | |
b1 EQU $0001 | |
b2 EQU $0002 | |
b3 EQU $0003 | |
b4 EQU $0004 | |
b5 EQU $0005 | |
b6 EQU $0006 | |
b7 EQU $0007 | |
b8 EQU $0008 | |
b9 EQU $0009 | |
b10 EQU $000A | |
b11 EQU $000B | |
px EQU $0100 | |
py EQU $0101 | |
syscall btn ; Reads b0..b11 | |
ld b, [px] | |
ld c, [py] | |
ld a, [b0] | |
cmp a, 0 | |
jreq +1 | |
dec b | |
ld a, [b1] | |
cmp a, 0 | |
jreq +1 | |
inc b | |
ld [px], b | |
ld a, [b2] | |
cmp a, 0 | |
jreq +1 | |
dec c | |
ld a, [b3] | |
cmp a, 0 | |
jreq +1 | |
inc c | |
ld [py], c | |
yield | |
jp $0000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- This Lua code was used to generate the suboperations used by by the VM interpreter. | |
-- Every instruction actually consists of two subinstructions to simplify the interpreter logic. | |
-- The first step is used to decode/load, the second step is used calculate/store | |
-- 't' refers to an internal register used for temporary calculations during instructions. | |
-- It's used to glue the sources to destinations for various instructions | |
subopnames={ | |
[0]='nop', | |
'ldta', | |
'ldtb', | |
'ldtc', | |
'ldtd', | |
'ldtia', | |
'ldtib', | |
'ldtic', | |
'ldtid', | |
'pop', | |
'ldtimmb', | |
'ldtimmw', | |
'ldtimmf', | |
'ldtmem', | |
'ldat', | |
'ldbt', | |
'ldct', | |
'lddt', | |
'ldiat', | |
'ldibt', | |
'ldict', | |
'ldidt', | |
'push', | |
'ldmemt', | |
'addat', | |
'subat', | |
'mulat', | |
'divat', | |
'modat', | |
'cmpat', | |
'inca', | |
'incb', | |
'incc', | |
'incd', | |
'deca', | |
'decb', | |
'decc', | |
'decd', | |
'jmp', | |
'call', | |
'jreq', | |
'jrne', | |
'jrlt', | |
'jrge', | |
'syscall', | |
} | |
subopcodes={} | |
for i = 0, #subopnames do | |
subopcodes[subopnames[i]] = i | |
end | |
ops={} | |
-- ld (a, b, c, d), (a, b, c, d, [a], [b], [c], [d], imm8, imm16, immf, [imm16]) (4 x 12) | |
-- (add, sub, mul, div, mod, cmp) a, (a, b, c, d, [a], [b], [c], [d], imm8, imm16, immf, [imm16]) (6 x 12) | |
for _, u in ipairs{ | |
{', a', 'ldta'}, | |
{', b', 'ldtb'}, | |
{', c', 'ldtc'}, | |
{', d', 'ldtd'}, | |
{', [a]', 'ldtia'}, | |
{', [b]', 'ldtib'}, | |
{', [c]', 'ldtic'}, | |
{', [d]', 'ldtid'}, | |
{', imm8', 'ldtimmb'}, | |
{', imm16', 'ldtimmw'}, | |
{', immf', 'ldtimmf'}, | |
{', [imm16]', 'ldtmem'}, | |
} do | |
for _, v in ipairs{ | |
{'ld a', 'ldat'}, | |
{'ld b', 'ldbt'}, | |
{'ld c', 'ldct'}, | |
{'ld d', 'lddt'}, | |
{'add a', 'addat'}, | |
{'sub a', 'subat'}, | |
{'mul a', 'mulat'}, | |
{'div a', 'divat'}, | |
{'mod a', 'modat'}, | |
{'cmp a', 'cmpat'}, | |
} do | |
table.insert(ops, {name = v[1] .. u[1], sop1 = u[2], sop2 = v[2]}) | |
end | |
end | |
-- ld ([a], [b], [c], [d], imm8, imm16, immf, [imm16]), (a, b, c, d) (8 x 4) | |
for _, u in ipairs{ | |
{', a', 'ldta'}, | |
{', b', 'ldtb'}, | |
{', c', 'ldtc'}, | |
{', d', 'ldtd'}, | |
} do | |
for _, v in ipairs{ | |
{'ld [a]', 'ldiat'}, | |
{'ld [b]', 'ldibt'}, | |
{'ld [c]', 'ldict'}, | |
{'ld [d]', 'ldidt'}, | |
{'ld [mem]', 'ldmemt'}, | |
} do | |
table.insert(ops, {name = v[1] .. u[1], sop1 = u[2], sop2 = v[2]}) | |
end | |
end | |
-- push (a, b, c, d) (4) | |
for _, u in ipairs{ | |
{'push a', 'ldta'}, | |
{'push b', 'ldtb'}, | |
{'push c', 'ldtc'}, | |
{'push d', 'ldtd'}, | |
} do | |
table.insert(ops, {name = u[1], sop1 = u[2], sop2 = 'push'}) | |
end | |
-- pop (a, b, c, d) (4) | |
for _, u in ipairs{ | |
{'pop a', 'ldat'}, | |
{'pop b', 'ldbt'}, | |
{'pop c', 'ldct'}, | |
{'pop d', 'lddt'}, | |
} do | |
table.insert(ops, {name = u[1], sop1 = 'pop', sop2 = u[2]}) | |
end | |
-- (inc, dec) (a, b, c, d) (2 x 4) | |
for _, u in ipairs{ | |
{'inc a', 'inca'}, | |
{'inc b', 'incb'}, | |
{'inc c', 'incc'}, | |
{'inc d', 'incd'}, | |
{'dec a', 'deca'}, | |
{'dec b', 'decb'}, | |
{'dec c', 'decc'}, | |
{'dec d', 'decd'}, | |
} do | |
table.insert(ops, {name = u[1], sop1 = 'nop', sop2 = u[2]}) | |
end | |
-- (jmp, call) (imm16, [imm16]) (2 x 2) | |
for _, u in ipairs{ | |
{' imm16', 'ldtimmw'}, | |
{' [imm16]', 'ldtmem'}, | |
} do | |
for _, v in ipairs{ | |
{'jmp', 'jmp'}, | |
{'call', 'call'}, | |
} do | |
table.insert(ops, {name = v[1] .. u[1], sop1 = u[2], sop2 = v[2]}) | |
end | |
end | |
-- (jpeq, jpne, jplt, ifgt) imm16 (4) | |
for _, u in ipairs{ | |
{'jreq imm16', 'jreq'}, | |
{'jrne imm16', 'jrne'}, | |
{'jrlt imm16', 'jrlt'}, | |
{'jrge imm16', 'jrge'}, | |
} do | |
table.insert(ops, {name = u[1], sop1 = 'ldtimmw', sop2 = u[2]}) | |
end | |
-- return | |
table.insert(ops, {name = 'return', sop1 = 'pop', sop2 = 'jmp'}) | |
-- syscall | |
table.insert(ops, {name = 'syscall imm8', sop1 = 'ldtimmb', sop2 = 'syscall'}) | |
opnames={'[0x00] = "nop"'} | |
opsubs1={} | |
opsubs2={} | |
for i, op in ipairs(ops) do | |
local opsub1 = subopcodes[op.sop1] | |
local opsub2 = subopcodes[op.sop2] | |
if not opsub1 then | |
error('invalid subop ' .. op.sop1 .. ' in slot 1 of opcode ' .. i) | |
end | |
if not opsub2 then | |
error('invalid subop ' .. op.sop2 .. ' in slot 2 of opcode ' .. i) | |
end | |
table.insert(opnames, string.format('[0x%02x] = "%s"', i, op.name)) | |
table.insert(opsubs1, string.format('%02x', opsub1)) | |
table.insert(opsubs2, string.format('%02x', opsub2)) | |
end | |
print('opnames={\n ' .. table.concat(opnames, ',\n ') .. '\n}') | |
print('op1="' .. table.concat(opsubs1, '') .. '"') | |
print('op2="' .. table.concat(opsubs2, '') .. '"') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Addressing Modes | |
`a` = a register (accumulator) (0 extra bytes) | |
`b` = b register (0 extra bytes) | |
`c` = c register (0 extra bytes) | |
`d` = d register (0 extra bytes) | |
`[a]` = indirected a (0 extra bytes) | |
`[b]` = indirected b (0 extra bytes) | |
`[c]` = indirected c (0 extra bytes) | |
`[d]` = indirected d (0 extra bytes) | |
`imm8` = immediate unsigned 8-bit integer (1 extra byte) | |
`imm16` = immediate signed 16-bit integer (2 extra bytes, little-endian) | |
`immf` = immediate signed 16.16 fixed point (4 extra bytes, little-endian) | |
`[imm16]` = indirected signed 16-bit address (2 extra bytes) | |
# Miscellaneous Notes | |
- all registers and memory words are 16.16 fixed point, but it is possible to load 8 or 16 bit immediate values for convenience. | |
- arithmetic operations are only possible through the accumulator. | |
- `cmp` is the only instruction affect flags, used by `jreq`/`jrnz`/j`rlt`/`jrge`. | |
- `jrXX` instructions are relative jumps | |
- the stack pointer is inaccessible but starts at $1000 and grows downward as stuff is pushed. | |
- every instruction takes 1 byte for the opcode + any extra bytes required by its addressing mode. | |
- `yield` will suspend execution of the program so the scripts outside the VM have a chance to run | |
- `syscall` will request a special operation from external code, for stuff that doesn't belong in the VM itself. so far there's just `syscall btn` (`syscall $00`), which loads the first 12 bytes of global memory to the state of the pico8 buttons. | |
# Opcode chart | |
``` | |
[0x00] = "nop", | |
[0x01] = "ld a, a", | |
[0x02] = "ld b, a", | |
[0x03] = "ld c, a", | |
[0x04] = "ld d, a", | |
[0x05] = "add a, a", | |
[0x06] = "sub a, a", | |
[0x07] = "mul a, a", | |
[0x08] = "div a, a", | |
[0x09] = "mod a, a", | |
[0x0a] = "cmp a, a", | |
[0x0b] = "ld a, b", | |
[0x0c] = "ld b, b", | |
[0x0d] = "ld c, b", | |
[0x0e] = "ld d, b", | |
[0x0f] = "add a, b", | |
[0x10] = "sub a, b", | |
[0x11] = "mul a, b", | |
[0x12] = "div a, b", | |
[0x13] = "mod a, b", | |
[0x14] = "cmp a, b", | |
[0x15] = "ld a, c", | |
[0x16] = "ld b, c", | |
[0x17] = "ld c, c", | |
[0x18] = "ld d, c", | |
[0x19] = "add a, c", | |
[0x1a] = "sub a, c", | |
[0x1b] = "mul a, c", | |
[0x1c] = "div a, c", | |
[0x1d] = "mod a, c", | |
[0x1e] = "cmp a, c", | |
[0x1f] = "ld a, d", | |
[0x20] = "ld b, d", | |
[0x21] = "ld c, d", | |
[0x22] = "ld d, d", | |
[0x23] = "add a, d", | |
[0x24] = "sub a, d", | |
[0x25] = "mul a, d", | |
[0x26] = "div a, d", | |
[0x27] = "mod a, d", | |
[0x28] = "cmp a, d", | |
[0x29] = "ld a, [a]", | |
[0x2a] = "ld b, [a]", | |
[0x2b] = "ld c, [a]", | |
[0x2c] = "ld d, [a]", | |
[0x2d] = "add a, [a]", | |
[0x2e] = "sub a, [a]", | |
[0x2f] = "mul a, [a]", | |
[0x30] = "div a, [a]", | |
[0x31] = "mod a, [a]", | |
[0x32] = "cmp a, [a]", | |
[0x33] = "ld a, [b]", | |
[0x34] = "ld b, [b]", | |
[0x35] = "ld c, [b]", | |
[0x36] = "ld d, [b]", | |
[0x37] = "add a, [b]", | |
[0x38] = "sub a, [b]", | |
[0x39] = "mul a, [b]", | |
[0x3a] = "div a, [b]", | |
[0x3b] = "mod a, [b]", | |
[0x3c] = "cmp a, [b]", | |
[0x3d] = "ld a, [c]", | |
[0x3e] = "ld b, [c]", | |
[0x3f] = "ld c, [c]", | |
[0x40] = "ld d, [c]", | |
[0x41] = "add a, [c]", | |
[0x42] = "sub a, [c]", | |
[0x43] = "mul a, [c]", | |
[0x44] = "div a, [c]", | |
[0x45] = "mod a, [c]", | |
[0x46] = "cmp a, [c]", | |
[0x47] = "ld a, [d]", | |
[0x48] = "ld b, [d]", | |
[0x49] = "ld c, [d]", | |
[0x4a] = "ld d, [d]", | |
[0x4b] = "add a, [d]", | |
[0x4c] = "sub a, [d]", | |
[0x4d] = "mul a, [d]", | |
[0x4e] = "div a, [d]", | |
[0x4f] = "mod a, [d]", | |
[0x50] = "cmp a, [d]", | |
[0x51] = "ld a, imm8", | |
[0x52] = "ld b, imm8", | |
[0x53] = "ld c, imm8", | |
[0x54] = "ld d, imm8", | |
[0x55] = "add a, imm8", | |
[0x56] = "sub a, imm8", | |
[0x57] = "mul a, imm8", | |
[0x58] = "div a, imm8", | |
[0x59] = "mod a, imm8", | |
[0x5a] = "cmp a, imm8", | |
[0x5b] = "ld a, imm16", | |
[0x5c] = "ld b, imm16", | |
[0x5d] = "ld c, imm16", | |
[0x5e] = "ld d, imm16", | |
[0x5f] = "add a, imm16", | |
[0x60] = "sub a, imm16", | |
[0x61] = "mul a, imm16", | |
[0x62] = "div a, imm16", | |
[0x63] = "mod a, imm16", | |
[0x64] = "cmp a, imm16", | |
[0x65] = "ld a, immf", | |
[0x66] = "ld b, immf", | |
[0x67] = "ld c, immf", | |
[0x68] = "ld d, immf", | |
[0x69] = "add a, immf", | |
[0x6a] = "sub a, immf", | |
[0x6b] = "mul a, immf", | |
[0x6c] = "div a, immf", | |
[0x6d] = "mod a, immf", | |
[0x6e] = "cmp a, immf", | |
[0x6f] = "ld a, [imm16]", | |
[0x70] = "ld b, [imm16]", | |
[0x71] = "ld c, [imm16]", | |
[0x72] = "ld d, [imm16]", | |
[0x73] = "add a, [imm16]", | |
[0x74] = "sub a, [imm16]", | |
[0x75] = "mul a, [imm16]", | |
[0x76] = "div a, [imm16]", | |
[0x77] = "mod a, [imm16]", | |
[0x78] = "cmp a, [imm16]", | |
[0x79] = "ld [a], a", | |
[0x7a] = "ld [b], a", | |
[0x7b] = "ld [c], a", | |
[0x7c] = "ld [d], a", | |
[0x7d] = "ld [mem], a", | |
[0x7e] = "ld [a], b", | |
[0x7f] = "ld [b], b", | |
[0x80] = "ld [c], b", | |
[0x81] = "ld [d], b", | |
[0x82] = "ld [mem], b", | |
[0x83] = "ld [a], c", | |
[0x84] = "ld [b], c", | |
[0x85] = "ld [c], c", | |
[0x86] = "ld [d], c", | |
[0x87] = "ld [mem], c", | |
[0x88] = "ld [a], d", | |
[0x89] = "ld [b], d", | |
[0x8a] = "ld [c], d", | |
[0x8b] = "ld [d], d", | |
[0x8c] = "ld [mem], d", | |
[0x8d] = "push a", | |
[0x8e] = "push b", | |
[0x8f] = "push c", | |
[0x90] = "push d", | |
[0x91] = "pop a", | |
[0x92] = "pop b", | |
[0x93] = "pop c", | |
[0x94] = "pop d", | |
[0x95] = "inc a", | |
[0x96] = "inc b", | |
[0x97] = "inc c", | |
[0x98] = "inc d", | |
[0x99] = "dec a", | |
[0x9a] = "dec b", | |
[0x9b] = "dec c", | |
[0x9c] = "dec d", | |
[0x9d] = "jmp imm16", | |
[0x9e] = "call imm16", | |
[0x9f] = "jmp [imm16]", | |
[0xa0] = "call [imm16]", | |
[0xa1] = "jreq imm16", | |
[0xa2] = "jrne imm16", | |
[0xa3] = "jrlt imm16", | |
[0xa4] = "jrge imm16", | |
[0xa5] = "return", | |
[0xa6] = "syscall imm8" | |
[0xff] = "yield" | |
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- pico8 interpreter code. | |
function unhex(s,n) | |
n = n or 2 | |
local t = {} | |
for i = 1, #s, n do | |
add(t, ('0x' .. sub(s, i, i+n-1)) + 0) | |
end | |
return t | |
end | |
function nop()end | |
va=0 | |
vb=0 | |
vc=0 | |
vd=0 | |
veq=0 | |
vlt=0 | |
vs=0x1000 | |
vg={} | |
for i=0,16384 do | |
vg[i]=0 | |
end | |
vpc=0 | |
function vrdb() | |
local v=vprg[vpc+1] | |
vpc+=1 | |
return v | |
end | |
function vrdw() | |
local l=vrdb() | |
return l+vrdb()*256 | |
end | |
function vrdf() | |
local a=vrdb() | |
local b=vrdb() | |
local c=vrdb() | |
return a/256/256+b/256+c+vrdb()*256 | |
end | |
vsops={ | |
nop, | |
function()vt=va end, | |
function()vt=vb end, | |
function()vt=vc end, | |
function()vt=vd end, | |
function()vt=vg[va]end, | |
function()vt=vg[vb]end, | |
function()vt=vg[vc]end, | |
function()vt=vg[vd]end, | |
function()vs+=1 vt=vg[vs]end, | |
function()vt=vrdb()end, | |
function()vt=vrdw()end, | |
function()vt=vrdf()end, | |
function()vt=vg[vrdw()]end, | |
function()va=vt end, | |
function()vb=vt end, | |
function()vc=vt end, | |
function()vd=vt end, | |
function()vg[va]=vt end, | |
function()vg[vb]=vt end, | |
function()vg[vc]=vt end, | |
function()vg[vd]=vt end, | |
function()vg[vs]=vt vs-=1 end, | |
function()vg[vrdw()]=vt end, | |
function()va+=vt end, | |
function()va-=vt end, | |
function()va*=vt end, | |
function()va/=vt end, | |
function()va%=vt end, | |
function()veq=va==vt vlt=va<vt end, | |
function()va+=1 end, | |
function()vb+=1 end, | |
function()vc+=1 end, | |
function()vd+=1 end, | |
function()va-=1 end, | |
function()vb-=1 end, | |
function()vc-=1 end, | |
function()vd-=1 end, | |
function()vpc=vt end, | |
function()vg[vs]=vpc vs-=1 vpc=vt end, | |
function()if veq then vpc+=vt end end, | |
function()if not veq then vpc+=vt end end, | |
function()if vlt then vpc+=vt end end, | |
function()if not vlt then vpc+=vt end end, | |
function()vscl[vt+1]()end | |
} | |
vscl={ | |
function() | |
for i=0,12 do | |
vg[i]=btn(i%6,flr(i/6))and 1 or 0 | |
end | |
end | |
} | |
op1=unhex"0001010101010101010101020202020202020202020303030303030303030304040404040404040404050505050505050505050606060606060606060607070707070707070707080808080808080808080a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0c0c0c0c0c0c0c0c0c0c0d0d0d0d0d0d0d0d0d0d0101010101020202020203030303030404040404010203040909090900000000000000000b0b0d0d0b0b0b0b090a" | |
op2=unhex"000e0f101118191a1b1c1d0e0f101118191a1b1c1d0e0f101118191a1b1c1d0e0f101118191a1b1c1d0e0f101118191a1b1c1d0e0f101118191a1b1c1d0e0f101118191a1b1c1d0e0f101118191a1b1c1d0e0f101118191a1b1c1d0e0f101118191a1b1c1d0e0f101118191a1b1c1d0e0f101118191a1b1c1d1213141517121314151712131415171213141517161616160e0f10111e1f2021222324252627262728292a2b262c" | |
hex='0123456789abcdef' | |
src='a600' -- syscall btn | |
..'700001' -- ld b, [px] | |
..'710101' -- ld c, [py] | |
..'6f0000' -- ld a, [b0] | |
..'5a00' -- cmp a, 0 | |
..'a10100' -- jreq +1 | |
..'9a' -- dec b | |
..'6f0100' -- ld a, [b1] | |
..'5a00' -- cmp a, 0 | |
..'a10100' -- jreq +1 | |
..'96' -- inc b | |
..'820001' -- ld [px], b | |
..'6f0200' -- ld a, [b2] | |
..'5a00' -- cmp a, 0 | |
..'a10100' -- jreq +1 | |
..'9b' -- dec c | |
..'6f0300' -- ld a, [b3] | |
..'5a00' -- cmp a, 0 | |
..'a10100' -- jreq +1 | |
..'97' -- inc c | |
..'870101' -- ld [py], c | |
..'ff' -- yield | |
..'9d0000' -- jp $0000 | |
vprg=unhex(src) | |
function vrun() | |
while true do | |
local b=vrdb() | |
if b==0xff then | |
return | |
end | |
b+=1 | |
vsops[op1[b]+1]() | |
vsops[op2[b]+1]() | |
end | |
end | |
c=0 | |
function _draw() | |
rectfill(0,0,128,128,flr(c/128)%3) | |
cursor() | |
color(7) | |
print('a = ' .. va) | |
print('b = ' .. vb) | |
print('c = ' .. vc) | |
print('d = ' .. vd) | |
print('[0x100] = ' .. vg[0x100]) | |
print('[0x101] = ' .. vg[0x101]) | |
print(stat(1)) | |
circ(vg[0x100], vg[0x101], 5) | |
c+=1 | |
end | |
vg[0x100], vg[0x101] = 64, 64 | |
function _update() | |
vrun() | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment