Skip to content

x86 / DOSBox

The x86 preset in the IDE gives you a DOSBox environment running DOS 6.22 — a complete MS-DOS system in your browser, with VGA graphics support and the NASM assembler for writing 8086/286/386 assembly code. This is the retro PC experience: COM programs, INT 21h for DOS calls, and INT 10h for BIOS video.

If you grew up with a DOS PC in the late 80s or early 90s, this will feel very familiar.

Languages in the IDE

Language Notes
x86 assembly (NASM) Netwide Assembler — clean Intel syntax

Quick start

  1. Select x86 (DOSBox) as the platform.
  2. Load the Assembly template.
  3. Build — NASM compiles your source, the resulting COM file runs in DOSBox automatically.

COM vs EXE

The IDE template produces .COM files — the simplest DOS executable format. A COM file is a flat binary loaded at offset $100 in a 64 KB segment. This is perfect for learning and small programs. Larger programs needing multiple segments require the EXE format.

The x86 in context

The Intel 8086 (1978) is the CPU at the base of the x86 family — every Intel/AMD processor today is a backward-compatible descendant. The 8086 is a 16-bit processor with a segmented memory architecture. DOSBox emulates a PC in this era running at a speed appropriate for the software.

Registers

The 8086 has 14 programmer-visible registers:

Register Size Purpose
AX 16-bit Accumulator (AH=high byte, AL=low byte)
BX 16-bit Base — general purpose, indirect addressing
CX 16-bit Counter — used by LOOP, SHIFT, REP instructions
DX 16-bit Data — used with AX for 32-bit ops, I/O port
SI 16-bit Source Index
DI 16-bit Destination Index
SP 16-bit Stack Pointer
BP 16-bit Base Pointer (stack frame access)
CS 16-bit Code Segment
DS 16-bit Data Segment
ES 16-bit Extra Segment
SS 16-bit Stack Segment
IP 16-bit Instruction Pointer
FLAGS 16-bit Status flags

The 8-bit halves of AX, BX, CX, DX are accessible individually: AL, AH, BL, BH, CL, CH, DL, DH.

Your first program

; Hello World — x86 COM program (NASM syntax)
; Build: nasm hello.asm -f bin -o hello.com

        BITS 16
        ORG 100h        ; COM files start at offset 0x100

start:
        mov dx, msg     ; DX = address of message
        mov ah, 09h     ; DOS INT 21h function 09 = print string
        int 21h         ; Call DOS

        mov ax, 4C00h   ; DOS INT 21h function 4C = exit, AL=return code
        int 21h

msg     DB "Hello from x86 DOS!", 13, 10, "$"   ; $ terminates the string for INT 21h/09

Core instructions

Moving data

mov ax, 42          ; AX = 42  (immediate)
mov bx, ax          ; BX = AX  (register to register)
mov ax, [bx]        ; AX = word at address BX  (memory)
mov [bx], cx        ; Word at BX = CX
mov al, [1234h]     ; AL = byte at address 1234h
mov word [bx], 100  ; Write 100 to word at BX

Arithmetic

add ax, bx          ; AX = AX + BX
add ax, 10          ; AX = AX + 10
sub bx, cx          ; BX = BX - CX
inc ax              ; AX++
dec bx              ; BX--
mul bx              ; AX = AX * BX  (unsigned, result in DX:AX)
div bx              ; AX = DX:AX / BX, DX = remainder
neg ax              ; AX = -AX

Logic

and ax, 0x00FF      ; AX = AX AND 0x00FF  (isolate low byte)
or  ax, 0x8000      ; AX = AX OR 0x8000   (set bit 15)
xor ax, ax          ; AX = 0  (fastest way to zero a register)
not ax              ; AX = ~AX
shl ax, 1           ; AX = AX << 1  (multiply by 2)
shr ax, 1           ; AX = AX >> 1  (divide by 2, unsigned)

Comparisons and jumps

cmp ax, 10          ; Compare AX with 10 (sets flags)
je  equal           ; Jump if equal (ZF=1)
jne not_equal       ; Jump if not equal
jl  less            ; Jump if less than (signed)
jg  greater         ; Jump if greater than (signed)
jb  below           ; Jump if below (unsigned less-than)
ja  above           ; Jump if above (unsigned greater-than)
jmp label           ; Unconditional jump

Stack

push ax             ; Push AX onto stack
push bx
pop  bx             ; Pop into BX
pop  ax

; PUSH and POP all registers at once
pusha               ; Push AX,CX,DX,BX,SP,BP,SI,DI (286+)
popa                ; Pop them back

Subroutines

        call my_func    ; Push IP and jump to my_func

my_func:
        ; ... do something ...
        ret             ; Pop IP and return

Loops

The LOOP instruction decrements CX and jumps if CX ≠ 0:

        mov cx, 10      ; Loop 10 times
again:  ; ... your code ...
        loop again      ; CX--, jump back if CX != 0

Segmented memory

The 8086 addresses memory using segment:offset pairs. The physical address = (segment × 16) + offset. This gives access to up to 1 MB of RAM.

