194 lines
4.7 KiB
Go
194 lines
4.7 KiB
Go
package marcie
|
|
|
|
import "io"
|
|
|
|
// Machine represents a MARIE system. It can be used to run code.
|
|
type Machine struct {
|
|
memory [4096]int16
|
|
counter int16
|
|
accumulator int16
|
|
|
|
output io.Writer
|
|
input io.Reader
|
|
}
|
|
|
|
// Opcode represents an opcode for the machine.
|
|
type Opcode uint8
|
|
|
|
const (
|
|
JNS Opcode = 0x0
|
|
LOAD Opcode = 0x1
|
|
STORE Opcode = 0x2
|
|
ADD Opcode = 0x3
|
|
SUBT Opcode = 0x4
|
|
INPUT Opcode = 0x5
|
|
OUTPUT Opcode = 0x6
|
|
HALT Opcode = 0x7
|
|
SKIPCOND Opcode = 0x8
|
|
JUMP Opcode = 0x9
|
|
CLEAR Opcode = 0xA
|
|
ADDI Opcode = 0xB
|
|
JUMPI Opcode = 0xC
|
|
LOADI Opcode = 0xD
|
|
STOREI Opcode = 0xE
|
|
)
|
|
|
|
// Instruction provides a convenient way to create an instruction word
|
|
func Instruction (opcode Opcode, operand uint16) (instruction int16) {
|
|
instruction |= int16(uint16(opcode) << 12)
|
|
instruction |= int16(operand)
|
|
|
|
return
|
|
}
|
|
|
|
// Error is an error type that can be returned from the machine's methods if
|
|
// they experience problems.
|
|
type Error int
|
|
|
|
const (
|
|
ErrorUnknownInstruction Error = iota
|
|
)
|
|
|
|
// Error returns a textual description of the error.
|
|
func (err Error) Error() (description string) {
|
|
switch err {
|
|
case ErrorUnknownInstruction:
|
|
description = "machine encountered unknown instruction"
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Execute starts execution at the address specified by offset. The machine will
|
|
// run until a HALT instruction is encountered, or the end of program memory is
|
|
// reached. If an unknown instruction is encountered, the machine halts and
|
|
// returns an error.
|
|
func (machine *Machine) Execute (offset int16) (err error) {
|
|
machine.counter = offset
|
|
machine.accumulator = 0
|
|
for int(machine.counter) < len(machine.memory) {
|
|
word := machine.memory[machine.counter]
|
|
opcode := Opcode(word >> 12) & 0x000F
|
|
operand := word & 0x0FFF
|
|
memoryValue := int16(0)
|
|
|
|
if int(operand) < len(machine.memory) {
|
|
memoryValue = machine.memory[operand]
|
|
}
|
|
|
|
switch opcode {
|
|
case JNS:
|
|
// Store the PC at address X and jump to X+1
|
|
machine.memory[operand] = machine.counter
|
|
machine.counter = operand
|
|
|
|
case LOAD:
|
|
// Load contents of address X into AC
|
|
machine.accumulator = memoryValue
|
|
|
|
case STORE:
|
|
// Store the contents of AC at address X
|
|
machine.memory[operand] = machine.accumulator
|
|
|
|
case ADD:
|
|
// Add the contents of address X to AC
|
|
machine.accumulator += memoryValue
|
|
|
|
case SUBT:
|
|
// Subtract the contents of address X from AC
|
|
machine.accumulator -= memoryValue
|
|
|
|
case INPUT:
|
|
// Input a value from the keyboard into AC
|
|
buffer := []byte { 0 }
|
|
_, err = machine.input.Read(buffer)
|
|
if err != nil { return }
|
|
machine.accumulator = int16(buffer[0])
|
|
|
|
case OUTPUT:
|
|
// Output the value in AC to the display
|
|
buffer := []byte { byte(machine.accumulator) }
|
|
_, err = machine.output.Write(buffer)
|
|
if err != nil { return }
|
|
|
|
case HALT:
|
|
// Terminate program
|
|
return
|
|
|
|
case SKIPCOND:
|
|
// Skip next instruction on condition
|
|
condition := false
|
|
switch operand {
|
|
case 0x000:
|
|
condition = machine.accumulator < 0
|
|
case 0x400:
|
|
condition = machine.accumulator == 0
|
|
case 0x800:
|
|
condition = machine.accumulator > 0
|
|
}
|
|
|
|
if condition {
|
|
machine.counter ++
|
|
}
|
|
|
|
case JUMP:
|
|
// Load the value of X into PC
|
|
machine.counter = operand - 1
|
|
|
|
case CLEAR:
|
|
// Put all zeros in AC
|
|
machine.accumulator = 0
|
|
|
|
case ADDI:
|
|
// Add indirect: Use the value at X as the actual
|
|
// address of the data operand to add to AC
|
|
machine.accumulator += machine.memory[memoryValue]
|
|
|
|
case JUMPI:
|
|
// Jump indirect: Use the value at X as the address to
|
|
// jump to
|
|
machine.counter += machine.memory[memoryValue] - 1
|
|
|
|
case LOADI:
|
|
// Load indirect: Use the value at X as the address of
|
|
// the value to load
|
|
machine.accumulator = machine.memory[memoryValue]
|
|
|
|
case STOREI:
|
|
// Store indirect: Use X the value at X as the address
|
|
// of where to store the value
|
|
machine.memory[memoryValue] = machine.accumulator
|
|
|
|
default:
|
|
err = ErrorUnknownInstruction
|
|
}
|
|
|
|
machine.counter ++
|
|
}
|
|
return
|
|
}
|
|
|
|
// SetOutput sets the io.Writer that the machine will write to.
|
|
func (machine *Machine) SetOutput (writer io.Writer) {
|
|
machine.output = writer
|
|
}
|
|
|
|
// SetInput sets the io.Reader that the machine will read from.
|
|
func (machine *Machine) SetInput (reader io.Reader) {
|
|
machine.input = reader
|
|
}
|
|
|
|
// LoadProgram loads an image into the machine's memory. If the program is
|
|
// smaller than the machine's memory, the remaining cells are filled with
|
|
// zeroes. If the program is larger than the machine's memory, the remaining
|
|
// words are ignored.
|
|
func (machine *Machine) LoadProgram (program []int16) {
|
|
for index := 0; index < len(machine.memory); index ++ {
|
|
if index < len(program) {
|
|
machine.memory[index] = program[index]
|
|
} else {
|
|
machine.memory[index] = 0
|
|
}
|
|
}
|
|
}
|