packages feed

retroclash-sim-0.1.0: src/RetroClash/Sim/VGA.hs

{-# LANGUAGE NumericUnderscores, RecordWildCards #-}
{-# LANGUAGE RankNTypes #-}
module RetroClash.Sim.VGA
    ( vgaSink
    , initSink
    , SinkState
    ) where

import RetroClash.Utils
import RetroClash.VGA

import Control.Monad.State
import Data.Foldable (for_)
import Control.Lens hiding (Index)
import Clash.Prelude hiding (lift)
import Data.Proxy

import Debug.Trace

vgaRetrace :: VGATiming visible -> (Int, Bit)
vgaRetrace VGATiming{..} = (snatToNum pulseWidth + snatToNum postWidth - 1, toActiveDyn polarity True)

data SinkState n
    = Visible (Index n)
    | WaitSync Bool
    | Retrace Int
    deriving (Show)

{-# INLINE vgaSink #-}
vgaSink
    :: forall w h rgb m ps. (KnownNat w, KnownNat h, MonadState (SinkState w, SinkState h) m)
    => VGATimings ps w h
    -> (Index w -> Index h -> rgb -> m ())
    -> (Bit, Bit, rgb)
    -> m Bool
vgaSink VGATimings{..} paint (hsync0, vsync0, color) = do
    (x, endLine) <- stateZoom _1 $ direction w horizRetrace hsync
    (y, endFrame) <- stateZoomIf endLine _2 $ direction h vertRetrace vsync
    for_ (liftA2 (,) x y) $ \(x, y) -> paint x y color
    return $ endLine && endFrame
  where
    (horizRetrace, hsyncTarget) = vgaRetrace vgaHorizTiming
    (vertRetrace, vsyncTarget) = vgaRetrace vgaVertTiming

    vsync = vsync0 == vsyncTarget
    hsync = hsync0 == hsyncTarget

    w = snatToNum (SNat @w)
    h = snatToNum (SNat @h)

    direction vis retrace sync s = case s of
        Retrace n -> ((Nothing, False), s')
          where
            n' = n + 1
            s' = if n' == retrace then Visible 0 else Retrace n'
        Visible i -> ((Just i, False), s')
          where
            s' = maybe (WaitSync sync) Visible $ succIdx i
        WaitSync prevSync -> ((Nothing, end), s')
          where
            end = not prevSync && sync
            s' = if end then Retrace 0 else WaitSync sync

initSink :: (SinkState w, SinkState h)
initSink = (WaitSync False, WaitSync False)

{-# INLINE stateZoom #-}
stateZoom :: (MonadState s0 m) => Lens' s0 s -> (s -> (a, s)) -> m a
stateZoom = stateZoomIf True

{-# INLINE stateZoomIf #-}
stateZoomIf :: (MonadState s0 m) => Bool -> Lens' s0 s -> (s -> (a, s)) -> m a
stateZoomIf update l f = state $ \s0 ->
  let s = view l s0
      (x, s') = f s
      s0' = s0 & l .~ s'
  in (x, if update then s0' else s0)