If you follow my twitter, facebook, or github feeds, you may have noticed that I’ve been writing a PDP-8 Emulator for the iPad.
Here’s a screenshot:
My purpose in writing this emulator (other than sheer nostalgia) is to use it as a training tool for new programmers. I think every new programmer should spend a week or two programming one of these old machines. It seems to me that there is no better way to understand what a computer really is, than to touch a real computer and program it at the bit level, in machine language. Once you have done that, all the magic disappears and is replaced with cold, hard reality. And, let me tell you, programming a PDP-8 is cold, hard, reality. Oh boy, is it ever!
I’ve tried to be faithful to the machine and it’s environment. The front panel is a decent abstract representation of the original PDP8, and the lights blink appropriately, and with the correct data. (though I couldn’t resist making the lights touch sensitive, like the ECP-18).
The paper tapes in the reader and punch move at appropriate speeds, and the holes represent the true data. They also make the right kinds of noises. The teletype prints at the appropriate speed (though you can speed it up if you want (you will)) and behaves very much like an ASR-33, making all the appropriate noises, and responding properly to carriage return and line feed characters, etc. (Yes, you can overprint!)
I found some binary images of old PDP8 paper tapes here, and managed to get them into my emulator by munging their format with a little C program I wrote, and then transporting them to the iPad using Dropbox. The results have been both satisfying and heart-wrenching. That ancient code works!
I sit in front of my half-pound, $600 iPad, in its nice little case, and its bluetooth keyboard, surrounded by programming manuals, and marked up listings of a program I am working on. And I realize that I’m using code written half a century ago by men and women (probably a lot of women back then) who hauled themselves up by their bootstraps to get that silly little machine working. A machine that weighed 500lbs, was the size of a refrigerator, and cost $20,000 in 1967.
Could those men and women ever have guessed that their code would be running in a hand-held tablet computer and would be used to train programmers in the twenty-first century? Some – many – must still be alive. I wonder what they’d think if they knew.
My emulator is written in Lua, using the Codea framework for the iPad. This is a hugely convenient language for iPad development. Lua is fast (enough), and Codea has wonderful graphics primitives, and a simple, yet very effective graphics framework for developing highly interactive animated programs.
This made the animation (and sound generation) of the front panel, the teletype, and the paper tape reader/punch a snap.
Emulating the PDP8 internals was a bit of a challenge since Lua only has one numeric type: floating point. Doing 12-bit logic using floating point math is, uh, interesting. On the other hand, I get a huge kick out of watching FOCAL
(FORmula CALculator: a language similar to Basic) run on my PDP-8, and do it’s own floating point math, using the logical operations that I concocted from Lua’s floating point math. <grin>
You should see those lights blink!
The execution speed is about 4,000 instructions per second. While that’s 1/7th the speed of a PDP8/S, it’s pretty impressive for an iPad running a “byte-code interpreted” language like Lua, emulating 12 bit logic using floating point math! I wasn’t expecting that kind of performance. It actually runs all that old DEC software at reasonable speed. Even FOCAL
runs fast enough to compute square roots in half a second or so.
And, again, the blinking of the lights during a compile is deeply satisfying. Watch that and you’ll know where all those 1950s sci-fi movies got their ideas from.
##MODE-B Getting the Emulator working was really very easy. I’ve probably invested 30 hours in it overall; and that includes learning Codea and Lua.
The development process was lickety-split. Codea’s Lua editor for the iPad is intuitive and powerful (though it has no refactorings <sob>
). The edit/test loop was, perhaps, 10 seconds long. I could add a line or two of code, run the app, see the effect, and then hop back into the editor just like that. It was a satisfying treat, and I had a ball doing it.
Of course I wrote tests for the tricky bits. I wrote a little test framework just for that purpose, and I put a TEST
button on the front panel of the emulator to make it easy for me to run those tests. The emulation code itself would have been nearly impossible if I hadn’t had unit tests. And of course, I used the TDD discipline for that code. In the end, there were over 100 tests for the various instructions and behaviors of the PDP-8.
For the GUI, on the other hand (and there’s a lot of GUI code), tests were unnecessary (<GASP>
). My eyes were the tests. I knew what I wanted to see, and so I spun around the edit/test loop every 10 seconds. Writing tests in a TDD style would have been horrifically difficult, and an utter waste of time.
On the other hand, I was still using the TDD rhythm. I knew what I wanted to see on the screen. That was my test. I simply modified the code until that test passed. So even though I wasn’t writing tests, it felt like I was – it felt like TDD.
It’s true, of course, that I don’t have automated regression tests for my GUI. On the other hand, it’s absurdly simple to ensure that everything is working. So far the lack of automated GUI tests has not impacted my loop time.
Of course without refactoring tools the code got a bit messy. I refactored when I couldn’t stand it anymore; but the backlog of messiness is larger than I’d like. I’ll continue to clean that code over time; but it’s much slower going without good refactoring tools.
Anyway, for the sake of this article, let’s call this style of programming: MODE-B.
MODE-B
is the style that allows you to edit on the screen, and see the results either on the screen, or in a passing test, in seconds. It’s a hyper-speed development loop that doesn’t require listings, pencils, compile time, setup time, or any other impediment. The time between editing the code, and seeing it run, is much less than one minute.
##MODE-A
Having gotten the PDP8 Emulator to work. And having gotten all the old tools, like the paper tape editor, and the Pal-3 assembler, up and running. I set about to write a simple program. This program would allow the user to type a simple formula on the keyboard, and then it would print the result. For example, if the user typed: 25+32
, the computer would print 57
.
On a PDP-8, this is a non-trivial program. I’ve included it below for those of you who want to see how a pretty poor PDP-8 programmer has written it.
The process was the same as the process I used back in the late ’70s when working on assembly language programs on a Teradyne M365
(An 18 bit relative of a PDP8). We had magnetic tape, instead of paper tape; and the computer was a bit more powerful than a PDP-8. But the process was still the same. It goes like this:
Assume that you are in the middle of writing the program below. You’ve already got some of it written, and you are adding to it. Remember, this computer only has 4K words. It can’t store much in it’s memory. Remember, also, that the only mass storage you have is paper tape. So your source code is on a single long paper tape.
- Write the changes you want to make to your program on the current listing. You’ll have changes on many pages, so put paper clips in those pages if the listing is long.
- Load the editor from paper tape. This will take a few minutes so get some coffee.
- Set the front panel switches to
6003
: to compress spaces, and use the high speed reader/punch. Run the editor (by toggling0200
into the PC register and hitting theRUN
button) - Put your source code paper tape into the reader.
- Read in one “page” of code from paper tape using the
R
command. (50 lines or less. 1 minute or so). - Go to that page in your listing and make any changes using the
I
,C
, andD
commands. Remember you don’t have a screen, so you are editing line by line, using line numbers. Plan to spend some time at this. - Print out the current page using the
L
command. Make sure all your changes are correct. - Punch the current page to paper tape using the
P
command. (a minute or so). - Punch the current page and read in the next with the
N
command and if that wasn’t the last page, go to step 6. - Remove the new source tape from the punch and label it with a title and a version number. Don’t ever forget the version number!
- Load the assembler into memory from paper tape (10 minutes or so).
- Set the front panel switches to
2002
: the “pass one, output to printer” configuration. - Load your source tape into the reader.
- Load
0200
into the PC register, and hitRUN
. - Pass one compile will read your whole source tape and then print your symbol table. (10 minutes or so)
- After the computer halts, set the front panel switches to
4003
: the “pass two, output to punch” configuration. - Load your source tape into the reader.
- Push
RUN
. Pass two compile will read your whole source tape and punch your binary paper tape. (15 min or so). Source code errors will print during this pass. - After the computer halts, if there are errors, throw away the paper tape that was punched and go to step 1. Otherwise remove the binary tape from the punch and label it with a title and version number. (I don’t need to remind you about that version number, right?)
- Set the front panel switches to
6002
: the “pass three, output to printer” configuration. - Load your source tape into the reader.
- Push
RUN
. Pass three compile will read your whole source tape and print your program listing. You’ll need this for debugging, so don’t neglect it. (30 min or so because the printer is very slow.) Make sure you have enough paper in the printer! - Tear off the listing and check it over.
- Put your binary tape into the reader.
- Set the PC register to
7777
(the address of the bin loader which is usually kept in core memory) and hitRUN
. If the bin loader is not in memory for some reason, then you’ll have to toggle in theRIM
loader and then load the bin loader paper tape before doing this step. - When the computer halts, your program has been loaded into memory. Run it, and see if it works.
This process is highly abbreviated. There are lots of littler steps in there, but you get the idea.
This is MODE-A
. It’s a very fragile, error-prone process that takes an hour or so to execute. It could be a lot more for a large-ish program. A very small program might make it around the loop in 15 minutes. The program I was writing grew to be about 20-30 minute or so, and I cheated by allowing my “teletype” to run at 10X the normal rate.
To get my silly little program to work, I went around that loop seven times. It took me about a week, and about five hours total. A lot of that time was writing the code with a pencil because, without a screen editor, there was just no way to avoid hand writing, and using a lot of eraser.
Back in the ’70s I spent days, weeks, and years working in MODE-A
. All programmers did. That was what programming was back then.
And here’s the thing about MODE-A
: YOU ARE CAREFUL. Every mistake costs you an hour or so. So you spend a lot of time going over the details, making sure your code is right; that you edited it correctly; that the switches are set correctly; that the tapes are labelled correctly; etc.
In MODE-A
you take nothing for granted. You do everything deliberately and carefully. Because that’s the only way to go fast. (If “fast” is the word.)
Let’s call this carefulness and deliberateness: MODE-A
behavior.
##MODE-A
vs MODE-B
MODE-A
is a lot slower than MODE-B
. The loop time is impossibly large, and the amount you can get done in each loop is ridiculously small. For example, my first loop through this process was to write, and debug, the subroutine that read in a line of text from the keyboard, terminated with a CR (Carriage Return… Yes, the teletype had a “carriage”, or rather a “print head” that could be “returned”.)
MODE-B
is fast! Really, really, fast. The time through the loop is very short, and you can get a lot done in each loop. For example, it only took me a few loops around to get the paper tape animation through the reader and punch to work correctly. Every PDP-8 machine instruction took a loop or two. Getting the scrolling of the TTY paper took two or three loops.
And, of course, I wasn’t using listings. I didn’t write the code on paper first. I could go anywhere in the program I wanted and edit any line I wanted in a flash. I had syntax highlighting, automatic indenting, search and replace, scrolling, tabs, and on-line documentation.
MODE-B
is fast!
##The MODE-B
Imperative!
So then why do so many programmers still work in MODE-A
? They do, you know. They pile mess upon mess, and framework upon framework, until their loop time grows from seconds to minutes and longer? They inject so many dependencies that the builds become fragile and error-prone. They create so many unisolated external dependencies that they might as well be using paper tape. Why would anybody do anything that increased their loop time? Why wouldn’t everyone defend their loop time with their lives?
Isn’t avoiding MODE-A
a guilt-edged priority? Shouldn’t we all do everything we possibly can to keep our development cycle in MODE-B
? Isn’t MODE-B
an imperative?
Do you want to know the secret for staying in MODE-B
? I know what it is. I’ll tell you.
The secret for staying in
MODE-B
is to useMODE-A
behavior.
##HOLY STRUCTURED METHODOLOGY BATMAN!
I just discovered who wrote the PAL III assembler for the PDP-8. Hold on to your hats. It was Ed Yourdon.
PDP8 Program to accept two numbers and a single operator, and print the result.
*20
0020 7563 MCR, -215
0021 0212 KLF, 212
0022 7540 MSPC, -240
0023 7520 MZERO, -260
0024 7766 M10, -12
0025 0276 PROMPT, 276 />
0026 0215 KCR, 215
0027 7525 MPLUS, -253
0030 7523 MMINUS, -255
0031 0277 QMARK, 277
0032 0260 KZERO, 260
/WORKING STORAGE
0033 0000 REM, 0
/CALL SUBROUTINE IN ARG
0034 0000 CALL, 0
0035 3046 DCA AC
0036 1434 TAD I CALL
0037 3047 DCA CALLEE
0040 1034 TAD CALL
0041 7001 IAC
0042 3447 DCA I CALLEE
0043 2047 ISZ CALLEE
0044 1046 TAD AC
0045 5447 JMP I CALLEE
0046 0000 AC, 0
0047 0000 CALLEE, 0
----------------
*200
/CALC A+B OR A-B
/MAIN LOOP: PROMPT, GET CMD, PRINT RESLT
0200 6046 TLS
0201 7200 IDLE, CLA
0202 1026 TAD KCR
0203 4034 JMS CALL
0204 0425 PRTCHAR
0205 7200 CLA
0206 1025 TAD PROMPT
0207 4034 JMS CALL
0210 0425 PRTCHAR
0211 4034 JMS CALL
0212 0400 RDBUF
0213 2000 BUF
0214 4034 JMS CALL
0215 0462 SKPSPC
0216 2000 BUF
0217 3222 DCA .+3
0220 4034 JMS CALL
0221 0477 GETNUM
0222 0000 0
0223 3261 DCA A
0224 1622 TAD I .-2
0225 3263 DCA OP
0226 1222 TAD .-4
0227 7001 IAC
0230 3233 DCA .+3
0231 4034 JMS CALL
0232 0477 GETNUM
0233 0000 0
0234 3262 DCA B
0235 1263 TAD OP
0236 1027 TAD MPLUS
0237 7650 SNA CLA
0240 5254 JMP ADD
0241 1263 TAD OP
0242 1030 TAD MMINUS
0243 7650 SNA CLA
0244 5251 JMP SUB
0245 1031 TAD QMARK
0246 4034 JMS CALL
0247 0425 PRTCHAR
0250 5201 JMP IDLE
0251 1262 SUB, TAD B
0252 7041 CIA
0253 7410 SKP
0254 1262 ADD, TAD B
0255 1261 TAD A
0256 4034 JMS CALL
0257 0600 PRTNUM
0260 5201 JMP IDLE
0261 0000 A, 0
0262 0000 B, 0
0263 0000 OP, 0
----------------
*400
/READ A BUFFER UP TO A CR
0400 0000 RDBUF, 0
0401 7200 CLA
0402 1600 TAD I RDBUF
0403 2200 ISZ RDBUF
0404 3215 DCA BUFPTR
0405 4216 RDNXT, JMS RDCHAR
0406 3615 DCA I BUFPTR
0407 1615 TAD I BUFPTR
0410 1020 TAD MCR
0411 7450 SNA
0412 5600 JMP I RDBUF
0413 2215 ISZ BUFPTR
0414 5205 JMP RDNXT
0415 0000 BUFPTR, 0
/READ ONE CHAR
0416 0000 RDCHAR, 0
0417 7200 CLA
0420 6031 KSF
0421 5220 JMP .-1
0422 6036 KRB
0423 4225 JMS PRTCHAR
0424 5616 JMP I RDCHAR
/PRINT ONE CHAR
0425 0000 PRTCHAR,0
0426 6041 TSF
0427 5226 JMP .-1
0430 6046 TLS
0431 3245 DCA CH
0432 1245 TAD CH
0433 1020 TAD MCR
0434 7440 SZA
0435 5242 JMP RETCHR
0436 1021 TAD KLF
0437 6041 TSF
0440 5237 JMP .-1
0441 6046 TLS
0442 7200 RETCHR, CLA
0443 1245 TAD CH
0444 5625 JMP I PRTCHAR
0445 0000 CH, 0
/PRT A BUFFER
0446 0000 PRTBUF, 0
0447 7200 CLA
0450 1646 TAD I PRTBUF
0451 2246 ISZ PRTBUF
0452 3215 DCA BUFPTR
0453 1615 PRTNXT, TAD I BUFPTR
0454 4225 JMS PRTCHAR
0455 2215 ISZ BUFPTR
0456 1020 TAD MCR
0457 7640 SZA CLA
0460 5253 JMP PRTNXT
0461 5646 JMP I PRTBUF
----------------
/SKIP SPACES AC= FIRST NON-SPACE
0462 0000 SKPSPC, 0
0463 7200 CLA
0464 1662 TAD I SKPSPC
0465 2262 ISZ SKPSPC
0466 3215 DCA BUFPTR
0467 1615 NXTCHR, TAD I BUFPTR
0470 2215 ISZ BUFPTR
0471 1022 TAD MSPC
0472 7650 SNA CLA
0473 5267 JMP NXTCHR
0474 7240 CLA CMA
0475 1215 TAD BUFPTR
0476 5662 JMP I SKPSPC
/GET DECIMAL NUMBER
0477 0000 GETNUM, 0
0500 7200 CLA
0501 3335 DCA NUMBER
0502 1677 TAD I GETNUM
0503 3215 DCA BUFPTR
0504 1615 NXTDIG, TAD I BUFPTR
0505 1023 TAD MZERO
0506 3334 DCA DIGIT
0507 1334 TAD DIGIT
0510 7710 SPA CLA
0511 5327 JMP NONUM
0512 1024 TAD M10
0513 1334 TAD DIGIT
0514 7700 SMA CLA
0515 5327 JMP NONUM
0516 1335 TAD NUMBER
0517 7100 CLL
0520 7006 RTL
0521 1335 TAD NUMBER
0522 7004 RAL
0523 1334 TAD DIGIT
0524 3335 DCA NUMBER
0525 2215 ISZ BUFPTR
0526 5304 JMP NXTDIG
0527 1215 NONUM, TAD BUFPTR
0530 3677 DCA I GETNUM
0531 2277 ISZ GETNUM
0532 1335 TAD NUMBER
0533 5677 JMP I GETNUM
0534 0000 DIGIT, 0
0535 0000 NUMBER, 0
----------------
/DIVIDE AC BY ARG
/Q IN AC, R IN REM
0536 0000 DIV, 0
0537 3033 DCA REM
0540 1736 TAD I DIV
0541 2336 ISZ DIV
0542 7041 CIA
0543 3361 DCA MDVSOR
0544 3362 DCA QUOTNT
0545 1033 TAD REM
0546 1361 DIVLUP, TAD MDVSOR
0547 7510 SPA
0550 5353 JMP DIVDUN
0551 2362 ISZ QUOTNT
0552 5346 JMP DIVLUP
0553 7041 DIVDUN, CIA
0554 1361 TAD MDVSOR
0555 7041 CIA
0556 3033 DCA REM
0557 1362 TAD QUOTNT
0560 5736 JMP I DIV
0561 0000 MDVSOR, 0
0562 0000 QUOTNT, 0
----------------
*600
/PRINT NUMBER IN DECIMAL
DECIMAL
0600 0000 PRTNUM, 0
0601 4034 JMS CALL
0602 0536 DIV
0603 1750 1000
0604 4225 JMS PRTDIG
0605 7200 CLA
0606 1033 TAD REM
0607 4034 JMS CALL
0610 0536 DIV
0611 0144 100
0612 4225 JMS PRTDIG
0613 7200 CLA
0614 1033 TAD REM
0615 4034 JMS CALL
0616 0536 DIV
0617 0012 10
0620 4225 JMS PRTDIG
0621 7200 CLA
0622 1033 TAD REM
0623 4225 JMS PRTDIG
0624 5600 JMP I PRTNUM
/PRINT A DIGIT IN AC
OCTAL
0625 0000 PRTDIG, 0
0626 1032 TAD KZERO
0627 4034 JMS CALL
0630 0425 PRTCHAR
0631 5625 JMP I PRTDIG
----------------
*2000
2000 0000 BUF,0