mksim

A PDP-8 emulator written in Go.

package main

import (
	"io"
	"os"
)

type Device interface {
	// Select returns true if addr is addressed to this device, false otherwise.
	// Th device should get any values it needs from the registers (such as AC).
	Select(addr uint16, mk *MK12) bool

	// Get is called to receive 12-bits of input from the device when the ORAC
	// control signal is asserted.
	Get() (data uint16)

	// The IOT instruction is broken down into three stages, the device should
	// implement the proper functionality at each stage by returning 3 booleans
	// that represent control signals:
	// skp - IOSKIP - Skip the next instruction if this is true at any stage
	// clr - ACCLR  - Clear the AC register if this is true at any stage
	// or  - ORAC   - OR input data with AC and store it in AC
	Iop1() (skip bool, clr bool, or bool)
	Iop2() (skip bool, clr bool, or bool)
	Iop4() (skip bool, clr bool, or bool)
}

const (
	PT_READER   = 0o01
	PT_PUNCH    = 0o02
	TT_KEYBOARD = 0o03
	TT_PRINTER  = 0o04
)

/////////////////////
// TeleType Device
//

type TeleTypeKeyboard interface {
	ReadByte() (byte, error)
	Buffered() int
}

type TeleTypePrinter interface {
	WriteByte(byte) error
	Flush() error
	Available() int
}

type TeleTypeDevice struct {
	Device
	In  int // Last teletype input char  (keyboard)
	Out int // Last teletype output char (printer)

	Keyboard TeleTypeKeyboard
	Printer  TeleTypePrinter

	ac  uint16 // Local copy of AC
	dev uint   // Device currently being interfaced with (Keyboard or printer)
}

func (tt *TeleTypeDevice) Select(addr uint16, mk *MK12) bool {

	if addr == TT_KEYBOARD || addr == TT_PRINTER {
		if addr == TT_PRINTER {
			tt.dev = TT_PRINTER
			// Save AC in case we need to print it
			tt.ac = mk.AC
		} else {
			tt.dev = TT_KEYBOARD
		}
		return true
	}
	return false
}

func (tt *TeleTypeDevice) Get() (data uint16) {
	cData, err := tt.Keyboard.ReadByte()
	if err != nil {
		panic("Read error")
	}
	return uint16(cData)
}

func (tt *TeleTypeDevice) Iop1() (skip bool, clr bool, or bool) {
	if tt.dev == TT_KEYBOARD {
		if tt.Keyboard.Buffered() > 0 { // KSF - Skip if incoming data available
			skip = true
		}
	} else if tt.dev == TT_PRINTER {
		if tt.Printer.Available() > 0 { // TSF - Skip if available to send
			skip = true
		}
	}
	return skip, clr, or
}

func (tt *TeleTypeDevice) Iop2() (skip bool, clr bool, or bool) {
	if tt.dev == TT_KEYBOARD { // KCC
		// Clear internal indicator of character ready (will be done when we read the byte)
		clr = true // Signal to clear AC in preparation of data exchange
	}
	return skip, clr, or
}

func (tt *TeleTypeDevice) Iop4() (skip bool, clr bool, or bool) {
	if tt.dev == TT_KEYBOARD { // KRS - Read keyboard buffer
		or = true
	} else if tt.dev == TT_PRINTER { // TPC - Print the contents of AC 4-11
		tt.Printer.WriteByte(byte(tt.ac & 0b000011111111))
		tt.Printer.Flush()
	}
	return skip, clr, or
}

///////////////////////////////////
// Paper Tape Reader/Punch Device (PC8-E)
//

type PaperTapeDevice struct {
	Device

	inTape  *os.File
	outTape *os.File

	// Read Buffer - Register to hold the character read from the tape
	RB uint16
	// Reader Flag - Flag to signify if character is available to read
	RF bool

	// Punch buffer - register to hold the character to punch
	PB uint16
	// Punch Flag - Denote a punch operation is complete
	PF bool

	// Device currently being interfaced with
	dev int
	// Local copy of AC
	ac uint16
}

func (pt *PaperTapeDevice) Select(addr uint16, mk *MK12) bool {
	if addr == PT_READER {
		pt.dev = PT_READER
		return true
	} else if addr == PT_PUNCH {
		pt.dev = PT_PUNCH
		// Flag starts true
		pt.PF = true
		// Save AC for use in PPC instruction
		pt.ac = mk.AC
		return true
	}
	return false
}

func (pt *PaperTapeDevice) Get() (data uint16) {
	return pt.RB
}

func (pt *PaperTapeDevice) Iop1() (skip bool, clr bool, or bool) {
	if pt.dev == PT_READER {
		skip = pt.RF
	}
	if pt.dev == PT_PUNCH {
		skip = pt.PF
	}
	return
}

func (pt *PaperTapeDevice) Iop2() (skip bool, clr bool, or bool) {
	if pt.dev == PT_READER {
		or = true
	}
	if pt.dev == PT_PUNCH {
		// Clear punch flag
		pt.PF = false
		// Clear PB?
		// pt.PB = 0
	}
	return
}

func (pt *PaperTapeDevice) Iop4() (skip bool, clr bool, or bool) {
	if pt.dev == PT_READER {
		// Clear reader flag
		pt.RF = false
		// Read character(byte) into RB
		nextByte := make([]byte, 1)
		_, err := pt.inTape.Read(nextByte)
		if err != nil {
			if err == io.EOF {
				// End of file
				pt.RB = 0
			} else {
				println("papertape jam")
				panic(err)
			}
		} else {
			pt.RB = uint16(nextByte[0])
		}
		// Set reader flag
		pt.RF = true
	}
	if pt.dev == PT_PUNCH {
		// Punch character
		_, err := pt.outTape.Write([]byte{byte(pt.ac)})
		if err != nil {
			panic(err)
		}
		// Set Punch flag
		pt.PF = true
	}
	return
}