Skip to content

Commit

Permalink
Start some BIOS tests
Browse files Browse the repository at this point in the history
  • Loading branch information
smoynes committed Oct 24, 2023
1 parent d9fd8c3 commit 23e3450
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 33 deletions.
26 changes: 26 additions & 0 deletions internal/monitor/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,29 @@ func (img *SystemImage) LoadTo(loader *vm.Loader) (uint16, error) {

return count, nil
}

// Generate takes a BIOS routine, i.e. a trap or exception handler, and generates the code for it.
func Generate(routine Routine) (vm.ObjectCode, error) {
var pc uint16

obj := vm.ObjectCode{
Orig: routine.Orig,
Code: make([]vm.Word, 0, len(routine.Code)),
}

for _, oper := range routine.Code {
if oper == nil {
panic("operation is nil")
}

if encoded, err := oper.Generate(routine.Symbols, pc+uint16(routine.Orig)); err != nil {
return obj, fmt.Errorf("generate: %s: %w", oper, err)
} else {
for i := range encoded {
obj.Code = append(obj.Code, vm.Word(encoded[i]))
}
}
}

return obj, nil
}
54 changes: 21 additions & 33 deletions internal/monitor/traps.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,53 +10,41 @@ import (
// - Vector: 0x25
// - Handler: 0x0520
//
// Adapted from Fig. 9.14, 3/e.
// Adapted from https://github.com/chiragsakhuja/lc3tools/tree/master/src/lc3os.cpp
var TrapHalt = Routine{
Name: "HALT",
Vector: vm.TrapTable + vm.TrapHALT,
Orig: 0x0520,
Symbols: asm.SymbolTable{
"ASCIINEWLINE": 0x052f - 1,
"SAVER0": 0x0530 - 1,
"SAVER1": 0x0531 - 1,
"HALTMESSAGE": 0x0532 - 1,
"MCR": 0x0533 - 1,
"MASK": 0x0534 - 1,
"HALTMESSAGE": 0x0526 - 1,
"MCR": 0x0527 - 1,
"MASK": 0x0528 - 1,
},
Code: []asm.Operation{
/* 0x0520 */
&asm.ST{SR: "R1", SYMBOL: "SAVER1"}, // R1 -> [SAVER1] ; Store R1 to hold MCR address.
&asm.ST{SR: "R0", SYMBOL: "SAVER0"}, // R0 -> [SAVER0] ; Store R0 to hold temporary value.

// Print a message. Alert the media.
/* 0x0522 */
&asm.LD{DR: "R0", SYMBOL: "ASCIINEWLINE"},
&asm.TRAP{LITERAL: 0x21}, // OUT
/* 0x0520 */
&asm.LEA{DR: "R0", SYMBOL: "HALTMESSAGE"},
&asm.TRAP{LITERAL: 0x21}, // TODO: PUTS
&asm.LD{DR: "R0", SYMBOL: "ASCIINEWLINE"},
&asm.TRAP{LITERAL: 0x21}, // OUT

// Clear RUN flag in Machine Control Register.
/* 0x0528 */
&asm.LDI{DR: "R1", SYMBOL: "MCR"}, // Fetch R1 <- [MCR] ; Load MCR.
&asm.LD{DR: "R0", SYMBOL: "MASK"}, // Fetch R0 <- MASK ; Load bitmask.
&asm.AND{DR: "R0", SR1: "R1", SR2: "R0"}, // Clear top bit.
&asm.STI{SR: "R0", SYMBOL: "MCR"}, // Store R0 -> [MCR] ; Replace value in MCR.

// Exit from HALT(!?): restore registers and return from trap.
/* 0x052c */
&asm.LD{DR: "R1", OFFSET: 0x0003},
&asm.LD{DR: "R0", OFFSET: 0x0001},
&asm.RTI{},
/* 0x0522 */
&asm.LDI{DR: "R0", SYMBOL: "MCR"}, // R0 <- [MCR] ; Load MCR.
&asm.LD{DR: "R1", SYMBOL: "MASK"}, // R1 <- MASK ; Load bitmask.
&asm.AND{DR: "R0", SR1: "R0", SR2: "R1"}, // R1 <- R0 & R1 ; Clear top bit using bit mask
&asm.STI{SR: "R0", SYMBOL: "MCR"}, // [MCR]<- R0 ; Replace value in MCR.

// Halt again, if we reach here, forever.
/* 0x0525 */
&asm.BR{
NZP: uint8(vm.ConditionNegative | vm.ConditionPositive | vm.ConditionZero),
SYMBOL: "",
OFFSET: ^(uint16(5)) + 1,
},

// Routine data.
/* 0x052f */ &asm.FILL{LITERAL: uint16('\n')}, // ASCIINEWLINE.
/* 0x0530 */ &asm.BLKW{ALLOC: 1}, // Stored R0.
/* 0x0531 */ &asm.BLKW{ALLOC: 1}, // Stored R1.
/* 0x0532 */ &asm.STRINGZ{LITERAL: "!"}, // HALTMESSAGE.
/* 0x0533 */ &asm.FILL{LITERAL: uint16(vm.MCRAddr)}, // What it says.
/* 0x0534 */ &asm.FILL{LITERAL: 0x7fff}, // MASK to clear top bit.
/* 0x0526 */ &asm.STRINGZ{LITERAL: "!"}, // HALTMESSAGE.
/* 0x0527 */ &asm.FILL{LITERAL: uint16(vm.MCRAddr)}, // I/O address of MCR.
/* 0x0528 */ &asm.FILL{LITERAL: 0x7fff}, // MASK to clear top bit.
},
}

Expand Down
86 changes: 86 additions & 0 deletions internal/monitor/traps_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package monitor

import (
"testing"

"github.com/smoynes/elsie/internal/asm"
"github.com/smoynes/elsie/internal/log"
"github.com/smoynes/elsie/internal/vm"
)

type trapHarness struct{ *testing.T }

func (*trapHarness) Logger() *log.Logger {
log.LogLevel.Set(log.Debug)
return log.DefaultLogger()
}

func TestTrap_Halt(tt *testing.T) {
t := trapHarness{tt}

trap := TrapHalt
haltRoutine := Routine{
Name: "Test" + trap.Orig.String(),
Vector: 0x35,
Orig: trap.Orig,
Code: trap.Code,
Symbols: trap.Symbols,
}

obj, err := Generate(haltRoutine)

if err != nil {
t.Error(err)
}

if len(obj.Code) < 9 {
// Code must be AT LEAST 10 words: 7 instructions and a few bytes of data.
t.Error("code too short", len(obj.Code))
} else if len(obj.Code) >= 50 {
// It really should not be TOO long.
t.Error("code too long", len(obj.Code))
}

// We wish to test this trap without depending upon others so we stub the OUT trap.
putsRoutine := Routine{
Name: "Stub OUT",
Orig: TrapOut.Orig,
Vector: TrapOut.Vector,
Code: []asm.Operation{
&asm.RTI{},
},
Symbols: map[string]uint16{},
}

image := SystemImage{
log: t.Logger(),
Symbols: nil,
Traps: []Routine{
haltRoutine,
putsRoutine},
}

machine := vm.New(
WithSystemImage(&image),
)

// Now, we create code to execute the trap under test.
code := vm.ObjectCode{
Orig: 0x3000,
Code: []vm.Word{
vm.Word(vm.NewInstruction(vm.TRAP, 0x25).Encode()),
},
}

loader := vm.NewLoader(machine)
loader.Load(code)

Check failure on line 76 in internal/monitor/traps_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `loader.Load` is not checked (errcheck)

err = machine.Step()
if err != nil {
t.Error(err)
}

if machine.PC != 0x3000 {
t.Error("pc", machine)
}
}

0 comments on commit 23e3450

Please sign in to comment.