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

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
}
})