In a COM file, all four segment registers (CS, DS, ES, SS) point to the same 64 KB segment — the program segment. You can mostly ignore segmentation in small COM programs.

For programs accessing more than 64 KB of data, you'd need to switch DS/ES — but the IDE templates stay within COM constraints.

; COM file memory layout
; CS = DS = ES = SS = PSP segment

; ORG 100h because:
; $0000 to $00FF = PSP (Program Segment Prefix, set up by DOS)
; Your code starts at $0100

mov ax, ds          ; DS already set correctly for COM files

DOS interrupts (INT 21h)

DOS provides services via INT 21h. Set AH to the function number, fill in other registers, then int 21h:

AH Function Notes
01h Read character (echo) Returns char in AL
02h Write character DL = character
09h Write string DX = address, string ends with $
0Ah Buffered input DX = buffer
0Bh Check key status AL = 0 (no key) or $FF
2Ch Get system time CH=hr, CL=min, DH=sec, DL=hundredths
4Ch Exit program AL = return code
; Write character 'A' to stdout
mov dl, 'A'
mov ah, 02h
int 21h

; Read a character (wait for keypress)
mov ah, 01h
int 21h             ; AL = character pressed

BIOS interrupts

The PC BIOS provides low-level services below DOS:

Interrupt Service Notes
INT 10h Video Set mode, write char, scroll, etc.
INT 16h Keyboard Read key, check status
INT 1Ah Timer/clock Get/set time

INT 10h — Video BIOS

; Set video mode — 320x200, 256 colours (Mode 13h)
mov ax, 0013h       ; AH=00 (set mode), AL=13h (mode 13)
int 10h

; Back to text mode
mov ax, 0003h       ; Mode 3 = 80x25 text
int 10h

; Write character at cursor position
mov ah, 0Eh         ; BIOS teletype output
mov al, 'A'         ; Character
int 10h

Mode 13h — 320×200 256 colours

Mode 13h is the beloved VGA mode used by nearly every DOS game from 1990 onwards. The screen is a linear 64000-byte framebuffer at segment $A000:

; Set mode 13h
mov ax, 0013h
int 10h

; Point ES at video memory ($A000:0000)
mov ax, 0A000h
mov es, ax

; Write pixel — colour 15 (white) at centre (160, 100)
; Pixel address = y*320 + x
mov bx, 100*320 + 160
mov byte [es:bx], 15    ; Colour index 15 in the VGA palette

Mode 13h has a 256-colour palette that you can customise via VGA DAC registers (ports $3C8/$3C9).

VGA palette programming

; Set palette entry 15 to red (RGB each 0-63 in VGA DAC)
mov dx, 3C8h        ; VGA DAC write address port
mov al, 15          ; Palette entry 15
out dx, al

mov dx, 3C9h        ; VGA DAC data port
mov al, 63          ; Red = maximum
out dx, al
mov al, 0           ; Green = 0
out dx, al
mov al, 0           ; Blue = 0
out dx, al

Reading the keyboard (INT 16h)

; Wait for a keypress
mov ah, 00h
int 16h             ; AH = scan code, AL = ASCII character

; Check if key is available (non-blocking)
mov ah, 01h
int 16h
jz  no_key          ; ZF set = no key waiting
; If key available: AH=scan code, AL=ASCII (call AH=00 to consume it)

Minimal Mode 13h demo

; Plasma-style colour cycling in Mode 13h
        BITS 16
        ORG 100h

        ; Set mode 13h
        mov ax, 0013h
        int 10h

        ; Point ES at video memory
        mov ax, 0A000h
        mov es, ax

main_loop:
        ; Fill screen with colour based on pixel position
        xor di, di          ; DI = 0 (start of video buffer)
        xor bx, bx          ; BX = pixel counter (y*320+x)
fill:
        mov al, bl          ; Use low byte of position as colour
        stosb               ; Write AL to ES:[DI] and DI++
        inc bx
        cmp bx, 64000       ; Done when 320*200 pixels drawn
        jb fill

        ; Check for keypress — exit on any key
        mov ah, 01h
        int 16h
        jz main_loop        ; No key — loop

        ; Exit
        mov ax, 4C00h
        int 21h

Common mistakes

Forgetting ORG 100h — COM files must start at offset 0x100 because the first 256 bytes ($000–$0FF) of the segment are the PSP (Program Segment Prefix) set up by DOS. If you forget this, all your addresses are wrong.

Word vs byte operationsmov ax, [bx] reads a 16-bit word (two bytes). mov al, [bx] reads one byte. Getting these mixed up is the most common x86 beginner bug.

Segment register surprises — in a COM file, CS=DS=ES=SS. But if you ever change DS or ES (e.g., pointing ES at video memory), remember to reload it from CS when accessing your own data.

String terminator for INT 21h/09h — the print-string function expects the string to end with $ (ASCII 36), not a null byte. NULL-terminated strings are for C; DOS uses $-terminated ones.

Stack direction — the x86 stack grows downward. PUSH AX decrements SP by 2, then writes AX. This is the same direction as the 6502 and Z80.

See also