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

598 lines
20 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("indexed pointer fields in structs") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
%zeropage basicsafe
main {
struct Node {
bool flag
^^uword uptr
}
struct Wrapper {
bool z
^^Node node
}
^^Node @shared n = [true, 4000]
^^Node @shared inner = [true, 4500]
^^Wrapper @shared w = [false, 4000]
&ubyte poweroff = $f203
&uword slot_low = $0300
&uword slot_hi = $0302
uword index
sub start() {
n.uptr[3] = 9999
n.uptr[1] = 7777
n.uptr[9] = 5555
slot_low = n.uptr[3]
index = 1
n.uptr[index] = 1111
slot_hi = n.uptr[1]
w.node = inner
w.node.uptr[2] = 1234
poweroff = 1
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
val machine = compileResult!!.simulate()
machine.assertMemory(0x0300, 9999 and 0xff)
machine.assertMemory(0x0301, 9999 shr 8)
machine.assertMemory(0x0302, 1111 and 0xff)
machine.assertMemory(0x0303, 1111 shr 8)
machine.assertMemory(4000+6, 9999 and 0xff)
machine.assertMemory(4000+7, 9999 shr 8)
machine.assertMemory(4000+2, 1111 and 0xff)
machine.assertMemory(4000+3, 1111 shr 8)
machine.assertMemory(4000+18, 5555 and 0xff)
machine.assertMemory(4000+19, 5555 shr 8)
machine.assertMemory(4500+4, 1234 and 0xff)
machine.assertMemory(4500+5, 1234 shr 8)
}
test("more struct pointer field operations") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
%zeropage basicsafe
main {
struct Node {
bool flag
^^uword uptr
}
^^Node @shared n = [true, 4000]
^^Node @shared m = [true, 5000]
^^uword @shared normalptr = 4000
&ubyte poweroff = $f203
&uword n_after_swap = $0300
&uword m_after_swap = $0302
&uword n_after_add = $0304
&uword lsb_result = $0306
&uword msb_result = $0308
&uword idxinc_result = $030A
sub start() {
swap(n.uptr, m.uptr)
n_after_swap = n.uptr
m_after_swap = m.uptr
n.uptr += 2
n_after_add = n.uptr
n.uptr[3] = 100
n.uptr[3]++
idxinc_result = n.uptr[3]
lsb_result = lsb(normalptr)
msb_result = msb(normalptr)
poweroff = 1
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
val machine = compileResult!!.simulate()
machine.assertMemory(0x0300, 5000 and 0xff)
machine.assertMemory(0x0301, 5000 shr 8)
machine.assertMemory(0x0302, 4000 and 0xff)
machine.assertMemory(0x0303, 4000 shr 8)
machine.assertMemory(0x0304, 5004 and 0xff)
machine.assertMemory(0x0305, 5004 shr 8)
machine.assertMemory(0x0306, 160)
machine.assertMemory(0x0308, 15)
machine.assertMemory(0x030A, 101)
machine.assertMemory(0x030B, 0)
machine.assertMemory(5010, 101)
machine.assertMemory(5011, 0)
}
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("double increment/decrement of uword struct field via typed pointer") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
&uword result = $02
struct Sprite {
ubyte pad
ubyte pad2
uword x
}
^^Sprite[4] sprites
sub start() {
^^Sprite sprite = sprites[0]
sprite.x = 255
sprite.x++
sprite.x++
result = sprite.x ; expect $0101
poweroff = 1
}
}
""".trimIndent()
val compileResult = compileText(Cx16Target(), false, src, outputDir)
var machine = compileResult!!.simulate()
machine.assertMemory(0x02, 0x01)
machine.assertMemory(0x03, 0x01)
val src2 = $$"""
%option no_sysinit
%launcher none
%address $1000
main {
&ubyte poweroff = $f203
&uword result = $02
struct Sprite {
ubyte pad
ubyte pad2
uword x
}
^^Sprite[4] sprites
sub start() {
^^Sprite sprite = sprites[0]
sprite.x = 1
sprite.x--
sprite.x--
result = sprite.x ; expect $ffff
poweroff = 1
}
}
""".trimIndent()
val compileResult2 = compileText(Cx16Target(), false, src2, outputDir)
machine = compileResult2!!.simulate()
machine.assertMemory(0x02, 0xff)
machine.assertMemory(0x03, 0xff)
}
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)
}
})