4 ms·
One of the best resources I've found for the PDP-8: https://homepage.cs.uiowa.edu/~jones/pdp8/man/index.html https://homepage.cs.uiowa.edu/~jones/pdp8/man/index
by shadowofneptune 4y ago
One of the best resources I've found for the PDP-8: https://homepage.cs.uiowa.edu/~jones/pdp8/man/index.html https://homepage.cs.uiowa.edu/~jones/pdp8/man/index.html
To give an idea of why this is so hard, an PDP-8 without any add-ons has just two registers: a program counter and an accumulator. Similar to the MOS 6502, you instead used the first part of memory, the 'zero page,' as a register file. Self-modifying code was used to do subroutines, as was rather common at the time. You'd load the return address into the accumulator, call the subroutine, and then the subroutine would store the return address for later inside of its own code. A stock PDP-8 also had only 4096 12-bit words to work with at most.
The PDP-8 was envisioned more as a piece of lab equipment, not as a general-purpose computer. That an operating system could run on it astonished its creators.
- kragen 4y agoIt's not so much that running C on the PDP-8 is hard as that it involves unappetizing compromises. 12-bit arithmetic is plenty for many purposes, for example, but as the page says, C insists that int should be at least 16 bits. Making a subroutine recursive on the PDP-8 is very expensive, but C subroutines are by default capable of recursion. The degree of transistor parsimony is astounding; you could sort of do position-independent code by making it "PC-relative", but that replaced the low 7 bits of the PC with the operand field rather than adding them to it. Probably more ergonomic in a way, but it also saved a 12-bit adder! The Intersil 6100 implemented a full PDP-8 with the optional EAE in only 4000 transistors, less than a 6502; this is particularly impressive when you consider that the 6100 was CMOS, which uses significantly more transistors than the 6502's NMOS. One of the most interesting things I've seen come out of the PDP-8 world in recent years is John Cowan's PDP-8/X, a 32-bit PDP-8: https://github.com/johnwcowan/pdp8x https://github.com/johnwcowan/pdp8x The Data General Nova was strongly inspired by the PDP-8, but it had four accumulators instead of one, and you could index off two of them as well as the PC and the zero page. The Xerox Alto was a Nova clone with reprogrammable microcode, and the early Smalltalk virtual machines were implemented in its microcode. In an analogous fashion, Smalltalk-80 allows fetches and stores to index off of any of four base registers: the current method object (for constants; fetch only), global variables (indirected through the current method object IIRC), the stack frame, and the current object, with the offset from the base being a field in the instruction byte in most cases. And, as is well known, instead of an accumulator it has an operand stack (but, for example, no swap instruction). Smalltalk-80 has the densest code I've ever seen in any system. I was thinking it might be interesting to do something like that in a closer-to-the-hardware PDP-8-like fashion, avoiding the need for self-modifying code and making subroutines reentrant by default. PC-relative loads would replace the low N bits of the instruction, as on the -8. Global loads and stores would use the zero page, as on the -8. Stack-frame loads and stores would replace the low 4 bits of the stack pointer, which would be incremented and decremented by 16 on call and return, thus also needing no adder. And a fourth base register, the "workspace pointer", would be XORed with the N bits from the operand field. Objects to be pointed to by the workspace pointer might be variable in size; you might carve one page up into 4-word objects, while another one was divided into 64-word objects. When you were messing with one of the 4-word objects, you just wouldn't use "offsets" of more than 3 in your workspace-pointer-relative instructions, because those would index into some other (possibly unrelated) object of the same size. Four XOR gates instead of a full carry chain. I feel like such a machine with a load-store architecture and an operand stack instead of an accumulator could be extremely ergonomic, much more than the -8, in very little logic.
- Taniwha 4y agoso essentially a B5500 and subsequent systems - it had 2 top of stack registers, a hardware stack and mostly 8-bit opcodes (longer ones for constants and addressing) - super CISCy - that made sense in a time where memory access was very expensive - modern CPUS with on-chip caches and super fast memory don't benefit as much from such extreme opcode compression
- kragen 4y agoNo, it's mostly the opposite extreme from the B5500, though both are "stack machines": 1. What I'm proposing for operands is to have a limited-depth hardware operand stack on the chip (3-10 items) instead of an accumulator register. Maybe a bidirectional shift register with two taps to draw off the top two stack items every N bits, for example. The B5500 kept its "hardware" stack in RAM. 2. Separate operand stack and call stack, not unified like the B5500. The call stack in memory doesn't support pushing and popping values, just indexing off it for local variables; in essence you have a call stack frame base pointer register but no call stack pointer register. 3. It's a load-store architecture, which is the opposite of super-CISCy and in particular the opposite of the B5500. No type tags! Hopefully no microcode! Like the PDP-8, you can put your large constants in a constant pool. (Maybe allocate a couple of ALU instructions for pushing 0, 1, and -1.) 4. Fixed-size instructions, like the -8. Probably more than 8 bits per instruction (weren't B5500 syllables 12 bits though?) so that you have room for an offset field of at least 6 bits. Finally, a quibble: as I understand it, it's not so much that dense code is important when memory access is very expensive as that dense code is important when memory is expensive. RV32C has better code density than the B5500* and typically gets a significant speed boost out of it because that significantly boosts I$ hit rates for any given cache size, and I$ is always expensive. But think about small CPUs in general: if your entire CPU is 4000 transistors, an extra 1024 bits of 6T SRAM would cost 6144 more transistors, which drops the number of CPUs you can get onto a die by more than half. ______ * Even though 16-bit RV32C instructions are bigger than 12-bit syllables, they have source and destination fields, so you need less of them.
- Taniwha 4y ago
- rahen 4y agoTo be pedantic, the 6502 is luxury compared to the PDP-8. It has twice the number of instructions, more registers, and... a stack. Without a stack, Digital used a convoluted trick to implement their subroutines. They would store the current PC address at memory location 0, branch to the subroutine, then branch back to the address referenced at location 0 when the routine was done. Then we have the EE implementation. Digital came up with all the tricks in the book to save components: wired OR gates, gates that act as NAND or NOR depending on the input voltage, diode-capacitor gates and more. The "straight" 8 is an EE marvel. Without any extension such as floating point arithmetic, they managed to engineer its logic in DTL using an outstandingly low count of 1409 transistors. I don't think anything has ever come close to that. In comparison, a 6502 requires around 3500, more than twice the straight 8. And the PDP-8 could use multiple disk storage and teletypes at the same time, drive lab equipment, run an OS, have several users... it was quite more versatile and useful than 6502 8-bit computers. This makes the PDP-8 one of the more interesting computer architecture to study, and indeed it has been used as a reference in many EE books. In particular, check out "The Art of Digital Design", Prosser, 2nd edition, which re-creates one in TTL logic.
- CyborgCabbage 4y ago6502 does not have native mul and div
- rahen 4y agoYou're right, my bad. Edited.
- kragen 4y ago1409 transistors, 10148 diodes, 5615 resistors, and 1674 capacitors, according to http://www.pdp8online.com/straight8/functional_restore.shtml http://www.pdp8online.com/straight8/functional_restore.shtml. There have definitely been simpler computers than that. For example, the https://en.wikipedia.org/wiki/LGP-30 https://en.wikipedia.org/wiki/LGP-30 had 113 vacuum tubes, 1450 diodes, and probably also some ungodly number of resistors, capacitors, and inductors. Unlike the "straight 8", but like the PDP-8/S, it was bit-serial. Many of the tubes were in 15 flip-flops, which provided restoration (amplification), inversion, and sequentiality (that is, memory, as opposed to pure combinational logic). To quibble a bit about the 6502, it was a bit more complex than the -8, but not by nearly as much as you say. The 6502 had 4528 transistors, including diode-connected transistors and depletion-load transistors used as pullup resistors. In its NMOS logic, an N-input NAND gate required N enhancement-mode transistors and a depletion-load pullup; in the straight 8's DTL, an N-input NAND required one transistor, a pullup resistor, and N diodes. So 1409 transistors is more or less 1200 (inverting) logic gates, and 4528 transistors is probably closer to 2000 gates. The Intersil 6100 was a CMOS PDP-8, with 2N transistors per N-input NAND. It had 4000 transistors, which is probably also about 1200 inverting logic gates, but it included the EAE. Further comparison at https://retrocomputing.stackexchange.com/questions/5843/pdp-8-transistor-count https://retrocomputing.stackexchange.com/questions/5843/pdp-.... To quibble a bit about subroutine calls, JMS didn't store the current PC at memory location 0; it stored it at the operand address, then continued execution at the next memory word after that. So the called subroutine had its return address just before the beginning of its code. This mechanism doesn't directly support recursion but at least it does directly support more than one level of subroutine calls, which your alternative wouldn't.
- aap_ 4y agoSubroutines are usually not done with self-modifying code on the PDP-8. Instead the JMS instruction stores the next PC in the addressed location E and transfers control to E+1. The return is done with an indirect jump through E. ... JMS FOO ... <- return here FOO, 0 ... <- first instruction executed ... JMP I FOO