8608/generate-architecture.lua
2024-08-11 02:39:37 -06:00

297 lines
8.3 KiB
Lua

-- 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