9fans.net/go@v0.0.5/games/spacewar/pdp1/pdp1.go (about)

     1  // Copyright (c) 1996 Barry Silverman, Brian Silverman, Vadim Gerasimov.
     2  // Portions Copyright (c) 2009 The Go Authors.
     3  //
     4  // Permission is hereby granted, free of charge, to any person obtaining a copy
     5  // of this software and associated documentation files (the "Software"), to deal
     6  // in the Software without restriction, including without limitation the rights
     7  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8  // copies of the Software, and to permit persons to whom the Software is
     9  // furnished to do so, subject to the following conditions:
    10  //
    11  // The above copyright notice and this permission notice shall be included in
    12  // all copies or substantial portions of the Software.
    13  //
    14  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    20  // THE SOFTWARE.
    21  
    22  // This package and spacewar.go implement a simple PDP-1 emulator
    23  // complete enough to run the original PDP-1 video game Spacewar!
    24  //
    25  // They are a translation of the Java emulator pdp1.java in
    26  // http://spacewar.oversigma.com/sources/sources.zip.
    27  //
    28  // See also the PDP-1 handbook at http://www.dbit.com/~greeng3/pdp1/pdp1.html
    29  //
    30  // http://spacewar.oversigma.com/readme.html reads:
    31  //
    32  //	Spacewar! was conceived in 1961 by Martin Graetz, Stephen Russell,
    33  //	and Wayne Wiitanen. It was first realized on the PDP-1 in 1962 by
    34  //	Stephen Russell, Peter Samson, Dan Edwards, and Martin Graetz,
    35  //	together with Alan Kotok, Steve Piner, and Robert A Saunders.
    36  //	Spacewar! is in the public domain, but this credit paragraph must
    37  //	accompany all distributed versions of the program.
    38  //
    39  //	This is the original version! Martin Graetz provided us with a
    40  //	printed version of the source. We typed in in again - it was about
    41  //	40 pages long - and re-assembled it with a PDP-1 assembler written
    42  //	in PERL. The resulting binary runs on a PDP-1 emulator written as
    43  //	a Java applet. The code is extremely faithful to the original. There
    44  //	are only two changes. 1)The spaceships have been made bigger and
    45  //	2) The overall timing has been special cased to deal with varying
    46  //	machine speeds.
    47  //
    48  //	The "a", "s", "d", "f" keys control one of the spaceships. The "k",
    49  //	"l", ";", "'" keys control the other. The controls are spin one
    50  //	way, spin the other, thrust, and fire.
    51  //
    52  //	Barry Silverman
    53  //	Brian Silverman
    54  //	Vadim Gerasimov
    55  //
    56  package pdp1 // import "9fans.net/go/games/spacewar/pdp1"
    57  
    58  import (
    59  	"bufio"
    60  	"fmt"
    61  	"io"
    62  )
    63  
    64  type Word uint32
    65  
    66  const mask = 0777777
    67  const sign = 0400000
    68  
    69  const (
    70  	_ = iota // 00
    71  	opAND
    72  	opIOR
    73  	opXOR
    74  	opXCT
    75  	_
    76  	_
    77  	opCALJDA
    78  
    79  	opLAC // 10
    80  	opLIO
    81  	opDAC
    82  	opDAP
    83  	_
    84  	opDIO
    85  	opDZM
    86  	_
    87  
    88  	opADD // 20
    89  	opSUB
    90  	opIDX
    91  	opISP
    92  	opSAD
    93  	opSAS
    94  	opMUS
    95  	opDIS
    96  
    97  	opJMP // 30
    98  	opJSP
    99  	opSKP
   100  	opSFT
   101  	opLAW
   102  	opIOT
   103  	_
   104  	opOPR
   105  )
   106  
   107  // A Trapper represents an object with a Trap method.
   108  // The machine calls the Trap method to implement the
   109  // PDP-1 IOT instruction.
   110  type Trapper interface {
   111  	Trap(y Word)
   112  }
   113  
   114  // An M represents the machine state of a PDP-1.
   115  // Clients can set Display to install an output device.
   116  type M struct {
   117  	AC, IO, PC, OV Word
   118  	Mem            [010000]Word
   119  	Flag           [7]bool
   120  	Sense          [7]bool
   121  	Halt           bool
   122  }
   123  
   124  // Step runs a single machine instruction.
   125  func (m *M) Step(t Trapper) error {
   126  	inst := m.Mem[m.PC]
   127  	m.PC++
   128  	return m.run(inst, t)
   129  }
   130  
   131  // Normalize actual 32-bit integer i to 18-bit ones-complement integer.
   132  // Interpret mod 0777777, because 0777777 == -0 == +0 == 0000000.
   133  func norm(i Word) Word {
   134  	i += i >> 18
   135  	i &= mask
   136  	if i == mask {
   137  		i = 0
   138  	}
   139  	return i
   140  }
   141  
   142  type UnknownInstrError struct {
   143  	Inst Word
   144  	PC   Word
   145  }
   146  
   147  func (e UnknownInstrError) Error() string {
   148  	return fmt.Sprintf("unknown instruction %06o at %06o", e.Inst, e.PC)
   149  }
   150  
   151  type HaltError Word
   152  
   153  func (e HaltError) Error() string {
   154  	return fmt.Sprintf("executed HLT instruction at %06o", e)
   155  }
   156  
   157  type LoopError Word
   158  
   159  func (e LoopError) Error() string {
   160  	return fmt.Sprintf("indirect load looping at %06o", e)
   161  }
   162  
   163  func (m *M) run(inst Word, t Trapper) error {
   164  	ib, y := (inst>>12)&1, inst&07777
   165  	op := inst >> 13
   166  	if op < opSKP && op != opCALJDA {
   167  		for n := 0; ib != 0; n++ {
   168  			if n > 07777 {
   169  				return LoopError(m.PC - 1)
   170  			}
   171  			ib = (m.Mem[y] >> 12) & 1
   172  			y = m.Mem[y] & 07777
   173  		}
   174  	}
   175  
   176  	switch op {
   177  	case opAND:
   178  		m.AC &= m.Mem[y]
   179  	case opIOR:
   180  		m.AC |= m.Mem[y]
   181  	case opXOR:
   182  		m.AC ^= m.Mem[y]
   183  	case opXCT:
   184  		m.run(m.Mem[y], t)
   185  	case opCALJDA:
   186  		a := y
   187  		if ib == 0 {
   188  			a = 64
   189  		}
   190  		m.Mem[a] = m.AC
   191  		m.AC = (m.OV << 17) + m.PC
   192  		m.PC = a + 1
   193  	case opLAC:
   194  		m.AC = m.Mem[y]
   195  	case opLIO:
   196  		m.IO = m.Mem[y]
   197  	case opDAC:
   198  		m.Mem[y] = m.AC
   199  	case opDAP:
   200  		m.Mem[y] = m.Mem[y]&0770000 | m.AC&07777
   201  	case opDIO:
   202  		m.Mem[y] = m.IO
   203  	case opDZM:
   204  		m.Mem[y] = 0
   205  	case opADD:
   206  		m.AC += m.Mem[y]
   207  		m.OV = m.AC >> 18
   208  		m.AC = norm(m.AC)
   209  	case opSUB:
   210  		diffSigns := (m.AC^m.Mem[y])>>17 == 1
   211  		m.AC += m.Mem[y] ^ mask
   212  		m.AC = norm(m.AC)
   213  		if diffSigns && m.Mem[y]>>17 == m.AC>>17 {
   214  			m.OV = 1
   215  		}
   216  	case opIDX:
   217  		m.AC = norm(m.Mem[y] + 1)
   218  		m.Mem[y] = m.AC
   219  	case opISP:
   220  		m.AC = norm(m.Mem[y] + 1)
   221  		m.Mem[y] = m.AC
   222  		if m.AC&sign == 0 {
   223  			m.PC++
   224  		}
   225  	case opSAD:
   226  		if m.AC != m.Mem[y] {
   227  			m.PC++
   228  		}
   229  	case opSAS:
   230  		if m.AC == m.Mem[y] {
   231  			m.PC++
   232  		}
   233  	case opMUS:
   234  		if m.IO&1 == 1 {
   235  			m.AC += m.Mem[y]
   236  			m.AC = norm(m.AC)
   237  		}
   238  		m.IO = (m.IO>>1 | m.AC<<17) & mask
   239  		m.AC >>= 1
   240  	case opDIS:
   241  		m.AC, m.IO = (m.AC<<1|m.IO>>17)&mask,
   242  			((m.IO<<1|m.AC>>17)&mask)^1
   243  		if m.IO&1 == 1 {
   244  			m.AC = m.AC + (m.Mem[y] ^ mask)
   245  		} else {
   246  			m.AC = m.AC + 1 + m.Mem[y]
   247  		}
   248  		m.AC = norm(m.AC)
   249  	case opJMP:
   250  		m.PC = y
   251  	case opJSP:
   252  		m.AC = (m.OV << 17) + m.PC
   253  		m.PC = y
   254  	case opSKP:
   255  		cond := y&0100 == 0100 && m.AC == 0 ||
   256  			y&0200 == 0200 && m.AC>>17 == 0 ||
   257  			y&0400 == 0400 && m.AC>>17 == 1 ||
   258  			y&01000 == 01000 && m.OV == 0 ||
   259  			y&02000 == 02000 && m.IO>>17 == 0 ||
   260  			y&7 != 0 && !m.Flag[y&7] ||
   261  			y&070 != 0 && !m.Sense[(y&070)>>3] ||
   262  			y&070 == 010
   263  		if (ib == 0) == cond {
   264  			m.PC++
   265  		}
   266  		if y&01000 == 01000 {
   267  			m.OV = 0
   268  		}
   269  	case opSFT:
   270  		for count := inst & 0777; count != 0; count >>= 1 {
   271  			if count&1 == 0 {
   272  				continue
   273  			}
   274  			switch (inst >> 9) & 017 {
   275  			case 001: // rotate AC left
   276  				m.AC = (m.AC<<1 | m.AC>>17) & mask
   277  			case 002: // rotate IO left
   278  				m.IO = (m.IO<<1 | m.IO>>17) & mask
   279  			case 003: // rotate AC and IO left.
   280  				w := uint64(m.AC)<<18 | uint64(m.IO)
   281  				w = w<<1 | w>>35
   282  				m.AC = Word(w>>18) & mask
   283  				m.IO = Word(w) & mask
   284  			case 005: // shift AC left (excluding sign bit)
   285  				m.AC = (m.AC<<1|m.AC>>17)&mask&^sign | m.AC&sign
   286  			case 006: // shift IO left (excluding sign bit)
   287  				m.IO = (m.IO<<1|m.IO>>17)&mask&^sign | m.IO&sign
   288  			case 007: // shift AC and IO left (excluding AC's sign bit)
   289  				w := uint64(m.AC)<<18 | uint64(m.IO)
   290  				w = w<<1 | w>>35
   291  				m.AC = Word(w>>18)&mask&^sign | m.AC&sign
   292  				m.IO = Word(w)&mask&^sign | m.AC&sign
   293  			case 011: // rotate AC right
   294  				m.AC = (m.AC>>1 | m.AC<<17) & mask
   295  			case 012: // rotate IO right
   296  				m.IO = (m.IO>>1 | m.IO<<17) & mask
   297  			case 013: // rotate AC and IO right
   298  				w := uint64(m.AC)<<18 | uint64(m.IO)
   299  				w = w>>1 | w<<35
   300  				m.AC = Word(w>>18) & mask
   301  				m.IO = Word(w) & mask
   302  			case 015: // shift AC right (excluding sign bit)
   303  				m.AC = m.AC>>1 | m.AC&sign
   304  			case 016: // shift IO right (excluding sign bit)
   305  				m.IO = m.IO>>1 | m.IO&sign
   306  			case 017: // shift AC and IO right (excluding AC's sign bit)
   307  				w := uint64(m.AC)<<18 | uint64(m.IO)
   308  				w = w >> 1
   309  				m.AC = Word(w>>18) | m.AC&sign
   310  				m.IO = Word(w) & mask
   311  			default:
   312  				return UnknownInstrError{inst, m.PC - 1}
   313  			}
   314  		}
   315  	case opLAW:
   316  		if ib == 0 {
   317  			m.AC = y
   318  		} else {
   319  			m.AC = y ^ mask
   320  		}
   321  	case opIOT:
   322  		t.Trap(y)
   323  	case opOPR:
   324  		if y&0200 == 0200 {
   325  			m.AC = 0
   326  		}
   327  		if y&04000 == 04000 {
   328  			m.IO = 0
   329  		}
   330  		if y&01000 == 01000 {
   331  			m.AC ^= mask
   332  		}
   333  		if y&0400 == 0400 {
   334  			m.PC--
   335  			return HaltError(m.PC)
   336  		}
   337  		switch i, f := y&7, y&010 == 010; {
   338  		case i == 7:
   339  			for i := 2; i < 7; i++ {
   340  				m.Flag[i] = f
   341  			}
   342  		case i >= 2:
   343  			m.Flag[i] = f
   344  		}
   345  	default:
   346  		return UnknownInstrError{inst, m.PC - 1}
   347  	}
   348  	return nil
   349  }
   350  
   351  // Load loads the machine's memory from a text input file
   352  // listing octal address-value pairs, one per line, matching the
   353  // regular expression ^[ +]([0-7]+)\t([0-7]+).
   354  func (m *M) Load(r io.Reader) error {
   355  	b := bufio.NewReader(r)
   356  	for {
   357  		line, err := b.ReadString('\n')
   358  		if err != nil {
   359  			if err != io.EOF {
   360  				return err
   361  			}
   362  			break
   363  		}
   364  		// look for ^[ +]([0-9]+)\t([0-9]+)
   365  		if line[0] != ' ' && line[0] != '+' {
   366  			continue
   367  		}
   368  		i := 1
   369  		a := Word(0)
   370  		for ; i < len(line) && '0' <= line[i] && line[i] <= '7'; i++ {
   371  			a = a*8 + Word(line[i]-'0')
   372  		}
   373  		if i >= len(line) || line[i] != '\t' || i == 1 {
   374  			continue
   375  		}
   376  		v := Word(0)
   377  		j := i
   378  		for i++; i < len(line) && '0' <= line[i] && line[i] <= '7'; i++ {
   379  			v = v*8 + Word(line[i]-'0')
   380  		}
   381  		if i == j {
   382  			continue
   383  		}
   384  		m.Mem[a] = v
   385  	}
   386  	return nil
   387  }