Files
prog8/compiler/test/codegeneration/TestExecution6502.kt
T

418 lines
14 KiB
Kotlin

package prog8tests.codegeneration
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldNotBeEmpty
import io.kotest.matchers.longs.shouldBeLessThan
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.code.target.Cx16Target
import prog8tests.helpers.CapturingSerialIO
import prog8tests.helpers.compileText
import prog8tests.helpers.simulate
import razorvine.ksim65.Disassembler
import java.nio.file.Files
/**
* These tests execute the compiled 6502 code in the ksim65 simulator.
* To interface correctly with the simulator, the following directives are required in the Prog8 source:
*
* - %option no_sysinit: Prevents the inclusion of the standard system initialization code which is not needed here.
* - %launcher none: No need for a program launcher.
* - %address $1000: The program should be loaded at $1000, which is where the simulator starts execution.
* - You can use memory-mapped variables to easily read their values after execution.
* - Best practice is to cleanly power off the simulator by writing to the poweroff register at $f203.
* - The Disassembler class can be used to disassemble the code loaded into the simulator's memory.
* That is an alternative way to see what assembly code is generated by Prog8. (the usual way is to load the 64tass-generated .asm file)
* A big benefit of using the Disassembler could be that the resulting assembly source code is much more compact BUT it has NO SYMBOLS WHATSOEVER.
*
* Simulator hardware interface registers:
* - $f000-$f003: Programmable Timer (4 registers).
* - $f100-$f109: Real Time Clock (10 registers).
* - $f200: Serial input register (read a byte from it).
* - $f201: Serial output register (write a byte to output it).
* - $f202: Reset register (writing any value triggers a CPU reset).
* - $f203: Power-off register (writing any value stops the simulator).
*/
class TestExecution6502 : FunSpec({
val outputDir = Files.createTempDirectory("prog8test")
test("simple addition") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
&ubyte result = $02 ; result is stored in memory at $02
sub start() {
ubyte @shared a = 10
ubyte @shared b = 20
result = a + b + 12
poweroff = 1 ; power off the simulator
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
val machine = compileResult!!.simulate()
machine.assertMemory(0x02, 42)
}
test("serial output") {
val src = $$"""
%encoding iso
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
sub start() {
serial.print("HELLO!")
poweroff = 1 ; power off the simulator
}
}
serial {
&ubyte serial_out = $f201
sub print(str message) {
repeat {
cx16.r0L = @(message)
if_z
return
serial_out = cx16.r0L
message++
}
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
compileResult shouldNotBe null
val serialIO = CapturingSerialIO()
val machine = compileResult!!.simulate(serialAndPower = serialIO)
serialIO.assertOutput("HELLO!")
println(machine.cpu.snapshot())
}
test("poweroff register") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
sub start() {
poweroff = 1 ; power off the simulator
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
val machine = compileResult!!.simulate(1000)
machine.cpu.totalCycles shouldBeLessThan 200
}
test("long repeat loops") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
&long result = $02 ; result is stored in memory at $02
sub start() {
long @shared qq = 0
repeat 70000 {
qq++
}
result = qq
long @shared iters = 70000
repeat iters {
qq++
}
result = qq
poweroff = 1
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
// 140000 iterations * ~40 cycles = 5.6 million. Let's use 10 million to be safe.
val machine = compileResult!!.simulate(maxCycles = 10000000L)
machine.assertMemory(0x02, (140000L and 0xff).toInt())
machine.assertMemory(0x03, ((140000L shr 8) and 0xff).toInt())
machine.assertMemory(0x04, ((140000L shr 16) and 0xff).toInt())
machine.assertMemory(0x05, ((140000L shr 24) and 0xff).toInt())
}
test("top level RTS exits simulator") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
sub start() {
; do nothing just RTS
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
val machine = compileResult!!.simulate(1000)
machine.cpu.totalCycles shouldBeLessThan 250
}
test("reset register") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte reset_reg = $f202
&ubyte counter = $02
sub start() {
counter++
if counter == 1 {
reset_reg = 1
}
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
val machine = compileResult!!.simulate()
machine.assertMemory(0x02, 2)
}
test("real time clock") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
&word year = $f100
&ubyte month = $f102
&ubyte day = $f103
&word res_year = $02
&ubyte res_month = $04
&ubyte res_day = $05
sub start() {
res_year = year
res_month = month
res_day = day
poweroff = 1 ; power off the simulator
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
val machine = compileResult!!.simulate()
val now = java.time.LocalDateTime.now()
machine.assertMemory(0x02, now.year and 0xff)
machine.assertMemory(0x03, (now.year shr 8) and 0xff)
machine.assertMemory(0x04, now.monthValue)
machine.assertMemory(0x05, now.dayOfMonth)
}
test("timer") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
&ubyte timer_ctrl = $f000
&ubyte timer_interval_hi = $f003
&ubyte timer_cnt_lo = $f001
&ubyte res_lo = $02
sub start() {
timer_interval_hi = $ff
timer_ctrl = 1 ; enable
repeat 100 {
cx16.r0L++ ; some delay
}
res_lo = timer_cnt_lo
poweroff = 1 ; power off the simulator
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
val machine = compileResult!!.simulate()
machine.assertMemory(0x02, 28)
}
test("serial input") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
&ubyte serial_in = $f200
&ubyte res1 = $02
&ubyte res2 = $03
sub start() {
res1 = serial_in
res2 = serial_in
poweroff = 1 ; power off the simulator
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
compileResult shouldNotBe null
val serialIO = CapturingSerialIO()
serialIO.provideInput("AB")
val machine = compileResult!!.simulate(serialAndPower = serialIO)
machine.assertMemory(0x02, 'A'.code)
machine.assertMemory(0x03, 'B'.code)
}
test("disassembler") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
sub start() {
ubyte @shared a = 10
ubyte @shared b = 20
ubyte c = a + b
poweroff = 1
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
val machine = compileResult!!.simulate(execute = false)
val disassembler = Disassembler(machine.cpu)
val (lines, _) = disassembler.disassemble(machine.ram.data, 0x1000..0x1040, 0x0000)
lines.shouldNotBeEmpty()
// we check if some expected instructions are there
lines.any { it.contains("lda", ignoreCase = true) } shouldBe true
lines.any { it.contains("sta", ignoreCase = true) } shouldBe true
lines.any { it.contains("adc", ignoreCase = true) } shouldBe true
}
test("timeout throws exception") {
val src = """
%option no_sysinit
%launcher none
%address $1000
main {
sub start() {
repeat {
; infinite loop that isn't just jmp *
cx16.r0L = 1
}
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), true, src, outputDir)
compileResult shouldNotBe null
shouldThrow<AssertionError> {
compileResult!!.simulate(maxCycles = 100)
}
}
test("div_longs positive 1234567890 / 88888 = 13889") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
sub start() {
long @shared v = 1234567890
v /= 88888
%asm {{
lda p8v_v
sta $2000
lda p8v_v+1
sta $2001
lda p8v_v+2
sta $2002
lda p8v_v+3
sta $2003
}}
poweroff = 1
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
val machine = compileResult!!.simulate(maxCycles = 30000)
val b0 = machine.ram[0x2000].toInt() and 0xff
val b1 = machine.ram[0x2001].toInt() and 0xff
val b2 = machine.ram[0x2002].toInt() and 0xff
val b3 = machine.ram[0x2003].toInt() and 0xff
println("div_longs result bytes: $b0 $b1 $b2 $b3 (hex: ${b0.toString(16)} ${b1.toString(16)} ${b2.toString(16)} ${b3.toString(16)})")
machine.assertMemory(0x2000, 0x41)
machine.assertMemory(0x2001, 0x36)
machine.assertMemory(0x2002, 0x00)
machine.assertMemory(0x2003, 0x00)
}
test("struct with inlined array memory layout (6502)") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
struct Node {
ubyte a
bool flag
ubyte[5] array
word number
}
sub start() {
^^Node k2 = [1, false, [65,66,67,68,0], 9999]
^^Node k3 = $4000
k3^^=k2^^
poweroff = 1
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
val machine = compileResult!!.simulate()
machine.assertMemory(0x4000, 1)
machine.assertMemory(0x4001, 0)
machine.assertMemory(0x4002, 65)
machine.assertMemory(0x4003, 66)
machine.assertMemory(0x4004, 67)
machine.assertMemory(0x4005, 68)
machine.assertMemory(0x4006, 0)
machine.assertMemory(0x4007, 9999 and 0xff)
machine.assertMemory(0x4008, (9999 shr 8) and 0xff)
}
})