1002 lines
27 KiB
Lua
1002 lines
27 KiB
Lua
--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
|