{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeOperators    #-}
module CLaSH.Prelude.BlockRam where

import GHC.TypeLits           (KnownNat, type (^))
import Prelude                hiding ((!!))

import CLaSH.Prelude.Mealy    (cmealy)
import CLaSH.Signal           (Signal)
import CLaSH.Signal.Explicit  (CSignal, SClock, systemClock)
import CLaSH.Signal.Bundle    (bundle)
import CLaSH.Sized.Unsigned   (Unsigned)
import CLaSH.Sized.Vector     (Vec, (!!), replace)

{-# INLINE blockRam #-}
-- | Create a blockRAM with space for @n@ elements.
--
-- * __NB__: Read value is delayed by 1 cycle
-- * __NB__: Initial output value is 'undefined'
--
-- > bram40 :: Signal (Unsigned 6) -> Signal (Unsigned 6) -> Signal Bool
-- >        -> Signal Bit -> Signal Bit
-- > bram40 = blockRam (replicate d40 H)
blockRam :: (KnownNat n, KnownNat m)
         => Vec n a             -- ^ Initial content of the BRAM, also
                                -- determines the size, @n@, of the BRAM.
                                --
                                -- __NB__: __MUST__ be a constant.
         -> Signal (Unsigned m) -- ^ Write address @w@
         -> Signal (Unsigned m) -- ^ Read address @r@
         -> Signal Bool         -- ^ Write enable
         -> Signal a            -- ^ Value to write (at address @w@)
         -> Signal a
         -- ^ Value of the 'blockRAM' at address @r@ from the previous clock
         -- cycle
blockRam = cblockRam systemClock

{-# INLINE blockRamPow2 #-}
-- | Create a blockRAM with space for 2^@n@ elements
--
-- * __NB__: Read value is delayed by 1 cycle
-- * __NB__: Initial output value is 'undefined'
--
-- > bram32 :: Signal (Unsigned 5) -> Signal (Unsigned 5) -> Signal Bool
-- >        -> Signal Bit -> Signal Bit
-- > bram32 = blockRamPow2 (replicate d32 H)
blockRamPow2 :: (KnownNat (2^n), KnownNat n)
             => Vec (2^n) a         -- ^ Initial content of the BRAM, also
                                    -- determines the size, @2^n@, of the BRAM.
                                    --
                                    -- __NB__: __MUST__ be a constant.
             -> Signal (Unsigned n) -- ^ Write address @w@
             -> Signal (Unsigned n) -- ^ Read address @r@
             -> Signal Bool         -- ^ Write enable
             -> Signal a            -- ^ Value to write (at address @w@)
             -> Signal a
             -- ^ Value of the 'blockRAM' at address @r@ from the previous clock
             -- cycle
blockRamPow2 = blockRam

{-# NOINLINE cblockRam #-}
-- | Create a blockRAM with space for @n@ elements
--
-- * __NB__: Read value is delayed by 1 cycle
-- * __NB__: Initial output value is 'undefined'
--
-- > type ClkA = Clk "A" 100
-- >
-- > clkA100 :: SClock ClkA
-- > clkA100 = sclock
-- >
-- > bram40 :: CSignal ClkA (Unsigned 6) -> CSignal ClkA (Unsigned 6)
-- >        -> CSignal ClkA Bool -> CSignal ClkA Bit -> ClkA CSignal Bit
-- > bram40 = cblockRam clkA100 (replicate d40 H)
cblockRam :: (KnownNat n, KnownNat m)
          => SClock clk               -- ^ 'Clock' to synchronize to
          -> Vec n a                  -- ^ Initial content of the BRAM, also
                                      -- determines the size, @n@, of the BRAM.
                                      --
                                      -- __NB__: __MUST__ be a constant.
          -> CSignal clk (Unsigned m) -- ^ Write address @w@
          -> CSignal clk (Unsigned m) -- ^ Read address @r@
          -> CSignal clk Bool         -- ^ Write enable
          -> CSignal clk a            -- ^ Value to write (at address @w@)
          -> CSignal clk a
          -- ^ Value of the 'blockRAM' at address @r@ from the previous clock
          -- cycle
cblockRam clk binit wr rd en din =
    cmealy clk bram' (binit,undefined) (bundle clk (wr,rd,en,din))
  where
    bram' (ram,o) (w,r,e,d) = ((ram',o'),o)
      where
        ram' | e         = replace ram w d
             | otherwise = ram
        o'               = ram !! r

{-# INLINE cblockRamPow2 #-}
-- | Create a blockRAM with space for 2^@n@ elements
--
-- * __NB__: Read value is delayed by 1 cycle
-- * __NB__: Initial output value is 'undefined'
--
-- > type ClkA = Clk "A" 100
-- >
-- > clkA100 :: SClock ClkA
-- > clkA100 = sclock
-- >
-- > bramC32 :: CSignal ClkA (Unsigned 5) -> CSignal ClkA (Unsigned 5)
-- >         -> CSignal ClkA Bool -> CSignal ClkA Bit -> CSignal ClkA Bit
-- > bramC32 = cblockRamPow2 clkA100 (replicate d32 H)
cblockRamPow2 :: (KnownNat n, KnownNat (2^n))
              => SClock clk               -- ^ 'Clock' to synchronize to
              -> Vec (2^n) a              -- ^ Initial content of the BRAM, also
                                          -- determines the size, @2^n@, of
                                          -- the BRAM.
                                          --
                                          -- __NB__: __MUST__ be a constant.
              -> CSignal clk (Unsigned n) -- ^ Write address @w@
              -> CSignal clk (Unsigned n) -- ^ Read address @r@
              -> CSignal clk Bool         -- ^ Write enable
              -> CSignal clk a            -- ^ Value to write (at address @w@)
              -> CSignal clk a
              -- ^ Value of the 'blockRAM' at address @r@ from the previous
              -- clock cycle
cblockRamPow2 = cblockRam