{-# LANGUAGE BangPatterns        #-}
{-# LANGUAGE CPP                 #-}
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE MagicHash           #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications    #-}
{-# LANGUAGE TypeFamilies        #-}
{-# LANGUAGE TypeOperators       #-}
{-# LANGUAGE Trustworthy #-}
{-# OPTIONS_GHC -fno-cpr-anal #-}
{-# OPTIONS_HADDOCK show-extensions #-}
module Clash.Explicit.RAM
  ( 
    asyncRam
  , asyncRamPow2
    
  , asyncRam#
  )
where
import Data.Maybe            (fromJust, isJust)
import GHC.Stack             (HasCallStack, withFrozenCallStack)
import GHC.TypeLits          (KnownNat)
import qualified Data.Vector as V
import Clash.Explicit.Signal ((.&&.), unbundle, unsafeSynchronizer)
import Clash.Promoted.Nat    (SNat (..), snatToNum, pow2SNat)
import Clash.Signal.Internal (Clock (..), Signal (..), clockEnable)
import Clash.Sized.Unsigned  (Unsigned)
import Clash.XException      (errorX)
asyncRamPow2
  :: forall wdom rdom wgated rgated n a
   . (KnownNat n, HasCallStack)
  => Clock wdom wgated
  
  -> Clock rdom rgated
  
  -> Signal rdom (Unsigned n)
  
  -> Signal wdom (Maybe (Unsigned n, a))
  
  -> Signal rdom a
  
asyncRamPow2 = \wclk rclk rd wrM -> withFrozenCallStack
  (asyncRam wclk rclk (pow2SNat (SNat @ n)) rd wrM)
{-# INLINE asyncRamPow2 #-}
asyncRam
  :: (Enum addr, HasCallStack)
  => Clock wdom wgated
   
  -> Clock rdom rgated
   
  -> SNat n
  
  -> Signal rdom addr
  
  -> Signal wdom (Maybe (addr, a))
  
  -> Signal rdom a
   
asyncRam = \wclk rclk sz rd wrM ->
  let en       = isJust <$> wrM
      (wr,din) = unbundle (fromJust <$> wrM)
  in  withFrozenCallStack
      (asyncRam# wclk rclk sz (fromEnum <$> rd) en (fromEnum <$> wr) din)
{-# INLINE asyncRam #-}
asyncRam#
  :: HasCallStack
  => Clock wdom wgated
  
  -> Clock rdom rgated
  
  -> SNat n            
  -> Signal rdom Int  
  -> Signal wdom Bool 
  -> Signal wdom Int  
  -> Signal wdom a    
  -> Signal rdom a    
asyncRam# wclk rclk sz rd en wr din =
    unsafeSynchronizer wclk rclk dout
  where
    rd'  = unsafeSynchronizer rclk wclk rd
    ramI = V.replicate
              (snatToNum sz)
              (withFrozenCallStack (errorX "asyncRam#: initial value undefined"))
    en'  = case clockEnable wclk of
             Nothing  -> en
             Just wgt -> en .&&. wgt
    dout = go ramI rd' en' wr din
    go :: V.Vector a -> Signal wdom Int -> Signal wdom Bool
       -> Signal wdom Int -> Signal wdom a -> Signal wdom a
    go !ram (r :- rs) (e :- es) (w :- ws) (d :- ds) =
      let ram' = upd ram e w d
          o    = ram V.! r
      in  o :- go ram' rs es ws ds
    upd ram True  addr d = ram V.// [(addr,d)]
    upd ram False _    _ = ram
{-# NOINLINE asyncRam# #-}