A bit of an investigation - which PDP-7, exactly, was the one which first ran Unix, and why was that such a limitation:
Interesting article. In my study of the B programming language I’ve been reading a lot about the PDP-7 and PDP-7 UNIX lately. I find it thoroughly amazing that something we would recognize as UNIX fit onto such a simple machine with only 8 KW of RAM and still had room to run a user program! The instruction set of the -7 makes a 6502 look terribly bloated.
The PDP-7 is an 18-bits word machine and its instruction set is still much like the one of the PDP-1D (the timesharing version of the PDP-1).
*) Additionally to the PDP-1’s instruction set it includes two’s complement add (but apparently no subtraction, while there is multiplication and division, even with a few flavors) and a “link” carry flag.
(Personal note: I really like the concise beauty of the PDP-1’s instruction set – and, at first glance, the PDP-7’s one feels already a bit bloated or even compromised in comparison.)
For more information (including a list of all PDP-7s by serial number) and links to various related DEC material and manuals see: The Digital Equipment Corporation PDP–7 Computer
A small assortment of links on Bitsavers (all PDFs):
Sales brochure (including a comprehensive list of the instruction set): http://www.bitsavers.org/pdf/dec/pdp7/PDP-7_Brochure.pdf
User handbook: http://www.bitsavers.org/pdf/dec/pdp7/F-75_PDP-7userHbk_Jun65.pdf
Operating manual: http://www.bitsavers.org/pdf/dec/pdp7/DECSYS-7_OperMan.pdf
There is a Two’s Complement Add (TAD) that will effectively subtract.
I believe the multiply and divide were optional as part of the Extended Arithmetic Element (EAE) option.
As part of “decompiling” the B threaded code interpreter I’ve been digging deep into PDP-7 assembly language
(Continuing on the PDP-1/PDP-7 comparison)
However, there seems to be no two’s complement instruction (cma
seems to be still one’s complement = all bits flipped)*. There seem to be changes to the shift/rotate instructions as well: On the PDP-1, these were barrel shifters (up to 9 bit positions encoded directly in the instruction, whereas the PDP-7 requires a dedicated setup instruction for this) and rcl
/rcr
was a combined registers 36-bit shift/rotate instruction. The PDP-7 doesn’t feature a user addressable IO register, but there’s now MQ for multiplication and division, and “long shifts” and “normalize”. Moreover, there’s a real time clock (which is, I guess, some improvement for a real-time computer).
Other notable differences (after skimming the handbook): There’s now a dedicated integer version for multiplication and division (previously this was accomplished by adjusting to the right and padding). Display output is now also analog (on the PDP-1 this was purely digital) and there’s now a vector mode (this is much like the later PDP-12, etc).
Regarding multiply/divide: This is interesting, since the “automatic multiply/divide” was originally an option on the PDP-1, but soon became standard, while it became optional again on the PDP-8 (which uses quite the same EAE/MQ arrangement). It really illustrates quite well the transition.
(If multiply/divide was optional, this means that barrel shifts and 36-bit shifts were also optional, as they required the EAE/MQ circuitry.)
Increasingly interesting, especially with a look at C: The conditional index and skip instruction was changed. (This is the basic building block for count-up loops, combining an increment with a conditional skip of the next instruction.) On the PDP-1, this was isp
, “index and skip if positive” (which includes zero), on the PDP-7 it’s restricted to isz
, “index and skip if zero”. I’m not sure, if there’s much practical consequence to this, but this introduces a potential error when a memory register is incremented more than once in a loop, since we’re now testing for exactly zero instead of general overflow.
Edit: On second glance, this provides for long, 36-bit loop indices (previously, it was just 131071 iterations at max before an overflow to zero/positive.) This is an essential improvement.
*) According to the handbook, a two’s complement subtraction is actually 4 instructions and a constant:
ONE, 01 /constant (1)
lac B /load subtrahend
cma /1's complement
tad ONE /add 1, now 2's complement
tad A /-B + A => AC
(Note: I’m maintaing a PDP-1 emulation, Spacewar! and did some research on the game in question, Inside Spacewar! — A Software Archeological Approach, and eventually wrote my own video game for the PDP-1, Ironic Computer Space Simulator (for the DEC PDP-1, 2016) – see also Retrochallenge 2016/10: Ironic Computer Space Simulator … and I really enjoyed working with this instruction set. )
You are correct, as far as I know. However, the LAW (Load Accumulator With) instruction was used as something of a trick to subtract a constant. The opcode for LAW is:
76xxxx = 111 11x xxx xxx xxx xxx
The entire binary representation of that instruction is loaded into the accumulator. So if the instruction was, say,
111 111 111 111 111 111
which is a valid LAW instruction, the accumulator would then hold:
AC = 111 111 111 111 111 111
Which is -1 in two’s complement.
Follow that with
TAD B
and AC would then hold B-1
Any negative constant up to -8192 (I think, it’s late) could be “added” to AC to perform an effective subtraction.
That trick was used a lot in the early UNIX code.
I should probably mention that the PDP-7 UNIX, at least in the B interpreter, used a lot of self-modifying code. The most common use I have seen was jump tables: an index loaded into the accumulator was added to a base address. The resulting address held the address to jump to (typically a routine). The routine address was added to a jump instruction, that was then deposited into a memory location and executed to jump to the indexed entry.
Regarding 111 111 111 111 111 111 – you could also combine cla + cma - opr
(clear AC + complement AC - 1x the group code) in a single micro coded instruction – I see, on the PDP-7 there’s actually the mnemonic clc
for this.
Regarding self-modification: this is mainly due to the lack of a stack, so a return address has to be passed in the accumulator, but it’s also of use for dispatch tables, etc. A nice trick is to have a variable in an operand and refer to this by indirect addressing in some other bit of code as a pointer. *
The PDP-1 had some extra instructions for things like this, which were essentially an assembler in hardware (these seem to be missing in the PDP-7):
dac
… general store instr., deposit AC in memory register ( C(AC) → C(Y) )
dap
… deposit AC in address part ( C(AC6…17) → C(Y6…17) )
dip
… deposit AC in instruction part ( C(AC0…5) → C(Y0…5) )
and the same for the IO register.
So you could do things like this:
law LBL /load address of a label into AC
adc OFS /add an offset
dap .+1 /deposit in address part of next instruction (. = current addr.)
jmp /address fixed up by previous instr.
This was also essential for subroutines, where the return address was passed in AC and then stored by the subroutine in a jmp
instruction to accomplish the return.
Is there an equivalent on the PDP-7, or do you add the instruction part as a constant to the address?
*) Nifty pointer example:
/an instruction adding a value to AC
LB1, add TBL /operand is contents of addr. labeled TBL
/some other passage in the code using indirect addressing
sub i LB1 /use the same value as above for a subtraction
/another bit of code, advancing the pointer
idx LB1 /increment instr. at LB1 (essentially increment the operand)
/reset the pointer to start of the table
law TBL
dap LB1
/a table of values
TBL, 1
73
14
...
P.S.: For a comprehensive overview and comparison of the PDP-1 instruction set see:
This is a rather strange coincidence:
On the PDP-1 the defer bit (or i-bit, bit 5 counting from the left) was used for loading negative values. So LAW N actually loads an unsigned 12-bit value N, however, if the i-bit is set, it loads the 1’s complement (LAW -N). So the trick would be loading the 1’s complement of zero as in LAW i 0
or just LAW i
.
On the PDP-7 (which apparently lacks the LAW-N instruction), this is quite similar, but for entirely different reasons. Quote from the handbook:
To initialze a core memory location with a negative number, where the complete word (bits 0-17) is to be regarded, it is necessary to take the 1’s complement of the number and then subtract the octal code 760000. [The opcode of LAW; N.L.] For example, if the desired count is 755, memory location Y is loaded with -755 as follows. The 1’s complement of 000755 is 777022, which can be represented as the sum of 760000 and 17023. Since 760000 is the operation code for LAW, the resulting program sequence is used:
LAW 17023
DAC YIn actual practice this operation is seldom used, since the PDP-7 Symbolic Assembler has defined the special character LAM to load negative numbers for counting or masking purposes. The character LAM is a special case of the LAW instruction, equal to LAW 17777.
Help!
So loading the number -N (in 1’s complement) is apparently equivalent to
LAW 17777 + N
if N is zero, we load -0, which is -1 in 2’s complement.
(LAW 17777 = 0760000 + 017777 = 0777777 = %111 111 111 111 111 111 – an instruction which loads itself! I guess, you could as well just use “-0” in assembler. Take that, Gödel! – By this we can prove that a PDP-7 program is consistent and free of errors as long as it consists of “load -0” instructions. )
Fascinating comparison, and a glimpse into the evolution of instruction sets. For a first CPU design from any given team, the dominant thoughts must have been ‘can we do this?’ and ‘will it be useful?’ - and then comes experience, and the chance to see what’s missing, what’s awkward, what’s slow.
It would be interesting to know how much study the Digital team made of existing machines as they converged on the PDP-1. I think it’s relatively common for the earliest machines to lack indexing and to use self-modifying code instead. And, lacking indexing, also lacking a stack, and so needing to wait for David Wheeler to invent his Wheeler Jump (only needed to wait until 1951, fortunately.)
As the first programmer for EDSAC, Wheeler invented ways of working which have now become standard. He realised that lines of program code could often be reused, and created the subroutine and the idea of keeping frequently-needed subroutines in a separate library which could be called on as necessary. He also developed the “Wheeler Jump” to allow a program to pass control to a subroutine, the precursor of the “goto” statement known to everyone who has ever written a program in Basic.
(IEEE, via Wikipedia)
Here’s a blog post on the subject of this thread and the thread itself (a bit of advertising for the forum):