161
votes
\$\begingroup\$

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.

Example:

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!                                                                   
    0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 
    
    
    ################################################################################
    ##                                                                            ##
    ##  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.

\$\endgroup\$
20
  • 5
    \$\begingroup\$ 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... \$\endgroup\$ Commented Jan 23, 2012 at 13:17
  • 3
    \$\begingroup\$ @ChrisBrowne good luck being masochistic! I am currently turning my 8086 into a 80386 and have learned a lot from this project so far. \$\endgroup\$
    – copy
    Commented Jan 23, 2012 at 17:24
  • 2
    \$\begingroup\$ +1 +favorite ...i can't begin to express the feeling i got when i saw this question. \$\endgroup\$
    – ixtmixilix
    Commented Feb 26, 2012 at 21:33
  • 2
    \$\begingroup\$ @copy It is never too late to make a golf competition for every single language/host pair \$\endgroup\$ Commented Dec 17, 2012 at 0:21
  • 2
    \$\begingroup\$ @MartinBüttner Sure, the question is older than that tag and has basically been a popularity contest anyway \$\endgroup\$
    – copy
    Commented Mar 21, 2015 at 0:32

15 Answers 15

85
votes
\$\begingroup\$

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 ...
R

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.

emu8086.py

console.py

disasm.py

On github

\$\endgroup\$
4
  • 9
    \$\begingroup\$ That's one hell of a first Code Golf post. +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 ... \$\endgroup\$ Commented Feb 26, 2012 at 22:59
  • \$\begingroup\$ @DC thanks :) was a fun challenge! \$\endgroup\$
    – j-a
    Commented Feb 26, 2012 at 23:20
  • 1
    \$\begingroup\$ Still can't believe someone actually did this :-) Great job! \$\endgroup\$
    – copy
    Commented Feb 26, 2012 at 23:46
  • 1
    \$\begingroup\$ Amazing! Congratulations! How many lines it had in the end? \$\endgroup\$
    – Will Lp
    Commented Nov 30, 2012 at 18:56
60
votes
\$\begingroup\$

Haskell, 256 234 196 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:

------------------------------------------------------------
-- Imports

-- They're the only lines I allow to go over 80 characters.
-- For the simple reason the code would work just as well without the
-- actual symbol list, but I like to keep it up to date to better
-- grasp my dependency graph.

import           Control.Monad.Reader      (ReaderT,runReaderT,ask,lift,forever,forM,when,void)
import           Control.Monad.ST          (ST,runST)
import           Control.Monad.Trans.Maybe (MaybeT,runMaybeT)
import           Data.Array.ST             (STUArray,readArray,writeArray,newArray,newListArray)
import           Data.Bits                 (FiniteBits,(.&.),(.|.),xor,shiftL,shiftR,testBit,finiteBitSize)
import           Data.Bool                 (bool)
import qualified Data.ByteString as B      (unpack,getContents)
import           Data.Char                 (chr,isPrint) -- for screen dump
import           Data.Int                  (Int8)
import           Data.STRef                (STRef,newSTRef,readSTRef,writeSTRef,modifySTRef)
import           Data.Word                 (Word8,Word16)

------------------------------------------------------------
-- Bytes and Words
-- Bytes are 8 bits.  Words are 16 bits.  Addressing is little-endian.

-- Phantom types.  Essentially (only?) used for the ALU
byte = undefined :: Word8
word = undefined :: Word16

-- Byte to word conversion
byteToWordSE = (fromIntegral :: Int8 -> Word16) .
               (fromIntegral :: Word8 -> Int8)

-- Two-bytes to word conversion
concatBytes :: Word8 -> Word8 -> Word16
concatBytes l h = fromIntegral l .|. (fromIntegral h `shiftL` 8)

-- Word to two bytes conversion
wordToByteL,wordToByteH :: Word16 -> Word8
wordToByteL = fromIntegral
wordToByteH = fromIntegral . (`shiftR` 8)

-- A Place is an lvalue byte or word.  In absence of I/O ports, this
-- means RAM or register file.  This type synonym is not strictly
-- needed, but without it it's unclear I could keep the alu function
-- type signature under twice 80 characters, so why not keep this.
type Place s = (STUArray s Word16 Word8,Word16)

-- Read and write, byte or word, from RAM or register file

class (Ord a,FiniteBits a,Num a) => Width a where
  readW  :: Place s ->      MonadCPU s a
  writeW :: Place s -> a -> MonadCPU s ()

instance Width Word8 where
  readW  =  liftST    . uncurry readArray
  writeW = (liftST .) . uncurry writeArray

instance Width Word16 where
  readW (p,a) = concatBytes <$> readW (p,a) <*> readW (p,a+1)
  writeW (p,a) val = do
    writeW (p,a)   $ wordToByteL val
    writeW (p,a+1) $ wordToByteH val

------------------------------------------------------------
-- CPU object

-- The actual CPU state.  Yeah, I obviously don't have all flags in! :-D
data CPU s = CPU { ram  :: STUArray s Word16 Word8
                 , regs :: STUArray s Word16 Word8
                 , cf :: STRef s Bool
                 , zf :: STRef s Bool
                 , sf :: STRef s Bool }

newCPU rawRam = do ramRef <- newListArray (0,0xFFFF) rawRam
                   regFile <- newArray (0,17) 0
                   cf <- newSTRef False
                   zf <- newSTRef False
                   sf <- newSTRef False
                   return $ CPU ramRef regFile cf zf sf

-- Register addresses within the register file.  Note odd placement
-- for BX and related.  Also note the 16-bit registers have a wider
-- pitch.  IP was shoehorned in recently, it doesn't really need an
-- address here, but it made other code shorter, so that's that.

-- In the 8-bit subfile, only regAl is used in the code (and it's 0,
-- so consider that a line I could totally have skipped)
[regAl,regAh,regCl,regCh,regDl,regDh,regBl,regBh] = [0..7]

-- In the 16-bit file, they're almost if not all referenced.  8086
-- sure is clunky.
[regAx,regCx,regDx,regBx,regSp,regBp,regSi,regDi,regIp] = [0,2..16]

-- These functions look like I got part of the Lens intuition
-- independently, come to look at it after the fact.  Cool :-)
readCpu  ext   = liftST .      readSTRef    . ext =<< ask
writeCpu ext f = liftST . flip writeSTRef f . ext =<< ask

-- It looks like the only operations IP can receive are relative moves
-- (incrIP function below) and a single absolute set: RET.  I deduce
-- only short jumps, not even near, were in the spec.
incrIP i = do old <- readReg regIp
              writeReg regIp (old + i)
              return old

-- Read next instruction.  Directly from RAM, so no pipeline prefetch.
readInstr8 = incrIP 1 >>= readRam
readInstr16 = concatBytes <$> readInstr8 <*> readInstr8

-- RAM/register file R/W specializers
readReg  reg      = ask >>= \p -> readW  (regs p,reg)
readRam  addr     = ask >>= \p -> readW  (ram p ,addr)
writeReg reg val  = ask >>= \p -> writeW (regs p,reg)  val
writeRam addr val = ask >>= \p -> writeW (ram p ,addr) val

-- I'm not quite sure what those do anymore, or why they're separate.
decodeReg8  n = fromIntegral $ (n `shiftL` 1) .|. (n `shiftR` 2)
decodeReg16 n = fromIntegral $  n `shiftL` 1
readDecodedReg8 = readReg . decodeReg8
readDecodedReg16 = readReg . decodeReg16

-- The monad type synonym make type signatures easier :-(
type MonadCPU s = MaybeT (ReaderT (CPU s) (ST s))

-- Specialized liftST, because the one from Hackage loses the
-- parameter, and I need it to be able to qualify Place.
liftST :: ST s a -> MonadCPU s a
liftST = lift . lift

------------------------------------------------------------
-- Instructions

-- This is arguably the core secret of the 8086 architecture.
-- See statement links for actual explanations.
readModRM = do
  modRM <- readInstr8
  let mod   =  modRM           `shiftR` 6
      opReg = (modRM .&. 0x38) `shiftR` 3
      rm    =  modRM .&. 0x07
  cpu <- ask
  operand <- case mod of
               0 -> do
                 addr <- case rm of
                           1 -> (+) <$> readReg regBx <*> readReg regDi
                           2 -> (+) <$> readReg regBp <*> readReg regSi
                           6 -> readInstr16
                           7 -> readReg regBx
                 return (ram cpu,addr)
               2 -> do
                 addr <- case rm of
                           5 -> (+) <$> readReg regDi <*> readInstr16
                           7 -> (+) <$> readReg regBx <*> readInstr16
                 return (ram cpu,addr)
               3 -> return (regs cpu,2*fromIntegral rm)
  return (operand,opReg,opReg)

-- Stack operations.  PUSH by value (does NOT reproduce PUSH SP behavior)
push16 val = do
  sp <- 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 :: Word16)

-- So, yeah, JMP seems to be relative (short) only.  Well, if that's enough…
jump cond = when cond . void . incrIP . byteToWordSE =<< readInstr8

-- The ALU.  The most complicated type signature in this file.  An
-- initial argument as a phantom type I tried to get rid of and
-- failed.
alu :: Width w => w -> MonadCPU s w -> MonadCPU s w -> Place s
    -> (w -> w -> MonadCPU s (Bool,Maybe Bool,w)) -> MonadCPU s ()
alu _ a b r op = do
  (rw,c,v) <- a >>= (b >>=) . op
  when rw $ writeW r v
  maybe (return ()) (writeCpu cf) c
  writeCpu zf (v == 0)
  writeCpu sf (testBit v (finiteBitSize 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 -> bool 0 1 <$> readCpu cf >>= \c ->
                      return (True, Just (a >= negate (b + c)), a + b + c)
decodeALU 3 = \a b -> bool 0 1 <$> readCpu 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 :: Width w => w -> w -> MonadCPU s (Bool,Maybe Bool,w)
opIncDec    = \a b -> return (True, Nothing,                    a   +   b)

-- Main iteration: process one instuction
-- That's the rest of the meat, but that part's expected.
processInstr = do
  opcode <- readInstr8
  regs <- 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 (readW operand) (readDecodedReg8 reg) operand (decodeALU aluOp)
    1 -> do
      (operand,reg,_) <- readModRM
      alu word (readW 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 (readW zReg) (return   1 ) zReg opIncDec -- 16b INC
    0x48 -> alu word (readW zReg) (return (-1)) zReg opIncDec -- 16b DEC
    0x50 -> readW zReg >>= push16                       -- 16b PUSH reg
    0x58 -> pop16 >>= writeW zReg                       -- 16b POP reg
    0x90 -> do v1 <- readW zReg                         -- 16b XCHG (or NOP)
               v2 <- readReg regAx
               writeW zReg (v2 :: Word16)
               writeReg regAx (v1 :: Word16)
    0xB0 -> readInstr8  >>= writeW zReg -- (BUG!)       -- 8b MOV reg,imm
    0xB8 -> readInstr16 >>= writeW zReg                 -- 16b MOV reg,imm
    _ -> case bool opcode 0x82 (opcode == 0x80) of
      0x72 -> jump       =<< readCpu cf                 -- JB/JNAE/JC
      0x74 -> jump       =<< readCpu zf                 -- JE/JZ
      0x75 -> jump . not =<< readCpu zf                 -- JNE/JNZ
      0x76 -> jump       =<< (||) <$> readCpu cf <*> readCpu zf -- JBE
      0x77 -> jump . not =<< (||) <$> readCpu cf <*> readCpu zf -- JA
      0x79 -> jump . not =<< readCpu sf                 -- JNS
      0x81 -> do                                        -- 16b arith to imm
        (operand,_,op) <- readModRM
        alu word (readW operand) readInstr16 operand (decodeALU op)
      0x82 -> do                                        -- 8b arith to imm
        (operand,_,op) <- readModRM
        alu byte (readW operand) readInstr8 operand (decodeALU op)
      0x83 -> do                                        -- 16b arith to 8s imm
        (operand,_,op) <- readModRM
        alu word (readW operand) (byteToWordSE <$> readInstr8) operand
            (decodeALU op)
      0x86 -> do                                        -- 8b XCHG reg,RM
        (operand,reg,_) <- readModRM
        v1 <- readDecodedReg8 reg
        v2 <- readW operand
        writeReg (decodeReg8 reg) (v2 :: Word8)
        writeW operand v1
      0x88 -> do                                        -- 8b MOV RM,reg
        (operand,reg,_) <- readModRM
        readDecodedReg8 reg >>= writeW operand
      0x89 -> do                                        -- 16b MOV RM,reg
        (operand,reg,_) <- readModRM
        readDecodedReg16 reg >>= writeW operand
      0x8A -> do                                        -- 8b MOV reg,RM
        (operand,reg,_) <- readModRM
        val <- readW operand
        writeReg (decodeReg8 reg) (val :: Word8)
      0x8B -> do                                        -- 16b MOV reg,RM
        (operand,reg,_) <- readModRM
        val <- readW operand
        writeReg (decodeReg16 reg) (val :: Word16)
      0xC3 -> pop16 >>= writeReg regIp                  -- RET
      0xC7 -> do (operand,_,_) <- readModRM             -- 16b MOV RM,imm
                 readInstr16 >>= writeW operand
      0xE8 -> readInstr16 >>= incrIP >>= push16         -- CALL relative
      0xEB -> jump True                                 -- JMP short
      0xF4 -> fail "Halting and Catching Fire"          -- HLT
      0xF9 -> writeCpu cf True                          -- STC
      0xFE -> do                                        -- 8-bit INC/DEC RM
        (operand,_,op) <- readModRM
        alu byte (readW operand) (return $ 1-2*op) operand
            (\a b -> return (True,Nothing,a+b)) -- kinda duplicate :(

------------------------------------------------------------

main = do
  rawRam <- (++ repeat 0) . B.unpack <$> B.getContents
  putStr $ unlines $ runST $ do
    cpu <- newCPU rawRam
    flip runReaderT cpu $ runMaybeT $ do
      writeReg regSp (0x100 :: Word16)
      forever processInstr

    -- Next three lines is the screen dump extraction.
    forM [0..25] $ \i -> forM [0..79] $ \j -> do
      c <- chr . fromIntegral <$> readArray (ram cpu) (0x8000 + 80*i + j)
      return $ bool ' ' c (isPrint c)

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.

Update 2: it's been five years, I figured an update to get it to compile flawlessly on the latest GHC could be in order. Along the way:

  • got rid of liftM, liftM2 and such. I love having <$> and <*> in the Prelude.
  • Data.Bool and Data.ByteString, saves a bit and cleans up.
  • IP register used to be special (unaddressable), now it's in the register file. It doesn't make so much 8086 sense, but hey I'm a golfer.
  • It's all pure ST-based code now. From a golfing point of view, this sucks, because it made a lot of type signatures necessary. On the other hand, I had a row with my conscience and I lost, so now you get the clean, long code.
  • So now this is git-tracked.
  • Added more serious comments. As a consequence, the way I count lines has changed: I'm dropping empty and pure-comment lines. I hereby guarantee all lines but the imports are less than 80 characters long. I'm not dropping type signatures since the one I've left are actually needed to get it to compile properly (thank you very much ST cleanliness).

As the code comments say, 5 lines (the Data.Char import, the 8-bit register mappings and the screen dump) are out of spec, so you're very welcome to discount them if you feel so inclined :-)

\$\endgroup\$
4
  • 3
    \$\begingroup\$ 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. \$\endgroup\$
    – copy
    Commented Feb 27, 2012 at 18:17
  • 3
    \$\begingroup\$ Nice work! Very short. I should learn haskell. \$\endgroup\$
    – j-a
    Commented Feb 29, 2012 at 17:48
  • \$\begingroup\$ What's .|.? /10char \$\endgroup\$ Commented Mar 21, 2015 at 2:07
  • \$\begingroup\$ @octatoan the operation known in x86 opcodes as OR. \$\endgroup\$
    – J B
    Commented Mar 22, 2015 at 21:13
46
votes
\$\begingroup\$

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.

http://sourceforge.net/p/fake86/code/ci/master/tree/src/fake86/cpu.c

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!

\$\endgroup\$
7
  • 3
    \$\begingroup\$ Good job! Do the games actually run at full speed? \$\endgroup\$
    – copy
    Commented Sep 9, 2013 at 2:27
  • 1
    \$\begingroup\$ 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. \$\endgroup\$
    – Mike C
    Commented Sep 9, 2013 at 3:09
  • 1
    \$\begingroup\$ 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? \$\endgroup\$
    – user3799
    Commented Sep 23, 2013 at 12:03
  • 2
    \$\begingroup\$ 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! :) \$\endgroup\$
    – Mike C
    Commented Sep 27, 2013 at 17:42
  • 1
    \$\begingroup\$ @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! \$\endgroup\$
    – gideon
    Commented Jan 26, 2014 at 6:22
40
votes
\$\begingroup\$

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}
/REGTAB[[AL CL DL BL AH CH DH BH][AX CX DX BX SP BP SI DI]]
/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}
/IMMTAB{ADD OR ADC SBB AND SUB XOR CMP }cvlit
/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}

/HLT{exit}
/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 }
/GRP1TAB{TEST --- NOT NEG MUL IMUL DIV IDIV } cvlit
/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 }

