Idiomatic PDP-1 Assembly

161 views
Skip to first unread message

Kevin McGrath

unread,
Jan 20, 2026, 7:17:55 PMJan 20
to [PiDP-1]
I know some are working on a book on the subject, and I'm very interested in that, but in the mean time I think it would be good to discuss various assembly language patterns that are helpful for the PDP-1.  I don't think that there's a "right way" to program the PDP-1, but it's different enough to be confusing at first for experienced modern machine assembly language programmers, so I'd love to see a collection of "how I implemented" specific techniques.

For example, shockingly for me, the PDP-1 doesn't have a stack pointer, so my first thought was "how do I write a subroutine"?  Turns out there's a couple different ways to do it:

A quick a dirty way seems to be to use "jsp" to jump to the routine, saving PC+1 in the accumulator, which then "dap" deposits the return address directly into the "jmp" instruction at the end of the routine to provide a return.

/ Subroutine (non-recursive, trashes accumulator! 20us):
jsp sub / 5us Saves PC+1 in accumulator and jumps to sub
...
sub, dap ret1 / 10us Deposit "address part", return address into the jmp instruction for the return
...
ret1, jmp 0 / 5us Returns after the jsp

This is probably the fastest way to implement a subroutine, but it's non-recursive and the return address must be in the same 4K word bank as the caller.  I also see in some example code the following method with has the advantage of saving the accumulator and allowing for a full 16-bit return address, I think... I haven't actually tried this method in extended mode:

