Lesson 21 of 48 intermediate

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

Data transfer instructions are like a moving company. MOV is the basic truck — it copies items from one place to another. XCHG is two people swapping seats. LEA is getting the address of a house written on a card without actually visiting. PUSH/POP is stacking boxes in a warehouse (LIFO). IN/OUT is sending and receiving packages through a delivery window (I/O port).

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

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. 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. 2. MOV AX, 5678h — immediate to register, the value 5678h is encoded directly in the instruction bytes.
  3. 3. MOV [0100h], AX — register to memory using direct addressing. Stores 78h at DS:0100h and 56h at DS:0101h (little-endian).
  4. 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. 5. LEA DI, [SI+30h] — computes 0050h + 30h = 0080h and puts it in DI. Crucially, it does NOT read from address 0080h.
  6. 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. 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. 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. 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. 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?
In what order does the stack return values? Remember LIFO — last in, first out.
Show answer
Bug: The POP order is wrong. PUSH saves in order AX, BX, CX, DX — so the stack has DX on top, then CX, then BX, then AX at the bottom. POP AX first actually pops DX's value into AX! The correct POP order is the reverse of PUSH: POP DX, POP CX, POP BX, POP AX. This is the classic stack ordering mistake. Fix: POP DX / POP CX / POP BX / POP AX.

Explain like I'm 5

MOV is like copying a drawing from one paper to another — the original stays the same and the destination gets the copy. XCHG is like two kids swapping lunch boxes. LEA is like writing down someone's address on a card — you know WHERE they live but you did not visit them. PUSH is like stacking plates: the last plate you put on top is the first one you take off (POP). IN/OUT is like a mail slot in a door — you push letters out or peek at what comes in.

Fun fact

The LEA instruction is one of the most 'abused' instructions in x86 history. Because it performs address arithmetic without accessing memory, modern compilers use LEA to do fast addition and multiplication by small constants — for example, LEA EAX,[EBX+EBX*4] computes EBX×5 in a single cycle. This trick is used billions of times per second on every modern PC.

Hands-on challenge

Write an 8086 assembly routine that: (1) Sets up DS to 3000h and ES to 4000h using MOV, (2) Uses LEA to compute the address BX+SI+10h and stores the result in DI, (3) Reads a byte from I/O port 60h, (4) Uses XLAT with a lookup table to convert the byte, (5) Pushes AX, BX, CX on the stack, calls a hypothetical subroutine address, then pops them in correct order, (6) Uses LDS to load a far pointer from memory at DS:0200h. Trace every register value and stack change.

More resources

Open interactive version (quiz + challenge) ← Back to course: Microprocessor A–Z