On this page I want to collect examples of neat, clever, interesting, insightful or otherwise surprising uses of PDP-6/10 assembly language. This includes uses of the instruction set, the system architecture and the assembler (MIDAS or MACRO).
The source of the examples has only been MACDMP so far but I hope more will come as I read more code. If you want to add to this, tell me (mail, irc, ...).
Instructions are of the form OP AC,@Y(X), where AC is an accumulator, @ the indirection bit, X an index register and Y a 18 bit value. The last three are used to calculate the effective address (E).
When the indirection bit is set for an instruction, the word at the effective address is fetched and the effective address calculation is started again on this new word.
This is very different to how the indirection bit works on some other architectures (the PDP-4 and -5 family for instance) where it just adds one indirection, not a loop. Consequently the way to think about this is that the effective address is the address of another instruction and with the indirection bit you're using the effective address of that instruction. This sounds trivial, so I'll illustrate with an example (from MACDMP):
JSP P,UWAIT MOVE CKS,D ;OUTPUT HEADER ... UWAIT: ... DATAI DC,@(P) ...
JSP sets P to the return address and jumps to UWAIT which reads a word from DECtape. (P) is the return address and with indirection DATAI will read the word into whatever is used as the effective word by the instruction after the JSP, in this case D.
Not all instructions use all fields, so you can use them to hold data for other instructions. Examples from MACDMP:
SKIPA A,C15 SKIPA A,BELL .... C15: TRNA 15 ... BELL: AOJ A,7
TRNA and AOJ don't use E, so it can be used to store some ASCII values. SKIPA is also used in a clever way here. Since the condition is always, E is not used for the test. But if AC != 0, it will be loaded with C(E). So this is an unconditional skip with a load using an unused field of another instruction.
On the PDP-6, addresses 0-17 are used as fast accumulators normally. The core memory with the same addresses (called shadow memory) is used by read-in mode to bootstrap from a device (DECtape usually). Starting the machine in read-in mode will disable the fast ACs, which makes the shadow memory shine through, until the program counter becomes > 17 (octal).
If you still want to have access to shadow memory (for instance to install a read-in loader), that can be done! Since it's the virtual address that decides whether to address the fast ACs or shadow memory, the trick is to use a virtual address that doesn't address the fast ACs but have it wrap around into shadow memory in user mode using relocation. The following example is slightly adapted and cleaned up from MACDMP:
REL=36000 LOC 1 BAR: 777777,,REL ;DATAO TO PROCESSOR (RELOCATION & PROTECTION WHILE IN USER MODE) LOSS: LOADER,,SHAD ;BLT POINTER INTO SHADOW LOC 41 JSR 16 ;EXIT FROM USER HACK LOC 37176 ; This load address is essentially arbitrary, but above REL ; This is executed in user mode, so adjust addresses for relocation OFFSET -REL FOO: BLT LOSS,SHAD+LODEND-1 0 LOADER: ; address of source words in user mode OFFSET 0 OFFSET -REL-. SHAD: ; address of shadow memory in user space OFFSET 0 OFFSET -. ; assemble code at 0 ... ; Here comes the code we want to put into shadow memory ... LODEND: OFFSET 0 WHOLE [ DATAO BAR JRST 1,FOO]
The code inside the WHOLE macro is executed word by word by the paper tape RIM loader (see below). The DATAO sets up relocation by REL and gives us 256kw virtual memory. Note that REL+777777 is more than an 18 bit address can hold and so will wrap around. The JRST jumps to FOO in user mode, where we do nothing but BLT the shadow loader into shadow memory. The way the addresses are calculated is a bit confusing. The crucial part is OFFSET -REL-., which gives us the virtual address of shadow memory in user mode, in this case 742000. Since it does not address an AC in virtual memory, the fast ACs will not be addressed by the memory controller, relocation will wrap it around to 0 and we'll address shadow memory!After the BLT we execute a UUO which will cause transfer to 41 in exec mode. There we jump to 17 (16 will be clobbered), which has been set up to contain a SKIPA into the paper tape RIM loader previously.
The priority interrupt system on the PDP-6 and -10 is an interesting beast. It's quite simple in theory but understanding how it behaves exactly is not always quite obvious. The 340 display driver in ITS uses an interesting hack that revealed a bug in the KA10 emulator.
We have the following code in memory:
50: JSR LPTBRK 51: JSR DRECYC 54: BLKO DIS,DBLKOP 55: CONO PI,4010
When the 340 display needs data, it will raise a PI request on channel 6, and a BLKO will transfer the next word of the display list. A non-overflowing BLK instruction will accept (hold) the interrupt and immediately dismiss it again.
What happens when the BLKO overflows is interesting: somewhat counterintuitively an overflowing BLK instruction does not even hold the interrupt and consequently does not dismiss it either. Instead the machine enters a PI overflow cycle (PI OV) and executes the second instruction. It is the second instruction that will then hold the interrupt, unless it's an IO instruction.
Normally the second instruction will be a JSR to the service routine, but in this case the CONO requests an interrupt on channel 4. This has two subtle implications: First, the interrupt is not held because a CONO is an IO instruction. Secondly, because the interrupt is not held, PI OV is not cleared, so the machine will stay in the overflow cycle, but now for channel 4. This means the next instruction executed is the one at 51 (50 is never executed here at all), which actually handles the BLKO overflow. When it dismisses the channel 4 interrupt with a JEN, the original PI request on channel 6 has still not been accepted, so the processor will execute the BLKO at 54 again. This time there is no overflow and execution continues as normal.
To write raw words to the output file (as opposed to RIM or SBLK format) which is necessary for some MACDMP hacks, MIDAS has a WORD statement. With some clever macros you can use this to write assembly more comfortably. This is again from MACDMP, but reformatted for clarity:
DEFINE WHOLE A IF2,[ REDEF IRPC X,,[A] INFO REDEF,[X] IFSE [X][^M][ INFO OUT REDEF ] TERMIN ] TERMIN DEFINE REDEF %B DEFINE INFO %Y,%A %Y [%B!!%A] TERMIN TERMIN DEFINE OUT Q WORD Q TERMIN
It's then used like this:
WHOLE [ HRLZI 2,(JRST) HRLI 17,(CONO PTR,) HRRI 17,60 MOVEM 17,20 HRLI 17,(CONSO PTR,) HRRI 17,10 MOVEM 17,21 MOVEM 17,24 ... ]
IRPC will assign to X consecutive characters of the string A. Then for every character, INFO REDEF concatenates it with the current string %B and redefines the result as %B. REDEF without arguments clears %B.
When the character is CR (end of line), the string %B is passed to OUT, which is really just WORD in this case, to assemble %B and write the resulting word into the output file. Then the string is cleared and the next line is read.