8086 Addressing Modes & Instruction Encoding
8086 instructions combine opcode-specific fields with an optional ModR/M byte, displacement, and immediate data. Decoding works by separating register selection, effective-offset formation, segment selection, and little-endian payload bytes.
An assembly operand such as [BX+SI+8] is a request for an addressing form, not a byte encoding. The assembler chooses an opcode form, encodes the allowed register combination, selects a displacement width, and writes multi-byte values in little-endian order. To decode machine code reliably, reverse those decisions in the same order.
Variable-length structure #
An 8086 instruction contains some subset of:
[segment/repeat/lock prefix] [opcode] [ModR/M] [displacement] [immediate]
The 8086 core encoding described by Intel varies from one to six bytes; optional segment-override, repeat, or lock prefix bytes precede it. Later x86 extensions add more prefixes and encoding families and limit the complete instruction to fifteen bytes. This entry decodes the 8086 format only.
The opcode is authoritative. It determines whether a ModR/M byte follows, whether a field selects a register or extends the opcode, what operand width means, and whether displacement or immediate bytes are present. There is no universal d, w, or s bit position across every instruction.
The ModR/M byte #
For instructions that use it, the byte is divided into:
bits 7..6 mod register versus memory and displacement width
bits 5..3 reg register operand or opcode extension
bits 2..0 r/m register operand or memory address recipe
When mod=11₂, r/m selects a register. Otherwise it selects an effective-address expression:
r/m |
8086 memory expression |
|---|---|
000 |
BX + SI |
001 |
BX + DI |
010 |
BP + SI |
011 |
BP + DI |
100 |
SI |
101 |
DI |
110 |
direct disp16 when mod=00; otherwise BP |
111 |
BX |
The mod field supplies the displacement policy:
mod |
Meaning |
|---|---|
00 |
memory, no displacement, except r/m=110 means direct 16-bit offset |
01 |
memory plus sign-extended 8-bit displacement |
10 |
memory plus 16-bit displacement |
11 |
register operand; no memory effective address |
The direct-address exception is why [BP] cannot use mod=00,r/m=110: that bit pattern already means a standalone 16-bit offset. An assembler encodes [BP] as BP + disp8(0).
Register codes depend on width #
The same three-bit code names different registers under byte and word width:
| code | byte (w=0) |
word (w=1) |
|---|---|---|
000 |
AL |
AX |
001 |
CL |
CX |
010 |
DL |
DX |
011 |
BL |
BX |
100 |
AH |
SP |
101 |
CH |
BP |
110 |
DH |
SI |
111 |
BH |
DI |
This table applies where the opcode defines an ordinary general-register field. Segment-register fields use a smaller mapping; group opcodes may use reg as an operation selector instead of an operand.
Effective offset is not the physical address #
For a memory operand, ModR/M and displacement produce a 16-bit effective offset. Segment-default rules then choose DS or SS (or a segment override), and the bus interface calculates the real-mode physical address.
BP in an address expression normally selects SS; combinations without BP normally select DS. The encoding therefore influences segment selection as well as arithmetic.
Worked encoding: MOV AX,[BX+SI+8] #
For the MOV r16,r/m16 form:
- opcode
100010dw₂usesd=1(theregfield is destination) andw=1(word), giving8Bh; - an eight-bit displacement requires
mod=01; - destination
AXgivesreg=000; [BX+SI]givesr/m=000;- ModR/M is therefore
01 000 000₂ = 40h; - the displacement is
08h.
assembly bytes
mov ax, [bx + si + 8] 8B 40 08
Decode it without the source:
8BsaysMOV word register ← register/memory;40says memory,disp8, destinationAX, base/indexBX+SI;08is a positive displacement;- no
BPappears, so the default segment isDS.
Worked exception: [BP] #
mov [bp], ax 89 46 00
89 selects MOV r/m16 ← r16. ModR/M 46h is 01 000 110₂: memory with an eight-bit displacement, source AX, base BP. The final 00 is the necessary zero displacement. Because BP is the base, the default segment is SS.
Immediate and displacement byte order #
Multi-byte numeric fields are little-endian. A 16-bit displacement 1234h appears as 34 12; an immediate word ABCDh appears as CD AB. Instruction bytes are still consumed from low to high addresses. “Little-endian” describes the order within multi-byte fields, not a reversal of the whole instruction.
Relative branches encode a signed displacement from the address after the instruction. The assembler computes
and rejects a short form when is outside the signed eight-bit range. The encoded field is not the absolute target address.
A disciplined decode procedure #
- Establish processor mode and starting address.
- Consume recognized prefixes.
- Decode the opcode using the correct ISA generation.
- If required, split ModR/M into
mod,reg, andr/m. - Determine operand width and whether
regis an operand or opcode extension. - Consume the displacement width selected by
mod. - Consume immediate or relative fields selected by the opcode.
- Form the effective offset and then apply the default or overridden segment.
- Confirm the decoded instruction ends exactly where the next one begins.
One wrong byte boundary turns following data into plausible-looking nonsense. Re-synchronize from a known control-flow target rather than trusting a long speculative decode.
Common traps #
- Intel and AT&T syntaxes reverse operand order and use different memory notation; syntax is not encoding.
LEAcomputes an effective offset and does not dereference memory.mod=11means register, even when the assembly syntax you expected used brackets.r/m=110changes meaning undermod=00.- an eight-bit displacement is signed when added to the effective address.
- later x86 adds address-size prefixes, SIB bytes, REX, VEX, and EVEX; do not apply the 8086 matrix to them unchanged.