| // Copyright (c) 1996 Barry Silverman, Brian Silverman, Vadim Gerasimov. |
| // Portions Copyright (c) 2009 The Go Authors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a copy |
| // of this software and associated documentation files (the "Software"), to deal |
| // in the Software without restriction, including without limitation the rights |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| // copies of the Software, and to permit persons to whom the Software is |
| // furnished to do so, subject to the following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included in |
| // all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| // THE SOFTWARE. |
| |
| // This package and spacewar.go implement a simple PDP-1 emulator |
| // complete enough to run the original PDP-1 video game Spacewar! |
| // See ../../nacl/README for details on running them. |
| // |
| // They are a translation of the Java emulator pdp1.java in |
| // http://spacewar.oversigma.com/sources/sources.zip. |
| // |
| // See also the PDP-1 handbook at http://www.dbit.com/~greeng3/pdp1/pdp1.html |
| // |
| // http://spacewar.oversigma.com/readme.html reads: |
| // |
| // Spacewar! was conceived in 1961 by Martin Graetz, Stephen Russell, |
| // and Wayne Wiitanen. It was first realized on the PDP-1 in 1962 by |
| // Stephen Russell, Peter Samson, Dan Edwards, and Martin Graetz, |
| // together with Alan Kotok, Steve Piner, and Robert A Saunders. |
| // Spacewar! is in the public domain, but this credit paragraph must |
| // accompany all distributed versions of the program. |
| // |
| // This is the original version! Martin Graetz provided us with a |
| // printed version of the source. We typed in in again - it was about |
| // 40 pages long - and re-assembled it with a PDP-1 assembler written |
| // in PERL. The resulting binary runs on a PDP-1 emulator written as |
| // a Java applet. The code is extremely faithful to the original. There |
| // are only two changes. 1)The spaceships have been made bigger and |
| // 2) The overall timing has been special cased to deal with varying |
| // machine speeds. |
| // |
| // The "a", "s", "d", "f" keys control one of the spaceships. The "k", |
| // "l", ";", "'" keys control the other. The controls are spin one |
| // way, spin the other, thrust, and fire. |
| // |
| // Barry Silverman |
| // Brian Silverman |
| // Vadim Gerasimov |
| // |
| package pdp1 |
| |
| import ( |
| "bufio" |
| "fmt" |
| "os" |
| "io" |
| ) |
| |
| type Word uint32 |
| |
| const mask = 0777777 |
| const sign = 0400000 |
| |
| const ( |
| _ = iota // 00 |
| opAND |
| opIOR |
| opXOR |
| opXCT |
| _ |
| _ |
| opCALJDA |
| |
| opLAC // 10 |
| opLIO |
| opDAC |
| opDAP |
| _ |
| opDIO |
| opDZM |
| _ |
| |
| opADD // 20 |
| opSUB |
| opIDX |
| opISP |
| opSAD |
| opSAS |
| opMUS |
| opDIS |
| |
| opJMP // 30 |
| opJSP |
| opSKP |
| opSFT |
| opLAW |
| opIOT |
| _ |
| opOPR |
| ) |
| |
| // A Trapper represents an object with a Trap method. |
| // The machine calls the Trap method to implement the |
| // PDP-1 IOT instruction. |
| type Trapper interface { |
| Trap(y Word) |
| } |
| |
| // An M represents the machine state of a PDP-1. |
| // Clients can set Display to install an output device. |
| type M struct { |
| AC, IO, PC, OV Word |
| Mem [010000]Word |
| Flag [7]bool |
| Sense [7]bool |
| Halt bool |
| } |
| |
| |
| // Step runs a single machine instruction. |
| func (m *M) Step(t Trapper) os.Error { |
| inst := m.Mem[m.PC] |
| m.PC++ |
| return m.run(inst, t) |
| } |
| |
| // Normalize actual 32-bit integer i to 18-bit ones-complement integer. |
| // Interpret mod 0777777, because 0777777 == -0 == +0 == 0000000. |
| func norm(i Word) Word { |
| i += i >> 18 |
| i &= mask |
| if i == mask { |
| i = 0 |
| } |
| return i |
| } |
| |
| type UnknownInstrError struct { |
| Inst Word |
| PC Word |
| } |
| |
| func (e UnknownInstrError) String() string { |
| return fmt.Sprintf("unknown instruction %06o at %06o", e.Inst, e.PC) |
| } |
| |
| type HaltError Word |
| |
| func (e HaltError) String() string { |
| return fmt.Sprintf("executed HLT instruction at %06o", e) |
| } |
| |
| type LoopError Word |
| |
| func (e LoopError) String() string { return fmt.Sprintf("indirect load looping at %06o", e) } |
| |
| func (m *M) run(inst Word, t Trapper) os.Error { |
| ib, y := (inst>>12)&1, inst&07777 |
| op := inst >> 13 |
| if op < opSKP && op != opCALJDA { |
| for n := 0; ib != 0; n++ { |
| if n > 07777 { |
| return LoopError(m.PC - 1) |
| } |
| ib = (m.Mem[y] >> 12) & 1 |
| y = m.Mem[y] & 07777 |
| } |
| } |
| |
| switch op { |
| case opAND: |
| m.AC &= m.Mem[y] |
| case opIOR: |
| m.AC |= m.Mem[y] |
| case opXOR: |
| m.AC ^= m.Mem[y] |
| case opXCT: |
| m.run(m.Mem[y], t) |
| case opCALJDA: |
| a := y |
| if ib == 0 { |
| a = 64 |
| } |
| m.Mem[a] = m.AC |
| m.AC = (m.OV << 17) + m.PC |
| m.PC = a + 1 |
| case opLAC: |
| m.AC = m.Mem[y] |
| case opLIO: |
| m.IO = m.Mem[y] |
| case opDAC: |
| m.Mem[y] = m.AC |
| case opDAP: |
| m.Mem[y] = m.Mem[y]&0770000 | m.AC&07777 |
| case opDIO: |
| m.Mem[y] = m.IO |
| case opDZM: |
| m.Mem[y] = 0 |
| case opADD: |
| m.AC += m.Mem[y] |
| m.OV = m.AC >> 18 |
| m.AC = norm(m.AC) |
| case opSUB: |
| diffSigns := (m.AC^m.Mem[y])>>17 == 1 |
| m.AC += m.Mem[y] ^ mask |
| m.AC = norm(m.AC) |
| if diffSigns && m.Mem[y]>>17 == m.AC>>17 { |
| m.OV = 1 |
| } |
| case opIDX: |
| m.AC = norm(m.Mem[y] + 1) |
| m.Mem[y] = m.AC |
| case opISP: |
| m.AC = norm(m.Mem[y] + 1) |
| m.Mem[y] = m.AC |
| if m.AC&sign == 0 { |
| m.PC++ |
| } |
| case opSAD: |
| if m.AC != m.Mem[y] { |
| m.PC++ |
| } |
| case opSAS: |
| if m.AC == m.Mem[y] { |
| m.PC++ |
| } |
| case opMUS: |
| if m.IO&1 == 1 { |
| m.AC += m.Mem[y] |
| m.AC = norm(m.AC) |
| } |
| m.IO = (m.IO>>1 | m.AC<<17) & mask |
| m.AC >>= 1 |
| case opDIS: |
| m.AC, m.IO = (m.AC<<1|m.IO>>17)&mask, |
| ((m.IO<<1|m.AC>>17)&mask)^1 |
| if m.IO&1 == 1 { |
| m.AC = m.AC + (m.Mem[y] ^ mask) |
| } else { |
| m.AC = m.AC + 1 + m.Mem[y] |
| } |
| m.AC = norm(m.AC) |
| case opJMP: |
| m.PC = y |
| case opJSP: |
| m.AC = (m.OV << 17) + m.PC |
| m.PC = y |
| case opSKP: |
| cond := y&0100 == 0100 && m.AC == 0 || |
| y&0200 == 0200 && m.AC>>17 == 0 || |
| y&0400 == 0400 && m.AC>>17 == 1 || |
| y&01000 == 01000 && m.OV == 0 || |
| y&02000 == 02000 && m.IO>>17 == 0 || |
| y&7 != 0 && !m.Flag[y&7] || |
| y&070 != 0 && !m.Sense[(y&070)>>3] || |
| y&070 == 010 |
| if (ib == 0) == cond { |
| m.PC++ |
| } |
| if y&01000 == 01000 { |
| m.OV = 0 |
| } |
| case opSFT: |
| for count := inst & 0777; count != 0; count >>= 1 { |
| if count&1 == 0 { |
| continue |
| } |
| switch (inst >> 9) & 017 { |
| case 001: // rotate AC left |
| m.AC = (m.AC<<1 | m.AC>>17) & mask |
| case 002: // rotate IO left |
| m.IO = (m.IO<<1 | m.IO>>17) & mask |
| case 003: // rotate AC and IO left. |
| w := uint64(m.AC)<<18 | uint64(m.IO) |
| w = w<<1 | w>>35 |
| m.AC = Word(w>>18) & mask |
| m.IO = Word(w) & mask |
| case 005: // shift AC left (excluding sign bit) |
| m.AC = (m.AC<<1|m.AC>>17)&mask&^sign | m.AC&sign |
| case 006: // shift IO left (excluding sign bit) |
| m.IO = (m.IO<<1|m.IO>>17)&mask&^sign | m.IO&sign |
| case 007: // shift AC and IO left (excluding AC's sign bit) |
| w := uint64(m.AC)<<18 | uint64(m.IO) |
| w = w<<1 | w>>35 |
| m.AC = Word(w>>18)&mask&^sign | m.AC&sign |
| m.IO = Word(w)&mask&^sign | m.AC&sign |
| case 011: // rotate AC right |
| m.AC = (m.AC>>1 | m.AC<<17) & mask |
| case 012: // rotate IO right |
| m.IO = (m.IO>>1 | m.IO<<17) & mask |
| case 013: // rotate AC and IO right |
| w := uint64(m.AC)<<18 | uint64(m.IO) |
| w = w>>1 | w<<35 |
| m.AC = Word(w>>18) & mask |
| m.IO = Word(w) & mask |
| case 015: // shift AC right (excluding sign bit) |
| m.AC = m.AC>>1 | m.AC&sign |
| case 016: // shift IO right (excluding sign bit) |
| m.IO = m.IO>>1 | m.IO&sign |
| case 017: // shift AC and IO right (excluding AC's sign bit) |
| w := uint64(m.AC)<<18 | uint64(m.IO) |
| w = w >> 1 |
| m.AC = Word(w>>18) | m.AC&sign |
| m.IO = Word(w) & mask |
| default: |
| goto Unknown |
| } |
| } |
| case opLAW: |
| if ib == 0 { |
| m.AC = y |
| } else { |
| m.AC = y ^ mask |
| } |
| case opIOT: |
| t.Trap(y) |
| case opOPR: |
| if y&0200 == 0200 { |
| m.AC = 0 |
| } |
| if y&04000 == 04000 { |
| m.IO = 0 |
| } |
| if y&01000 == 01000 { |
| m.AC ^= mask |
| } |
| if y&0400 == 0400 { |
| m.PC-- |
| return HaltError(m.PC) |
| } |
| switch i, f := y&7, y&010 == 010; { |
| case i == 7: |
| for i := 2; i < 7; i++ { |
| m.Flag[i] = f |
| } |
| case i >= 2: |
| m.Flag[i] = f |
| } |
| default: |
| Unknown: |
| return UnknownInstrError{inst, m.PC - 1} |
| } |
| return nil |
| } |
| |
| // Load loads the machine's memory from a text input file |
| // listing octal address-value pairs, one per line, matching the |
| // regular expression ^[ +]([0-7]+)\t([0-7]+). |
| func (m *M) Load(r io.Reader) os.Error { |
| b := bufio.NewReader(r) |
| for { |
| line, err := b.ReadString('\n') |
| if err != nil { |
| if err != os.EOF { |
| return err |
| } |
| break |
| } |
| // look for ^[ +]([0-9]+)\t([0-9]+) |
| if line[0] != ' ' && line[0] != '+' { |
| continue |
| } |
| i := 1 |
| a := Word(0) |
| for ; i < len(line) && '0' <= line[i] && line[i] <= '7'; i++ { |
| a = a*8 + Word(line[i]-'0') |
| } |
| if i >= len(line) || line[i] != '\t' || i == 1 { |
| continue |
| } |
| v := Word(0) |
| j := i |
| for i++; i < len(line) && '0' <= line[i] && line[i] <= '7'; i++ { |
| v = v*8 + Word(line[i]-'0') |
| } |
| if i == j { |
| continue |
| } |
| m.Mem[a] = v |
| } |
| return nil |
| } |