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