more emulator features, sub # instr

This commit is contained in:
Redo
2024-08-12 22:52:33 -06:00
parent 4ae98df548
commit 6de62423e8
11 changed files with 563 additions and 122 deletions

View File

@@ -4,7 +4,12 @@
-- 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")
@@ -17,6 +22,11 @@ local lk = love.keyboard
local la = love.audio
local PlaySound
local CPURequestInterrupt
local TimerSchedule
local TickCPU
local jsrInstrs = {0x20, 0x2C, 0x0C}
----
local function InitColorset()
@@ -220,15 +230,17 @@ local ProgramDisplay = {
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)
local div = " | "
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 = {}
@@ -274,7 +286,7 @@ local function pdLinesFromDasm(dasm)
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.."\""))
addDasmLine(addr+len-#data, data, "\""..table.concat(datastr).."\"")
data = {}
datastr = {}
end
@@ -285,7 +297,7 @@ local function pdLinesFromDasm(dasm)
if len<=maxlen then
addDasmLine(addr, data, text)
elseif #data>0 then
addDasmLine(addr+len-#data, data, "\""..table.concat(datastr.."\""))
addDasmLine(addr+len-#data, data, "\""..table.concat(datastr).."\"")
end
end
end
@@ -298,7 +310,7 @@ local function InitProgramDisplay(pd, dasmText)
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.lines, pd.addrLines, pd.lineAddrs = pdLinesFromDasm(dasmText, pd.maxChars)
pd.midLine = math.floor(pd.numLines/2)
pd.init = true
end
@@ -368,7 +380,7 @@ local function InitCharDisplay(cd)
end
local Keyboard = {
addrRange = {0x0500, 0x05FF},
addrRange = {0xF100, 0xF1FF},
queueSize = 16,
queue = {},
interrupts = false,
@@ -392,7 +404,6 @@ local function KeyboardOnWrite(addr, cpu, mem, kb)
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
@@ -431,10 +442,11 @@ local function RedrawKeyInfo(x, y, uk, run)
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)
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
@@ -471,12 +483,15 @@ local function gpioDiv(addr, cpu, mem, gpio)
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,
[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)
@@ -516,31 +531,124 @@ local function InitConsole(con)
con.prevInputIdx = 0
con.tempError = nil
end
local function RedrawConsole(con)
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 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)
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 ConsoleExec(con, mem)
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
@@ -564,6 +672,69 @@ local function ConsoleExec(con, mem)
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)
@@ -582,27 +753,27 @@ local function ConsoleExec(con, mem)
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))
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, mem)
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, mem)
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
@@ -637,6 +808,10 @@ local soundNames = {
"success",
"write","writeDone",
}
local soundCounts = {
--["step"] = 100,
["write"] = 100,
}
local function InitSound()
for _, name in ipairs(soundNames) do
local sound = {
@@ -645,33 +820,36 @@ local function InitSound()
lastPlayed = 0,
}
local fn = "content/" .. name .. ".wav"
for i = 1, 4 do
for i = 1, soundCounts[name] or 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
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
local function UpdateSound()
if QueuedSounds[SoundFrame] then
for _, name in ipairs(QueuedSounds[SoundFrame]) do
PlaySound(name)
----
-- 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
QueuedSounds[SoundFrame] = nil
TimerSchedules[TimerFrame] = nil
end
SoundFrame = SoundFrame + 1
TimerFrame = TimerFrame + 1
end
@@ -777,7 +955,7 @@ local CPU = {
c = ffi.new("struct CPU"),
}
local cpuDll = ffi.load("8608emulator.dll")
local function TickCPU(cpu, mem, count, countinstrs, breakaddr)
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)
@@ -792,10 +970,6 @@ CPURequestInterrupt = function(cpu)
cpu.c.irq = 1;
end
function RunToNextInstr(cpu)
end
----
--local function RedrawVideoDisplay(vd, mem)
@@ -825,20 +999,32 @@ local function RedrawCharDisplay(cd, mem)
local acolor = cd.addrColor + abase
local colormem = ReadMemory(mem, acolor)
local colorid = colormem%64
local highlight = colormem>=128
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", cd.scrX + cx*cd.fontWidth, cd.scrY + cy*cd.fontHeight, cd.fontWidth, cd.fontHeight)
lg.setColor(0, 0, 0)
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>=32 then
if val>=0x20 and val<=0x7F then -- printable ascii
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
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", cd.scrX + cx*cd.fontWidth, cd.scrY + cy*cd.fontHeight, cd.fontWidth, cd.fontHeight)
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
@@ -861,9 +1047,9 @@ local function RedrawWindow(usekeyboard, runcpu)
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)
RedrawFPSCounter(128, 4)
RedrawKeyInfo(128+64+16, 4, usekeyboard, runcpu)
RedrawConsole(Console, usekeyboard)
lg.setCanvas()
end
@@ -955,13 +1141,14 @@ local function endFrame()
end
local RunCPU = false
local CPUSpeed = 555
local CPUSpeed = MaxTicksPerFrame
local UseKeyboard = false
function love.draw()
startFrame()
UpdateTimer()
UpdateGPIO(GPIO, CPU, Memory)
UpdateSound()
CPU.c.frame = CPU.c.frame + 1
if RunCPU then
@@ -984,14 +1171,14 @@ function love.keypressed(k)
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=="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=="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)
ConsoleKey(Console, k, CPU, Memory)
end
end
end