PDP-1 Character Generators

94 views
Skip to first unread message

Norbert Landsteiner

unread,
Jan 22, 2026, 9:32:55 AMJan 22
to [PiDP-1]
Maybe useful: various historic subroutines for putting characters on the display, along with their character definitions:


The first character generator by Ben Gurley & Weldon Clark is interesting for various reasons:
First, Ben Gurley, the designer of the PDP-1, is one of the authors. (This feels different from the highly optimized routines, more relaxed, like the prevalent feeling was that the PDP-1 is really fast.)
Then, it's written in Ed Fredkin's FRAP. Maybe also interesting for a rather different approach to PDP-1 assembly than Macro.
(FRAP is the very first assembler for the PDP-1, based on Ed Fredkin's insight that most of assembler code is about checking and enforcing rules. Thus, in order to fit into the 1K memory block that originally came with the BBN prototype, he made his assembler with as few rules as possible. Hence, FRAP is for Free of Rules Assembler Program. As a bonus, after reading in the manual that the paper tape reader can run tape forwards and backwards, Fredkin made his assembler run the source tape forwards for the first pass and then backwards for the second pass with no need to rethread it again. This was also the downfall of FRAP, as this introduced some unreliability and reportedly annoying chatter of the reader for longer source tapes, which became a thing with the standard 4K core memory blocks.)

I've personally used the 1964 one in the past.

(See the individual pages with the listings for links to the original source documents.)


PS: If you have memory to spare, writing a JIT compiler may be worth it.
Much like the outline routine for the spaceships in Spacewar!, plenty of time is lost by dropping AC and IO and then reloading them, again, just to parse and interpret the character matrix code. If we split parsing and display code, we can go mostly with register swapping, additions and dpy instructions without dropping anything.
(Compare https://www.masswerk.at/rc2016/10/09.html for what the resulting JIT code could look like. In this instance, this was a viable approach, since it was about displaying numbers only. A full-fledged lower- and upper-case alphabet may be another story, though.)


Best,
Norbert

MICHAEL GARDI

unread,
Jan 22, 2026, 10:14:57 AMJan 22
to [PiDP-1]
I used the compiled character technique to great effect in my Lunar Lander game as I was displaying a fairly large number of few characters (mostly numbers) on screen (up to 22 at a time). One thing I did a little differently was to generate the characters externally as assembler code and include them in my program rather than implement a JIT in code as Norbert did (or borrow his :-).  The generated code looks like this:

c0, lio dgy / 0
lac dgx
add (2000
dpy-i 4200
add (2000
ioh
dpy-i 4200
add (2000
ioh
dpy-i 4200
lac dgx
rcr 9s
rcr 9s
sub (2000
rcr 9s
rcr 9s
ioh
dpy-i 4200
add (10000
ioh
dpy-i 4200
lac dgx
rcr 9s
rcr 9s
sub (2000
rcr 9s
rcr 9s
ioh
dpy-i 4200
add (10000
ioh
dpy-i 4200
lac dgx
rcr 9s
rcr 9s
sub (2000
rcr 9s
rcr 9s
ioh
dpy-i 4200
add (10000
ioh
dpy-i 4200
lac dgx
rcr 9s
rcr 9s
sub (2000
rcr 9s
rcr 9s
ioh
dpy-i 4200
add (10000
ioh
dpy-i 4200
lac dgx
rcr 9s
rcr 9s
sub (2000
rcr 9s
rcr 9s
ioh
dpy-i 4200
add (10000
ioh
dpy-i 4200
lac dgx
rcr 9s
rcr 9s
sub (2000
rcr 9s
rcr 9s
add (2000
ioh
dpy-i 4200
add (2000
ioh
dpy-i 4200
add (2000
ioh
dpy-i 4200
jmp ndg

This is for the single digit 0, which brings me to the " If you have memory to spare" caveat. This code is incredibly fast (relatively speaking for a PDP-1) but takes up quite a bit of memory for what it does.

I had though at one point that this technique might be good for on screen "sprites" so my initial implementation of the LEM in my game did this. The problem is that for a single 18x18 bit LEM image to be rendered the code generated was about 300 "words" long. I would need at a minimum 5 different versions of the LEM image (with the ship pointing in different directions) so call it 1500 words. The total available memory for the PDP-1 is 4096 words (without bank switching), so just the LEM images would occupy about 37% of the available memory.

The other way to render the LEM images is to add their bitmaps to the program as data and write a single routine to draw these bitmaps to the screen based on that data.  When I wrote the code to draw these bitmaps it turned out to be 36 words long. So the total overhead of drawing the LEM this way was 18 word bitmaps x 5 bitmaps + 36 words of code or 126 words, less that 3% of the available memory.

A classic speed vs space dilemma. I can tell you with my program sitting at 3k+ right now I did not have the space for faster LEM drawing.

Mike

Bill E

unread,
Jan 22, 2026, 12:55:16 PMJan 22
to [PiDP-1]
Norbert, interesting info. Of course I had to get sidetracked yet again, I assembled the MIT version. It actually does something. Not the right something, but it does draw some dots. I haven't quite figured out how to use it yet, I guess I'm going to have to figure out what the code is doing. Thanks for the links.
Bill

Bill E

unread,
Jan 22, 2026, 4:04:10 PMJan 22
to [PiDP-1]
First, the source code for the 'codeword display' version of the sw software generator doesn't seem to be functional code. For one thing, it uses an uninitialized variable.

Looking into the Type 33 symbol generator addon for the Type 30 display, seems to be a hardware implementation of the various software generators. It gets passed 2 18-bit words that represent a 5x7 character, just like the various programs do. I've thought about implementing this IOT off and on, I might end up doing so. We'll see.

As for the variations of the Type 30, there were some number of options that could be ordered, but I haven't tracked them down. One was a color display!
As the handbook says: 

Many variations of the Type 30 Display are available. Some of these include
hardware for line and curve generation .

I've researched as much as I have time for now, if someone has a burning interest, maybe they can track down more details of just what options were available.
Bill

Norbert Landsteiner

unread,
Jan 22, 2026, 6:25:48 PMJan 22
to [PiDP-1]
Regarding completeness of the Codeword Display, I may be missing something, but I fail to see any missing symbols. Please refer to the linked PDF.
I see a few possible gotchas, `del`, which is the same symbol/label as `siz` with value 02000 (these are two labels on the same line), and the automatic assembler variables `cty`,`cw`, `x` and 'y'. Mind that in Macro, if a symbol is used once as an assembler variable, this propagates to the symbol as a whole and related markup isn't required for every occurrence. So `cty` is homonymous with `\cty` and `cw` is the same as `\cw`, etc., if any of the occurrences has related markup (in the original an overbar on any character). The listing replicates this feature as found in the original source.

(That's one of the reasons why I have been so "defensive" about original Macro features and not mixing them up with more modern approaches or features, attempting to "fix" the original behavior and by this also inevitably introducing conflicts along the way.)

Yes, the Type 33 symbol generator reminds much of the automatic multiply/divide as it substitutes a previous software routine by a more performant hardware version.

Regarding the various version of the Type 30, mind that this wasn't exclusively tied to the PDP-1, but was available as a general display and was used with later PDPs. As the Type 30 seems to have enjoyed a longer life span than the PDP-1, we can't be sure if any of those advanced features (like vector) were available for the PDP-1. (This may be an interesting field of research.)

As for the Type 30 and color, my favorite fun fact is that there was a set of photographic filters available to isolate either the fast blue or the long-sustain yellow-green. Meaning, you could capture either just the instance (fast blue) or an entire time series much like an onion skin diagram (long sustain). – Pointing out the 4D characteristics of the Type 30, as in width, height, intensity, and time (x/y/z/t).

Best,
Norbert

Bill E

unread,
Jan 22, 2026, 8:23:02 PMJan 22
to [PiDP-1]
Codeword display - I'll have to compare again, might have missed something in the cut/paste. It's complaining about 2dl not being defined. It's  not very obvious how to run it.
Bill

Bill E

unread,
Jan 22, 2026, 8:38:23 PMJan 22
to [PiDP-1]
Sorry, I meant 2dl has no initialized value, and the linked pdf shows the same.  Near the beginning of cwd, a sub \2dl is done, but it has not had any value assigned to it yet. From recollection, a variable has no initialized value, it's whatever happens to be in memory at the assigned location.

Bill

Norbert Landsteiner

unread,
Jan 22, 2026, 9:19:58 PM (14 days ago) Jan 22
to [PiDP-1]
Ah, 2dl and 4dl are configurable dot offsets.
The related "codeword digit display", found immediately below this in the PDF has set them (as labeled memory locations) as

2dl,    4000
4dl,   10000

A suitable preamble may be therefore

init,  law 4000
       dac \2dl
       sal 1s
       dac \4dl

If we look closely, this is what the code near 'siz' does:

siz,del, 2000
         dap sex     /deposit return address
         lac del     /load 2000 from del
         sal 1s      /scale <<1
         dac \2dl    /store 4000 in \2dl
         sal 1s      /scale <<1
         dac \4dl    /store 10000 in \4dl
sex,     jmp .       /return


so, label "siz" for initializing size (as a derivate of the value in "del") is probably supposed to go on the next line ("dap sex").
As-is, we must initialize by calling 

                     jsp siz+1

(Notably, neither is "siz" used anywhere in the code, nor is "del" used as a call. So, as-is, this subroutine is actually dead code and is clearly meant to be called separately, i.e. for initialization.)

Best,
Norbert

Norbert Landsteiner

unread,
Jan 22, 2026, 9:34:01 PM (14 days ago) Jan 22
to [PiDP-1]
Or, rather, the idea is probably to deposit a value for "del" by "jda siz", which would reflect the use of labels more closely.

E.g.,

   law 2000     /dot offset for display rendering
   jda siz      /configure (set del, 2dl, 4dl)

However, if we're fine with what's already in "del", "jsp siz+1" does the job. 


Best,
Norbert

Norbert Landsteiner

unread,
Jan 22, 2026, 11:15:39 PM (14 days ago) Jan 22
to [PiDP-1]
So, just for fun, here's an annotated version of the source code:

define setup A,B          /macro: set up a loop counter in A for B iterations
        law i B           /load -B (for isp iterations skipping at 0)
        dac A             /deposit in A
term

cwd,    0                 /addr of pattern goes here
        dap cwx           /deposit "return address"
        lac i cwd         /load pattern word (1/2)
        dac \cw           /store it in cw
        idx cwd           /index addr to point to next pattern word (2/2)
        xct i cwx         /execute loc. after call to get x-coor
        dac \x            /store it
        idx cwx           /index pointer
        xct i cwx         /execute 2nd loc after call to get y-coor
        lio i cwd         /load 2nd code word (pattern) into IO
        spi               /skip on sign-bit unset
        sub \2dl          /subtract 2 (drop the character by 2 deltas)
        dac \y            /deposit as y
        idx cwx           /index cwx to actual return address (call+3)
        setup \ctr, 22    /set up a loop over 18 bits
        setup \cty, 7     /loop over 7 vertical positions

d,      setup \ctx, 5     /loop over 5 horizontal positions)

c,      lio \cw           /read pattern
        ril 1s            /roll left for next bit
        dio \cw           /and deposit it again
        spi               /skip if sign-bit unset
        jmp plt           /plot a dot

a,      isp \ctr          /index bit counter, skip if positive (0)
        jmp b             /still parsing, skip forwards
        lac i cwd         /read second pattern word (already incremented before)
        dac cw            /deposit it in cw
        setup \ctr, 22    /set up a loop for the next 18 bits
b,      isp ctx           /index x-counter and skip on 0
        jmp inx           /increment x-coor
        isp cty           /index y-counter and skip on 0
        jmp iny           /increment y-coor
cwx,    jmp .             /return

inx,    lac x             /increment and update x-coor by delta
        add del
        dac x
        jmp c

iny,    lac x             /reset x-coor (subtract 4 deltas)
        sub \4dl
        dac x
        lac y             /increment and update y-coor by delta
        add del
        dac y
        jmp d

plt,    lac x             /display a dot at x/y
        lio y
        dpy-i
        jmp a
                          /init size
siz,del, 2000             /dot delta
        dap sex           /deposit return addr
        lac del           /load delta
        sal 1s            /times 2
        dac \2dl          /deposit in \2dl (2 deltas, char. drop)
        sal 1s            /times 2
        dac \4dl          /deposit in \4dl (4 deltas, x-coor reset)
sex,    jmp .             /return


variables
constants

start

/alphabetic codeword tables

lwr,    374200          000000          /space, printing
        561020          010604          /1
        774040          005056          /2
        564204          203056          /3
        (...)



From this, we may infer the following usage:

                        /initialize
        law 2000        /dot offset (delta in display coors, highest 10 bit significant, here 2 dots)

        jda siz         /configure (set del, \2dl, \4dl)

                        /display a character
        lac (lwr+2      /load code word (addr. of display pattern), here "1"
        jda cwd         /call code word routine
        lac \x          /(re)load x-position (executed from inside routine)
        lac \y          /(re)load y-position (executed from inside routine)
        (...)           /routine returns here

       To position the plot, either explicitly set \x and \y,
       or use actual values for x and y instead of "
lac \x", "lac \y".

Best,
Norbert

Bill E

unread,
Jan 23, 2026, 8:11:15 AM (14 days ago) Jan 23
to [PiDP-1]

Thanks for the analysis, I really didn't want to spend the time to decipher it. However, it doesn't work. Here's what I assembled, and yes, the call to siz is fairly useless. But, don't spend more time on this unless you're motivated, I've spent too much just for playing around, on to some more coding.
Bill
chars.mac

Norbert Landsteiner

unread,
Jan 23, 2026, 10:22:25 AM (13 days ago) Jan 23
to [PiDP-1]
IDK, it does work for me (macro1.c, my own web emulation).
Here a simple test displaying "123" in a loop (at dot delta 2000 = 4 display locations):

codeword-123.png


Is there some fault in the tool chain?
See the attached zip-archive for the source, listing and rim.


Best,
Norbert
codeword-test.zip

Kevin McGrath

unread,
Jan 23, 2026, 4:37:34 PM (13 days ago) Jan 23
to [PiDP-1]
Is this the same character generator as DECUS #1 / BBN-37?  I wish I could find all of the early DECUS memos, seems most are lost...

https://archive.computerhistory.org/resources/text/DEC/pdp-1/DEC.pdp_1.1961.102650098.pdf

Norbert Landsteiner

unread,
Jan 23, 2026, 5:36:54 PM (13 days ago) Jan 23
to [PiDP-1]
No, the DECUS #1 / BBN-37 by  Ben Gurley and Weldon Clark is a different one. It's upper-case only and written in FRAP.
You can find it here (along with an emulated character dump):


(This is a collection of various subroutines and "Codeword Display" is the second to last in this. The very last one seems to be a precursor to this, which does digits only.)

For the entire list see here: https://www.masswerk.at/spacewar/chargen/
(The page is more about the evolution of display character sets and their glyphs, but also provides the source code. "Codeword Display" is the only one of them, which runs out of the box using Macro. BBN-37 should run, but requires translation. The 1964 one requires some clean-up, but may provide some inspiration, as-is. I've used its character set in various instances, e.g., in the Spacewar! emulation.)

Best,
Norbert

Bill E

unread,
Jan 24, 2026, 12:53:59 PM (12 days ago) Jan 24
to [PiDP-1]
Ok, I have characters showing up, but this isn't what you think it is:
cg1.jpg

or this:
cg2.jpg

Why do things in software when you can do it in hardware? Well, ok, virtual hardware.
This is the Type 33 Symbol Generator implemented and running, exactly as it did, down to the timing (I think).
The CHM -1C has this installed, hoping I can verify some choices I made with them.

Now I need to figure out how to get rid of that window bar at the top of the screen, wasn't there when I was using wayland/wayfire, now I'm using wayland/labwd to fix a vnc issue.

Bill
Reply all
Reply to author
Forward
0 new messages