From e3a4b375e3eefeb5d6db250949ff451039eb5583 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 10 Aug 2020 08:33:03 +0900 Subject: [PATCH] vdc loop is working but it seems 'plot (var)' is screwing up --- Videotron2K.md | 7 +- src/net/torvald/tsvm/VMGUI.kt | 27 +-- src/net/torvald/tsvm/vdc/V2kRunTest.kt | 117 +++++++++ src/net/torvald/tsvm/vdc/Videotron2K.kt | 306 ++++++++++++++++++++---- 4 files changed, 389 insertions(+), 68 deletions(-) create mode 100644 src/net/torvald/tsvm/vdc/V2kRunTest.kt diff --git a/Videotron2K.md b/Videotron2K.md index bb941f3..a3b1597 100644 --- a/Videotron2K.md +++ b/Videotron2K.md @@ -24,8 +24,8 @@ Consider the following code snippet: ``` DEFINE RATEF 60 ; 60 fps -DEFINE height 448 -DEFINE width 560 +DEFINE HEIGHT 448 +DEFINE WIDTH 560 SCENE initialise @ mov r1 height ; this line runs only once when the SCENE is called @@ -144,6 +144,7 @@ NOTE: Any drawing command will clobber internal memory starting from address zer * fillin byte y x-start y-end-exclusive : fills entire scanline of `y` with `byte` from the horizontal position `x-start` through `y-end-exclusive` MINUS ONE. final (px,py) will be (scanline,x-end-exclusive) * plot byte... : writes bytes into the framebuffer. The `px` register will auto-increment but `py` won't! +* plotp byte... : same as `plot` but treats bytes as PackBits-compressed payload * fillscr byte : fills entire screen with a given byte * goto x y : writes `x` to px and `y` to py (use `mov px ` to write to px/py only) * border r g b : sets border colour @@ -162,7 +163,7 @@ NOTE: Any drawing command will clobber internal memory starting from address zer #### Predefined Constants * RATET : framerate defined by miliseconds between each frame. Mutually exclusive with RATEF. -* RATEF : framerate defined by how many frames must be shown in one second. Mutually exclusive with RATET. +* RATEF : framerate defined by how many frames must be shown in one second. Mutually exclusive with RATET ### Auto-incrementation diff --git a/src/net/torvald/tsvm/VMGUI.kt b/src/net/torvald/tsvm/VMGUI.kt index 1f2bc6b..b4018d6 100644 --- a/src/net/torvald/tsvm/VMGUI.kt +++ b/src/net/torvald/tsvm/VMGUI.kt @@ -105,6 +105,19 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() fun poke(addr: Long, value: Byte) = vm.poke(addr, value) + private fun renderGame(delta: Float) { + gpu.render(delta, batch, 0f, 0f) + + } + + + override fun dispose() { + super.dispose() + batch.dispose() + coroutineJob.cancel() + vm.dispose() + } + private val gpuTestPaletteKt = """ val w = 560 val h = 448 @@ -385,20 +398,6 @@ while True: print("Apparent FPS: " + str(1000000000.0 / (tend - tstart))) """.trimIndent() - - - private fun renderGame(delta: Float) { - gpu.render(delta, batch, 0f, 0f) - - } - - - override fun dispose() { - super.dispose() - batch.dispose() - coroutineJob.cancel() - vm.dispose() - } } const val EMDASH = 0x2014.toChar() \ No newline at end of file diff --git a/src/net/torvald/tsvm/vdc/V2kRunTest.kt b/src/net/torvald/tsvm/vdc/V2kRunTest.kt new file mode 100644 index 0000000..234e423 --- /dev/null +++ b/src/net/torvald/tsvm/vdc/V2kRunTest.kt @@ -0,0 +1,117 @@ +package net.torvald.tsvm.vdc + +import com.badlogic.gdx.ApplicationAdapter +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.backends.lwjgl.LwjglApplication +import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration +import com.badlogic.gdx.graphics.OrthographicCamera +import com.badlogic.gdx.graphics.g2d.SpriteBatch +import com.badlogic.gdx.graphics.glutils.ShaderProgram +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import net.torvald.tsvm.* +import net.torvald.tsvm.peripheral.GraphicsAdapter + +class V2kRunTest(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() { + + val vm = VM(64.kB()) + lateinit var gpu: GraphicsAdapter + + lateinit var batch: SpriteBatch + lateinit var camera: OrthographicCamera + + lateinit var vmRunner: VMRunner + + lateinit var coroutineJob: Job + lateinit var vdc: Videotron2K + + override fun create() { + super.create() + + gpu = GraphicsAdapter(vm, lcdMode = false) + + vm.peripheralTable[1] = PeripheralEntry( + VM.PERITYPE_GPU_AND_TERM, + gpu, + GraphicsAdapter.VRAM_SIZE, + 16, + 0 + ) + + batch = SpriteBatch() + camera = OrthographicCamera(appConfig.width.toFloat(), appConfig.height.toFloat()) + camera.setToOrtho(false) + camera.update() + batch.projectionMatrix = camera.combined + Gdx.gl20.glViewport(0, 0, appConfig.width, appConfig.height) + + vm.getPrintStream = { gpu.getPrintStream() } + vm.getErrorStream = { gpu.getErrorStream() } + vm.getInputStream = { gpu.getInputStream() } + + vdc = Videotron2K(gpu) + + vmRunner = VMRunnerFactory(vm, "js") + coroutineJob = GlobalScope.launch { + vdc.eval(Videotron2K.screenfiller) + } + + + Gdx.input.inputProcessor = vm.getIO() + } + + private var updateAkku = 0.0 + private var updateRate = 1f / 60f + + override fun render() { + Gdx.graphics.setTitle("${AppLoader.appTitle} $EMDASH F: ${Gdx.graphics.framesPerSecond}") + + super.render() + + val dt = Gdx.graphics.rawDeltaTime + updateAkku += dt + + var i = 0L + while (updateAkku >= updateRate) { + updateGame(updateRate) + updateAkku -= updateRate + i += 1 + } + + renderGame(dt) + } + + private var latch = true + + private fun updateGame(delta: Float) { + vm.update(delta) + } + + private fun renderGame(delta: Float) { + gpu.render(delta, batch, 0f, 0f) + + } + + override fun dispose() { + super.dispose() + batch.dispose() + coroutineJob.cancel() + vm.dispose() + } +} + +fun main() { + ShaderProgram.pedantic = false + val appConfig = LwjglApplicationConfiguration() + appConfig.foregroundFPS = 60 + appConfig.backgroundFPS = 60 + appConfig.vSyncEnabled = false + appConfig.useGL30 = true + appConfig.resizable = false + appConfig.title = "Videotron2K Test" + appConfig.forceExit = true + appConfig.width = 560 + appConfig.height = 448 + LwjglApplication(V2kRunTest(appConfig), appConfig) +} \ No newline at end of file diff --git a/src/net/torvald/tsvm/vdc/Videotron2K.kt b/src/net/torvald/tsvm/vdc/Videotron2K.kt index 42ccf58..f1aec0b 100644 --- a/src/net/torvald/tsvm/vdc/Videotron2K.kt +++ b/src/net/torvald/tsvm/vdc/Videotron2K.kt @@ -4,6 +4,10 @@ import net.torvald.UnsafeHelper import net.torvald.random.HQRNG import net.torvald.tsvm.peripheral.GraphicsAdapter import java.lang.NumberFormatException +import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.collections.HashSet /** * See ./Videotron2K.md for documentation @@ -15,18 +19,24 @@ import java.lang.NumberFormatException class Videotron2K(var gpu: GraphicsAdapter?) { companion object { - private const val REGISTER_PREFIX = 0x7FFFFFFF_00000000L - private const val VARIABLE_PREFIX = 0x3FFFFFFF_00000000L + const val REGISTER_PREFIX = 0x7FFFFFFF_00000000L + const val VARIABLE_PREFIX = 0x3FFFFFFF_00000000L + const val PREFIX_MASK = (0xFFFFFFFFL).shl(32) - private const val REG_PX = REGISTER_PREFIX or 12 - private const val REG_PY = REGISTER_PREFIX or 13 - private const val REG_FRM = REGISTER_PREFIX or 14 - private const val REG_TMR = REGISTER_PREFIX or 15 + const val REG_PX = REGISTER_PREFIX or 12 + const val REG_PY = REGISTER_PREFIX or 13 + const val REG_FRM = REGISTER_PREFIX or 14 + const val REG_TMR = REGISTER_PREFIX or 15 - private const val REG_R1 = REGISTER_PREFIX - private const val REG_C1 = REGISTER_PREFIX + 6 + const val REG_R1 = REGISTER_PREFIX + const val REG_C1 = REGISTER_PREFIX + 6 - private const val INDEXED_SCENE_MAX = 1048575 + const val INDEXED_SCENE_MAX = 1048575 + + const val VARIABLE_RATET = VARIABLE_PREFIX or 0 + const val VARIABLE_RATEF = VARIABLE_PREFIX or 1 + const val VARIABLE_WIDTH = VARIABLE_PREFIX or 2 + const val VARIABLE_HEIGHT = VARIABLE_PREFIX or 3 private val specialRegs = hashMapOf( "px" to REG_PX, @@ -67,20 +77,22 @@ class Videotron2K(var gpu: GraphicsAdapter?) { DEFINE width 560 SCENE fill_line - @ mov 0 px + @ mov px 0 plot c1 ; will auto-increment px by one inc c1 - cmp c1 251 r3 - movzr r3 c3 0 ; mov (-zr r3) c3 0 -- first, the comparison is made with r3 then runs 'mov c3 0' if r3 == 0 - cmp px 560 r1 - exitzr r1 + cmp c1 251 r1 + movzr r1 c1 0 ; mov (-zr r1) c1 0 -- first, the comparison is made with r1 then runs 'mov c1 0' if r1 == 0 + exitzr px END SCENE SCENE loop_frame - @ mov 0 py + @ mov py 0 perform fill_line inc py + cmp py 448 r1 + movzr r1 py 0 next + ; exeunt ; there's no EXIT command so this scene will make the program to go loop indefinitely END SCENE @@ -89,47 +101,94 @@ class Videotron2K(var gpu: GraphicsAdapter?) { """.trimIndent() } - private var regs = UnsafeHelper.allocate(16 * 8) - private var internalMem = UnsafeHelper.allocate(16384) + internal var regs = UnsafeHelper.allocate(16 * 4) + internal var internalMem = UnsafeHelper.allocate(16384) + internal var callStack = Stack>() // Pair of Scene-ID (has SCENE_PREFIX; 0 with no prefix for root scene) and ProgramCounter /* Compile-time variables */ private var scenes = HashMap>() // Long can have either SCENE_PREFIX- or INDEXED_SCENE_PREFIX-prefixed value private var varIdTable = HashMap() // String is always uppercase, Long always has VARIABLE_PREFIX added //private var sceneIdTable = HashMap() // String is always uppercase, Long always has SCENE_PREFIX added - private var currentScene: Long? = null // if it's named_scene, VARIABLE_PREFIX is added; indexed_scene does not. + internal var currentScene = 0L // VARIABLE_PREFIX is normally added but ROOT scene is just zero. Used by both parser and executor + internal var currentLineIndex = 0 - /* Run-time variables */ - private var variableMap = HashMap() // VarId, Integer-value + internal var pcLoopedBack = false + internal var exeunt = false + + /* Run-time variables and status */ + internal var variableMap = HashMap() // VarId with VARIABLE_PREFIX, Integer-value + internal var sleepLatch = false + + internal var performanceCounterTmr = 0L + + fun resetVarIdTable() { + varIdTable.clear() + varIdTable["RATET"] = VARIABLE_RATET + varIdTable["RATEF"] = VARIABLE_RATEF + varIdTable["WIDTH"] = VARIABLE_WIDTH + varIdTable["HEIGHT"] = VARIABLE_HEIGHT + } private val reComment = Regex(""";[^\n]*""") private val reTokenizer = Regex(""" +""") private val reGeneralReg = Regex("""[rR][0-9]""") private val reCountReg = Regex("""[cC][0-9]""") - private val debugPrint = true + private val infoPrint = true + private val debugPrint = false private val rng = HQRNG() fun eval(command: String) { val rootStatements = parseCommands(command) - if (debugPrint) { + if (infoPrint) { scenes.forEach { id, statements -> println("SCENE #${id and 0xFFFFFFFFL}") - statements.forEach { println("$it") } + statements.forEachIndexed { i, it -> println("I ${i.toString().padEnd(5, ' ')}$it") } println("END SCENE\n") } - rootStatements.forEach { println(it) } + rootStatements.forEachIndexed { i, it -> println("I ${i.toString().padEnd(5, ' ')}$it") } } + + execute(rootStatements) } - private fun execute(rootStatements: ArrayList) { + private fun execute(rootStatements: Array) { variableMap.clear() + currentScene = 0 + regs.fillWith(0) - rootStatements.forEach { + while (!exeunt) { + val scene = if (currentScene == 0L) rootStatements else scenes[currentScene]!! + val it = scene[currentLineIndex] + val oldSceneNo = currentScene + if (it.prefix != StatementPrefix.INIT || it.prefix == StatementPrefix.INIT && !pcLoopedBack) { + if (debugPrint) println("Run-Scene: ${currentScene and 0xFFFFFFFFL}, Lindex: $currentLineIndex, Inst: $it") + Command.checkConditionAndRun(it.command, this, it.args) + if (debugPrint) println("Reg-r1: ${regs.getInt((REG_R1 and 0xF) * 4)}, c1: ${regs.getInt((REG_C1 and 0xF) * 4)}, px: ${regs.getInt((REG_PX and 0xF) * 4)}") + } + + + + + // increment PC + currentLineIndex += 1 + //// check if PC should loop back into the beginning of the scene + if (currentScene == oldSceneNo && currentLineIndex == scene.size) { + currentLineIndex = 0 + pcLoopedBack = true + } + if (currentScene != oldSceneNo) { + pcLoopedBack = false + } } + exeunt = false + + + if (infoPrint) println("Ende") } /** @@ -137,9 +196,9 @@ class Videotron2K(var gpu: GraphicsAdapter?) { * * @return root statements; scene statements are stored in 'scenes' */ - private fun parseCommands(command: String): ArrayList { + private fun parseCommands(command: String): Array { scenes.clear() - varIdTable.clear() + resetVarIdTable() //sceneIdTable.clear() val rootStatements = ArrayList() val sceneStatements = ArrayList() @@ -156,7 +215,7 @@ class Videotron2K(var gpu: GraphicsAdapter?) { try { val scenenum = scenenumStr.toLong() - currentScene = scenenum + currentScene = VARIABLE_PREFIX or scenenum } catch (e: NumberFormatException) { throw IllegalArgumentException("Line $lnum: Illegal scene numeral on $scenenumStr") @@ -174,23 +233,23 @@ class Videotron2K(var gpu: GraphicsAdapter?) { currentScene = registerNewVariable(sceneName) // scenes use same addr space as vars, to make things easier on the backend } else if (wordsUpper[0] == "END" && wordsUpper[1] == "SCENE") { // END SCENE - if (currentScene == null) { + if (currentScene == 0L) { throw IllegalArgumentException("Line $lnum: END SCENE is called without matching SCENE definition") } - scenes[currentScene!!] = sceneStatements.toTypedArray() + scenes[currentScene] = sceneStatements.toTypedArray() sceneStatements.clear() - currentScene = null + currentScene = 0L } else { - val cmdBuffer = if (currentScene != null) sceneStatements else rootStatements + val cmdBuffer = if (currentScene != 0L) sceneStatements else rootStatements cmdBuffer.add(translateLine(lnum + 1, stmt)) } } - return rootStatements + return rootStatements.toTypedArray() } private fun translateLine(lnum: Int, line: String): VT2Statement { @@ -310,30 +369,32 @@ object Command { const val NOT = 0x68 const val NEG = 0x70 - const val CMP = 0x100 + const val CMP = 0x80 + const val MOV = 0x88 + const val DATA = 0x90 + const val MCP = 0x98 - const val MOV = 0x200 - const val DATA = 0x208 - const val MCP = 0x210 + const val PERFORM = 0x100 + const val NEXT = 0x108 + const val EXIT = 0x110 + const val EXEUNT = 0x118 - const val PERFORM = 0x300 - const val NEXT = 0x308 - const val EXIT = 0x310 - const val EXEUNT = 0x318 + const val FILLIN = 0x200 + const val PLOT = 0x208 + const val FILLSCR = 0x210 + const val GOTO = 0x218 + const val BORDER = 0x220 + const val PLOTP = 0x228 - const val FILLIN = 0x400 - const val PLOT = 0x408 - const val FILLSCR = 0x410 - const val GOTO = 0x418 - const val BORDER = 0x420 + const val WAIT = 0x700 + const val DEFINE = 0x708 - const val WAIT = 0x600 - - const val DEFINE = 0xFFF8 + const val INST_ID_MAX = 0x800 // 256 if divided by 8 val dict = HashMap() val varDefiningCommands = HashSet() + val transferInst = HashSet() // fill in conditionals to dict @@ -368,6 +429,7 @@ object Command { "FILLIN" to FILLIN, "PLOT" to PLOT, + "PLOTP" to PLOTP, "FILLSCR" to FILLSCR, "GOTO" to GOTO, "BORDER" to BORDER, @@ -392,7 +454,149 @@ object Command { varDefiningCommands.add(command + cond) } } + + /* transferInst = */hashSetOf( + PERFORM, EXIT, EXEUNT + ).forEach { command -> + transferInst.add(command) + + conditional.forEachIndexed { cond, _ -> + transferInst.add(command + cond) + } + } } val reverseDict = HashMap(dict.entries.associate { (k,v)-> v to k }) -} \ No newline at end of file + + /** + * function (instance: Videotron2K, args: LongArray) + */ + val instSet = Array<(Videotron2K, LongArray) -> Unit>(256) { { _, _ -> TODO("Instruction ID $it (${reverseDict[it * 8]})") } } + + init { + instSet[NOP] = { _, _ -> } + instSet[DEFINE shr 3] = { instance, args -> // DEFINE variable value + if (args.size != 2) throw ArgsCountMismatch(2, args) + + instance.variableMap[args[0]] = if (args[1] and Videotron2K.VARIABLE_PREFIX == Videotron2K.VARIABLE_PREFIX) { + instance.variableMap[args[1]] ?: throw NullVar() + } + else if (args[1] and Videotron2K.PREFIX_MASK == 0L) { + args[1].toInt() + } + else { + throw UnknownVariableType(args[1]) + } + } + instSet[MOV shr 3] = { instance, args -> // MOV register value + if (args.size != 2) throw ArgsCountMismatch(2, args) + checkRegisterLH(args[0]) + instance.regs.setInt((args[0] and 0xF) * 4, resolveVar(instance, args[1])) + } + instSet[INC shr 3] = { instance, args -> // INC register + if (args.size != 1) throw ArgsCountMismatch(1, args) + checkRegisterLH(args[0]) + instance.regs.setInt((args[0] and 0xF) * 4, 1 + instance.regs.getInt((args[0] and 0xF) * 4)) + } + instSet[DEC shr 3] = { instance, args -> // DEC register + if (args.size != 1) throw ArgsCountMismatch(1, args) + checkRegisterLH(args[0]) + instance.regs.setInt((args[0] and 0xF) * 4, 1 - instance.regs.getInt((args[0] and 0xF) * 4)) + } + instSet[NEXT shr 3] = { instance, _ -> + instance.regs.setInt((Videotron2K.REG_FRM and 0xF) * 4, 1 + instance.regs.getInt((Videotron2K.REG_FRM and 0xF) * 4)) + instance.sleepLatch = true + + val timeTook = (System.nanoTime() - instance.performanceCounterTmr).toDouble() + println("Frame time: ${timeTook / 1000000.0} ms (fps: ${1000000000.0 / timeTook})") + instance.performanceCounterTmr = System.nanoTime() + } + instSet[CMP shr 3] = { instance, args -> // CMP rA rB rC + if (args.size != 3) throw ArgsCountMismatch(3, args) + val v1 = resolveVar(instance, args[0]) + val v2 = resolveVar(instance, args[1]) + val cmpvar = if (v1 > v2) 1 else if (v1 < v2) -1 else 0 + instance.regs.setInt((args[2] and 0xF) * 4, cmpvar) + } + instSet[PLOT shr 3] = { instance, args -> // PLOT vararg-bytes + if (args.isNotEmpty()) { + val px = instance.regs.getInt((Videotron2K.REG_PX and 0xF) * 4) + val py = instance.regs.getInt((Videotron2K.REG_PY and 0xF) * 4) + val width = instance.variableMap[Videotron2K.VARIABLE_WIDTH]!! + val memAddr = py * width + px + + args.forEachIndexed { index, value -> + instance.gpu?.poke(memAddr.toLong() + index, value.toByte()) + } + + // write back auto-incremented value + instance.regs.setInt((Videotron2K.REG_PX and 0xF) * 4, (px + args.size) fmod width) + } + } + instSet[PERFORM shr 3] = { instance, args -> // PERFORM scene + instance.callStack.push(instance.currentScene to instance.currentLineIndex) + instance.currentScene = args[0] + instance.currentLineIndex = -1 + } + instSet[EXIT shr 3] = { instance, _ -> + val (scene, line) = instance.callStack.pop() + instance.currentScene = scene + instance.currentLineIndex = line + //println("EXIT!") + //Thread.sleep(1000L) + } + instSet[EXEUNT shr 3] = { instance, _ -> + instance.exeunt = true + } + } + + fun checkConditionAndRun(inst: Int, instance: Videotron2K, args: LongArray) { + val opcode = inst shr 3 + val condCode = inst and 7 + + if (condCode == 0) { + //if (inst !in transferInst) instance.currentLineIndex += 1 + instSet[opcode].invoke(instance, args) + return + } + + val condition = when (condCode) { + 1 -> resolveVar(instance, args[0]) == 0 + 2 -> resolveVar(instance, args[0]) != 0 + 3 -> resolveVar(instance, args[0]) > 0 + 4 -> resolveVar(instance, args[0]) < 0 + 5 -> resolveVar(instance, args[0]) >= 0 + 6 -> resolveVar(instance, args[0]) <= 0 + else -> throw InternalError() + } + + if (condition) { + //if (inst !in transferInst) instance.currentLineIndex += 1 + instSet[opcode].invoke(instance, args.sliceArray(1 until args.size)) + } + } + + private fun resolveVar(instance: Videotron2K, arg: Long): Int { + return if (arg and Videotron2K.REGISTER_PREFIX == Videotron2K.REGISTER_PREFIX) { + instance.regs.getInt((arg and 0xF) * 4) + } + else if (arg and Videotron2K.VARIABLE_PREFIX == Videotron2K.VARIABLE_PREFIX) { + instance.variableMap[arg] ?: throw NullVar() + } + else arg.toInt() + } + + private fun checkRegisterLH(arg: Long) { + if (arg and Videotron2K.REGISTER_PREFIX != Videotron2K.REGISTER_PREFIX) { + throw BadLeftHand("register") + } + } + + private infix fun Int.fmod(other: Int) = Math.floorMod(this, other) +} + +internal class ArgsCountMismatch(expected: Int, args: LongArray) : RuntimeException("Argument count mismatch: expected $expected, got ${args.size}") +internal class UnknownVariableType(arg: Long) : RuntimeException("Unknown variable type: ${arg.ushr(32).toString(16)}") +internal class NullVar : RuntimeException("Variable is undeclared") +internal class BadLeftHand(type: String) : RuntimeException("Left-hand argument is not $type") +internal class BadRightHand(type: String) : RuntimeException("Right-hand argument is not $type")