Building an x86 Assembly Emulator with VGA Graphics Support
This weekend, I fell down a nostalgic rabbit hole and built something I've been wanting to make for years: an x86 assembly emulator with VGA Mode 13h graphics support. If you grew up in the demoscene era of the 90s, you'll know exactly why Mode 13h still holds a special place in many programmers' hearts.
The Demoscene Nostalgia
Back in the 90s, the demoscene was where programmers pushed hardware to its absolute limits. Armed with nothing but raw assembly language and intimate knowledge of VGA registers, talented coders created mind-bending visual effects that ran on machines with a fraction of the power we carry in our pockets today. Mode 13h—the 320×200, 256-color graphics mode—was the canvas for countless fire effects, plasma generators, and rotating 3D objects.
I wanted to recapture some of that magic and make it accessible for anyone curious about how those old-school demos worked.
What I Built
The result is a command-line x86 assembly emulator written in Go that lets you write and run authentic assembly programs with full VGA graphics support. The emulator handles x86 real mode instructions, maintains proper segment registers, and renders pixels to a graphical window using the Ebiten game library.
Here's the simplest possible program to set a red pixel:
.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 (from standard palette)
MOV [DI], AL ; Write pixel to ES:DI
HLT
Run it with ./asm-emu pixels.asm and you get a window with a single red pixel in the top-left corner. Simple, but it works exactly like it would on real hardware.
The Technical Details
Building this required implementing several key components:
x86 Instruction Set: The emulator supports the core instructions you'd need for graphics programming—data movement (MOV, PUSH, POP), arithmetic (ADD, SUB, MUL, DIV), logical operations (AND, OR, XOR, shifts and rotations), and control flow (JMP, conditional jumps, CALL, RET, LOOP).
Real Mode Segmentation: One of the most interesting parts was implementing authentic x86 real mode addressing. The linear address calculation (segment << 4) + offset means you can access the full 1MB address space just like on actual hardware. The VGA framebuffer lives at segment 0xA000, offset 0x0000—exactly where it would be on a real PC.
VGA Mode 13h: This mode gives you a linear framebuffer of 320×200 pixels, with each byte representing one pixel's color index into a 256-color palette. The default palette provides the classic 16 CGA colors, but programs can customize all 256 colors through the VGA DAC ports (0x3C8 for the index, 0x3C9 for RGB data).
BIOS Interrupts: The emulator implements INT 10h for video mode setting and INT 16h for keyboard input, allowing programs to respond to user input just like old DOS programs did.
Creating Graphics
The real fun begins when you start drawing. Here's how you'd fill the screen with random noise:
.code
MOV AX, 13h
INT 10h
MOV AX, 0xA000
MOV ES, AX
XOR DI, DI
loop_start:
; Generate pseudo-random color
MOV AX, DI
XOR AX, 0x1234
MOV AL, AH
MOV [DI], AL
INC DI
CMP DI, 64000 ; 320x200 = 64,000 pixels
JL loop_start
wait_for_key:
MOV AH, 0x01 ; Check for keystroke
INT 0x16
JZ wait_for_key ; No key? Keep waiting
HLT
The emulator also supports customizing the palette for more interesting effects:
; Set palette color 42 to bright purple
MOV DX, 0x3C8 ; DAC write index port
MOV AL, 42
OUT DX, AL
MOV DX, 0x3C9 ; DAC data port
MOV AL, 63 ; Red (0-63)
OUT DX, AL
MOV AL, 0 ; Green
OUT DX, AL
MOV AL, 63 ; Blue
OUT DX, AL
This opens up possibilities for color cycling effects, palette animations, and all the classic tricks demoscene programmers used to create fluid motion without redrawing pixels.
Building It with Claude Code
I built this entire project over a weekend using Claude Code, Anthropic's command-line tool for agentic coding. The workflow was remarkably smooth—I'd describe what I wanted to implement, and Claude would generate the Go code, handle the architecture decisions, and even write the test cases.
What impressed me most was how well Claude understood the technical nuances of x86 assembly. When I described the segment override syntax or the VGA DAC port sequence, it knew exactly what I meant and implemented it correctly. The iterative development process felt natural—we'd implement an instruction, test it with a small assembly program, then move on to the next feature.
What's Next?
This was purely a weekend exploration, but there are some obvious extensions if I return to it:
- More instructions
- More demo effects (and supporting emulator code to make them work)
- Timing simulation (so programs run at consistent speeds)
- Additional video modes (text mode, other graphics modes)
But honestly, the current feature set is enough to write some genuinely fun programs. You can create plasma effects, fire simulations, starfields, and other classic demoscene staples.
Try It Yourself
The entire project is open source and available on GitHub. It's written in Go, builds with a simple make command, and comes with several example programs to get you started.
If you've ever been curious about how those old-school demos worked, or if you want to experience the unique constraints and creativity of programming directly against hardware registers, give it a try. There's something deeply satisfying about writing raw assembly and watching pixels appear on screen, exactly where you told them to.
This project was built as a weekend exploration with Claude Code. All code is MIT licensed and available for educational use.