{-# LANGUAGE DeriveDataTypeable #-} -- | Complete abstract description of the DCPU-16 instruction set. -- -- Based on Version 1.1 of the DCPU-16 Specification by Mojang, retrieved from 0x10c.com. -- -- Contains a trivial 'Label' extension, which isn't present in machine code -- but is useful for dealing with assembly. module DCPU16.Instructions ( Instruction(..) -- * Operands , Operand(..) , Register(..) , Word(..) -- * Operations , BasicOp(..) , NonBasicOp(..) ) where -- Fundamental types import Data.Word hiding (Word) import Data.Data import Data.ByteString -- Serialization import Data.Serialize import Data.Bits import Data.Maybe (fromMaybe) import Control.Applicative hiding (Const) -- | Abstract DCPU-16 instruction set. -- -- Can be read and written as machine code via the Serialize instance. data Instruction = Basic BasicOp Operand Operand | NonBasic NonBasicOp Operand | Data Word | Label ByteString -- ^ Not present in machine code, for assembler utility only. | Comment Bool String -- ^ Boolean true if comment is alone on its own line. deriving (Eq,Read,Show,Data,Typeable) data BasicOp = SET | ADD | SUB | MUL | DIV | MOD | SHL | SHR | AND | BOR | XOR | IFE | IFN | IFG | IFB deriving (Eq,Read,Show,Data,Typeable) data NonBasicOp = JSR | Reserved Word16 -- ^ Opcode not defined yet. deriving (Eq,Read,Show,Data,Typeable) -- | Values instructions operate on. -- -- Direct operands use the value passed to them. -- -- Indirect operands treat that value as an address for a specific word in memory. data Operand = Direct Register | Indirect Register -- ^ At address [register]. | Offset Word Register -- ^ At address [next word + register]. | Pop | Peek | Push | SP | PC | O -- ^ Overflow. | IndirectLiteral Word | DirectLiteral Word | ShortLiteral Word -- ^ Restricted to 0x00-0x1f, 5 bits. deriving (Eq,Read,Show,Data,Typeable) data Register = A|B|C|X|Y|Z|I|J deriving (Eq,Read,Show,Data,Typeable,Enum) -- | Constant data. -- -- Assembly may use adresses of labels to initialize such data: since the -- address may not be known immediately, the label extension is added. data Word = Const Word16 | LabelAddr ByteString deriving (Eq,Read,Show,Data,Typeable) -- Machine language encoding/decoding follows. -- -- Rather crude for now, needs refactoring to use a bit-aware packer. And -- general cleanup. -- | Machine code encoding/decoding. instance Serialize Instruction where put (Basic op a b) = do let (a',aw) = packOp a (b',bw) = packOp b putWord16be $ (((b' `shiftL` 6) .|. a') `shiftL` 4) .|. fromOpCode op maybe (return ()) put aw maybe (return ()) put bw put (NonBasic op a) = putNonBasic opCode a where opCode = case op of JSR->0x01; Reserved x->0x3f.&.x putNonBasic op a = do let (a',w) = packOp a putWord16be $ shiftL (shiftL a' 6 .|. op) 4 maybe (return ()) put w put (Data x) = put x put (Label s) = return () get = do w <- getWord16be let [b,a,op] = fmap (maskShr w) [(0xfc00,10), (0x03f0,4), (0x000f,0)] if op==0 then NonBasic (getNBCode a) <$> getOp b else Basic (toOpCode op) <$> getOp a <*> getOp b where maskShr w (mask,sh) = shiftR (w.&.mask) sh instance Serialize Word where put (LabelAddr s) = fail $ "can not serialize label address "++show s put (Const x) = putWord16be x get = Const <$> getWord16be getNBCode 0x01 = JSR getNBCode op = Reserved op -- | Parse 6-bit operand. getOp :: Word16 -> Get Operand getOp op | op<=0x17 = getRegMode op -- 0x18-0x1d are simple operands | op==0x1e = IndirectLiteral <$> get | op==0x1f = DirectLiteral <$> get | op>=0x20 = return $ (ShortLiteral . Const) (op-0x20) | otherwise = return $ toOperand op -- | Pack 6-bit operand and any additional word it has. packOp :: Operand -> (Word16, Maybe Word) packOp (Direct r) = (regId r, Nothing) packOp (Indirect r) = (0x08+regId r, Nothing) packOp (Offset off r) = (0x10+regId r, Just off) packOp (IndirectLiteral w) = (0x1e, Just w) packOp (DirectLiteral w) = (0x1f, Just w) packOp (ShortLiteral (Const w)) = (0x20+w, Nothing) packOp (ShortLiteral (LabelAddr s)) = error $ "can not serialize label address "++show s packOp o = (fromOperand o, Nothing) regId = fromIntegral . fromEnum getRegMode :: Word16 -> Get Operand getRegMode op | op .&. 0x10 == 0x10 = return (Indirect r) | op .&. 0x08 == 0x08 = do offset <- get return (Offset offset r) | otherwise = return (Direct r) where r = toEnum . fromIntegral $ op .&. 0x7 toOperand 0x18 = Pop; toOperand 0x19 = Peek; toOperand 0x1a = Push toOperand 0x1b = SP; toOperand 0x1c = PC; toOperand 0x1d = O fromOperand Pop = 0x18; fromOperand Peek = 0x19; fromOperand Push = 0x1a fromOperand SP = 0x1b; fromOperand PC = 0x1c; fromOperand O = 0x1d fromOpCode SET=0x1; fromOpCode ADD=0x2; fromOpCode SUB=0x3 fromOpCode MUL=0x4; fromOpCode DIV=0x5; fromOpCode MOD=0x6; fromOpCode SHL=0x7 fromOpCode SHR=0x8; fromOpCode AND=0x9; fromOpCode BOR=0xa; fromOpCode XOR=0xb fromOpCode IFE=0xc; fromOpCode IFN=0xd; fromOpCode IFG=0xe; fromOpCode IFB=0xf toOpCode 0x1=SET; toOpCode 0x2=ADD; toOpCode 0x3=SUB toOpCode 0x4=MUL; toOpCode 0x5=DIV; toOpCode 0x6=MOD; toOpCode 0x7=SHL toOpCode 0x8=SHR; toOpCode 0x9=AND; toOpCode 0xa=BOR; toOpCode 0xb=XOR toOpCode 0xc=IFE; toOpCode 0xd=IFN; toOpCode 0xe=IFG; toOpCode 0xf=IFB