Clean Coder Blog


25 September 2021

When I was 15 or so, my father would drive me, and my best friend, Tim Conrad, to the Digital Equipment Corporation (DEC) sales office each Saturday. This was a 30 min drive. My father would drop us off in the morning and pick us up in the late afternoon. He spend two hours in his car each Saturday hauling us around.

Thanks Dad!

Tim and I would spend our day "playing" with the floor model of the PDP-8 they had at the office. The office staff were very accommodating and accepting of our presence, and they helped us out if we needed any fresh rolls of teleprinter paper, or paper tape.

Several years later, at the age of 20, I found myself working at Teradyne Applied Systems in Chicago. The computer we used there was called an M365; but it was really just an upgraded PDP-8. We used it to control lasers in order to trim electronic components to very precise values.

Forty four years later, in May of 2015, I started playing with a cute little Lua environment on my iPad called Codea. I wrote several fun little programs, like lunar lander, etc. But then I thought: "Wouldn't it be fun to write a PDP-8 Emulator?"

A few days/weeks later I had a nice little PDP-8 emulator running on my iPad. I found some archived binary images of ancient paper tapes and managed to load them into my emulator. This allowed me to run the suite of development tools that I had used back in those early days.

Then Apple decided it didn't want people writing code on the Ipad that was not distributed through the App store, so they blocked the means by which Codea users could share source code. Indeed, I couldn't even move Lua source code to my new iPads. So the emulator was lost.

Fortunately I had put the last working version up on GitHub.

At some point, Apple reopened the channel, perhaps due to a court case. I discovered this a few weeks back, and loaded that old source code back into my iPad. It worked like a champ.

I made a few changes to deal with the bigger screen, and the faster processor, and then announced it on twitter. I think many people have played with it since.

You can get the emulator here. You'll find a lot of good tutorial information, and several demonstration videos in that repository.

Euler 4

As you may know I have a youtube series on the cleancoders.com channel, in which I walk through the problems in the Euler project solving them in Clojure and then taking them to the max, Myth-buster style.

Euler 4 is a simple little problem of finding the factors of palindromic numbers. I quickly solved it in Clojure, and then I thought it would be fun to write a PDP-8 program to solve it.

Down the rathole I went.

