draft of the VCD-programming thing

This commit is contained in:
minjaesong
2020-07-23 15:00:41 +09:00
parent 9aaf6200eb
commit 9ecacb7ece
7 changed files with 330 additions and 11 deletions

149
Videotron2K.md Normal file
View File

@@ -0,0 +1,149 @@
# Videotron2K
Videotron2K emulates hypothetical video display controller where target video device has framebuffer and the controller has a 1 scanline buffer and 16 registers and programmed in specialised assembly.
When program is running, video display controller must maintain the output signal (unless explicitly blanked, this behaviour depends on the display controller) and when the program ends, screen must be blanked.
## Registers
Videotron2K has 6 general and 4 special register, of which
- r1 through r6 : general register of 32 bit signed integer
- tmr : an RTC of signed integer, resolution of a milisecond
- frm : a frame counter of 32 bit signed integer
- px and py : a cursor where drawing operation is happening
- c1 through c6 : counter variable in 32 bit signed integer
Most commands accept pseudo-register of '0', which returns 0 when read and ignores any writing attempts.
The system uses two's complement for negative numbers.
## Assembly Format
Consider the following code snippet:
```
DEFINE RATEF 60 ; 60 fps
DEFINE height 448
DEFINE width 560
SCENE initialise
@ mov r1 height ; this line runs only once when the SCENE is called
fillin 254 r1 0 width ; fillin fills entire scanline.
; Syntax: fillin pixel-value scanline x-start x-end-exclusive
; final (px,py) will be (scanline,x-end-exclusive)
dec r1
exitzr r1 ; if condition is not met, the next line runs
; if next line is not there, it goes to the first non-@ line
END SCENE
SCENE 0 ; indexed scene
goto 100 100 ; moves pixel cursor to (x,y) = (100,100)
plot 1 54 231 7 82 64 22 5 ; writes following bytes into the framebuffer
END SCENE
SCENE 1 ; indexed scene
goto 100 102 ; moves pixel cursor to (x,y) = (100,102)
plot 231 1 54 17 182 62 2 35 ; writes following bytes into the framebuffer
END SCENE
SCENE anim
@ define cnt 2 ; definition of the local constant
@ mov c1 0
perform c1 ; accessing the indexed scene
inc c1 ; slightly inefficient way to make comparision
cmp c1 cnt r1 ; slightly inefficient way to make comparision
exitzr r1
END SCENE
perform initialise ; this command executes whatever defined in the target scene
; perform only accepts scene name
perform anim
goto 0 447
plot 0 0 254 254
next ; advance a frame counter (frm) and sleeps until it is time to draw next frame
exeunt ; this explicitly ends the program
```
### Conditional Postfixes
Commands can have "conditional postfix" used to execute the command conditionally.
- zr r : if r == 0
- nz r : if r != 0
- gt r : if r > 0
- ls r : if r < 0
- ge r : if r >= 0
- le r : if r <= 0
## Available Commands
### Arithmetic
* add rA rB rC : rC = rA + rB
* sub rA rB rC : rC = rA - rB
* mul rA rB rC : rC = rA * rB
* div rA rB rC : rC = rA / rB
* and rA rB rC : rC = rA & rB
* or rA rB rC : rC = rA | rB
* xor rA rB rC : rC = rA ^ rB
* shl rA rB rC : rC = rA << rB
* shr rA rB rC : rC = rA >> rB
* ushr rA rB rC : rC = rA >>> rB
* inc rA rB : rC = rA + 1
* dec rA rB : rC = rA - 1
* not R : R = !R (ones' complement of R)
* neg R : R = -R (twos' complement of R)
### Conditional
* cmp rA rB rC : compares rA and rB and stores result to rC. 1 if rA > rB, -1 if rA < rB, 0 if rA == rB.
### Data Control
NOTE: Any drawing command will clobber internal memory starting from address zero.
* mov rA rB/I : assignes R with contents of rB/constant I
* data rA/I bytes : writes bytes to the internal memory of address starting from rA/I
* mcp from y x len : copies part of the internal memory to the framebuffer, from the internal memory address `from`,
to scanline `y`, horizontal position `x`, with copying length of `len`.
### Flow Control
* perform scenename : gosub into the scenename
* next : advance a frame counter (frm) and sleeps until it is time to draw next frame
* exit : terminates current scene. System will error out if this command is used outside of a scene
* exeunt : completely terminates the program
### Drawing
NOTE: Any drawing command will clobber internal memory starting from address zero.
* fillin byte y x-start y-end-exclusive : fills entire scanline of `y` 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
* fillscr byte : fills entire screen with a given byte
* goto x y : writes `x` to px and `y` to py (use `mov px <something>` to write to px/py only)
* border r g b : sets border colour
### Timing Control
* wait : waits for external signal before advancing to next frame.
### Assembler-only
* define : defines a global variable
* defvar : defines a scene-local variable
* scene name|number : defines a named scene
* end scene : ends a scene definition
#### 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.

View File

@@ -217,6 +217,7 @@ var basicInterpreterStatus = {};
basicInterpreterStatus.gosubStack = [];
basicInterpreterStatus.variables = {};
basicInterpreterStatus.defuns = {};
basicInterpreterStatus.rnd = 0; // stores mantissa (23 bits long) of single precision floating point number
/*
@param lnum line number
@param args instance of the SyntaxTreeReturnObj
@@ -311,6 +312,8 @@ basicInterpreterStatus.builtin = {
}
var resolvedargs = resolve(args[llll]);
if (resolvedargs === undefined) resolvedargs = "";
if (args[llll].type == "number")
print(" "+resolvedargs+" ");
else
@@ -385,6 +388,12 @@ basicInterpreterStatus.builtin = {
});
return argum[0] || argum[1];
},
"RND" : function(lnum, args) {
if (!(args.length > 0 && args[0].value === 0))
basicInterpreterStatus.rnd = (basicInterpreterStatus.rnd * 214013 + 2531011) % 16777216; // GW-BASIC does this
return (basicInterpreterStatus.rnd) / 16777216;
},
"TEST" : function(lnum, args) {
if (args.length != 1) throw lang.syntaxfehler(lnum, args.length + " arguments were given");
return resolve(args[0]);
@@ -1163,7 +1172,7 @@ function SyntaxTreeReturnObj(type, value, nextLine) {
this.value = value;
this.nextLine = nextLine;
}
basicFunctions._gotoCmds = { GOTO:1, GOSUB:1 };
basicFunctions._gotoCmds = { GOTO:1, GOSUB:1 }; // put nonzero (truthy) value here
basicFunctions._executeSyntaxTree = function(lnum, syntaxTree, recDepth) {
var _debugExec = false;
var recWedge = "> ".repeat(recDepth);

View File

@@ -7,7 +7,7 @@ import sun.nio.ch.DirectBuffer
class GraphicsJSR223Delegate(val vm: VM) {
private fun getFirstGPU(): GraphicsAdapter? {
return vm.findPeribyType(VM.PERITYPE_TERM)?.peripheral as? GraphicsAdapter
return vm.findPeribyType(VM.PERITYPE_GPU_AND_TERM)?.peripheral as? GraphicsAdapter
}
fun resetPalette() {

View File

@@ -124,7 +124,7 @@ class VM(
val HW_RESERVE_SIZE = 1024.kB()
val USER_SPACE_SIZE = 8192.kB()
const val PERITYPE_TERM = "gpu"
const val PERITYPE_GPU_AND_TERM = "gpu"
}
internal fun translateAddr(addr: Long): Pair<Any?, Long> {

View File

@@ -2,17 +2,12 @@ package net.torvald.tsvm
import com.badlogic.gdx.ApplicationAdapter
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.InputProcessor
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration
import com.badlogic.gdx.graphics.OrthographicCamera
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import kotlinx.coroutines.*
import net.torvald.tsvm.peripheral.GraphicsAdapter
import net.torvald.tsvm.peripheral.IOSpace
import java.io.FileReader
import java.io.InputStream
import java.io.OutputStream
import java.io.StringReader
class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() {
@@ -34,7 +29,7 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter()
gpu = GraphicsAdapter(vm, lcdMode = false)
vm.peripheralTable[1] = PeripheralEntry(
VM.PERITYPE_TERM,
VM.PERITYPE_GPU_AND_TERM,
gpu,
GraphicsAdapter.VRAM_SIZE,
16,

View File

@@ -1,8 +1,7 @@
package net.torvald.tsvm
import java.io.FileInputStream
import net.torvald.tsvm.peripheral.GraphicsAdapter
import java.io.FileReader
import javax.script.Compilable
import javax.script.ScriptContext
import javax.script.ScriptEngineManager
import javax.script.SimpleScriptContext
@@ -38,6 +37,16 @@ object VMRunnerFactory {
}
}
}
"vt2" -> {
object : VMRunner(extension) {
val engine = Videotron2K(vm.findPeribyType(VM.PERITYPE_GPU_AND_TERM)!!.peripheral!! as GraphicsAdapter)
override suspend fun executeCommand(command: String) {
engine.eval(command)
}
}
}
else -> {
object : VMRunner(extension) {
private val engine = ScriptEngineManager().getEngineByExtension(extension)

View File

@@ -0,0 +1,157 @@
package net.torvald.tsvm
import net.torvald.UnsafeHelper
import net.torvald.tsvm.peripheral.GraphicsAdapter
import java.lang.NumberFormatException
/**
* See ./Videotron2K.md for documentation
*/
class Videotron2K(val gpu: GraphicsAdapter) {
private var regs = UnsafeHelper.allocate(16 * 8)
private var internalMem = UnsafeHelper.allocate(16384)
private var scenes = HashMap<String, Array<VT2Statement>>()
private var varIdTable = HashMap<String, Long>()
private val reComment = Regex(""";[^\n]*""")
private val reTokenizer = Regex(""" +""")
private val conditional = arrayOf("ZR", "NZ", "GT", "LS", "GE", "LE")
fun eval(command: String) {
var command = command.replace(reComment, "")
}
private fun translateLine(lnum: Int, line: String): VT2Statement? {
val tokens = line.split(reTokenizer)
if (tokens.isEmpty()) return null
val isInit = tokens[0] == "@"
val cmdstr = tokens[isInit.toInt()].toUpperCase()
val cmdcond = (conditional.linearSearch { it == cmdstr.substring(cmdstr.length - 2, cmdstr.length) } ?: -1) + 1
val realcmd = if (cmdcond > 0) cmdstr.substring(0, cmdstr.length - 2) else cmdstr
val cmd: Int = Command.dict[realcmd] ?: throw RuntimeException("Syntax Error at line $lnum")
val args = tokens.subList(1 + isInit.toInt(), tokens.size).map { parseArgString(it) }
return VT2Statement(if (isInit) StatementPrefix.INIT else StatementPrefix.NONE, cmd or cmdcond, args.toLongArray())
}
private fun parseArgString(token: String): Long {
if (token.toIntOrNull() != null)
return token.toLong().and(0xFFFFFFFF)
else if (token.endsWith('h') && token.substring(0, token.lastIndex).toIntOrNull() != null)
return token.substring(0, token.lastIndex).toInt(16).toLong().and(0xFFFFFFFF)
else if (token.startsWith('r') && token.substring(1, token.length).toIntOrNull() != null)
return REGISTER_PREFIX or token.substring(1, token.length).toLong().and(0xFFFFFFFF)
else {
TODO("variable assignation and utilisation")
}
}
private class VT2Statement(val prefix: Int = StatementPrefix.NONE, val command: Int, val args: LongArray)
fun dispose() {
regs.destroy()
}
private fun Boolean.toInt() = if (this) 1 else 0
private fun <T> Array<T>.linearSearch(selector: (T) -> Boolean): Int? {
this.forEachIndexed { index, it ->
if (selector.invoke(it)) return index
}
return null
}
companion object {
private const val REGISTER_PREFIX = 0x7FFFFFFF_00000000L
private const val VARIABLE_PREFIX = 0x3FFFFFFF_00000000L
}
}
object StatementPrefix {
const val NONE = 0
const val INIT = 1
}
object Command {
const val NOP = 0
const val ADD = 0x8
const val SUB = 0x10
const val MUL = 0x18
const val DIV = 0x20
const val AND = 0x28
const val OR = 0x30
const val XOR = 0x38
const val SHL = 0x40
const val SHR = 0x48
const val USHR = 0x50
const val INC = 0x58
const val DEC = 0x60
const val NOT = 0x68
const val NEG = 0x70
const val CMP = 0x100
const val MOV = 0x200
const val DATA = 0x208
const val MCP = 0x210
const val PERFORM = 0x300
const val NEXT = 0x308
const val EXIT = 0x310
const val EXEUNT = 0x318
const val FILLIN = 0x400
const val PLOT = 0x408
const val FILLSCR = 0x410
const val GOTO = 0x418
const val BORDER = 0x420
const val WAIT = 0x600
const val DEFINE = 0xFFF8
val dict = hashMapOf(
"NOP" to NOP,
"ADD" to ADD,
"SUB" to SUB,
"MUL" to MUL,
"DIV" to DIV,
"AND" to AND,
"OR" to OR,
"XOR" to XOR,
"SHL" to SHL,
"SHR" to SHR,
"USHR" to USHR,
"INC" to INC,
"DEC" to DEC,
"NOT" to NOT,
"NEG" to NEG,
"CMP" to CMP,
"MOV" to MOV,
"DATA" to DATA,
"MCP" to MCP,
"PERFORM" to PERFORM,
"NEXT" to NEXT,
"EXIT" to EXIT,
"EXEUNT" to EXEUNT,
"FILLIN" to FILLIN,
"PLOT" to PLOT,
"FILLSCR" to FILLSCR,
"GOTO" to GOTO,
"BORDER" to BORDER,
"WAIT" to WAIT,
"DEFINE" to DEFINE
)
}