Files
Halcyon/vm/vm_test.go
T
2026-09-19 20:21:47 +02:00

115 lines
2.6 KiB
Go

package vm
import "testing"
type testBus struct {
in map[uint16]int32
out map[uint16]int32
}
func (b *testBus) In(p uint16) int32 { return b.in[p] }
func (b *testBus) Out(p uint16, v int32) { b.out[p] = v }
func run(t *testing.T, src string, budget int) (*CPU, *testBus) {
t.Helper()
prog, err := Assemble(src)
if err != nil {
t.Fatal(err)
}
c, err := New(prog, 256)
if err != nil {
t.Fatal(err)
}
b := &testBus{in: map[uint16]int32{5: 42}, out: map[uint16]int32{}}
c.Run(b, budget)
return c, b
}
func TestSumLoop(t *testing.T) {
c, b := run(t, `
ldi r0, 0 ; sum
ldi r1, 1 ; i
ldi r2, 11
loop:
add r0, r1
addi r1, 1
blt r1, r2, loop
out 7, r0
halt
`, 1000)
if c.Status != Halted || b.out[7] != 55 {
t.Fatalf("status=%v out=%d", c.Status, b.out[7])
}
}
func TestLiCallStackMemory(t *testing.T) {
c, b := run(t, `
li r0, 0x12345678
call double
stw r0, [sp-8] ; below current sp
ldw r3, [sp-8]
in r4, 5
add r3, r4
out 1, r3
halt
double:
add r0, r0
ret
`, 1000)
want := int32(0x12345678)*2 + 42
if c.Status != Halted || b.out[1] != want {
t.Fatalf("status=%v fault=%q out=%x want %x", c.Status, c.Fault, b.out[1], want)
}
}
func TestYieldAndBudget(t *testing.T) {
prog, _ := Assemble("l: addi r0, 1\n jmp l")
c, _ := New(prog, 64)
b := &testBus{}
if used := c.Run(b, 100); used != 100 || c.Status != Running {
t.Fatalf("used=%d status=%v", used, c.Status)
}
r0 := c.R[0]
c.Run(b, 100)
if c.R[0] != r0+50 {
t.Fatalf("did not resume: %d -> %d", r0, c.R[0])
}
}
func TestFaults(t *testing.T) {
c, _ := run(t, "ldi r0, 1\n ldi r1, 0\n div r0, r1", 100)
if c.Status != Faulted {
t.Fatal("expected div fault")
}
c, _ = run(t, "ldi r1, 300\n ldw r0, [r1]", 100)
if c.Status != Faulted {
t.Fatal("expected memory fault")
}
}
func TestStateRoundTrip(t *testing.T) {
c, _ := run(t, "ldi r0, 9\n stb r0, [r1+3]\n yield\n ldi r0, 1", 100)
d, _ := New(c.Prog, 256)
if err := d.UnmarshalState(c.MarshalState()); err != nil {
t.Fatal(err)
}
if d.R != c.R || d.PC != c.PC || d.RAM[3] != 9 || d.Status != Yielded {
t.Fatal("state mismatch")
}
}
func TestAssemblerCaseInsensitiveDirectives(t *testing.T) {
// LI expands to two instructions; label distances must agree in any case.
lower, err := Assemble(".equ K 5\n li r1, 0x12345\n jmp end\n nop\nend: halt")
if err != nil {
t.Fatal(err)
}
upper, err := Assemble(".EQU K 5\n LI r1, 0x12345\n JMP end\n NOP\nend: HALT")
if err != nil {
t.Fatal(err)
}
if string(lower) != string(upper) {
t.Fatal("upper- and lower-case source assembled differently")
}
}