269 lines
7.4 KiB
Lua
269 lines
7.4 KiB
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 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("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 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
|
|
|
|
-- Output instruction list
|
|
local info = table.concat(infolines).."\n"..table.concat(lt)
|
|
--print(info)
|
|
local fo = io.open("instructionList.txt", "w")
|
|
if fo then
|
|
fo:write(info)
|
|
fo:close()
|
|
end
|
|
|
|
|
|
-- Output customASM definitions
|
|
local fi = io.open("arch-8608-template.asm")
|
|
local asmDataStr = fi:read("*a")
|
|
fi:close()
|
|
|
|
local asmTable = string.format(asmDataStr, table.concat(asmlines, "\n\t"))
|
|
local fo = io.open("arch-8608.asm", "w")
|
|
if fo then
|
|
fo:write(asmTable)
|
|
fo:close()
|
|
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("rom-8608-defs")
|
|
buildArch(arch)
|