soyuz-0.0.0: DCPU16/Instructions.hs
{-# 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