-- generate-architecture.lua -- This program uses the definitions in 8610-definition.lua to generate the assembler definitions, instruction list, and microcode. -- Also see 8610-definition.lua local debugInfo = {} local function getDebugInfo() return " ("..debugInfo.mnem.." sub "..debugInfo.sub..")" end local function hex(n) return string.format("%02X", n) end local function cycleAddSignals(cycle, cyc2, cyc2t, ops, sigs, opsUsed, sigsUsed) for _, name in ipairs(cycle) do if ops[name] then opsUsed[name] = true cycleAddSignals(ops[name], cyc2, cyc2t, ops, sigs, opsUsed, sigsUsed) elseif sigs[name] then sigsUsed[name] = true if not cyc2t[name] then cyc2t[name] = true table.insert(cyc2, name) else error("overspecified signal name: "..name..getDebugInfo()) end else error("invalid signal name: "..name.." does not name a signal or operation") end end end local function cycleSimplify(cycle, ops, sigs, opsUsed, sigsUsed) local cyc2, cyc2t = {}, {} cycleAddSignals(cycle, cyc2, cyc2t, ops, sigs, opsUsed, sigsUsed) return cyc2 end local function cycleToUcycle(cycle, ops, sigs, opsUsed, sigsUsed) local ucycle = {} local cyc2 = cycleSimplify(cycle, ops, sigs, opsUsed, sigsUsed) local nonempty = false for _, name in ipairs(cyc2) do nonempty = true local sig = sigs[name] assert(sig, "no signal named "..name) for i, sbit in ipairs(sig) do ucycle[sbit.rom] = ucycle[sbit.rom] or {} assert(not ucycle[sbit.rom][sbit.bit], "multiply defined bit") ucycle[sbit.rom][sbit.bit] = true end end return ucycle end local function encodeInstruction(opcode, instr, ucode, ops, sigs, opsUsed, sigsUsed) for sub, cycle in ipairs(instr) do debugInfo.sub = sub-1 local ucycle = cycleToUcycle(cycle, ops, sigs, opsUsed, sigsUsed) local uaddr = opcode*4 + (sub-1) assert(not ucode[uaddr], "overused ucode addr "..uaddr) ucode[uaddr] = ucycle end end local function sigsFromRoms(roms) local sigs = {} for romidx, rom in ipairs(roms) do for sigidx, sig in ipairs(rom.signals) do sigs[sig] = sigs[sig] or {} table.insert(sigs[sig], { rom = romidx, bit = sigidx-1, }) end end return sigs end local subruleDefs = [[ #subruledef rel8 { {addr} => { reladdr = addr - $ - 2 assert(reladdr <= 127, "Relative jump target is too far away") assert(reladdr >= -128, "Relative jump target is too far away") reladdr`8 } } #subruledef neg8 { {value} => { mvalue = -value mvalue`8 } }]] local function getAsmCode(mnem, instr) local opcodeS = string.format("$%02X", instr.opcode) local mnemPreImm = mnem mnem = mnem:gsub("imm8rel", "{value: rel8}") mnem = mnem:gsub("imm8neg", "{value: neg8}") mnem = mnem:gsub("imm8s" , "{value: s8}") mnem = mnem:gsub("imm8u" , "{value: u8}") mnem = mnem:gsub("imm8" , "{value: i8}" ) mnem = mnem:gsub("imm16" , "{value: i16}" ) if mnem == mnemPreImm then return mnem.." => "..opcodeS else return mnem.." => "..opcodeS.." @ value" end end local function getAsmsFromInstr(mnem, instr, aliases) local t = {} table.insert(t, getAsmCode(mnem, instr)) if aliases and aliases[mnem] then for _, mnem2 in ipairs(aliases[mnem]) do table.insert(t, getAsmCode(mnem2, instr)) end end return t end local function transformDescription(arch, desc) if arch.descShortcuts then for _, sc in ipairs(arch.descShortcuts) do pat, rep = unpack(sc) pat = pat:gsub("%%", "%%%%") desc = desc:gsub(pat, rep) end end return desc end local function printUnusedThing(thing, all, used) for name, _ in pairs(all) do if not used[name] then print("Unused "..thing..": "..name) end end end local function printUnused(ops, sigs, opsUsed, sigsUsed) printUnusedThing("Macro-operation", ops, opsUsed) printUnusedThing("Control Signal", sigs, sigsUsed) end local asmDataStr = [[ %s #ruledef { %s } ]] local function archToUcode(arch, writeFiles) local sigs = sigsFromRoms(arch.roms) local ops = arch.operations local sigsUsed = {} local opsUsed = {} local ucode = {} local opcodesUsed = {} local numOpcodesUsed = 0 local infolines = {} local asmlines = {} local catlet = "X" for _, instr in ipairs(arch.instructions) do if instr.category or instr.catlet then assert(instr.catlet, "category header has no catlet") catlet = instr.catlet if instr.category then assert(not instr.mnem, "mnem in category header") assert(not instr.opcode, "opcode in category header") table.insert(infolines, "\n") table.insert(infolines, instr.category.." ("..catlet.."):\n") end else local mnem = instr.mnem or error("instr has no mnem") local opcode = instr.opcode or error("instr has no opcode "..mnem) local ncycles = instr.ncycles or #instr assert(#instr <= 8, "operation too long "..opcode) local len = math.ceil(#instr / 4) if len>1 then assert(opcode%2==0, "len>1 on odd opcode") end for i = 1, len do local opcode2 = opcode+i-1 if opcodesUsed[opcode2] then local other = opcodesUsed[opcode2] error("overused opcode "..hex(opcode2)..": "..mnem.." part "..i.." overwrites "..other[2].." part "..other[3]) end opcodesUsed[opcode2] = {catlet, mnem, i} numOpcodesUsed = numOpcodesUsed+1 end debugInfo.mnem = mnem encodeInstruction(opcode, instr, ucode, ops, sigs, opsUsed, sigsUsed) if instr.desc then local desc = transformDescription(arch, instr.desc) table.insert(infolines, mnem..(" "):rep(13-#mnem)..hex(opcode).." "..ncycles.." "..desc.."\n") local mnem2 = arch.transformMnemonic and arch.transformMnemonic(mnem) or mnem local asms = getAsmsFromInstr(mnem2, instr, arch.aliases) for _, a in ipairs(asms) do table.insert(asmlines, a) end end end end printUnused(ops, sigs, opsUsed, sigsUsed) local lt = {"Opcodes used: "..numOpcodesUsed.."/256\n", " "} for i = 0, 15 do table.insert(lt, hex(i):sub(2, 2)) end table.insert(lt, "\n") for line = 0, 255, 16 do table.insert(lt, hex(line).." | ") for opcode = line, line+15 do if opcodesUsed[opcode] then table.insert(lt, opcodesUsed[opcode][1]) else table.insert(lt, "-") end end table.insert(lt, "\n") end if writeFiles then -- Output instruction list local info = arch.instructionListHeader..table.concat(infolines).."\n"..table.concat(lt) --print(info) local fo = io.open(arch.instructionListFile, "w") if fo then fo:write(info) fo:close() else print("Could not open \""..arch.instructionListFile.."\" for write") end -- Output customASM definitions local asmTable = arch.assemblerDefsHeader..string.format(asmDataStr, subruleDefs, table.concat(asmlines, "\n\t") ) local fo = io.open(arch.assemblerDefsFile, "w") if fo then fo:write(asmTable) fo:close() else print("Could not open \""..arch.assemblerDefsFile.."\" for write") end end return ucode end local function ucodeToBricks(ucode, roms) local bricks = {} for uaddr, ucycle in pairs(ucode) do for romidx, uline in ipairs(ucycle) do for bitidx, t in pairs(uline) do local rom = roms[romidx] assert(bitidx >=0 and bitidx <= rom.size[3]-1) local x = rom.pos[1] + (uaddr % rom.size[1]) local y = rom.pos[2] - math.floor(uaddr / rom.size[1]) local z = rom.pos[3] + bitidx bricks[x] = bricks[x] or {} bricks[x][y] = bricks[x][y] or {} assert(not bricks[x][y][z], "overlapping brick at "..x.." "..y.." "..z) bricks[x][y][z] = true end end end return bricks end ts = ts or { eval = function() end, call = function() end, } ts.eval [[ function commandShiftBrick(%x, %y, %z) { commandToServer('shiftBrick', %x, %y, %z); } function commandPlantBrick() { commandToServer('plantBrick'); } ]] local function plantBrickAt(pos, brickPos) local dx, dy, dz = pos[1]-brickPos[1], pos[2]-brickPos[2], pos[3]-brickPos[3] ts.call("commandShiftBrick", dy, -dx, dz) brickPos[1] = pos[1]; brickPos[2] = pos[2]; brickPos[3] = pos[3]; ts.call("commandPlantBrick") end local function buildBricks(bricks) local brickPos = {0, 0, 0} for x, xb in pairs(bricks) do for y, xyb in pairs(xb) do for z, t in pairs(xyb) do plantBrickAt({x, y, z}, brickPos) end end end end local function generateArchitecture(arch, writeFiles, build) local ucode = archToUcode(arch, writeFiles) if build then local bricks = ucodeToBricks(ucode, arch.roms) buildBricks(bricks) end end return generateArchitecture