summaryrefslogtreecommitdiff
path: root/src/pkg/exp/spacewar/pdp1.go
blob: e3abd6807fde07c73a9762017b18051ef067827e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
// 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
}