Take the 2-minute tour ×
Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. It's 100% free, no registration required.

Note: A couple of answers have arrived. Consider upvoting newer answers too.

The 8086 is Intel's first x86 microprocessor. Your task is to write an emulator for it. Since this is relatively advanced, I want to limit it a litte:

  • Only the following opcodes need to be implemented:
    • mov, push, pop, xchg
    • add, adc, sub, sbb, cmp, and, or, xor
    • inc, dec
    • call, ret, jmp
    • jb, jz, jbe, js, jnb, jnz, jnbe, jns
    • stc, clc
    • hlt, nop
  • As a result of this, you only need to calculate the carry, zero and sign flags
  • Don't implement segments. Assume cs = ds = ss = 0.
  • No prefixes
  • No kinds of interrupts or port IO
  • No string functions
  • No two-byte opcodes (0F..)
  • No floating point arithmetic
  • (obviously) no 32-bit things, sse, mmx, ... whatever has not yet been invented in 1979
  • You do not have to count cycles or do any timing

Start with ip = 0 and sp = 100h.

Input: Your emulator should take a binary program in any kind of format you like as input (read from file, predefined array, ...) and load it into memory at address 0.

Output: The video RAM starts at address 8000h, every byte is one (ASCII-)character. Emulate a 80x25 screen to console. Treat zero bytes like spaces.


08000   2E 2E 2E 2E 2E 2E 2E 2E 2E 00 00 00 00 00 00 00   ................
08010   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
08020   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
08030   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
08040   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
08050   48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 00 00 00   Hello,.world!...

Note: This is very similiar to the real video mode, which is usually at 0xB8000 and has another byte per character for colors.

Winning criteria:

  • All of the mentioned instructions need to be implemented
  • I made an uncommented test program (link, nasm source) that should run properly. It outputs

    Hello, world!                                                                   
    ##                                                                            ##
    ##  0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987                          ##
    ##                                                                            ##
    ##  0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400     ##
    ##                                                                            ##
    ##  2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97    ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
  • I am not quite sure if this should be codegolf; it's kind of a hard task, so any submission will win many upvotes anyway. Please comment.

Here are some links to help you on this task:

This is my first entry to this platform. If there are any mistakes, please point them out; if I missed a detail, simply ask.

share|improve this question
far too advanced for me, but I'm very eager to see answers to this question as it's precisely the sort of stuff I'm most interested in! I may take a crack at it later if I'm feeling particularly masochistic... –  Chris Browne Jan 23 '12 at 13:17
@ChrisBrowne good luck being masochistic! I am currently turning my 8086 into a 80386 and have learned a lot from this project so far. –  copy Jan 23 '12 at 17:24
+1 +favorite ...i can't begin to express the feeling i got when i saw this question. –  ixtmixilix Feb 26 '12 at 21:33
@copy It is never too late to make a golf competition for every single language/host pair –  Yauhen Yakimovich Dec 17 '12 at 0:21
@MartinBüttner Sure, the question is older than that tag and has basically been a popularity contest anyway –  copy Mar 21 at 0:32

11 Answers 11

Feel free to fork and golf it: https://github.com/julienaubert/py8086

Result I included an interactive debugger as well.

CF:0 ZF:0 SF:0 IP:0x0000
AX:0x0000  CX:0x0000  DX:0x0000  BX:0x0000  SP:0x0100  BP:0x0000  SI:0x0000  DI:0x0000
AL:  0x00  CL:  0x00  DL:  0x00  BL:  0x00  AH:  0x00  CH:  0x00  DH:  0x00  BH:  0x00
stack: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ...
cmp SP, 0x100
[Enter]:step [R]:run [B 0xadr]:add break [M 0xadr]:see RAM [Q]:quit

B 0x10
M 0x1
M 0x1: 0xfc 0x00 0x01 0x74 0x01 0xf4 0xbc 0x00 0x10 0xb0 0x2e 0xbb ...

CF:0 ZF:0 SF:1 IP:0x0010
AX:0x002e  CX:0x0000  DX:0x0000  BX:0xffff  SP:0x1000  BP:0x0000  SI:0x0000  DI:0x0000
AL:  0x2e  CL:  0x00  DL:  0x00  BL:  0xff  AH:  0x00  CH:  0x00  DH:  0x00  BH:  0x00
stack: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ...
cmp BX, 0xffff
[Enter]:step [R]:run [B 0xadr]:add break [M 0xadr]:see RAM [Q]:quit

There are three files: emu8086.py (required) console.py (optional for display output), disasm.py (optional, to get a listing of the asm in the codegolf).

To run with the display (note uses curses):

python emu8086.py 

To run with interactive debugger:

python emu8086.py a b

To run with non-interactive "debugger":

python emu8086.py a

The program "codegolf" should be in the same directory.




On github

share|improve this answer
That's one hell of a first Code Golf post. +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 ... –  Dillon Cower Feb 26 '12 at 22:59
@DC thanks :) was a fun challenge! –  j-a Feb 26 '12 at 23:20
Still can't believe someone actually did this :-) Great job! –  copy Feb 26 '12 at 23:46
Amazing! Congratulations! How many lines it had in the end? –  Will P Nov 30 '12 at 18:56

Haskell, 256 234 lines

I've had this work-in-progress one for some time, I intended to polish it a bit more before publishing, but now the fun's officially started, there's not much point in keeping it hidden anymore. I noticed while extracting it that it's exactly 256 lines long, so I suppose it is at a "remarkable" point of its existence.

What's in: barely enough of the 8086 instruction set to run the example binary flawlessly. Self-modifying code is supported. (prefetch: zero bytes)
Ironically, the first sufficient iterations of the code were longer and supported less of the opcode span. Refactoring ended up beneficial both to code length and to opcode coverage.

What's out: obviously, segments, prefixes and multibyte opcodes, interrupts, I/O ports, string operations, and FP. I initially did follow the original PUSH SP behavior, but had to drop it after a few iterations.

