From 776c7e0b23c483543ca478b76825b2e7c00bdc62 Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Tue, 6 Dec 2022 11:53:24 -0500 Subject: [PATCH] Compile FPGA configurations to Lua --- mesecons_fpga/init.lua | 35 ++++++++++++++++- mesecons_fpga/logic.lua | 85 ++++++++++++++++++++++++----------------- 2 files changed, 84 insertions(+), 36 deletions(-) diff --git a/mesecons_fpga/init.lua b/mesecons_fpga/init.lua index fe86ad4..70bca1c 100644 --- a/mesecons_fpga/init.lua +++ b/mesecons_fpga/init.lua @@ -312,6 +312,37 @@ plg.red_box_around = function(i) end +local compile_cache = {} + +plg.compile_with_cache = function(instr) + local entry = compile_cache[instr] + if entry then + -- Prevent removal from the cache + entry.age = 0 + else + entry = { + func = lcore.compile(lcore.deserialize(instr)), + age = 0, + } + compile_cache[instr] = entry + end + return entry.func +end + +-- Entries are removed from the cache if they are unused for at least a minute. +local function clean_compile_cache() + for instr, entry in pairs(compile_cache) do + if entry.age < 1 then + entry.age = entry.age + 1 + else + compile_cache[instr] = nil + end + end + minetest.after(60, clean_compile_cache) +end +minetest.after(60, clean_compile_cache) + + plg.update = function(pos) local meta = minetest.get_meta(pos) if meta:get_int("valid") ~= 1 then @@ -323,9 +354,9 @@ plg.update = function(pos) return end - local is = lcore.deserialize(meta:get_string("instr")) + local func = plg.compile_with_cache(meta:get_string("instr")) local A, B, C, D = plg.getports(pos) - A, B, C, D = lcore.interpret(is, A, B, C, D) + A, B, C, D = func(A, B, C, D) plg.setports(pos, A, B, C, D) end diff --git a/mesecons_fpga/logic.lua b/mesecons_fpga/logic.lua index 0e268a2..810481b 100644 --- a/mesecons_fpga/logic.lua +++ b/mesecons_fpga/logic.lua @@ -6,16 +6,16 @@ local operations = { -- gate: Internal name -- short: Serialized form, single character -- fs_name: Display name, padded to 4 characters - -- func: Function that applies the operation + -- func: Function that returns tokens representing the operation -- unary: Whether this gate only has one input - { gate = "and", short = "&", fs_name = " AND", func = function(a, b) return a and b end }, - { gate = "or", short = "|", fs_name = " OR", func = function(a, b) return a or b end }, - { gate = "not", short = "~", fs_name = " NOT", func = function(_, b) return not b end, unary = true }, - { gate = "xor", short = "^", fs_name = " XOR", func = function(a, b) return a ~= b end }, - { gate = "nand", short = "?", fs_name = "NAND", func = function(a, b) return not (a and b) end }, + { gate = "and", short = "&", fs_name = " AND", func = function(a, b) return a, "and", b end }, + { gate = "or", short = "|", fs_name = " OR", func = function(a, b) return a, "or", b end }, + { gate = "not", short = "~", fs_name = " NOT", func = function(_, b) return "not", b end, unary = true }, + { gate = "xor", short = "^", fs_name = " XOR", func = function(a, b) return a, "~=", b end }, + { gate = "nand", short = "?", fs_name = "NAND", func = function(a, b) return "not (", a, "and", b, ")" end }, { gate = "buf", short = "_", fs_name = " =", func = function(_, b) return b end, unary = true }, - { gate = "xnor", short = "=", fs_name = "XNOR", func = function(a, b) return a == b end }, - { gate = "nor", short = "!", fs_name = " NOR", func = function(a, b) return not (a or b) end }, + { gate = "xnor", short = "=", fs_name = "XNOR", func = function(a, b) return a, "==", b end }, + { gate = "nor", short = "!", fs_name = " NOR", func = function(a, b) return "not (", a, "or", b, ")" end }, } lg.get_operations = function() @@ -177,46 +177,63 @@ lg.validate = function(t) return nil end --- interpreter -lg.interpret = function(t, a, b, c, d) - local function _action(s, v1, v2) +local fpga_env = setmetatable({}, { + __index = function(_, var) + error("FPGA code tried to read undeclared variable " .. var) + end, + __newindex = function(_, var) + error("FPGA code tried to set undeclared variable " .. var) + end, +}) + +-- compiler +lg.compile = function(t) + -- Get token generation function from action gate string + local function _action(s) for i, data in ipairs(operations) do if data.gate == s then - return data.func(v1, v2) + return data.func end end - return false -- unknown gate + return nil -- unknown gate end - local function _op(t, regs, io_in) - if t.type == "reg" then - return regs[t.n] + -- Serialize input operand + local function _op(t) + if t == nil then + return nil + elseif t.type == "reg" then + return "r" .. t.n else -- t.type == "io" - return io_in[t.port] + return "i" .. t.port + end + end + -- Serialize destination + local function _dst(t) + if t.type == "reg" then + return "local r" .. t.n + else -- t.type == "io" + return "o" .. t.port end end - local io_in = {A=a, B=b, C=c, D=d} - local regs = {} - local io_out = {} + local tokens = { + -- Declare inputs and outputs: + "return function(iA, iB, iC, iD) local oA, oB, oC, oD", + } for i = 1, 14 do local cur = t[i] if next(cur) ~= nil then - local v1, v2 - if cur.op1 ~= nil then - v1 = _op(cur.op1, regs, io_in) - end - v2 = _op(cur.op2, regs, io_in) - - local result = _action(cur.action, v1, v2) - - if cur.dst.type == "reg" then - regs[cur.dst.n] = result - else -- cur.dst.type == "io" - io_out[cur.dst.port] = result - end + table.insert(tokens, _dst(cur.dst)) + table.insert(tokens, "=") + table.insert_all(tokens, {_action(cur.action)(_op(cur.op1), _op(cur.op2))}) end end - return io_out.A, io_out.B, io_out.C, io_out.D + table.insert(tokens, "return oA, oB, oC, oD end") + + local code = table.concat(tokens, " ") + local func = assert(loadstring(code))() + setfenv(func, fpga_env) + return func end return lg