Skip to content

Instantly share code, notes, and snippets.

@Bananattack
Created June 24, 2016 07:00
Show Gist options
  • Save Bananattack/e1fff4f84ae74fec25540c3a97c3e264 to your computer and use it in GitHub Desktop.
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.
; 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 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, '') .. '"')
# 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"
```
-- 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