diff --git a/internal/monitor/traps.go b/internal/monitor/traps.go index df43278..0b6464a 100644 --- a/internal/monitor/traps.go +++ b/internal/monitor/traps.go @@ -186,3 +186,101 @@ var TrapPuts = Routine{ /*0x0470 */ &asm.FILL{LITERAL: uint16(vm.DDRAddr)}, // data-registers. }, } + +// TrapHalt is the system call to stop the machine. +// - Table: 0x0000 +// - Vector: 0x23 +// - Handler: 0x04a0 +// +// Adapted from Fig. 9.12, 3/e. +var TrapGetc = Routine{ + Name: "GETC", + Vector: vm.TrapTable + vm.Word(vm.TrapHALT), + Orig: 0x04a0, + Symbols: asm.SymbolTable{ + "START": 0x04a0, + "L1": 0x04a4, + "LOOP": 0x04a8, + "L2": 0x04aa, + "INPUT": 0x04af, + "L3": 0x04b2, + + "SAVER1": 0x04bc, + "SAVER2": 0x04bd, + "SAVER3": 0x04bc, + "NEWLINE": 0xffff, + "DSR": 0xffff, + }, + Code: []asm.Operation{ + // Store registers to restore before return. + &asm.ST{SR: "R1", SYMBOL: "SAVER1"}, + &asm.ST{SR: "R2", SYMBOL: "SAVER2"}, + &asm.ST{SR: "R3", SYMBOL: "SAVER3"}, + &asm.LD{DR: "R2", SYMBOL: "NEWLINE"}, + + /*L1:0x04a4*/ + &asm.LDI{DR: "R1", SYMBOL: "DSR"}, // Check if DDR is empty. + &asm.BR{NZP: asm.CondZP, SYMBOL: "L1"}, + &asm.STI{SR: "R2", SYMBOL: "DDR"}, // Echo newline. + + // Load prompt address + &asm.LEA{DR: "R1", SYMBOL: "PROMPT"}, + + /*LOOP:0x04a8*/ + &asm.LDR{DR: "R0", SR: "R1", OFFSET: 0}, + &asm.BR{NZP: asm.CondZP, SYMBOL: "INPUT"}, + /*L2:0x04aa*/ + &asm.LDI{DR: "R3", SYMBOL: "DSR"}, // Check again if DDR is ready. + &asm.BR{NZP: asm.CondZP, SYMBOL: "L2"}, + &asm.STI{SR: "R0", SYMBOL: "DDR"}, // Echo next prompt char. + + &asm.ADD{DR: "R1", SR1: "R1", LITERAL: 1}, // Increment prompt pointer. + &asm.BR{NZP: asm.CondNZP, SYMBOL: "LOOP"}, // Iterate to LOOP. + + /*INPUT:0x04af*/ + &asm.LDI{DR: "R3", SYMBOL: "KBSR"}, // Check for KBD ready. + &asm.BR{NZP: asm.CondZP, SYMBOL: "INPUT"}, + &asm.LDI{DR: "R0", SYMBOL: "KBDR"}, // Load KBD input into trap param. + + /*L3:0x04b2*/ + &asm.LDI{DR: "R3", SYMBOL: "DSR"}, // Check yet again if DDR ready. + &asm.BR{NZP: asm.CondZP, SYMBOL: "L3"}, + &asm.STI{SR: "R0", SYMBOL: "DDR"}, // Echo input. + + /*L4:0x04b5*/ + &asm.LDI{DR: "R3", SYMBOL: "DSR"}, // Check once moce if DDR is ready. + &asm.BR{NZP: asm.CondZP, SYMBOL: "L4"}, + &asm.STI{SR: "R2", SYMBOL: "DDR"}, // Echo newline. + + // Restore work registers. + &asm.LD{DR: "R1", SYMBOL: "SAVER1"}, + &asm.LD{DR: "R2", SYMBOL: "SAVER2"}, + &asm.LD{DR: "R3", SYMBOL: "SAVER3"}, + + // Return from trap. + &asm.RTI{}, + + // Stored register allocations. + /*SAVER1:0x04bc*/ + &asm.BLKW{ALLOC: 0x0001}, + /*SAVER2:0x04bd*/ + &asm.BLKW{ALLOC: 0x0001}, + /*SAVER3:0x04be*/ + &asm.BLKW{ALLOC: 0x0001}, + + // Address constants. + /*DSR:0x04bf*/ + &asm.FILL{LITERAL: 0xfe02}, + /*DDR:0x04c0*/ + &asm.FILL{LITERAL: 0xfe04}, + /*KBSR:0x04c1*/ + &asm.FILL{LITERAL: 0xfe00}, + /*DDR:0x04c2*/ + &asm.FILL{LITERAL: 0xfe02}, + /*NEWLINE:0x04c3*/ + &asm.FILL{LITERAL: 0x000a}, + + /*PROMPT:0x04c4*/ + &asm.STRINGZ{LITERAL: "Input a character>"}, + }, +} diff --git a/internal/monitor/traps_test.go b/internal/monitor/traps_test.go index 08ffc74..6362d76 100644 --- a/internal/monitor/traps_test.go +++ b/internal/monitor/traps_test.go @@ -31,6 +31,60 @@ func (*trapHarness) Logger() *log.Logger { return log.DefaultLogger() } +func TestTrap_Getc(tt *testing.T) { + t := NewHarness(tt) + + obj, err := Generate(TrapGetc) + + if err != nil { + t.Error(err) + } + + if len(obj.Code) < 10 { + t.Error("code way too short", len(obj.Code)) + return + } + + image := SystemImage{ + logger: t.Logger(), + Symbols: nil, + Traps: []Routine{TrapGetc}, + } + + machine := vm.New( + WithSystemImage(&image), + ) + + // Now, we create code to execute the trap under test. + code := vm.ObjectCode{ + Orig: 0x3000, + Code: []vm.Word{ + vm.NewInstruction(vm.TRAP, 0x23).Encode(), + }, + } + + loader := vm.NewLoader(machine) + + unsafeLoad(loader, code) + + for i := 0; i < 500; i++ { + err = machine.Step() + + if testing.Verbose() { + t.Logf("Stepped\n%s\n%s\nerr %v", machine, machine.REG, err) + } + + if errors.Is(err, vm.ErrHalted) { + break + } else if !machine.MCR.Running() { + break + } else if err != nil { + t.Error(err) + break + } + } +} + func TestTrap_Halt(tt *testing.T) { t := NewHarness(tt) @@ -196,6 +250,7 @@ func TestTrap_Out(tt *testing.T) { if len(vals) != 1 || vals[0] != 0x2365 { t.Errorf("displayed %+v", vals) } + } func TestTrap_Puts(tt *testing.T) { diff --git a/internal/vm/intr.go b/internal/vm/intr.go index 3cad5d2..e3289c1 100644 --- a/internal/vm/intr.go +++ b/internal/vm/intr.go @@ -228,6 +228,7 @@ const ( TrapTable = Word(0x0000) // TRAP (0x0000:0x00ff) TrapOUT = uint8(0x21) // OUT TrapPUTS = uint8(0x22) // PUTS + TrapGETC = uint8(0x23) // GETC TrapHALT = uint8(0x25) // HALT )