8086 Memory Management and I/O Programming
Explore advanced memory management techniques and I/O programming concepts for the 8086 microprocessor.
Memory Organization and Management
The 8086 microprocessor manages memory through a segmented architecture that allows efficient access to large memory spaces using 16-bit registers.
Memory Segmentation Details
Each segment can be up to 64KB in size, and segments can overlap or be completely separate depending on the application requirements.
; Segment register initialization example
.MODEL SMALL
.STACK 100H
.DATA
data_array DW 1000 DUP(?)
buffer DB 512 DUP(?)
.CODE
START:
MOV AX, @DATA ; Load data segment address
MOV DS, AX ; Initialize DS
MOV ES, AX ; Initialize ES (same as DS)
; Different segment for extra data
MOV AX, 2000H ; Different segment
MOV ES, AX ; ES points to different segment
; Now DS and ES point to different segments
MOV [SI], AX ; Store to DS:SI
MOV ES:[DI], BX ; Store to ES:DI
Memory Allocation Strategies
- Static Allocation: Memory allocated at compile time
- Dynamic Allocation: Memory allocated at runtime using DOS functions
- Stack Allocation: Temporary storage using stack operations
- Heap Management: Custom memory management routines
Advanced Memory Operations
Advanced memory management includes techniques for efficient data movement, memory copying, and large data structure handling.
Block Memory Operations
; Fast memory copy using string instructions
FAST_MEMCPY PROC
; Input: SI = source, DI = destination, CX = word count
; Copy CX words from DS:SI to ES:DI
CLD ; Clear direction flag (forward)
CMP CX, 8 ; Check if worth optimizing
JL SIMPLE_COPY
; Optimize for word-aligned copies
REP MOVSW ; Copy words (faster than bytes)
RET
SIMPLE_COPY:
; Simple byte copy for small amounts
SHL CX, 1 ; Convert word count to byte count
REP MOVSB
RET
FAST_MEMCPY ENDP
; Memory fill operation
MEMORY_FILL PROC
; Input: DI = destination, AX = fill value, CX = count
CLD
REP STOSW ; Fill memory with word value
RET
MEMORY_FILL ENDP
; Memory compare operation
MEMORY_COMPARE PROC
; Input: SI = source1, DI = source2, CX = count
; Output: ZF = 1 if equal, ZF = 0 if different
CLD
REPE CMPSB ; Compare bytes while equal
RET
MEMORY_COMPARE ENDP
Segment Management
; Dynamic segment allocation
ALLOCATE_SEGMENT PROC
; Allocate a new 64KB segment
; Output: AX = segment address, CF = error flag
MOV AH, 48H ; DOS allocate memory function
MOV BX, 1000H ; Request 64KB (4096 paragraphs)
INT 21H
JC ALLOC_ERROR ; Jump if error
; AX contains segment address
CLC ; Clear carry flag (success)
RET
ALLOC_ERROR:
STC ; Set carry flag (error)
RET
ALLOCATE_SEGMENT ENDP
; Free allocated segment
FREE_SEGMENT PROC
; Input: ES = segment to free
MOV AH, 49H ; DOS free memory function
INT 21H
RET
FREE_SEGMENT ENDP
Large Data Structure Management
; Handle data structures larger than 64KB
LARGE_ARRAY_ACCESS PROC
; Input: DX:AX = element index, BX = element size
; Output: ES:DI = address of element
; Calculate byte offset = index × element_size
MUL BX ; DX:AX = index × element_size
; Calculate segment offset
MOV CX, DX ; Save high word
MOV DI, AX ; Low word becomes offset
; Convert high word to segments (divide by 16)
MOV AX, CX
MOV CL, 4
SHR AX, CL ; Divide by 16
; Add to base segment
ADD AX, [BASE_SEGMENT]
MOV ES, AX
RET
LARGE_ARRAY_ACCESS ENDP
BASE_SEGMENT DW ?
I/O Programming Fundamentals
The 8086 provides two types of I/O: Memory-mapped I/O and Port-mapped I/O. Understanding both approaches is essential for hardware interfacing.
Port-Mapped I/O
; Basic port I/O operations
; Reading from ports
IN AL, 60H ; Read byte from port 60H (keyboard data)
IN AX, 40H ; Read word from port 40H (timer)
; Using DX for port address (allows full 16-bit range)
MOV DX, 378H ; Parallel port address
IN AL, DX ; Read from parallel port
OUT DX, AL ; Write to parallel port
; 16-bit port I/O
MOV DX, 1F0H ; IDE controller data port
IN AX, DX ; Read 16-bit data
OUT DX, AX ; Write 16-bit data
; Port bit manipulation
MOV DX, 21H ; Interrupt mask register
IN AL, DX ; Read current mask
OR AL, 02H ; Set bit 1 (disable IRQ1)
OUT DX, AL ; Write back modified mask
Memory-Mapped I/O
; Video memory access (memory-mapped I/O)
.MODEL SMALL
.CODE
START:
MOV AX, 0B800H ; CGA/VGA text mode video segment
MOV ES, AX ; ES points to video memory
; Write character directly to video memory
MOV DI, 0 ; Top-left corner (row 0, col 0)
MOV AL, 'A' ; Character to display
MOV AH, 07H ; Attribute (white on black)
MOV ES:[DI], AX ; Write to video memory
; Write to position (row 5, column 10)
MOV AX, 5 ; Row
MOV BX, 80 ; Columns per row
MUL BX ; AX = row × 80
ADD AX, 10 ; Add column
SHL AX, 1 ; Multiply by 2 (2 bytes per character)
MOV DI, AX ; DI = byte offset
MOV AL, 'B' ; Character
MOV AH, 0FH ; Bright white on black
MOV ES:[DI], AX ; Write to video memory
Hardware Register Programming
; Programmable Interval Timer (8253/8254) programming
INIT_TIMER PROC
; Initialize timer for square wave generation
; Program timer 0 for 1000 Hz output
MOV AL, 36H ; Timer 0, LSB/MSB, Mode 3 (square wave)
OUT 43H, AL ; Command register
; Calculate count value: 1193180 / 1000 = 1193
MOV AX, 1193 ; Count value
OUT 40H, AL ; Send low byte
MOV AL, AH
OUT 40H, AL ; Send high byte
RET
INIT_TIMER ENDP
; Parallel port programming
PARALLEL_INIT PROC
; Initialize parallel port for output
MOV DX, 37AH ; Control port
IN AL, DX ; Read current settings
AND AL, 0DFH ; Clear bit 5 (enable output)
OUT DX, AL ; Write back
RET
PARALLEL_INIT ENDP
PARALLEL_WRITE PROC
; Input: AL = data to write
MOV DX, 378H ; Data port
OUT DX, AL ; Write data
; Strobe signal
MOV DX, 37AH ; Control port
IN AL, DX
OR AL, 01H ; Set strobe bit
OUT DX, AL
AND AL, 0FEH ; Clear strobe bit
OUT DX, AL
RET
PARALLEL_WRITE ENDP
DMA Programming
Direct Memory Access (DMA) allows peripherals to transfer data directly to/from memory without CPU intervention, improving system performance.
DMA Controller Programming
; DMA channel setup for data transfer
SETUP_DMA PROC
; Setup DMA channel 1 for memory-to-memory transfer
; Input: SI = source, DI = destination, CX = count
CLI ; Disable interrupts during setup
; Disable DMA channel 1
MOV AL, 05H ; Channel 1 mask bit
OUT 0AH, AL ; Single channel mask register
; Clear byte pointer flip-flop
OUT 0CH, AL ; Any value clears flip-flop
; Set transfer mode
MOV AL, 45H ; Channel 1, read transfer, single mode
OUT 0BH, AL ; Mode register
; Set source address (channel 1 address register)
MOV AX, SI
OUT 02H, AL ; Low byte
MOV AL, AH
OUT 02H, AL ; High byte
; Set page register for channel 1
MOV AX, DS
MOV CL, 4
SHR AX, CL ; Convert segment to page
OUT 83H, AL ; Page register
; Set transfer count
DEC CX ; DMA count is n-1
MOV AL, CL
OUT 03H, AL ; Low byte
MOV AL, CH
OUT 03H, AL ; High byte
; Enable DMA channel 1
MOV AL, 01H ; Channel 1 enable
OUT 0AH, AL ; Single channel mask register
STI ; Re-enable interrupts
RET
SETUP_DMA ENDP
DMA Transfer Monitoring
; Check DMA transfer status
CHECK_DMA_STATUS PROC
; Check if DMA transfer is complete
; Output: ZF = 1 if complete, ZF = 0 if in progress
IN AL, 08H ; DMA status register
TEST AL, 02H ; Test channel 1 terminal count
RET
CHECK_DMA_STATUS ENDP
; Wait for DMA completion
WAIT_DMA_COMPLETE PROC
PUSH AX
WAIT_LOOP:
CALL CHECK_DMA_STATUS
JZ WAIT_LOOP ; Continue waiting if not complete
POP AX
RET
WAIT_DMA_COMPLETE ENDP
Interrupt-Driven I/O
Interrupt-driven I/O provides efficient handling of slow I/O devices without blocking the CPU.
Serial Port Programming
; 8250 UART programming for serial communication
INIT_SERIAL PROC
; Initialize COM1 (3F8H) for 9600 baud, 8N1
; Set DLAB (Divisor Latch Access Bit)
MOV DX, 3FBH ; Line Control Register
IN AL, DX
OR AL, 80H ; Set DLAB
OUT DX, AL
; Set baud rate to 9600 (divisor = 12)
MOV DX, 3F8H ; Divisor Latch Low
MOV AL, 12 ; Low byte of divisor
OUT DX, AL
MOV DX, 3F9H ; Divisor Latch High
MOV AL, 0 ; High byte of divisor
OUT DX, AL
; Configure line parameters (8N1)
MOV DX, 3FBH ; Line Control Register
MOV AL, 03H ; 8 bits, no parity, 1 stop bit
OUT DX, AL ; This also clears DLAB
; Enable interrupts
MOV DX, 3F9H ; Interrupt Enable Register
MOV AL, 01H ; Enable received data interrupt
OUT DX, AL
; Set RTS and DTR
MOV DX, 3FCH ; Modem Control Register
MOV AL, 03H ; Set RTS and DTR
OUT DX, AL
RET
INIT_SERIAL ENDP
; Serial interrupt handler
SERIAL_ISR PROC
PUSH AX
PUSH DX
PUSH DS
MOV AX, @DATA
MOV DS, AX
; Check interrupt type
MOV DX, 3FAH ; Interrupt Identification Register
IN AL, DX
TEST AL, 01H ; Check if interrupt pending
JNZ ISR_EXIT ; No interrupt pending
AND AL, 06H ; Mask interrupt type bits
CMP AL, 04H ; Received data available?
JE HANDLE_RECEIVE
CMP AL, 02H ; Transmitter holding register empty?
JE HANDLE_TRANSMIT
JMP ISR_EXIT
HANDLE_RECEIVE:
MOV DX, 3F8H ; Data register
IN AL, DX ; Read received character
; Store character in buffer
MOV BX, OFFSET rx_buffer
MOV SI, rx_head
MOV [BX+SI], AL
INC SI
CMP SI, BUFFER_SIZE
JL NO_WRAP
MOV SI, 0
NO_WRAP:
MOV rx_head, SI
JMP ISR_EXIT
HANDLE_TRANSMIT:
; Handle transmit interrupt
; Send next character from transmit buffer
ISR_EXIT:
; Send EOI to interrupt controller
MOV AL, 20H
OUT 20H, AL ; Send EOI to PIC
POP DS
POP DX
POP AX
IRET
SERIAL_ISR ENDP
BUFFER_SIZE EQU 256
rx_buffer DB BUFFER_SIZE DUP(?)
rx_head DW 0
rx_tail DW 0
Keyboard Controller Programming
; Low-level keyboard controller programming
KEYBOARD_INIT PROC
; Initialize 8042 keyboard controller
; Disable keyboard
CALL WAIT_INPUT_EMPTY
MOV AL, 0ADH ; Disable keyboard command
OUT 64H, AL
; Read configuration
CALL WAIT_INPUT_EMPTY
MOV AL, 20H ; Read configuration command
OUT 64H, AL
CALL WAIT_OUTPUT_FULL
IN AL, 60H ; Read configuration byte
; Modify configuration
OR AL, 01H ; Enable keyboard interrupt
AND AL, 0BFH ; Clear keyboard disable bit
MOV BL, AL ; Save modified configuration
; Write configuration
CALL WAIT_INPUT_EMPTY
MOV AL, 60H ; Write configuration command
OUT 64H, AL
CALL WAIT_INPUT_EMPTY
MOV AL, BL ; Modified configuration
OUT 60H, AL
; Enable keyboard
CALL WAIT_INPUT_EMPTY
MOV AL, 0AEH ; Enable keyboard command
OUT 64H, AL
RET
KEYBOARD_INIT ENDP
WAIT_INPUT_EMPTY PROC
PUSH AX
WAIT_INPUT:
IN AL, 64H ; Read status register
TEST AL, 02H ; Check input buffer full bit
JNZ WAIT_INPUT ; Wait until empty
POP AX
RET
WAIT_INPUT_EMPTY ENDP
WAIT_OUTPUT_FULL PROC
PUSH AX
WAIT_OUTPUT:
IN AL, 64H ; Read status register
TEST AL, 01H ; Check output buffer full bit
JZ WAIT_OUTPUT ; Wait until full
POP AX
RET
WAIT_OUTPUT_FULL ENDP
Performance Optimization
Optimizing memory access and I/O operations is crucial for high-performance 8086 applications.
Memory Access Optimization
; Optimized memory operations
FAST_MEMORY_CLEAR PROC
; Clear large memory block efficiently
; Input: ES:DI = destination, CX = word count
MOV AX, 0 ; Fill value (zero)
CLD ; Forward direction
; Use word operations for speed
REP STOSW ; Fill with words (faster than bytes)
RET
FAST_MEMORY_CLEAR ENDP
; Cache-friendly array processing
PROCESS_ARRAY_OPTIMIZED PROC
; Process array in cache-friendly manner
; Input: SI = array address, CX = element count
MOV BX, 0 ; Array index
PROCESS_LOOP:
; Load multiple elements to reduce memory access
MOV AX, [SI+BX] ; Element 0
MOV DX, [SI+BX+2] ; Element 1
; Process both elements
ADD AX, 100 ; Process element 0
ADD DX, 100 ; Process element 1
; Store back
MOV [SI+BX], AX
MOV [SI+BX+2], DX
ADD BX, 4 ; Move to next pair
SUB CX, 2 ; Processed 2 elements
JA PROCESS_LOOP ; Continue if more elements
RET
PROCESS_ARRAY_OPTIMIZED ENDP
I/O Performance Optimization
; Buffered I/O for better performance
BUFFERED_READ PROC
; Buffered file reading
; Input: BX = file handle, CX = bytes to read
; Output: Buffer contains data, AX = bytes read
CMP CX, BUFFER_SIZE
JLE DIRECT_READ
; Large read - use multiple buffer loads
MOV DI, 0 ; Total bytes read
BUFFER_LOOP:
PUSH CX
MOV CX, BUFFER_SIZE ; Read buffer size
MOV AH, 3FH ; DOS read function
MOV DX, OFFSET read_buffer
INT 21H
; Copy from buffer to destination
MOV SI, OFFSET read_buffer
MOV CX, AX ; Bytes actually read
REP MOVSB ; Copy to user buffer
ADD DI, AX ; Add to total
POP CX
SUB CX, AX ; Subtract from remaining
JA BUFFER_LOOP ; Continue if more to read
MOV AX, DI ; Return total bytes read
RET
DIRECT_READ:
; Small read - direct operation
MOV AH, 3FH
MOV DX, OFFSET user_buffer
INT 21H
RET
BUFFERED_READ ENDP
BUFFER_SIZE EQU 4096
read_buffer DB BUFFER_SIZE DUP(?)
user_buffer DB 8192 DUP(?)
Numerical Problems and Examples
Problem 1: Memory Segmentation Calculation
Question: Calculate the physical addresses for the following segment:offset pairs: 1234:5678, ABCD:EF01, FFFF:0010
Solution:
Physical Address = (Segment × 16) + Offset
1. 1234:5678
Physical Address = (1234H × 16) + 5678H
= 12340H + 5678H
= 179B8H
2. ABCD:EF01
Physical Address = (ABCDH × 16) + EF01H
= ABCD0H + EF01H
= 1ABD1H
3. FFFF:0010
Physical Address = (FFFFH × 16) + 0010H
= FFFF0H + 0010H
= 100000H
Note: The last address (100000H) exceeds the 8086's 20-bit
address space (FFFFFH), demonstrating address wraparound.
Answer: 179B8H, 1ABD1H, 100000H (with wraparound)
Problem 2: DMA Transfer Time Calculation
Question: Calculate the time to transfer 64KB using DMA at 1 MB/s transfer rate.
Solution:
Given:
- Data size = 64KB = 65,536 bytes
- Transfer rate = 1 MB/s = 1,048,576 bytes/s
Transfer Time = Data Size / Transfer Rate
= 65,536 bytes / 1,048,576 bytes/s
= 0.0625 seconds
= 62.5 milliseconds
Answer: 62.5 ms
Problem 3: Port Address Decoding
Question: Design an address decoder for I/O ports 300H-30FH. What are the address lines needed?
Solution:
Port range: 300H - 30FH (16 consecutive ports)
Binary analysis:
300H = 0011 0000 0000 0000B
30FH = 0011 0000 0000 1111B
Address lines needed:
- A15-A4: Must be 001100000000B (fixed)
- A3-A0: Variable bits for port selection (0000-1111)
Decoder logic:
Enable = A15' · A14' · A13 · A12 · A11' · A10' · A9' · A8' · A7' · A6' · A5' · A4'
Port selection: A3, A2, A1, A0
Answer: Need A15-A4 for base address decoding, A3-A0 for port selection
Problem 4: Buffer Size Optimization
Question: Calculate optimal buffer size for disk I/O if disk access time is 10ms and transfer rate is 500 KB/s.
Solution:
Given:
- Disk access time (seek + latency) = 10ms
- Transfer rate = 500 KB/s = 500,000 bytes/s
During 10ms access time, we could transfer:
Transfer capacity = 500,000 bytes/s × 0.01s = 5,000 bytes
Optimal buffer size should be at least 5KB to minimize
the impact of access time overhead.
However, practical considerations:
- Memory constraints in 8086 systems
- Typical buffer sizes: 512 bytes, 1KB, 2KB, 4KB
Recommendation: Use 4KB buffer as compromise between
performance and memory usage.
Answer: Optimal buffer size ≈ 4KB
Real-World Applications
Understanding practical applications helps in applying memory management and I/O concepts effectively.
Data Acquisition System
; Real-time data acquisition using interrupts and buffering
.MODEL SMALL
.DATA
sample_buffer DW 1000 DUP(?) ; Circular buffer
buffer_head DW 0 ; Write pointer
buffer_tail DW 0 ; Read pointer
samples_lost DW 0 ; Overflow counter
.CODE
START:
; Initialize ADC and timer
CALL INIT_ADC
CALL INIT_TIMER
; Main processing loop
MAIN_LOOP:
CALL PROCESS_SAMPLES
CALL UPDATE_DISPLAY
JMP MAIN_LOOP
; Timer interrupt for sampling
TIMER_ISR PROC
PUSH AX
PUSH BX
PUSH DS
MOV AX, @DATA
MOV DS, AX
; Read ADC value
CALL READ_ADC ; Result in AX
; Store in circular buffer
MOV BX, buffer_head
MOV sample_buffer[BX], AX
ADD BX, 2 ; Next word position
CMP BX, 2000 ; End of buffer?
JL NO_WRAP_HEAD
MOV BX, 0 ; Wrap to beginning
NO_WRAP_HEAD:
; Check for buffer overflow
CMP BX, buffer_tail
JNE NO_OVERFLOW
INC samples_lost ; Count lost sample
JMP ISR_EXIT
NO_OVERFLOW:
MOV buffer_head, BX ; Update head pointer
ISR_EXIT:
POP DS
POP BX
POP AX
IRET
TIMER_ISR ENDP
PROCESS_SAMPLES PROC
; Process available samples
MOV BX, buffer_tail
PROCESS_LOOP:
CMP BX, buffer_head ; Any samples available?
JE PROCESS_DONE
MOV AX, sample_buffer[BX] ; Get sample
CALL ANALYZE_SAMPLE ; Process sample
ADD BX, 2 ; Next sample
CMP BX, 2000 ; Wrap check
JL NO_WRAP_TAIL
MOV BX, 0
NO_WRAP_TAIL:
MOV buffer_tail, BX ; Update tail
JMP PROCESS_LOOP
PROCESS_DONE:
RET
PROCESS_SAMPLES ENDP
Multi-Tasking System
; Simple cooperative multitasking system
TASK_CONTROL STRUC
task_sp DW ? ; Stack pointer
task_ss DW ? ; Stack segment
task_state DB ? ; Task state
task_priority DB ? ; Priority level
TASK_CONTROL ENDS
.DATA
current_task DW 0
task_table TASK_CONTROL 4 DUP(<>) ; 4 tasks maximum
.CODE
; Task scheduler (called by timer interrupt)
SCHEDULER PROC
; Save current task context
MOV BX, current_task
MOV AX, SIZE TASK_CONTROL
MUL BX ; BX = task index × structure size
MOV BX, AX
MOV task_table[BX].task_sp, SP
MOV task_table[BX].task_ss, SS
; Find next ready task
MOV CX, 4 ; Number of tasks
MOV DX, current_task
FIND_TASK:
INC DX
CMP DX, 4
JL CHECK_TASK
MOV DX, 0 ; Wrap to first task
CHECK_TASK:
MOV BX, DX
MOV AX, SIZE TASK_CONTROL
MUL BX
MOV BX, AX
CMP task_table[BX].task_state, 1 ; Ready state?
JE SWITCH_TASK
LOOP FIND_TASK
; No ready task found, continue current
RET
SWITCH_TASK:
MOV current_task, DX
; Restore new task context
MOV SS, task_table[BX].task_ss
MOV SP, task_table[BX].task_sp
RET
SCHEDULER ENDP
Summary and Best Practices
Effective memory management and I/O programming require understanding of hardware limitations and optimal use of available resources.
Key Guidelines
- Memory Efficiency: Use appropriate data structures and avoid memory waste
- I/O Performance: Use buffering and interrupt-driven techniques
- Error Handling: Always check for and handle error conditions
- Resource Management: Properly allocate and free system resources
- Real-Time Constraints: Meet timing requirements for time-critical applications
- Hardware Compatibility: Understand target hardware limitations