--love 1 -- This is the main code file for the 8608 architecture emulator. -- It is meant to be run with the Love2d game engine: https://love2d.org -- This file deals mostly with the user interface, controls, and high-level functions. -- Emulation of the CPU internals is done by the 8608emulator.dll library. See 8608emulator.c for details. -- Number of frames to highlight memory and instructions when they're accessed local MainHighlightTime = 8 -- If true, char display renders in default print fallback mode; otherwise, terminal print mode is used local PrintMode = true -- Maximum number of CPU cycles per frame local MaxTicksPerFrame = 555 require("colorset") local ffi = require("ffi") local lg = love.graphics local li = love.image local le = love.event local lt = love.timer local lk = love.keyboard local la = love.audio local PlaySound local CPURequestInterrupt local TimerSchedule local TickCPU local jsrInstrs = {0x20, 0x2C, 0x0C} ---- local function InitColorset() for i = 0, 63 do local c = ColorSet[i+1] ColorSet[i] = { c[1]/255, c[2]/255, c[3]/255, } ColorSet[i+1] = nil end end local eventTypes = {} local function registerEvent(mem, addrFirst, addrLast, read, write, func) table.insert(eventTypes, func) local id = #eventTypes for addr = addrFirst, addrLast do if read then mem.c.onread [addr] = id end if write then mem.c.onwrite[addr] = id end end end local function handleEvents(cpu, mem) for i = 0, mem.c.numevents-1 do local event = mem.c.events[i] eventTypes[event.id](event.addr, cpu, mem) end mem.c.numevents = 0 end local RegDisplay = { scrX = 384+16, scrY = 32+16+8, width = 128, height = 192, fontWidth = 8, fontHeight = 12, registers = { { name = "A" , idx = "a" , x=8,y=8 ,w=2}, { name = "B" , idx = "b" , x=8,y=8+16 ,w=2}, { name = "C" , idx = "c" , x=8,y=8+16* 2,w=2}, { name = "U" , idx = "u" , x=8,y=8+16* 3,w=2}, { name = "T" , idx = "t" , x=8,y=8+16* 4,w=2}, { name = "X" , idx = "p" , x=8,y=8+16* 5,w=4}, { name = "Y" , idx = "q" , x=8,y=8+16* 6,w=4}, { name = "S" , idx = "s" , x=8,y=8+16* 7,w=2}, { name = "V" , idx = "v" , x=8,y=8+16* 8,w=4}, { name = "I" , idx = "i" , x=8,y=8+16* 9,w=4}, { name = "Ins", idx = "instr", x=8,y=8+16*10,w=2}, { name = "Cyc", idx = "cycle", x=64+8-16,y=8+16*10,w=2}, }, flags = { { name = "CF" , idx = "cf" , x=64+8,y=8 }, { name = "NZ" , idx = "nz" , x=64+8,y=8+16 }, { name = "IRq", idx = "irq", x=64+8,y=8+16*2}, { name = "IEn", idx = "ien", x=64+8,y=8+16*5}, { name = "Int", idx = "ifg", x=64+8,y=8+16*3}, { name = "Run", idx = "rfg", x=64+8,y=8+16*4}, }, } local function InitRegDisplay(rd) lg.print("Registers", rd.scrX, rd.scrY-12) lg.rectangle("line", rd.scrX, rd.scrY, rd.width, rd.height+1) for i, reg in ipairs(rd.registers) do lg.rectangle("line", rd.scrX+reg.x+(rd.fontWidth*(4-reg.w)), rd.scrY+reg.y, rd.fontWidth*reg.w+1, rd.fontHeight+1) lg.print(reg.name, rd.scrX+reg.x+32+4, reg.y + rd.scrY) end for i, flg in pairs(rd.flags) do lg.rectangle("line", rd.scrX+flg.x, rd.scrY+flg.y, rd.fontHeight+1, rd.fontHeight+1) lg.print(flg.name, rd.scrX+flg.x+rd.fontHeight+4, flg.y+rd.scrY) end end local function printValue(val, w, x, y, fw) for i = 1, w do local v = math.floor(val/math.pow(16,i-1))%16 lg.print(string.format("%01X", v), x+(fw*(4-i)), y+1) end end local function RedrawRegDisplay(rd, cpu, mem) for i, reg in ipairs(rd.registers) do lg.setColor(0,0,0) lg.rectangle("fill", rd.scrX+reg.x+(rd.fontWidth*(4-reg.w)), rd.scrY+reg.y, rd.fontWidth*reg.w, rd.fontHeight) lg.setColor(1,1,1) local val = cpu.c[reg.idx]%(math.pow(16, reg.w)) printValue(val, reg.w, rd.scrX+reg.x, rd.scrY+reg.y, rd.fontWidth) end for i, flg in pairs(rd.flags) do local val = cpu.c[flg.idx] if val~=0 then lg.setColor(1,1,1) else lg.setColor(0,0,0) end lg.rectangle("fill", rd.scrX+flg.x, rd.scrY+flg.y, rd.fontHeight, rd.fontHeight) end lg.setColor(1,1,1) end local ReadMemory local WriteMemory local StackDisplay = { scrX = 8+384+32+6, scrY = 32+192+32-4+16, lines = 16, fontHeight = 12, } local function InitStackDisplay(sd) sd.width = 96+1 sd.height = 12+(sd.lines*sd.fontHeight) lg.print("Stack", sd.scrX, sd.scrY-12) lg.rectangle("line", sd.scrX, sd.scrY, sd.width+1, sd.height+1) end local function RedrawStackDisplay(sd, cpu, mem) lg.setColor(0,0,0) lg.rectangle("fill", sd.scrX, sd.scrY, sd.width, sd.height) lg.setColor(1,1,1) for i = 1, sd.lines do local addr = ((cpu.c.s%256)+256-i)%65536 local val = ReadMemory(mem, addr) if addr>=0x0100 then lg.print(string.format("%04X -%2i %02X", addr, i, val), sd.scrX+8, sd.scrY+8+(12*(i-1))) end end end local MemoryDisplays = { { scrX = 8, scrY = 32+192+32-4+16, columns = 16, columnSpace = 4, rows = 16, fontHeight = 12, showAscii = false, addr = 0x0000, highlightTime = MainHighlightTime, }, } local function InitMemoryDisplay(md) local cw = 7 md.mainCols = 9+md.columns*3+md.columnSpace-1 md.width = cw*(md.mainCols)+cw*(md.showAscii and (md.columns+5) or 0)-8 md.height = 12+md.fontHeight*md.rows lg.print("Memory", md.scrX, md.scrY-12) lg.rectangle("line", md.scrX, md.scrY, md.width+1, md.height+1) end local function RedrawMemoryDisplay(md, cpu, mem) lg.setColor(0,0,0) lg.rectangle("fill", md.scrX, md.scrY, md.width, md.height) lg.setColor(1,1,1) local highlightAddrs = {} highlightAddrs[cpu.c.p] = "X" highlightAddrs[cpu.c.q] = "Y" highlightAddrs[(cpu.c.s%256)+256] = "S" highlightAddrs[cpu.c.v] = "V" highlightAddrs[(cpu.c.i-1)%65536] = "I" local cw = 7 local tickDraw = cpu.c.frame-md.highlightTime for l = 1, md.rows do local addr = md.addr + (l-1)*md.columns local lx = md.scrX+8 local ly = md.scrY+8+(md.fontHeight*(l-1)) lg.print(string.format("%04X", addr).." | ", lx, ly) lx = lx+(cw*7) for x = 1, md.columns do local a = addr + (x-1) local v = ReadMemory(mem, a) if highlightAddrs[a] then lg.line(lx, ly, lx, ly+11) lg.line(lx-1, ly, lx+15, ly) lg.line(lx-1, ly+11, lx+15, ly+11) lg.print(highlightAddrs[a], lx-cw, ly) end if mem.c.writes[a] > tickDraw then lg.rectangle("fill",lx-1,ly,cw*2+2,11) lg.setColor(0,0,0) end lg.print(string.format("%02X", v), lx, ly) lg.setColor(1,1,1) if mem.c.reads[a] > tickDraw then lg.rectangle("line", lx-1, ly, cw*2+2+1, 11+1) end lx = lx+(cw*3) if x%md.columnSpace==0 then lx = lx+cw end end if md.showAscii then lg.print("|", lx, ly) lx = lx+cw end if md.showAscii then for x = 1, md.columns do local a = addr + (x-1) local v = ReadMemory(mem, a) if v>=128 then v = 0 end local c = string.char(v) lg.print(c, md.scrX+lx+((x-1)*cw), md.scrY+8+(12*(l-1))) end end end end local ProgramDisplay = { scrX = 8+384+8+128+8, scrY = 32+8+16, fontHeight = 12, numLines = 34, highlightTime = MainHighlightTime, maxChars = 30, } local function toPrintableChar(v) if v>=0x20 and v<=0x7E then return string.char(v) else return "\\x"..string.format("%02X", v) end end local function pdLinesFromDasm(dasm, maxChars) local div = "|" local lines, addrLines, lineAddrs = {}, {}, {} local function addDasmLine(addr, data, text) text = text:sub(1, maxChars - #div*2 - 8) local dataStr = "" if data then local dt = {} for _, v in ipairs(data) do table.insert(dt, string.format("%02X", v)) end dataStr = table.concat(dt, " ") end dataStr = dataStr .. (" "):rep(8 - #dataStr) if (not data) or #data==0 then table.insert(lines, (" "):rep(4+#div+8) .. div .. text) else table.insert(lines, string.format("%04X", addr) .. div .. dataStr .. div .. text ) end local la = {} if data then for i = 1, #data do addrLines[addr+i-1] = #lines table.insert(la, addr+i-1) end end lineAddrs[#lines] = la end for line in dasm:gmatch("[^\r\n]+") do if line~="" and not line:find("^ *outp | addr | data %(base 16%)$") then local addrh, datah, rest = line:match("^ *[0-9a-fA-F:]+ | *([0-9a-fA-F]+) | ([0-9a-fA-F ]*) ; +(.+)$") local text = rest local label = nil if rest:find(":") then label, text = rest:match("^(.+): *(.*)$") end local addr = tonumber(addrh, 16) if label then addDasmLine(addr, nil, label..":") else local len = 0 local maxlen = 3 local data = {} local datastr = {} for v in datah:gfind("[0-9a-fA-F][0-9a-fA-F]") do if #data>=maxlen then addDasmLine(addr+len-#data, data, "\""..table.concat(datastr).."\"") data = {} datastr = {} end table.insert(data, tonumber(v, 16)) table.insert(datastr, toPrintableChar(tonumber(v, 16))) len = len+1 end if len<=maxlen then addDasmLine(addr, data, text) elseif #data>0 then addDasmLine(addr+len-#data, data, "\""..table.concat(datastr).."\"") end end end end return lines, addrLines, lineAddrs end local function InitProgramDisplay(pd, dasmText) pd.width = 256 pd.height = 12+pd.fontHeight*pd.numLines-3 lg.print("Program", pd.scrX, pd.scrY-12) lg.rectangle("line", pd.scrX, pd.scrY, pd.width, pd.height) pd.firstLine = 1 pd.lines, pd.addrLines, pd.lineAddrs = pdLinesFromDasm(dasmText, pd.maxChars) pd.midLine = math.floor(pd.numLines/2) pd.init = true end local function RedrawProgramDisplay(pd, cpu, mem) if not pd.init then return end lg.setColor(0,0,0) lg.rectangle("fill", pd.scrX, pd.scrY, pd.width-1, pd.height-1) lg.setColor(1,1,1) local rectwidth = pd.width-18 local rectheight = pd.fontHeight-1 local tickDraw = cpu.c.frame-pd.highlightTime local instrAddr = (cpu.c.i-1)%65536 local instrLine = pd.addrLines[instrAddr] if instrLine then if pd.firstLine > instrLine then pd.firstLine = math.max(instrLine-pd.midLine, 1) elseif pd.firstLine+pd.numLines-1 < instrLine then pd.firstLine = math.min(instrLine-pd.numLines+pd.midLine, #pd.lines-pd.numLines+1) end end local screenlineidx = 0 for lineidx = pd.firstLine, pd.firstLine+pd.numLines-1 do local line = pd.lines[lineidx] local lineaddrs = pd.lineAddrs[lineidx] if line then local x, y = pd.scrX+8, pd.scrY+4+screenlineidx*pd.fontHeight if lineaddrs then for _, addr in ipairs(lineaddrs) do if mem.c.reads[addr] > tickDraw then lg.rectangle("line", x, y, rectwidth, rectheight) end end end if instrLine==lineidx then lg.rectangle("fill", x, y, rectwidth, rectheight) lg.setColor(0,0,0) end lg.print(line, x, y) lg.setColor(1,1,1) end screenlineidx = screenlineidx + 1 end end --local VideoDisplay = { -- width = 256, height = 128, -- scrX = 8, scrY = 256, -- addr = 0x8000, --} --local function InitVideoDisplay(vd) -- lg.print("Video Display", vd.scrX, vd.scrY-12) -- vd.imageData = li.newImageData(vd.width, vd.height) -- lg.setColor(1,1,1) -- lg.rectangle("line", vd.scrX, vd.scrY, vd.width+1, vd.height+1) --end local CharDisplay = { width = 64*6, height = 16*12, rows = 16, cols = 64, fontWidth = 6, fontHeight = 12, scrX = 8, scrY = 32+8+16, addrChar = 0xC000, addrColor = 0xC800, } local function InitCharDisplay(cd) lg.print("Char Display", cd.scrX, cd.scrY-12) cd.font = lg.newFont("content/consola.ttf", cd.fontHeight-1, "mono") end local Keyboard = { addrRange = {0xF100, 0xF1FF}, queueSize = 16, queue = {}, interrupts = false, queueEmpty = true, } local function kbSetNext(kb, cpu, mem) local newval = kb.queue[1] or 0 if mem.c.data[kb.addrRange[1]] ~= newval then for a = kb.addrRange[1], kb.addrRange[2] do mem.c.data[a] = newval end end end local function KeyboardOnRead(addr, cpu, mem, kb) table.remove(kb.queue, 1) kbSetNext(kb, cpu, mem) end local function KeyboardOnWrite(addr, cpu, mem, kb) local val = mem.c.data[addr] kb.interrupts = val~=0 mem.c.data[addr] = kb.queue[1] or 0 end local keycodes = require("keycodes") local function KeyboardOnKey(kb, key, press, cpu, mem) local code = keycodes[key] or keycodes["invalid"] if code==0x7F then print("invalid key: "..key) end table.insert(kb.queue, code + (press and 128 or 0)) if #kb.queue > kb.queueSize then table.remove(kb.queue, 1) end kb.queueEmpty = false kbSetNext(kb, cpu, mem) if kb.interrupts then CPURequestInterrupt(cpu) end end local function RedrawFPSCounter(x, y) lg.setColor(0,0,0) lg.rectangle("fill",x,y,64,12) lg.setColor(1,1,1) lg.print("FPS: "..lt.getFPS(), x,y) end local function printHighlight(s, o, h, x, y) x = x+o*7 local w = 7*#s lg.setColor(1,1,1) if h then lg.rectangle("fill", x, y, w, 12) lg.setColor(0,0,0) end lg.print(s, x, y) end local function RedrawKeyInfo(x, y, uk, run) lg.setColor(0,0,0) lg.rectangle("fill",x,y,768,12+16) lg.setColor(1,1,1) printHighlight("[F4] Toggle keyboard", 0, lk.isDown("f4"), x, y) lg.setColor(1,1,1) if uk then printHighlight("Keystrokes passed to device", 23, false, x, y) else printHighlight("[R] "..(run and "Stop" or "Run "), 23, lk.isDown("r"), x, y) if not run then printHighlight("[S] Step" , 33, lk.isDown("s"), x, y) -- printHighlight("[T] Tick once" , 48, lk.isDown("t"), x, y) printHighlight("[X] Step Over" , 43, lk.isDown("x"), x, y) end printHighlight("[I] Interrupt" , 58, lk.isDown("i"), x, y) printHighlight("[Q] Quit" , 73, lk.isDown("q"), x, y) end end local GPIO = {} local function InitGPIO(gpio) gpio.mulLeft = 0 gpio.mulRight = 0 gpio.divLeft = 0 gpio.divRight = 0 gpio.timerCount = 0 end local function UpdateGPIO(gpio, cpu, mem) if gpio.timerCount>0 then gpio.timerCount = gpio.timerCount-1 if gpio.timerCount<=0 then CPURequestInterrupt(cpu) end end end local function gpioSetValue(name, func) return function(addr, cpu, mem, gpio) gpio[name] = ReadMemory(mem, addr) func(addr, cpu, mem, gpio) end end local function gpioMul(addr, cpu, mem, gpio) local base = math.floor(addr/256)*256 local res = gpio.mulLeft*gpio.mulRight WriteMemory(mem, base+0x00, math.floor(res/256)) WriteMemory(mem, base+0x01, res%256) end local function gpioDiv(addr, cpu, mem, gpio) local base = math.floor(addr/256)*256 WriteMemory(mem, base+0x02, math.floor(gpio.divLeft/gpio.divRight)) WriteMemory(mem, base+0x03, gpio.divLeft%gpio.divRight) end local function gpioPopcount(v) return 0 -- todo end local gpioFunctions = { [0x00] = gpioSetValue("mulLeft" , gpioMul), [0x01] = gpioSetValue("mulRight", gpioMul), [0x02] = gpioSetValue("divLeft" , gpioDiv), [0x03] = gpioSetValue("divRight", gpioDiv), [0x04] = function(addr, cpu, mem, gpio) WriteMemory(mem, addr, gpioPopcount(ReadMemory(mem, addr))) end, [0x05] = function(addr, cpu, mem, gpio) gpio.timerCount = 60/10; WriteMemory(mem, addr, 0); end } local function GPIOOnWrite(addr, cpu, mem, gpio) local offset = addr%256 local func = gpioFunctions[offset] if func then func(addr, cpu, mem, gpio) end end local peripherals = { CharDisplay = { range = {0xC000, 0xCFFF}, write = true }, BootROM = { range = {0xFC00, 0xFFFF}, write = false }, MainRAM = { range = {0x0000, 0x1FFF}, write = true }, -- VideoDisplay = { range = {0x8000, 0xFFFF}, write = true }, Keyboard = { range = {0xF100, 0xF1FF}, write = true, onread = function(addr, cpu, mem) KeyboardOnRead (addr, cpu, mem, Keyboard) end, onwrite = function(addr, cpu, mem) KeyboardOnWrite(addr, cpu, mem, Keyboard) end, }, GPIO = { range = {0xF000, 0xF0FF}, write = true, onwrite = function(addr, cpu, mem) GPIOOnWrite(addr, cpu, mem, GPIO) end, }, } ---- -- Console local Console = { scrX = 8, scrY = 20, width = 512+8, height = 20, maxChars = 72, blinkFrames = 60, } local function InitConsole(con) con.blinkFrame = 0 con.input = "" con.prevInputs = {} con.prevInputIdx = 0 con.tempError = nil end local function RedrawConsole(con, kb) lg.setColor(0,0,0) lg.rectangle("fill", con.scrX, con.scrY, con.width, con.height) lg.setColor(1,1,1) lg.rectangle("line", con.scrX, con.scrY, con.width, con.height) if not kb then if con.blinkFrame < con.blinkFrames/2 then local curx, cury = con.scrX + #con.input*7 + 2, con.scrY+3 lg.rectangle("fill", curx, cury, 8, 12) end con.blinkFrame = con.blinkFrame + 1 if con.blinkFrame >= con.blinkFrames then con.blinkFrame = 0 end if #con.input==0 then lg.setColor(0.5,0.5,0.5) lg.print(con.tempError or "Enter Command (>ticks | !addr | @addr | addr=byte | addr=byte1 byte2...)", con.scrX+2+8, con.scrY+4) else lg.print(con.input, con.scrX+2, con.scrY+4) end end end local function ConsoleError(con, err) con.tempError = err end local function conWriteDataBlock(con, mem, addr, data) local writeTime = 2 ConsoleError(con, "Writing") for i, byte in ipairs(data) do TimerSchedule((i-1)*writeTime, function() if con.tempError ~= nil then con.tempError = con.tempError .. "." end mem.c.data[(addr+i-1)%65536] = byte PlaySound("write") end) end TimerSchedule((#data-1)*writeTime, function() PlaySound("writeDone") PlaySound("success") ConsoleError(con, "Wrote " .. #data .. " byte" .. (#data>1 and "s" or "") .. " to address $" .. string.format("%04X", addr)) end) end local function tickCpuOnce(cpu, mem, byInstr) TickCPU(cpu, mem, 1, byInstr, nil) end local function tickCpuUntil(con, cpu, mem, contfunc, strfunc, donestr, step) con.tempError = "" local tickTime = 1 local tickStep, byInstr if step then tickStep = step byInstr = true else tickStep = MaxTicksPerFrame byInstr = false end local i = 1 local recFunc recFunc = function() for j = 1, tickStep do if contfunc(i) then tickCpuOnce(cpu, mem, byInstr) else PlaySound("writeDone") PlaySound("success") ConsoleError(con, donestr) return end i = i+1 end if con.tempError ~= nil then con.tempError = strfunc(i) end PlaySound("write") TimerSchedule(tickTime, recFunc) end recFunc() end local function tickCpuStepOver(con, cpu, mem, pd) local stepOver = false for _, v in ipairs(jsrInstrs) do if cpu.c.instr == v then stepOver = true end end if not stepOver then tickCpuOnce(cpu, mem, true) return end local line = pd.addrLines and pd.addrLines[(cpu.c.i-1)%65536] if not line then tickCpuOnce(cpu, mem, true) return end local addr while not addr do line = line+1 local addrs = pd.lineAddrs[line] if not addrs then tickCpuOnce(cpu, mem, true) return end addr = addrs[1] end --print(string.format("%04X", addr)) tickCpuUntil(con, cpu, mem, --function() return (cpu.c.i ~= (addr+1)%65536) and (cpu.c.rfg==1) end, function() return (cpu.c.i ~= (addr+1)%65536) end, function() return "Running to address $" .. string.format("%04X", addr) end, nil, nil ) end local function ConsoleExec(con, cpu, mem) PlaySound("enter") if con.input=="" then PlaySound("error") con.tempError = nil return end local ip = con.input con.input = "" print("exec \""..ip.."\"") table.insert(con.prevInputs, 1, ip) con.prevInputIdx = 0 if ip:sub(1, 1)=="@" then local rest = ip:sub(2, #ip) local addr = tonumber(rest, 16) if not addr then ConsoleError(con, "Error: Expected a hex number") PlaySound("error") return end if addr<0 then addr = 0 end if addr>65536-256 then addr = 65536-256 end MemoryDisplays[1].addr = addr ConsoleError(con, "Memory display set to address $" .. string.format("%04X", addr)) PlaySound("success") elseif ip:sub(1, 1)=="!" then if RunCPU then ConsoleError(con, "CPU must be paused to change execution") PlaySound("error") return end local rest = ip:sub(2, #ip) local addr = tonumber(rest, 16) if not addr then ConsoleError(con, "Error: Expected a hex number") PlaySound("error") return end if addr<0 then addr = 0 end if addr>65535 then addr = 65535 end cpu.c.i = (addr+1)%65536 cpu.c.cycle = 0 cpu.c.instr = ReadMemory(mem, addr) ConsoleError(con, "CPU instruction pointer set to $" .. string.format("%04X", addr)) PlaySound("success") elseif ip:sub(1, 2)==">>" then if RunCPU then ConsoleError(con, "CPU must be paused to step") PlaySound("error") return end local rest = ip:sub(3, #ip) local addr = tonumber(rest, 16) if not addr then ConsoleError(con, "Error: Expected a hex number") PlaySound("error") return end if addr<0 then addr = 0 end if addr>65535 then addr = 65535 end tickCpuUntil(con, cpu, mem, function() return cpu.c.i ~= (addr+1)%65536 end, function() return "Running to address $" .. string.format("%04X", addr) end, "Ran to address " .. string.format("%04X", addr), nil ) elseif ip:sub(1, 1)==">" then if RunCPU then ConsoleError(con, "CPU must be paused to single-step") PlaySound("error") return end local rest = ip:sub(2, #ip) local steps = tonumber(rest, 16) if rest=="" then steps = 1 end if not steps then ConsoleError(con, "Error: Expected a hex number") PlaySound("error") return end if steps<1 then steps = 1 end if steps>65535 then steps = 65535 end tickCpuUntil(con, cpu, mem, function(i) return i<=steps; end, function(i) return "Step $"..string.format("%X", i).."/$"..string.format("%X", steps) end, "Ran $" .. string.format("%02X", steps) .. " steps", steps<(MaxTicksPerFrame/4) and steps or nil ) elseif ip:find("=") then local addrS, rest = ip:match("^ *([0-9a-fA-F]+) *= *([0-9a-fA-F ]+)$") local addr = tonumber(addrS or "", 16) if not (addr and rest) then ConsoleError(con, "Error: Expected [hex number] = [hex numbers]") PlaySound("error") return end local data = {} for byteS in rest:gmatch("[0-9a-fA-F]+") do local byte = tonumber(byteS, 16) if byte>255 then ConsoleError(con, "Error: Data byte \"" .. byteS .. "\" is greater than $FF") PlaySound("error") return end table.insert(data, byte) end conWriteDataBlock(con, mem, addr, data) else ConsoleError(con, "Error: Unknown command") PlaySound("error") end end local function shiftDown() return lk.isDown("lshift") or lk.isDown("rshift") end local function ConsoleKey(con, k, cpu, mem) local add if k=="backspace" then con.input = con.input:sub(1, #con.input-1) elseif shiftDown() and k=="1" then add = "!" elseif shiftDown() and k=="2" then add = "@" elseif shiftDown() and k=="." then add = ">" elseif k>="0" and k<="9" then add = k elseif #k==1 and k>="a" and k<="f" then add = k:upper() elseif k=="=" then add = "=" elseif k=="space" then add = " " elseif k=="return" then ConsoleExec(con, cpu, mem) elseif k=="up" and con.prevInputs[con.prevInputIdx+1] then if con.prevInputIdx==0 then con.prevInputs[0] = con.input end con.prevInputIdx = con.prevInputIdx + 1 con.input = con.prevInputs[con.prevInputIdx] elseif k=="down" then if con.prevInputIdx>0 then con.prevInputIdx = con.prevInputIdx - 1 con.input = con.prevInputs[con.prevInputIdx] else if con.input ~= "" then table.insert(con.prevInputs, 1, con.input) end con.input = "" end else return end if add and #con.input < con.maxChars then con.input = con.input .. add; con.tempError = nil; end con.blinkFrame = 0 end local function DrawHelp() end ---- -- Sound local Sounds = {} local soundNames = { "enter","error", "interrupt", "keyOff","keyOn", "runOff","runOn", "start", "step", "success", "write","writeDone", } local soundCounts = { --["step"] = 100, ["write"] = 100, } local function InitSound() for _, name in ipairs(soundNames) do local sound = { name = name, sources = {}, lastPlayed = 0, } local fn = "content/" .. name .. ".wav" for i = 1, soundCounts[name] or 4 do table.insert(sound.sources, la.newSource(fn, "static")) end Sounds[name] = sound end end PlaySound = function(name) local sound = Sounds[name] or error("no sound with name: " .. name) sound.lastPlayed = (sound.lastPlayed % #sound.sources) + 1 sound.sources[sound.lastPlayed]:play() end ---- -- Timer local TimerFrame = 0 local TimerSchedules = {} TimerSchedule = function(time, func) local frame = TimerFrame + time TimerSchedules[frame] = TimerSchedules[frame] or {} table.insert(TimerSchedules[frame], func) end local function UpdateTimer() if TimerSchedules[TimerFrame] then for _, func in ipairs(TimerSchedules[TimerFrame]) do func() end TimerSchedules[TimerFrame] = nil end TimerFrame = TimerFrame + 1 end ---- -- CPU Interface ffi.cdef [[ struct Event { int id; int addr; }; struct Memory { int data[65536]; int canwrite[65536]; int writes[65536]; int reads[65536]; int onread[65536]; int onwrite[65536]; struct Event events[4096]; int numevents; }; ]] local Memory = { c = ffi.new("struct Memory"), } local function InitMemory(mem, pers) for i = 0, 65535 do mem.c.data[i] = 0 mem.c.canwrite[i] = 0 mem.c.writes[i] = 0 mem.c.reads[i] = 0 mem.c.onread[i] = 0 mem.c.onwrite[i] = 0 end for k, per in pairs(pers) do if per.onread then registerEvent(mem, per.range[1], per.range[2], true , false, per.onread ) end if per.onwrite then registerEvent(mem, per.range[1], per.range[2], false, true , per.onwrite) end for a = per.range[1], per.range[2] do mem.c.canwrite[a] = (per.write and 1 or 0) end end end ReadMemory = function(mem, addr) return mem.c.data[addr%65536]%256 end WriteMemory = function(mem, addr, val) if mem.c.canwrite[addr%65536]~=0 then mem.c.data[addr%65536] = val%256 end end --local function AssembleToMemory(mem, fn, arch) -- local data, code, symbols = asm.assembleFile(fn) -- for addr = 0, 65535 do -- if data[addr] then -- mem.c.data[addr] = data[addr] -- end -- end -- return data, code, symbols --end local function CopyBinToMemory(mem, binText) for i = 1, #binText do local c = binText:sub(i, i) local v = string.byte(c) local addr = i-1 mem.c.data[addr] = v end end local function CopyHexToMemory(mem, hexText) for i = 1, #hexText, 2 do local c = hexText:sub(i, i+1) local v = tonumber(c, 16) or error("CopyHexToMemory: Invalid hex pair \"" .. c .. "\"") local addr = (i-1)/2 mem.c.data[addr] = v end end ffi.cdef [[ struct CPU { int a; int b; int c; int u; int t; int p; int q; int s; int v; int i; int cf; int nz; int irq; int ifg; int rfg; int ien; int instr; int cycle; int instrpre; int frame; }; int TickCPU(struct CPU* const cpu, struct Memory* const mem, const int count, const int countinstrs, const int breakaddr); ]] local CPU = { c = ffi.new("struct CPU"), } local cpuDll = ffi.load("8608emulator.dll") TickCPU = function(cpu, mem, count, countinstrs, breakaddr) local countleft = count while countleft>0 do countleft = cpuDll.TickCPU(cpu.c, mem.c, countleft, countinstrs and 1 or 0, breakaddr or 0xFFFFFFFF) handleEvents(cpu, mem) end end local function InitCPU(cpu) cpu.c.rfg = 1 cpu.c.instr = 0x41 -- reset instruction end CPURequestInterrupt = function(cpu) cpu.c.irq = 1; end ---- --local function RedrawVideoDisplay(vd, mem) -- local vd = VideoDisplay -- for y = 0, vd.height-1 do -- for x = 0, vd.width-1 do -- local a = vd.addr + y*vd.width + x -- local colorid = ReadMemory(mem, a)%64 -- local color = ColorSet[colorid] -- vd.imageData:setPixel(x, y, color[1], color[2], color[3]) -- end -- end -- local img = lg.newImage(vd.imageData) -- lg.setColor(1,1,1) -- lg.draw(img, vd.scrX, vd.scrY) --end local function RedrawCharDisplay(cd, mem) lg.rectangle("line", cd.scrX, cd.scrY, cd.width+1, cd.height+1) lg.setColor(0,0,0) lg.rectangle("fill", cd.scrX, cd.scrY, cd.width, cd.height) lg.setFont(cd.font) local cd = CharDisplay for cy = 0, cd.rows-1 do for cx = 0, cd.cols-1 do local abase = cy*cd.cols + cx local achar = cd.addrChar + abase local acolor = cd.addrColor + abase local colormem = ReadMemory(mem, acolor) local colorid = colormem%64 local highlight = PrintMode or colormem>=128 lg.setColor(ColorSet[colorid]) local scrx = cd.scrX + cx*cd.fontWidth local scry = cd.scrY + cy*cd.fontHeight local scrw = cd.fontWidth local scrh = cd.fontHeight if highlight then lg.rectangle("fill", scrx, scry, scrw, scrh) if PrintMode then lg.setColor(1,1,1) else lg.setColor(0,0,0) end end local val = ReadMemory(mem, achar)%128 if val>=0x20 and val<=0x7F then -- printable ascii local char = string.char(val) lg.print(char, scrx, scry) elseif val>=0x10 and val<=0x17 then -- solid color local r, g, b = math.floor(val/4)%2, math.floor(val/2)%2, val%2 lg.setColor(r, g, b) lg.rectangle("fill", scrx, scry, scrw, scrh) elseif val>=0x80 and val<=0x8F then -- 2x2 pixels lg.setColor(0,0,0) if val %2==1 then lg.rectangle("fill", scrx+scrw/2, scry+scrh/2, scrw/2, scrh/2) end -- bottom right if math.floor(val/2)%2==1 then lg.rectangle("fill", scrx , scry+scrh/2, scrw/2, scrh/2) end -- bottom left if math.floor(val/4)%2==1 then lg.rectangle("fill", scrx+scrw/2, scry , scrw/2, scrh/2) end -- top right if math.floor(val/4)%2==1 then lg.rectangle("fill", scrx , scry , scrw/2, scrh/2) end -- top left elseif val>=0xA0 and val<=0xDF then -- kana -- todo end end end lg.setColor(1,1,1) lg.setFont(InfoFont) end local function InitWindowCanvas() WindowCanvas = lg.newCanvas(WindowX, WindowY) lg.setCanvas(WindowCanvas) lg.setColor(1,1,1) lg.setFont(InfoFont) lg.print("8608 Emulator", 4, 4) end local function RedrawWindow(usekeyboard, runcpu) lg.setCanvas(WindowCanvas) lg.setFont(InfoFont) RedrawCharDisplay(CharDisplay, Memory) --RedrawVideoDisplay(VideoDisplay, Memory) RedrawRegDisplay(RegDisplay, CPU, Memory) RedrawStackDisplay(StackDisplay, CPU, Memory) RedrawProgramDisplay(ProgramDisplay, CPU, Memory) for _, md in ipairs(MemoryDisplays) do RedrawMemoryDisplay(md, CPU, Memory) end RedrawFPSCounter(128, 4) RedrawKeyInfo(128+64+16, 4, usekeyboard, runcpu) RedrawConsole(Console, usekeyboard) lg.setCanvas() end ---- local function readFile(fn) local fi = io.open(fn, "rb") if not fi then print("Could not open file: \""..fn.."\"") return end local data = fi:read("*a") fi:close() return data end local function fileExt(fn) return fn:match("%.([^%.]+)$").."" end local function changeFileExt(fn, ext) return fn:gsub("%.[^%.]+$", "."..ext) end local function fileExists(fn) local fi = io.open(fn, "rb") if fi then fi:close() return true else return false end end local function importListing(fn) if not fileExists(fn) then return false end local text = readFile(fn) InitProgramDisplay(ProgramDisplay, text) end local function importProgram(fn) local ext = fileExt(fn) local data = readFile(fn) if not data then return end if ext=="bin" then CopyBinToMemory(Memory, data) elseif ext=="hex" then CopyHexToMemory(Memory, data) else print("importProgram: Invalid file extension: \""..ext.."\"") return end if not importListing(changeFileExt(fn, "lis")) then importListing(changeFileExt(fn, "txt")) end end function love.load() InitColorset() lg.setDefaultFilter("nearest", "nearest") lg.setLineWidth(1) lg.setLineStyle("rough") InfoFont = lg.newFont("content/consola.ttf", 12, "mono") InitMemory(Memory, peripherals) InitWindowCanvas() InitCPU(CPU) InitGPIO(GPIO) --InitVideoDisplay(VideoDisplay) InitCharDisplay(CharDisplay) InitRegDisplay(RegDisplay) InitStackDisplay(StackDisplay) InitConsole(Console) InitSound() if arg[2] then importProgram(arg[2]) end for _, md in ipairs(MemoryDisplays) do InitMemoryDisplay(md) end RedrawWindow() lg.setCanvas() PlaySound("start") end local targetFrameRate = 60 local targetFrameTime = (1/targetFrameRate)-0.002 local frameStartTime = 0 local lastFrameTime = 0 local function startFrame() frameStartTime = lt.getTime() end local function endFrame() local frameTime = lt.getTime() - frameStartTime if frameTime < targetFrameTime then lt.sleep(targetFrameTime - frameTime) lastFrameTime = frameTime end end local RunCPU = false local CPUSpeed = MaxTicksPerFrame local UseKeyboard = false function love.draw() startFrame() UpdateTimer() UpdateGPIO(GPIO, CPU, Memory) CPU.c.frame = CPU.c.frame + 1 if RunCPU then TickCPU(CPU, Memory, CPUSpeed, false, nil) end RedrawWindow(UseKeyboard, RunCPU) lg.setColor(1,1,1) lg.draw(WindowCanvas, 0, 0, 0, 2, 2) endFrame() end function love.keypressed(k) if k=="f4" then UseKeyboard = not UseKeyboard; PlaySound(UseKeyboard and "keyOn" or "keyOff"); else if UseKeyboard then KeyboardOnKey(Keyboard, k, true, CPU, Memory) else if k=="q" then le.quit() elseif k=="s" and (not RunCPU) then tickCpuOnce(CPU, Memory, true); PlaySound("step"); elseif k=="x" and (not RunCPU) then tickCpuStepOver(Console, CPU, Memory, ProgramDisplay); PlaySound("step"); --elseif k=="t" then TickCPU(CPU, Memory, 1, false, nil) elseif k=="r" then RunCPU = not RunCPU; PlaySound(RunCPU and "runOn" or "runOff"); elseif k=="i" then CPU.c.irq = 1; PlaySound("interrupt"); --elseif k=="u" then CPU.c.rfg = 1 else ConsoleKey(Console, k, CPU, Memory) end end end end function love.keyreleased(k) if k~="f4" and UseKeyboard then KeyboardOnKey(Keyboard, k, false, CPU, Memory) end end