/OPTAB{
{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 PUSH}{CX PUSH}{DX PUSH}{BX PUSH}{SP PUSH}{BP PUSH}{SI PUSH}{DI PUSH}              %50-57
 {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
{NOP}{CX AXCH}{DX AXCH}{BX AXCHG}{SP AXCH}{BP AXCH}{SI AXCH}{DI AXCH}             %90-97
 {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
}cvlit

/break{ /hook /pause load def }
/c{ /hook {} def }
/doprompt{
    (\nbreak>)print
    flush(%lineedit)(r)file
    cvx {exec}stopped pop }
/pause{ doprompt }
/hook{}

/stdout(%stdout)(w)file
/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
    (\n)print
}
/mainloop{{
    %regdump
    OPTAB fetchb get
    dup ==
    exec
    %pstack flush
    %hook
    stor
    /ic ic 1 add def ictime
}loop}

/printvideo{
    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
    (\n)print
}
/ic 0
/ictime{ic 10 mod 0 eq {onq} if}
/timeq 10
/onq{ %printvideo
}
>>begin
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
pop[

mainloop
printvideo

%eof

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!
0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~


################################################################################
##                                                                            ##
##  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    ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
################################################################################





GS<1>
\$\endgroup\$
4
  • 5
    \$\begingroup\$ I wonder... Is the hot-key to close an application alt-F4 because F4h is the 8086 HLT opcode? \$\endgroup\$ Commented Nov 30, 2012 at 21:49
  • 6
    \$\begingroup\$ I just want to tell you that you are absolutely awesome for implementing this in Postscript. \$\endgroup\$
    – cemper93
    Commented Dec 9, 2012 at 13:32
  • 1
    \$\begingroup\$ That code is short. It deserves more upvotes. Have mine, for a start. \$\endgroup\$
    – J B
    Commented Feb 23, 2013 at 10:34
  • 3
    \$\begingroup\$ wait... postscript is a programming language?! ;) \$\endgroup\$
    – n611x007
    Commented Mar 30, 2013 at 19:35
31
votes
\$\begingroup\$

Javascript

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.

https://i.sstatic.net/54a6S.png

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?

Cheers,

Johannes

\$\endgroup\$
5
  • \$\begingroup\$ 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 :-) \$\endgroup\$
    – copy
    Commented Nov 13, 2012 at 20:56
  • \$\begingroup\$ @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 \$\endgroup\$ Commented Dec 17, 2012 at 0:08
  • \$\begingroup\$ @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 \$\endgroup\$
    – fluquid
    Commented Feb 24, 2013 at 0:02
  • 1
    \$\begingroup\$ The link is broken for me. (IE on windows 8) \$\endgroup\$ Commented Oct 29, 2015 at 15:24
  • \$\begingroup\$ This comes very late. Character zero is another space in video RAM. \$\endgroup\$
    – Joshua
    Commented Feb 20, 2017 at 16:34
30
votes
\$\begingroup\$

C++

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.

\$\endgroup\$
6
  • 4
    \$\begingroup\$ 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."; \$\endgroup\$
    – copy
    Commented Apr 16, 2012 at 17:44
  • \$\begingroup\$ 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! \$\endgroup\$
    – user3799
    Commented Apr 16, 2012 at 21:00
  • \$\begingroup\$ You might have made a mistake there: Near jumps (0xE8) don't push the cs register \$\endgroup\$
    – copy
    Commented Apr 17, 2012 at 16:57
  • 1
    \$\begingroup\$ That would be the problem, good catch! You seem very experienced with the 8086, did you program for it? \$\endgroup\$
    – user3799
    Commented Apr 18, 2012 at 22:19
  • 1
    \$\begingroup\$ 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). \$\endgroup\$
    – copy
    Commented Apr 19, 2012 at 14:01
28
votes
\$\begingroup\$

C

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

\$\endgroup\$
5
  • \$\begingroup\$ Nice job RichTX! \$\endgroup\$
    – user3799
    Commented Jul 3, 2012 at 15:25
  • \$\begingroup\$ 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. \$\endgroup\$
    – RichTX
    Commented Jul 6, 2012 at 15:38
  • \$\begingroup\$ +1 I peeked at your code to figure out how the carry flag works. \$\endgroup\$ Commented Dec 3, 2012 at 6:14
  • \$\begingroup\$ The link is down for me. \$\endgroup\$
    – Tyilo
    Commented Oct 18, 2013 at 0:13
  • \$\begingroup\$ Download link fixed \$\endgroup\$
    – RichTX
    Commented Feb 1, 2021 at 0:36
27
votes
\$\begingroup\$

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.

https://github.com/Alegend45/IBM5150

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

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:

.........
╤╤╤╤╤╤╤╤╤╤╤╤╤╤
0123456789:;?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~


################################################################################
########################################################################
    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 :)

http://www.youtube.com/watch?v=qnAssaTpmnA

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.

start:
    sti
    mov ah, 9
    mov dx, joetext
    int 21h
...

joetext:
    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

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

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

Features:

  • 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

Demo

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

http://js86emu.chadrempp.com/

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

Source

Full source here. https://github.com/crempp/js86emu

I tried to paste 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://github.com/crempp/js86emu/blob/39dbcb7106a0aaf59e003cd7f722acb4b6923d87/src/js/emu/cpus/8086.js

*Edit - updated for new demo and repo location

\$\endgroup\$
3
  • \$\begingroup\$ Wow, wonderful! However, it would be ideal if the code was in your post itself, as we prefer our posts to be self-contained. \$\endgroup\$
    – Doorknob
    Commented Apr 25, 2014 at 12:17
  • \$\begingroup\$ @Doorknob, I 'm not sure I understand. You would like me to post 4,400 lines of code inline, in the post? \$\endgroup\$
    – crempp
    Commented Apr 25, 2014 at 12:30
  • \$\begingroup\$ 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! :-) \$\endgroup\$
    – Doorknob
    Commented Apr 25, 2014 at 13:01
13
votes
\$\begingroup\$

Java

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

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!

\$\endgroup\$
13
votes
\$\begingroup\$

Common Lisp - 580 loc (442 w/o blank lines & comments)

I used this challenge as an excuse to learn Common Lisp. Here's the result:

;;; Program settings

(defparameter *disasm* nil "Whether to disassemble")

(defmacro disasm-instr (on-disasm &body body)
  `(if *disasm*
       ,on-disasm
       (progn ,@body)))

;;; State variables

(defparameter *ram* (make-array (* 64 1024) :initial-element 0 :element-type '(unsigned-byte 8)) "Primary segment")
(defparameter *stack* (make-array (* 64 1024) :initial-element 0 :element-type '(unsigned-byte 8)) "Stack segment")
(defparameter *flags* '(:cf 0 :sf 0 :zf 0) "Flags")
(defparameter *registers* '(:ax 0 :bx 0 :cx 0 :dx 0 :bp 0 :sp #x100 :si 0 :di 0) "Registers")
(defparameter *ip* 0 "Instruction pointer")
(defparameter *has-carried* nil "Whether the last wraparound changed the value")
(defparameter *advance* 0 "Bytes to advance IP by after an operation")

;;; Constants

(defconstant +byte-register-to-word+ '(:al (:ax nil) :ah (:ax t) :bl (:bx nil) :bh (:bx t) :cl (:cx nil) :ch (:cx t) :dl (:dx nil) :dh (:dx t)) "Mapping from byte registers to word registers")
(defconstant +bits-to-register+ '(:ax :cx :dx :bx :sp :bp :si :di) "Mapping from index to word register")
(defconstant +bits-to-byte-register+ '(:al :cl :dl :bl :ah :ch :dh :bh) "Mapping from index to byte register")

;;; Constant mappings

(defun bits->word-reg (bits)
  (elt +bits-to-register+ bits))

(defun bits->byte-reg (bits)
  (elt +bits-to-byte-register+ bits))

(defun address-for-r/m (mod-bits r/m-bits)
  (disasm-instr
      (if (and (= mod-bits #b00) (= r/m-bits #b110))
      (list :disp (peek-at-word))
      (case r/m-bits
        (#b000 (list :base :bx :index :si))
        (#b001 (list :base :bx :index :di))
        (#b010 (list :base :bp :index :si))
        (#b011 (list :base :bp :index :di))
        (#b100 (list :index :si))
        (#b101 (list :index :di))
        (#b110 (list :base :bp))
        (#b111 (list :base :bx))))
    (if (and (= mod-bits #b00) (= r/m-bits #b110))
    (peek-at-word)
    (case r/m-bits
      (#b000 (+ (register :bx) (register :si)))
      (#b001 (+ (register :bx) (register :di)))
      (#b010 (+ (register :bp) (register :si)))
      (#b011 (+ (register :bp) (register :di)))
      (#b100 (register :si))
      (#b101 (register :di))
      (#b110 (register :bp))
      (#b111 (register :bx))))))

;;; Convenience functions

(defun reverse-little-endian (low high)
  "Reverse a little-endian number."
  (+ low (ash high 8)))

(defun negative-p (value is-word)
  (or (if is-word (>= value #x8000) (>= value #x80)) (< value 0)))

(defun twos-complement (value is-word)
  (if (negative-p value is-word)
      (- (1+ (logxor value (if is-word #xffff #xff))))
      value))

(defun wrap-carry (value is-word)
  "Wrap around an carried value."
  (let ((carry (if is-word (>= value #x10000) (>= value #x100))))
    (setf *has-carried* carry)
    (if carry
    (if is-word (mod value #x10000) (mod value #x100))
    value)))

;;; setf-able locations

(defun register (reg)
  (disasm-instr reg
    (getf *registers* reg)))

(defun set-reg (reg value)
  (setf (getf *registers* reg) (wrap-carry value t)))

(defsetf register set-reg)

(defun byte-register (reg)
  (disasm-instr reg
    (let* ((register-to-word (getf +byte-register-to-word+ reg)) (word (first register-to-word)))
      (if (second register-to-word)
      (ash (register word) -8)
      (logand (register word) #x00ff)))))

(defun set-byte-reg (reg value)
  (let* ((register-to-word (getf +byte-register-to-word+ reg)) (word (first register-to-word)) (wrapped-value (wrap-carry value nil)))
    (if (second register-to-word)
    (setf (register word) (+ (ash wrapped-value 8) (logand (register word) #x00ff)))
    (setf (register word) (+ wrapped-value (logand (register word) #xff00))))))

(defsetf byte-register set-byte-reg)

(defun flag (name)
  (getf *flags* name))

(defun set-flag (name value)
  (setf (getf *flags* name) value))

(defsetf flag set-flag)

(defun flag-p (name)
  (= (flag name) 1))

(defun set-flag-p (name is-set)
  (setf (flag name) (if is-set 1 0)))

(defsetf flag-p set-flag-p)

(defun byte-in-ram (location segment)
  "Read a byte from a RAM segment."
  (elt segment location))

(defsetf byte-in-ram (location segment) (value)
  "Write a byte to a RAM segment."
  `(setf (elt ,segment ,location) ,value))

(defun word-in-ram (location segment)
  "Read a word from a RAM segment."
  (reverse-little-endian (elt segment location) (elt segment (1+ location))))

