Data Transfer Instructions
MOV, XCHG, LEA, PUSH, POP, XLAT, IN, OUT — moving data is the most common 8086 operation
Open interactive version (quiz + challenge)Real-world analogy
What is it?
Data transfer instructions move data between registers, memory, I/O ports, and the stack without performing arithmetic. MOV is the most common instruction, copying data from source to destination. XCHG swaps values atomically. LEA computes addresses without memory access. LDS/LES load far pointers. PUSH/POP manage the stack. XLAT performs table lookups. IN/OUT access I/O ports. None of these instructions affect the flags register (except SAHF and POPF).
Real-world relevance
Data transfer instructions make up roughly 30-40% of all instructions in typical programs. The MOV instruction alone accounts for about 20% of all executed instructions in real workloads. IN/OUT instructions are used extensively in operating system kernel code and device drivers to communicate with hardware. PUSH/POP are essential for function calls, and XCHG with memory is still used for lock-free programming patterns on modern x86.
Key points
- MOV — The Fundamental Transfer — MOV copies data from source to destination. It works between registers, between register and memory, or from an immediate value to register/memory. It does NOT affect any flags. You cannot MOV memory-to-memory or MOV directly into a segment register from an immediate.
- MOV with Segment Registers — Segment registers (CS, DS, ES, SS) can only be loaded from a general register or memory — not from an immediate value. CS cannot be the destination of MOV (it would change the code stream). To load DS with 1000h, first load AX with 1000h, then MOV DS, AX.
- XCHG — Exchange — XCHG atomically swaps the contents of two operands. One operand must be a register. XCHG AX,BX swaps their values without needing a temporary variable. XCHG with memory is implicitly locked on the bus, making it useful for semaphores.
- LEA — Load Effective Address — LEA loads the effective address (offset) of the source operand into the destination register — it does NOT access memory. LEA BX,[SI+10h] puts SI+10h into BX. Often used as a fast way to compute address arithmetic or add a register plus a constant.
- LDS and LES — Load Far Pointer — LDS reg, mem loads a 32-bit far pointer from memory: the 16-bit offset goes into the specified register, and the 16-bit segment goes into DS. LES does the same but loads ES instead. Used for accessing data in other segments.
- PUSH and POP — Stack Operations — PUSH decrements SP by 2, then stores a 16-bit value at SS:SP. POP reads a 16-bit value from SS:SP, then increments SP by 2. The stack grows downward (toward lower addresses). Only 16-bit values can be pushed/popped on the 8086.
- XLAT — Table Lookup — XLAT (Translate) replaces AL with the byte at DS:[BX+AL]. BX points to the base of a 256-byte lookup table, and AL is the index. After XLAT, AL contains the table entry. Perfect for character translation, code conversions, and fast mapping functions.
- IN and OUT — I/O Port Access — IN reads a byte or word from an I/O port into AL or AX. OUT writes AL or AX to an I/O port. The port number can be an immediate (0-FFh) or in DX (0-FFFFh). These are the only way to communicate with I/O devices in the 8086 isolated I/O model.
- LAHF and SAHF — Flags Transfer — LAHF loads AH with the lower 8 bits of the flags register (SF, ZF, AF, PF, CF). SAHF stores AH back into the lower flags byte. Useful for saving and restoring flag state, and for 8080 compatibility.
- PUSHF and POPF — Full Flags Save/Restore — PUSHF pushes the entire 16-bit flags register onto the stack. POPF pops a 16-bit value from the stack into the flags register. Used to save and restore the complete processor state around critical sections or interrupt handlers.
Code example
; Complete data transfer instruction showcase
; === SETUP ===
MOV AX, 1000h
MOV DS, AX ; DS = 1000h
MOV AX, 2000h
MOV SS, AX ; SS = 2000h
MOV SP, 0FFEh ; Stack at top of segment
; === MOV variations ===
MOV AX, 5678h ; Immediate to register
MOV BX, AX ; Register to register
MOV [0100h], AX ; Register to memory
MOV CX, [0100h] ; Memory to register
; === XCHG ===
MOV AX, 1111h
MOV BX, 2222h
XCHG AX, BX ; AX=2222h, BX=1111h
; === LEA vs MOV ===
MOV SI, 0050h
LEA DI, [SI+30h] ; DI = 0080h (address calc only)
MOV DI, [SI+30h] ; DI = word at DS:0080h (data!)
; === PUSH / POP ===
PUSH AX ; Save AX on stack (SP -= 2)
PUSH BX ; Save BX on stack (SP -= 2)
PUSH CX ; Save CX on stack (SP -= 2)
; ... do work ...
POP CX ; Restore CX (SP += 2)
POP BX ; Restore BX (SP += 2)
POP AX ; Restore AX (SP += 2)
; Registers restored in reverse order!
; === XLAT (table lookup) ===
; Assume hex_table DB '0123456789ABCDEF' at DS:0200h
MOV BX, 0200h ; BX = table base
MOV AL, 0Ch ; Index 12
XLAT ; AL = [BX+AL] = 'C' (43h)
; === I/O ===
IN AL, 60h ; Read keyboard scancode
MOV DX, 03F8h ; COM1 port
OUT DX, AL ; Send character to serial port
; === Flags save/restore ===
PUSHF ; Save all flags
CLI ; Disable interrupts
; ... critical section ...
POPF ; Restore flags (and IF state)Line-by-line walkthrough
- 1. MOV AX, 1000h / MOV DS, AX — two-step segment register load. Cannot do MOV DS, 1000h directly because the 8086 instruction encoding does not support immediate-to-segment.
- 2. MOV AX, 5678h — immediate to register, the value 5678h is encoded directly in the instruction bytes.
- 3. MOV [0100h], AX — register to memory using direct addressing. Stores 78h at DS:0100h and 56h at DS:0101h (little-endian).
- 4. XCHG AX, BX — atomically swaps both registers in one instruction. No temporary variable needed. If one operand were memory, the bus would be locked.
- 5. LEA DI, [SI+30h] — computes 0050h + 30h = 0080h and puts it in DI. Crucially, it does NOT read from address 0080h.
- 6. PUSH AX / PUSH BX / PUSH CX — each PUSH decrements SP by 2 and stores the value. Stack grows downward from 0FFEh to 0FF8h.
- 7. POP CX / POP BX / POP AX — reverse order! CX was pushed last so it must be popped first. Each POP increments SP by 2.
- 8. XLAT with BX=0200h, AL=0Ch — reads byte at DS:(0200h + 0Ch) = DS:020Ch and puts it in AL. One-instruction table lookup.
- 9. IN AL, 60h — reads the keyboard scan code from I/O port 60h (fixed port, fits in 8 bits so no DX needed).
- 10. PUSHF / CLI / ... / POPF — saves the flags including IF, disables interrupts for a critical section, then restores the original interrupt state.
Spot the bug
; Goal: Save and restore all registers around a subroutine
PUSH AX
PUSH BX
PUSH CX
PUSH DX
; ... call subroutine that clobbers AX,BX,CX,DX ...
POP AX ; Restore AX?
POP BX ; Restore BX?
POP CX ; Restore CX?
POP DX ; Restore DX?Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- 8086 Data Transfer Instructions (GeeksforGeeks)
- MOV, PUSH, POP, XCHG Explained (YouTube)
- 8086 Instruction Set Reference (TutorialsPoint)
- LEA Instruction Deep Dive (x86 Reference)