As you might have gathered from my previous articles, I am a bit of computer and electronics history buff. I’m old enough to actually remember using an 8-bit Heathkit H-89 computer running CP/M (when I was five!), but I know of an earlier era of computing history only through books like Steven Levy’s Hackers.
The first several chapters of this book tell the story of revolutionary computers like the TX-0 and PDP-1, and the people who worked on them at MIT in the 1960s. There, they created — among other things — the very first digital video game, Spacewar! If you haven’t read Hackers, or even if you have and want to know more, you can read a detailed account of Spacewar’s origin online.
Several years ago, the Computer History Museum restored one of MIT’s PDP-1s to operation and they demonstrate it every weekend. Sadly, they weren’t giving a demo while I was there, but there are several videos online, of which this is the best:
Even better, Norbert Landsteiner has created a very accurate online emulator for the PDP-1 so you can play Spacewar! yourself. Also on his site, you can see Minskytron, Munching Squares, and Snowflake in action. He has painstakingly documented the inner workings of Spacewar as well.
Watching the videos and playing the game and the demos on an emulator really brings the stories from Hackers to life, but how could I consider myself a true PDP-1 aficionado until I had programmed one? And as K&R famously tell us, “the first program to write is the same for all languages: print the words ‘hello, world’.”
Since the only functional PDP-1 still in existence resides at the Computer History Museum, I’ll be using a cross-assembler and emulator instead. SIMH is an collection of emulators for mainframe and minicomputers made from the late 50s through the late 70s, including the PDP-1. Also available on the SIMH website is a collection of tools, including cross-assemblers for the PDP-1 and several other computers. After getting the tools, I found and read the manual for the PDP-1 and the assembler.
In case you don’t have quite the same level of commitment I did, here are the Cliff’s Notes: The PDP-1 has an 18-bit word length (this was before computers standardized on multiples of 8 bits), and uses a 6-bit character code called FIODEC (this was also pre-ASCII). Therefore, it’s possible to pack three 6-bit characters into a single 18-bit word. The term “word” could be a bit ambiguous when discussing a program that prints out English words, so to clarify, I am henceforth referring to 18-bit computer words every time.
The PDP-1 has two registers: the accumulator and the I/O register. The accumulator is where it does all the math, and the I/O register is where it stages data for input or output. The tyo instruction will type out the character represented by the right six bits of the I/O register. Since three 6-bit characters are packed into an 18-bit word, outputting them requires some low-level bit manipulation. When doing bit shifts, it’s possible to treat the accumulator and the I/O register as a combined 36-bit register, with the I/O register on the left, and the accumulator on the right.
To print the entire string of characters, the program will start by loading a word containing three characters into the accumulator and clearing the I/O register. It will then shift the left six bits from the accumulator into the right six bits of the I/O register, and print out the character. This shift and print sequence is repeated twice more, until the accumulator is empty. At this point, the address of the current word is incremented and compared against the address at the end of the string to determine whether the entire string has been output. If so, the program halts; otherwise it repeats the entire process.
Each line of the assembly language represents one atomic operation that the CPU can perform. In the left column terminated by commas are labels: names that I can use to refer to a certain location later in other instructions. In the center are the actual instructions and their operands. To the right of the instructions, I have provided comments to explain what each one is doing.
Before I can run the program, I need to assemble the source (hello.mac) into a paper-tape image (hello.rim) that I can load into the emulator. I’ll start the emulator, attach the paper tape, and boot from it. And at last, it prints “hello, world”:
Well, that’s a little anticlimactic after all that effort, but I can make this more interesting by examining my program using the DDT debugger that is mentioned in Hackers and briefly shown on the video above. An archive containing paper tape image and instructions for DDT is available on the SIMH website.
First I load the DDT tape and inspect the part of memory where I assembled my program. To do so, I type the address followed by /, and DDT prints out what it finds there. After displaying the first address, pressing backspace repeatedly will show each subsequent location. The memory is currently empty (0) because I haven’t loaded my program yet.
Now let’s load my program. First I escape back to the emulator prompt with ^E, attach my paper tape, and continue execution where I left off. I press Y to tell DDT to load a binary program from paper tape. Now when I inspect the same locations as before, DDT disassembles my program. The same instructions I put in my original source code are there but the labels are missing, replaced by raw octal values.
This is because I haven’t loaded a symbol table, so I’ll load that, which I told the macro1 assembler to create with the -s option. After escaping to the emulator prompt again using ^E, I attach the symbol tape. I resume execution and tell DDT to load the symbols from tape by pressing T. When I inspect the memory a third time, my labels are used in the disassembly.
Things get a little confusing toward the end because the DDT displays the words containing “hello, world” as if they were assembly instructions, but I can use ~ to show each word as a FIODEC triplet, and see that, indeed, my string is there, packed into 4 words.
Finally, I’ll set a breakpoint at location 103 by typing 103B. This will stop the execution of the program just before it prints out each letter. I’ll tell DDT to display values in octal by pressing C. Then I’ll run my program starting from location 100 by typing 100G. DDT stops the program, displays the location where it stopped (1 word after the lu2 label), and shows the value of the accumulator at that point.
I press ~ to show the value as FIODEC characters and see that at this point in the program, the h has been shifted out of the accumulator, leaving the characters el. When I look at the I/O register by typing I/, the h is there, waiting to be printed out. Now I continue the program’s execution by pressing P. When I continue, the tyo instruction executes and displays the letter h, which was in the right six bits of the I/O register. The output of each character is a bit obscured because DDT prints out info for the next breakpoint immediately after it, but it’s there at the start of each line.
I repeat this process each time the breakpoint occurs, and can follow each character in “hello” as it is shifted out of the accumulator, into the I/O register, and then typed out. At this point, I’ve got the idea, so I press B again to remove the breakpoint, followed by P to resume execution. The program prints out the remaining characters, then stops when it reaches the hlt instruction.
That’s about as far as my dedication to this historical experiment goes, but it was quite an interesting experience to write and debug a program the way people would have done it over half a century ago. In some ways, DDT feels surprisingly modern. As debugging techniques go, a symbolic debugger with breakpoints sure beats watching lights blink on the console. On the other hand, the level of effort required just to print “hello, world” gives me a new level of appreciation for what these old-school hackers were able to achieve on the PDP-1.