@davidbak suggests a possible physical implementation motivation for the design choice:
In the time we're talking guys writing the instruction set would prioritize minimal number of gates and then minimal gate depth/delay over just about everything else, I would think.
BX
is the only one of ACDB that can be used in a 16-bit addressing mode:
[ (BX|BP) + (DI|SI) + (0 | disp8 | disp16) ]
, where any of the 3 components are optional.
BX
is also the only 16-bit-addressing-mode register that has a low/high half. So maybe in 8086 it was physically on the boundary between the split low/high registers and the address-capable registers that the AGU had to read.
Or maybe not: 8086 ModR/M and opcode encodings were designed before the hardware by Stephen Morse, primarily a software guy. We can't know whether he considered a HW layout benefit, or thought of this as a logical reason, or whether it just worked out well for the HW design, or maybe I'm way off base and it isn't even helpful for the HW implementation.
(off topic re: low-8 of other registers) In x86-64, a REX prefix changes the meaning from AH/CH/DH/BH to SPL/BPL/SIL/DIL, in that order (Intel manual vol.2, Appendix B.1.4.2, Table B-5). In 16/32 bit modes, 16-bit operand size was the smallest for SP/ESP and the other non-X registers. (Making registers more uniform helps compilers, except that compilers sometimes end up wasting a REX prefix by picking a register that needs one to access the low 8 component.)
The pusha
/popa
ordering matches too, and while that's interesting, the internal implementation probably uses a counter and goes through the same fetch-by-index logic as explicit register operands. So it doesn't add new information that pusha
goes in order of the encodings.
It makes sense that the physical layout of the register file would match the register-number encodings, though, to keep the decoding logic simple. I had a look at the addressing-mode encodings, to see if there was a similar pattern there. (I haven't looked at 8086 gate diagrams, but maybe the AGU fetches directly from the last 4 registers in the register file without going through the full indexing that can select any of the 8.)
It's complicated by the fact that 16-bit doesn't have a SIB byte, so base and base+index modes share the same 3 bit R/M field in the ModR/M byte.
Intel x86 manual vol.2, 2.1.5 Addressing-Mode Encoding of ModR/M and SIB Bytes, Table 2-1. 16-Bit Addressing Forms with the ModR/M Byte
Effective Address | R/M field | (added): /r encoding
[BX+SI] 000
[BX+DI] 001
[BP+SI] 010
[BP+DI] 011
[SI] 100 | SI=110
[DI] 101 | DI=111
disp16 (no base) 110 | (BP=101)
[BX] 111 | BX=011
So BX alone is at the opposite end from BX+SI and BX+DI, but it's one wrap-around away from being adjacent to the other two codes that involve BX. When SI and DI are involved, bit0=0 means SI, bit0=1 means DI.
When there are both base and index registers (bit2=0), then bit1=0 means BX, bit1=1 means BP. So that's maybe consistent with BX being earlier in the physical layout of the register file (lower numbers to access it). But R/M fields clearly need significant decoding before they turn into register fetches.
Still, I think it's plausible that the AGU in 8086 has a "back door" into the register file that can only select from the last 4 registers (in /r
field and pusha
encoding order). Note that 8086 uses the adder in its regular ALU for address calculations, but the addressing-mode decoding hardware might use different paths to fetch inputs for the ALU's address calculations. (Totally guessing; certainly possible it just decodes those addressing modes to the usual 3-bit register codes and drives the normal ALU through regular register-fetch paths.)
In 32/64-bit addressing modes, the encoding matches the usual register encoding, so presumably (in CPUs without out-of-order / register renaming, like 80386) the AGU can access the register file with the same 3-bit address as the ALU.
(fun fact, 32/64-bit shares the same pattern of [e/rbp]
being the escape code for disp32 with no base (or RIP relative), which is why EBP always needs at least a disp8 = 0. This is why disassembly looks like [ebp+0]
vs. [edx]
.) Same for r13
in x86-64 mode, because it has the same code as rbp
(except for the extra bit in the REX prefix).
PUSHA
, which suggests it might correspond to the internal register file implementation...