I used TDD to get the individual subroutines working. Among the subroutines I wrote were single and double precision multiply and divide routines. (We didn't use the word "functions" back then.) The poor PDP-8 could only add. It couldn't even subtract. Subtraction was accomplished by using twos-complement addition (let the reader understand;-)

Was this fun? Yes, at first it was kinda cool to reminisce, and to feel all the old knowledge and instincts come flooding back into my brain. But once the "novelty" wore off, it stopped being fun, and just turned into work -- grinding, tedious, work.

It took me several hours, over a period of a few days, but I got the blasted thing working. It's not an experience I'd like to repeat. Working on a PDP-8 is a PITA, even when with all the cheats I supply in my Emulator.

Here, for your edification, is my solution to Euler 4 on a PDP-8. This code solves the problem; but I'm quite sure it has some really nasty bugs anyway. I am in no way proud of this code. I'm just not willing to improve it. If you study it you'll see just how awful it is. I mean, among other sins I used truly naive algorithms for multiplying and dividing numbers.

Anyway, be careful. The lure of the rathole is very compelling.

/EULER 4 SOLUTION PZERO=20 *200 MAIN, CLA TLS TAD SEED ISZ SEED CIA JMS CALL MKPAL JMS CALL PRDOT CLA TAD MAXFAC DCA FAC FACLUP, CLA TAD FAC TAD K100 SMA CLA JMP MAIN JMS CALL DLOAD DPAL TAD FAC CIA JMS CALL ISFAC SKP JMP GOTFAC CLA TAD I OFP /OTHER FAC > 999 TRY NEXT PAL. TAD MAXFAC SMA CLA JMP MAIN ISZ FAC JMP FACLUP GOTFAC, CLA TAD I OFP TAD MAXFAC SMA CLA JMP MAIN JMS CRLF CLA TAD FAC CIA JMS CALL PRAC JMS CALL PRDOT CLA TAD I OFP JMS CALL PRAC JMS CRLF JMS CALL DLOAD DPAL JMS CALL PRDACC JMS CRLF HLT DECIMAL
SEED, -999
MAXFAC, -999 OCTAL
FAC, 0
OFP, OTHFAC+1 *400 /MAKE A PALINDROMIC NUMBER FROM A SEED.
/ABC->ABCCBA IN DECIMAL IN DACC AND STORED IN DPAL MKPAL, 0 DCA DPAL+1 DCA DPAL TAD DPAL+1 JMS CALL DIV K10 DCA WRK TAD REM DCA DIGS TAD WRK JMS CALL DIV K10 DCA DIGS+2 TAD REM DCA DIGS+1 JMS CALL DLOAD DPAL TAD K1000 JMS CALL DMUL JMS CALL DSTORE DPAL CLA TAD DIGS JMS CALL MUL K10 TAD DIGS+1 JMS CALL MUL K10 TAD DIGS+2 DCA DWRK+1 DCA DWRK JMS CALL DLOAD DPAL JMS CALL DADD DWRK JMS CALL DSTORE DPAL JMP I MKPAL /SKIP IF AC IS A FACTOR OF DACC. AC=0
ISFAC, 0 DCA DFAC+1 DCA DFAC JMS CALL DDIV DFAC JMS CALL DSTORE OTHFAC JMS CALL DLOAD DREM JMS CALL DSKEQ D0 SKP ISZ ISFAC JMP I ISFAC DFAC, 0 0
OTHFAC, 0 0 OCTAL
DPAL, 0 0 DIGS, 0 0 0 WRK, 0 DWRK, 0 0 // PZERO FOR EULER *PZERO DECIMAL
K100, 100
K1000, 1000
K10, 10 OCTAL PZERO = . ~ *1000
/DMATHLIB /DLOAD - LOAD ARG INTO DACC, AC=0
DLOAD, 0 CLA TAD I DLOAD ISZ DLOAD DCA DARGP TAD I DARGP DCA DACC ISZ DARGP TAD I DARGP DCA DACC+1 JMP I DLOAD /DOUBLE PRECISION STORE ACCUMULATOR POINTED TO BY ARG
DSTORE, 0 CLA TAD I DSTORE DCA DARGP ISZ DSTORE TAD DACC DCA I DARGP ISZ DARGP TAD DACC+1 DCA I DARGP JMP I DSTORE /SKIP IF DOUBLE PRECISION ARGUMENT IS EQUAL TO DACC. AC=0
DSKEQ, 0 CLA TAD I DSKEQ DCA DARGP ISZ DSKEQ TAD DACC CIA TAD I DARGP SZA CLA JMP I DSKEQ ISZ DARGP TAD DACC+1 CIA TAD I DARGP SNA CLA ISZ DSKEQ JMP I DSKEQ /DOUBLE PRECISION ADD ARGUMENT TO DACC. AC=0 DADD, 0 CLA CLL TAD I DADD ISZ DADD DCA DARGP TAD DARGP IAC DCA DARGP2 TAD I DARGP2 TAD DACC+1 DCA DACC+1 RAL TAD I DARGP TAD DACC DCA DACC JMP I DADD /COMPLEMENT AND INCREMENT DACC DCIA, 0 CLA CLL TAD DACC+1 CMA IAC DCA DACC+1 TAD DACC CMA SZL IAC DCA DACC JMP I DCIA /MULTIPY DACC BY AC
DMUL, 0 CIA DCA PLIERD JMS DSTORE DCAND JMS DLOAD D0 TAD PLIERD SNA CLA JMP I DMUL
DMUL1, JMS DADD DCAND ISZ PLIERD JMP DMUL1 JMP I DMUL PLIERD, 0
DCAND, 0 0 /DIV DACC BY DARG (AWFUL) R IN DREM AC=0
DDIV, 0 CLA TAD I DDIV ISZ DDIV DCA .+4 JMS DSTORE DVDEND JMS DLOAD 0 JMS DCIA /NEGATE DIVISOR JMS DSTORE DVSOR JMS DLOAD DVDEND DCA DQUOT DCA DQUOT+1 JMP DDIV1 DDIV2, ISZ DQUOT+1 // INCREMENT DQUOT SKP ISZ DQUOT DDIV1, JMS DSTORE DREM JMS DADD DVSOR TAD DACC SMA CLA JMP DDIV2 JMS DLOAD DQUOT JMP I DDIV DARGP, 0
DARGP2, 0 DVSOR, 0 0
DVDEND, 0 0
DQUOT, 0 0 /PAGE ZERO DATA FOR DMATHLIB *PZERO
DACC, 0 0
D0, 0 0
DREM, 0 0
PZERO=.
~ /SINGLE PRECISION MATH LIBRARY *2000
/DIVIDE AC BY ARGP (SLOW AND NAIVE)
/Q IN AC, R IN REM
DIV, 0 DCA REM TAD I DIV ISZ DIV DCA ARGP TAD I ARGP CIA DCA MDVSOR DCA QUOTNT TAD REM
DIVLUP, TAD MDVSOR SPA JMP DIVDUN ISZ QUOTNT JMP DIVLUP
DIVDUN, CIA TAD MDVSOR CIA DCA REM TAD QUOTNT JMP I DIV
MDVSOR, 0
QUOTNT, 0
ARGP, 0 /MULTIPLY AC BY ARGP (SLOW AND NAIVE)
/GIVING SINGLE PRECISION PRODUCT IN AC MUL, 0 DCA CAND TAD I MUL ISZ MUL DCA ARGP TAD I ARGP SNA JMP I MUL CIA DCA PLIER TAD CAND ISZ PLIER JMP .-2 JMP I MUL
CAND, 0
PLIER, 0 /PZERO FOR SMATHLIB *PZERO
REM, 0
PZERO=.
~ /TTY UTILS *3000
/PRINT ONE CHAR IN AC. IF CR THEN PRINT LF. PRTCHAR,0 TSF JMP .-1 TLS DCA CH TAD CH TAD MCR SZA JMP RETCHR TAD KLF TSF JMP .-1 TLS
RETCHR, CLA TAD CH JMP I PRTCHAR
CH, 0
MCR, -215 /PRINT AC AS ONE DECIMAL DIGIT AC=0
PRDIG, 0 TAD K260 TSF JMP .-1 TLS CLA JMP I PRDIG K260, 260 /PRINT THE DACC IN DECIMAL PRDACC, 0 JMS CALL DSTORE DACSV JMS CALL DDIV D1E6 TAD DACC+1 JMS PRDIG JMS CALL DLOAD DREM JMS CALL DDIV D1E5 TAD DACC+1 JMS PRDIG JMS CALL DLOAD DREM JMS CALL DDIV D1E4 TAD DACC+1 JMS PRDIG JMS CALL DLOAD DREM JMS CALL DDIV D1E3 TAD DACC+1 JMS PRDIG JMS CALL DLOAD DREM JMS CALL DDIV D1E2 TAD DACC+1 JMS PRDIG JMS CALL DLOAD DREM JMS CALL DDIV D1E1 TAD DACC+1 JMS PRDIG JMS CALL DLOAD DREM TAD DACC+1 JMS PRDIG JMS CALL DLOAD DACSV JMP I PRDACC DACSV, 0 0
D1E6, 0364 1100
D1E5, 0030 3240
D1E4, 2 3420
D1E3, 0 1750
D1E2, 0 144
D1E1, 0 12 /PRINT AC, AC=AC
PRAC, 0 DCA SAC TAD SAC JMS CALL DIV D1E3+1 JMS PRDIG TAD REM JMS CALL DIV D1E2+1 JMS PRDIG TAD REM JMS CALL DIV D1E1+1 JMS PRDIG TAD REM JMS PRDIG TAD SAC JMP I PRAC
SAC, 0 /PRINT DOT AC=AC
PRDOT, 0 DCA SAC TAD KDOT JMS TYPE TAD SAC JMP I PRDOT /----------------------
/PZERO TEST LIBRARY *PZERO TYPE, 0 / AC=0 TSF JMP .-1 TLS CLA JMP I TYPE CRLF, 0 / AC=0 CLA TAD KCR JMS TYPE TAD KLF JMS TYPE JMP I CRLF /SOUND BELL AND HALT WITH ADDR OF BAD TEST IN AC ERROR, 0 CLA TAD KBELL JMS TYPE CLA CMA TAD ERROR HLT /PRINT DOT, COUNT ERROR PASS, 0 CLA TAD KDOT JMS TYPE ISZ TESTS JMP I PASS /TESTS COMPLETE, PRINT ZERO AND HALT WITH # OF TESTS IN AC. TSTDUN, JMS CRLF TAD KZERO JMS TYPE JMS CRLF TAD TESTS HLT /CALL SUBROUTINE
CALL, 0 DCA AC TAD I CALL DCA CALLEE TAD CALL IAC DCA I CALLEE ISZ CALLEE TAD AC JMP I CALLEE
AC, 0
CALLEE, 0 TESTS, 0 KZERO, 260
KBELL, 207
KCR, 215
KLF, 212
KDOT, 256 PZERO=.
~
$