diff --git a/docs/source/todo.rst b/docs/source/todo.rst
index ccfeb8de7..86b3159bf 100644
--- a/docs/source/todo.rst
+++ b/docs/source/todo.rst
@@ -3,7 +3,6 @@ TODO
For next release
^^^^^^^^^^^^^^^^
-- IR/VM: improve unit tests
- write some documentation about the compiler architecture and where to plug a code generator onto.
- vm: implement remaining sin/cos functions in virtual/math.p8 and merge tables
diff --git a/intermediate/test/TestIRFileInOut.kt b/intermediate/test/TestIRFileInOut.kt
new file mode 100644
index 000000000..d5835c2da
--- /dev/null
+++ b/intermediate/test/TestIRFileInOut.kt
@@ -0,0 +1,113 @@
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.ints.shouldBeGreaterThan
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.types.instanceOf
+import prog8.code.StStaticVariable
+import prog8.code.SymbolTable
+import prog8.code.core.CbmPrgLauncherType
+import prog8.code.core.CompilationOptions
+import prog8.code.core.OutputType
+import prog8.code.core.ZeropageType
+import prog8.code.target.Cx16Target
+import prog8.intermediate.IRFileReader
+import prog8.intermediate.IRFileWriter
+import prog8.intermediate.IRProgram
+import java.nio.file.Path
+import kotlin.io.path.deleteExisting
+import kotlin.io.path.name
+import kotlin.io.path.readLines
+import kotlin.io.path.writeText
+
+class TestIRFileInOut: FunSpec({
+ test("test IR writer") {
+ val st = SymbolTable()
+ val target = Cx16Target()
+ val tempdir = Path.of(System.getProperty("java.io.tmpdir"))
+ val options = CompilationOptions(
+ OutputType.RAW,
+ CbmPrgLauncherType.NONE,
+ ZeropageType.DONTUSE,
+ emptyList(),
+ floats = false,
+ noSysInit = true,
+ compTarget = target,
+ loadAddress = target.machine.PROGRAM_LOAD_ADDRESS,
+ outputDir = tempdir
+ )
+ val program = IRProgram("unittest-irwriter", st, options, target)
+ val writer = IRFileWriter(program)
+ writer.writeFile()
+ val generatedFile = tempdir.resolve("unittest-irwriter.p8ir")
+ val lines = generatedFile.readLines()
+ lines.first() shouldBe ""
+ lines.last() shouldBe ""
+ generatedFile.deleteExisting()
+ lines.size shouldBeGreaterThan 20
+ }
+
+ test("test IR reader") {
+ val source="""
+
+compTarget=virtual
+output=PRG
+launcher=BASIC
+zeropage=KERNALSAFE
+loadAddress=0
+dontReinitGlobals=false
+evalStackBaseAddress=null
+
+
+
+uword sys.wait.jiffies=0 zp=DONTCARE
+
+
+
+&uword cx16.r0=65282
+
+
+
+
+
+
+
+load.b r1,42
+
+
+
+
+
+
+
+
+return
+
+
+
+
+
+
+
+uword sys.wait.jiffies
+
+
+ loadm.w r0,sys.wait.jiffies
+ syscall 13
+
+
+return
+
+
+
+
+"""
+ val tempfile = kotlin.io.path.createTempFile(suffix = ".p8ir")
+ tempfile.writeText(source)
+ val filepart = tempfile.name.dropLast(5)
+ val reader = IRFileReader(tempfile.parent, filepart)
+ val program = reader.readFile()
+ tempfile.deleteExisting()
+ program.name shouldBe "test-ir-reader"
+ program.blocks.size shouldBe 2
+ program.st.lookup("sys.wait.jiffies") shouldBe instanceOf()
+ }
+})
\ No newline at end of file
diff --git a/intermediate/test/TestInstructions.kt b/intermediate/test/TestInstructions.kt
index a3f3b5313..814fb154e 100644
--- a/intermediate/test/TestInstructions.kt
+++ b/intermediate/test/TestInstructions.kt
@@ -2,6 +2,7 @@ import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
+import org.junit.jupiter.api.fail
import prog8.intermediate.*
diff --git a/virtualmachine/test/TestVm.kt b/virtualmachine/test/TestVm.kt
new file mode 100644
index 000000000..f0fa3e0c0
--- /dev/null
+++ b/virtualmachine/test/TestVm.kt
@@ -0,0 +1,52 @@
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.collections.shouldBeEmpty
+import io.kotest.matchers.shouldBe
+import prog8.intermediate.Instruction
+import prog8.intermediate.Opcode
+import prog8.intermediate.VmDataType
+import prog8.vm.Memory
+import prog8.vm.VirtualMachine
+import prog8.vm.VmRunner
+
+class TestVm: FunSpec( {
+ test("vm execution: empty program") {
+ val memory = Memory()
+ val vm = VirtualMachine(memory, emptyList(), 0xff00)
+ vm.callStack.shouldBeEmpty()
+ vm.valueStack.shouldBeEmpty()
+ vm.pc shouldBe 0
+ vm.stepCount shouldBe 0
+ vm.run(throttle = false)
+ vm.callStack.shouldBeEmpty()
+ vm.valueStack.shouldBeEmpty()
+ vm.pc shouldBe 0
+ vm.stepCount shouldBe 1
+ }
+
+ test("vm execution: modify memory") {
+ val memory = Memory()
+ val program = listOf(
+ Instruction(Opcode.LOAD, VmDataType.WORD, reg1=1, value=12345),
+ Instruction(Opcode.STOREM, VmDataType.WORD, reg1=1, value=1000),
+ Instruction(Opcode.RETURN)
+ )
+ val vm = VirtualMachine(memory, program, 0xff00)
+
+ memory.getUW(1000) shouldBe 0u
+ vm.callStack.shouldBeEmpty()
+ vm.valueStack.shouldBeEmpty()
+ vm.pc shouldBe 0
+ vm.stepCount shouldBe 0
+ vm.run(throttle = false)
+ memory.getUW(1000) shouldBe 12345u
+ vm.callStack.shouldBeEmpty()
+ vm.valueStack.shouldBeEmpty()
+ vm.pc shouldBe 2
+ vm.stepCount shouldBe 3
+ }
+
+ test("vmrunner") {
+ val runner = VmRunner()
+ runner.runProgram(";comment\n------PROGRAM------\n;comment\n", false)
+ }
+})