--love 1 require("colorset") local ffi = require("ffi") RelPath = "../" AsmIncluded = true local asm = dofile("../assembler-8608.lua") local Arch = dofile("../rom-8608-defs.lua") local lg = love.graphics local li = love.image local le = love.event local lt = love.timer local lk = love.keyboard ---- 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+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 = "P" , idx = "p" , x=8,y=8+16* 5,w=4}, { name = "Q" , idx = "q" , x=8,y=8+16* 6,w=4}, { name = "S" , idx = "s" , x=8,y=8+16* 7,w=4}, { 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-16) 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, 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-16) 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-i)%65536 local val = ReadMemory(mem, addr) lg.print(string.format("%04X -%2i %02X", addr, i, val), sd.scrX+8, sd.scrY+8+(12*(i-1))) end end local MemoryDisplays = { { scrX = 8, scrY = 32+192+32-4, columns = 16, columnSpace = 4, rows = 16, fontHeight = 12, showAscii = false, addr = 0x3000, highlightTime = 8, }, } 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-16) 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] = "P" highlightAddrs[cpu.c.q] = "Q" highlightAddrs[cpu.c.s] = "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, fontHeight = 12, numLines = 34, highlightTime = 8, } local function pdLinesFromDasm(dasm) local lines, addrLines, lineAddrs = {}, {}, {} for line in dasm:gmatch("[^\n]+") do if line~="..." then local addrh, datah, rest = line:match("^(.+) | (.+) | +(.+)$") local text = rest local label = nil if rest:find(":") then label, text = rest:match("^(.+): (.+)$") end local addr = tonumber(addrh, 16) if label then --table.insert(lines, " | "..label..":") table.insert(lines, " | "..label..":") lineidx = #lines end --table.insert(lines, addrh.." | "..text) table.insert(lines, addrh.." | "..datah.." | "..text) local lineidx = #lines local len = 0; for _ in datah:gfind("[0-9a-fA-F][0-9a-fA-F]") do len = len+1 end; for i = 1, len do addrLines[addr+i-1] = lineidx end lineAddrs[lineidx] = addr else table.insert(lines, "") end end return lines, addrLines, lineAddrs end local function InitProgramDisplay(pd, data, code, arch) pd.width = 256 pd.height = 12+pd.fontHeight*pd.numLines-3 lg.print("Program", pd.scrX, pd.scrY-16) lg.rectangle("line", pd.scrX, pd.scrY, pd.width, pd.height) pd.firstLine = 1 pd.data = data pd.code = code local dasm = asm.disassembleMemory(data, code, arch) pd.lines, pd.addrLines, pd.lineAddrs = pdLinesFromDasm(dasm) pd.midLine = math.floor(pd.numLines/2) end local function RedrawProgramDisplay(pd, cpu, mem) 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 lineaddr = pd.lineAddrs[lineidx] if line then local x, y = pd.scrX+8, pd.scrY+4+screenlineidx*pd.fontHeight if lineaddr and mem.c.reads[lineaddr] > tickDraw then lg.rectangle("line", x, y, rectwidth, rectheight) 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-16) 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, addrChar = 0x0800, addrColor = 0x0C00, } local function InitCharDisplay(cd) lg.print("Char Display", cd.scrX, cd.scrY-16) cd.font = lg.newFont("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) lg.setColor(1,1,1) printHighlight("[ESC] Toggle keyboard", 0, lk.isDown("escape"), 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) printHighlight("[T] Tick once", 33, lk.isDown("t"), x, y) printHighlight("[S] Step once", 48, lk.isDown("s"), x, y) printHighlight("[Q] Quit", 63, 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.intQueued = false end local function UpdateGPIO(gpio, cpu, mem) if gpio.intQueued then gpio.intQueued = false CPURequestInterrupt(cpu) 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.intQueued = true; 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 = {0x0800, 0x0FFF}, write = true }, BootROM = { range = {0x0000, 0x03FF}, write = false }, SystemRAM = { range = {0x1000, 0x1FFF}, write = true }, UserROM = { range = {0x2000, 0x2FFF}, write = false }, UserRAM = { range = {0x3000, 0x3FFF}, write = true }, VideoDisplay = { range = {0x8000, 0xFFFF}, write = true }, Keyboard = { range = {0x0500, 0x05FF}, 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 = {0x0400, 0x04FF}, write = true, onwrite = function(addr, cpu, mem) GPIOOnWrite(addr, cpu, mem, GPIO) end, }, } ---- 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 = asm.assembleFile(fn, arch) for addr = 0, 65535 do if data[addr] then mem.c.data[addr] = data[addr] end end return data, code 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; 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 CPU 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) lg.setCanvas() end function love.load() InitColorset() lg.setDefaultFilter("nearest", "nearest") lg.setLineWidth(1) lg.setLineStyle("rough") InfoFont = lg.newFont("consola.ttf", 12, "mono") InitMemory(Memory, peripherals) InitWindowCanvas() InitCPU(CPU) InitGPIO(GPIO) --InitVideoDisplay(VideoDisplay) InitCharDisplay(CharDisplay) InitRegDisplay(RegDisplay) InitStackDisplay(StackDisplay) local data, code = AssembleToMemory(Memory, arg[2] or "../../8608programs/emutest.asm", Arch) InitProgramDisplay(ProgramDisplay, data, code, Arch) for _, md in ipairs(MemoryDisplays) do InitMemoryDisplay(md) end RedrawWindow() lg.setCanvas() end local RunCPU = false local CPUSpeed = 4999 local UseKeyboard = false function love.draw() 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) end function love.keypressed(k) if k=="escape" then UseKeyboard = not UseKeyboard else if UseKeyboard then KeyboardOnKey(Keyboard, k, true, CPU, Memory) else if k=="q" then le.quit() elseif k=="s" then TickCPU(CPU, Memory, 1, true , nil) elseif k=="t" then TickCPU(CPU, Memory, 1, false, nil) elseif k=="o" then RunToNextInstr(cpu) elseif k=="r" then RunCPU = not RunCPU elseif k=="i" then CPU.c.irq = 1 elseif k=="u" then CPU.c.rfg = 1 end end end end function love.keyreleased(k) if k~="escape" and UseKeyboard then KeyboardOnKey(Keyboard, k, false, CPU, Memory) end end