Lesson 8 of 48 intermediate

Memory, Addressing, and I/O Concepts

How the CPU finds and accesses data — the memory map, addressing modes, and the I/O subsystem

Open interactive version (quiz + challenge)

Real-world analogy

Imagine a city where every building has a unique street address (memory addressing). The CPU is a delivery driver who needs to find buildings using different methods: sometimes they know the exact address (direct addressing), sometimes they know 'the building 3 doors down from the post office' (based addressing), and sometimes they check a note that says which address to go to (indirect addressing). Memory is the rows of buildings, and I/O ports are special service windows on the city's edge — separate from the main buildings but reachable by the same delivery truck.

What is it?

Memory in the 8086 system is a 1 MB address space (00000h-FFFFFh) accessed through segmentation, where physical address = segment * 16 + offset. The CPU supports multiple addressing modes — immediate, register, direct, register indirect, based, indexed, and combinations — to flexibly specify where data lives. I/O is accessed either through a separate 64 KB port space (using IN/OUT instructions) or through memory-mapped addresses. Address decoding hardware ensures the right chip responds to each address.

Real-world relevance

When your computer boots, the CPU fetches its first instruction from ROM at address FFFF0h — that's a direct result of the memory map design. When you type on a keyboard, the CPU reads I/O port 60h using IN AL, 60h. When a program accesses video memory, it writes to addresses starting at B8000h (memory-mapped I/O). Understanding the memory map and addressing modes is essential for writing device drivers, BIOS code, bootloaders, and any software that interacts directly with hardware.

Key points

Code example

; Demonstrating memory addressing modes and I/O on 8086

; --- Setup ---
MOV AX, 1000h
MOV DS, AX           ; DS = 1000h (data segment)
MOV AX, 2000h
MOV SS, AX           ; SS = 2000h (stack segment)
MOV SP, 0FFEh        ; SP = top of stack area

; --- Immediate Addressing ---
MOV AX, 1234h        ; AX = 1234h (value in instruction)
MOV CX, 0005h        ; CX = 5 (loop counter)

; --- Direct Addressing ---
MOV [0100h], AX      ; store 1234h at DS:0100h
                      ; physical = 10000h + 0100h = 10100h

; --- Register Indirect ---
MOV BX, 0100h
MOV DX, [BX]         ; DX = value at DS:BX = DS:0100h
                      ; DX now = 1234h

; --- Based Addressing (array access) ---
MOV BX, 0200h        ; BX = base of array
MOV AX, [BX+0]       ; element 0
MOV AX, [BX+2]       ; element 1
MOV AX, [BX+4]       ; element 2

; --- Based + Indexed (2D array) ---
MOV BX, 0300h        ; base of 2D table
MOV SI, 0010h        ; row offset
MOV AX, [BX+SI+4]    ; table[row][col]
                      ; EA = 0300h + 0010h + 4 = 0314h

; --- Stack Addressing (BP-based) ---
PUSH 0042h           ; push value onto stack
MOV BP, SP
MOV AX, [BP+0]       ; read top of stack via BP
                      ; uses SS segment: SS:BP

; --- I/O Port Access ---
IN AL, 60h            ; port-mapped I/O: read keyboard port
                      ; M/IO = 0, address bus = 0060h

MOV DX, 03F8h
IN AL, DX             ; read COM1 serial port data
                      ; indirect port addressing via DX

; --- Memory-Mapped I/O (video memory) ---
MOV AX, 0B800h
MOV ES, AX            ; ES points to video memory
MOV DI, 0000h
MOV WORD [ES:DI], 0741h  ; write 'A' (41h) in white (07h)
                          ; to top-left of text screen

HLT

