DEC’s PDP-8
|
(The above photo and the front-panel photo below are from Paul Pierce’s PDP-8 collection.)
The round gray spots in the middle and the light gray lettering on the right have incandescent
light bulbs behind them. They light up to show the contents of the various hardware
registers, which is useful when single-stepping for debugging purposes. It’s
also fun to write tricky programs that make the lights light up in ways that
humans find
In addition to twelve data switches, called the switch register, there are:
START | Start | Begin program execution |
---|---|---|
LOAD ADD | Load Address | Load program counter from switch register |
DEP | Deposit | Store switch register into memory addressed by program counter |
EXAM | Examine |
Load accumulator from memory addressed by program counter (The contents of the accumulator would be visible on front-panel lights.) |
CONT | Continue | Resume program execution |
STOP | Stop | Halts program execution at the end of the current instruction |
SING STEP | Single Step | When up, causes CONT to execute just one memory cycle |
SING INST | Single Instruction | When up, causes CONT to execute just one instruction |
To the left of the switch register, there are six additional switches,
three for the data field and three for the instruction field, which also
get loaded when the user hits
Given the 12-bit word length, when thinking about the PDP-8, think octal.
I/O Instructions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Operate Microinstructions | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Here’s a cute little PDP-8 program.
Location | Contents | Instruction |
---|---|---|
0004 | 1005 | TAD 5 |
0005 | 3410 | DCA I 10 |
0006 | 5004 | JMP 4 |
0007 | 5404 | JMP I 4 |
0010 | 0011 | (data) |
0011 | 2010 | ISZ 10 |
This program clears all memory in the current instruction field, including itself, to zero.
It’s not an algorithm because it doesn’t halt. Also, it doesn’t work
in general...at one point it tries to execute the undefined 7777 instruction, which
originally was effectively a
Here’s one possible version
Location | Contents | Instruction | Comments |
---|---|---|---|
0000 | 7200 | CLA | Get next character |
0001 | 1410 | TAD I 10 | |
0002 | 7450 | SNA | Null byte? |
0003 | 7402 | HLT | Yes: halt |
0004 | 6046 | TLS | No: print it |
0005 | 6041 | TSF | Wait for printing done |
0006 | 5005 | JMP 5 | |
0007 | 5000 | JMP 0 | Loop |
0010 | 0010 | ||
0011 | 0110 | ’H’ | |
0012 | 0105 | ’E’ | |
0013 | 0114 | ’L’ | |
0014 | 0114 | ’L’ | |
0015 | 0117 | ’O’ | |
0016 | 0054 | ’,’ | |
0017 | 0040 | ’ ’ | |
0020 | 0127 | ’W’ | |
0021 | 0117 | ’O’ | |
0022 | 0122 | ’R’ | |
0023 | 0114 | ’L’ | |
0024 | 0104 | ’D’ | |
0025 | 0056 | ’.’ | |
0026 | 0015 | (CR) |
It takes three character times at 300 baud to physically return the carriage on an ASR33. 8-) |
0027 | 0012 | (LF) | |
0030 | 0377 | (RUBOUT) | |
0031 | 0000 | (NUL) |
An important part of the PDP-8 canon is the program called the RIM loader.
This program, which would be toggled into the computer with the
front-panel switches, loads a program into memory
from a paper tape in “RIM” format
Toggle in the RIM loader, load the BIN loader, load your program. Bootstrap loaders are for sissies. 8-)
12-bit addresses and data would be represented by the 6 LSBs of pairs of
consecutive characters, with addresses identified by a 1 in the next-to-most
significant bit of the first character in the pair. Thus, addresses were pairs
of characters of the form,
Here’s what might be on a paper tape to load location 12348 with the number 55338:
Binary | Octal | Notes |
---|---|---|
01 001 010 | 112 | First byte of address has bit 1 (from left) set |
00 011 100 | 034 | |
11 111 111 | 377 | RUBOUT rubs out a mistake I made |
00 101 101 | 055 | The two bytes of data |
00 011 011 | 033 |
Following is the version for the low-speed paper tape reader attached to the teletype. Like the first program above, it never halts, but just gets stuck in a tight loop when the tape ends. You hit the front panel STOP switch when it’s done.
Location | Contents | Instruction | Comments |
---|---|---|---|
7756 | 6032 | KCC | Clear accumulator and keyboard flag; begin reading next paper tape position |
7757 | 6031 | KSF | Wait for next character |
7760 | 5357 | JMP 7757 | |
7761 | 6036 | KRB | Clear accumulator and read 8 bits; begin reading next paper tape position |
7762 | 7106 | CLL RTL | Shift left 4 bits |
7763 | 7006 | RTL | |
7764 | 7510 | SPA | MSB zero? |
7765 | 5357 | JMP 7757 | No, ignore (probably a RUBOUT) |
7766 | 7006 | RTL |
7 LSBs now in link and upper 6 accumulator bits
(if link set, first 6-bit byte of address, else data) |
7767 | 6031 | KSF | Wait for next byte |
7770 | 5367 | JMP 7767 | |
7771 | 6034 | KRS | Bitwise-or second byte into lower 6 bits |
7772 | 7420 | SNL | Address or data? |
7773 | 3776 | DCA I 7776 | Store data |
7774 | 3376 | DCA 7776 | Store address (if data, new address follows anyway) |
7775 | 5356 | JMP 7756 | |
7776 | ---- | Temp. storage for address | |
7777 | (reserved for BIN loader’s starting address) |
Here’s the high-speed reader version. Note the tricky code at location 7765: since this modifies the saved address, sequences of error-erasing RUBOUTs must begin with addresses. An early version instead had a CLA in location 7755, which was neither necessary (the front-panel START switch clears the accumulator) nor correct (too many RCFs when looping because of a RUBOUT); but this explains why the BIN loader doesn’t use 7755.
Location | Contents | Instruction | Comments |
---|---|---|---|
7756 | 6014 | RCF | Clear high-speed reader flag; begin reading next paper tape position |
7757 | 6011 | RSF | Wait for next character |
7760 | 5357 | JMP 7757 | |
7761 | 6016 | RRB RCF | Read character; clear flag and begin reading next paper tape position |
7762 | 7106 | CLL RTL | Shift left 4 bits |
7763 | 7006 | RTL | |
7764 | 7510 | SPA | MSB zero? |
7765 | 5374 | JMP 7774 |
No, ignore (probably a RUBOUT) Tricky code: since RCF doesn’t clear the AC, we JMP to a DCA and then loop. |
7766 | 7006 | RTL |
7 LSBs now in link and upper 6 accumulator bits
(if link set, first 6-bit byte of address, else data) |
7767 | 6011 | RSF | Wait for next byte |
7770 | 5367 | JMP 7767 | |
7771 | 6016 | RRB RCF | Bitwise-or second byte into lower 6 bits; clear flag and begin reading next paper tape position |
7772 | 7420 | SNL | Address or data? |
7773 | 3776 | DCA I 7776 | Store data |
7774 | 3376 | DCA 7776 | Store address (if data, new address follows anyway) |
7775 | 5357 | JMP 7757 | |
7776 | ---- | Temp. storage for address | |
7777 | (reserved for BIN loader’s starting address) |
The BIN loader is somewhat more interesting.
In addition to the
Folks who like tricky, self-modifying spaghetti code will appreciate some parts of the BIN loader:
/ / Uninitialized temp. storage: / 7612 ---- / RUBOUT flag (always 0 or 7777) 7613 ---- / current CDF instruction 7614 ---- / most recent input char 7615 ---- / checksum 7616 ---- / current address / 7617 through 7625 unused / / The following subroutine reads the first character of a pair. / It ignores anything between pairs of RUBOUTs and handles changing the data field. / / Tricky code: if it reads a header or trailer (2xx), it just returns normally, / which gets to a JMP instruction that just loops right back (header) or jumps to / the halt code (trailer); otherwise, for an address or data byte (the usual case), / it ISZs its return address to get past that JMP. / 7626 ---- / return address 7627 3212 DCA 7612 / AC 0 on entry...reset RUBOUT flag 7630 4260 JMS 7660 / read a char into AC and 7614 7631 1300 TAD 7700 / why add HLT instruction? because it happens to be -376 7632 7750 SPA SNA CLA / was the char 377 (RUBOUT)? 7633 5237 JMP 7637 / no 7634 2212 ISZ 7612 / yes, was RUBOUT flag 7777? 7635 7040 CMA / no, make AC 7777 to store in RUBOUT flag 7636 5227 JMP 7627 / else leave AC 0; and in any event, loop storing RUBOUT flag 7637 1212 TAD 7612 / char was not RUBOUT; get RUBOUT flag 7640 7640 SZA CLA / even number of RUBOUTs? 7641 5230 JMP 7630 / no, ignore until we get another 7642 1214 TAD 7614 / get char 7643 0274 AND 7674 / mask 0300 (two MSBs of char) 7644 1341 TAD 7741 / why add group-2 CLA? because it happens to be -200 7645 7510 SPA / at least 0200? 7646 2226 ISZ 7626 / no, increment return address (can’t skip) 7647 7750 SPA SNA CLA / at least 0300? 7650 5626 JMP I 7626 / no, return / yes, data field request: 7651 1214 TAD 7614 / get char 7652 0256 AND 7656 / mask mem. field bits 7653 1257 TAD 7657 / make new CDF instruction 7654 3213 DCA 7613 / save it for later 7655 5230 JMP 7630 / / Static data: / 7656 0070 / memory field bit mask 7657 6201 CDF 0 / archetypal CDF instruction / / The following subroutine reads the next character from the paper tape / into the accumulator and location 7614. / 7660 ---- / return address 7661 ---- / will become JMP 7662 low-speed or JMP 7670 high-speed 7662 6031 KSF 7663 5262 JMP 7662 / wait for low-speed reader 7664 6036 KRB / read a byte, begin reading next 7665 3214 DCA 7614 / save byte 7666 1214 TAD 7614 / get it back in AC 7667 5660 JMP I 7660 / return 7670 6011 RSF 7671 5270 JMP 7670 / wait for high-speed reader 7672 6016 RRB RCF / read a byte, begin reading next 7673 5265 JMP 7665 / / More data: / 7674 0300 / mask for two MSBs of char / / The end game: / 7675 4343 JMS 7743 / assemble two 6-bit chars 7676 7041 CIA / negate 7677 1215 TAD 7615 / add checksum 7700 7402 HLT / checksum (presumably zero) visible / in front-panel accumulator lights / / Initialization: / 7701 6032 KCC / clear AC, reset LS and HS reader flags, 7702 6014 RCF / and begin reading (don’t know from which yet) 7703 6214 RDF / current data field into AC(6-8) 7704 1257 TAD 7657 / make CDF instruction 7705 3213 DCA 7613 / save it for later 7706 7604 LAS / read front-panel switches into AC 7707 7700 SMA CLA / high-speed reader? 7710 1353 TAD 7753 / yes, add 6 7711 1352 TAD 7752 / add JMP 7662 7712 3261 DCA 7661 / save either JMP 7662 or JMP 7670 7713 4226 JMS 7626 / read 1st char 7714 5313 JMP 7713 / loop until subr. ISZs its return addr. / / Here begins the main loop. The accumulator will be 0, thus initializing / the checksum, when we fall through from the startup code immediately above. / 7715 3215 DCA 7615 / store checksum 7716 1213 TAD 7613 / get CDF instruction 7717 3336 DCA 7736 / save it 7720 1214 TAD 7614 / get input char 7721 3354 DCA 7754 / save it 7722 4260 JMS 7660 / read 2nd char into AC and 7614 7723 3376 DCA 7776 / save it 7724 4226 JMS 7626 / read another 1st char 7725 5275 JMP 7675 / if subr. didn’t ISZ its return addr., goto halt code 7726 4343 JMS 7743 / assemble two 6-bit chars. 7727 7420 SNL / address? 7730 5336 JMP 7736 / no 7731 3216 DCA 7616 / yes, save addr. 7732 1354 TAD 7754 / add 1st char 7733 1376 TAD 7776 / add 2nd char 7734 1215 TAD 7615 / add previous checksum 7735 5315 JMP 7715 / loop 7736 ---- / becomes CDF whatever 7737 3616 DCA I 7616 / store data 7740 2216 ISZ 7616 / next address 7741 7600 CLA / Tricky code: we need a no-op here that the ISZ can skip / if the address we just loaded happens to be 7777. A CLA / will do because it doesn’t do anything that the DCA hasn’t / already done; and a group-2 CLA happens to have the value, / -200, which is used above. 7742 5332 JMP 7732 / go figure new checksum / / The following subroutine assembles two characters into a 12-bit word / with the address indicator bit in the link. / 7743 ---- 7744 1354 TAD 7754 7745 7106 CLL RTL 7746 7006 RTL 7747 7006 RTL 7750 1376 TAD 7776 7751 5743 JMP I 7743 / / More data: / 7752 5262 JMP 7662 / JMP instruction for selecting low-speed reader 7753 0006 / augend for above JMP’s address for high-speed reader 7754 ---- / temp. storage for 1st char / / 7755 through 7775 reserved for RIM loader / 7776 ---- / temp. storage for 2nd char (shared by RIM loader) 7777 5301 JMP 7701
Name | Size (bits) |
Description | Use |
---|---|---|---|
PC | 12 | Program Counter | Holds address of next instruction |
MA | 12 | Memory Address Register | Holds address of memory location currently being accessed |
MB | 12 | Memory Buffer | All data in and out of memory go through this register |
IR | 3 | Instruction Register | Saves instruction opcodes across major states |
DF | 3 | Data Field | Upper three bits of 15-bit address when indirectly accessing data |
IF | 3 | Instruction Field | Upper three bits of 15-bit address when fetching instructions or directly accessing data |
IB | 3 | Instruction Buffer | Holds new IF until next JMP or JMS |
AC | 12 | Accumulator | Where arithmetic happens |
L | 1 | Link | Overflow bit for accumulator |
MQ | 12 | Multiplier/Quotient Register | Optional fast storage for partial products, etc. |
Program Execution |
FETCH | Fetch instruction; execute I/O instructions, microinstructions and JMP direct. |
---|---|---|
DEFER | Get indirect addresses; execute JMP indirect. | |
EXECUTE | Execute memory-reference instructions other than JMP. | |
“Data Break” (DMA) |
WC | Word count |
CA | Current address | |
B | Break (data transfer) |
The Arrow Symbols on the Diagrams | |
---|---|
1 —> flip-flop 0 —> flip-flop |
Set or clear the flip-flop |
0 —> register | Clear the register |
+1 —> register | Increment the contents of the register |
regx —> regy | Bitwise-or regx into regy |
regx -j-> regy | (Actually a solid arrow with an overstruck ‘j’) “Jam transfer” (transfer both 1s and 0s) |
When
one of the switches is pressed, things happen at one of four times,
SP0 through SP3. Everything shown at
a particular time basically begins at the same time and takes
a non-zero amount of time to complete; so,
for example, when we see
Note that most register-to-register transfers are
When a program is running, things happen on each edge of a two-phase clock. The four times, called T1A, T1B, T2A and T2B, correspond to the horizontal dotted lines (or the bottom of the page in the case of T2B) on the flow diagram.
The basic idea for a core memory read-restore cycle (i.e., “major state”):
0200 7001 IAC 0201 1250 TAD 250 0202 7402 HLT ... 0250 0005Here’s some of what happens when we set the switch register to 0200 then hit
Operation | Time | RUN | Major State |
PC | MA | (MA) | MB | IR | AC | Comments |
---|---|---|---|---|---|---|---|---|---|---|
LOAD ADD | SP0 | 0 | - | ---- | ---- | ---- | - | ---- | 0—>RUN | |
SP1 | 0 | - | 0000 | ---- | ---- | - | ---- | 0—>PC | ||
SP2 | 0 | - | 0200 | ---- | ---- | - | ---- | SR—>PC | ||
SP3 | 0 | - | 0200 | ---- | ---- | - | ---- | |||
START | SP0 | 0 | - | 0200 | ---- | ---- | - | ---- | 0—>RUN | |
SP1 | 0 | FETCH | 0200 | 0200 | 7001 | 0000 | 0 | 0000 | 0—>AC; 0—>MB; 0—>IR; 1—>F; PC-j->MA | |
SP2 | 0 | FETCH | 0200 | 0200 | 7001 | 0000 | 0 | 0000 | Get ready for memory cycles | |
SP3 | 1 | FETCH | 0200 | 0200 | 7001 | 0000 | 0 | 0000 | 1—>RUN | |
IAC | T1A | 1 | FETCH | 0200 | 0200 | 7001 | 7001 | 7 | 0000 | (MA)—>MB; MB(0—2)transitions—>IR |
T1B | 1 | FETCH | 0201 | 0200 | 7001 | 7001 | 7 | 0000 | +1—>PC | |
T2A | 1 | FETCH | 0201 | 0201 | 1250 | 7001 | 7 | 0000 | PC-j->MA | |
T2B | 1 | FETCH | 0201 | 0201 | 1250 | 0000 | 0 | 0001 | +1—>AC; 0—>MB; 0—>IR; 1—>F | |
TAD 250 | T1A | 1 | FETCH | 0201 | 0201 | 1250 | 1250 | 1 | 0001 | (MA)—>MB; MB(0—2)transitions—>IR |
T1B | 1 | FETCH | 0202 | 0201 | 1250 | 1250 | 1 | 0001 | +1—>PC | |
T2A | 1 | FETCH | 0202 | 0250 | 0005 | 1250 | 1 | 0001 | MB(5—11)-j->MA | |
T2B | 1 | EXECUTE | 0202 | 0250 | 0005 | 0000 | 1 | 0001 | 0—>MB; 1—>E | |
T1A | 1 | EXECUTE | 0202 | 0250 | 0005 | 0005 | 1 | 0001 | (MA)—>MB; | |
T1B | 1 | EXECUTE | 0202 | 0250 | 0005 | 0005 | 1 | 0004 | MB half add to AC | |
T2A | 1 | EXECUTE | 0202 | 0202 | 7402 | 0005 | 1 | 0004 | PC-j->MA | |
T2B | 1 | FETCH | 0202 | 0202 | 7402 | 0000 | 0 | 0006 | AC carry; 0—>MB; 0—>IR; 1—>F | |
HLT | T1A | 1 | FETCH | 0202 | 0202 | 7402 | 7402 | 7 | 0006 | (MA)—>MB; MB(0—2)transitions—>IR |
T1B | 0 | FETCH | 0203 | 0202 | 7402 | 7402 | 7 | 0006 | +1—>PC; 0—>RUN | |
T2A | 0 | FETCH | 0203 | 0203 | 7402 | 7 | 0006 | PC-j->MA | ||
T2B | 0 | FETCH | 0203 | 0203 | 0000 | 0 | 0006 | 0—>MB; 0—>IR; 1—>F |
For more information, see Chapter 2 of the PDP-8 Maintenance Manual.