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
LINK: Loading
[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.