mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
1059 lines
35 KiB
Kotlin
1059 lines
35 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.ErrorReporterForTests
|
|
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("dynamic byte and word for loops stop before wrap") {
|
|
val src = $$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte byteAscending = $02
|
|
&ubyte byteDescending = $03
|
|
&ubyte byteEmpty = $04
|
|
&ubyte wordAscending = $05
|
|
&ubyte wordDescending = $06
|
|
&ubyte evalOrder = $07
|
|
&ubyte orderedCount = $08
|
|
&ubyte byteUnsignedDescending = $09
|
|
&ubyte wordUnsignedDescending = $0a
|
|
|
|
sub start() {
|
|
byte ordered
|
|
for ordered in getFrom() to getTo() step getStep() {
|
|
orderedCount++
|
|
}
|
|
|
|
byte b
|
|
byte @shared bfrom = 1
|
|
byte @shared bto = 5
|
|
byte @shared bstep = 2
|
|
for b in bfrom to bto step bstep {
|
|
byteAscending++
|
|
}
|
|
|
|
byte @shared bdownFrom = 5
|
|
byte @shared bdownTo = 1
|
|
byte @shared bdownStep = -2
|
|
for b in bdownFrom to bdownTo step bdownStep {
|
|
byteDescending++
|
|
}
|
|
|
|
ubyte ub
|
|
ubyte @shared ubFrom = 5
|
|
ubyte @shared ubTo = 1
|
|
byte @shared ubStep = -2
|
|
for ub in ubFrom to ubTo step ubStep {
|
|
byteUnsignedDescending++
|
|
}
|
|
|
|
byte @shared zeroStep = 0
|
|
for b in bfrom to bto step zeroStep {
|
|
byteEmpty++
|
|
}
|
|
|
|
uword w
|
|
uword @shared wfrom = 65530
|
|
uword @shared wto = 65535
|
|
uword @shared wstep = 3
|
|
for w in wfrom to wto step wstep {
|
|
wordAscending++
|
|
}
|
|
|
|
word wdown
|
|
word @shared wdownFrom = 300
|
|
word @shared wdownTo = 290
|
|
word @shared wdownStep = -4
|
|
for wdown in wdownFrom to wdownTo step wdownStep {
|
|
wordDescending++
|
|
}
|
|
|
|
uword uwdown
|
|
uword @shared uwdownFrom = 65530
|
|
uword @shared uwdownTo = 65520
|
|
word @shared uwdownStep = -3
|
|
for uwdown in uwdownFrom to uwdownTo step uwdownStep {
|
|
wordUnsignedDescending++
|
|
}
|
|
|
|
poweroff = 1
|
|
}
|
|
|
|
sub getFrom() -> byte {
|
|
evalOrder = 1
|
|
return 1
|
|
}
|
|
|
|
sub getTo() -> byte {
|
|
evalOrder = evalOrder * 10 + 2
|
|
return 5
|
|
}
|
|
|
|
sub getStep() -> byte {
|
|
evalOrder = evalOrder * 10 + 3
|
|
return 2
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
|
val machine = compileResult!!.simulate()
|
|
machine.assertMemory(0x02, 3)
|
|
machine.assertMemory(0x03, 3)
|
|
machine.assertMemory(0x04, 0)
|
|
machine.assertMemory(0x05, 2)
|
|
machine.assertMemory(0x06, 3)
|
|
machine.assertMemory(0x07, 123)
|
|
machine.assertMemory(0x08, 3)
|
|
machine.assertMemory(0x09, 3)
|
|
machine.assertMemory(0x0a, 4)
|
|
}
|
|
|
|
test("signed legacy loop variables stop on overflow with unsigned dynamic steps") {
|
|
val src = $$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&ubyte byteCount = $0b
|
|
&ubyte wordCount = $0c
|
|
sub start() {
|
|
byte b
|
|
byte @shared bfrom = 120
|
|
byte @shared bto = 127
|
|
ubyte @shared bstep = 10
|
|
for b in bfrom to bto step bstep {
|
|
byteCount++
|
|
}
|
|
|
|
word w
|
|
word @shared wfrom = 32760
|
|
word @shared wto = 32767
|
|
uword @shared wstep = 10
|
|
for w in wfrom to wto step wstep {
|
|
wordCount++
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
|
val machine = compileResult!!.simulate()
|
|
machine.assertMemory(0x0b, 1)
|
|
machine.assertMemory(0x0c, 1)
|
|
}
|
|
|
|
test("dynamic legacy for loop break targets remain scoped") {
|
|
val src = $$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte outerCount = $02
|
|
&ubyte poweroff = $f203
|
|
|
|
sub start() {
|
|
byte outer
|
|
byte inner
|
|
byte @shared outerFrom = 1
|
|
byte @shared outerTo = 3
|
|
byte @shared outerStep = 1
|
|
byte @shared innerFrom = 1
|
|
byte @shared innerTo = 3
|
|
byte @shared innerStep = 1
|
|
|
|
for outer in outerFrom to outerTo step outerStep {
|
|
for inner in innerFrom to innerTo step innerStep {
|
|
break
|
|
}
|
|
outerCount++
|
|
break
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
val errors = ErrorReporterForTests()
|
|
val compileResult = compileText(Cx16Target(), false, src, outputDir, errors = errors)
|
|
if (compileResult == null)
|
|
throw AssertionError(errors.errors.joinToString("\n"))
|
|
val machine = compileResult.simulate()
|
|
machine.assertMemory(0x02, 1)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
test("list iteration on 6502 target (sentinel comparison against &struct.field)") {
|
|
// regression: the desugared loop's sentinel comparison `cursor == &list.Tail` used to
|
|
// crash the 6502 code generator (PtAddressOf wrapping a dereference has no identifier)
|
|
val src = $$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
struct MyNode {
|
|
^^MyNode Succ
|
|
^^MyNode Pred
|
|
ubyte value
|
|
}
|
|
struct MyList {
|
|
^^MyNode Head
|
|
pointer Tail
|
|
^^MyNode TailPred
|
|
}
|
|
&ubyte poweroff = $f203
|
|
&ubyte count = $02
|
|
&ubyte sum = $03
|
|
|
|
sub start() {
|
|
^^MyList mylist = memory("mylist", sizeof(MyList), 0)
|
|
^^MyNode n1 = memory("n1", sizeof(MyNode), 0)
|
|
^^MyNode n2 = memory("n2", sizeof(MyNode), 0)
|
|
^^MyNode n3 = memory("n3", sizeof(MyNode), 0)
|
|
mylist.Head = &mylist.Tail as ^^MyNode
|
|
mylist.Tail = 0
|
|
mylist.TailPred = &mylist.Head as ^^MyNode
|
|
; append nodes to the tail of the circular list
|
|
n1.value = 10
|
|
n1.Succ = &mylist.Tail as ^^MyNode
|
|
n1.Pred = mylist.TailPred
|
|
mylist.TailPred.Succ = n1
|
|
mylist.TailPred = n1
|
|
n2.value = 20
|
|
n2.Succ = &mylist.Tail as ^^MyNode
|
|
n2.Pred = mylist.TailPred
|
|
mylist.TailPred.Succ = n2
|
|
mylist.TailPred = n2
|
|
n3.value = 40
|
|
n3.Succ = &mylist.Tail as ^^MyNode
|
|
n3.Pred = mylist.TailPred
|
|
mylist.TailPred.Succ = n3
|
|
mylist.TailPred = n3
|
|
; forward traversal using implicit loop var (inferred ^^MyNode)
|
|
for node in mylist {
|
|
if node==0 { break }
|
|
count++
|
|
sum += node.value
|
|
}
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
|
val machine = compileResult!!.simulate()
|
|
machine.assertMemory(0x02, 3)
|
|
machine.assertMemory(0x03, 70)
|
|
}
|
|
|
|
test("constant byte store via pointer plus constant offset") {
|
|
// regression: storing a constant byte to ptr[const] or @(ptr+const) never loaded the
|
|
// constant into A, so the low byte of the pointer (or other garbage) got stored instead
|
|
val src = $$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
struct Point {
|
|
uword x
|
|
ubyte y
|
|
}
|
|
|
|
sub start() {
|
|
uword @shared ptr_zp = $2000
|
|
uword @shared @nozp ptr_nozp = $2100
|
|
ptr_zp[3] = $80
|
|
@(ptr_zp+4) = $27
|
|
ptr_nozp[5] = $42
|
|
@(ptr_nozp+6) = $99
|
|
|
|
; the originally reported scenario: field store via a cast pointer,
|
|
; which goes through a non-zeropage auto_heap_value temp
|
|
pointer @shared buffer = $2200
|
|
(buffer as ^^Point).x = 160
|
|
(buffer as ^^Point).y = 120
|
|
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
for (optimize in listOf(false, true)) {
|
|
val compileResult = compileText(Cx16Target(), optimize, src, outputDir)
|
|
val machine = compileResult!!.simulate()
|
|
machine.assertMemory(0x2003, 0x80) // ptr_zp[3] = $80
|
|
machine.assertMemory(0x2004, 0x27) // @(ptr_zp+4) = $27
|
|
machine.assertMemory(0x2105, 0x42) // ptr_nozp[5] = $42
|
|
machine.assertMemory(0x2106, 0x99) // @(ptr_nozp+6) = $99
|
|
machine.assertMemory(0x2200, 160) // Point.x lsb
|
|
machine.assertMemory(0x2201, 0) // Point.x msb
|
|
machine.assertMemory(0x2202, 120) // Point.y
|
|
}
|
|
}
|
|
|
|
test("union zero-init via memory, write byte and read overlapping word") {
|
|
val src = $$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&uword result = $02
|
|
|
|
union U {
|
|
uword w
|
|
ubyte b
|
|
}
|
|
|
|
sub start() {
|
|
^^U u = memory("u", sizeof(U), 0)
|
|
u.b = $42
|
|
result = u.w
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
|
val machine = compileResult!!.simulate()
|
|
machine.assertMemory(0x02, 0x42)
|
|
machine.assertMemory(0x03, 0x00)
|
|
}
|
|
|
|
test("union copy via pointer dereference") {
|
|
val src = $$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&uword result = $02
|
|
|
|
union U {
|
|
uword w
|
|
ubyte[2] bytes
|
|
}
|
|
|
|
sub start() {
|
|
^^U u1 = memory("u1", sizeof(U), 0)
|
|
^^U u2 = memory("u2", sizeof(U), 0)
|
|
u1.bytes[0] = $78
|
|
u1.bytes[1] = $56
|
|
u2^^ = u1^^
|
|
result = u2.w
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
|
val machine = compileResult!!.simulate()
|
|
machine.assertMemory(0x02, 0x78)
|
|
machine.assertMemory(0x03, 0x56)
|
|
}
|
|
|
|
test("union in array of instances") {
|
|
val src = $$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
&uword result0 = $02
|
|
&uword result1 = $04
|
|
|
|
union U {
|
|
uword w
|
|
ubyte b
|
|
}
|
|
|
|
U[2] arr
|
|
|
|
sub start() {
|
|
arr[0].b = $42
|
|
arr[1].b = $77
|
|
result0 = arr[0].w
|
|
result1 = arr[1].w
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
|
val machine = compileResult!!.simulate()
|
|
machine.assertMemory(0x02, 0x42)
|
|
machine.assertMemory(0x03, 0x00)
|
|
machine.assertMemory(0x04, 0x77)
|
|
machine.assertMemory(0x05, 0x00)
|
|
}
|
|
|
|
test("lsb/msb/lsw/msw result delivered to a cx16 virtual register parameter") {
|
|
val src = $$"""
|
|
%option no_sysinit
|
|
%launcher none
|
|
%address $1000
|
|
|
|
main {
|
|
&ubyte poweroff = $f203
|
|
|
|
; each routine copies the value it received in the @R0 virtual register to a probe address
|
|
asmsub recordLsb(ubyte c @R0) {
|
|
%asm {{
|
|
lda cx16.r0L
|
|
sta $3000
|
|
rts
|
|
}}
|
|
}
|
|
asmsub recordMsb(ubyte c @R0) {
|
|
%asm {{
|
|
lda cx16.r0L
|
|
sta $3001
|
|
rts
|
|
}}
|
|
}
|
|
asmsub recordLsw(uword c @R0) {
|
|
%asm {{
|
|
lda cx16.r0
|
|
sta $3002
|
|
lda cx16.r0+1
|
|
sta $3003
|
|
rts
|
|
}}
|
|
}
|
|
asmsub recordMsw(uword c @R0) {
|
|
%asm {{
|
|
lda cx16.r0
|
|
sta $3004
|
|
lda cx16.r0+1
|
|
sta $3005
|
|
rts
|
|
}}
|
|
}
|
|
|
|
sub start() {
|
|
uword @shared l
|
|
l = $1234
|
|
recordLsb(lsb(l))
|
|
recordMsb(msb(l))
|
|
recordLsw(lsw(l))
|
|
recordMsw(msw(l))
|
|
poweroff = 1
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
|
|
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
|
val machine = compileResult!!.simulate()
|
|
machine.assertMemory(0x3000, 0x34) // lsb($1234)
|
|
machine.assertMemory(0x3001, 0x12) // msb($1234)
|
|
machine.assertMemory(0x3002, 0x34) // lsw($1234) low
|
|
machine.assertMemory(0x3003, 0x12) // lsw($1234) high
|
|
machine.assertMemory(0x3004, 0x00) // msw($1234) low
|
|
machine.assertMemory(0x3005, 0x00) // msw($1234) high
|
|
}
|
|
})
|