297 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			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
 |