8608/generate-architecture.lua

288 lines
8.0 KiB
Lua

-- generate-architecture.lua
-- This program uses the definitions in 8608-definition.lua to generate the assembler definitions, instruction list, and microcode.
-- Also see 8608-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)
for _, name in ipairs(cycle) do
if ops[name] then
cycleAddSignals(ops[name], cyc2, cyc2t, ops, sigs)
elseif sigs[name] then
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)
local cyc2, cyc2t = {}, {}
cycleAddSignals(cycle, cyc2, cyc2t, ops, sigs)
return cyc2
end
local function cycleToUcycle(cycle, ops, sigs)
local ucycle = {}
local cyc2 = cycleSimplify(cycle, ops, sigs)
local hasbase = false
local nonempty = false
for _, name in ipairs(cyc2) do
nonempty = true
if name=="always1" then hasbase = true end
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
if nonempty and (not hasbase) then error("cycle has no base"..getDebugInfo()) end
return ucycle
end
local function encodeInstruction(opcode, instr, ucode, ops, sigs)
for sub, cycle in ipairs(instr) do
debugInfo.sub = sub-1
local ucycle = cycleToUcycle(cycle, ops, sigs)
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 relJmpStr = [[
%s {addr} => {
reladdr = addr - $ - 2
assert(reladdr <= 127, "%s: Relative jump target is too far away")
assert(reladdr >= -128, "%s: Relative jump target is too far away")
%s @ reladdr`8
}]]
local wordRelStr = [[
%s+{value: i%i} => {
assert(value <= %i, "Relative address is too far away")
assert(value >= %i, "Relative address is too far away")
%s @ value`%i
}
%s-{value: i%i} => {
mvalue = -value
assert(mvalue <= %i, "Relative address is too far away")
assert(mvalue >= %i, "Relative address is too far away")
%s @ mvalue`%i
}]]
local byteNegStr = [[
%s {value:i8} => {
mvalue = -value
%s @ mvalue`8
}
]]
local function getAsmCode(mnem, instr)
local reljmp = instr.jmp and instr.rel
local opcodeS = string.format("$%02X", instr.opcode)
if reljmp then
assert(mnem:find("imm8"), "relative jump without imm8")
local mnemPart = mnem:gsub(" imm8", "")
return string.format(relJmpStr,
mnemPart, mnemPart, mnemPart,
opcodeS
)
elseif mnem:find("%+imm") then
local mnemPart, bitsS = mnem:match("^([^%+]+)%+imm([0-9]+)$")
local bits = tonumber(bitsS)
local maxVal = math.pow(2, bits-1)-1
local minVal = -math.pow(2, bits-1)
return string.format(wordRelStr,
mnemPart, bits, maxVal, minVal, opcodeS, bits,
mnemPart, bits, maxVal, minVal, opcodeS, bits
)
elseif mnem:find("imm8neg") then
mnemPart = mnem:match("^([^ ]+) imm8neg$")
return string.format(byteNegStr, mnemPart, opcodeS)
elseif mnem:find("imm8") then
mnem = mnem:gsub("imm8", "{value: i8}")
return mnem.." => "..opcodeS.." @ value"
elseif mnem:find("imm16") then
mnem = mnem:gsub("imm16", "{value: i16}")
return mnem.." => "..opcodeS.." @ value"
else
return mnem.." => "..opcodeS
end
end
local function getAsmsFromInstr(instr, aliases)
local mnem = instr.mnem
local t = {}
if mnem then
table.insert(t, getAsmCode(mnem, instr))
if aliases[mnem] then
for _, mnem2 in ipairs(aliases[mnem]) do
table.insert(t, getAsmCode(mnem2, instr))
end
end
end
return t
end
local asmDataStr = [[
#ruledef {
%s
}
]]
local function archToUcode(arch)
local sigs = sigsFromRoms(arch.roms)
local ops = arch.operations
local ucode = {}
local opcodesUsed = {}
local numOpcodesUsed = 0
local infolines = {}
local asmlines = {}
local catlet = "X"
for _, instr in ipairs(arch.instructions) do
if instr.category then
catlet = instr.catlet
table.insert(infolines, "\n")
table.insert(infolines, instr.category.." ("..catlet.."):\n")
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
assert(not opcodesUsed[opcode2], "overused opcode "..hex(opcode))
opcodesUsed[opcode2] = catlet
numOpcodesUsed = numOpcodesUsed+1
end
debugInfo.mnem = mnem
encodeInstruction(opcode, instr, ucode, ops, sigs)
if instr.desc then
table.insert(infolines, mnem..(" "):rep(13-#mnem)..hex(opcode).." "..ncycles.." "..instr.desc.."\n")
end
local asms = getAsmsFromInstr(instr, arch.aliases)
for _, a in ipairs(asms) do table.insert(asmlines, a) end
end
end
local lt = {"Opcodes used: "..numOpcodesUsed.."/255\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])
else table.insert(lt, "-") end
end
table.insert(lt, "\n")
end
local writeFiles = true
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, 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 buildArch(arch)
local ucode = archToUcode(arch)
local bricks = ucodeToBricks(ucode, arch.roms)
buildBricks(bricks)
end
local arch = require("8608-definition")
buildArch(arch)