mksim
A PDP-8 emulator written in Go.
package main
import (
"fmt"
"log"
"os"
"github.com/jroimartin/gocui"
)
var lastKey byte
var switchRegister uint16
type CUIFrontPanel struct {
g *gocui.Gui
MemoryViewerPage int
}
func (fp *CUIFrontPanel) PowerOn(mk MK12) {
// Initialize Console interface on powerup and save it
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
fp.g = g
// Layout
g.SetManagerFunc(layout)
// Keybindings
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, proceed); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeySpace, gocui.ModNone, step); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyHome, gocui.ModNone, loadPC); err != nil {
log.Panicln(err)
}
// F1-F12 Keys for Switch register
if err := g.SetKeybinding("", gocui.KeyF1, gocui.ModNone, switchRegister1); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyF2, gocui.ModNone, switchRegister2); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyF3, gocui.ModNone, switchRegister3); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyF4, gocui.ModNone, switchRegister4); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyF5, gocui.ModNone, switchRegister5); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyF6, gocui.ModNone, switchRegister6); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyF7, gocui.ModNone, switchRegister7); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyF8, gocui.ModNone, switchRegister8); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyF9, gocui.ModNone, switchRegister9); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyF10, gocui.ModNone, switchRegister10); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyF11, gocui.ModNone, switchRegister11); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyF12, gocui.ModNone, switchRegister12); err != nil {
log.Panicln(err)
}
// Start CUI loop
go g.MainLoop()
}
func (fp *CUIFrontPanel) PowerOff() {
fp.g.Close()
}
func (fp *CUIFrontPanel) Update(mk MK12) {
var status string
var attr = gocui.AttrBold
if mk.STATE.HALT {
status = "HALT"
attr |= gocui.ColorRed
} else if mk.STATE.SSTEP {
status = "STEP"
attr |= gocui.ColorBlue
} else {
status = "RUN"
attr |= gocui.ColorGreen
}
updateStatus(fp.g, status, attr)
updateRegister(fp.g, "accumulator-register", mk.AC)
updateRegister(fp.g, "counter-register", mk.PC)
updateRegister(fp.g, "instruction-register", mk.IR)
updateRegister(fp.g, "address-register", mk.MA)
updateRegister(fp.g, "buffer-register", mk.MB)
updateRegister(fp.g, "switch-register", mk.SR)
debugPrint(fp.g, mk.IRd)
if 0 <= fp.MemoryViewerPage && fp.MemoryViewerPage <= 0o7777 {
updateMemory(fp.g, mk.MEM, uint16(fp.MemoryViewerPage)&0b0000111110000000)
} else {
updateMemory(fp.g, mk.MEM, mk.PC&0b0000111110000000)
}
updateZeroMemory(fp.g, mk.MEM)
}
func (fp *CUIFrontPanel) ReadSwitches() uint16 {
return switchRegister
}
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
// Register size
regNum := 6
regWStart := 0
regWidth := 15
regWEnd := regWStart + regWidth
regHeight := 2
regHStop := maxY - 3
regHStart := regHStop - ((regHeight + 1) * regNum)
regHEnd := regHStart + regHeight
// Memory size
memWEnd := maxX - 1
memWidth := 45 // (4 octal numbers * 8 columns + 7 spaces in between + 2 on the outside + 1 extra + 3 for address)
memWStart := memWEnd - memWidth
memHEnd := maxY - 1
memHeight := 18 // 16 lines(rows) of 8 locations(cols) gives us a total of 128
memHStart := memHEnd - memHeight
// Auto-register size
autoWEnd := maxX - 1
autoWidth := memWidth
autoWStart := autoWEnd - autoWidth
autoHEnd := memHStart - 1
autoHeight := 4
autoHStart := autoHEnd - autoHeight
// Console size
consoleWStart := regWEnd + 1
consoleWEnd := memWStart - 1
// consoleWidth := consoleWEnd - consoleWStart
consoleHStart := regHStart - 3
consoleHEnd := maxY - 4
// Debug Console
dconsoleWStart := consoleWStart
dconsoleWEnd := consoleWEnd
dconsoleHStart := consoleHEnd + 1
dconsoleHEnd := maxY - 1
// Status text
statusWStart := regWStart
statusWEnd := regWEnd
statusHStart := regHStart - 3
statusHEnd := regHStart - 1
// Title Text
titleWStart := regWStart
titleWEnd := regWEnd
titleHStart := regHStop
titleHEnd := maxY - 1
// Program Counter
if v, err := g.SetView("counter-register", regWStart, regHStart, regWEnd, regHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = " PC "
}
regHStart = regHEnd + 1
regHEnd = regHStart + regHeight
// Instruction Register
if v, err := g.SetView("instruction-register", regWStart, regHStart, regWEnd, regHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = " IR "
}
regHStart = regHEnd + 1
regHEnd = regHStart + regHeight
// Accumulator Register
if v, err := g.SetView("accumulator-register", regWStart, regHStart, regWEnd, regHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = " AC "
}
regHStart = regHEnd + 1
regHEnd = regHStart + regHeight
// Memory Address Register
if v, err := g.SetView("address-register", regWStart, regHStart, regWEnd, regHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = " MA "
}
regHStart = regHEnd + 1
regHEnd = regHStart + regHeight
// Memory Buffer Register
if v, err := g.SetView("buffer-register", regWStart, regHStart, regWEnd, regHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = " MB "
}
regHStart = regHEnd + 1
regHEnd = regHStart + regHeight
// Switch Register
if v, err := g.SetView("switch-register", regWStart, regHStart, regWEnd, regHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = " SR "
}
// Memory Viewer
if v, err := g.SetView("memory", memWStart, memHStart, memWEnd, memHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = " PAGE "
// v.Autoscroll = true
}
// Auto Memory Viewer
if v, err := g.SetView("memory-zero", autoWStart, autoHStart, autoWEnd, autoHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = " ZERO "
// v.Autoscroll = true
}
// Teletype printer + keyboard
if v, err := g.SetView("teletype", consoleWStart, consoleHStart, consoleWEnd, consoleHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = " CONSOLE "
v.Wrap = true
v.Autoscroll = true
}
// Debug command console
if v, err := g.SetView("dbg-console", dconsoleWStart, dconsoleHStart, dconsoleWEnd, dconsoleHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = " DEBUG "
v.Autoscroll = true
}
// Program Title Text
if v, err := g.SetView("title-text", titleWStart, titleHStart, titleWEnd, titleHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.FgColor = gocui.AttrBold
s := "MKSIM"
centerd := fmt.Sprintf("%*s", -regWidth, fmt.Sprintf("%*s", (regWidth+len(s))/2, s))
fmt.Fprint(v, centerd)
}
// Status Text
if v, err := g.SetView("status-text", statusWStart, statusHStart, statusWEnd, statusHEnd); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.FgColor = gocui.AttrBold
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
g.Close()
os.Exit(0)
return gocui.ErrQuit
}
func proceed(g *gocui.Gui, v *gocui.View) error {
lastKey = '\n'
return nil
}
func step(g *gocui.Gui, v *gocui.View) error {
lastKey = ' '
return nil
}
func loadPC(g *gocui.Gui, v *gocui.View) error {
lastKey = 17 // Device Control 1 is Load Program Counter
return nil
}
func switchRegister1(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b100000000000
updateRegister(g, "switch-register", switchRegister)
return nil
}
func switchRegister2(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b010000000000
updateRegister(g, "switch-register", switchRegister)
return nil
}
func switchRegister3(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b001000000000
updateRegister(g, "switch-register", switchRegister)
return nil
}
func switchRegister4(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b000100000000
updateRegister(g, "switch-register", switchRegister)
return nil
}
func switchRegister5(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b000010000000
updateRegister(g, "switch-register", switchRegister)
return nil
}
func switchRegister6(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b000001000000
updateRegister(g, "switch-register", switchRegister)
return nil
}
func switchRegister7(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b000000100000
updateRegister(g, "switch-register", switchRegister)
return nil
}
func switchRegister8(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b000000010000
updateRegister(g, "switch-register", switchRegister)
return nil
}
func switchRegister9(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b000000001000
updateRegister(g, "switch-register", switchRegister)
return nil
}
func switchRegister10(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b000000000100
updateRegister(g, "switch-register", switchRegister)
return nil
}
func switchRegister11(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b000000000010
updateRegister(g, "switch-register", switchRegister)
return nil
}
func switchRegister12(g *gocui.Gui, v *gocui.View) error {
switchRegister ^= 0b000000000001
updateRegister(g, "switch-register", switchRegister)
return nil
}
var b byte
func getLastKey() byte {
if lastKey != 0 {
b = lastKey
lastKey = 0
} else {
b = 0
}
return b
}
func updateRegister(g *gocui.Gui, registerName string, registerVal uint16) {
g.Update(func(g *gocui.Gui) error {
v, err := g.View(registerName)
if err != nil {
return err
}
v.Clear()
fmt.Fprintf(v, " %12.12b ", registerVal)
return nil
})
}
func debugPrint(g *gocui.Gui, msg string) {
g.Update(func(g *gocui.Gui) error {
v, err := g.View("dbg-console")
if err != nil {
return err
}
fmt.Fprint(v, "\n"+msg)
return nil
})
}
func updateStatus(g *gocui.Gui, status string, atr gocui.Attribute) {
regWidth := 15
g.Update(func(g *gocui.Gui) error {
v, err := g.View("status-text")
if err != nil {
return err
}
v.Clear()
v.FgColor = atr
centerd := fmt.Sprintf("%*s", -regWidth, fmt.Sprintf("%*s", (regWidth+len(status))/2, status))
fmt.Fprint(v, centerd)
return nil
})
}
// Updates the memory view
// Takes the mem array and the page to display
func updateMemory(g *gocui.Gui, mem [4096]uint16, page uint16) {
memRow := 0o10
var memStr = fmt.Sprintf("%02o00 0 1 2 3 4 5 6 7\n", page>>6)
memStr += fmt.Sprintf("00 %04o ", mem[page])
for loc := page + 1; loc < page+128; loc++ {
memStr += fmt.Sprintf("%04o ", mem[loc])
if (loc+1)%8 == 0 && loc+1 < page+128 {
memStr += fmt.Sprintf("\n%02.2o ", memRow)
memRow = (memRow + 0o10) % 0o100
}
}
g.Update(func(g *gocui.Gui) error {
v, err := g.View("memory")
if err != nil {
return err
}
v.Clear()
fmt.Fprint(v, memStr)
return nil
})
}
func updateZeroMemory(g *gocui.Gui, mem [4096]uint16) {
var memStr = "0000 0 1 2 3 4 5 6 7\n"
memStr += fmt.Sprintf("00 %04o ", mem[0])
for loc := 1; loc < 16; loc++ {
memStr += fmt.Sprintf("%04o ", mem[loc])
if loc == 7 {
memStr += "\n10 "
}
}
g.Update(func(g *gocui.Gui) error {
v, err := g.View("memory-zero")
if err != nil {
return err
}
v.Clear()
fmt.Fprint(v, memStr)
return nil
})
}
type CursedTeleprinter struct {
g *gocui.Gui
}
// Printer
func (p *CursedTeleprinter) WriteByte(c byte) error {
p.g.Update(func(g *gocui.Gui) error {
v, err := g.View("teletype")
if err != nil {
return err
}
fmt.Fprintf(v, "%c", c)
return nil
})
return nil
}
func (ct *CursedTeleprinter) Flush() error {
return nil
}
func (ct *CursedTeleprinter) Available() int {
return 1
}