The Clean Code Blog

by Robert C. Martin (Uncle Bob)

Roots

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=.
~
$