Line-by-line walkthrough

  1. 1. We start by setting up segment registers. DS=1000h means our data area starts at physical address 10000h. SS=2000h with SP=0FFEh means our stack grows downward from physical address 20FFEh.
  2. 2. MOV AX, 1234h demonstrates immediate addressing — the value 1234h is encoded right in the instruction bytes. No memory access needed beyond the instruction fetch.
  3. 3. MOV [0100h], AX uses direct addressing. The assembler encodes 0100h as a displacement in the instruction. Physical address = DS*16 + 0100h = 10100h. The data bus carries 1234h from AX to memory.
  4. 4. MOV BX, 0100h / MOV DX, [BX] shows register indirect addressing. BX holds the offset, and the CPU computes DS*16 + BX to find the physical address. The value at that address (1234h, which we stored earlier) loads into DX.
  5. 5. The based addressing section (MOV AX, [BX+0], [BX+2], [BX+4]) treats BX as the base of an array. Adding 0, 2, 4 accesses consecutive 16-bit elements — this is how you walk through arrays in assembly.
  6. 6. MOV AX, [BX+SI+4] is the most powerful mode: based + indexed + displacement. BX is the table base, SI is the row offset, and 4 is the column offset. The CPU adds all three to compute the effective address.
  7. 7. Stack access via BP (MOV AX, [BP+0]) automatically uses the SS segment instead of DS. This is how functions access their parameters and local variables on the stack.
  8. 8. IN AL, 60h shows port-mapped I/O. The address bus carries 0060h, M/IO is set to 0 (selecting I/O space), and the keyboard controller responds with the scan code on the data bus.
  9. 9. The video memory write (MOV WORD [ES:DI], 0741h) is memory-mapped I/O. ES points to segment B800h (video RAM), and writing 0741h places the character 'A' (41h) with white attribute (07h) at the top-left screen position.

Spot the bug

; Program to copy 10 bytes from source to destination
; Source:      DS:1000h
; Destination: DS:2000h

MOV AX, 3000h
MOV DS, AX         ; DS = 3000h
MOV SI, 1000h      ; source offset
MOV DI, 2000h      ; destination offset
MOV CX, 10         ; 10 bytes to copy

COPY_LOOP:
  MOV AL, [SI]     ; read byte from DS:SI
  MOV [DI], AL     ; write byte to DS:DI
  INC SI
  INC DI
  DEC CX
  JNZ COPY_LOOP

; BUG: What if source is at DS:1000h and
; destination needs to be in a DIFFERENT segment?
Need a hint?
Both [SI] and [DI] default to the DS segment. What if the destination address requires a different segment base? Which segment does DI usually pair with for string operations?
Show answer
Bug: Both MOV AL, [SI] and MOV [DI], AL use the DS segment by default. If the destination needs to be in a different segment (e.g., copying data to video memory at B800:0000), writing to DS:DI writes to the wrong physical address. Fix: Use a segment override for the destination — MOV [ES:DI], AL — and set ES to the destination segment (e.g., MOV AX, 0B800h / MOV ES, AX). Even better, use the 8086's string instruction MOVSB which automatically reads from DS:SI and writes to ES:DI, handling the segment separation correctly: REP MOVSB copies CX bytes from DS:SI to ES:DI with auto-increment.

Explain like I'm 5

Imagine a huge hotel with 1 million rooms (that's memory). Each room has a number (address). The CPU is a guest who needs to find specific rooms. Sometimes they know the exact room number (direct addressing), sometimes they ask the front desk 'what room is in register BX?' (indirect addressing), and sometimes they calculate 'start at room BX, go SI rooms down the hall, then 4 more doors' (based+indexed). There's also a separate row of service windows (I/O ports) where the guest can talk to the kitchen, laundry, and concierge without going to a room.

Fun fact

The 8086's segmented memory architecture was a source of both innovation and frustration. It allowed a 16-bit CPU to access 1 MB of memory, which was huge in 1978. But the overlapping segments created the infamous 'segment:offset' confusion that plagued DOS programmers for over a decade. The terms 'near pointer' (16-bit, same segment) and 'far pointer' (32-bit, segment:offset) became essential vocabulary. Intel finally escaped segmentation with the flat memory model in 32-bit protected mode — but the legacy lives on in the x86 architecture's segment registers.

Hands-on challenge

Design a memory map for a simple 8086-based system with: 32 KB of RAM starting at 00000h, 8 KB of ROM at the top of the address space (containing the reset vector), and a memory-mapped I/O device at address A0000h. Show: (1) the address ranges for each component, (2) which address lines (A0-A19) are used for chip select decoding, (3) write 8086 code that initializes a data structure in RAM, copies data to the I/O device, and then jumps to a routine in ROM. Annotate each instruction with its addressing mode.

More resources

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