Stellarator/src/Emulation.hs
2019-06-02 13:41:08 -07:00

1177 lines
31 KiB
Haskell

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ApplicativeDo #-}
module Emulation where
import Asm hiding (a, s)
import Atari2600
import System.IO.Unsafe
import Control.Lens hiding (set, op, index)
import Control.Monad.Reader
import Data.Maybe
import Data.Array.IO hiding (index)
import Data.Bits hiding (bit)
import Data.ByteString hiding (putStrLn, putStr)
import Data.IORef
import Data.Int
import CPU
import Sound.ProteaAudio
import Data.Word
import DebugState
import Control.Concurrent
import Disasm hiding (make16)
import Display
import Foreign.Ptr
import Stella
import Memory
import Metrics
import Numeric
import Prelude hiding (last, and)
import System.Clock
-- import VideoOps hiding (bit)
import qualified Graphics.Rendering.OpenGL as GL
-- import qualified SDL
import Graphics.UI.GLFW hiding (getTime)
#if TRACE
import Data.Array.Storable
#endif
readMemory :: Word16 -> MonadAtari Word8
writeMemory :: Word16 -> Word8 -> MonadAtari ()
illegal :: Word8 -> MonadAtari ()
--
-- {-# INLINE readMemory #-}
readMemory addr' = do
let addr = addr' .&. 0x1fff -- 6507
byte <- pureReadMemory (memoryType addr) addr
atari <- ask
let bankStateRef = atari ^. bankState
liftIO $ modifyIORef bankStateRef $ bankSwitch addr 0
return byte
-- {-# INLINE writeMemory #-}
writeMemory addr' v = do
let addr = addr' .&. 0x1fff -- 6507
pureWriteMemory (memoryType addr) addr v
atari <- ask
let bankStateRef = atari ^. bankState
liftIO $ modifyIORef bankStateRef $ bankSwitch addr v
-- {-# INLINE tick #-}
tick :: Int -> MonadAtari ()
tick n = do
modifyClock id (+ fromIntegral n)
-- c <- useClock id
stellaTickFor (3*n)
-- {-# INLINE debugStr #-}
debugStr _ _ = return ()
-- {-# INLINE debugStrLn #-}
debugStrLn _ _ = return ()
-- {- INLINE illegal -}
illegal i = do
dumpState
error $ "Illegal opcode 0x" ++ showHex i ""
debugStr :: Int -> String -> MonadAtari ()
debugStrLn :: Int -> String -> MonadAtari ()
-- {-# INLINE incPC #-}
incPC :: MonadAtari ()
incPC = addPC 1
-- {-# INLINABLE read16 #-}
read16 :: Word16 -> MonadAtari Word16
read16 addr = do
lo0 <- readMemory addr
hi0 <- readMemory (addr+1)
return $ make16 lo0 hi0
-- {-# INLINABLE read16tick #-}
read16tick :: Word16 -> MonadAtari Word16
read16tick addr = do
tick 1
lo0 <- readMemory addr
tick 1
hi0 <- readMemory (addr+1)
return $ make16 lo0 hi0
-- {-# INLINABLE read16zpTick #-}
read16zpTick :: Word8 -> MonadAtari Word16
read16zpTick addr = do
tick 1
lo0 <- readMemory (i16 addr)
tick 1
hi0 <- readMemory (i16 addr+1)
return $ make16 lo0 hi0
-- http://www.emulator101.com/6502-addressing-modes.html
-- Note, a 6502 performs a read or write *every* clock cycle
-- regardless of what instruction is being executed.
-- 6 clock cycles...
-- {-# INLINABLE writeIndX #-}
writeIndX :: Word8 -> MonadAtari ()
writeIndX src = do
tick 1
index <- getX
addr <- getPC >>= readMemory
tick 1
discard $ readMemory (i16 addr)
addrX <- read16zpTick (addr+index)
tick 1
writeMemory addrX src
incPC
-- 3 clock cycles
-- {-# INLINABLE writeZeroPage #-}
writeZeroPage :: Word8 -> MonadAtari ()
writeZeroPage src = do
tick 1
addr <- getPC >>= readMemory
tick 1
writeMemory (i16 addr) src
incPC
-- 4 clock cycles
-- {-# INLINABLE writeAbs #-}
writeAbs :: Word8 -> MonadAtari()
writeAbs src = do
addr <- getPC >>= read16tick
tick 1
writeMemory addr src
addPC 2
-- 6 clock cycles
-- {-# INLINABLE writeIndY #-}
writeIndY :: Word8 -> MonadAtari ()
writeIndY src = do
tick 1
index <- getY
addr' <- getPC >>= readMemory
addr <- read16zpTick addr'
let (halfAddrY, addrY) = halfSum addr index
tick 1
discard $ readMemory halfAddrY
tick 1
writeMemory addrY src
incPC
-- 4 clock cycles
-- {-# INLINABLE writeZeroPageX #-}
writeZeroPageX :: Word8 -> MonadAtari ()
writeZeroPageX src = do
tick 1
index <- getX
addr <- getPC >>= readMemory
tick 1
discard $ readMemory (i16 addr)
tick 1
writeMemory (i16 $ addr+index) src
incPC
-- 4 clock cycles
-- {-# INLINABLE writeZeroPageY #-}
writeZeroPageY :: Word8 -> MonadAtari ()
writeZeroPageY src = do
tick 1
index <- getY
addr <- getPC >>= readMemory
tick 1
discard $ readMemory (i16 addr)
tick 1
writeMemory (i16 $ addr+index) src
incPC
-- 5 clock cycles
-- {-# INLINABLE writeAbsY #-}
writeAbsY :: Word8 -> MonadAtari ()
writeAbsY src = do
index <- getY
addr <- getPC >>= read16tick
tick 1
let (halfAddrY, addrY) = halfSum addr index
discard $ readMemory halfAddrY
tick 1
writeMemory addrY src
addPC 2
-- 5 clock cycles
-- {-# INLINABLE writeAbsX #-}
writeAbsX :: Word8 -> MonadAtari ()
writeAbsX src = do
index <- getX
addr <- getPC >>= read16tick
tick 1
let (halfAddrX, addrX) = halfSum addr index
discard $ readMemory halfAddrX
tick 1
writeMemory addrX src
addPC 2
-- 6 clock cycles
-- {-# INLINABLE readIndX #-}
readIndX :: MonadAtari Word8
readIndX = do
tick 1
index <- getX
addr0 <- getPC >>= readMemory
tick 1
discard $ readMemory (i16 addr0)
addr1 <- read16zpTick (addr0+index)
tick 1
incPC
readMemory addr1
-- 3 clock cycles
-- {-# INLINABLE readZeroPage #-}
readZeroPage :: MonadAtari Word8
readZeroPage = do
tick 1
addr <- getPC >>= readMemory
tick 1
src <- readMemory (i16 addr)
incPC
return src
-- 2 clock cycles
-- {-# INLINABLE readImm #-}
readImm :: MonadAtari Word8
readImm = do
tick 1
src <- getPC >>= readMemory
incPC
return src
-- XXX consider applicable ops like *>
-- 4 clock cycles
-- {-# INLINABLE readAbs #-}
readAbs :: MonadAtari Word8
readAbs = do
p0 <- getPC
src <- (read16tick p0 <* tick 1) >>= readMemory
addPC 2
return src
-- 5-6 clock cycles
-- {-# INLINABLE readIndY #-}
readIndY :: MonadAtari Word8
readIndY = do
tick 1
addr' <- getPC >>= readMemory
addr <- read16zpTick addr'
index <- getY
let (halfAddrY, addrY) = halfSum addr index
when (halfAddrY /= addrY) $ do
tick 1
discard $ readMemory halfAddrY
tick 1
src <- readMemory addrY
incPC
return src
-- 4 clock cycles
-- {-# INLINABLE readZeroPageX #-}
readZeroPageX :: MonadAtari Word8
readZeroPageX = do
tick 1
index <- getX
addr <- getPC >>= readMemory
tick 1
discard $ readMemory (i16 addr)
tick 1
incPC
readMemory (i16 $ addr+index)
-- 4 clock cycles
-- {-# INLINABLE readZeroPageY #-}
readZeroPageY :: MonadAtari Word8
readZeroPageY = do
tick 1
index <- getY
addr <- getPC >>= readMemory
tick 1
discard $ readMemory (i16 addr)
tick 1
incPC
readMemory (i16 $ addr+index)
-- 4-5 clock cycles
-- {-# INLINABLE readAbsX #-}
readAbsX :: MonadAtari Word8
readAbsX = do
index <- getX
addr <- getPC >>= read16tick
addPC 2
let (halfAddrX, addrX) = halfSum addr index
when (halfAddrX /= addrX) $ do
tick 1
discard $ readMemory halfAddrX
tick 1
readMemory addrX
-- 4-5 clock cycles
-- {-# INLINABLE readAbsY #-}
readAbsY :: MonadAtari Word8
readAbsY = do
index <- getY
addr <- getPC >>= read16tick
addPC 2
let (halfAddrY, addrY) = halfSum addr index
when ( halfAddrY /= addrY) $ do
tick 1
discard $ readMemory halfAddrY
tick 1
readMemory addrY
-- 2-4 clock cycles
-- {-# INLINABLE bra #-}
bra :: MonadAtari Bool -> Bool -> MonadAtari ()
bra getFlag value = do
tick 1
offset <- getPC >>= readMemory
f <- getFlag
incPC
when (value == f) $ do
tick 1
discard $ getPC >>= readMemory
oldP <- getPC
let (halfAddr, addr) = halfSignedSum oldP offset
when (halfAddr /= addr) $ do
tick 1
discard $ readMemory halfAddr
putPC addr
-- 2 clock cycles
-- {-# INLINABLE set #-}
set :: (Bool -> MonadAtari ()) -> Bool -> MonadAtari ()
set putFlag value = do
tick 1
discard $ getPC >>= readMemory
putFlag value
-- 2 clock cycles
-- {-# INLINABLE nop #-}
nop :: MonadAtari ()
nop = do
tick 1
discard $ getPC >>= readMemory
{-
-- 3 clock cycles. Undocumented.
-- {-# INLINABLE nop #-}
dop :: MonadAtari ()
nop = do
tick 1
discard $ getPC >>= readMemory
-}
-- 3 clock cycles
-- {-# INLINABLE jmp #-}
jmp :: MonadAtari ()
jmp = getPC >>= read16tick >>= putPC
-- 5 clock cycles
-- NB address wraps around in page XXX
-- Not correct here.
-- Looks like the torture test might not catch this.
-- Aha! That's why ALIGN is used before addresses!
-- {-# INLINABLE jmp_indirect #-}
jmp_indirect :: MonadAtari ()
jmp_indirect = do
getPC >>= read16tick >>= read16tick >>= putPC
-- {-# INLINABLE uselessly #-}
uselessly :: m () -> m ()
uselessly = id
-- 5 clock cycles
-- {-# INLINABLE withZeroPage #-}
withZeroPage :: (Word8 -> MonadAtari Word8) -> MonadAtari ()
withZeroPage op = do
tick 1
addr <- getPC >>= readMemory
tick 1
src <- readMemory (i16 addr)
tick 1
uselessly $ writeMemory (i16 addr) src
tick 1
op src >>= writeMemory (i16 addr)
incPC
-- 2 clock cycles
-- {-# INLINABLE withAcc #-}
withAcc :: (Word8 -> MonadAtari Word8) -> MonadAtari ()
withAcc op = do
tick 1
discard $ getPC >>= readMemory
getA >>= op >>= putA
-- 6 clock cycles
-- {-# INLINE withAbs #-}
withAbs :: (Word8 -> MonadAtari Word8) -> MonadAtari ()
withAbs op = do
addr <- getPC >>= read16tick
tick 1
src <- readMemory addr
tick 1
uselessly $ writeMemory addr src
tick 1
dst <- op src
addPC 2
writeMemory addr dst
-- 6 clock cycles
withZeroPageX :: (Word8 -> MonadAtari Word8) -> MonadAtari ()
withZeroPageX op = do
tick 1
index <- getX
addr <- getPC >>= readMemory
let addrX = addr+index
tick 1
discard $ readMemory (i16 addr)
tick 1
src <- readMemory (i16 addrX)
tick 1
writeMemory (i16 addrX) src
tick 1
dst <- op src
writeMemory (i16 addrX) dst
incPC
-- 7 clock cycles
-- {-# INLINE withAbsX #-}
withAbsX :: (Word8 -> MonadAtari Word8) -> MonadAtari ()
withAbsX op = do
p0 <- getPC
index <- getX
addr <- read16tick p0
let (halfAddrX, addrX) = halfSum addr index
tick 1
discard $ readMemory halfAddrX
tick 1
src <- readMemory addrX
tick 1
uselessly $ writeMemory addrX src
tick 1
addPC 2
dst <- op src
writeMemory addrX dst
-- 7 clock cycles
-- {-# INLINABLE brk #-}
brk :: MonadAtari ()
brk = do
tick 1
p0 <- getPC
incPC
discard $ readMemory p0
p1 <- getPC
incPC
tick 1
push $ hi p1
incPC
tick 1
push $ lo p1
putB True
incPC
tick 1
getP >>= push . (.|. 0x20) -- always on bit
putI True
read16tick 0xfffe >>= putPC -- irq/brk XXX
-- Am I using wrong address for IRQ. Should it be 0xfffe for IRQ, 0xfffa for NMI?
-- XXX not supported correctly for now
-- {-# INLINABLE irq #-}
irq :: MonadAtari ()
irq = do
fi <- getI
if not fi
then nmi False
else return ()
-- {-# INLINABLE push #-}
push :: Word8 -> MonadAtari ()
push v = do
sp <- getS
writeMemory (0x100+i16 sp) v
putS (sp-1)
-- {-# INLINABLE pull #-}
pull :: MonadAtari Word8
pull = do
sp <- getS
let sp' = sp+1
putS sp'
readMemory (0x100+i16 sp')
-- 3 clock cycles
-- {-# INLINABLE pha #-}
pha :: MonadAtari ()
pha = do
tick 1
discard $ getPC >>= readMemory
tick 1
getA >>= push
-- 3 clock cycles
-- {-# INLINABLE php #-}
php :: MonadAtari ()
php = do
tick 1
discard $ getPC >>= readMemory
tick 1
getP >>= push . (.|. 0x30)
-- 4 clock cycles
-- {-# INLINABLE plp #-}
plp :: MonadAtari ()
plp = do
tick 1
p0 <- getPC
discard $ readMemory p0
tick 1
s <- getS
discard $ readMemory (0x100+i16 s)
tick 1
pull >>= putP
-- 4 clock cycles
-- {-# INLINABLE pla #-}
pla :: MonadAtari ()
pla = do
tick 1
p0 <- getPC
discard $ readMemory p0
tick 1
s <- getS
discard $ readMemory (0x100+i16 s)
tick 1
pull >>= setNZ >>= putA
-- {-# INLINABLE nmi #-}
nmi :: Bool -> MonadAtari ()
nmi sw = do
p0 <- getPC
push $ hi p0
push $ lo p0
putB sw
getP >>= push . (.|. 0x20) -- always on bit
putI True
read16 0xfffe >>= putPC -- irq/brk XXX
tick 7
-- 6 clock cycles
-- {-# INLINABLE rti #-}
rti :: MonadAtari ()
rti = do
tick 1
p0 <- getPC
void $ readMemory p0
tick 1
s <- getS
discard $ readMemory (0x100 + fromIntegral s)
tick 1
pull >>= putP
make16 <$> (tick 1 >> pull) <*> (tick 1 >> pull) >>= putPC
-- 6 clock cycles
-- {-# INLINABLE jsr #-}
jsr :: MonadAtari ()
jsr = do
tick 1
p0 <- getPC
pcl <- readMemory p0
incPC
tick 1
s <- getS
discard $ readMemory (0x100 + fromIntegral s)
p2 <- getPC
tick 1
push $ hi p2
tick 1
push $ lo p2
tick 1
pch <- readMemory p2
putPC $ make16 pcl pch
-- 6 clock cycles
-- {-# INLINABLE rts #-}
rts :: MonadAtari ()
rts = do
tick 1
discard $ getPC >>= readMemory
tick 1
s <- getS
discard $ readMemory (0x100+i16 s)
p0 <- make16 <$> (tick 1 >> pull) <*> (tick 1 >> pull)
tick 1
discard $ readMemory p0
putPC (p0+1)
makeDelayArray:: [(Word16, Int)] -> IO (IOUArray Word16 Int)
makeDelayArray delayList = do
delayArray <- newArray (0, 0x2c) 0
forM_ delayList $ \(addr, d) -> writeArray delayArray addr d
return delayArray
initState :: Int -> Int -> Int -> Int ->
IOUArray Int Word8 ->
#if TRACE
StorableArray Int Word8 ->
#endif
BankState ->
IOUArray Int Word8 ->
Word16 ->
Window ->
GL.Program ->
GL.AttribLocation ->
GL.TextureObject ->
GL.TextureObject ->
Ptr Word8 ->
Ptr Word8 ->
[(Word16, Int)] ->
Controllers ->
IO Atari2600
initState xscale' yscale' width height ram'
#if TRACE
record'
#endif
initBankState rom' initialPC window prog attrib initTex initLastTex initTextureData initLastTextureData delayList controllerType = do
stellaDebug' <- newIORef DebugState.start
bankState' <- newIORef initBankState
t <- liftIO $ getTime Realtime
let nt = addTime t (1000000000 `div` 60)
nextFrameTime' <- newIORef nt
parity <- newIORef False
clock' <- newIORef 0
-- debug' <- newIORef 8
stellaClock' <- newIORef 0
#if TRACE
recordPtr' <- newIORef 0
#endif
boolArray' <- newArray (0, maxBool) False
intArray' <- newArray (0, maxInt) 0 -- Overkill
word64Array' <- newArray (0, maxWord64) 0
word16Array' <- newArray (0, maxWord16) 0 -- Overkill
word8Array' <- newArray (0, maxWord8) 0
liftIO $ st word16Array' pc initialPC
delayArray <- makeDelayArray delayList
return $ Atari2600 {
_frameParity = parity,
_nextFrameTime = nextFrameTime',
_xscale = xscale',
_yscale = yscale',
_windowWidth = width,
_windowHeight = height,
_rom = rom',
#if TRACE
_record = record',
_recordPtr = recordPtr',
#endif
_ram = ram',
_stellaDebug = stellaDebug',
_bankState = bankState',
_clock = clock',
_stellaClock = stellaClock',
_boolArray = boolArray',
_intArray = intArray',
_word64Array = word64Array',
_word16Array = word16Array',
_word8Array = word8Array',
_controllers = controllerType,
_sdlWindow = window,
_textureData = initTextureData,
_lastTextureData = initLastTextureData,
_tex = initTex,
_lastTex = initLastTex,
_glProg = prog,
_glAttrib = attrib,
_delays = delayArray
}
{-
Here's a standard kernel:
StartOfFrame
;--------------------------------------------------
; Start of vertical blank processing
;--------------------------------------------------
lda #0
sta VBLANK
lda #2
sta VSYNC
sta WSYNC
sta WSYNC
sta WSYNC ; 3 scanlines of VSYNC signal
lda #0
sta VSYNC
;--------------------------------------------------
; 37 scanlines of vertical blank...
;--------------------------------------------------
ldx #0
VerticalBlank sta WSYNC
inx
cpx #37
bne VerticalBlank
;--------------------------------------------------
; Do 192 scanlines of colour-changing (our picture)
;--------------------------------------------------
ldx #0 ; this counts our scanline number
...
Lines sta WSYNC
inx
cpx #192
bne Lines
;--------------------------------------------------
; 30 scanlines of overscan...
;--------------------------------------------------
lda #%01000010
sta VBLANK ; end of screen - enter blanking
ldx #0
Overscan sta WSYNC
inx
cpx #30
bne Overscan
jmp StartOfFrame
-}
-- Keyboard wiring
--
-- |key00 - D4 IN4|key01 - D4 IN1|key02 - D4 IN0|key03 - D4 IN5|key04 - D4 IN3|key05 - D4 IN2|
-- |key10 - D5 IN4|key11 - D5 IN1|key12 - D5 IN0|key13 - D5 IN5|key14 - D5 IN3|key15 - D5 IN2|
-- |key20 - D6 IN4|key21 - D6 IN1|key22 - D6 IN0|key23 - D6 IN5|key24 - D6 IN3|key25 - D6 IN2|
-- |key30 - D7 IN4|key31 - D7 IN1|key32 - D7 IN0|key33 - D7 IN5|key34 - D7 IN3|key35 - D7 IN2|
-- {- INLINE stellaVsync -}
stellaVsync :: Word8 -> MonadAtari ()
stellaVsync v = do
oldv <- load vsync
when (testBit oldv 1 && not (testBit v 1)) $ do
vpos @= 0
renderDisplay
vsync @= v
-- {-# INLINE pureReadRom #-}
-- | pureReadRom sees address in full 6507 range 0x0000-0x1fff
pureReadRom :: Word16 -> MonadAtari Word8
pureReadRom addr = do
atari <- ask
let m = atari ^. rom
let bankStateRef = atari ^. bankState
bankState' <- liftIO $ readIORef bankStateRef
let bankedAddress = bankAddress bankState' addr
liftIO $ readArray m bankedAddress
-- {-# INLINE pureWriteRom #-}
-- | pureWriteRom sees address in full 6507 range 0x0000-0x1fff
-- You can write to Super Chip "ROM"
pureWriteRom :: Word16 -> Word8 -> MonadAtari ()
pureWriteRom addr v = do
atari <- ask
let m = atari ^. rom
let bankStateRef = atari ^. bankState
bankState' <- liftIO $ readIORef bankStateRef
when (bankWritable bankState' addr) $ do
let bankedAddress = bankAddress bankState' addr
liftIO $ writeArray m bankedAddress v
-- {-# INLINE pureReadMemory #-}
-- | pureReadMemory expects an address in range 0x0000-0x1fff
-- The 'pure' refers to the fact that there are no side effects,
-- i.e. it won't trigger bank switching.
--
-- From http://atariage.com/forums/topic/27190-session-5-memory-architecture/
--
-- Atari 2600 Memory Map:
----------------------
-- $0000-002F TIA Primary Image
-- $0030-005F [shadow] TIA
-- $0060-007F [shadow-partial] TIA
-- $0080-00FF 128 bytes of RAM Primary Image (zero page image)
-- $0100-002F [shadow] TIA
-- $0130-005F [shadow] TIA
-- $0160-017F [shadow-partial] TIA
-- $0180-01FF [shadow] 128 bytes of RAM (CPU stack image)
-- $0200-022F [shadow] TIA
-- $0230-025F [shadow] TIA
-- $0260-027F [shadow-partial] TIA
-- $0280-029F 6532-PIA I/O ports and timer Primary image
-- $02A0-02BF [shadow] 6532-PIA
-- $02C0-02DF [shadow] 6532-PIA
-- $02D0-02FF [shadow] 6532-PIA
-- $0300-032F [shadow] TIA
-- $0330-035F [shadow] TIA
-- $0360-037F [shadow-partial] TIA
-- $0380-039F [shadow] 6532-PIA
-- $03A0-03BF [shadow] 6532-PIA
-- $03C0-03DF [shadow] 6532-PIA
-- $03E0-03FF [shadow] 6532-PIA
-- $0400-07FF [shadow] Repeat the pattern from $0000-03FF
-- $0800-0BFF [shadow] Repeat the pattern from $0000-03FF
-- $0C00-0FFF [shadow] Repeat the pattern from $0000-03FF
--
-- $1000-17FF Lower 2K Cartridge ROM (4K carts start here)
-- $1800-1FFF Upper 2K Cartridge ROM (2K carts go here)
pureReadMemory :: MemoryType -> Word16 -> MonadAtari Word8
pureReadMemory ROM addr = pureReadRom addr
pureReadMemory TIA addr = readStella (addr `mod` 0x30) -- surprising!
pureReadMemory RIOT addr = readStella (0x280+(addr .&. 0x1f))
pureReadMemory RAM addr = do
atari <- ask
let m = atari ^. ram
liftIO $ readArray m (iz addr .&. 0x7f)
-- {-# INLINE pureWriteMemory #-}
pureWriteMemory :: MemoryType -> Word16 -> Word8 -> MonadAtari ()
pureWriteMemory TIA addr v = writeStella (addr .&. 0x3f) v
pureWriteMemory RIOT addr v = writeStella (0x280+(addr .&. 0x1f)) v
pureWriteMemory ROM addr v = pureWriteRom addr v
pureWriteMemory RAM addr v = do
atari <- ask
let m = atari ^. ram
#if TRACE
let r = atari ^. record
i <- liftIO $ readIORef (atari ^. recordPtr)
#endif
let realAddress = iz addr .&. 0x7f
liftIO $ writeArray m realAddress v
#if TRACE
liftIO $ writeArray r i (i8 realAddress)
liftIO $ writeArray r (i+1) v
liftIO $ writeIORef (atari ^. recordPtr) (i+2)
#endif
-- {-# INLINABLE dumpMemory #-}
dumpMemory :: MonadAtari ()
dumpMemory = do
regPC <- getPC
b0 <- readMemory regPC
b1 <- readMemory (regPC+1)
b2 <- readMemory (regPC+2)
liftIO $ putStr $ "(PC) = "
liftIO $ putStr $ showHex b0 "" ++ " "
liftIO $ putStr $ showHex b1 "" ++ " "
liftIO $ putStrLn $ showHex b2 ""
let (_, mne, _) = disasm regPC [b0, b1, b2]
liftIO $ putStrLn $ mne
-- {-# INLINABLE dumpRegisters #-}
dumpRegisters :: MonadAtari ()
dumpRegisters = do
regPC <- getPC
liftIO $ putStr $ " pc = " ++ showHex regPC ""
regP <- getP
liftIO $ do
putStr $ " flags = " ++ showHex regP ""
putStr $ "(N=" ++ showHex ((regP `shift` (-7)) .&. 1) ""
putStr $ ",V=" ++ showHex ((regP `shift` (-6)) .&. 1) ""
putStr $ ",B=" ++ showHex (regP `shift` ((-4)) .&. 1) ""
putStr $ ",D=" ++ showHex (regP `shift` ((-3)) .&. 1) ""
putStr $ ",I=" ++ showHex (regP `shift` ((-2)) .&. 1) ""
putStr $ ",Z=" ++ showHex (regP `shift` ((-1)) .&. 1) ""
putStr $ ",C=" ++ showHex (regP .&. 1) ""
regA <- getA
liftIO $ putStr $ ") A = " ++ showHex regA ""
regX <- getX
liftIO $ putStr $ " X = " ++ showHex regX ""
regY <- getY
liftIO $ putStrLn $ " Y = " ++ showHex regY ""
regS <- getS
liftIO $ putStrLn $ " N = " ++ showHex regS ""
-- {-# INLINABLE dumpState #-}
dumpState :: MonadAtari ()
dumpState = do
dumpMemory
dumpRegisters
{-
- TIA Summary
6-bit Address
Address Name
76543210 Function
---+---------+-----------+------------------------------------------
00 | VSYNC | ......1. | vertical sync set-clear
01 | VBLANK | 11....1. | vertical blank set-clear
02 | WSYNC | strobe | wait for leading edge of horizontal blank
03 | RSYNC | strobe | reset horizontal sync counter
04 | NUSIZ0 | ..111111 | number-size player-missile 0
05 | NUSIZ1 | ..111111 | number-size player-missile 1
06 | COLUP0 | 1111111. | color-lum player 0
07 | COLUP1 | 1111111. | color-lum player 1
08 | COLUPF | 1111111. | color-lum playfield
09 | COLUBK | 1111111. | color-lum background
0A | CTRLPF | ..11.111 | control playfield ball size & collisions
0B | REFP0 | ....1... | reflect player 0
0C | REFP1 | ....1... | reflect player 1
0D | PF0 | 1111.... | playfield register byte 0
0E | PF1 | 11111111 | playfield register byte 1
0F | PF2 | 11111111 | playfield register byte 2
10 | RESP0 | strobe | reset player 0
11 | RESP1 | strobe | reset player 1
12 | RESM0 | strobe | reset missile 0
13 | RESM1 | strobe | reset missile 1
14 | RESBL | strobe | reset ball
15 | AUDC0 | ....1111 | audio control 0
16 | AUDC1 | ...11111 | audio control 1
17 | AUDF0 | ...11111 | audio frequency 0
18 | AUDF1 | ....1111 | audio frequency 1
19 | AUDV0 | ....1111 | audio volume 0
1A | AUDV1 | ....1111 | audio volume 1
1B | GRP0 | 11111111 | graphics player 0
1C | GRP1 | 11111111 | graphics player 1
1D | ENAM0 | ......1. | graphics (enable) missile 0
1E | ENAM1 | ......1. | graphics (enable) missile 1
1F | ENABL | ......1. | graphics (enable) ball
20 | HMP0 | 1111.... | horizontal motion player 0
21 | HMP1 | 1111.... | horizontal motion player 1
22 | HMM0 | 1111.... | horizontal motion missile 0
23 | HMM1 | 1111.... | horizontal motion missile 1
24 | HMBL | 1111.... | horizontal motion ball
25 | VDELP0 | .......1 | vertical delay player 0
26 | VDELP1 | .......1 | vertical delay player 1
27 | VDELBL | .......1 | vertical delay ball
28 | RESMP0 | ......1. | reset missile 0 to player 0
29 | RESMP1 | ......1. | reset missile 1 to player 1
2A | HMOVE | strobe | apply horizontal motion
2B | HMCLR | strobe | clear horizontal motion registers
2C | CXCLR | strobe | clear collision latches
---+---------+-----------+------------------------------------------
-}
samplesRef = unsafePerformIO (newIORef Nothing)
audioSamples = pack [truncate (127*sin(10*2*pi*t/1024)) | t <- [0..4095]]
doAudio v = do
x <- readIORef samplesRef
-- when (isJust x) $ void $ soundStop (fromJust x)
when (isNothing x) $ do
samples <- sampleFromMemoryPcm audioSamples 1 44100 8 (fromIntegral (0xf .&. 0xf::Word8) / 15.0)
writeIORef samplesRef (Just samples)
soundLoop samples 1 1 0 1 -- left volume, right volume, time difference between left and right, pitch factor for playback
-- {- INLINABLE writeStella -}
writeStella :: Word16 -> Word8 -> MonadAtari ()
writeStella addr v = do
when (addr <= 0x2c) $ do
delays' <- view delays
d <- liftIO $ readArray delays' addr
graphicsDelay d
case addr of
0x00 -> stellaVsync v -- VSYNC
0x01 -> stellaVblank v -- VBLANK
0x02 -> stellaWsync -- WSYNC
0x04 -> nusiz0 @= v -- NUSIZ0
0x05 -> nusiz1 @= v -- NUSIZ1
0x06 -> (pcStep @-> pcColup0) >> colup0 @= v -- COLUP0
0x07 -> (pcStep @-> pcColup1) >> colup1 @= v -- COLUP1
0x08 -> (pcStep @-> pcColupf) >> colupf @= v -- COLUPF
0x09 -> (pcStep @-> pcColubk) >> colubk @= v -- COLUBK
0x0a -> ctrlpf @= v >> makePlayfield -- CTRLPF
0x0b -> refp0 @= v -- REFP0
0x0c -> refp1 @= v -- REFP1
-- I'm sure I read delay should be 3 for PF registers
-- but that doesn't make sense to me.
-- See docs/adventure_pf_timing.txt
0x0d -> (pcStep @-> pcPf0) >> pf0 @= v >> makePlayfield -- PF0
0x0e -> (pcStep @-> pcPf1) >> pf1 @= v >> makePlayfield -- PF1
0x0f -> (pcStep @-> pcPf2) >> pf2 @= v >> makePlayfield -- PF2
0x10 -> (pcStep @-> pcResp0) >> hpos @-> ppos0 -- RESP0
0x11 -> (pcStep @-> pcResp1) >> hpos @-> ppos1 -- RESP1
0x12 -> (pcStep @-> pcResm0) >> hpos @-> mpos0 -- RESM0
0x13 -> (pcStep @-> pcResm1) >> hpos @-> mpos1 -- RESM1
0x14 -> (pcStep @-> pcResbl) >> load hpos >>= (return . max (picx+2)) >>= (bpos @=) -- RESBL
0x15 -> return () -- liftIO $ putStrLn $ "AUDC0 = " ++ showHex v ""
0x16 -> return () -- liftIO $ putStrLn $ "AUDC1 = " ++ showHex v ""
0x17 -> return () -- liftIO $ putStrLn $ "AUDF0 = " ++ showHex v ""
0x18 -> return () -- liftIO $ putStrLn $ "AUDF1 = " ++ showHex v ""
0x19 -> return () -- liftIO $ do
-- putStrLn $ "AUDV0 = " ++ showHex v ""
-- doAudio v
0x1a -> return () -- liftIO $ putStrLn $ "AUDV1 = " ++ showHex v ""
-- graphicsDelay of 1 chosen to stop spurious pixel in
-- "CCE" in Freeway.
0x1b -> do -- GRP0
newGrp0 @= v
newGrp1 @-> oldGrp1
0x1c -> do -- GRP1
newGrp1 @= v
newGrp0 @-> oldGrp0
newBall @-> oldBall
0x1d -> enam0 @= v -- ENAM0
0x1e -> enam1 @= v -- ENAM1
0x1f -> newBall @= testBit v 1 -- ENABL
0x20 -> hmp0 @= v -- HMP0
0x21 -> hmp1 @= v -- HMP1
0x22 -> hmm0 @= v -- HMM0
0x23 -> hmm1 @= v -- HMM1
0x24 -> hmbl @= v -- HMBL
0x25 -> delayP0 @= testBit v 0 -- VDELP0
0x26 -> delayP1 @= testBit v 0 -- VDELP1
0x27 -> delayBall @= testBit v 0 -- VDELBL
0x28 -> resmp0 @= v
0x29 -> resmp1 @= v
0x2a -> stellaHmove -- HMOVE
0x2b -> stellaHmclr -- HMCLR
0x2c -> stellaCxclr -- CXCLR
0x280 -> do
swcha @= v -- XXX just added
0x281 -> swacnt @= v
0x294 -> startIntervalTimerN 1 v
0x295 -> startIntervalTimerN 8 v
0x296 -> startIntervalTimerN 64 v
0x297 -> startIntervalTimerN 1024 v
_ -> return () -- liftIO $ putStrLn $ "writing TIA 0x" ++ showHex addr ""
renderDisplay :: MonadAtari ()
renderDisplay = do
window <- view sdlWindow
prog <- view glProg
attrib <- view glAttrib
parityRef <- view frameParity
parity <- liftIO $ readIORef parityRef
liftIO $ modifyIORef parityRef not
tex' <- view tex
lastTex' <- view lastTex
ptr <- view textureData
lastPtr <- view lastTextureData
windowWidth' <- view windowWidth
windowHeight' <- view windowHeight
liftIO $ if parity
then do
updateTexture tex' ptr
updateTexture lastTex' lastPtr
else do
updateTexture lastTex' ptr
updateTexture tex' lastPtr
liftIO $ draw windowWidth' windowHeight' prog attrib
waitUntilNextFrameDue
liftIO $ swapBuffers window
return ()
waitUntilNextFrameDue :: MonadAtari ()
waitUntilNextFrameDue = do
nextFrameTimeRef <- view nextFrameTime
nextFrameTime' <- liftIO $ readIORef nextFrameTimeRef
t <- liftIO $ getTime Realtime
let frameTimeAfter = addTime nextFrameTime' (1000000000 `div` fps)
liftIO $ writeIORef nextFrameTimeRef frameTimeAfter
let TimeSpec {sec=secondsToGo, nsec=nanosecondsToGo} = diffTimeSpec nextFrameTime' t
let timeToGo = fromIntegral secondsToGo+fromIntegral nanosecondsToGo/1e9 :: Double
when (nextFrameTime' `gtTime` t) $ do
let milliSecondsToGo = 1000.0 * timeToGo
-- liftIO $ SDL.delay $ floor milliSecondsToGo
liftIO $ threadDelay $ floor milliSecondsToGo
initHardware :: MonadAtari ()
initHardware = do
store inpt0 0x80
store inpt1 0x80
store inpt2 0x80
store inpt3 0x80
store inpt4 0x80
store inpt5 0x80
store swcha 0b11111111
store swacnt 0b00000000
store swchb 0b00001011
store xbreak (-1)
store ybreak (-1)
forM_ [0..3] $ \i-> forM_ [0..5] $ \j -> store (kbd i j) False
pclo <- readMemory 0x1ffc
pchi <- readMemory 0x1ffd
let initialPC = fromIntegral pclo+(fromIntegral pchi `shift` 8)
liftIO $ putStrLn $ "Starting at address: 0x" ++ showHex initialPC ""
store pc initialPC