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 }