115 lines
2.6 KiB
Go
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")
|
|
}
|
|
}
|