--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. local MainHighlightTime = 8 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 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, } 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) local div = " | " local lines, addrLines, lineAddrs = {}, {}, {} local function addDasmLine(addr, data, text) 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.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 = {0x0500, 0x05FF}, 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 CPURequestInterrupt 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) end -- printHighlight("[T] Tick once" , 48, lk.isDown("t"), x, y) printHighlight("[I] Interrupt" , 43, lk.isDown("i"), x, y) printHighlight("[Q] Quit" , 70, 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 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) 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 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 "Type a Command (@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 local function ConsoleError(con, err) con.tempError = err end local function ConsoleExec(con, mem) PlaySound("enter") if con.input=="" then PlaySound("error") 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: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 for i, byte in ipairs(data) do mem.c.data[(addr+i-1)%65536] = byte PlaySound("write", i) end PlaySound("writeDone", #data) PlaySound("success", #data) ConsoleError(con, "Wrote " .. #data .. " byte" .. (#data>1 and "s" or "") .. " to address $" .. string.format("%04X", addr)) end end local function shiftDown() return lk.isDown("lshift") or lk.isDown("rshift") end local function ConsoleKey(con, k, mem) local add if k=="backspace" then con.input = con.input:sub(1, #con.input-1) elseif shiftDown() and k=="2" 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, 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 function InitSound() for _, name in ipairs(soundNames) do local sound = { name = name, sources = {}, lastPlayed = 0, } local fn = "content/" .. name .. ".wav" for i = 1, 4 do table.insert(sound.sources, la.newSource(fn, "static")) end Sounds[name] = sound end end local SoundFrame = 0 local QueuedSounds = {} PlaySound = function(name, delay) if (not delay) or delay==0 then local sound = Sounds[name] or error("no sound with name: " .. name) sound.lastPlayed = (sound.lastPlayed % #sound.sources) + 1 sound.sources[sound.lastPlayed]:play() else local time = SoundFrame + delay QueuedSounds[time] = QueuedSounds[time] or {} table.insert(QueuedSounds[time], name) end end local function UpdateSound() if QueuedSounds[SoundFrame] then for _, name in ipairs(QueuedSounds[SoundFrame]) do PlaySound(name) end QueuedSounds[SoundFrame] = nil end SoundFrame = SoundFrame + 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") local function TickCPU(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 function RunToNextInstr(cpu) 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 = colormem>=128 lg.setColor(ColorSet[colorid]) if highlight then lg.rectangle("fill", cd.scrX + cx*cd.fontWidth, cd.scrY + cy*cd.fontHeight, cd.fontWidth, cd.fontHeight) lg.setColor(0, 0, 0) end local val = ReadMemory(mem, achar)%128 if val>=32 then local char = string.char(val) lg.print(char, cd.scrX + cx*cd.fontWidth, cd.scrY + cy*cd.fontHeight) elseif val>=16 and val<=31 then local r, g, b = math.floor(val/4)%2, math.floor(val/2)%2, val%2 lg.setColor(r, g, b) lg.rectangle("fill", cd.scrX + cx*cd.fontWidth, cd.scrY + cy*cd.fontHeight, cd.fontWidth, cd.fontHeight) 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+32, 4) RedrawKeyInfo(128+32+64+16, 4, usekeyboard, runcpu) RedrawConsole(Console) 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 = 555 local UseKeyboard = false function love.draw() startFrame() UpdateGPIO(GPIO, CPU, Memory) UpdateSound() 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 TickCPU(CPU, Memory, 1, true , nil); PlaySound("step"); --elseif k=="t" then TickCPU(CPU, Memory, 1, false, nil) --elseif k=="o" then RunToNextInstr(cpu) 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, Memory) end end end end function love.keyreleased(k) if k~="f4" and UseKeyboard then KeyboardOnKey(Keyboard, k, false, CPU, Memory) end end