mirror of
https://github.com/osmarks/random-stuff
synced 2024-10-31 19:06:15 +00:00
171 lines
5.2 KiB
Lua
171 lines
5.2 KiB
Lua
local function fill_arr(length, with)
|
|
local t = {}
|
|
for i = 1, length do
|
|
t[i] = 0
|
|
end
|
|
return t
|
|
end
|
|
|
|
--[[
|
|
registers are 16 bits
|
|
registers 0 to 15 are r0 to rf
|
|
register 0 always contains 0 because this makes many things more elegant
|
|
register 15 is additionally the program counter because why not
|
|
]]
|
|
|
|
local function init(code)
|
|
-- preallocate 64KiB of memory
|
|
-- 64KiB is enough for anyone
|
|
-- (TODO: allow moar somehow?)
|
|
local memory = fill_arr(65536, 0)
|
|
-- load code into memory, at start
|
|
for i = 1, #code do
|
|
memory[i] = code:byte(i)
|
|
end
|
|
return {
|
|
memory = memory,
|
|
registers = fill_arr(16, 0)
|
|
}
|
|
end
|
|
|
|
--[[
|
|
instructions (everything >8 bits is big endian):
|
|
HALT - 00 - halt execution
|
|
NOP - 01 - do nothing
|
|
PEEK - 02 [register 1][register 2] [16-bit constant] - load value at (constant + ri2) in memory into ri1
|
|
POKE - 03 [register 1][register 2] [16-bit constant] - ↑ but other way round
|
|
ADD - 04 [register 1][register 2] [16-bit constant] - save (constant + ri2) to ri1
|
|
JEQ - 05 [register 1][register 2] [16-bit constant] - set program counter to constant if ri1 = ri2
|
|
JNE - 06 [register 1][register 2] [16-bit constant] - set program counter to constant if ri1 != ri2
|
|
JLT - 07 [register 1][register 2] [16-bit constant] - set program counter to constant if ri1 < ri2
|
|
SUB - 08 [register 1][register 2] [16-bit constant] - save (ri2 - constant) to ri1
|
|
MUL - 09 [register 1][register 2] [16-bit constant] - save (ri2 * constant) to ri1
|
|
DIV - 10 [register 1][register 2] [16-bit constant] - save (ri2 / constant) to ri1
|
|
MOD - 11 [register 1][register 2] [16-bit constant] - save (ri2 % constant) to ri1
|
|
SYSC - 12 something whatever TODO
|
|
|
|
TODO: bitops, syscalls
|
|
|
|
Integers are always unsigned because negative numbers are hard.
|
|
]]
|
|
|
|
local band = bit.band
|
|
local brshift = bit.brshift
|
|
|
|
local function hi_nybble(x) return brshift(x, 4) end
|
|
local function lo_nybble(x) return band(x, 0xF) end
|
|
local function u16from(hi, lo) return hi * 0x100 + lo end
|
|
local function truncate(x) return band(0xFFFF, x) end
|
|
local function u16_add(x, y) return truncate(x + y) end
|
|
local function u16_sub(x, y) return truncate(x - y) end
|
|
local function u16_div(x, y) return truncate(x / y) end
|
|
local function u16_mod(x, y) return truncate(x % y) end
|
|
local function u16to(x) return brshift(x, 8), band(x, 0xFF) end
|
|
|
|
local function step(state)
|
|
local function get_reg(ix)
|
|
if ix == 0 then return 0
|
|
else return state.registers[ix + 1] end
|
|
end
|
|
local function set_reg(ix, x) if ix ~= 0 then state.registers[ix + 1] = x end end
|
|
local function get_mem(pos)
|
|
return u16from(state.memory[pos + 1], state.memory[pos + 2])
|
|
end
|
|
local function set_mem(pos, x)
|
|
local b1, b2 = u16to(x)
|
|
state.memory[pos + 1] = b1
|
|
state.memory[pos + 2] = b2
|
|
end
|
|
|
|
local bpos = state.registers[16]
|
|
-- read four bytes from program counter location onward
|
|
local b1, b2, b3, b4 = unpack(state.memory, bpos + 1, bpos + 5)
|
|
|
|
-- increment program counter
|
|
state.registers[16] = bpos + 4
|
|
if state.registers[16] > #state.memory then
|
|
return false
|
|
end
|
|
|
|
-- HALT
|
|
if b1 == 0x00 then
|
|
return false
|
|
-- NOP
|
|
elseif b1 == 0x01 then
|
|
-- do nothing whatsoever
|
|
-- still doing nothing
|
|
-- PEEK
|
|
elseif b1 == 0x02 then
|
|
-- calculate address - sum constant + provided register value
|
|
local addr = u16_add(u16from(b3, b4), get_reg(lo_nybble(b2)))
|
|
set_reg(hi_nybble(b2), get_mem(addr))
|
|
-- POKE
|
|
elseif b1 == 0x03 then
|
|
local addr = u16_add(u16from(b3, b4), get_reg(lo_nybble(b2)))
|
|
set_mem(addr, get_reg(hi_nybble(b2)))
|
|
-- ADD
|
|
elseif b1 == 0x04 then
|
|
set_reg(hi_nybble(b2), u16_add(u16from(b3, b4), get_reg(lo_nybble(b2))))
|
|
-- JEQ
|
|
elseif b1 == 0x05 then
|
|
if get_reg(hi_nybble(b2)) == get_reg(lo_nybble(b2)) then
|
|
state.registers[16] = u16from(b3, b4)
|
|
end
|
|
-- JNE - maybe somehow factor out the logic here, as it's very close to JEQ
|
|
elseif b1 == 0x06 then
|
|
if get_reg(hi_nybble(b2)) ~= get_reg(lo_nybble(b2)) then
|
|
state.registers[16] = u16from(b3, b4)
|
|
end
|
|
-- JLT - see JNE
|
|
elseif b1 == 0x07 then
|
|
if get_reg(hi_nybble(b2)) < get_reg(lo_nybble(b2)) then
|
|
state.registers[16] = u16from(b3, b4)
|
|
end
|
|
-- SUB
|
|
elseif b1 == 0x08 then
|
|
set_reg(hi_nybble(b2), u16_sub(get_reg(lo_nybble(b2)), u16from(b3, b4)))
|
|
-- MUL
|
|
elseif b1 == 0x09 then
|
|
set_reg(hi_nybble(b2), u16_mul(u16from(b3, b4), get_reg(lo_nybble(b2))))
|
|
-- DIV
|
|
elseif b1 == 0x10 then
|
|
set_reg(hi_nybble(b2), u16_div(get_reg(lo_nybble(b2)), u16from(b3, b4)))
|
|
-- MOD
|
|
elseif b1 == 0x11 then
|
|
set_reg(hi_nybble(b2), u16_mod(get_reg(lo_nybble(b2)), u16from(b3, b4)))
|
|
-- TEST
|
|
elseif b1 == 0xFF then
|
|
for i, v in ipairs(state.registers) do
|
|
print(("r%x: %04x"):format(i - 1, v))
|
|
end
|
|
else
|
|
error(("illegal opcode %02x at %04x"):format(b1, state.registers[16]))
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function unhexize(s)
|
|
local s = s:gsub("[^0-9A-Fa-f]", "")
|
|
local out = {}
|
|
for i = 1, #s, 2 do
|
|
local pair = s:sub(i, i + 1)
|
|
table.insert(out, string.char(tonumber(pair, 16)))
|
|
end
|
|
return table.concat(out)
|
|
end
|
|
|
|
local state = init(unhexize [[04 b0 00 08
|
|
04 10 01 ff
|
|
04 e0 ff 02
|
|
03 e0 a0 00
|
|
04 de 03 01
|
|
02 c0 a0 00
|
|
02 dd a0 00
|
|
ff 00 00 00
|
|
04 44 00 01
|
|
03 40 20 01
|
|
04 aa 00 01
|
|
07 ab 00 00]])
|
|
|
|
while step(state) do end |