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 #

Instruction byte stream prefix byte(s) segment / repeat / lock opcode ModR/M displacement immediate ModR/M byte mod · bits 7…6 register/memory + disp width reg · bits 5…3 operand or opcode extension r/m · bits 2…0 register or address recipe mod decision 00 · memory; no disp* 01 · memory + signed disp8 10 · memory + disp16 11 · register memory recipes BX+SI · BX+DI · BP+SI · BP+DI SI · DI · BP* · BX * mod=00,r/m=110 is direct disp16 opcode defines the meaning of every following field
The opcode defines whether ModR/M exists and what its reg field means; ModR/M then selects a register operand or an effective-address recipe.

An 8086 instruction contains some subset of:

text
[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:

text
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:

  1. opcode 100010dw₂ uses d=1 (the reg field is destination) and w=1 (word), giving 8Bh;
  2. an eight-bit displacement requires mod=01;
  3. destination AX gives reg=000;
  4. [BX+SI] gives r/m=000;
  5. ModR/M is therefore 01 000 000₂ = 40h;
  6. the displacement is 08h.
text
assembly                 bytes
mov ax, [bx + si + 8]    8B 40 08

Decode it without the source:

  • 8B says MOV word register ← register/memory;
  • 40 says memory, disp8, destination AX, base/index BX+SI;
  • 08 is a positive displacement;
  • no BP appears, so the default segment is DS.

Worked exception: [BP] #

text
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

d=target−next_ipd = target - next\_ip

and rejects a short form when dd is outside the signed eight-bit range. The encoded field is not the absolute target address.

A disciplined decode procedure #

  1. Establish processor mode and starting address.
  2. Consume recognized prefixes.
  3. Decode the opcode using the correct ISA generation.
  4. If required, split ModR/M into mod, reg, and r/m.
  5. Determine operand width and whether reg is an operand or opcode extension.
  6. Consume the displacement width selected by mod.
  7. Consume immediate or relative fields selected by the opcode.
  8. Form the effective offset and then apply the default or overridden segment.
  9. 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.
  • LEA computes an effective offset and does not dereference memory.
  • mod=11 means register, even when the assembly syntax you expected used brackets.
  • r/m=110 changes meaning under mod=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.

Sources

  1. docsThe 8086 Family User's Manualpdfnotes
  2. docsIntel 64 and IA-32 Architectures Software Developer's Manualpdf

Searches titles, aliases, headings, summaries, and full text.↑↓ move ↵ open esc close