(defsetf word-in-ram (location segment) (value)
  "Write a word to a RAM segment."
  `(progn
     (setf (elt ,segment ,location) (logand ,value #x00ff))
     (setf (elt ,segment (1+ ,location)) (ash (logand ,value #xff00) -8))))

(defun indirect-address (mod-bits r/m-bits is-word)
  "Read from an indirect address."
  (disasm-instr
      (if (= mod-bits #b11) (register (if is-word (bits->word-reg r/m-bits) (bits->byte-reg r/m-bits)))
      (let ((base-index (address-for-r/m mod-bits r/m-bits)))
        (unless (getf base-index :disp)
          (setf (getf base-index :disp)
            (case mod-bits
              (#b00 0)
              (#b01 (next-instruction))
              (#b10 (next-word)))))
        base-index))
    (let ((address-base (address-for-r/m mod-bits r/m-bits)))
      (case mod-bits
    (#b00 (if is-word (word-in-ram address-base *ram*) (byte-in-ram address-base *ram*)))
    (#b01 (if is-word (word-in-ram (+ address-base (peek-at-instruction)) *ram*) (byte-in-ram (+ address-base (peek-at-instruction)) *ram*)))
    (#b10 (if is-word (word-in-ram (+ address-base (peek-at-word)) *ram*) (byte-in-ram (+ address-base (peek-at-word)) *ram*)))
    (#b11 (if is-word (register (bits->word-reg r/m-bits)) (byte-register (bits->byte-reg r/m-bits))))))))

(defsetf indirect-address (mod-bits r/m-bits is-word) (value)
  "Write to an indirect address."
  `(let ((address-base (address-for-r/m ,mod-bits ,r/m-bits)))
    (case ,mod-bits
      (#b00 (if ,is-word (setf (word-in-ram address-base *ram*) ,value) (setf (byte-in-ram address-base *ram*) ,value)))
      (#b01 (if ,is-word (setf (word-in-ram (+ address-base (peek-at-instruction)) *ram*) ,value) (setf (byte-in-ram (+ address-base (peek-at-instruction)) *ram*) ,value)))
      (#b10 (if ,is-word (setf (word-in-ram (+ address-base (peek-at-word)) *ram*) ,value) (setf (byte-in-ram (+ address-base (peek-at-word)) *ram*) ,value)))
      (#b11 (if ,is-word (setf (register (bits->word-reg ,r/m-bits)) ,value) (setf (byte-register (bits->byte-reg ,r/m-bits)) ,value))))))

;;; Instruction loader

(defun load-instructions-into-ram (instrs)
  (setf *ip* 0)
  (setf (subseq *ram* 0 #x7fff) instrs)
  (length instrs))

(defun next-instruction ()
  (incf *ip*)
  (elt *ram* (1- *ip*)))

(defun next-word ()
  (reverse-little-endian (next-instruction) (next-instruction)))

(defun peek-at-instruction (&optional (forward 0))
  (incf *advance*)
  (elt *ram* (+ *ip* forward)))

(defun peek-at-word ()
  (reverse-little-endian (peek-at-instruction) (peek-at-instruction 1)))

(defun advance-ip ()
  (incf *ip* *advance*)
  (setf *advance* 0))

(defun advance-ip-ahead-of-indirect-address (mod-bits r/m-bits)
  (cond
    ((or (and (= mod-bits #b00) (= r/m-bits #b110)) (= mod-bits #b10)) 2)
    ((= mod-bits #b01) 1)
    (t 0)))

(defun next-instruction-ahead-of-indirect-address (mod-bits r/m-bits)
  (let ((*ip* *ip*))
    (incf *ip* (advance-ip-ahead-of-indirect-address mod-bits r/m-bits))
    (incf *advance*)
    (next-instruction)))

(defun next-word-ahead-of-indirect-address (mod-bits r/m-bits)
  (let ((*ip* *ip*))
    (incf *ip* (advance-ip-ahead-of-indirect-address mod-bits r/m-bits))
    (incf *advance* 2)
    (next-word)))

;;; Memory access

(defun read-word-from-ram (loc &optional (segment *ram*))
  (word-in-ram loc segment))

(defun write-word-to-ram (loc word &optional (segment *ram*))
  (setf (word-in-ram loc segment) word))

(defun push-to-stack (value)
  (decf (register :sp) 2)
  (write-word-to-ram (register :sp) value *stack*))

(defun pop-from-stack ()
  (incf (register :sp) 2)
  (read-word-from-ram (- (register :sp) 2) *stack*))

;;; Flag effects

(defun set-cf-on-add (value)
  (setf (flag-p :cf) *has-carried*)
  value)

(defun set-cf-on-sub (value1 value2)
  (setf (flag-p :cf) (> value2 value1))
  (- value1 value2))

(defun set-sf-on-op (value is-word)
  (setf (flag-p :sf) (negative-p value is-word))
  value)

(defun set-zf-on-op (value)
  (setf (flag-p :zf) (= value 0))
  value)

;;; Operations

;; Context wrappers

(defun with-one-byte-opcode-register (opcode fn)
  (let ((reg (bits->word-reg (mod opcode #x08))))
    (funcall fn reg)))

(defmacro with-mod-r/m-byte (&body body)
  `(let* ((mod-r/m (next-instruction)) (r/m-bits (logand mod-r/m #b00000111)) (mod-bits (ash (logand mod-r/m #b11000000) -6)) (reg-bits (ash (logand mod-r/m #b00111000) -3)))
     ,@body))

(defmacro with-in-place-mod (dest mod-bits r/m-bits &body body)
  `(progn
     ,@body
     (when (equal (car ',dest) 'indirect-address)
       (decf *advance* (advance-ip-ahead-of-indirect-address ,mod-bits ,r/m-bits)))))

;; Templates

(defmacro mov (src dest)
  `(disasm-instr (list "mov" :src ,src :dest ,dest)
     (setf ,dest ,src)))

(defmacro xchg (op1 op2)
  `(disasm-instr (list "xchg" :op1 ,op1 :op2 ,op2)
     (rotatef ,op1 ,op2)))

(defmacro inc (op1 is-word)
  `(disasm-instr (list "inc" :op1 ,op1)
     (set-sf-on-op (set-zf-on-op (incf ,op1)) ,is-word)))

(defmacro dec (op1 is-word)
  `(disasm-instr (list "dec" :op1 ,op1)
     (set-sf-on-op (set-zf-on-op (decf ,op1)) ,is-word)))

;; Group handling

(defmacro parse-group-byte-pair (opcode operation mod-bits r/m-bits)
  `(,operation ,mod-bits ,r/m-bits (oddp ,opcode)))

(defmacro parse-group-opcode (&body body)
  `(with-mod-r/m-byte
     (case reg-bits
       ,@body)))

;; One-byte opcodes on registers

(defun clear-carry-flag ()
  (disasm-instr '("clc")
    (setf (flag-p :cf) nil)))

(defun set-carry-flag ()
  (disasm-instr '("stc")
    (setf (flag-p :cf) t)))

(defun push-register (reg)
  (disasm-instr (list "push" :src reg)
    (push-to-stack (register reg))))

(defun pop-to-register (reg)
  (disasm-instr (list "pop" :dest reg)
    (setf (register reg) (pop-from-stack))))

(defun inc-register (reg)
  (inc (register reg) t))

(defun dec-register (reg)
  (dec (register reg) t))

(defun xchg-register (reg)
  (disasm-instr (if (eql reg :ax) '("nop") (list "xchg" :op1 :ax :op2 reg))
    (xchg (register :ax) (register reg))))

(defun mov-byte-to-register (opcode)
  (let ((reg (bits->byte-reg (mod opcode #x08))))
    (mov (next-instruction) (byte-register reg))))

(defun mov-word-to-register (reg)
  (mov (next-word) (register reg)))

;; Flow control

(defun jmp-short ()
  (disasm-instr (list "jmp" :op1 (twos-complement (next-instruction) nil))
    (incf *ip* (twos-complement (next-instruction) nil))))

(defmacro jmp-short-conditionally (opcode condition mnemonic)
  `(let ((disp (next-instruction)))
     (if (evenp ,opcode)
       (disasm-instr (list (concatenate 'string "j" ,mnemonic) :op1 (twos-complement disp nil))
     (when ,condition
       (incf *ip* (twos-complement disp nil))))
       (disasm-instr (list (concatenate 'string "jn" ,mnemonic) :op1 (twos-complement disp nil))
     (unless ,condition
       (incf *ip* (twos-complement disp nil)))))))

(defun call-near ()
  (disasm-instr (list "call" :op1 (twos-complement (next-word) t))
    (push-to-stack (+ *ip* 2))
    (incf *ip* (twos-complement (next-word) t))))

(defun ret-from-call ()
  (disasm-instr '("ret")
    (setf *ip* (pop-from-stack))))

;; ALU

(defmacro parse-alu-opcode (opcode operation)
  `(let ((mod-8 (mod ,opcode 8)))
     (case mod-8
       (0
    (with-mod-r/m-byte
      (,operation (byte-register (bits->byte-reg reg-bits)) (indirect-address mod-bits r/m-bits nil) nil mod-bits r/m-bits)))
       (1
    (with-mod-r/m-byte
      (,operation (register (bits->word-reg reg-bits)) (indirect-address mod-bits r/m-bits t) t mod-bits r/m-bits)))
       (2
    (with-mod-r/m-byte
      (,operation (indirect-address mod-bits r/m-bits nil) (byte-register (bits->byte-reg reg-bits)) nil)))
       (3
    (with-mod-r/m-byte
      (,operation (indirect-address mod-bits r/m-bits t) (register (bits->word-reg reg-bits)) t)))
       (4
    (,operation (next-instruction) (byte-register :al) nil))
       (5
    (,operation (next-word) (register :ax) t)))))

(defmacro add-without-carry (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "add" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (set-zf-on-op (set-sf-on-op (set-cf-on-add (incf ,dest ,src)) ,is-word)))))

(defmacro add-with-carry (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "adc" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (set-zf-on-op (set-sf-on-op (set-cf-on-add (incf ,dest (+ ,src (flag :cf)))) ,is-word)))))

(defmacro sub-without-borrow (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "sub" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (let ((src-value ,src))
     (set-zf-on-op (set-sf-on-op (set-cf-on-sub (+ (decf ,dest src-value) src-value) src-value) ,is-word))))))

(defmacro sub-with-borrow (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "sbb" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (let ((src-plus-cf (+ ,src (flag :cf))))
     (set-zf-on-op (set-sf-on-op (set-cf-on-sub (+ (decf ,dest src-plus-cf) src-plus-cf) src-plus-cf) ,is-word))))))

(defmacro cmp-operation (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "cmp" :src ,src :dest ,dest)
     (set-zf-on-op (set-sf-on-op (set-cf-on-sub ,dest ,src) ,is-word))))

(defmacro and-operation (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "and" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (set-zf-on-op (set-sf-on-op (setf ,dest (logand ,src ,dest)) ,is-word))
       (setf (flag-p :cf) nil))))

(defmacro or-operation (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "or" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (set-zf-on-op (set-sf-on-op (setf ,dest (logior ,src ,dest)) ,is-word))
       (setf (flag-p :cf) nil))))

(defmacro xor-operation (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "xor" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (set-zf-on-op (set-sf-on-op (setf ,dest (logxor ,src ,dest)) ,is-word))
       (setf (flag-p :cf) nil))))

(defmacro parse-group1-byte (opcode operation mod-bits r/m-bits)
  `(case (mod ,opcode 4)
    (0 (,operation (next-instruction-ahead-of-indirect-address ,mod-bits ,r/m-bits) (indirect-address ,mod-bits ,r/m-bits nil) nil mod-bits r/m-bits))
    (1 (,operation (next-word-ahead-of-indirect-address ,mod-bits ,r/m-bits) (indirect-address ,mod-bits ,r/m-bits t) t mod-bits r/m-bits))
    (3 (,operation (twos-complement (next-instruction-ahead-of-indirect-address ,mod-bits ,r/m-bits) nil) (indirect-address ,mod-bits ,r/m-bits t) t mod-bits r/m-bits))))

(defmacro parse-group1-opcode (opcode)
  `(parse-group-opcode
     (0 (parse-group1-byte ,opcode add-without-carry mod-bits r/m-bits))
     (1 (parse-group1-byte ,opcode or-operation mod-bits r/m-bits))
     (2 (parse-group1-byte ,opcode add-with-carry mod-bits r/m-bits))
     (3 (parse-group1-byte ,opcode sub-with-borrow mod-bits r/m-bits))
     (4 (parse-group1-byte ,opcode and-operation mod-bits r/m-bits))
     (5 (parse-group1-byte ,opcode sub-without-borrow mod-bits r/m-bits))
     (6 (parse-group1-byte ,opcode xor-operation mod-bits r/m-bits))
     (7 (parse-group1-byte ,opcode cmp-operation mod-bits r/m-bits))))

;; Memory and register mov/xchg

(defun xchg-memory-register (opcode)
  (let ((is-word (oddp opcode)))
    (with-mod-r/m-byte
      (if is-word
      (xchg (register (bits->word-reg reg-bits)) (indirect-address mod-bits r/m-bits is-word))
      (xchg (byte-register (bits->byte-reg reg-bits)) (indirect-address mod-bits r/m-bits is-word))))))

(defmacro mov-immediate-to-memory (mod-bits r/m-bits is-word)
  `(if ,is-word
       (mov (next-word-ahead-of-indirect-address ,mod-bits ,r/m-bits) (indirect-address ,mod-bits ,r/m-bits t))
       (mov (next-instruction-ahead-of-indirect-address ,mod-bits ,r/m-bits) (indirect-address ,mod-bits ,r/m-bits nil))))

(defmacro parse-group11-opcode (opcode)
  `(parse-group-opcode
     (0 (parse-group-byte-pair ,opcode mov-immediate-to-memory mod-bits r/m-bits))))

(defmacro parse-mov-opcode (opcode)
  `(let ((mod-4 (mod ,opcode 4)))
     (with-mod-r/m-byte
       (case mod-4
     (0
      (mov (byte-register (bits->byte-reg reg-bits)) (indirect-address mod-bits r/m-bits nil)))
     (1
      (mov (register (bits->word-reg reg-bits)) (indirect-address mod-bits r/m-bits t)))
     (2
      (mov (indirect-address mod-bits r/m-bits nil) (byte-register (bits->byte-reg reg-bits))))
     (3
      (mov (indirect-address mod-bits r/m-bits t) (register (bits->word-reg reg-bits))))))))

;; Group 4/5 (inc/dec on EAs)

(defmacro inc-indirect (mod-bits r/m-bits is-word)
  `(inc (indirect-address ,mod-bits ,r/m-bits ,is-word) ,is-word))

(defmacro dec-indirect (mod-bits r/m-bits is-word)
  `(dec (indirect-address ,mod-bits ,r/m-bits ,is-word) ,is-word))

(defmacro parse-group4/5-opcode (opcode)
  `(parse-group-opcode
     (0 (parse-group-byte-pair ,opcode inc-indirect mod-bits r/m-bits))
     (1 (parse-group-byte-pair ,opcode dec-indirect mod-bits r/m-bits))))

;;; Opcode parsing

(defun in-paired-byte-block-p (opcode block)
  (= (truncate (/ opcode 2)) (/ block 2)))

(defun in-4-byte-block-p (opcode block)
  (= (truncate (/ opcode 4)) (/ block 4)))

(defun in-8-byte-block-p (opcode block)
  (= (truncate (/ opcode 8)) (/ block 8)))

(defun in-6-byte-block-p (opcode block)
  (and (= (truncate (/ opcode 8)) (/ block 8)) (< (mod opcode 8) 6)))

(defun parse-opcode (opcode)
  "Parse an opcode."
  (cond
    ((not opcode) (return-from parse-opcode nil))
    ((= opcode #xf4) (return-from parse-opcode '("hlt")))
    ((in-8-byte-block-p opcode #x40) (with-one-byte-opcode-register opcode #'inc-register))
    ((in-8-byte-block-p opcode #x48) (with-one-byte-opcode-register opcode #'dec-register))
    ((in-8-byte-block-p opcode #x50) (with-one-byte-opcode-register opcode #'push-register))
    ((in-8-byte-block-p opcode #x58) (with-one-byte-opcode-register opcode #'pop-to-register))
    ((in-8-byte-block-p opcode #x90) (with-one-byte-opcode-register opcode #'xchg-register))
    ((in-8-byte-block-p opcode #xb0) (mov-byte-to-register opcode))
    ((in-8-byte-block-p opcode #xb8) (with-one-byte-opcode-register opcode #'mov-word-to-register))
    ((= opcode #xf8) (clear-carry-flag))
    ((= opcode #xf9) (set-carry-flag))
    ((= opcode #xeb) (jmp-short))
    ((in-paired-byte-block-p opcode #x72) (jmp-short-conditionally opcode (flag-p :cf) "b"))
    ((in-paired-byte-block-p opcode #x74) (jmp-short-conditionally opcode (flag-p :zf) "z"))
    ((in-paired-byte-block-p opcode #x76) (jmp-short-conditionally opcode (or (flag-p :cf) (flag-p :zf)) "be"))
    ((in-paired-byte-block-p opcode #x78) (jmp-short-conditionally opcode (flag-p :sf) "s"))
    ((= opcode #xe8) (call-near))
    ((= opcode #xc3) (ret-from-call))
    ((in-6-byte-block-p opcode #x00) (parse-alu-opcode opcode add-without-carry))
    ((in-6-byte-block-p opcode #x08) (parse-alu-opcode opcode or-operation))
    ((in-6-byte-block-p opcode #x10) (parse-alu-opcode opcode add-with-carry))
    ((in-6-byte-block-p opcode #x18) (parse-alu-opcode opcode sub-with-borrow))
    ((in-6-byte-block-p opcode #x20) (parse-alu-opcode opcode and-operation))
    ((in-6-byte-block-p opcode #x28) (parse-alu-opcode opcode sub-without-borrow))
    ((in-6-byte-block-p opcode #x30) (parse-alu-opcode opcode xor-operation))
    ((in-6-byte-block-p opcode #x38) (parse-alu-opcode opcode cmp-operation))
    ((in-4-byte-block-p opcode #x80) (parse-group1-opcode opcode))
    ((in-4-byte-block-p opcode #x88) (parse-mov-opcode opcode))
    ((in-paired-byte-block-p opcode #x86) (xchg-memory-register opcode))
    ((in-paired-byte-block-p opcode #xc6) (parse-group11-opcode opcode))
    ((in-paired-byte-block-p opcode #xfe) (parse-group4/5-opcode opcode))))

;;; Main functions

(defun execute-instructions ()
  "Loop through loaded instructions."
  (loop
     for ret = (parse-opcode (next-instruction))
     until (equal ret '("hlt"))
     do (advance-ip)
     finally (return t)))

(defun disasm-instructions (instr-length)
  "Disassemble code."
  (loop
     for ret = (parse-opcode (next-instruction))
     collecting ret into disasm
     until (= *ip* instr-length)
     do (advance-ip)
     finally (return disasm)))

(defun loop-instructions (instr-length)
  (if *disasm*
      (disasm-instructions instr-length)
      (execute-instructions)))

(defun load-instructions-from-file (file)
  (with-open-file (in file :element-type '(unsigned-byte 8))
    (let ((instrs (make-array (file-length in) :element-type '(unsigned-byte 8) :initial-element 0 :adjustable t)))
      (read-sequence instrs in)
      instrs)))

(defun load-instructions (&key (file nil))
  (if file
      (load-instructions-from-file file)
      #()))

(defun print-video-ram (&key (width 80) (height 25) (stream t) (newline nil))
  (dotimes (line height)
    (dotimes (column width)
      (let ((char-at-cell (byte-in-ram (+ #x8000 (* line 80) column) *ram*)))
    (if (zerop char-at-cell)
        (format stream "~a" #\Space)
        (format stream "~a" (code-char char-at-cell)))))
    (if newline (format stream "~%"))))

(defun disasm (&key (file nil))
  (setf *disasm* t)
  (loop-instructions (load-instructions-into-ram (load-instructions :file file))))

(defun main (&key (file nil) (display nil) (stream t) (newline nil))
  (setf *disasm* nil)
  (loop-instructions (load-instructions-into-ram (load-instructions :file file)))
  (when display
    (print-video-ram :stream stream :newline newline)))

Here's the output in Emacs:

Emacs window with two panes, a section of the Lisp source on the left and the REPL output with the required content on the right.

I want to highlight three main features. This code makes heavy use of macros when implementing instructions, such as mov, xchg, and the artithmetic operations. Each instruction includes a disasm-instr macro call. This implements the disassembly alongside the actual code using an if over a global variable set at runtime. I am particularly proud of the destination-agnostic approach used for writing values to registers and indirect addresses. The macros implementing the instructions don't care about the destination, since the forms spliced in for either possibility will work with the generic setf Common Lisp macro.

The code can be found at my GitHub repo. Look for the "codegolf" branch, as I've already started implementing other features of the 8086 in master. I have already implemented the overflow and parity flags, along with the FLAGS register.

There are three operations in this not in the 8086, the 0x82 and 0x83 versions of the logical operators. This was caught very late, and it would be quite messy to remove those operations.

I would like to thank @j-a for his Python version, which inspired me early on in this venture.

\$\endgroup\$
2
  • 3
    \$\begingroup\$ Incredible first answer! Welcome to the site :) \$\endgroup\$
    – DJMcMayhem
    Commented Jul 28, 2017 at 21:43
  • 1
    \$\begingroup\$ Very cool language choice! \$\endgroup\$
    – copy
    Commented Jul 28, 2017 at 22:53
12
votes
\$\begingroup\$

C - 319 348 lines

This is a more or less direct translation of my Postscript program to C. Of course the stack usage is replaced with explicit variables. An instruction's fields are broken up into the variables o - instruction opcode byte, d - direction field, w - width field. If it's a "mod-reg-r/m" instruction, the m-r-rm byte is read into struct rm r. Decoding the reg and r/m fields proceeds in two steps: calculating the pointer to the data and loading the data, reusing the same variable. So for something like ADD AX,BX, first x is a pointer to ax and y is a pointer to bx, then x is the contents (ax) and y is the contents (bx). There's lots of casting required to reuse the variable for different types like this.

The opcode byte is decoded with a table of function pointers. Each function body is composed using macros for re-usable pieces. The DW macro is present in all opcode functions and decodes the d and w variables from the o opcode byte. The RMP macro performs the first stage of decoding the "m-r-rm" byte, and LDXY performs the second stage. Opcodes which store a result use the p variable to hold the pointer to the result location and the z variable to hold the result value. Flags are calculated after the z value has been computed. The INC and DEC operations save the carry flag before using the generic MATHFLAGS function (as part of the ADD or SUB submacro) and restore it afterwords, to preserve the Carry.

Edit: bugs fixed!
Edit: expanded and commented. When trace==0 it now outputs an ANSI move-to-0,0 command when dumping the video. So it better simulates an actual display. The BIGENDIAN thing (that didn't even work) has been removed. It relies in some places on little-endian byte order, but I plan to fix this in the next revision. Basically, all pointer access needs to go through the get_ and put_ functions which explicitly (de)compose the bytes in LE order.

#include<ctype.h>
#include<stdint.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/stat.h>
#include<unistd.h>
#define P printf
#define R return
#define T typedef
T intptr_t I; T uintptr_t U;
T short S; T unsigned short US;
T signed char C; T unsigned char UC; T void V;  // to make everything shorter
U o,w,d,f; // opcode, width, direction, extra temp variable (was initially for a flag, hence 'f')
U x,y,z;   // left operand, right operand, result
void *p;   // location to receive result
UC halt,debug=0,trace=0,reg[28],null[2],mem[0xffff]={ // operating flags, register memory, RAM
    1, (3<<6),        // ADD ax,ax
    1, (3<<6)+(4<<3), // ADD ax,sp
    3, (3<<6)+(4<<3), // ADD sp,ax
    0xf4 //HLT
};

// register declaration and initialization
#define H(_)_(al)_(ah)_(cl)_(ch)_(dl)_(dh)_(bl)_(bh)
#define X(_)_(ax)     _(cx)     _(dx)     _(bx)     _(sp)_(bp)_(si)_(di)_(ip)_(fl)
#define SS(_)_(cs)_(ds)_(ss)_(es)
#define HD(_)UC*_;      // half-word regs declared as unsigned char *
#define XD(_)US*_;      // full-word regs declared as unsigned short *
#define HR(_)_=(UC*)(reg+i++);      // init and increment by one
#define XR(_)_=(US*)(reg+i);i+=2;   // init and increment by two
H(HD)X(XD)SS(XD)V init(){I i=0;H(HR)i=0;X(XR)SS(XR)}    // declare and initialize register pointers
enum { CF=1<<0, PF=1<<2, AF=1<<4, ZF=1<<6, SF=1<<7, OF=1<<11 };

#define HP(_)P(#_ ":%02x ",*_);     // dump a half-word reg as zero-padded hex
#define XP(_)P(#_ ":%04x ",*_);     // dump a full-word reg as zero-padded hex
V dump(){ //H(HP)P("\n");
    P("\n"); X(XP)
    if(trace)P("%s %s %s %s ",*fl&CF?"CA":"NC",*fl&OF?"OV":"NO",*fl&SF?"SN":"NS",*fl&ZF?"ZR":"NZ");
    P("\n");  // ^^^ crack flag bits into strings ^^^
}

// get and put into memory in a strictly little-endian format
I get_(void*p,U w){R w? *(UC*)p + (((UC*)p)[1]<<8) :*(UC*)p;}
V put_(void*p,U x,U w){ if(w){ *(UC*)p=x; ((UC*)p)[1]=x>>8; }else *(UC*)p=x; }
// get byte or word through ip, incrementing ip
UC fetchb(){ U x = get_(mem+(*ip)++,0); if(trace)P("%02x(%03o) ",x,x); R x; }
US fetchw(){I w=fetchb();R w|(fetchb()<<8);}

T struct rm{U mod,reg,r_m;}rm;      // the three fields of the mod-reg-r/m byte
rm mrm(U m){ R(rm){ (m>>6)&3, (m>>3)&7, m&7 }; }    // crack the mrm byte into fields
U decreg(U reg,U w){    // decode the reg field, yielding a uintptr_t to the register (byte or word)
    if (w)R (U)((US*[]){ax,cx,dx,bx,sp,bp,si,di}[reg]);
    else R (U)((UC*[]){al,cl,dl,bl,ah,ch,dh,bh}[reg]); }
U rs(US*x,US*y){ R get_(x,1)+get_(y,1); }  // fetch and sum two full-words
U decrm(rm r,U w){      // decode the r/m byte, yielding uintptr_t
    U x=(U[]){rs(bx,si),rs(bx,di),rs(bp,si),rs(bp,di),get_(si,1),get_(di,1),get_(bp,1),get_(bx,1)}[r.r_m];
    switch(r.mod){ case 0: if (r.r_m==6) R (U)(mem+fetchw()); break;
                   case 1: x+=fetchb(); break;
                   case 2: x+=fetchw(); break;
                   case 3: R decreg(r.r_m,w); }
    R (U)(mem+x); }

// opcode helpers
    // set d and w from o
#define DW  if(trace){ P("%s:\n",__func__); } \
            d=!!(o&2); \
            w=o&1;
    // fetch mrm byte and decode, setting x and y as pointers to args and p ptr to dest
#define RMP rm r=mrm(fetchb());\
            x=decreg(r.reg,w); \
            y=decrm(r,w); \
            if(trace>1){ P("x:%d\n",x); P("y:%d\n",y); } \
            p=d?(void*)x:(void*)y;

    // fetch x and y values from x and y pointers
#define LDXY \
            x=get_((void*)x,w); \
            y=get_((void*)y,w); \
            if(trace){ P("x:%d\n",x); P("y:%d\n",y); }

    // normal mrm decode and load
#define RM  RMP LDXY

    // immediate to accumulator
#define IA x=(U)(p=w?(UC*)ax:al); \
           x=get_((void*)x,w); \
           y=w?fetchw():fetchb();

    // flags set by logical operators
#define LOGFLAGS  *fl=0; \
                  *fl |= ( (z&(w?0x8000:0x80))           ?SF:0) \
                       | ( (z&(w?0xffff:0xff))==0        ?ZF:0) ;

    // additional flags set by math operators
#define MATHFLAGS *fl |= ( (z&(w?0xffff0000:0xff00))     ?CF:0) \
                       | ( ((z^x)&(z^y)&(w?0x8000:0x80)) ?OF:0) \
                       | ( ((x^y^z)&0x10)                ?AF:0) ;

    // store result to p ptr
#define RESULT \
        if(trace)P(w?"->%04x ":"->%02x ",z); \
        put_(p,z,w);

// operators, composed with helpers in the opcode table below
    // most of these macros will "enter" with x and y already loaded with operands
#define PUSH(x) put_(mem+(*sp-=2),*(x),1)
#define POP(x) *(x)=get_(mem+(*sp+=2)-2,1)
#define ADD z=x+y; LOGFLAGS MATHFLAGS RESULT
#define ADC x+=(*fl&CF); ADD
#define SUB z=d?x-y:y-x; LOGFLAGS MATHFLAGS RESULT
#define SBB d?y+=*fl&CF:(x+=*fl&CF); SUB
#define CMP p=null; SUB
#define AND z=x&y; LOGFLAGS RESULT
#define  OR z=x|y; LOGFLAGS RESULT
#define XOR z=x^y; LOGFLAGS RESULT
#define INC(r) w=1; d=1; p=(V*)r; x=(S)*r; y=1; f=*fl&CF; ADD *fl=(*fl&~CF)|f;
#define DEC(r) w=1; d=1; p=(V*)r; x=(S)*r; y=1; f=*fl&CF; SUB *fl=(*fl&~CF)|f;
#define F(f) !!(*fl&f)
#define J(c) U cf=F(CF),of=F(OF),sf=F(SF),zf=F(ZF); y=(S)(C)fetchb(); \
                  if(trace)P("<%d> ", c); \
                  if(c)*ip+=(S)y;
#define JN(c) J(!(c))
#define IMM(a,b) rm r=mrm(fetchb()); \
            p=(void*)(y=decrm(r,w)); \
            a \
            x=w?fetchw():fetchb(); \
            b \
            d=0; \
            y=get_((void*)y,w); \
            if(trace){ P("x:%d\n",x); P("y:%d\n",y); } \
            if(trace){ P("%s ", (C*[]){"ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"}[r.reg]); } \
            switch(r.reg){case 0:ADD break; \
                          case 1:OR break; \
                          case 2:ADC break; \
                          case 3:SBB break; \
                          case 4:AND break; \
                          case 5:SUB break; \
                          case 6:XOR break; \
                          case 7:CMP break; }
#define IMMIS IMM(w=0;,w=1;x=(S)(C)x;)
#define TEST z=x&y; LOGFLAGS MATHFLAGS
#define XCHG f=x;z=y; LDXY if(w){*(US*)f=y;*(US*)z=x;}else{*(UC*)f=y;*(UC*)z=x;}
#define MOV z=d?y:x; RESULT
#define MOVSEG
#define LEA RMP z=((UC*)y)-mem; RESULT
#define NOP
#define AXCH(r) x=(U)ax; y=(U)(r); w=1; XCHG
#define CBW *ax=(S)(C)*al;
#define CWD z=(I)(S)*ax; *dx=z>>16;
#define CALL x=w?fetchw():(S)(C)fetchb(); PUSH(ip); (*ip)+=(S)x;
#define WAIT
#define PUSHF PUSH(fl)
#define POPF POP(fl)
#define SAHF x=*fl; y=*ah; x=(x&~0xff)|y; *fl=x;
#define LAHF *ah=(UC)*fl;
#define mMOV if(d){ x=get_(mem+fetchw(),w); if(w)*ax=x; else*al=x; } \
             else { put_(mem+fetchw(),w?*ax:*al,w); }
#define MOVS
#define CMPS
#define STOS
#define LODS
#define SCAS
#define iMOVb(r) (*r)=fetchb();
#define iMOVw(r) (*r)=fetchw();
#define RET(v) POP(ip); if(v)*sp+=v*2;
#define LES
#define LDS
#define iMOVm if(w){iMOVw((US*)y)}else{iMOVb((UC*)y)}
#define fRET(v) POP(cs); RET(v)
#define INT(v)
#define INT0
#define IRET
#define Shift rm r=mrm(fetchb());
#define AAM
#define AAD
#define XLAT
#define ESC(v)
#define LOOPNZ
#define LOOPZ
#define LOOP
#define JCXZ
#define IN
#define OUT
#define INv
#define OUTv
#define JMP x=fetchw(); *ip+=(S)x;
#define sJMP x=(S)(C)fetchb(); *ip+=(S)x;
#define FARJMP
#define LOCK
#define REP
#define REPZ
#define HLT halt=1
#define CMC *fl=(*fl&~CF)|((*fl&CF)^1);
#define NOT
#define NEG
#define MUL
#define IMUL
#define DIV
#define IDIV
#define Grp1 rm r=mrm(fetchb()); \
             y=decrm(r,w); \
             if(trace)P("%s ", (C*[]){}[r.reg]); \
             switch(r.reg){case 0: TEST; break; \
                           case 2: NOT; break; \
                           case 3: NEG; break; \
                           case 4: MUL; break; \
                           case 5: IMUL; break; \
                           case 6: DIV; break; \
                           case 7: IDIV; break; }
#define Grp2 rm r=mrm(fetchb()); \
             y=decrm(r,w); \
             if(trace)P("%s ", (C*[]){"INC","DEC","CALL","CALL","JMP","JMP","PUSH"}[r.reg]); \
             switch(r.reg){case 0: INC((S*)y); break; \
                           case 1: DEC((S*)y); break; \
                           case 2: CALL; break; \
                           case 3: CALL; break; \
                           case 4: *ip+=(S)y; break; \
                           case 5: JMP; break; \
                           case 6: PUSH((S*)y); break; }
#define CLC *fl=*fl&~CF;
#define STC *fl=*fl|CF;
#define CLI
#define STI
#define CLD
#define STD

// opcode table
// An x-macro table of pairs (a, b) where a becomes the name of a void function(void) which
// implements the opcode, and b comprises the body of the function (via further macro expansion)
#define OP(_)\
/*dw:bf                 wf                     bt                    wt   */ \
_(addbf, RM ADD)      _(addwf, RM ADD)       _(addbt,  RM ADD)     _(addwt, RM ADD)     /*00-03*/\
_(addbi, IA ADD)      _(addwi, IA ADD)       _(pushes, PUSH(es))   _(popes, POP(es))    /*04-07*/\
_(orbf,  RM OR)       _(orwf,  RM OR)        _(orbt,   RM OR)      _(orwt,  RM OR)      /*08-0b*/\
_(orbi,  IA OR)       _(orwi,  IA OR)        _(pushcs, PUSH(cs))   _(nop0,       )      /*0c-0f*/\
_(adcbf, RM ADC)      _(adcwf, RM ADC)       _(adcbt,  RM ADC)     _(adcwt, RM ADC)     /*10-13*/\
_(adcbi, IA ADC)      _(adcwi, IA ADC)       _(pushss, PUSH(ss))   _(popss, POP(ss))    /*14-17*/\
_(sbbbf, RM SBB)      _(sbbwf, RM SBB)       _(sbbbt,  RM SBB)     _(sbbwt, RM SBB)     /*18-1b*/\
_(sbbbi, IA SBB)      _(sbbwi, IA SBB)       _(pushds, PUSH(ds))   _(popds, POP(ds))    /*1c-1f*/\
_(andbf, RM AND)      _(andwf, RM AND)       _(andbt, RM AND)      _(andwt, RM AND)     /*20-23*/\
_(andbi, IA AND)      _(andwi, IA AND)       _(esseg, )            _(daa, )             /*24-27*/\
_(subbf, RM SUB)      _(subwf, RM SUB)       _(subbt, RM SUB)      _(subwt, RM SUB)     /*28-2b*/\
_(subbi, IA SUB)      _(subwi, IA SUB)       _(csseg, )            _(das, )             /*2c-2f*/\
_(xorbf, RM XOR)      _(xorwf, RM XOR)       _(xorbt, RM XOR)      _(xorwt, RM XOR)     /*30-33*/\
_(xorbi, IA XOR)      _(xorwi, IA XOR)       _(ssseg, )            _(aaa, )             /*34-37*/\
_(cmpbf, RM CMP)      _(cmpwf, RM CMP)       _(cmpbt, RM CMP)      _(cmpwt, RM CMP)     /*38-3b*/\
_(cmpbi, IA CMP)      _(cmpwi, IA CMP)       _(dsseg, )            _(aas, )             /*3c-3f*/\
_(incax, INC(ax))     _(inccx, INC(cx))      _(incdx, INC(dx))     _(incbx, INC(bx))    /*40-43*/\
_(incsp, INC(sp))     _(incbp, INC(bp))      _(incsi, INC(si))     _(incdi, INC(di))    /*44-47*/\
_(decax, DEC(ax))     _(deccx, DEC(cx))      _(decdx, DEC(dx))     _(decbx, DEC(bx))    /*48-4b*/\
_(decsp, DEC(sp))     _(decbp, DEC(bp))      _(decsi, DEC(si))     _(decdi, DEC(di))    /*4c-4f*/\
_(pushax, PUSH(ax))   _(pushcx, PUSH(cx))    _(pushdx, PUSH(dx))   _(pushbx, PUSH(bx))  /*50-53*/\
_(pushsp, PUSH(sp))   _(pushbp, PUSH(bp))    _(pushsi, PUSH(si))   _(pushdi, PUSH(di))  /*54-57*/\
_(popax, POP(ax))     _(popcx, POP(cx))      _(popdx, POP(dx))     _(popbx, POP(bx))    /*58-5b*/\
_(popsp, POP(sp))     _(popbp, POP(bp))      _(popsi, POP(si))     _(popdi, POP(di))    /*5c-5f*/\
_(nop1, ) _(nop2, )   _(nop3, ) _(nop4, )    _(nop5, ) _(nop6, )   _(nop7, ) _(nop8, )  /*60-67*/\
_(nop9, ) _(nopA, )   _(nopB, ) _(nopC, )    _(nopD, ) _(nopE, )   _(nopF, ) _(nopG, )  /*68-6f*/\
_(jo, J(of))          _(jno, JN(of))         _(jb, J(cf))          _(jnb, JN(cf))       /*70-73*/\
_(jz, J(zf))          _(jnz, JN(zf))         _(jbe, J(cf|zf))      _(jnbe, JN(cf|zf))   /*74-77*/\
_(js, J(sf))          _(jns, JN(sf))         _(jp, )               _(jnp, )             /*78-7b*/\
_(jl, J(sf^of))       _(jnl_, JN(sf^of))     _(jle, J((sf^of)|zf)) _(jnle,JN((sf^of)|zf))/*7c-7f*/\
_(immb, IMM(,))       _(immw, IMM(,))        _(immb1, IMM(,))      _(immis, IMMIS)      /*80-83*/\
_(testb, RM TEST)     _(testw, RM TEST)      _(xchgb, RMP XCHG)    _(xchgw, RMP XCHG)   /*84-87*/\
_(movbf, RM MOV)      _(movwf, RM MOV)       _(movbt, RM MOV)      _(movwt, RM MOV)     /*88-8b*/\
_(movsegf, RM MOVSEG) _(lea, LEA)            _(movsegt, RM MOVSEG) _(poprm,RM POP((US*)p))/*8c-8f*/\
_(nopH, )             _(xchgac, AXCH(cx))    _(xchgad, AXCH(dx))   _(xchgab, AXCH(bx))  /*90-93*/\
_(xchgasp, AXCH(sp))  _(xchabp, AXCH(bp))    _(xchgasi, AXCH(si))  _(xchadi, AXCH(di))  /*94-97*/\
_(cbw, CBW)           _(cwd, CWD)            _(farcall, )          _(wait, WAIT)        /*98-9b*/\
_(pushf, PUSHF)       _(popf, POPF)          _(sahf, SAHF)         _(lahf, LAHF)        /*9c-9f*/\
_(movalb, mMOV)       _(movaxw, mMOV)        _(movbal, mMOV)       _(movwax, mMOV)      /*a0-a3*/\
_(movsb, MOVS)        _(movsw, MOVS)         _(cmpsb, CMPS)        _(cmpsw, CMPS)       /*a4-a7*/\
_(testaib, IA TEST)   _(testaiw, IA TEST)    _(stosb, STOS)        _(stosw, STOS)       /*a8-ab*/\
_(lodsb, LODS)        _(lodsw, LODS)         _(scasb, SCAS)        _(scasw, SCAS)       /*ac-af*/\
_(movali, iMOVb(al))  _(movcli, iMOVb(cl))   _(movdli, iMOVb(dl))  _(movbli, iMOVb(bl)) /*b0-b3*/\
_(movahi, iMOVb(ah))  _(movchi, iMOVb(ch))   _(movdhi, iMOVb(dh))  _(movbhi, iMOVb(bh)) /*b4-b7*/\
_(movaxi, iMOVw(ax))  _(movcxi, iMOVw(cx))   _(movdxi, iMOVw(dx))  _(movbxi, iMOVw(bx)) /*b8-bb*/\
_(movspi, iMOVw(sp))  _(movbpi, iMOVw(bp))   _(movsii, iMOVw(si))  _(movdii, iMOVw(di)) /*bc-bf*/\
_(nopI, )             _(nopJ, )              _(reti, RET(fetchw())) _(retz, RET(0))     /*c0-c3*/\
_(les, LES)           _(lds, LDS)            _(movimb, RMP iMOVm)  _(movimw, RMP iMOVm) /*c4-c7*/\
_(nopK, )             _(nopL, )              _(freti, fRET(fetchw())) _(fretz, fRET(0)) /*c8-cb*/\
_(int3, INT(3))       _(inti, INT(fetchb())) _(int0, INT(0))       _(iret, IRET)        /*cc-cf*/\
_(shiftb, Shift)      _(shiftw, Shift)       _(shiftbv, Shift)     _(shiftwv, Shift)    /*d0-d3*/\
_(aam, AAM)           _(aad, AAD)            _(nopM, )             _(xlat, XLAT)        /*d4-d7*/\
_(esc0, ESC(0))       _(esc1, ESC(1))        _(esc2, ESC(2))       _(esc3, ESC(3))      /*d8-db*/\
_(esc4, ESC(4))       _(esc5, ESC(5))        _(esc6, ESC(6))       _(esc7, ESC(7))      /*dc-df*/\
_(loopnz, LOOPNZ)     _(loopz, LOOPZ)        _(loop, LOOP)         _(jcxz, JCXZ)        /*e0-e3*/\
_(inb, IN)            _(inw, IN)             _(outb, OUT)          _(outw, OUT)         /*e4-e7*/\
_(call, w=1; CALL)    _(jmp, JMP)            _(farjmp, FARJMP)     _(sjmp, sJMP)        /*e8-eb*/\
_(invb, INv)          _(invw, INv)           _(outvb, OUTv)        _(outvw, OUTv)       /*ec-ef*/\
_(lock, LOCK)         _(nopN, )              _(rep, REP)           _(repz, REPZ)        /*f0-f3*/\
_(hlt, HLT)           _(cmc, CMC)            _(grp1b, Grp1)        _(grp1w, Grp1)       /*f4-f7*/\
_(clc, CLC)           _(stc, STC)            _(cli, CLI)           _(sti, STI)          /*f8-fb*/\
_(cld, CLD)           _(std, STD)            _(grp2b, Grp2)        _(grp2w, Grp2)       /*fc-ff*/
#define OPF(a,b)void a(){DW b;}     // generate opcode function
#define OPN(a,b)a,                  // extract name
OP(OPF)void(*tab[])()={OP(OPN)};    // generate functions, declare and populate fp table with names

V clean(C*s){I i;       // replace unprintable characters in 80-byte buffer with spaces
    for(i=0;i<80;i++)
        if(!isprint(s[i]))
            s[i]=' ';
}
V video(){I i;          // dump the (cleaned) video memory to the console
    C buf[81]="";
    if(!trace)P("\e[0;0;f");
    for(i=0;i<28;i++)
        memcpy(buf, mem+0x8000+i*80, 80),
        clean(buf),
        P("\n%s",buf);
    P("\n");
}

static I ct;        // timer memory for period video dump
V run(){while(!halt){if(trace)dump();
    if(!ct--){ct=10; video();}
    tab[o=fetchb()]();}}
V dbg(){
    while(!halt){
        C c;
        if(!ct--){ct=10; video();}
        if(trace)dump();
        //scanf("%c", &c);
        fgetc(stdin);
        //switch(c){
        //case '\n':
        //case 's':
            tab[o=fetchb()]();
            //break;
        //}
    }
}

I load(C*f){struct stat s; FILE*fp;     // load a file into memory at address zero
    R (fp=fopen(f,"rb"))
        && fstat(fileno(fp),&s) || fread(mem,s.st_size,1,fp); }

I main(I c,C**v){
    init();
    if(c>1){            // if there's an argument
        load(v[1]);     //     load named file
    }
    *sp=0x100;          // initialize stack pointer
    if(debug) dbg();    // if debugging, debug
    else run();         // otherwise, just run
    video();            // dump final video
    R 0;}               // remember what R means? cf. line 9

Using macros for the stages of the various operations makes for a very close semantic match to the way the postscript code operates in a purely sequential fashion. For example, the first four opcodes, 0x00-0x03 are all ADD instructions with varying direction (REG -> REG/MOD, REG <- REG/MOD) and byte/word sizes, so they are represented exactly the same in the function table.

_(addbf, RM ADD)      _(addwf, RM ADD)       _(addbt,  RM ADD)     _(addwt, RM ADD)

The function table is instantiated with this macro:

OP(OPF)

which applies OPF() to each opcode representation. OPF() is defined as:

#define OPF(a,b)void a(){DW b;}     // generate opcode function

So, the first four opcodes expand (once) to:

void addbf(){ DW RM ADD ; }
void addwf(){ DW RM ADD ; }
void addbt(){ DW RM ADD ; }
void addwt(){ DW RM ADD ; }

These functions distinguish themselves by the result of the DW macro which determines direction and byte/word bits straight from the opcode byte. Expanding the body of one of these functions (once) produces:

if(trace){ P("%s:\n",__func__); }  // DW: set d and w from o
d=!!(o&2);
w=o&1;
RMP LDXY  // RM: normal mrm decode and load
z=x+y; LOGFLAGS MATHFLAGS RESULT  // ADD
;

Where the main loop has already set the o variable:

while(!halt){tab[o=fetchb()]();}}

Expanding one more time gives all the "meat" of the opcode:

// DW: set d and w from o
if(trace){ P("%s:\n",__func__); }
d=!!(o&2);
w=o&1;

// RMP: fetch mrm byte and decode, setting x and y as pointers to args and p ptr to dest
rm r=mrm(fetchb());
x=decreg(r.reg,w);
y=decrm(r,w);
if(trace>1){ P("x:%d\n",x); P("y:%d\n",y); }
p=d?(void*)x:(void*)y;

// LDXY: fetch x and y values from x and y pointers
x=get_((void*)x,w);
y=get_((void*)y,w);
if(trace){ P("x:%d\n",x); P("y:%d\n",y); }

z=x+y;   // ADD
// LOGFLAGS: flags set by logical operators
*fl=0;
*fl |= ( (z&(w?0x8000:0x80))           ?SF:0)
     | ( (z&(w?0xffff:0xff))==0        ?ZF:0) ;

// MATHFLAGS: additional flags set by math operators
*fl |= ( (z&(w?0xffff0000:0xff00))     ?CF:0)
     | ( ((z^x)&(z^y)&(w?0x8000:0x80)) ?OF:0)
     | ( ((x^y^z)&0x10)                ?AF:0) ;

// RESULT: store result to p ptr
if(trace)P(w?"->%04x ":"->%02x ",z);
put_(p,z,w);
;

And the fully-preprocessed function, passed through indent:

void
addbf ()
{
  if (trace)
    {
      printf ("%s:\n", __func__);
    }
  d = ! !(o & 2);
  w = o & 1;
  rm r = mrm (fetchb ());
  x = decreg (r.reg, w);
  y = decrm (r, w);
  if (trace > 1)
    {
      printf ("x:%d\n", x);
      printf ("y:%d\n", y);
    }
  p = d ? (void *) x : (void *) y;
  x = get_ ((void *) x, w);
  y = get_ ((void *) y, w);
  if (trace)
    {
      printf ("x:%d\n", x);
      printf ("y:%d\n", y);
    }
  z = x + y;
  *fl = 0;
  *fl |=
    ((z & (w ? 0x8000 : 0x80)) ? SF : 0) | ((z & (w ? 0xffff : 0xff)) ==
                        0 ? ZF : 0);
  *fl |=
    ((z & (w ? 0xffff0000 : 0xff00)) ? CF : 0) |
    (((z ^ x) & (z ^ y) & (w ? 0x8000 : 0x80)) ? OF : 0) |
    (((x ^ y ^ z) & 0x10) ? AF : 0);
  if (trace)
    printf (w ? "->%04x " : "->%02x ", z);
  put_ (p, z, w);;
}

Not the greatest C style for everyday use, but using macros this way seems pretty perfect for making the implementation here very short and very direct.

Test program output, with tail of the trace output:

43(103) incbx:
->0065 
ax:0020 cx:0015 dx:0190 bx:0065 sp:1000 bp:0000 si:0000 di:00c2 ip:013e fl:0000 NC NO NS NZ 
83(203) immis:
fb(373) 64(144) x:100
y:101
CMP ->0001 
ax:0020 cx:0015 dx:0190 bx:0065 sp:1000 bp:0000 si:0000 di:00c2 ip:0141 fl:0000 NC NO NS NZ 
76(166) jbe:
da(332) <0> 
ax:0020 cx:0015 dx:0190 bx:0065 sp:1000 bp:0000 si:0000 di:00c2 ip:0143 fl:0000 NC NO NS NZ 
f4(364) hlt:

.........                                                                       
Hello, world!                                                                   
0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 


################################################################################
##                                                                            ##
##  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 shared some earlier versions in comp.lang.c but they weren't very interested.

\$\endgroup\$
2
3
votes
\$\begingroup\$

Pascal - 667 lines including whitelines, comments and debug routines.

A late submission... But since there was no entry in pascal yet...
It is not finished yet but it works. Could be a lot shorter by eliminating white lines, comments and debug routines.
But on the other hand... code should be readable (and that's the reason I am a Pascal user)

terminal view

(******************************************************************************
*  emulator for 8086/8088 subset as requested in                              *
* https://codegolf.stackexchange.com/questions/4732/emulate-an-intel-8086-cpu *
* (c)2019 by ir. Marc Dendooven                                               *
* V0.2 DEV                                                                    *
******************************************************************************) 

program ed8086;
uses sysutils;

const showSub = 0; // change level to show debug info

type    nibble = 0..15;
        oct = 0..7;
        quad = 0..3;
        address = 0..$FFFF; // should be extended when using segmentation

const memSize = $9000; 
      AX=0; CX=1; DX=2; BX=3; SP=4; BP=5; SI=6; DI=7;

type location = record
                    memory: boolean; // memory or register
                    aORi: address; // address or index to register
                    content: word
                end;

var mem: array[0..memSize-1] of byte;
    IP,segEnd: word;
    IR: byte;
    mode: quad;
    sreg: oct;
    rm: oct;
    RX: array[AX..DI] of word; //AX,CX,DX,BX,SP,BP,SI,DI
    FC,FZ,FS: 0..1;
    cnt: cardinal = 0;
    d: 0..1;
    oper1,oper2: location;
    w: boolean; // is true when word / memory
    calldept: cardinal = 0;
    T: Dword;

// ---------------- general help methods ----------------
procedure display;
var x,y: smallint;
    c: byte;
begin
    writeln;
    for y := 0 to 24 do
        begin
          for x := 0 to 79 do
            begin
                c := mem[$8000+y*80+x];
                if c=0 then write(' ') else write(char(c));
            end;
            writeln
        end
end;    

procedure error(s: string);
begin
    writeln;writeln;
    writeln('ERROR: ',s);
    writeln('program aborted');
    writeln;
    writeln('IP=',hexstr(IP,4),' IR=',hexstr(IR,2));
    display;
    halt
end;

procedure debug(s: string);
begin
    if CallDept < showSub then writeln(s)
end;

procedure load;
// load the codegolf binary at address 0
var f,count: LongInt;
begin
    if not fileExists('codegolf') then error('file "codegolf" doesn''t exist in this directory');
    f := fileOpen('codegolf',fmOpenRead);
    count := fileRead(f,mem,memSize);
    fileClose(f);
    if count = -1 then error('Could not read file "codegolf"');
    writeln(count, ' bytes read to memory starting at 0');
    segEnd := count;
    writeln
end;

procedure mon;
begin
    if not (CallDept < showSub) then exit;
    writeln;
    writeln('------------- mon -------------');
    writeln('IP=',hexstr(IP,4));
    writeln('AX=',hexstr(RX[AX],4),' CX=',hexstr(RX[CX],4),' DX=',hexstr(RX[DX],4),' BX=',hexstr(RX[BX],4));
    writeln('SP=',hexstr(RX[SP],4),' BP=',hexstr(RX[BP],4),' SI=',hexstr(RX[SI],4),' DI=',hexstr(RX[DI],4));
    writeln('C=',FC,' Z=',FZ,' S=',FS);
    writeln('-------------------------------');
    writeln
end;

// --------------- memory and register access ----------------
// memory should NEVER be accessed direct
// in order to intercept memory mapped IO

function peek(a:address):byte;
begin
    peek := mem[a]
end;

procedure poke(a:address;b:byte);
begin
    mem[a]:=b;
    if not (CallDept < showSub) then exit;
    case a of $8000..$87CF: 
        begin 
            writeln;
            writeln('*** a character has been written to the screen ! ***');
            writeln('''',chr(b),''' to screenpos ',a-$8000)
        end
    end
end;

function peek2(a:address):word;
begin
    peek2 := peek(a)+peek(a+1)*256 
end;

procedure poke2(a:address; b:word);
begin
    poke(a,lo(b));
    poke(a+1,hi(b))
end;

function peekX(a: address): word;
begin
    if w then peekX := peek2(a) else peekX := peek(a)
end;

function fetch: byte;
begin
    fetch := peek(IP); inc(IP);
    debug('----fetching '+hexStr(fetch,2))
end;

function fetch2: word;
begin
    fetch2 := peek2(IP); inc(IP,2);
    debug('----fetching '+hexStr(fetch2,4))
end;

function getReg(r: oct): byte;
begin
    if r<4 then getReg := lo(RX[r])
            else getreg := hi(RX[r-4])
end;

function getReg2(r: oct): word;
begin
    getreg2 := RX[r]
end;

function getRegX(r: oct): word;
begin
    if w then getregX := getReg2(r) else getRegX := getReg(r)
end;

procedure test_ZS;
begin
    if w then FZ:=ord(oper1.content=0)
         else FZ:=ord(lo(oper1.content)=0);
    if w then FS:=ord(oper1.content>=$8000)
         else FS:=ord(oper1.content>=$80)
end;

procedure test_CZS;
begin
    test_ZS;
    if w then FC := ord(T>$FFFF)
         else FC := ord(T>$FF);
end;

// -------------- memory mode methods ------------------

procedure modRM;
// reads MRM byte
// sets mode (=the way rm is exploited)
// sets sreg: select general register (in G memory mode) or segment register
// sets rm: select general register or memory (in E memory mode)
var MrM: byte;
begin
    debug('---executing modRm');
    MrM := fetch;
    mode := MrM >> 6;
    sreg := (MrM and %00111000) >> 3;
    rm := MrM and %00000111;
    debug('------modRm '+hexstr(MrM,2)+' '+binstr(MrM,8)+'  '+
            'mode='+hexStr(mode,1)+' sreg='+hexStr(sreg,1)+' rm='+hexStr(rm,1))
end;


function mm_G: location;
// The reg field of the ModR/M byte selects a general register.
var oper: location;
begin
    debug('---executing mm_G');
    debug ('*** warning *** check for byte/word operations');   
    oper.memory := false;
    oper.aORi := sreg;
    oper.content := getRegX(sreg);
    debug('------register '+hexStr(sreg,1)+' read');
    mm_G := oper
end;

function mm_E: location;
// A ModR/M byte follows the opcode and specifies the operand. The operand is either a general­
// purpose register or a memory address. If it is a memory address, the address is computed from a
// segment register and any of the following values: a base register, an index register, a displacement.
var oper: location;
begin
    debug('---executing mm_E');
    case mode of
    0:  begin
            oper.memory := true;
            case rm of
            1: begin
                    oper.aORi:= RX[BX]+RX[DI];
                    oper.content := peekX(oper.aORi);
               end;
            6: begin //direct addressing
                    oper.aORi := fetch2;
                    oper.content := peekX(oper.aORi);
                    debug('----------direct addressing');
                    debug('----------address='+hexstr(oper.aORi,4));
                    debug('----------value='+hexStr(oper.content,4))
               end;
            7: begin
                    oper.aORi := RX[BX];
                    oper.content := peekX(oper.aORi)
               end;
            else
                error('mode=0, rm value not yet implemented')
            end
        end;
//  1: + 8 bit signed displacement      
    2:  begin
            oper.memory := true;
            case rm of
            5:  begin
                    oper.aORi := RX[DI]+fetch2; //16 unsigned displacement
                    oper.content := peekX(oper.aORi)
                end;
            7:  begin
                    oper.aORi := RX[BX]+fetch2; //16 unsigned displacement
                    oper.content := peekX(oper.aORi)
                end
            else
                error('mode=2, rm value not yet implemented')
            end
        end;
    3:  begin
            oper.memory := false;
            oper.aORi := rm;
            oper.content := getRegX(rm);
            debug('------register '+hexStr(rm,1)+' read');
        end;
    else
        error('mode '+intToStr(mode)+' of Ew not yet implemented')
    end;
    mm_E := oper
end;

function mm_I: location;
begin
    debug('---executing mm_I');
    if w
        then if d=0     then mm_I.content := fetch2
                        else mm_I.content := int8(fetch)
        else mm_I.content := fetch;
    debug('------imm val read is '+hexStr(mm_I.content,4))
end;

procedure mm_EI;
// E should be called before I since immediate data comes last
begin
    modRM;
    oper1 := mm_E;
    oper2 := mm_I;
end;

procedure mm_EG;
begin
    modRM;
    if d=0  then begin oper1 := mm_E; oper2 := mm_G end
            else begin oper1 := mm_G; oper2 := mm_E end;
end;

procedure mm_AI;
begin
    oper1.memory := false;
    oper1.aORi := 0;
    oper1.content := getRegX(0);
    debug('----------address='+hexstr(oper1.aORi,4));
    debug('----------value='+hexStr(oper1.content,4));
    oper2 := mm_I
end;

procedure writeback;
begin
    with oper1 do
     begin
        debug('--executing writeBack');
        debug(hexStr(ord(memory),1));
        debug(hexStr(aORi,4));
        debug(hexStr(content,4));       
        if w 
            then
                if memory   then poke2(aORi,content)
                            else RX[aORi]:=content
            else
                if memory   then poke(aORi,content)
                            else 
                                if aORi<4   then RX[aORi] := hi(RX[aORi])*256+content
                                            else RX[aORi-4] := content*256+lo(RX[aORi-4])
     end            
end;

// -------------------------- instructions -------------------

procedure i_Jcond;
// fetch next byte
// if condition ok then add to IP as two's complement
var b: byte;
    cc: 0..$F;
begin
    debug('--executing Jcond');
    b := fetch;
    cc := IR and %1111;
    case cc of
        2:if FC=1 then IP := IP + int8(b); //JB, JC
        4:if FZ=1 then IP := IP + int8(b); //JZ
        5:if FZ=0 then IP := IP + int8(b); //JNZ
        6:if (FC=1) or (FZ=1) then IP := IP + int8(b); //JBE
        7:if (FC=0) and (FZ=0) then IP := IP + int8(b); // JA, JNBE
        9:if FS=0 then IP := IP + int8(b); //JNS
        else error('JCond not implemented for condition '+hexstr(cc,1))
    end 
end;

procedure i_HLT;
begin
    debug('--executing HLT');
    writeln;writeln('*** program terminated by HLT instruction ***');
    writeln('--- In the ''codegolf'' program this probably means');
    writeln('--- there is some logical error in the emulator');
    writeln('--- or the program reached the end without error');
    writeln('bye');
    display;
    halt
end;

procedure i_MOV;
begin
    debug('--executing MOV EG');
    debug('------'+hexStr(oper1.aORi,4)+' '+hexStr(oper1.content,4));
    debug('------'+hexStr(oper2.aORi,4)+' '+hexStr(oper2.content,4));
    oper1.content := oper2.content; 
    debug('------'+hexStr(oper1.aORi,4)+' '+hexStr(oper1.content,4));
    writeBack
end;

procedure i_XCHG;
var T: word; 
begin
        debug('--executing XCHG EG');
        T := oper1.content;
        oper1.content := oper2.content;
        oper2.content := T;
        writeback;
        oper1 := oper2;
        writeback
end;

procedure i_XCHG_RX_AX;
var T: word;
begin
    debug('--executing XCHG RX AX');
    T := RX[AX];
    RX[AX] := RX[IR and %111];
    RX[IR and %111] := T
end;

procedure i_MOV_RX_Iw;
begin 
    debug('--executing MOV Rw ,Iw');
    RX[IR and %111] := fetch2 
end;

procedure i_MOV_R8_Ib;
var r: oct;
    b: byte;
begin
    debug('--executing MOV Rb ,Ib');
    b := fetch;
    r := IR and %111;
    if r<4  then RX[r] := hi(RX[r])*256+b
            else RX[r-4] := b*256+lo(RX[r-4]) 
end;

procedure i_PUSH_RX;
begin
    debug('--executing PUSH RX');
    dec(RX[SP],2); //SP:=SP-2
    poke2(RX[SP],RX[IR and %111]) //poke(SP,RX) 
end;

procedure i_POP_RX;
begin
    debug('--executing POP RX');
    RX[IR and %111] := peek2(RX[SP]); //RX := peek(SP)
    inc(RX[SP],2); //SP:=SP+2
end;

procedure i_JMP_Jb;
var b: byte;
begin
    debug('--executing JMP Jb');    
    b := fetch;
    IP:=IP+int8(b)
end;

procedure i_CALL_Jv;
var w: word;
begin
    debug('--executing CALL Jv');   
    w := fetch2;
    dec(RX[SP],2); //SP:=SP-2   
    poke2(RX[SP],IP);
    IP:=IP+int16(w);
    inc(calldept)
end;

procedure i_RET;
begin
    debug('--executing RET');
    IP := peek2(RX[SP]);
    inc(RX[SP],2); //SP:=SP+2
    dec(calldept)
end;

procedure i_XOR;
begin
    debug('--executing XOR');
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));    
    oper1.content:=oper1.content xor oper2.content;
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    test_ZS;
    FC:=0;
    writeback
end;

procedure i_OR;
begin
    debug('--executing OR');
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));    
    oper1.content:=oper1.content or oper2.content;
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    test_ZS;
    FC:=0;
    writeback
end;

procedure i_AND;
begin
    debug('--executing AND');
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));    
    oper1.content:=oper1.content and oper2.content;
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    test_ZS;
    FC:=0;
    writeback
end;

procedure i_ADD;
begin
    debug('--executing ADD');
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    T := oper1.content + oper2.content;
    oper1.content := T;
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    test_CZS;
    writeback
end;

procedure i_ADC;
begin
    debug('--executing ADC');
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    T := oper1.content + oper2.content + FC;
    oper1.content := T;
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    test_CZS;
    writeback
end;

procedure i_SUB;
begin
    debug('--executing SUB');
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    T := oper1.content - oper2.content;
    oper1.content := T;
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    test_CZS;
    writeback
end;

procedure i_SBB;
begin
    debug('--executing SBB');
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    T := oper1.content - oper2.content - FC;
    oper1.content := T;
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    test_CZS;
    writeback
end;

procedure i_CMP;
begin
    debug('--executing CMP');
    T:=oper1.content-oper2.content;
    oper1.content:=T;
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    test_CZS;
end;

procedure i_INC;
begin
    debug('--executing INC');
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    inc(oper1.content);
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    test_ZS;
    // FC unchanged
    writeback
end;

procedure i_DEC;
begin
    debug('--executing DEC');
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    dec(oper1.content);
    debug('------'+hexStr(oper1.content,4)+' '+hexStr(oper2.content,4));
    test_ZS;
    // FC unchanged
    writeback
end;

procedure i_INC_RX;
var r: oct;
begin
    debug('--executing INC RX');
    r := IR and %111;
    inc(RX[r]);
    FZ:=ord(RX[r]=0);
    FS:=ord(RX[r]>=$8000)
    // FC unchanged
end;

procedure i_DEC_RX;
var r: oct;
begin
    debug('--executing DEC RX');
    r := IR and %111;
    dec(RX[r]);
    FZ:=ord(RX[r]=0);
    FS:=ord(RX[r]>=$8000)
    // FC unchanged
end;
// ------------------ special instructions --------------

procedure GRP1;
// GRP1 instructions use modRM byte
// sreg selects instruction
// one operand selected by rm in mm_E
// other operand is Immediate:
begin
    debug('-executing GRP1');
    case sreg of
    0:i_ADD;
    1:i_OR;
    2:i_ADC;
    4:i_AND;
    5:i_SUB;
    7:i_CMP;
    else
        error('subInstruction GRP1 number '+intToStr(sreg)+' is not yet implemented')
    end
end;

procedure GRP4;
begin
    debug('-executing GRP4 Eb');
    modRM;
    oper1 := mm_E;
    case sreg of
    0:i_INC;
    1:i_DEC;
    else
        error('subInstruction GRP4 number '+intToStr(sreg)+' is not yet implemented')
    end
end;

begin //main
    writeln('+-----------------------------------------------+');
    writeln('| emulator for 8086/8088 subset as requested in |');
    writeln('| codegolf challenge                            |');
    writeln('| (c)2019 by ir. Marc Dendooven                 |');
    writeln('| V0.2 DEV                                      |');
    writeln('+-----------------------------------------------+');   
    writeln;
    load;
    IP := 0;
    RX[SP] := $100;
    while IP < segEnd do begin //fetch execute loop
        mon;
        IR := fetch; // IR := peek(IP); inc(IP)
        inc(cnt);
        debug(intToStr(cnt)+'> fetching instruction '+hexstr(IR,2)+' at '+hexstr(IP-1,4));
        w := boolean(IR and %1);
        d := (IR and %10) >> 1;
        case IR of
        $00..$03: begin mm_EG; i_ADD end;   
        $04,$05: begin mm_AI; i_ADD end;
        $08..$0B: begin mm_EG; i_OR end;
        $18..$1B: begin mm_EG; i_SBB end;
        $20..$23: begin mm_EG; i_AND end;
        $28..$2B: begin mm_EG; i_SUB end;
        $30..$33: begin mm_EG; i_XOR end;
        $38..$3B: begin mm_EG; i_CMP end;
        $3C,$3D: begin mm_AI; i_CMP end; //error('$3C - CMP AL Ib - nyi');
        $40..$47: i_INC_RX;
        $48..$4F: i_DEC_RX;
        $50..$57: i_PUSH_RX; 
        $58..$5F: i_POP_RX;
        $72,$74,$75,$76,$77,$79: i_Jcond; // $70..7F has same format with other flags
        $80..$83: begin mm_EI; GRP1 end;
        $86..$87: begin mm_EG; i_XCHG end;  
        $88..$8B: begin mm_EG; i_MOV end; 
        $90: debug('--NOP');
        $91..$97: i_XCHG_RX_AX; 
        $B0..$B7: i_MOV_R8_Ib;
        $B8..$BF: i_MOV_RX_Iw;
        $C3: i_RET;
        $C6,$C7: begin d := 0; mm_EI; i_MOV end; //d is not used as s here... set to 0
        $E8: i_CALL_Jv; 
        $EB: i_JMP_Jb;
        $F4: i_HLT;
        $F9: begin debug('--executing STC');FC := 1 end;
        $FE: GRP4;
        else
            error('instruction is not yet implemented')
        end
    end;
    writeln;
    error('debug - trying to execute outside codesegment')
end.

\$\endgroup\$
3
votes
\$\begingroup\$

MASM32, 1063+104+35=1202 lines

Actually done as a project for my assembly course at my college...

We modified the video memory layout slightly so it reflects that of a real 8086 machine from the good old days, i.e. video memory starting at 0xb8000, and contains an extra byte per character for foreground / background color.

Don't know if it's short enough to meet the needs of an answer, since I'm new to codegolf... But anyway, I find it somewhat gripping to code an emulator of 8086 with, ..., x86 assembly itself! Take a look at the results below.

test color

Here goes the code. The code is also available at GitHub.

binloader.asm

; we assume that the memory is of 1MB size, and code should be loaded at 7c0:0
; that is different from real 8086s which boot up from ffff:0
; however 7c0:0 is more convenient for our test binary
; time permitted, we might switch to ffff:0 and code a small bios

LoadBinaryIntoEmulator PROC USES ebx esi, mem:ptr byte, lpFileName:ptr byte
LOCAL hFile:dword, numBytesRead:dword, fileSize:dword, mbrAddr:ptr dword

    MBR_SIZE equ 512
    mov numBytesRead, 0
    INVOKE CreateFileA, lpFileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
    cmp eax, INVALID_HANDLE_VALUE
    je loadbin_error_ret
    mov hFile, eax
    INVOKE GetFileSize, hFile, 0
    cmp eax, 512
    jb loadbin_file_small
    mov eax, 512
loadbin_file_small:
    mov fileSize, eax
    mov ecx, mem
    add ecx, 7c00h
    mov mbrAddr, ecx
    INVOKE ReadFile, hFile, ecx, fileSize, ADDR numBytesRead, 0 ; read file to 7c00h
    test eax, eax
    jz loadbin_error_ret
    mov ecx, mbrAddr
    cmp dword ptr [ecx + 510], 0aa55h ; check for mbr flag
    jne loadbin_error_ret ; error if no flag detected
    mov eax, 0 ; success
    ret
loadbin_error_ret:
    mov eax, 1
    ret
LoadBinaryIntoEmulator ENDP

term.asm

IFNDEF TERM_INC
TERM_INC equ <1>

InitEmuScreen PROC ; call this only once to initialize the emulator screen
LOCAL hCon:dword, hWin:dword, termSize:COORD, rect:SMALL_RECT, cursorInfo:CONSOLE_CURSOR_INFO
    INVOKE GetStdHandle, STD_OUTPUT_HANDLE
    mov hCon, eax
    mov rect.Left, 0
    mov rect.Top, 0
    mov rect.Right, 80-1
    mov rect.Bottom, 28
    INVOKE SetConsoleWindowInfo, hCon, 1, ADDR rect ; set the size of display area
    mov termSize.x, 80
    mov termSize.y, 29                              ; 25 lines for output, 2 lines for status, 1 line empty
    INVOKE SetConsoleScreenBufferSize, hCon, dword ptr termSize ; set the size of buffer (1 extra line)
    INVOKE GetConsoleWindow
    mov hWin, eax
    INVOKE GetWindowLong, hWin, GWL_STYLE
    mov ecx, WS_MAXIMIZEBOX
    not ecx
    and eax, ecx
    mov ecx, WS_MINIMIZEBOX
    not ecx
    and eax, ecx
    mov ecx, WS_SIZEBOX
    not ecx
    and eax, ecx ; eax := eax & ~WS_MAXIMIZEBOX & ~WS_SIZEBOX & ~WS_MINIMIZEBOX
    INVOKE SetWindowLong, hWin, GWL_STYLE, eax
    mov [cursorInfo.dwSize], 1
    mov [cursorInfo.bVisible], 0
    INVOKE SetConsoleCursorInfo, hCon, ADDR cursorInfo
    ret
InitEmuScreen ENDP

WriteEmuScreen PROC USES ebx esi, mem:ptr byte ; update the emulator screen with memory starting from b800:0
LOCAL hCon:dword, termCoord:COORD, textAttr:dword

    count equ 80*25
    mov esi, mem
    INVOKE GetStdHandle, STD_OUTPUT_HANDLE
    mov hCon, eax
    mov ecx, 0
_loop:
    cmp ecx, count
    jge _loop_end
    mov edx, ecx
    shr edx, 16 
    movzx eax, cx ; prepare ecx in dx:ax
    mov bx, 80   ; divide by 80 to get num of lines
    div bx
    mov [termCoord.y], ax
    mov [termCoord.x], dx
    push ecx
    INVOKE SetConsoleCursorPosition, hCon, dword ptr termCoord ; move cursor to termCoord
    movzx ecx, byte ptr [esi+1]
    mov textAttr, ecx
    INVOKE SetConsoleTextAttribute, hCon, textAttr ; set color attrs, etc.
    INVOKE WriteConsoleA, hCon, esi, 1, 0, 0 ; put one char
    pop ecx
    add esi, 2
    inc ecx
    jmp _loop
_loop_end:
    INVOKE SetConsoleCursorPosition, hCon, 0
    INVOKE SetConsoleTextAttribute, hCon, 0 ; not visible
    mov eax, 0
    ret
WriteEmuScreen ENDP

loadStatusColor MACRO colorType
    IFIDN <colorType>, <running>
        mov eax, BACKGROUND_GREEN
        or eax, BACKGROUND_INTENSITY
        EXITM
    ENDIF
    IFIDN <colorType>, <paused>
        mov eax, BACKGROUND_GREEN
        or eax, BACKGROUND_RED 
        or eax, BACKGROUND_INTENSITY
        EXITM
    ENDIF
    echo Error: Unknown status
ENDM

WriteStatusLine PROC statusLine1:ptr byte, statusLine2:ptr byte, textAttr:dword ; suppose that both lines are 80 chars
LOCAL hCon:dword, termCoord:COORD

    INVOKE GetStdHandle, STD_OUTPUT_HANDLE
    mov hCon, eax
    INVOKE SetConsoleTextAttribute, hCon, textAttr
    mov [termCoord.x], 0
    mov [termCoord.y], 26 ; second last line
    
    INVOKE SetConsoleCursorPosition, hCon, dword ptr termCoord
    INVOKE WriteConsoleA, hCon, statusLine1, 80, 0, 0
    add [termCoord.y], 1
    INVOKE SetConsoleCursorPosition, hCon, dword ptr termCoord
    INVOKE WriteConsoleA, hCon, statusLine2, 80, 0, 0

    INVOKE SetConsoleCursorPosition, hCon, 0
    INVOKE SetConsoleTextAttribute, hCon, 0 ; not visible
    ret

WriteStatusLine ENDP
ENDIF

emulator.asm

.686
.model flat, stdcall
option casemap:none

include         windows.inc
include         kernel32.inc
include         user32.inc
include         msvcrt.inc

includelib      user32.lib
includelib      kernel32.lib
includelib      msvcrt.lib

printf          PROTO C :ptr byte, :VARARG

.data
floppyPath      byte "./floppy.img", 0
haltMsgTitle    byte "Halted", 0
haltMsg         byte "HLT is executed; since interrupt is not supported, the emulator will now exit.", 0
UDMsgTitle      byte "Undefined Instruction", 0
UDMsg           byte "Encountered an undefined instruction. (#UD)", 0
debugMsg        byte "%d %d %d %d", 0AH, 0DH, 0
invalidOpMsg    byte "Invalid Operation!", 0Dh, 0Ah, 0
statusRunning   byte "Running", 0
statusPaused    byte "Paused", ' ', 0
statusLineFmt1  byte "%s ", 9 dup(' '), "AX=%04X CX=%04X DX=%04X BX=%04X SP=%04X BP=%04X SI=%04X DI=%04X", 0
statusLineFmt2  byte "any key = step, c = continue", 4 dup(' '), "IP=%04X FLAGS=%02X ES=%04X CS=%04X SS=%04X DS=%04X", 0
lineBuf1        byte 128 dup(?)
lineBuf2        byte 128 dup(?)
running         byte 0
REGB            label byte
REGW            label word
R_AL            label byte
R_AX            word 0
R_CX            word 0
R_DX            word 0
R_BX            word 0
R_SP            word 0
R_BP            word 0
R_SI            word 0
R_DI            word 0

REGS            label word                
R_ES            word 0
R_CS            word 0 ; mbr
R_SS            word 0
R_DS            word 0

R_FLAGS         byte 0

R_IP            word 7c00H

MEMO_Guard      byte 00FFH

statusTextAttr dword 0
screenRefreshCounter word 0

.data?
MEMO            byte 1048576 DUP(?)

.code


; mod[2] xxx r/m[3] passed by ah, start of instruction(host) passed by ebx
; when r/m is reg, need to pass word/byte in lowest bit of al
; not modify al, ah and ebx
; effective address(host) returned by edx,
; end of displacement field(host) returned by esi
computeEffectiveAddress MACRO LeaveLabel, DisableFallThroughLeave, SegmentType
                ; MACRO local label
                LOCAL NoDisplacement, MOD123, MOD23, RM_Decode, RM_Is1XX, RM_Is11X, AddDisplacment, MOD3, RM_IsWordReg
                ; ah already = mod[2] reg[3] r/m[3] or mod[2] op[3] r/m[3]
                mov cl, ah
                shr cl, 6; mod
                jnz MOD123
                ; mod = 00
                mov cl, ah ; mod[2] reg[3] r/m[3]
                and cl, 0111b ; r/m[3]
                cmp cl, 0110b ; check special case
                jne NoDisplacement
                ; r/m = 110 special case, 16bit displacement only
                movzx edi, word ptr [ebx + 2]
                lea esi, [ebx + 4] ; end of displacement field(host)
                xor edx, edx ; clear edx for displacement only
                jmp AddDisplacment
    NoDisplacement:
                xor edi, edi ; common case, no displacement
                lea esi, [ebx + 2] ; end of displacement field(host)
                jmp RM_Decode
    MOD123:
                cmp cl, 1
                jne MOD23
                ; mod = 01
                movzx edi, byte ptr [ebx + 2] ; 8bit displacement
                lea esi, [ebx + 3] ; end of displacement field(host)
                jmp RM_Decode
    MOD23:
                cmp cl, 2
                jne MOD3
                ; mod = 10
                movzx edi, word ptr [ebx + 2] ; 16bit displacement
                lea esi, [ebx + 4] ; end of displacement field(host)
                ; fall-through
    RM_Decode:
                ; displacement in edi
                movzx ecx, ah ; mod[2] reg[3] r/m[3]
                test ecx, 0100b
                jnz RM_Is1XX
                ; r/m = 0,b,i
                and ecx, 0010b ; Base = b ? BP : BX, R_BP = R_BX + 4
                movzx edx, word ptr R_BX[ecx * 2] ; actually word ptr is not needed
                movzx ecx, ah ; mod[2] reg[3] r/m[3]
                and ecx, 0001b ; Index = i ? DI : SI, R_DI = R_SI + 4
                movzx ecx, word ptr R_SI[ecx * 2]
                add edx, ecx
                jmp AddDisplacment
    RM_Is1XX:
                test ecx, 0010b
                jnz RM_Is11X
                ; r/m = 1,0,i
                and ecx, 0001b ; Index = i ? DI : SI, R_DI = R_SI + 4
                movzx edx, word ptr R_SI[ecx * 2]
                jmp AddDisplacment
    RM_Is11X:
                ; r/m = 1,1,~b
                and ecx, 0001b ; Base = b ? BP : BX, R_BP = R_BX + 4
                xor ecx, 1
                movzx edx, word ptr R_BX[ecx * 4]
                ; fall-through
    AddDisplacment:
                add edx, edi ; effective address(virtual) now in edx
                ; now edi free
                movzx ecx, SegmentType
                shl ecx, 4
                lea edx, MEMO[edx + ecx] ; effective address(host)
                jmp LeaveLabel
    MOD3:
                ; r/m = register
                lea esi, [ebx + 2] ; end of displacement(host) (No Displacement)
                movzx ecx, ah ; mod[2] reg[3] r/m[3], moved before jump to reuse code
                test al, 0001b ; first byte still in al, decide 16bit or 8bit register
                jnz RM_IsWordReg
                ; 8bit register
                and ecx, 0011b
                lea edx, REGB[ecx * 2] ; ACDB
                movzx ecx, ah
                and ecx, 0100b ; 0 -> L, 1 -> H
                shr ecx, 2
                add edx, ecx ; register host address now in edx
                jmp LeaveLabel
    RM_IsWordReg:
                ; 16bit register
                and ecx, 0111b
                lea edx, REGW[ecx * 2] ; register host address now in edx
                ; fall-through
    IF DisableFallThroughLeave
                jmp LeaveLabel
    ENDIF
ENDM

; ebx is flat addr in host machine
computeFlatIP MACRO
                movzx eax, R_CS
                shl eax, 4
                movzx ebx, R_IP
                lea ebx, MEMO[ebx + eax]
ENDM

; use ah
modifyFlagsInstruction MACRO instruction
                mov ah, R_FLAGS
                sahf
                instruction
                lahf
                mov R_FLAGS, ah
ENDM

; error case return address passed by ecx
; success will use ret
; flat ip in ebx
ArithLogic PROC
                movzx eax, word ptr [ebx]; read 2 bytes at once for later use, may exceed 1M, but we are in a emulator
                test eax, 11000100b ; only test low byte -- the first byte
                jz RegWithRegOrMem; must be 00xxx0xx, No Imm Op
                test eax, 01111100b
                jz ImmWithRegOrMem; must be 100000xx(with test 11000100b not zero), Imm to reg/mem
                test eax, 11000010b
                jz ImmToAcc; must be 00xxx10x(with test 11000100b not zero), Imm to accumulator Op
                jmp ecx; Other Instructions
    ImmToAcc:
                xor ecx, ecx
                test al, 0001b
                setnz cl
                lea edx, [R_AX] ; dest addr
                lea esi, [ebx + 1] ; src addr
                lea edi, [ecx + 2] ; delta ip
                ; now ebx free
                movzx ebx, al ; first byte contains op[3]
                jmp Operand
    ImmWithRegOrMem:
    RegWithRegOrMem:
                computeEffectiveAddress SrcIsRegOrImm, 0, R_DS
    SrcIsRegOrImm: ; not real src, d[1] decide real src
                mov edi, esi ; copy "end of displacement(host)" for delta ip
                sub edi, ebx ; compute delta ip, not yet count imm data, will be handled in SrcIsImm
                ; now ebx free
                test al, 10000000b ; first byte still in al
                jnz SrcIsImm
                ; Not Imm, Use Reg
                movzx ebx, al ; first byte contains op[3]
                shr ah, 2 ; not fully shift to eliminate index scaling
                movzx ecx, ah ; 00 mod[2] reg[3] x, moved before jump to reuse code
                test al, 0001b ; decide 16bit or 8bit register
                jnz REG_IsWordReg
                ; 8bit register
                and ecx, 0110b ; ecx = 00 mod[2] reg[3] x ; 0,2,4,6 -> ACDB 
                movzx ebx, ah
                and ebx, 1000b ; 0 -> L, 1 -> H
                shr ebx, 3
                lea esi, REGB[ecx + ebx] ; reg register host address now in esi
                jmp SRC_DEST ; first byte still in al
    REG_IsWordReg:
                ; 16bit register
                and ecx, 1110b
                lea esi, REGW[ecx] ; reg register host address now in esi
                jmp SRC_DEST ; first byte still in al
    SrcIsImm:
                movzx ebx, ah ; second byte contains op[3]
                ; compute delta ip
                xor ecx, ecx
                cmp al, 10000001b ; 100000 s[1] w[1], 00: +1, 01: +2, 10: +1, 11: +1
                sete cl
                lea edi, [edi + 1 + ecx] ; delta ip in edi
                ; imm data host address(end of displacement) already in esi
                jmp SRC_DEST ; first byte still in al
    SRC_DEST:
                ; first byte still in al
                test al, 10000000b;
                jnz Operand ; Imm to r/m, no need to exchange
                test al, 0010b; d[1] or s[1] (Imm to r/m case)
                jz Operand ; d = 0 no need to exchange
                xchg esi, edx ; put src in esi and dest in edx, for sub/sbb/cmp and write back
                ; fall-through
    Operand:           
                ; first byte still in al
                test al, 0001b ; decide 8bit or 16bit operand
                jnz OperandW ; word operand
                ; use 8bit partial reg for convenience
                ; generally that will be slower because of partial register stalls
                ; fortunately we don't need to read from cx or ecx, actually no stall occur
                mov cl, byte ptr [esi] ; src operand
                and ebx, 00111000b ; xx op[3] xxx, select bits, clear others
                ; Not shift, eliminate index * 8 for OpTable
                jmp dword ptr [OpTable + ebx]
    OperandW:
                ; first byte still in al
                test al, 10000000b
                jz NotSignExt ; Not Imm to r/m
                test al, 0010b; s[1]
                jz NotSignExt
                movsx cx, byte ptr [esi] ; src operand, sign ext
                jmp OperandWExec
    NotSignExt:
                mov cx, word ptr [esi] ; src operand
                ; fall-through
    OperandWExec:
                and ebx, 00111000b ; xx op[3] xxx, select bits, clear others
                ; Not shift, eliminate index * 8 for OpTable
                jmp dword ptr [OpTable + 4 + ebx]
    OpTable:
                ; could store diff to some near Anchor(e.g. OpTable) to save space
                ; but we use a straightforward method
                dword B_ADD, W_ADD
                dword B_OR, W_OR
                dword B_ADC, W_ADC
                dword B_SBB, W_SBB
                dword B_AND, W_AND
                dword B_SUB, W_SUB
                dword B_XOR, W_XOR
                dword B_CMP, W_CMP
    ByteOp:
        B_CMP:
                cmp byte ptr [edx], cl
                jmp WriteFlags
        B_XOR:
                xor byte ptr [edx], cl
                jmp WriteFlags
        B_SUB:
                sub byte ptr [edx], cl
                jmp WriteFlags
        B_AND:
                and byte ptr [edx], cl
                jmp WriteFlags
        B_SBB:
                mov ah, R_FLAGS
                sahf
                sbb byte ptr [edx], cl
                jmp WriteFlags
        B_ADC:
                mov ah, R_FLAGS
                sahf
                adc byte ptr [edx], cl
                jmp WriteFlags
        B_OR:
                or byte ptr [edx], cl
                jmp WriteFlags
        B_ADD:
                add byte ptr [edx], cl
                jmp WriteFlags
    WordOp:
        W_CMP:
                cmp word ptr [edx], cx
                jmp WriteFlags
        W_XOR:
                xor word ptr [edx], cx
                jmp WriteFlags
        W_SUB:
                sub word ptr [edx], cx
                jmp WriteFlags
        W_AND:
                and word ptr [edx], cx
                jmp WriteFlags
        W_SBB:
                mov ah, R_FLAGS
                sahf
                sbb word ptr [edx], cx
                jmp WriteFlags
        W_ADC:
                mov ah, R_FLAGS
                sahf
                adc word ptr [edx], cx
                jmp WriteFlags
        W_OR:
                or word ptr [edx], cx
                jmp WriteFlags
        W_ADD:
                add word ptr [edx], cx
                ; fall-through
    WriteFlags:
                lahf ; load flags into ah
                mov R_FLAGS, ah
                add R_IP, di
                ret
ArithLogic ENDP

Arith_INC_DEC PROC ; note: inc and dec is partial flags writer we need to load flags before inc or dec
                movzx eax, word ptr [ebx] ; read 2 byte at once, may exceed 1M, but we are in a emulator
                
                xor al, 01000000b 
                test al, 11110000b ; high 4 0100
                jz RegOnly
                xor al, 10111110b ; equiv to xor 11111110 at once
                test al, 11111110b 
                jz RegOrMem
                jmp ecx
    RegOnly:
                add R_IP, 1 ; 1byte long
                movzx ebx, al
                test al, 00001000b
                jnz RegOnlyDEC
                ; other bits in eax already clear
                ; INC
                ; note: load flags use ah
                modifyFlagsInstruction <inc word ptr REGW[ebx * 2]>
                ret
        RegOnlyDEC:
                modifyFlagsInstruction <dec word ptr REGW[ebx * 2 - 16]> ; 1 reg[3], *2 - 16
                ret
    RegOrMem:
                test ah, 00110000b
                jz Match
                jmp ecx ; other instructions
        Match:
                ; note: al lowest bit already w[1]
                computeEffectiveAddress INC_DEC_ComputeEA_Done, 0, R_DS
        INC_DEC_ComputeEA_Done:
                sub esi, ebx
                add R_IP, si
                test ah, 00001000b
                jnz RegOrMemDEC
                ; INC
                test al, 0001b
                jnz WordINC
                ; byte INC
                modifyFlagsInstruction <inc byte ptr [edx]>
                ret
            WordINC:
                modifyFlagsInstruction <inc word ptr [edx]>
                ret
        RegOrMemDEC:
                test al, 0001b
                jnz WordDEC
                ; byte DEC
                modifyFlagsInstruction <dec byte ptr [edx]>
                ret
        WordDEC:
                modifyFlagsInstruction <dec word ptr [edx]>
                ret
Arith_INC_DEC ENDP

; uses ah
GenerateJmpConditional MACRO jmp_cc
                movsx di, ah
                mov ah, R_FLAGS
                sahf
                jmp_cc Jmp_Short_Rel8_Inner
                add R_IP, 2 ; instruction length = 2 bytes
                ret
ENDM

; flat ip in ebx
; NOTE: cs can be changed!
; error case return address passed by ecx
; success will use ret
ControlTransfer PROC
                movzx eax, word ptr [ebx] ; read 2 byte at once, may exceed 1M, but we are in a emulator
                cmp al, 0E8h   ; parse instruction type
                je Call_Direct_Near
                cmp al, 09Ah
                je Call_Direct_Far
                cmp al, 0FFh
                je Call_Jmp_Indirect
                cmp al, 0EBh
                je Jmp_Short_Rel8
                cmp al, 0E9h
                je Jmp_Near_Rel16
                cmp al, 0EAh
                je Jmp_Direct_Far 
    FOR x, <70h,71h,72h,73h,74h,75h,76h,77h,78h,79h,7ah,7bh,7ch,7dh,7eh,7fh> ; conditional jmp
                cmp al, x
                je Jmp&x
    ENDM
                movzx edi, word ptr [ebx + 1] ; pop imm16 bytes
                cmp al, 0C2h
                je Ret_Near
                cmp al, 0CAh
                ; edi already load
                je Ret_Far

                xor edi, edi ; pop 0 byte
                cmp al, 0C3h
                je Ret_Near
                cmp al, 0CBh
                ; edi already clear
                je Ret_Far

                jmp ecx ; other instructions
                
    Jmp70h:     GenerateJmpConditional jo
    Jmp71h:     GenerateJmpConditional jno
    Jmp72h:     GenerateJmpConditional jb
    Jmp73h:     GenerateJmpConditional jae
    Jmp74h:     GenerateJmpConditional je
    Jmp75h:     GenerateJmpConditional jne
    Jmp76h:     GenerateJmpConditional jbe
    Jmp77h:     GenerateJmpConditional ja
    Jmp78h:     GenerateJmpConditional js
    Jmp79h:     GenerateJmpConditional jns
    Jmp7ah:     GenerateJmpConditional jpe
    Jmp7bh:     GenerateJmpConditional jpo
    Jmp7ch:     GenerateJmpConditional jl
    Jmp7dh:     GenerateJmpConditional jge
    Jmp7eh:     GenerateJmpConditional jle
    Jmp7fh:     GenerateJmpConditional jg

    Jmp_Short_Rel8:
                movsx di, ah
    Jmp_Short_Rel8_Inner:
                add di, 2 ; instruction length = 2 bytes
                add R_IP, di ; ip += rel8 sign extended to 16bit (relative to next instruction)
                ret
    Call_Direct_Near: ; near direct is ip relative, cannot reuse indirect code
                movzx edx, R_SP
                movzx ecx, R_SS
                sub R_SP, 2 ; write after read to avoid stall
                shl ecx, 4

                mov si, R_IP
                add si, 3 ; instruction length = 3 bytes
                mov word ptr MEMO[edx + ecx - 2], si
                add si, word ptr [ebx + 1]
                mov R_IP, si ; write back
                ret ; not reuse code
    Jmp_Near_Rel16:
                ; ip += displacement
                mov si, R_IP
                add si, 3 ; instruction length = 3 bytes
                add si, word ptr [ebx + 1]
                mov R_IP, si ; write back
                ret
    Call_Direct_Far:
                lea edx, [ebx + 1]
                lea esi, [ebx + 5]
                jmp Call_Indirect_Far ; reuse code
    Jmp_Direct_Far:
                lea edx, [ebx + 1]
                lea esi, [ebx + 5]
                jmp Jmp_Indirect_Far ; reuse code
    Call_Jmp_Indirect:
                test ah, 00110000b
                jz NotMatch ; xx00xxxx
                xor ah, 00110000b
                test ah, 00110000b
                jz NotMatch ; xx11xxxx
                computeEffectiveAddress Control_Flow_EA_Done, 0, R_DS
                ; fall-through
        Control_Flow_EA_Done:
                ; IMPORTANT: ah xor with 00110000b
                test ah, 00100000b
                jz Jmp_Indirect ; xx10sxxx
                ; xx01sxxx
                test ah, 00001000b
                jz Call_Indirect_Near ; xx010xxx
                jmp Call_Indirect_Far ; xx011xxx
            Jmp_Indirect:
                ; xx10sxxx
                test ah, 00001000b
                jz Jmp_Indirect_Near ; xx100xxx
                jmp Jmp_Indirect_Far ; xx101xxx
        NotMatch:
                jmp ecx
    Call_Indirect_Near:
                sub esi, ebx ; esi-ebx is command length
                add si, R_IP ; add to R_IP to get offset of next instruction
                ; now ebx free
                ; push ip
                movzx ebx, R_SP
                movzx ecx, R_SS
                sub R_SP, 2 ; write after read to avoid stall
                shl ecx, 4
                mov word ptr MEMO[ebx + ecx - 2], si
                ; fall-through
    Jmp_Indirect_Near:
                mov ax, word ptr [edx] ; load offset into cx
                mov R_IP, ax ; write back new ip
                ret

    Call_Indirect_Far:
                sub esi, ebx ; esi-ebx is command length
                add si, R_IP ; add to R_IP to get offset of next instruction
                ; now ebx free
                ; push cs, then push ip
                movzx ebx, R_SP
                movzx ecx, R_SS
                sub R_SP, 4 ; write after read to avoid stall
                shl ecx, 4

                movzx eax, R_CS
                shl eax, 16
                or eax, esi ; avoid partial register write then read whole register
                mov dword ptr MEMO[ebx + ecx - 4], eax
                ; fall-through
    Jmp_Indirect_Far:
                mov eax, dword ptr [edx]
                mov R_IP, ax
                shr eax, 16
                mov R_CS, ax
                ret

    Ret_Near:       
                movzx edx, R_SP
                movzx ecx, R_SS
                shl ecx, 4
                mov ax, word ptr MEMO[edx + ecx] ; rtn addr in ax

                mov R_IP, ax
                add di, 2 ; pop n + 2 byte
                add R_SP, di
                ret
    Ret_Far:
                movzx edx, R_SP
                movzx ecx, R_SS
                shl ecx, 4
                mov eax, dword ptr MEMO[edx + ecx]; rtn addr (high[16] = cs, low[16] = ip ) in eax

                mov R_IP, ax
                shr eax, 16
                mov R_CS, ax

                mov R_IP, ax
                add di, 4 ; pop n + 4 byte
                add R_SP, di
                ret
ControlTransfer ENDP

; error case return address passed by ecx
; flat ip in ebx
; success will use ret
DataTransferMOV PROC
                movzx eax, word ptr [ebx] ; read 2 byte at once, may exceed 1M, but we are in a emulator
                xor al, 10001000b
                test al, 11111100b ; high 6 100010
                jz RegWithRegOrMem ; 100010xx
                ; not xor
                test al, 11111001b
                jz SegRegWithRegOrMem; 100011x0, previous test with 11111100b not zero, thus test with 0100b must not zero
                xor al, 00101000b ; equiv to xor 10100000b at once
                test al, 11111100b ; high 6 101000
                jz MemWithAccumulator
                xor al, 00010000b ; equiv to xor 10110000b at once
                test al, 11110000b ; high 4 1011
                jz ImmToReg
                xor al, 01110110b ; equiv to xor 11000110b at once
                test al, 11111110b ; high 7 1100011
                jz ImmToRegOrMem
                jmp ecx         
    ImmToRegOrMem:
                or al, 10000000b ; set flag to reuse code, repeat macro maybe a little faster
                jmp WithRegOrMem
    SegRegWithRegOrMem:
                or al, 0001b ; SegReg case don't have w[1], manually set lowest bit
                ; fall-through
    RegWithRegOrMem:
                ; fall-through
    WithRegOrMem:
                computeEffectiveAddress SrcIsRegOrImm, 0, R_DS
    SrcIsRegOrImm: ; not real src, d[1] decide real src
                test al, 10000000b ; test flag
                jnz SrcIsImm
                ; Not Imm, Use Reg
                ; compute delta ip
                sub esi, ebx
                add R_IP, si
                ; now ebx, esi free

                shr ah, 2 ; not fully shift to eliminate index scaling
                movzx ecx, ah ; 00 mod[2] reg[3] x, moved before jump to reuse code

                test al, 0100b ; check if segment register
                jnz SegReg

                test al, 0001b ; decide 16bit or 8bit register
                jnz REG_IsWordReg
                ; 8bit register
                and ecx, 0110b ; ecx = 00 mod[2] reg[3] x ; 0,2,4,6 -> ACDB
                movzx ebx, ah
                and ebx, 1000b ; 0 -> L, 1 -> H
                shr ebx, 3

                test al, 0010b ; d[1]
                jnz ToByteReg
                ; from byteReg
                mov al, byte ptr REGB[ecx + ebx]
                mov byte ptr [edx], al
                ret
    ToByteReg:
                mov al, byte ptr [edx]
                mov byte ptr REGB[ecx + ebx], al
                ret
    REG_IsWordReg:
                ; 16bit register
                and ecx, 1110b
                test al, 0010b ; d[1]
                jnz ToWordReg
                ; from WordReg
                mov ax, word ptr REGW[ecx]
                mov word ptr [edx], ax
                ret
    ToWordReg:
                mov ax, word ptr [edx]
                mov word ptr REGW[ecx], ax
                ret
    SrcIsImm:
                mov cx, word ptr [esi] ; read 2 byte at once to reuse code, may exceed 1M, but we are in a emulator
                test al, 0001b ; w[1]
                jnz WordSrcImm
                ; Byte Imm
                add esi, 1
                sub esi, ebx
                add R_IP, si
                mov byte ptr [edx], cl
                ret
    WordSrcImm:
                add esi, 2
                sub esi, ebx
                add R_IP, si
                mov word ptr [edx], cx
                ret
    SegReg:
                and ecx, 0110b ; reg[3] = 0 seg[2]
                test al, 0010b ; d[1]
                jnz ToSegReg
                mov ax, word ptr REGS[ecx]
                mov word ptr [edx], ax
                ret
    ToSegReg:
                mov ax, word ptr [edx]
                mov word ptr REGS[ecx], ax
                ret
    ImmToReg:
                test al, 1000b ; w[1]
                jnz WordImmToReg
                ; byte Imm
                ; ebx + 1 already in ah, ebx free
                add R_IP, 2
                movzx ecx, al ; 0000 w[1] reg[3]
                and ecx, 0011b
                movzx ebx, al
                and ebx, 0100b
                shr ebx, 2
                mov byte ptr REGB[ecx * 2 + ebx], ah
                ret
    WordImmToReg:
                add R_IP, 3
                movzx ecx, al
                and ecx, 0111b
                mov ax, word ptr [ebx + 1]
                mov word ptr REGW[ecx * 2], ax
                ret
    MemWithAccumulator:
                add R_IP, 3
                movzx edx, word ptr [ebx + 1]
                movzx ecx, R_DS
                shl ecx, 4
                test al, 0010b ; 0 to accumulator
                jnz FromAccumulator
                mov bx, word ptr MEMO[edx + ecx] ; read 2 byte at once to reuse code, may exceed 1M, but we are in a emulator
                test al, 0001b ; w[1]
                jnz ToAX
                ; to AL
                mov R_AL, bl
                ret
    ToAX:
                mov R_AX, bx
                ret
    FromAccumulator:
                test al, 0001b ; w[1]
                jnz FromAX
                ; from AL
                mov bl, R_AL
                mov byte ptr MEMO[edx + ecx], bl
                ret
    FromAX:
                mov bx, R_AX
                mov word ptr MEMO[edx + ecx], bx
                ret

DataTransferMOV ENDP

; error case return address passed by ecx
; flat ip in ebx
; success will use ret
DataTransferStack PROC
                movzx eax, word ptr [ebx] ; read 2 byte at once, may exceed 1M, but we are in a emulator
                xor al, 01010000b
                test al, 11110000b ; high 4 0101
                jz Register ; 0101xxxx
                xor al, 01010110b ; equiv to xor 00000110b at once
                test al, 11100110b ; 000xx11x
                jz SegmentRegister
                xor al, 10001001b ; equiv to xor 10001111b at once
                jz PopRegOrMem
                xor al, 01110000b ; equiv to xor 11111111b at once
                jz PushRegOrMem
                jmp ecx
    Register:
                add R_IP, 1
                movzx esi, R_SP
                movzx ebx, R_SS
                shl ebx, 4

                movzx ecx, al
                and ecx, 0111b
                test al, 1000b
                jnz PopRegister
                ; push
                mov ax, word ptr REGW[ecx * 2]
                sub R_SP, 2 ; push word
                mov word ptr MEMO[ebx + esi - 2], ax
                ret
    PopRegister:
                ; pop
                mov ax, word ptr MEMO[ebx + esi]
                add R_SP, 2 ; pop word
                mov word ptr REGW[ecx * 2], ax
                ret
    SegmentRegister:
                add R_IP, 1
                movzx esi, R_SP
                movzx ebx, R_SS
                shl ebx, 4

                movzx ecx, al
                and ecx, 00011000b
                shr ecx, 2 ; not fully shift
                test al, 0001b
                jnz PopSegmentRegister
                ; push
                mov ax, word ptr REGS[ecx]
                sub R_SP, 2
                mov word ptr MEMO[ebx + esi - 2], ax
                ret
    PopSegmentRegister:
                ; pop
                mov ax, word ptr MEMO[ebx + esi]
                add R_SP, 2
                mov word ptr REGS[ecx], ax
                ret
    PopRegOrMem:
                test ah, 00111000b
                jnz NotMatch

                or al, 10000000b ; set flag for code reuse
                jmp EA_Compute
        PopRegOrMemEADone:
                mov ax, word ptr MEMO[ebx + esi]
                add R_SP, 2
                mov word ptr [edx], ax
                ret
    PushRegOrMem:
                xor ah, 00110000b
                test ah, 00111000b
                jnz NotMatch
                
                jmp EA_Compute
        PushRegOrMemEADone:
                mov ax, word ptr [edx]
                sub R_SP, 2
                mov word ptr MEMO[ebx + esi - 2], ax
                ret
    EA_Compute:
                or al, 0001b ; set lowest bit indicate word register in r/m, which is xor cleared on instruction type check
                computeEffectiveAddress EA_Done, 0, R_DS
        EA_Done:
                sub esi, ebx
                add R_IP, si
                movzx esi, R_SP
                movzx ebx, R_SS
                shl ebx, 4

                test al, 10000000b
                jz PushRegOrMemEADone
                jmp PopRegOrMemEADone
    NotMatch:
                jmp ecx
DataTransferStack ENDP

FlagInstruction PROC
                mov al, byte ptr [ebx]
                cmp al, 0F8h
                je ProcessClc
                cmp al, 0F9h
                je ProcessStc
                jmp ecx
    ProcessClc:
                add R_IP, 1
                modifyFlagsInstruction <clc>
                
                ret
    ProcessStc:
                add R_IP, 1
                modifyFlagsInstruction <stc>
                ret
FlagInstruction ENDP

XchgInstruction PROC
                movzx eax, word ptr [ebx] ; read 2 byte at once, may exceed 1M, but we are in a emulator
                xor al, 10000110b
                test al, 11111110b
                jz XchgRegOrMem
                xor al, 00010110b ; equiv to xor 10010000b
                test al, 11111000b
                jz XchgAX
                jmp ecx
XchgAX:         
                ; other bits in al already clear, only need to clear ah, but we just clear all
                add R_IP, 1
                mov bx, R_AX
                and eax, 0111b
                xchg bx, word ptr REGW[eax * 2]
                mov R_AX, bx
                ret
XchgRegOrMem:
                computeEffectiveAddress XchgRegOrMemEADone, 0, R_DS
XchgRegOrMemEADone:
                sub esi, ebx
                add R_IP, si
                ; now ebx, esi free

                shr ah, 2 ; not fully shift to eliminate index scaling
                movzx ecx, ah ; 00 mod[2] reg[3] x, moved before jump to reuse code

                test al, 0001b ; decide 16bit or 8bit register
                jnz XchgRegOrMemWord
                ; 8bit register
                and ecx, 0110b ; ecx = 00 mod[2] reg[3] x ; 0,2,4,6 -> ACDB
                movzx ebx, ah
                and ebx, 1000b ; 0 -> L, 1 -> H
                shr ebx, 3

                mov al, byte ptr [edx]
                xchg al, byte ptr REGB[ecx + ebx]
                mov byte ptr [edx], al
                ret
XchgRegOrMemWord:
                and ecx, 1110b
                mov ax, word ptr [edx]
                xchg ax, word ptr REGW[ecx]
                mov word ptr [edx], ax

XchgInstruction ENDP

computeEffectiveAddressUnitTest MACRO
                LOCAL callback, L1, L2, L3, L4, L5, L6
                mov ebx, offset MEMO
                mov byte ptr [ebx + 2], 0
                mov byte ptr [ebx + 3], 1
                mov ah, 00000110b
                push offset L1
                computeEffectiveAddress callback, 1, R_DS
    L1:
                mov ebx, offset MEMO
                mov byte ptr [ebx + 2], 10
                mov byte ptr [ebx + 3], 0
                mov ah, 00000110b
                push offset L2
                computeEffectiveAddress callback, 1, R_DS
    L2:
                mov ebx, offset MEMO
                mov ah, 00000000b
                mov R_BX, 4
                mov R_SI, 3
                push offset L3
                computeEffectiveAddress callback, 1, R_DS
    L3:
                mov ebx, offset MEMO
                mov ah, 10000000b
                mov byte ptr [ebx + 2], 10
                mov byte ptr [ebx + 3], 0
                mov R_BX, 4
                mov R_SI, 3
                push offset L4
                computeEffectiveAddress callback, 1, R_DS
    L4:
                mov ebx, offset MEMO
                mov ah, 00000101b
                mov R_DI, 5
                push offset L5
                computeEffectiveAddress callback, 1, R_DS
    L5:
                mov ebx, offset MEMO
                mov ah, 00000111b
                mov R_BX, 9
                push offset L6
                computeEffectiveAddress callback, 1, R_DS
    L6:
                ret
    callback:
                INVOKE printf, offset debugMsg, offset MEMO, edx, esi, 0
                ret
ENDM

include term.asm
include binloader.asm

main PROC
                INVOKE LoadBinaryIntoEmulator, ADDR MEMO, ADDR floppyPath
                INVOKE InitEmuScreen ; initialize terminal
                lea edi, [MEMO + 0b8000h]
                mov ecx, 80*25
                mov ax, 0
                rep lodsw          ; clrscr
ExecLoop:       
                ; draw video memory
                movzx eax, running
                mov esi, OFFSET statusRunning
                test eax, eax
                mov ebx, OFFSET statusPaused
                cmovz esi, ebx
                FOR x, <R_DI, R_SI, R_BP, R_SP, R_BX, R_DX, R_CX, R_AX>
                    movzx eax, x
                    push eax
                ENDM
                push esi
                push offset statusLineFmt1
                push offset lineBuf1
                call crt_sprintf
                add esp, 44

                FOR x, <R_DS, R_SS, R_CS, R_ES, R_FLAGS, R_IP>
                    movzx eax, x
                    push eax
                ENDM
                push offset statusLineFmt2
                push offset lineBuf2
                call crt_sprintf
                add esp, 32

                movzx eax, running
                test eax, eax
                jnz Exec_TextAttr_Running
                loadStatusColor paused
                jmp Exec_TextAttr_End
Exec_TextAttr_Running:
                loadStatusColor running
Exec_TextAttr_End:
                mov statusTextAttr, eax

                movzx eax, running
                test eax, eax
                jnz ExecRunning

                INVOKE WriteEmuScreen, ADDR [MEMO + 0b8000h]
                INVOKE WriteStatusLine, ADDR lineBuf1, ADDR lineBuf2, statusTextAttr
                ; check keyboard input
                INVOKE crt__getch
                cmp eax, 'c' ; 'c' is pressed
                jne RefreshedScreen
                mov running, 1
                jmp RefreshedScreen
ExecRunning:    
                add screenRefreshCounter, 1
                jno RefreshedScreen
                INVOKE WriteStatusLine, ADDR lineBuf1, ADDR lineBuf2, statusTextAttr
                INVOKE WriteEmuScreen, ADDR [MEMO + 0b8000h]
RefreshedScreen:
                ; execute next instruction
                pushad

                computeFlatIP
                movzx eax, byte ptr [ebx]
                cmp eax, 0F4h
                je EmulatorHalt
                push offset Executed

                mov ecx, OFFSET ExecIncDec
                jmp ArithLogic
ExecIncDec:
                mov ecx, OFFSET ExecControl
                jmp Arith_INC_DEC
ExecControl:    
                mov ecx, OFFSET ExecData
                jmp ControlTransfer
ExecData:       
                mov ecx, OFFSET ExecFlag
                jmp DataTransferMOV
ExecFlag:       
                mov ecx, OFFSET ExecPushPop
                jmp FlagInstruction
ExecPushPop:    
                mov ecx, OFFSET ExecXchg
                jmp DataTransferStack
ExecXchg:       
                mov ecx, OFFSET ExecUD
                jmp XchgInstruction
ExecUD:         
                mov ecx, MB_ICONERROR
                or ecx, MB_OK
                INVOKE MessageBox, NULL, ADDR UDMsg, ADDR UDMsgTitle, ecx
                add R_IP, 1 ; skip this opcode
                add esp, 4; pop offset Executed
Executed:
                popad
                jmp ExecLoop

EmulatorHalt:
                INVOKE WriteStatusLine, ADDR lineBuf1, ADDR lineBuf2, statusTextAttr
                INVOKE WriteEmuScreen, ADDR [MEMO + 0b8000h] ; update screen
                INVOKE MessageBox, NULL, ADDR haltMsg, ADDR haltMsgTitle, MB_OK
                INVOKE ExitProcess, 0
                ret
main ENDP
END main
```
\$\endgroup\$
2
  • \$\begingroup\$ This specific question is a popularity-contest, so code length is irrelevant and the only thing that matters is how many upvotes the answer gets. \$\endgroup\$ Commented Oct 31, 2021 at 15:58
  • \$\begingroup\$ Welcome to Code Golf! Even if code size was the scoring criterion on this particular challenge, as long as you make an effort to golf it it's allowed! Otherwise languages like Java would never be able to compete with, e.g., Perl or some of the custom golfing-oriented languages here. This answer looks really cool, by the way! \$\endgroup\$
    – rydwolf
    Commented Oct 31, 2021 at 17:36

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