Guest User

Haskell Nes Cpu

a guest
Mar 27th, 2017
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. module Nes where
  2.  
  3. import Data.Word
  4. import Data.Int
  5. import Data.Bits (Bits, (.|.), (.&.), shiftL, shiftR, testBit, xor, clearBit, setBit)
  6. import Data.List (foldl')
  7. import Control.Monad (ap)
  8. import Data.Maybe (fromMaybe)
  9. import Numeric (showHex)
  10.  
  11. import qualified Data.Vector as V
  12. import qualified Data.Map as M
  13. import Data.Vector ((!), (!?), (//))
  14.  
  15. data Cpu = Cpu
  16.  { rom :: V.Vector Word8
  17.  , memory :: V.Vector Word8
  18.  , a :: Word8
  19.  , x :: Word8
  20.  , y :: Word8
  21.  , pc :: Word16
  22.  , sp :: Word8
  23.  , nReg :: Bool
  24.  , vReg :: Bool
  25.  -- EMPTY
  26.  , bReg :: Bool
  27.  , dReg :: Bool
  28.  , iReg :: Bool
  29.  , zReg :: Bool
  30.  , cReg :: Bool
  31.  } deriving (Eq)
  32.  
  33. bool2int False = 0
  34. bool2int True = 1
  35.  
  36. fromListBE :: (Num b, Bits b) => [Bool] -> b
  37. fromListBE = foldl' f 0
  38.   where
  39.     f i b = (i `shiftL` 1) .|. bool2int b
  40.  
  41. statusRegisters :: Cpu -> Word8
  42. statusRegisters = fromListBE . (ap registers) . pure
  43.   where registers = [nReg, vReg, const True, bReg, dReg, iReg, zReg, cReg]
  44.  
  45.  
  46. instance Show Cpu where
  47.   show cpu =
  48.     "A=$" ++ (showHex . a) cpu " X=$" ++ (showHex . x) cpu " Y=$" ++ (showHex . y) cpu "\n" ++
  49.     "SP=$" ++ (showHex . sp) cpu " PC=$" ++ (showHex . pc) cpu "\n" ++
  50.     "NV-BDIZC" ++ "\n" ++
  51.     (show . bool2int . nReg) cpu ++
  52.     (show . bool2int . vReg) cpu ++ "--" ++
  53.     (show . bool2int . dReg) cpu ++
  54.     (show . bool2int . iReg) cpu ++
  55.     (show . bool2int . zReg) cpu ++
  56.     (show . bool2int . cReg) cpu
  57.  
  58.  
  59.  
  60. data AddressMode
  61.   = Immediate -- Immediate
  62.   | ZeroPage  -- ZeroPage
  63.   | ZeroPageX -- ZeroPage Indexed
  64.   | ZeroPageY -- ZeroPage Indexed
  65.   | Absolute -- Absolute
  66.   | AbsoluteX -- Indexed
  67.   | AbsoluteY -- Indexed
  68.   | Implied -- Implied
  69.   | Relative -- Relative
  70.   | Indirect --  AbsoluteIndirect, Only used for JMP
  71.   | IndirectX -- PreIndexed Indirect
  72.   | IndirectY -- PostIndexed Indirect
  73.   deriving (Show, Eq)
  74.  
  75. mkCpuFromMemory :: [Word8] -> Cpu
  76. mkCpuFromMemory m = Cpu
  77.   { rom = V.fromList m
  78.   , memory = V.fromList m
  79.   , a = 0x00, x = 0x00, y = 0x00
  80.   , pc = 0x0000, sp = 0x00
  81.   , nReg = False, vReg = False, dReg = False, iReg = False, zReg = False, cReg = False, bReg = True
  82.   }
  83.  
  84. mkCpu :: Int -> Cpu
  85. mkCpu b = Cpu
  86.   { rom = (V.replicate b 0x00)
  87.   , memory = (V.replicate b 0x00)
  88.   , a = 0x00, x = 0x00, y = 0x00
  89.   , pc = 0x0000, sp = 0x00
  90.   , nReg = False, vReg = False, dReg = False, iReg = False, zReg = False, cReg = False, bReg = True
  91.   }
  92.  
  93. writeWord8 :: V.Vector Word8 -> Word16 -> Word8 -> V.Vector Word8
  94. writeWord8 mem address byte = mem // [(fromIntegral address, byte)]
  95.  
  96. readWord8 :: Cpu -> Word16 -> Word8
  97. readWord8 cpu address = (memory cpu) ! (fromIntegral address)
  98.  
  99. readWord16 :: Cpu -> Word16 -> Word16
  100. readWord16 cpu address = fromWord8s $ V.slice (fromIntegral address) 2 (memory cpu)
  101.  
  102. fromWord8s :: V.Vector Word8 -> Word16
  103. fromWord8s ws = foldr accum 0x0000 ws
  104.   where accum o a = (a `shiftL` 8) .|. fromIntegral o
  105.  
  106. getArgument :: Cpu -> AddressMode -> Maybe Word16
  107. -- TODO: change the type of this to be *ARGUMENT*, either an address or nothing
  108. getArgument cpu Implied = Nothing
  109. getArgument cpu Immediate = Just $ pc cpu
  110. getArgument cpu Absolute = Just $ readWord16 cpu (pc cpu)
  111. getArgument cpu ZeroPage = Just $ fromIntegral $ readWord8 cpu (pc cpu)
  112. getArgument cpu ZeroPageX = Just $ fromIntegral operand
  113.   where
  114.     operand = readWord8 cpu $ (pc cpu) + (fromIntegral . x) cpu
  115. getArgument cpu ZeroPageY = Just $ fromIntegral operand
  116.   where
  117.     operand = readWord8 cpu $ (pc cpu) + (fromIntegral . y) cpu
  118. getArgument cpu AbsoluteX = Just $ argVal + (fromIntegral . x) cpu
  119.   where
  120.     argVal = readWord16 cpu (pc cpu)
  121. getArgument cpu AbsoluteY = Just $ argVal + (fromIntegral . y) cpu
  122.   where
  123.     argVal = readWord16 cpu (pc cpu)
  124. getArgument cpu Relative = Just $ fromIntegral operand
  125.   where
  126.     argVal = readWord8 cpu (pc cpu)
  127.     argVal' = fromIntegral argVal :: Int8
  128.    operand = 1 + (fromIntegral (pc cpu)) + (fromIntegral argVal')
  129. getArgument cpu Indirect = Just $ readWord16 cpu argVal
  130.   where
  131.     argVal = readWord16 cpu (pc cpu)
  132. getArgument cpu IndirectX = Just $ readWord16 cpu xIndex
  133.   where
  134.     argVal = readWord8 cpu (pc cpu )
  135.     xIndex = fromIntegral argVal + (fromIntegral . x) cpu
  136. getArgument cpu IndirectY = Just $ preAddress + (fromIntegral . y) cpu
  137.   where
  138.     argVal = fromIntegral $ readWord8 cpu (pc cpu)
  139.     preAddress = readWord16 cpu argVal
  140.  
  141.  
  142. --instructions ::
  143. instructions = M.fromList
  144.   [
  145.   -- ADC
  146.     (0x69, (Immediate, adc))
  147.   , (0x65, (ZeroPage, adc))
  148.   , (0x75, (ZeroPageX, adc))
  149.   , (0x6d, (Absolute, adc))
  150.   , (0x7d, (AbsoluteX, adc))
  151.   , (0x79, (AbsoluteY, adc))
  152.   , (0x61, (IndirectX, adc))
  153.   , (0x71, (IndirectY, adc))
  154.   -- AND
  155.   , (0x29, (Immediate, andOp))
  156.   , (0x25, (ZeroPage, andOp))
  157.   , (0x35, (ZeroPageX, andOp))
  158.   , (0x2d, (Absolute, andOp))
  159.   , (0x3d, (AbsoluteX, andOp))
  160.   , (0x39, (AbsoluteY, andOp))
  161.   , (0x21, (IndirectX, andOp))
  162.   , (0x31, (IndirectY, andOp))
  163.   -- ASL
  164.   , (0x0a, (Implied, asl))
  165.   , (0x06, (ZeroPage, asl))
  166.   , (0x16, (ZeroPageX, asl))
  167.   , (0x0e, (Absolute, asl))
  168.   , (0x1e, (AbsoluteX, asl))
  169.   -- BCC
  170.   , (0x90, (Relative, bcc))
  171.   -- BCS
  172.   , (0xb0, (Relative, bcs))
  173.   -- BEQ
  174.   , (0xf0, (Relative, beq))
  175.   -- BIT
  176.   , (0x24, (ZeroPage, bit))
  177.   , (0x2c, (Absolute, bit))
  178.   ]
  179.  
  180. incPc :: Cpu -> Cpu
  181. incPc c = c { pc = 1 + pc c }
  182.  
  183. incPcByMode :: Cpu -> AddressMode -> Cpu
  184. incPcByMode c Implied = c
  185. incPcByMode c Immediate = c { pc = 1 + pc c }
  186. incPcByMode c ZeroPage = c { pc = 1 + pc c }
  187. incPcByMode c ZeroPageX = c { pc = 1 + pc c }
  188. incPcByMode c ZeroPageY = c { pc = 1 + pc c }
  189. incPcByMode c Absolute = c { pc = 2 + pc c }
  190. incPcByMode c AbsoluteX = c { pc = 2 + pc c }
  191. incPcByMode c AbsoluteY = c { pc = 2 + pc c }
  192. incPcByMode c Indirect = c { pc = 2 + pc c }
  193. incPcByMode c IndirectX = c { pc = 1 + pc c }
  194. incPcByMode c IndirectY = c { pc = 1 + pc c }
  195. incPcByMode c Relative = c { pc = 1 + pc c }
  196.  
  197. data CpuError
  198.   = OpcodeNotImplemented Word8
  199.   | InvalidArgument String
  200.   deriving (Eq)
  201.  
  202. instance Show CpuError where
  203.    show (OpcodeNotImplemented opcode) = "OpcodeNotImplemented 0x" ++ (showHex opcode) ""
  204.    show (InvalidArgument inst) = "InvalidArgument for " ++ inst
  205.  
  206. lookupOpcode :: Word8 -> Either CpuError (AddressMode, Cpu -> Maybe Word16 -> Either CpuError Cpu)
  207. lookupOpcode o =
  208.   case M.lookup o instructions of
  209.     Nothing -> Left $ OpcodeNotImplemented o
  210.     Just mf -> Right mf
  211.  
  212. updateCpuM :: Cpu -> Either CpuError Cpu
  213. updateCpuM c = do
  214.   opcode <- return $ readWord8 c (pc c)
  215.   (mode, function) <- lookupOpcode opcode
  216.   c' <- return $ incPc c
  217.  argument <- return $ getArgument c' mode
  218.   c'' <- return $ incPcByMode c' mode
  219.  function c'' argument
  220.  
  221.  
  222. setZeroFlag :: Word8 -> Bool
  223. setZeroFlag 0x00 = True
  224. setZeroFlag _ = False
  225.  
  226. setCarryFlagAdd :: Word8 -> Word8 -> Bool
  227. setCarryFlagAdd x y =
  228.  testBit (x' + y') 8
  229.  where
  230.    x' = fromIntegral x
  231.     y' = fromIntegral y
  232.    s = (x' + y') :: Word16
  233.  
  234. setNegativeFlag :: Word8 -> Bool
  235. setNegativeFlag b = testBit b 7
  236.  
  237. setOverflowFlag :: Word8 -> Word8 -> Word8 -> Bool
  238. setOverflowFlag a b s = 0 /= ((a `xor` s) .&. (b `xor` s)) .&. 0x80
  239.  
  240. adc :: Cpu -> Maybe Word16 -> Either CpuError Cpu
  241. adc c (Just address) = Right $
  242.  c { a = a'
  243.     , zReg = setZeroFlag a'
  244.    , cReg = setCarryFlagAdd a1 a2
  245.    , nReg = setNegativeFlag a'
  246.     , vReg = setOverflowFlag a1 a2 a'
  247.    }
  248.  where
  249.    a' = a1 + a2 + (bool2int . cReg) c
  250.     a1 = readWord8 c address
  251.     a2 = a c
  252. adc _ Nothing = Left $ InvalidArgument "ADC"
  253.  
  254. andOp :: Cpu -> Maybe Word16 -> Either CpuError Cpu
  255. andOp c (Just address) = Right $
  256.   c { a = res
  257.     , zReg = setZeroFlag res
  258.     , nReg = setNegativeFlag res
  259.     }
  260.   where
  261.     arg = readWord8 c address
  262.     res = (a c) .&. arg
  263. andOp _ Nothing = Left $ InvalidArgument "AND"
  264.  
  265. asl :: Cpu -> Maybe Word16 -> Either CpuError Cpu
  266. asl c Nothing = Right $
  267.   c { a = a'
  268.    , cReg = testBit (a c) 7
  269.    , zReg = setZeroFlag a'
  270.     , nReg = setNegativeFlag a'
  271.    }
  272.  where
  273.    a' = (a c) `shiftL` 1
  274. asl c (Just address) = Right $
  275.   c { memory = m
  276.     , cReg = testBit arg 7
  277.     , zReg = setZeroFlag (a c)
  278.     , nReg = setNegativeFlag shifted
  279.     }
  280.   where
  281.     arg = readWord8 c address
  282.     shifted = arg `shiftL` 1
  283.     m = writeWord8 (memory c) address shifted
  284.  
  285. bcc :: Cpu -> Maybe Word16 -> Either CpuError Cpu
  286. bcc c (Just address) = Right $
  287.   if (cReg c)
  288.   then c
  289.   else c { pc = address }
  290. bcc _ Nothing = Left $ InvalidArgument "BCC"
  291.  
  292. bcs :: Cpu -> Maybe Word16 -> Either CpuError Cpu
  293. bcs c (Just address) = Right $
  294.   if (cReg c)
  295.   then c { pc = address }
  296.   else c
  297. bcs _ Nothing = Left $ InvalidArgument "BCS"
  298.  
  299. beq :: Cpu -> Maybe Word16 -> Either CpuError Cpu
  300. beq c (Just address) = Right $
  301.   if (zReg c)
  302.   then c { pc = address }
  303.   else c
  304. beq _ Nothing = Left $ InvalidArgument "BEQ"
  305.  
  306. bit :: Cpu -> Maybe Word16 -> Either CpuError Cpu
  307. bit c (Just address) = Right $
  308.   c { zReg = setZeroFlag result
  309.     , vReg = testBit arg 6
  310.     , nReg = setNegativeFlag arg
  311.     }
  312.   where
  313.     arg = readWord8 c address
  314.     result = (a c) .&. arg
  315. bit _ Nothing = Left $ InvalidArgument "BIT"
Advertisement
Add Comment
Please, Sign In to add comment