Close

Hello World, 60s Style

jb-langstonJ.B. Langston wrote 03/13/2021 at 04:50 • 10 min read • Like

Originally published in 2017.

PDP-1 at Lawrence Livermore National Laboratory

As you might have gathered from my projects, 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:

The Computer History Museum’s PDP-1 in Action

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.

hello   
/ above: title line - was punched in human readable letters on paper tape
/ below: location specifier - told assembler what address to assemble to
100/
lup,    lac i ptr        / load ac from address stored in pointer
    cli            / clear io register
lu2,    rcl 6s            / rotate combined ac + io reg 6 bits to the left
                / left 6 bits in ac move into right 6 bits of io reg
    tyo            / type out character in 6 right-most bits of io reg
    sza            / skip next instr if accumulator is zero
    jmp lu2            / otherwise do next character in current word
    idx ptr            / increment pointer to next word in message
    sas end            / skip next instr if pointer passes the end of message
    jmp lup            / otherwise do next word in message
    hlt            / halt machine
ptr,    msg            / pointer to current word in message
msg,    text "hello, world"    / 3 6-bit fiodec chars packed into each 18-bit word
end,    .                 / sentinel for end of message
start 100            / tells assembler where program starts

 Hello World in PDP-1 Assembly

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”:

C:\Users\jblang\Retro\DEC\PDP1>macro1 -s hello.mac
hello    - pass 1
hello    - pass 2

C:\Users\jblang\Retro\DEC\PDP1>pdp1

PDP-1 simulator V4.0-0 Beta        git commit id: ea898b24
sim> att ptr hello.rim
sim> boot ptr
hello, world
HALT instruction, PC: 000112 (000117)
sim> exit
Goodbye

 Program Assembly and Execution

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.

C:\Users\jblang\Retro\DEC\PDP1>pdp1

PDP-1 simulator V4.0-0 Beta        git commit id: ea898b24
sim> load ddt-loader.rim
sim> run

100/    0
101/    0
102/    0
103/    0
104/    0
105/    0
106/    0
107/    0
110/    0
111/    0
112/    0
113/    0
114/    0
115/    0
116/    0
117/    0
120/    0
121/    0

 Empty Memory

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.

Simulation stopped, PC: 006024 (JMP 6023)
sim> att ptr hello.rim
sim> cont
Y
100/    lac i 112
101/    cli
102/    rcl 77
103/    tyo
104/    sza
105/    jmp 102
106/    idx 112
107/    sas 117
110/    jmp 100
111/    hlt
112/    113
113/    law 6543
114/    sub i 4633
115/    2646
116/    sad i 4364
117/    117
120/    0
121/    0

 Program Loaded

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.

Simulation stopped, PC: 006023 (SZF1 I)
sim> att ptr hello.sym
sim> cont
T
100/    lac i ptr
lup+1/  cli
lu2/    rcl 77
lu2+1/  tyo
lu2+2/  sza
lu2+3/  jmp lu2
lu2+4/  idx ptr
lu2+5/  sas end
lu2+6/  jmp lup
lu2+7/  hlt
ptr/    msg
msg/    law end+6424    ~       hel
msg+1/  sub i end+4514  ~       lo,
msg+2/  end 2527        ~       wo
msg+3/  sad i end+4245  ~       rld
end/    end
end+1/  0
end+2/  0

 Symbols Loaded

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.

103B
C
100G
lu2+1)  654300  ~       el      I/      70      ~       h       P
hlu2+1) 430000  ~       l       I/      7065    ~       he      P
elu2+1) 0       ~               I/      706543  ~       hel     P
llu2+1) 463300  ~       o,      I/      43      ~       l       P
llu2+1) 330000  ~       ,       I/      4346    ~       lo      P
olu2+1) 0       B
P
, world
HALT instruction, PC: 000112 (000117)
sim>

 Using Breakpoints

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.

Like

Discussions