How does effective address calculation work on the PDP-10?

My understanding is that the instruction code contains an 18-bit address or offset, one bit for indirect, and a register code that's 4 bits. This probably means that the offset is added to the register, and the sum is the address (or the address of an address, depending on the indirect bit). And since you can't add an offset to R0, I guess that's somehow special cased to encode absolute addresses.

My confusion is about what happens next. It's gotta fetch that word from memory, right? Is that word actually the datum that's treated in whatever way by the instruction? Or is this word again treated in the same way as that 23-bit part of the instruction, adding the offset to a (possibly different) register, and maybe keep going like that?

## TL;DR: Yes

A (calculated) address (from an instruction) is always used to fetch a word (36 Bit), which, if the I bit was set, is interpreted again the same way - thus allowing indefinite addressing chaining - otherwise used at face value for the operation.

All instructions always an effective address, usually an operand in memory (*1)

As you already described, the effective address is calculated by using the three address fields of an instruction to calculate an 18 bit effective address (*2):

• The indirect bit (I).
• The 4 bit index field (X),
• The basic 18 bit address field (Y)

If the X field is zero, the effective address is simply the contents of the address field (Y).

If the index field X is nonzero, then it names an accumulator to use as an index register. Any accumulator except 0 can be so used. The lower half of the contents of that register is added to the address field (Y) from the instruction.

A set I bit now specifies indirection. If set the first step is taken as before, but the resulting (18 bit, no overflow), is used to address a word (36 bit that is). Of this word the bits 13..35 (the lower 23 bits) are interpreted exactly as if taken from an instruction

*1 - For immediate instructions, it's not a memory address memory, but used directly, as a operand. Like ANDI, ADDI, MOVEI, etc.

*2 - The whole instruction format is

• 9 bit OP
• 4 bit Accumulator
• 1 bit Indirect
• 4 bit Index
• This means 1) you can not point to 36 bit data 2) linked list traversal might require that specific value(s) is kept in specific register(s) Commented Nov 7, 2018 at 13:01
• @Wilson 1) Why not? 2) Why? (and which value?) Commented Nov 7, 2018 at 13:05
• 1) because the upper 18 bits of the word overlay the Indirect and Index fields. 2) because with each level of indirection, it's possibly adding an offset to a different register. So it's possible to create a data structure which behaves completely differently depending on register contents. Commented Nov 7, 2018 at 13:17
• @Wilson That's why I did the footnote about immediate - the address generated is the value. No indirection used. The I-bit gets ignored when immediate is used. What else. And regarding the instruction being an address, that's how every CPU with memory addressing works. On a 6502 the second byte of `LDA \$10` addresses the byte to be loaded, with `LDA #\$10` it uses the second byte as data (immideate), and `LDA (\$10)` uses the pointer at \$10 to load a byte. Looking close you'll find it in the 6502s encoding. The PDP-10 is just way more regular than the 6502 and allows a chain of pointers. Commented Nov 7, 2018 at 21:15
• @Wilson `MOVEI A,3(C)` is an immediate instruction. The number is treated as a data value, not an address. It falls under the answer's footnote 1. Commented Nov 8, 2018 at 11:08

Execution of all PDP-10 instructions first involves the computation of an "effective address" E. This computation is identical for all instructions, regardless of how they will use the result, and regardless of whether it's actually going to be used as an address, or used at all. The instruction format contains 3 relevant fields: Y (an 18-bit value), X (a 4-bit index field), and I, the indirect bit.

The term "modified address" is useful here. If the X field is zero, then the modified address is just the value in the Y field. If the X field is non-zero, then it names an accumulator (1 to 15) whose low-half content is added to Y to give the modified address (truncated to 18 bits).

If the I bit is zero, then the effective address E is equal to the modified address. If the I bit is one, then the word of memory (an "indirect word") addressed by the modified address is fetched. This indirect word has Y, X, and I fields that are then subject to the same effective address calculation. The iteration continues until a word with I bit zero is fetched, at which point we exit the computation with an effective address E computed from the Y and X values in this, the last-fetched indirect word.

For instructions that actually reference memory (e.g., MOVE from memory, MOVEM to memory) then E is the address of the operand.

For instructions that don't reference memory (e.g., MOVEI immediate), E may be the actual operand, a mask, a shift count, etc., depending on the instruction. In these cases "effective address" is a misnomer, though note that what the PDP-10 calls "move immediate" is called "load address" on other architectures.

Here is a trivial example I coded up and ran on a simh-emulated PDP-10 to demonstrate indirection. It's an intentionally preposterous example (not realistic).

a: z @b
b: z @c
c: z @d
d: 43 ; '#'

start: movei 1,@a
outchr 1
exit
end start

Note: 'z' is a pseudo-instruction that has opcode zero. It's useful for constructing address words such as this. '@' signifies setting the indirect bit.

The first instruction executed is "move immediate, indirect through location a, into accumulator 1". The I-bit is set in the instruction so we fetch the indirect word from 'a'. But that has a set I-bit so we fetch the indirect word from 'b'. But that has the I-bit set so we fetch the indirect word from 'c'. But that has the I-bit set so we fetch the indirect word from 'd'. That does not have the I-bit set so we're done, and the effective address E will be 43 octal.

Now, the instruction is move-immediate, so E is not really a memory address, it's the actual value. So accumulator 1, the target of the movei, is set to 43 octal.

From there we use a monitor call to output the content of acc 1 interpreted as an ASCII character: 43 octal is hash.

Here's the execution:

.exec foo
[LNKXCT FOO execution]
#
EXIT
.

If I had coded the move instruction as 'move 1,@a' (normal move, not move immediate) then the effective address 43 octal would have been used as the address of the operand; we'd have output the low 7 bits of location 43 (whatever might be there) as a character.

In real life I'd probably have written the simple form 'move 1,d' and not have used any indirection.

It is clearly possible to set up infinite loops in effective address calculation. The simplest is:

Z @.

"Dot" is the current location, so the infinitude should be obvious. As far as I recall, Z is not a valid opcode, but that does not matter; the CPU does not get as far as figuring that out. This burns CPU and memory cycles and executes no instructions. However, the sequence is interruptible. When I tried this long ago, the monitor (TOPS-10 6.03) would abort the program after some number of seconds of CPU had been squandered.