/ Subroutine (non-recursive, saves/restores the accumulator, can handle extended addresses, 40us)
jda sub / 10us
...
sub, (0 / Accumulator storage
dac ret1 / 10us Actual entry point of routine, save return address
...
lac sub / 10us
jmp i ret1 / 10us
ret1 (0

That got me thinking about how to implement a stack, so I thought up a couple macros that might do the trick:

/ Maintaining a stack:

stkptr, . / Stack pointer, needs to be initialized to something like 07777.  Stack grows from top of memory to bottom of memory.

/ Push the Accumulator onto the top of the stack (45us)
define pushac
dac i stkptr
lac stkptr / Post-decrement the stack pointer
sub (1
dac strptr
term

/ Pop Accumulator from the top of the stack (25us)
define popac
idx stkptr / Pre-increment the stack pointer
lac i stkptr
term

/ Copy Top of Stack to Accumulator (40us)
define tosac
lac stkptr
add (1
dap tsp
tsp, lac .
term

Another thing I've been thinking about, what does a template for a typical program look like in assembly?

TESTAPP / Name of program, will be printed on papertape in legible dot matrix text
100/ / Start address (at 100 to skip all of the sequence break vectors?)
appmain, ... / Main program here...
constants
variables
start appmain

So again, I'm not trying to tell anyone how to code (I haven't even tested the code above), I would just like to start a conversation about what the PDP-1 best practices are, or what other peoples solutions are.  It's a quirky machine, I'm loving it!

Thanks!

-Kevin.

Bill E

unread,
Jan 21, 2026, 7:44:15 AMJan 21
to [PiDP-1]
This could be an interesting topic, and also has high flame-war potential.
First, you can probably count on one hand people anywhere in the world that wrote -1 assembler back in the day that are also doing it now, so the pool of deep knowledge is limited.
Second, there will be 2 groups, those that will insist on 'purity' and doing things exactly like they were done 60 yrs ago, and those that try new things.
"Stack? That's totally against the spirit of -1 programming!". Actually not, it seems there was a stack implementation at MIT back in the day.

Having primed the flame pump, here is what I'm doing, and it's definitely a learning process. I haven't written assembler in decades:

I have been using jda almost exclusively because it saves the AC. It does take twice as long, though, 10usec. Also, if you write a macro that is a 'subroutine', how do you know without looking at its code if you do a jda or a jsp? Easier for me to standardize on one way unless it's hidden by a macro.

I have avoided xct. While it was used often, self-modifying code quickly became recognized as a Bad Idea once separate I&D space machines and real operating systems became more common, not to mention memory management. The -1 certainly encourages it, with dap and dip instructions. I don't use the 'dap x... jmp x .... x, jmp .' pattern.

I have started accumulating macros for dealing with bits like extended memory consistently, farjda, farjmp, fardac, farlac, etc. but note that macro and macro1 don't have any direct support for extended memory, including producing loadable tapes that load into extended memory. Unless, of course, there is some hidden magic somewhere. Haven't found any.

Other than that, I'm still learning. One instruction that I'm trying to use more is law, load AC immediate with a 12 bit (or is it 13 bits or 14 bits? Still haven't tracked that down). Saves a memory location and a cycle.

Now for the controversial bit. Regardless of any "it was revolutionary for the time" discussion, macro is really poor and error prone. 3 character symbols, symbol redefinition just by declaring a symbol more than once, and the horrible 'space means add', which leads to tortuous and incomprehensible code like 'spa+xyz-qrs...' to get the summed keywords to add up to the actual instruction you want. Not to mention very poor error checking. Try "flexo AbC' and see what you actually get. Macro1 improved things slightly, 6 chars for symbols and some more operators. There was also as assembler used at MIT and authentically -1, 'certainty', which improved things even more, but it seems to have vanished.

I've written a couple of thousand lines of assembler so far, and my goal isn't to see how closely I can do (or suffer thru doing) things 'the original way'. I want to be productive. Hence my new assembler, am1. That's what I'm using exclusively now, and the rim tapes it produces load and run just like 'real' ones. One driver was support for extended memory both in the code and in the loader. Long symbol names. No symbol magic redefinition. Local and global symbols. Error checking (gasp). Pseudo-linkable separate programs. Yes, pure evil.

Back to the topic, writing assembler for the -1 is certainly great fun and also a challenge. I've had to learn mostly the hard way how extended memory works, how the sbs system works, how that interacts with IOTs, etc. Some of it required digging into the emulator implementation, and I usually cross-check the pidp-1 impl with the simh one. For example, did you know that you can combine multiple operate instructions? There is a defined execution order. Where is that documented? Nowhere I could find without looking at the emulators.

BTW, that's where all the tortuous macro coding mentioned above comes in. I really don't understand why anyone thought 'cli cla clf' should mean 'cli+cla+clf'. The -1 has an or instruction, trivial change in the assembler. Am1 assumes that means 'cli | cla | clf', which makes far more sense to me. "But that will break existing code!". Mostly not, the expression with explicit adds and subtracts work fine. The original macro way guarantees you're going to write some code that isn't going to do anything like you thought it would and you'll pull your hair out trying to figure out why. I learned this from experience.

I suppose we should encourage anyone writing assembler for the -1 to start accumulating a list of tricks, knowledge, etc. to make life easier for everyone. Everyone writing assembler that is, which will still be a small pool. I'll try to remember to do so.

One final comment, it's great that we have a display implementation, tape reader and punch, soroban typewriter. But, how do you really communicate with the outside world? How do you store data? The real -1's had drums and mag tapes. There was DCS, which supported multiple serial devices (apparently mostly teletypes). That's why I implemented the Type 23 drum and a modernized (but still compatible) DCS. Using those, it's certainly feasible to have a web server, written in -1 assembler, running. Shades of modernity! Haven't tackled mag tapes or dectapes. Someone else, step up. Oh, and that's the reason I implemented dynamically-loaded IOTs, didn't want to have to hack the emulator every time I added something.

Flame on,
Bill

MICHAEL GARDI

unread,
Jan 21, 2026, 2:03:51 PMJan 21
to [PiDP-1]
I think this is a great idea Kevin. I started writing PDP-1 code about 7 weeks ago. Prior to that I hadn't written in assembler since the early 80s, mostly 6502 on an Apple //e.  After a bit of a slow start I really started to enjoy myself. 

Now we are all going to be approaching the PDP-1 from different directions. Bill's interest seems to be tool building and extending the emulator to encompass more of the PDP-1's ecosystem.  Kevin you seem to want to enhance the PDP-1's programming environment with more up-to-date concepts and features.  (Please correct me if I've misspoken here.) My ultimate goal is to have some piece of code that I have written run on the CHM's PDP-1. I guess that I am in the "purist" group that Bill mentions, not because I am a zealot, I simply want to make sure that my code works as expected on a real "bare bones" PDP-1.

Never the less I appreciate the contributions that have been made here so far. Kevin if I were to write a program that would greatly benefit from re-entrant code I know just where to start with your stack macros.  And Bill you have got me thinking more about jda vs jsp (I have only used jsp so far).

Most of the "patterns" that I have picked up come from this document, Retrochallenge 2016/10: Ironic Computer Space Simulator (ICSS), which links to Norbert Landsteiner's entry to Retrochallenge 2016/10, where he implements a version of Computer Space on the PDP-1. Computer Space was the very first coin operated video arcade game introduced by Nolan Bushnell in 1971. This was a perfect fit for the style of code I was looking to implement.  I have written about some of the patterns that I ended up using in my Lunar Lander for the PDP-1 blog. I would be happy to bring some of those over to this thread if you all thought it would be of some benefit, or maybe there is another place we cold post "tips".

Mike

Bill E

unread,
Jan 21, 2026, 3:17:08 PMJan 21
to [PiDP-1]
Not exactly an assembler tip, but I printed and laminated 3 cards, the PDP-1 Instruction List that's floating around, a sheet with Flex/Concise codes on one side, ASCII on the other, with a list of character replacements for the f/c chars that have no equivalent, and an IOT cheatsheet with information about the common IOTS I use, as well as the -1D extended instructions. It sits on my desk right beside me. I find this very useful.
Bill
Message has been deleted

Kevin McGrath

unread,
Jan 21, 2026, 4:25:32 PMJan 21
to [PiDP-1]
Yes, I've gotta have a laminated cheat sheet or it isn't assembly programming!  I laminated a legal sized printout of this one:

http://www.bitsavers.org/pdf/dec/pdp1/F16A_PDP-1_Instruction_List_196307.pdf

Folded in half, it makes for a great cheat sheet you can just flip over from the Instruction List to see details of the various instruction groups (shift, operate, skip) and a simplified I/O list.  Plus a FIO-DEC and Concise Code table.

Best yet, it seems to be an official cheat sheet from 1963.

Kevin McGrath

unread,
Jan 21, 2026, 4:28:07 PMJan 21
to [PiDP-1]
Idiomatic is probably the wrong word.  Maybe "PDP-1 Assembly Patterns" is closer to what I was wishing to discuss.  Or "Programming Tips and Tricks".

The PDP-1 must've certainly been a target for flame wars, perhaps even early Big Endian / Little Endian issues (why is the high bit "bit 0" and the low bit "bit 17"?).  While I do enjoy a good flame war, it was not my intent to start one with this conversation.

Bill, I didn't know about the "space" being the same as addition in Macro.  Is that just for the "operate group" of instructions?  Is it just me, or does that set of instructions feel more like microcode than normal machine instructions?  The Y field becomes some sort of or'ed together bit field that lets you do parallel initialization of the accumulator, I/O register and program flags, all in one 5us cycle.  I also didn't know about the 3 character long symbol uniqueness limitation, is that just for Macro on the PDP-1 or does it include the macro1 assembler?  I thought it was 6 characters, 3 is just painful.

It took me a bit to understand that open-parenthesis "(" pretty much means that this expression is a constant, but I keep seeing tilde "~" used and it confused me until I learned about what macro calls "variables".  If you use a tilde before a symbol for the first time, it assigns it to the uninitialized "variables" section of the program.  I don't know of another assembly language that does that, pretty wild.

I'm at the stage in my learning now where I want to implement a full program, so I'm trying to learn basic programming techniques.  Like, don't start your program at 100 because it interferes with the cal instruction, yet another way to call a subroutine (but, why?!).

For some reason I've been thinking a lot about character strings.  Should we be saving them with a zero terminated word ala C style, or is there a way in Macro / Am1 to calculate and save the string starting with its word length?  I mean, the PDP-1 should be able to run Colossal Cave Adventure, which could be terrific fun playing on the "Computeriter" / Sorboban, but you're going to need a string library I would think.

And Heaps!  A nice simple and small memory manager that used the memory between the end of the program/variables/constants and the stack, may be welcome.  Wow, is it time to actually crack open the Knuth books again?  I don't think I've done that since the '90s!

Thanks much for the links Mike!  I briefly checked out the MassWerk site before I began building my kit, but now I think I should just go absorb all those learned details Mr. Landsteiner figured out.

-Kevin.

MICHAEL GARDI

unread,
Jan 21, 2026, 4:49:50 PMJan 21
to [PiDP-1]
OK I'll kick in a pattern that I used, "Jump Table".  Lunar Lander required quite a bit of "text" (mostly numbers) to show the game status (Fuel, Score, Vertical and Horizontal Velocities). Because the name of the game with a Type 30 display is to plot as many dots on the screen on a per frame basis as you can to avoid "annoying" flicker (there is always going to be some flicker but there definitely is a threshold that you notice when you cross it) displaying a number had to be as fast as possible. So to that end I "borrowed" a trick from the Ironic Computer Space Simulator (ICSS) and "compiled" ten 5x7 bitmaps of the digits ( 0-9 ) into machine code.  Actually ICSS did this as part of program initialization with an assembly language routine to encode and add the machine code directly into memory at the end of the program. Pretty cool. My approach was slightly different. I encoded those same 5x7 bitmaps into assembly language using a Python Script and incorporated the resulting code into my program.  Not very "pure" but I could have done this by hand (and many hours of effort) so I didn't feel too bad about cheating. Here is what the code looked like:

/ Digits 0-9 compiled to assembler code. Used when displaying a multi digit number
/  on the screen. Starting point in (dgx,dgy). Exit with jmp to ngd which is in the calling routine.
c0, lio dgy / 0
lac dgx
add (2000
dpy-i 4200
add (2000
ioh
dpy-i 4200
add (2000
ioh
        ...
ioh
dpy-i 4200
jmp ndg

c1, ...

So basically a linear set of adds and subs to the x and y offsets followed by dpys to plot the points.  But that's not the pattern it's just a tip. The pattern starts here with a jump table pointing to the 10 digits.

/ Table of digit display addresses. Used to lookup the address for a particular digit
/  based on the digit index (0-9).
dtb, c0
   c1
   c2
   c3
   c4
      c5
   c6
   c7
   c8
   c9

Then the code to display a particular digit looks like this:

/ Display the digit in remainder. Offset in rem.  Starting point in (dgx,dgy).   
d1g,  law dtb / Load the address of the digit address table.
   add rem / Add the offset of the character we want.
   dap . 1 / Save the combined address into the next instruction.
   jmp i .    / Jump indirect to the correct character code.
          / Return will be jump to ndg.

/ Get ready for next digit.
ndg, lac dgx / Shift x coordinate to left.
...

I will admit that this code took me a couple of tries to get right.

Mike

Kevin McGrath

unread,
Jan 21, 2026, 6:09:35 PMJan 21
to [PiDP-1]
  Huh!  Interesting way to do a jump table on the PDP-1.  This is exactly the kind of thing that helps me progress my understanding of how the machine works.

  I've seen it before, the dpy-i 4200, but what does it do that's different from dpy-i?  Is the 4 the bit that changes the origin, and the 2 maybe the brightness of the electron beam?  Does changing the origin make you loose precision (i.e. get 512 pixels instead of 1024)?  The dpy-i turns it from 730007 to 720007 (turns off the automatic wait), then adding 4000 to that moves the origin from the center to the bottom left?  Oh course the manual just says that the Y field is ignored, but they clearly added some features on later machines.  I thought it was dpy 3000 to move the origin.  Oh no!  I guess I'll need to write a program to learn more about how it works.  LOL!

-Kevin.

MICHAEL GARDI

unread,
Jan 21, 2026, 6:44:31 PMJan 21
to [PiDP-1]
Ya the dpy stuff is pretty confusing. dpy-i is the "no-wait" version of the call. With dpy-i 4200 the wait bit gets set (the 4) and you have to have an ioh in you code to to "catch" the wait over. The 2 in this call is the brightness. dpy-i 3000 changes the origin to the lower left corner of the screen,  BUT PiDP-1 does not implement this yet. I have raised the issue on the PiDP-1 github. 
The dpy instruction (73cb07) causes one point to be displayed on the scope. (...) The three "b" bits control the brightness -- 4 is visible to photomultiplier tubes only, 7 is barely visible in a dark room, 0 is normal, and 3 is brightest. The "c" bits control the centering. 0 makes the origin in the center of the scope. 1 puts it at a the center of the bottom edge. 2 makes the origin be half way up the left edge, while 3 puts it at the lower left corner.

So, if c = 3, as in “dpy+3000”, the origin will be located at the lower left corner. 

Being able to move the origin to the lower left corner of the screen would have really simplified my LL code.

Mike 

Bill E

unread,
Jan 21, 2026, 6:47:45 PMJan 21
to [PiDP-1]
Replying to several things:
Mike, the jump table is cool, and it shows how back in the -1 era self-modifying code was used a lot. I will admit, I have used the dap, jmp construct myself, even though I commented against self-modifying code. It was the way things were done back then.

Character strings - macro provides 2 methods, flexo abc or the text 'some flex text'. The first one is just one word (but be careful, flex/concise uses shift chars, so flexo AbC will absolutely not do what you think it does. The text version is multiword, packed 3 chars per word. But,, there is no terminator. You just have to know. And, putting a null 'byte' doesn't work, binary 0 is a valid flex char, space. So, I put a following word after text with octal 13, which is a 'stop code'. 
Am1 supports those 2 exactly as they work in macro, but adds ascii 'an ascii string', which packs 2 ascii chars per word and DOES put a null byte at the end. Ascii existed back then, and from what I can tell, teletypes were connected to -1's, and they used ascii. That had to have been done by just dealing with it in code.

Flexo/concise to/from ascii is a pain, you have to use up a lot of space with mapping tables. I'll now admit to a total hack, I added an operation to the DCS implementation to convert a character to/from each, a total misuse of what an IOT did, control hardware. Although someone could have implemented a hardware translation table and accessed it via an IOT, so I'll claim that's what I did. :)

Going back to the beginning, here's an actual programming tip for the stack code. Don't do an add of 1. lac x, add (1), dac x uses 4 words and takes 30 usecs. Instead, use idx x. One word, 10 usecs. It increments x and leaves the incremented value in AC. Unfortunately, there is no decrement instruction, so you're stuck with sub for that one. Unless you have a PDP-1X, of which there was only 1. I'm pretty sure it added a decrement, as well as a number of new instructions that really made it not a -1.

Bill

Bill E

unread,
Jan 21, 2026, 6:51:16 PMJan 21
to [PiDP-1]
I guess that unless Angelo adds the dpy origin stuff, I will. It's a very minor change, but it is in the emulator code.
Bill

Bill E

unread,
Jan 21, 2026, 6:56:37 PMJan 21
to [PiDP-1]
As for macro treating space as add, from the macro manual
mac.jpg
So, jmp 100 is the same as jmp+100.
Bill

Norbert Landsteiner

unread,
Jan 22, 2026, 4:35:04 AMJan 22
to [PiDP-1]
Regarding simulating a CPU stack, I wrote something along the line back in – whoops, about 10 years ago. More as an exercise, never tested.

/ PDP-1 macros to emulate a cpu stack (nl, 2016)

/ init the stack pointer
define  stkinit
        lac (777700             / stack grows up towards 0777777
        dac \tos                / top of stack
        terminate

/ push a return address and continue at address A
/  18 cycles
define  jpsr A
        dac \act                / backup ac
        lap                     / transfer contents of program counter (pc) into ac
        dac i \tos              / store it (indirect) as return address
        idx i \tos              / increment it to point to next memory location
        idx \tos                / increment tos
        law A                   / load target address (absolute)
        dac \jpv                / set it up in a temporary jump vector
        lac \act                / restore AC
        jmp i \jpv              / jump (indirect) to subroutine
        terminate

/ pull a return address and continue there
/  16 cycles
define  return
        dac \act                / backup ac
        law i 1                 / decrement tos (tos = -1 + tos)
        add \tos
        dac \tos
        lac i \tos              / fetch (indirect) the return address
        dac \jpv                / set up a jump vector
        lac \act                / restore ac
        jmp i \jpv              / jump (indirect) to return
        terminate


/ usage example (neither economical nor reasonable)

ex1,    stkinit
        lac val
        jpsr by2
        dac res
        hlt

by2,    sal 1s
        return

val,    3
res,    0



(There's even a relocatable version, see the above link, but this would be rather slow, as about every second instruction involves indirection.)
But I honestly do think that doing it bare metal is much more fun.

Best,
Norbert

Angelo Papenhoff/aap

unread,
Jan 22, 2026, 7:22:14 AMJan 22
to [PiDP-1]
I think i can add it. would be nice to know more about these display options because the type 30 schematics that i was referencing don't have any origin shenanigans.

Bill E

unread,
Jan 22, 2026, 7:36:37 AMJan 22
to [PiDP-1]
It seems that not all Type 30's had this, I don't know if it was an option, a field mod, or a change in later versions of the display. I took a look at pdp1.c, seems it could be done just by doing some coordinate shifting in case 007 in the iot processing. I imagine that the real version implemented this in the display controller hardware. As usual, I couldn't resist playing, I'm figuring out the 1's complement math for shifting now, but the entire change should just be a few lines of code. Angelo, I won't check in any changes I make, but I'll show the changes here if I actually make them.
Bill

MICHAEL GARDI

unread,
Jan 22, 2026, 7:47:23 AMJan 22
to Bill E, [PiDP-1]
Is there any way to know if the PDP-1 at CHM supports origin relocation? All I really know is that the mass:werk emulator does based on the behavior of snowflake. 

Mike

Sent from Gmail on my iPhone


On Thu, Jan 22, 2026 at 7:36 AM Bill E <wjegr...@gmail.com> wrote:
It seems that not all Type 30's had this, I don't know if it was an option, a field mod, or a change in later versions of the display. I took a look at pdp1.c, seems it could be done just by doing some coordinate shifting in case 007 in the iot processing. I imagine that the real version implemented this in the display controller hardware. As usual, I couldn't resist playing, I'm figuring out the 1's complement math for shifting now, but the entire change should just be a few lines of code. Angelo, I won't check in any changes I make, but I'll show the changes here if I actually make them.
Bill

--
You received this message because you are subscribed to the Google Groups "[PiDP-1]" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pidp-1+un...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/pidp-1/54848ab7-e4cc-42dd-b7ca-cb81a6573dadn%40googlegroups.com.

Bill E

unread,
Jan 22, 2026, 8:22:17 AMJan 22
to [PiDP-1]
Sure, just ask them. I don't have an email addr, but it's on their page. Or, I know that there is some monitoring of this group by CHM,  might get a direct answer here.
BBill

Bill E

unread,
Jan 22, 2026, 8:26:31 AMJan 22
to [PiDP-1]
Could the code change be as simple as this? 1's complement math messes with my head sometimes. Basically, the coordinates need to be shifted by -512 and wrap around. I haven't tried this, I guess I will.

From the type 30 description:
The dpy instruction (73cb07) causes one point to be displayed on the scope.

The three "b" bits control the brightness -- 4 is visible to photomultiplier tubes only,
7 is barely visible in a dark room, 0 is normal, and 3 is brightest.

The "c" bits control the centering.
0 makes the origin in the center of the scope.
1 puts it at a the center of the bottom edge.
2 makes the origin be half way up the left edge.

3 puts it at the lower left corner.

The code change in pdp1.c:

case 007: // dpy
if(!pulse) {
pdp->dbx = 0;
pdp->dby = 0;
pdp->dint = 0;
} else {
pdp->dcp = nac;
pdp->dbx |= AC>>8;
pdp->dby |= IO>>8;
#ifdef DPY_SHIFT_ENABLED
                        if( ch & 010 )      // origin at bottom
                        {
                            pdp->dby ^= 01000;
                        }
                        if( ch & 020 )      // origin at left
                        {
                            pdp->dbx ^= 01000;
                        }
#endif
pdp->dint |= (MB>>6)&7;
pdp->dpy_defl_time = pdp->simtime + US(35);
pdp->dpy_time = pdp->dpy_defl_time + US(15);
}
break;

Bill

Bill E

unread,
Jan 22, 2026, 9:21:11 AMJan 22
to [PiDP-1]
Amazing, yes, it was that simple. Angelo, I have not checked this change in. Let me know if you want me to do so in my branch, or if you want to add it to the main branch.
Here's a test prog. Yes, it's in am1, but the rim loads just like any other one. I really should test negative coords, but I didn't. Probably works. :)
Bill
dpytst.am1
dpytst.rim

MICHAEL GARDI

unread,
Jan 22, 2026, 10:45:08 AMJan 22
to [PiDP-1]
Another PDP-1 assembler pattern, "Switch Statement".

The opcodes sad and sas are quite cool. Sad will skip the next instruction if the AC does not equal a value in memory, and sas will skip the next instruction if they are equal. Both leave the AC unchanged. So if you want to execute different pieces of code based on a specific value you can do the following.

        lac val        / Load the value to switch on.
        sas (1         / Is val 1?
        jmp v2         /   No - check for 2.
        / Value 1 code goes here.
        jmp end
v2,     sas (2         / Is val 2?
        jmp v3         /   No - check for 3.
        / Value 2 code goes here.
        jmp end
v3,     sas (3         / Is val 3?
        jmp def        /   No - do default code  
        / Value 3 code goes here.
        jmp end
def,    / Default code goes here.
end,

Mike

Bill E

unread,
Jan 22, 2026, 11:15:31 AMJan 22
to [PiDP-1]
A related tidbit, the skip group instructions, sma, spa, etc. can have their sense reversed by setting the indirect bit, e.g. smi, skip on minus becomes skip if not minus when smi i is used.
This doesn't apply to sad and sas, though. They are memory reference instructions and so use i to actually mean indirect.

Bill

MICHAEL GARDI

unread,
Jan 22, 2026, 12:28:12 PMJan 22
to [PiDP-1]
Good point on the indirect bit when skipping. I find sza -i (skip if not zero AC) especially useful.

Mike

Bill E

unread,
Jan 22, 2026, 1:05:43 PMJan 22
to [PiDP-1]
Here's a common pattern used for passing arguments to subroutines when you need more than just the AC and IO. I've used it, and just came across some original code that does.

Use:
jda foo
arg1
arg2
...

You implement like:
foo, 0
     dac rtnaddr  // will be the addr of the word following the jda, addr of arg1

     lac i rtnaddr
     dac a1
     idx rtnaddr

     lac i rtnaddr
     dac a2
     idx rtnaddr
     for as many args as you have
     
     when it's time to return to caller. rtnaddr will have been incremented to 1 past the last arg
     jmp i rtnaddr
rtnaddr, 0

Bill

Norbert Landsteiner

unread,
Jan 22, 2026, 7:47:19 PMJan 22
to [PiDP-1]
Regarding inverting conditions with the i-bit, similar applies to the program flags: while `stf n` sets flag n, `stf i n` clears flag n (where n: 1…6).
Also, 7 (as in `stf 7`) applies the set/unset state to all flags at once.

Norbert

Bill E

unread,
Jan 22, 2026, 8:30:59 PMJan 22
to [PiDP-1]
Another tip/pitfall - this only applies to using extended memory. 

The way you access locations in another bank is to enable extended memory, eem, then make an indirect reference using a full 16 bit address, such as jmp i x, x, 030010, location 10 in bank 3. 

BUT, be careful with jda, it does not work for this. Why? Because DEC stole the i bit for this instruction to differentiate it from cal, which has the same base opcode.
I wrote a macro to simulate it working properly.

Bill

Kevin McGrath

unread,
Jan 23, 2026, 3:48:01 PM (13 days ago) Jan 23
to [PiDP-1]
Bill, I wonder if the intent of the cal instruction was to provide some sort of OS call?  Does it always jump to 00100 no matter what bank/mode you're in?  So maybe the OS can fetch the Y field of the calling instruction like so:

100, . / AC stored here upon call
sub (1 / Point to the calling instruction
dap rtnadr
eem
lac i rtnadr / Fetch the Y field of the calling instruction
lem
dap func
idx rtnadr / Point back to the return address
...
jmp i rtnadr
rtnadr, .
func, .

/ Example OS call
lac arg1
cal (funcnum

Makes for a pretty clean operating system call, I would think, that is, if it works like I think it does.

Bill E

unread,
Jan 23, 2026, 4:10:48 PM (13 days ago) Jan 23
to [PiDP-1]
There has been a fair amount of discussion and confusion about cal. Yes, it seems the intent was to have some kind of call dispatcher, but it wasn't clear how it interacted with eem.
Seems no one has has the time (or interest) to check it out.
Bill

MICHAEL GARDI

unread,
Jan 24, 2026, 11:31:56 AM (12 days ago) Jan 24
to [PiDP-1]
I asked whether the CHM PDP-1 supported origin relocation, and Bill wrote:
 
    Or, I know that there is some monitoring of this group by CHM,  might get a direct answer here.

Sure enough Michael Cheponis was kind enough to send me an email. 

     Mike

     No.  It does not support origin relocation.  Or, rather, if it does, I've never seen a program use that feature.

     Seems to me it would take a little bit of logic to do that, and I don't remember seeing that logic.

     There IS a switch that moves the origin on the back, but we've not seen it documented nor have we ever used it, to my knowledge.

     I can ask others whether they know anything about origin relocation.

     -Mike

So it looks like no ""programmatic" origin relocation.  I appreciate Mr. Cheponis talking the time to respond.


Mike

MICHAEL GARDI

unread,
Jan 25, 2026, 1:17:57 PM (11 days ago) Jan 25
to [PiDP-1]
OK this is probably the most egregious example of self modifying code that I can think of, nevertheless it was quite useful.

In the normal course of the game, the LEM gets redrawn about 10 times per second while in motion, so the dots get displayed on the screen at the lowest intensity (7) or else the LEM appears overly bright and "streaky" in the Type 30 display.  On a good landing the LEM gets drawn much less frequently so I wanted to "up the brightness" of the dots. The dpy instruction is at the heart of the LEM drawing routine and I didn't want to slow things down by adding code around it.  So I created a couple of "variables":

dp7, dpy-i 4700 / dpy with minimal brightness.
dp3, dpy-i 4300 / dpy with maximum brightness.

Threw a label on the LEM drawing dpy:

nb7, dpy-i 4700

Then added the following to the beginning and end of good landing code where I wanted the LEM to be brighter:
        
       lac dp3 / Set to draw with maximum brightness.
    dac nb7
        ...
       lac dp7 / Set to draw with minimum brightness.
    dac nb7

I think I've earned the "Idiomatic" term the title of this thread implies.

Mike

Bill E

unread,
Jan 25, 2026, 1:27:56 PM (11 days ago) Jan 25
to [PiDP-1]
That's quite clever. Being able to modify code on the fly does have its uses.
Bill

Glenn Babecki

unread,
Jan 25, 2026, 1:30:29 PM (11 days ago) Jan 25
to Bill E, [PiDP-1]
I guess that's why "man invented more memory." 😉

--
You received this message because you are subscribed to the Google Groups "[PiDP-1]" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pidp-1+un...@googlegroups.com.

Kevin McGrath

unread,
Jan 26, 2026, 8:08:36 PM (10 days ago) Jan 26
to [PiDP-1]
  I can't let it slip by... that dap rtnadr should be a dac rtnadr.  It would need to know about all of the return address bits/bank info to be able to return to a different bank, right?  I should write up some test case code to see if this actually works, though I may have to use am1 to be able to load code into other banks, right?  I don't think macro lets you do that, does it?

-Kevin.

On Friday, January 23, 2026 at 12:48:01 PM UTC-8 Kevin McGrath wrote:
Bill, I wonder if the intent of the cal instruction was to provide some sort of OS call?  Does it always jump to 00100 no matter what bank/mode you're in?  So maybe the OS can fetch the Y field of the calling instruction like so:

100, . / AC stored here upon call
sub (1 / Point to the calling instruction
dap rtnadr
eem
lac i rtnadr / Fetch the Y field of the calling instruction
lem
dap func
idx rtnadr / Point back to the return address
...
jmp i rtnadr
rtnadr, .
func, .

/ Example OS call
lac arg1
cal (funcnum

Makes for a pretty clean operating system call, I would think, that is, if it works like I think it does.

Bill E

unread,
Jan 27, 2026, 6:57:07 AM (10 days ago) Jan 27
to [PiDP-1]
No, macro doesn't deal with extended memory, the bin loader knows nothing about it. This was one of my big drivers for my new assembler, which fully supports extended memory.
I encourage yo to dig more into cal, we need to rediscover details like this.
Bill

MICHAEL GARDI

unread,
Jan 29, 2026, 7:26:52 PM (7 days ago) Jan 29
to [PiDP-1]
There is another PDP-1 feature, Flags, that can be used in your programs. The DEC PDP-1 computer has six independent "program flags," which are user-addressable flip-flops that function as software-controlled switches or synchronizers. The are controlled by the opcodes:
  • stf n - Set the selected program flag where n is a number 1-6.
  • clf n - Clear the selected program flag where n is a number 1-6.
  • szf n - Skip the next instruction if program flag n is zero where n is a number 1-6.
While addresses 1 through 6 select individual program flags, address 7 is a special case that selects all program flags simultaneously.

IMG_3557.jpeg

In addition these flags are mapped to 6 lights on the Console. I used flags to control access to various sections of LL code , but I wish I had been more aware of Flags in my early development stage as they would have been an easy way to display runtime debugging "codes".

Mike

Reply all
Reply to author
Forward
0 new messages