A command-line x86 assembly emulator with VGA Mode 13h graphics support, written in Go.
A homage to the oldschool demoscene programming of the 90s, where cool graphics demos and effects were crafted in raw assembly language. Write retro VGA graphics programs just like the legends did!
make # Build
./asm-emu examples/noise.asm # Run (opens graphics window)
make test # TestPress ESC or close window to exit.
![]() Fire Effect examples/fire.asm
|
![]() Plasma examples/plasma.asm
|
![]() 3D Starfield examples/starfield.asm
|
![]() Sine Scroller examples/sine_scroller.asm
|
![]() Bouncing Line examples/bouncing-line.asm
|
./asm-emu <file.asm> # Run with graphics window
./asm-emu --gif output.gif <file.asm> # Record to animated GIF
./asm-emu --gif output.gif --gif-frames 60 # Shorter GIF (2 seconds)Options:
--gif <file>- Record output to animated GIF file (headless mode)--gif-frames <n>- Number of frames to capture (default: 90 = 3 seconds at 30fps)
Examples:
pixels.asm(colored pixels)bars.asm(color bars)noise.asm(random pattern with looping until ESC pressed)
.code
MOV AX, 13h ; Set VGA Mode 13h
INT 10h
MOV AX, 0xA000 ; Set ES to VGA segment
MOV ES, AX
XOR DI, DI ; DI = 0 (offset)
MOV AL, 4 ; Red color
MOV [DI], AL ; Write pixel to ES:DI (VGA memory)
HLTNumber formats: 100 (decimal), 0x64 / 64h (hex), 0A000h (hex with letter)
Memory: [BX], [SI], [DI+10] - supports both byte and word operations
Segments: CS, DS, ES, SS - full x86 real mode segment support
Mode 13h: 320×200, 256 colors
Memory access: VGA memory is at segment 0xA000 (linear address 0xA0000)
; Set ES to VGA segment
MOV AX, 0xA000
MOV ES, AX
; Write pixel at position (X, Y)
; Offset = Y * 320 + X
MOV DI, 0 ; offset
MOV AL, 15 ; white color
MOV [DI], AL ; writes to ES:DIColors 0-15: Black, Blue, Green, Cyan, Red, Magenta, Brown, LightGray, DarkGray, LightBlue, LightGreen, LightCyan, LightRed, LightMagenta, Yellow, White
Programs can customize the 256-color palette using VGA DAC ports:
; Set palette color 42 to bright purple (RGB: 63, 0, 63)
MOV DX, 0x3C8 ; DAC write index port
MOV AL, 42 ; Color index to modify
OUT DX, AL
MOV DX, 0x3C9 ; DAC data port
MOV AL, 63 ; Red component (0-63)
OUT DX, AL
MOV AL, 0 ; Green component
OUT DX, AL
MOV AL, 63 ; Blue component
OUT DX, ALPalette ports:
0x3C8: DAC write index (set color to modify)0x3C9: DAC data (write R, G, B in sequence, values 0-63)
Programs can detect and read keyboard input via BIOS INT 16h:
main_loop:
; ... render graphics ...
MOV AH, 0x01 ; Check for keystroke (non-destructive)
INT 0x16
JZ main_loop ; ZF=1 means no key available
MOV AH, 0x00 ; Read keystroke
INT 0x16
; Returns: AH = scan code, AL = ASCII character
CMP AL, 0x1B ; Check if ESC key
JE exit_program
JMP main_loop
exit_program:
HLTSupported keys: ESC, Enter, Space, Backspace, A-Z (lowercase)
Data: MOV, PUSH, POP, XCHG Arithmetic: ADD, SUB, MUL, DIV, IMUL, IDIV, INC, DEC, NEG Logical: AND, OR, XOR, NOT, SHL, SHR, SAL, SAR, ROL, ROR Control: CMP, TEST, JMP, JE/JZ, JNE/JNZ, JG, JGE, JL, JLE, JA, JAE, JB, JBE, CALL, RET, LOOP I/O: IN, OUT (for VGA palette control) Special: INT 10h/16h/21h, NOP, HLT
General Purpose (16-bit): AX, BX, CX, DX, SI, DI, BP, SP General Purpose (8-bit): AL/AH, BL/BH, CL/CH, DL/DH Segment: CS (Code), DS (Data), ES (Extra), SS (Stack) Special: IP (Instruction Pointer) Flags: CF, ZF, SF, OF
Total addressable memory: 1MB (x86 real mode)
VGA Memory: Linear address 0xA0000-0xAFFFF (64,000 bytes)
- Access via segment 0xA000, offset 0x0000-0xF9FF
- 320×200 pixels = 64,000 bytes
Segmentation: Uses authentic x86 real mode addressing
- Linear address = (segment << 4) + offset
- All segments default to 0x0000 for backward compatibility
- VGA Mode 13h graphics - 320×200 resolution with 256-color palette
- x86 real mode segments - Full CS, DS, ES, SS support with authentic addressing
- 1MB addressable memory - True 20-bit address space
- Customizable palette - Modify colors via VGA DAC ports (0x3C8/0x3C9)
- Keyboard input - INT 16h for interactive programs
- Window control - Press ESC or close window to exit (works with infinite loops)
- Complete x86 instruction set - Data movement, arithmetic, logic, control flow
Black screen:
- Set ES to 0xA000:
MOV AX, 0xA000; MOV ES, AX - Use segment-based addressing:
MOV [DI], ALwrites to ES:DI - Ensure program stays in a loop (busy-wait on keyboard) for graphics to render
Won't assemble: Use h suffix for hex starting with letters (0A000h)
Program won't exit: Close the VGA window or press ESC - the emulator will terminate gracefully
- 16-bit real mode only (no protected mode)
- Limited instruction set (no advanced x86 instructions)
- No FPU
- INT 16h function 0x00 is non-blocking (use function 0x01 in a loop for keyboard waits)
- github.com/hajimehoshi/ebiten/v2: Graphics rendering
This is a weekend project built together with Claude Code - an exploration of x86 assembly, emulator development, and the joy of retro programming. Educational project for learning assembly and bringing back the magic of the demoscene era.
MIT