Carry flag results are probably very messed up in a few cases of ADC/SBB.

Anyway, here's the code:

{-# LANGUAGE FlexibleContexts #-}

import Prelude hiding (read)
import Data.Char (chr,isPrint)
import Data.Word (Word8,Word16)
import Data.Int (Int8)
import Data.Bits
import Data.IORef
import Data.Array ((!))
import Data.Array.IO
import Control.Monad.Reader
import Control.Exception (bracket_)
import System
import System.IO (hSetEncoding,stdin,latin1)

ifte t f c = if c then t else f

concatBytes :: Word8 -> Word8 -> Word16
concatBytes l h = (fromIntegral l) .|. (fromIntegral h `shiftL` 8)

byteToWordSE :: Word8 -> Word16
byteToWordSE = (fromIntegral :: Int8 -> Word16) .
               (fromIntegral :: Word8 -> Int8)

wordToByteL,wordToByteH :: Word16 -> Word8
wordToByteL = fromIntegral
wordToByteH = fromIntegral . (`shiftR` 8)

byte = undefined :: Word8
word = undefined :: Word16

type Place = (IOArray Word16 Word8,Word16)
[regAl,regAh,regCl,regCh,regDl,regDh,regBl,regBh] = [0..7]
[regAx,regCx,regDx,regBx,regSp,regBp,regSi,regDi] = [0,2..14]

data Proc = Proc {
    ram :: IOArray Word16 Word8
  , regs :: IOArray Word16 Word8
  , ip :: IORef Word16
  , cf :: IORef Bool
  , zf :: IORef Bool
  , sf :: IORef Bool

readProc  ext   = liftIO .      readIORef    . ext =<< ask
writeProc ext f = liftIO . flip writeIORef f . ext =<< ask

modifyIP f = do
  ipRef <- liftM ip ask
  old <- liftIO $ readIORef ipRef
  liftIO $ modifyIORef ipRef f
  return old

readInstr8 = modifyIP (+1) >>= readRam
readInstr16 = liftM2 concatBytes readInstr8 readInstr8

class (Ord a,Bits a) => Width a where
  read :: MonadIO m => Place -> m a
  write :: MonadIO m => Place -> a -> m ()

instance Width Word8 where
  read = liftIO . uncurry readArray
  write p = liftIO . uncurry writeArray p

instance Width Word16 where
  read (p,a) = liftM2 concatBytes (read (p,a)) (read (p,a+1))
  write (p,a) val = do
    write (p,a)   $ wordToByteL val
    write (p,a+1) $ wordToByteH val

readReg reg  = ask >>= \p -> read (regs p,reg)
readRam addr = ask >>= \p -> read (ram p,addr)
writeReg reg val  = ask >>= \p -> write (regs p,reg) val
writeRam addr val = ask >>= \p -> write (ram p,addr) val

decodeReg8  n = fromIntegral $ (n `shiftL` 1) .|. (n `shiftR` 2)
decodeReg16 n = fromIntegral $  n `shiftL` 1
readDecodedReg8 = readReg . decodeReg8
readDecodedReg16 = readReg . decodeReg16

readModRM = do
  modRM <- readInstr8
  let mod   =  modRM           `shiftR` 6
      opReg = (modRM .&. 0x38) `shiftR` 3
      rm    =  modRM .&. 0x07
  proc <- ask
  operand <- case mod of
               0 -> do
                 addr <- case rm of
                           1 -> liftM2 (+) (readReg regBx) (readReg regDi)
                           2 -> liftM2 (+) (readReg regBp) (readReg regSi)
                           6 -> readInstr16
                           7 -> readReg regBx
                 return (ram proc,addr)
               2 -> do
                 addr <- case rm of
                           5 -> liftM2 (+) (readReg regDi) readInstr16
                           7 -> liftM2 (+) (readReg regBx) readInstr16
                 return (ram proc,addr)
               3 -> return (regs proc,2*fromIntegral rm)
  return (operand,opReg,opReg)

push16 val = do -- PUSH by value (doesn't reproduce PUSH SP behavior)
  sp <- liftM (subtract 2) (readReg regSp)
  writeReg regSp sp
  writeRam sp (val :: Word16)
pop16 = do
  sp <- readReg regSp
  val <- readRam sp
  writeReg regSp (sp+2)
  return val

jump cond = when cond . void . modifyIP . (+) . byteToWordSE =<< readInstr8

alu :: (Width w,MonadIO m,MonadReader Proc m) => w -> m w -> m w -> Place -> (w -> w -> m (Bool,Maybe Bool,w)) -> m ()
alu _ a b r op = do
  (rw,c,v) <- join (liftM2 op a b)
  when rw $ write r v
  maybe (return ()) (writeProc cf) c
  writeProc zf (v == 0)
  writeProc sf (testBit v (bitSize v - 1))
decodeALU 0 = (\a b -> return (True, Just (a >= negate b),       a   +   b))
decodeALU 1 = (\a b -> return (True, Just False,                 a  .|.  b))
decodeALU 2 = (\a b -> liftM (ifte 1 0) (readProc cf) >>= \c ->
                       return (True, Just (a >= negate (b + c)), a + b + c))
decodeALU 3 = (\a b -> liftM (ifte 1 0) (readProc cf) >>= \c ->
                       return (True, Just (a < b + c),           a - b - c))
decodeALU 4 = (\a b -> return (True, Just False,                 a  .&.  b))
decodeALU 5 = (\a b -> return (True, Just (a <= b),              a   -   b))
decodeALU 6 = (\a b -> return (True, Just False,                 a `xor` b))
decodeALU 7 = (\a b -> return (False,Just (a <= b),              a   -   b))
opIncDec    =  \a b -> return (True, Nothing,                    a   +   b)

processInstr = do
  opcode <- readInstr8
  regs <- liftM regs ask
  let zReg = (regs,decodeReg16 (opcode .&. 0x07))
  if opcode < 0x40 then -- no segment or BCD
    let aluOp = (opcode .&. 0x38) `shiftR` 3 in case opcode .&. 0x07 of
    0 -> do
      (operand,reg,_) <- readModRM
      alu byte (read operand) (readDecodedReg8 reg) operand (decodeALU aluOp)
    1 -> do
      (operand,reg,_) <- readModRM
      alu word (read operand) (readDecodedReg16 reg) operand (decodeALU aluOp)
    4 -> alu byte (readReg regAl) readInstr8 (regs,regAl) (decodeALU aluOp)
  else case opcode .&. 0xF8 of -- 16-bit (mostly) reg ops
    0x40 -> alu word (read zReg) (return   1 ) zReg opIncDec -- 16b INC
    0x48 -> alu word (read zReg) (return (-1)) zReg opIncDec -- 16b DEC
    0x50 -> read zReg >>= push16                        -- 16b PUSH reg
    0x58 -> pop16 >>= write zReg                        -- 16b POP reg
    0x90 -> do                                          -- 16b XCHG (or NOP)
      v1 <- read zReg
      v2 <- readReg regAx
      write zReg (v2 :: Word16)
      writeReg regAx (v1 :: Word16)
    0xB0 -> readInstr8  >>= write zReg -- (BUG!)        -- 8b MOV reg,imm
    0xB8 -> readInstr16 >>= write zReg                  -- 16b MOV reg,imm
    _ -> case if opcode == 0x80 then 0x82 else opcode of
      0x72 -> jump       =<< (readProc cf)              -- JB/JNAE/JC
      0x74 -> jump       =<< (readProc zf)              -- JE/JZ
      0x75 -> jump . not =<< (readProc zf)              -- JNE/JNZ
      0x76 -> jump       =<< liftM2 (||) (readProc cf) (readProc zf) -- JBE
      0x77 -> jump . not =<< liftM2 (||) (readProc cf) (readProc zf) -- JA
      0x79 -> jump . not =<< (readProc sf)              -- JNS
      0x81 -> do                                        -- 16b arith to imm
        (operand,_,op) <- readModRM
        alu word (read operand) readInstr16 operand (decodeALU op)
      0x82 -> do                                        -- 8b arith to imm
        (operand,_,op) <- readModRM
        alu byte (read operand) readInstr8 operand (decodeALU op)
      0x83 -> do                                        -- 16b arith to 8s imm
        (operand,_,op) <- readModRM
        alu word (read operand) (liftM byteToWordSE readInstr8) operand
            (decodeALU op)
      0x86 -> do                                        -- 8b XCHG reg,RM
        (operand,reg,_) <- readModRM
        v1 <- readDecodedReg8 reg
        v2 <- read operand
        writeReg (decodeReg8 reg) (v2 :: Word8)
        write operand v1
      0x88 -> do                                        -- 8b MOV RM,reg
        (operand,reg,_) <- readModRM
        readDecodedReg8 reg >>= write operand
      0x89 -> do                                        -- 16b MOV RM,reg
        (operand,reg,_) <- readModRM
        readDecodedReg16 reg >>= write operand
      0x8A -> do                                        -- 8b MOV reg,RM
        (operand,reg,_) <- readModRM
        val <- read operand 
        writeReg (decodeReg8 reg) (val :: Word8)
      0x8B -> do                                        -- 16b MOV reg,RM
        (operand,reg,_) <- readModRM
        val <- read operand 
        writeReg (decodeReg16 reg) (val :: Word16)
      0xC3 -> pop16 >>= writeProc ip                    -- RET
      0xC7 -> do                                        -- 16b MOV RM,imm
        (operand,_,_) <- readModRM
        readInstr16 >>= write operand
      0xE8 -> readInstr16 >>= modifyIP . (+) >>= push16 -- CALL relative
      0xEB -> jump True                                 -- JMP short
      0xF4 -> liftIO (exitWith ExitSuccess)             -- HLT
      0xF9 -> writeProc cf True                         -- STC
      0xFE -> do                                        -- 8-bit INC/DEC RM
        (operand,_,op) <- readModRM
        alu byte (read operand) (return $ 1-2*op) operand
            (\a b -> return (True,Nothing,a+b)) -- kinda duplicate :(

main = do
  ramRef <- newArray (0,0xFFFF) 0 :: IO (IOArray Word16 Word8)
  hSetEncoding stdin latin1
  mapM_ (uncurry (writeArray ramRef)) .
    zip [0..] .
    map (fromIntegral . fromEnum) =<<
  regFile <- newArray (0,15) 0 :: IO (IOArray Word16 Word8)
  ip <- newIORef 0
  cf <- newIORef False
  zf <- newIORef False
  sf <- newIORef False
  let proc = Proc ramRef regFile ip cf zf sf
  runReaderT (writeReg regSp (0x100 :: Word16)) proc

  bracket_ (return ()) 
           (dumpScreen ramRef) 
           (forever . flip runReaderT proc $ processInstr)

dumpScreen ramRef = do
  mem <- freeze ramRef
  forM_ [0..25] $ \i -> do
    forM_ [0..79] $ \j -> do
      let c = chr . fromIntegral $ mem ! (0x8000 + 80*i + j)
      putChar (if isPrint c then c else ' ')
    putChar '\n'

The output for the provided sample binary matches the specification perfectly. Try it out using an invocation such as:

runhaskell 8086.hs <8086.bin

Most non-implemented operations will simply result in a pattern matching failure.

I still intend to factor quite a bit more, and implement actual live output with curses.

Update 1: got it down to 234 lines. Better organized the code by functionality, re-aligned what could be, tried to stick to 80 columns. And refactored the ALU multiple times.

share|improve this answer
Nice one. It's really short, especially compared to my solution and the other one. Your code looks very good too, although I need to learn Haskell first. –  copy Feb 27 '12 at 18:17
Nice work! Very short. I should learn haskell. –  j-a Feb 29 '12 at 17:48
What's .|.? /10char –  octatoan Mar 21 at 2:07
@octatoan the operation known in x86 opcodes as OR. –  J B Mar 22 at 21:13

Postscript (130 200 367 517 531 222 246 lines)

Still a work-in-progress, but I wanted to show some code in an effort to encourage others to show some code.

The register set is represented as one string, so the various byte- and word- sized registers can naturally overlap by referring to substrings. Substrings are used as pointers throughout, so that a register and a memory location (substring of the memory string) can be treated uniformly in the operator functions.

Then there are a handful of words to get and store data (byte or word) from a "pointer", from memory, from mem[(IP)] (incrementing IP). Then there are a few functions to fetch the MOD-REG-R/M byte and set the REG and R/M and MOD variables, and decode them using tables. Then the operator functions, keyed to the opcode byte. So the execution loop is simply fetchb load exec.

I've only got a handful of opcodes implemented, but gGetting the operand decoding felt like such a milestone that I wanted to share it.

edit: Added words to sign-extend negative numbers. More opcodes. Bugfix in the register assignments. Comments. Still working on flags and filling-out the operators. Output presents some choices: output text to stdout on termination, continuously output using vt100 codes, output to the image window using CP437 font.

edit: Finished writing, begun debugging. It gets the first four dots of output! Then the carry goes wrong. Sleepy.

edit: I think I've got the Carry Flag sorted. Some of the story happened on comp.lang.postscript. I've added some debugging apparatus, and the output goes to the graphics window (using my previously-written Code-Page 437 Type-3 font), so the text output can be full of traces and dumps. It writes "Hello World!" and then there's that suspicious caret. Then a whole lotta nothin'. :( We'll get there. Thanks for all the encouragement!

edit: Runs the test to completion. The final few bugs were: XCHG doing 2{read store}repeat which of course copies rather than exchanges, AND not setting flags, (FE) INC trying to get a word from a byte pointer.

edit: Total re-write from scratch using the concise table from the manual (turned a new page!). I'm starting to think that factoring-out the store from the opcodes was a bad idea, but it helped keep the optab pretty. No screenshot this time. I added an instruction counter and a mod-trigger to dump the video memory, so it interleaves easily with the debug info.

edit: Runs the test program, again! The final few bugs for the shorter re-write were neglecting to sign-extend the immediate byte in opcodes 83 (the "Immediate" group) and EB (short JMP). 24-line increase covers additional debugging routines needed to track down those final bugs.

%a8086.ps Draught2:BREVITY
[/NULL<0000>/nul 0
/mem 16#ffff string %16-bit memory
/CF 0 /OF 0 /AF 0 /ZF 0 /SF 0
/regs 20 string >>begin %register byte storage
0{AL AH CL CH DL DH BL BH}{regs 2 index 1 getinterval def 1 add}forall pop
0{AX CX DX BX SP BP SI DI IP FL}{regs 2 index 2 getinterval def 2 add}forall pop

%getting and fetching
[/*b{0 get} %get byte from pointer
/*w{dup *b exch 1 get bbw} %get word from pointer
/*{{*b *w}W get exec} %get data(W) from pointer
/bbw{8 bitshift add} %lo-byte hi-byte -> word
/shiftmask{2 copy neg bitshift 3 1 roll 1 exch bitshift 1 sub and}
/fetchb{IP *w mem exch get bytedump   IP dup *w 1 add storew} % byte(IP++)
/fetchw{fetchb fetchb bbw} % word(IP),IP+=2

%storing and accessing
/storeb{16#ff and 0 exch put} % ptr val8 -> -
/storew{2 copy storeb -8 bitshift 16#ff and 1 exch put} % ptr val16 -> -
/stor{{storeb storew}W get exec} % ptr val(W) -> -
/memptr{16#ffff and mem exch {1 2}W get getinterval} % addr -> ptr(W)

%decoding the mod-reg-reg/mem byte
/mrm{fetchb 3 shiftmask /RM exch def 3 shiftmask /REG exch def /MOD exch def}
/decreg{REGTAB W get REG get} % REGTAB[W][REG]
%2 indexes,   with immed byte,   with immed word
/2*w{exch *w exch *w add}/fba{fetchb add}/fwa{fetchw add}
/RMTAB[[{BX SI 2*w}{BX DI 2*w}{BP SI 2*w}{BP DI 2*w}
    {SI *w}{DI *w}{fetchw}{BX *w}]
[{BX SI 2*w fba}{BX DI 2*w fba}{BP SI 2*w fba}{BP DI 2*w fba}
    {SI *w fba}{DI *w fba}{BP *w fba}{BX *w fba}]
[{BX SI 2*w fwa}{BX DI 2*w fwa}{BP SI 2*w fwa}{BP DI 2*w fwa}
    {SI *w fwa}{DI *w fwa}{BP *w fwa}{BX *w fwa}]]
/decrm{MOD 3 eq{REGTAB W get RM get} %MOD=3:register mode
    {RMTAB MOD get RM get exec memptr}ifelse} % RMTAB[MOD][RM] -> addr -> ptr

%setting and storing flags
/flagw{OF 11 bitshift SF 7 bitshift or ZF 6 bitshift or AF 4 bitshift CF or}
/wflag{dup 1 and /CF exch def dup -4 bitshift 1 and /AF exch def
    dup -6 bitshift 1 and /ZF exch def dup -7 bitshift 1 and /SF exch def
    dup -11 bitshift 1 and /OF exch def}
/nz1{0 ne{1}{0}ifelse}
/logflags{/CF 0 def /OF 0 def /AF 0 def %clear mathflags
    dup {16#80 16#8000}W get and nz1 /SF exch def
    dup {16#ff 16#ffff}W get and 0 eq{1}{0}ifelse /ZF exch def}
/mathflags{{z y x}{exch def}forall
    /CF z {16#ff00 16#ffff0000}W get and nz1 def
    /OF z x xor z y xor and {16#80 16#8000}W get and nz1 def
    /AF x y xor z xor 16#10 and nz1 def
    z} %leave the result on stack

%opcodes (each followed by 'stor')  %% { OPTAB fetchb get exec stor } loop
/ADD{2 copy add logflags mathflags}
/OR{or logflags}
/ADC{CF add ADD}
/SBB{D 1 xor {exch}repeat CF add 2 copy sub logflags mathflags}
/AND{and logflags}
/SUB{D 1 xor {exch}repeat 2 copy sub logflags mathflags}
/XOR{xor logflags}
/CMP{3 2 roll pop NULL 3 1 roll SUB} %dummy stor target
/INC{t CF exch dup * 1 ADD 3 2 roll /CF exch def}
/DEC{t CF exch dup * 1 SUB 3 2 roll /CF exch def}
/PUSH{SP dup *w 2 sub storew   *w SP *w memptr exch}
/POP{SP *w memptr *w   SP dup *w 2 add storew}

/jrel{w {CBW IP *w add IP exch}{NULL exch}ifelse}
/JO{fetchb OF 1 eq jrel }
/JNO{fetchb OF 0 eq jrel }
/JB{fetchb CF 1 eq jrel }
/JNB{fetchb CF 0 eq jrel }
/JZ{fetchb ZF 1 eq jrel }
/JNZ{fetchb ZF 0 eq jrel }
/JBE{fetchb CF ZF or 1 eq jrel }
/JNBE{fetchb CF ZF or 0 eq jrel }
/JS{fetchb SF 1 eq jrel }
/JNS{fetchb SF 0 eq jrel }
/JL{fetchb SF OF xor 1 eq jrel }
/JNL{fetchb SF OF xor 0 eq jrel }
/JLE{fetchb SF OF xor ZF or 1 eq jrel }
/JNLE{fetchb SF OF xor ZF or 0 eq jrel }

/bw{dup 16#80 and 0 ne{16#ff xor 1 add 16#ffff xor 1 add}if}
/immed{ W 2 eq{ /W 1 def
            mrm decrm dup * fetchb bw
    }{ mrm decrm dup * {fetchb fetchw}W get exec }ifelse
    exch IMMTAB REG get dup == exec }

%/TEST{ }
/XCHG{3 2 roll pop 2 copy exch * 4 2 roll * stor }
/AXCH{w dup AX XCHG }
/NOP{ NULL nul }
/pMOV{D{exch}repeat pop }
/mMOV{ 3 1 roll pop pop }
/MOV{ }
/LEA{w mrm decreg RMTAB MOD get RM get exec }

/CBW{dup 16#80 and 0 ne {16#ff xor 1 add 16#ffff xor 1 add } if }
/CWD{dup 16#8000 and 0 ne {16#ffff xor 1 add neg } if }
/CALL{w xp /xp{}def fetchw IP PUSH storew IP dup *w 3 2 roll add dsp /dsp{}def }
%/WAIT{ }
/PUSHF{NULL dup flagw storew 2 copy PUSH }
/POPF{NULL dup POP *w wflag }
%/SAHF{ }
%/LAHF{ }

%/MOVS{ }
%/CMPS{ }
%/STOS{ }
%/LODS{ }
%/SCAS{ }
/RET{w IP POP storew SP dup * 3 2 roll add }
%/LES{ }
%/LDS{ }

/JMP{IP dup fetchw exch *w add}
/sJMP{IP dup fetchb bw exch *w add}

/CMC{/CF CF 1 xor def NULL nul}
/CLC{/CF 0 def NULL nul}
/STC{/CF 1 def NULL nul}

/NOT{not logflags }
/NEG{neg logflags }
/Grp1{mrm decrm dup * GRP1TAB REG get
dup ==
exec }
/GRP2TAB{INC DEC {id CALL}{l id CALL}{id JMP}{l id JMP} PUSH --- } cvlit
/Grp2{mrm decrm GRP2TAB REG get
dup ==
exec }

%optab shortcuts
/2*{exch * exch *}
/rm{mrm decreg decrm D index 3 1 roll 2*} % fetch,decode mrm -> dest *reg *r-m
/rmp{mrm decreg decrm D index 3 1 roll} % fetch,decode mrm -> dest reg r-m
/ia{ {{AL dup *b fetchb}{AX dup *w fetchw}}W get exec } %immed to accumulator
/is{/W 2 def}
/b{/W 0 def} %select byte operation
/w{/W 1 def} %select word operation
/t{/D 1 def} %dest = reg
/f{/D 0 def} %dest = r/m
/xp{} /dsp{}
%/far{ /xp { <0000> PUSH storew } /dsp { fetchw pop } def }
/i{ {fetchb fetchw}W get exec }

{b f rm ADD}{w f rm ADD}{b t rm ADD}{w t rm ADD}{b ia ADD}{w ia ADD}{ES PUSH}{ES POP} %00-07
 {b f rm OR}{w f rm OR}{b t rm OR}{w t rm OR}{b ia OR}{w ia OR}{CS PUSH}{}            %08-0F
{b f rm ADC}{w f rm ADC}{b t rm ADC}{w t rm ADC}{b ia ADC}{w ia ADC}{SS PUSH}{SS POP} %10-17
 {b f rm SBB}{w f rm SBB}{b t rm SBB}{w t rm SBB}{b ia SBB}{w ia SBB}{DS PUSH}{DS POP}%18-1F
{b f rm AND}{w f rm AND}{b t rm AND}{w t rm AND}{b ia AND}{w ia AND}{ES SEG}{DAA}     %20-27
 {b f rm SUB}{w f rm SUB}{b t rm SUB}{w t rm SUB}{b ia SUB}{w ia SUB}{CS SEG}{DAS}    %28-2F
{b f rm XOR}{w f rm XOR}{b t rm XOR}{w t rm XOR}{b ia XOR}{w ia XOR}{SS SEG}{AAA}     %30-37
 {b f rm CMP}{w f rm CMP}{b t rm CMP}{w t rm CMP}{b ia CMP}{w ia CMP}{DS SEG}{AAS}    %38-3F
{w AX INC}{w CX INC}{w DX INC}{w BX INC}{w SP INC}{w BP INC}{w SI INC}{w DI INC}      %40-47
 {w AX DEC}{w CX DEC}{w DX DEC}{w BX DEC}{w SP DEC}{w BP DEC}{w SI DEC}{w DI DEC}     %48-4F
 {AX POP}{CX POP}{DX POP}{BX POP}{SP POP}{BP POP}{SI POP}{DI POP}                     %58-5F
{}{}{}{}{}{}{}{}  {}{}{}{}{}{}{}{}                                                    %60-6F
{JO}{JNO}{JB}{JNB}{JZ}{JNZ}{JBE}{JNBE} {JS}{JNS}{JP}{JNP}{JL}{JNL}{JLE}{JNLE}         %70-7F

{b f immed}{w f immed}{b f immed}{is f immed}{b TEST}{w TEST}{b rmp XCHG}{w rmp XCHG}   %80-87
 {b f rm pMOV}{w f rm pMOV}{b t rm pMOV}{w t rm pMOV}                                 %88-8B
   {sr f rm pMOV}{LEA}{sr t rm pMOV}{w mrm decrm POP}                                 %8C-8F
 {CBW}{CWD}{far CALL}{WAIT}{PUSHF}{POPF}{SAHF}{LAHF}                                  %98-9F
{b AL m MOV}{w AX m MOV}{b m AL MOV}{b AX m MOV}{MOVS}{MOVS}{CMPS}{CMPS}              %A0-A7
 {b i a TEST}{w i a TEST}{STOS}{STOS}{LODS}{LODS}{SCAS}{SCAS}                         %A8-AF
{b AL i MOV}{b CL i MOV}{b DL i MOV}{b BL i MOV}                                      %B0-B3
 {b AH i MOV}{b CH i MOV}{b DH i MOV}{b BH i MOV}                                     %B4-B7
 {w AX i MOV}{w CX i MOV}{w DX i MOV}{w BX i MOV}                                     %B8-BB
 {w SP i MOV}{w BP i MOV}{w SI i MOV}{w DI i MOV}                                     %BC-BF
{}{}{fetchw RET}{0 RET}{LES}{LDS}{b f rm i mMOV}{w f rm i mMOV}                       %C0-B7
 {}{}{fetchw RET}{0 RET}{3 INT}{fetchb INT}{INTO}{IRET}                               %C8-CF
{b Shift}{w Shift}{b v Shift}{w v Shift}{AAM}{AAD}{}{XLAT}                            %D0-D7
 {0 ESC}{1 ESC}{2 ESC}{3 ESC}{4 ESC}{5 ESC}{6 ESC}{7 ESC}                             %D8-DF
{LOOPNZ}{LOOPZ}{LOOP}{JCXZ}{b IN}{w IN}{b OUT}{w OUT}                                 %E0-E7
 {CALL}{JMP}{far JMP}{sJMP}{v b IN}{v w IN}{v b OUT}{v w OUT}                         %E8-EF
{LOCK}{}{REP}{z REP}{HLT}{CMC}{b Grp1}{w Grp}                                         %F0-F7
 {CLC}{STC}{CLI}{STI}{CLD}{STD}{b Grp2}{w Grp2}                                       %F8-FF

/break{ /hook /pause load def }
/c{ /hook {} def }
    cvx {exec}stopped pop }
/pause{ doprompt }

/bytedump{ <00> dup 0 3 index put stdout exch writehexstring ( )print }
/regdump{ REGTAB 1 get{ stdout exch writehexstring ( )print }forall
    stdout IP writehexstring ( )print
    {(NC )(CA )}CF get print
    {(NO )(OV )}OF get print
    {(NS )(SN )}SF get print
    {(NZ )(ZR )}ZF get print
    stdout 16#1d3 w memptr writehexstring
    OPTAB fetchb get
    dup ==
    %pstack flush
    /ic ic 1 add def ictime

    0 1 28 {
        80 mul 16#8000 add mem exch 80 getinterval {
            dup 0 eq { pop 32 } if
                    dup 32 lt 1 index 126 gt or { pop 46 } if
            stdout exch write
        } forall (\n)print
    } for
/ic 0
/ictime{ic 10 mod 0 eq {onq} if}
/timeq 10
/onq{ %printvideo
currentdict{dup type/arraytype eq 1 index xcheck and
    {bind def}{pop pop}ifelse}forall

SP 16#100 storew
(codegolf.8086)(r)file mem readstring pop



And the output (with the tail-end of abbreviated debugging output).

75 {JNZ}
19 43 {w BX INC}
83 {is f immed}
fb 64 CMP
76 {JBE}
da f4 {HLT}
Hello, world!

##                                                                            ##
##  0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987                          ##
##                                                                            ##
##  0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400     ##
##                                                                            ##
##  2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97    ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##

share|improve this answer
I wonder... Is the hot-key to close an application alt-F4 because F4h is the 8086 HLT opcode? –  luser droog Nov 30 '12 at 21:49
I just want to tell you that you are absolutely awesome for implementing this in Postscript. –  cemper93 Dec 9 '12 at 13:32
That code is short. It deserves more upvotes. Have mine, for a start. –  J B Feb 23 '13 at 10:34
Thanks. I took a lot of inspiration from yours. –  luser droog Feb 23 '13 at 17:22
wait... postscript is a programming language?! ;) –  naxa Mar 30 '13 at 19:35

C - 7143 lines (CPU itself 3162 lines)

EDIT: The Windows build now has drop-down menus to change out virtual disks.

I've written a full 80186/V20 PC emulator (with CGA/MCGA/VGA, sound blaster, adlib, mouse, etc), it's not a trivial thing to emulate an 8086 by any means. It took many months to get fully accurate. Here's the CPU module only out of my emulator.


I'll be the first to admit I use wayyy too many global variables in this emulator. I started writing this when I was still pretty new to C, and it shows. I need to clean some of it up one of these days. Most of the other source files in it don't look so ugly.

You can see all of the code (and some screenshots, one is below) through here: http://sourceforge.net/p/fake86

I would be very very happy to help anybody else out who is wanting to write their own, because it's a lot of fun, and you learn a LOT about the CPU! Disclaimer: I didn't add the V20's 8080 emulation since its almost never been used in a PC program. Seems like a lot of work for no gain.

Street Fighter 2!

share|improve this answer
Good job! Do the games actually run at full speed? –  copy Sep 9 '13 at 2:27
Thanks. Yeah, it runs many times faster than an 8088. On a modern system it can do 486-like speeds. On a real good processor, it's like a low-end Pentium. Unfortunately emulating a CPU can't be multithreaded really. I do all the video rendering in it's own thread though. I've run it on my old 400 MHz PowePC G3 also, on that it is down to true 8088 speeds. –  Mike C Sep 9 '13 at 3:09
Awesome! I also wanted to implement more op codes and segmentation; however, was unable to find very many test programs to run on it. Did you download old roms? –  Dave C Sep 23 '13 at 12:03
Dave, no actually there is a serious lack of 8086 test roms out there surprisingly as you found out too. The way I went about it was to just start by making a generic XT BIOS ROM run correctly. If that much works, your segmentation is likely fine. After that, it was just debugging until DOS started working... then on to apps and games! :) –  Mike C Sep 27 '13 at 17:42
@MikeC I'd like some beginner help or pointers! (Pun Intended :P). I've been a Desktop and Web App developer for many years now and slowly I've gotten to a point where I have the linux source code. I generally understand how how various pieces of an OS function and I've been able to play with tiny toy OS projects. But interacting with direct hardware just eludes me! –  gideon Jan 26 '14 at 6:22


I would like to submit our entry for this code challenge. It was written in c++ and runs the test program perfectly. We have implemented 90% of One Byte Op Codes and Basic Segmentation(some disabled because it does not work with the test program).

Program Write Up: http://davecarruth.com/index.php/2012/04/15/creating-an-8086-emulator

You can find the code in a zip file at the end of the Blog Post.

Screenshot executing test program: enter image description here

This took quite a bit of time... if you have any questions or comments then feel free to message me. It was certainly a great exercise in partner programming.

share|improve this answer
It's always good when people have fun on this challenge :) Just a couple of notes: My test program should work (and was tested) with all segments zeroed. Looking at some of your code, I noticed that the ret imm instruction is wrong (see here) and you're missing the 0xff group. I like your error messages though: throw "Immediate value can not store a value, retard."; –  copy Apr 16 '12 at 17:44
We had two main issues with the test program: 1) Segmentation - when there is a CALL we were pushing the CS on the stack... one of the functions in the test program didn't like this. 2) The test program expected our memory to be initialized to zero. Anyways, we had a lot of fun, thank so much for posting! –  Dave C Apr 16 '12 at 21:00
You might have made a mistake there: Near jumps (0xE8) don't push the cs register –  copy Apr 17 '12 at 16:57
That would be the problem, good catch! You seem very experienced with the 8086, did you program for it? –  Dave C Apr 18 '12 at 22:19
I am actually working on a x86 emulator project by myself. It's running freedos quite well and I currently work on full 32 bit support; just did not post here because it might not be fair to other posters (and the source code is slightly messed up). –  copy Apr 19 '12 at 14:01


I am writing a 486 emulator in javascript inspired by jslinux. If I had known how much work it would be, I would probably never have started, but now I want to finish it.

Then I came across your challenge and was very happy to have a 8086 program to test with.


You can "see" it run live here: http://codinguncut.com/jsmachine/

I had one issue when printing out the graphics buffer. Where there should be spaces, the memory contains "00" elements. Is it correct to interpret "0x00" as space or do I have a bug in my emulator?



share|improve this answer
Interesting, I actually know your name from your Screencasts, which I watched after the Haskell response in this challenge (and I also started an x86 emulator in Javascript). Yes, zero bytes should appear as spaces. I have also added the screenshot to your post. +1 anyway :-) –  copy Nov 13 '12 at 20:56
@Johannes I had a quick look through mycpu-min.js code. From what I can tell you have used only a few ideas from cpux86.js (of FB's jslinux). Congrats! A good job. Any chances to see non-compiled mycpu.js somewhere? Hopefully on github.com/codinguncut –  Yauhen Yakimovich Dec 17 '12 at 0:08
@YauhenYakimovich No, I have not reused any of jslinux code. I have implemented so far all 286 instructions minus paging and segmentation (mmu). My plan was to release the code under GPL, but I would really like to commercialize the idea for running i.e. Freedos or Reactos so I am still unsure about licensing. The truth is, it will take me a LONG time to implement full memory mgmt. and then a long time to get it to run at speed. I will definitely share at github.com/codinguncut. Thanks for your feedback, Johannes –  entirelysubjective Feb 24 '13 at 0:02


Great Challenge and my first one. I created an account just because the challenge intrigued me so much. The down side is that I couldn't stop thinking of the challenge when I had real, paying, programming work to do.

I feel compelled to get a completed 8086 emulation running, but that's another challenge ;-)

The code is written in ANSI-C, so just compile/link the .c files together, pass in the codegolf binary, and go.

source zipped

enter image description here

share|improve this answer
Nice job RichTX! –  Dave C Jul 3 '12 at 15:25
Thanks Dave. You too. I didn't understand the expectation of making the code as small as possible when I started, but it was still a challenge. –  RichTX Jul 6 '12 at 15:38
+1 I peeked at your code to figure out how the carry flag works. –  luser droog Dec 3 '12 at 6:14
The link is down for me. –  Tyilo Oct 18 '13 at 0:13

C++ 1064 lines

Fantastic project. I did an Intellivision emulator many years ago, so it was great to flex my bit-banging muscles again.

After about a week's work, I could not have been more excited when this happened:


    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

    0 1 4 9 ♠a ♣b ♠c    d ♦f ☺h   ` §☺b ,♦d E   f `♠i ↓♣b 8♠e Y h ↑♦b =☺f   `

    2 3 4 5 6 7 8 9  a ☺a ☻a ♥a ♦a ♣a ♠a aa     a  b ☺b ☻b ♥b ♦b ♣b ♠b bb       b
 c ☺c ☻c ♥c ♦c ♣c ♠c cc         c  d ☺d ☻d ♥d ♦d ♣d ♠d dd       d  e ☺e ☻e ♥e ♦e ♣e ♠e
 ee     e  f ☺f ☻f ♥f ♦f ♣f ♠f ff       f  g ☺g ☻g ♥g ♦g ♣g ♠g g        g  h ☺h ☻h ♥
h ♦h ♣h ♠h hh   h  i ☺i ☻i ♥i ♦i ♣i ♠i ii       i   `

A little debugging later and...SHAZAM! enter image description here

Also, I rebuilt the original test program without the 80386 extensions, since I wanted to build my emulator true to the 8086 and not fudge in any extra instructions. Direct link to code here: Zip file.

Ok I'm having too much fun with this. I broke out memory and screen management, and now the screen updates when the screen buffer is written to. I made a video :)


Updates: First pass of segmenting is in. Very few instructions are actually implemented, but I tested it by moving the CS/DS and SS around, and everything still runs fine.

Also added rudimentary interrupt handling. Very rudimentary. But I did implement int 21h to print a string. Added a few lines to the test source and uploaded that as well.

    mov ah, 9
    mov dx, joetext
    int 21h

    db 'This was printed by int 21h$', 0

enter image description here

If anyone has some fairly simple assembly code that would test the segments out, I'd love to play with it.

I'm trying to figure out how far I want to take this. Full CPU emulation? VGA mode? Now I'm writing DOSBox.

12/6: Check it out, VGA mode!

enter image description here

share|improve this answer
Any chance that you can post your code on a free site that doesn't require registration? Thanks –  Dave C Nov 26 '12 at 13:55
D'oh I didn't realize it required registration. Sorry about that! I'll try to do it when I get home tonight. –  JoeFish Nov 26 '12 at 15:04
@DaveC, check the latest edit. –  JoeFish Nov 27 '12 at 3:52
I wonder if there's a camelForth port. That would test the segments. –  luser droog Dec 3 '12 at 5:37
That's awesome! +1 again. btw, there is an 8086 port of camel forth bradrodriguez.com/papers/index.html . –  luser droog Dec 7 '12 at 7:09

C++ - 4455 lines

And no, I didn't just do the question's requirements. I did the ENTIRE 8086, including 16 never-before KNOWN opcodes. reenigne helped with figuring those opcodes out.


share|improve this answer
where is the 4455-line file? oh, I found it. the #include "cpu.h" is hard to see. –  luser droog Jan 21 '13 at 8:59
(w)holy switch statement! –  luser droog Jan 21 '13 at 9:00
Yeah, it's about to get worse, too, since I'm about to include NEC V20 support as well. –  Darius Goad Jan 21 '13 at 15:06
I've looked through reenigne's blog. Can't find anything about these extra opcodes. Is it online somewhere? –  luser droog Jan 22 '13 at 19:26
He hasn't updated his blog in a while. He's on #ibm5150 on EFNET, though, so you could ask him there. –  Darius Goad Jan 24 '13 at 0:43

Javascript - 4,404 lines

I stumbled upon this post when researching information for my own emulator. This Codegolf post has been absolutely invaluable to me. The example program and associated assembly made it possible to easily debug and see what was happening.

Thank you!!!

And here is the first version of my Javascript 8086 emulator.

Completed run


  • All the required opcodes for this challenge plus some extras that were similar enough that they were easy to code
  • Partially functional text mode (80x25) video (no interrupts yet)
  • Functioning stack
  • Basic (non-segmented) memory
  • Pretty decent debugging (gotta have this)
  • Code Page 437 font set loads dynamically from a bitmap representation


I have a demo online, feel free to play with it an let me know if you find bugs :)


To run the codegolf program

1) click on the settings button

enter image description here

2) then just click load (you can play with debug options here, like stepping through program). The codegolf program is the only one available at the moment, I'm working on getting more online.

enter image description here


Full source here. https://bitbucket.org/crempp/js86emu

I tried to past the guts of the 8086 emulation here (as suggested by doorknob) but it exceeded the character limit ("Body is limited to 30000 characters; you entered 158,272").

Here is a quick link to the code I was going to paste in here - https://bitbucket.org/crempp/js86emu/src/39dbcb7106a0aaf59e003cd7f722acb4b6923d87/src/js/emu/cpus/8086.js?at=v0.1

share|improve this answer
Wow, wonderful! However, it would be ideal if the code was in your post itself, as we prefer our posts to be self-contained. –  Doorknob Apr 25 '14 at 12:17
@Doorknob, I 'm not sure I understand. You would like me to post 4,400 lines of code inline, in the post? –  crempp Apr 25 '14 at 12:30
Umm... I didn't realize it was that long. Does it fit within the maximum character limit? If so, then yes, it would be great if your post was self-contained. Thanks! :-) –  Doorknob Apr 25 '14 at 13:01


I had wanted to do this challenge for so long, and I finally took the time to do so. It has been a wonderful experience so far and I'm proud to annonce that I've finally completed it.

Test Program Output


Source code is available on GitHub at NeatMonster/Intel8086. I've tried to document pretty much everything, with the help of the holly 8086 Family User's Manual.

I intend to implement all the missing opcodes and features, so you might want to check out the release 1.0 for a version with only the ones required for this challenge.

Many thanks to @copy!

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.