    {-|
    Copyright  :  (C) 2022     , QBayLogic B.V.
    License    :  BSD2 (see the file LICENSE)
    Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>

    = Efficient bundling of ROM content with the compiled code

    Leveraging Template Haskell, the content for the ROM components in this module
    is stored alongside the compiled Haskell code. It covers use cases where passing
    the initial content as a 'Clash.Sized.Vector.Vec' turns out to be
    problematically slow.

    The data is stored efficiently, with very little overhead (worst-case 7%, often
    no overhead at all).

    Unlike "Clash.Prelude.ROM.File", "Clash.Prelude.ROM.Blob" generates practically
    the same HDL as "Clash.Prelude.ROM" and is compatible with all tools consuming
    the generated HDL.
    -}

    {-# LANGUAGE CPP #-}
    {-# LANGUAGE Trustworthy #-}

    {-# OPTIONS_HADDOCK show-extensions #-}

    module Clash.Prelude.ROM.Blob
      ( -- * Asynchronous ROM defined by a 'MemBlob'
        asyncRomBlob
      , asyncRomBlobPow2
        -- * Synchronous 'MemBlob' ROM synchronized to an arbitrary clock
      , romBlob
      , romBlobPow2
        -- * Creating and inspecting 'MemBlob'
      , MemBlob
      , createMemBlob
      , memBlobTH
      , unpackMemBlob
        -- * Internal
      , asyncRomBlob#
      )
    where

    import Data.Array (listArray)
    import Data.Array.Base (unsafeAt)
    import GHC.Stack (withFrozenCallStack)
    import GHC.TypeLits (KnownNat, type (^))

    import Clash.Annotations.Primitive (hasBlackBox)
    import qualified Clash.Explicit.ROM.Blob as E
    import Clash.Explicit.BlockRam.Blob (createMemBlob, memBlobTH)
    import Clash.Explicit.BlockRam.Internal (MemBlob(..), unpackMemBlob)
    import Clash.Promoted.Nat (natToNum)
    import Clash.Signal (hideClock, hideEnable, HiddenClock, HiddenEnable)
    import Clash.Signal.Internal (Signal)
    import Clash.Sized.Internal.BitVector (BitVector)
    import Clash.Sized.Internal.Unsigned (Unsigned)
    import Clash.XException (deepErrorX)

    -- | An asynchronous/combinational ROM with space for @n@ elements
    --
    -- === See also:
    --
    -- * See "Clash.Sized.Fixed#creatingdatafiles" and
    -- "Clash.Prelude.BlockRam#usingrams" for ideas on how to use ROMs and RAMs.
    asyncRomBlob
      :: Enum addr
      => MemBlob n m
      -- ^ ROM content, also determines the size, @n@, of the ROM
      --
      -- __NB__: __MUST__ be a constant
      -> addr
      -- ^ Read address @r@
      -> BitVector m
      -- ^ The value of the ROM at address @r@
    asyncRomBlob :: forall addr (n :: Nat) (m :: Nat).
    Enum addr =>
    MemBlob n m -> addr -> BitVector m
    asyncRomBlob = \MemBlob n m
    content addr
    rd -> MemBlob n m -> Int -> BitVector m
    forall (m :: Nat) (n :: Nat). MemBlob n m -> Int -> BitVector m
    asyncRomBlob# MemBlob n m
    content (addr -> Int
    forall a. Enum a => a -> Int
    fromEnum addr
    rd)
    {-# INLINE asyncRomBlob #-}

    -- | An asynchronous/combinational ROM with space for 2^@n@ elements
    --
    -- === See also:
    --
    -- * See "Clash.Sized.Fixed#creatingdatafiles" and
    -- "Clash.Prelude.BlockRam#usingrams" for ideas on how to use ROMs and RAMs.
    asyncRomBlobPow2
      :: KnownNat n
      => MemBlob (2^n) m
      -- ^ ROM content, also determines the size, 2^@n@, of the ROM
      --
      -- __NB__: __MUST__ be a constant
      -> Unsigned n
      -- ^ Read address @r@
      -> BitVector m
      -- ^ The value of the ROM at address @r@
    asyncRomBlobPow2 :: forall (n :: Nat) (m :: Nat).
    KnownNat n =>
    MemBlob (2 ^ n) m -> Unsigned n -> BitVector m
    asyncRomBlobPow2 = MemBlob (2 ^ n) m -> Unsigned n -> BitVector m
    forall addr (n :: Nat) (m :: Nat).
    Enum addr =>
    MemBlob n m -> addr -> BitVector m
    asyncRomBlob
    {-# INLINE asyncRomBlobPow2 #-}

    -- | asyncRomBlob primitive
    asyncRomBlob#
      :: forall m n
       . MemBlob n m
      -- ^ ROM content, also determines the size, @n@, of the ROM
      --
      -- __NB__: __MUST__ be a constant
      -> Int
      -- ^ Read address @r@
      -> BitVector m
      -- ^ The value of the ROM at address @r@
    asyncRomBlob# :: forall (m :: Nat) (n :: Nat). MemBlob n m -> Int -> BitVector m
    asyncRomBlob# content :: MemBlob n m
    content@MemBlob{} = Int -> BitVector m
    safeAt
      where
        szI :: Int
    szI = forall (n :: Nat) a. (Num a, KnownNat n) => a
    natToNum @n @Int
        arr :: Array Int (BitVector m)
    arr = (Int, Int) -> [BitVector m] -> Array Int (BitVector m)
    forall i e. Ix i => (i, i) -> [e] -> Array i e
    listArray (Int
    0,Int
    szIInt -> Int -> Int
    forall a. Num a => a -> a -> a
    -Int
    1) ([BitVector m] -> Array Int (BitVector m))
    -> [BitVector m] -> Array Int (BitVector m)
    forall a b. (a -> b) -> a -> b
    $ MemBlob n m -> [BitVector m]
    forall (n :: Nat) (m :: Nat). MemBlob n m -> [BitVector m]
    unpackMemBlob MemBlob n m
    content

        safeAt :: Int -> BitVector m
        safeAt :: Int -> BitVector m
    safeAt Int
    i =
          if (Int
    0 Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    <= Int
    i) Bool -> Bool -> Bool
    && (Int
    i Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    < Int
    szI) then
            Array Int (BitVector m) -> Int -> BitVector m
    forall i. Ix i => Array i (BitVector m) -> Int -> BitVector m
    forall (a :: Type -> Type -> Type) e i.
    (IArray a e, Ix i) =>
    a i e -> Int -> e
    unsafeAt Array Int (BitVector m)
    arr Int
    i
          else
            (HasCallStack => BitVector m) -> BitVector m
    forall a. HasCallStack => (HasCallStack => a) -> a
    withFrozenCallStack
              (String -> BitVector m
    forall a. (NFDataX a, HasCallStack) => String -> a
    deepErrorX (String
    "asyncRom: address " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ Int -> String
    forall a. Show a => a -> String
    show Int
    i String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++
                           String
    " not in range [0.." String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ Int -> String
    forall a. Show a => a -> String
    show Int
    szI String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    ")"))
    {-# ANN asyncRomBlob# hasBlackBox #-}
    -- See: https://github.com/clash-lang/clash-compiler/pull/2511
    {-# CLASH_OPAQUE asyncRomBlob# #-}

    -- | A ROM with a synchronous read port, with space for @n@ elements
    --
    -- * __NB__: Read value is delayed by 1 cycle
    -- * __NB__: Initial output value is /undefined/, reading it will throw an
    -- 'Clash.XException.XException'
    --
    -- === See also:
    --
    -- * See "Clash.Sized.Fixed#creatingdatafiles" and
    -- "Clash.Explicit.BlockRam#usingrams" for ideas on how to use ROMs and RAMs.
    romBlob
      :: forall dom addr m n
       . ( HiddenClock dom
         , HiddenEnable dom
         , Enum addr
         )
      => MemBlob n m
      -- ^ ROM content, also determines the size, @n@, of the ROM
      --
      -- __NB__: __MUST__ be a constant
      -> Signal dom addr
      -- ^ Read address @r@
      -> Signal dom (BitVector m)
      -- ^ The value of the ROM at address @r@ from the previous clock cycle
    romBlob :: forall (dom :: Domain) addr (m :: Nat) (n :: Nat).
    (HiddenClock dom, HiddenEnable dom, Enum addr) =>
    MemBlob n m -> Signal dom addr -> Signal dom (BitVector m)
    romBlob = (Enable dom
     -> MemBlob n m -> Signal dom addr -> Signal dom (BitVector m))
    -> MemBlob n m -> Signal dom addr -> Signal dom (BitVector m)
    forall (dom :: Domain) r.
    HiddenEnable dom =>
    (Enable dom -> r) -> r
    hideEnable ((Clock dom
     -> Enable dom
     -> MemBlob n m
     -> Signal dom addr
     -> Signal dom (BitVector m))
    -> Enable dom
    -> MemBlob n m
    -> Signal dom addr
    -> Signal dom (BitVector m)
    forall (dom :: Domain) r. HiddenClock dom => (Clock dom -> r) -> r
    hideClock Clock dom
    -> Enable dom
    -> MemBlob n m
    -> Signal dom addr
    -> Signal dom (BitVector m)
    forall (dom :: Domain) addr (m :: Nat) (n :: Nat).
    (KnownDomain dom, Enum addr) =>
    Clock dom
    -> Enable dom
    -> MemBlob n m
    -> Signal dom addr
    -> Signal dom (BitVector m)
    E.romBlob)
    {-# INLINE romBlob #-}

    -- | A ROM with a synchronous read port, with space for 2^@n@ elements
    --
    -- * __NB__: Read value is delayed by 1 cycle
    -- * __NB__: Initial output value is /undefined/, reading it will throw an
    -- 'Clash.XException.XException'
    --
    -- === See also:
    --
    -- * See "Clash.Sized.Fixed#creatingdatafiles" and
    -- "Clash.Explicit.BlockRam#usingrams" for ideas on how to use ROMs and RAMs.
    romBlobPow2
      :: forall dom m n
       . ( HiddenClock dom
         , HiddenEnable dom
         , KnownNat n
         )
      => MemBlob (2^n) m
      -- ^ ROM content, also determines the size, 2^@n@, of the ROM
      --
      -- __NB__: __MUST__ be a constant
      -> Signal dom (Unsigned n)
      -- ^ Read address @r@
      -> Signal dom (BitVector m)
      -- ^ The value of the ROM at address @r@ from the previous clock cycle
    romBlobPow2 :: forall (dom :: Domain) (m :: Nat) (n :: Nat).
    (HiddenClock dom, HiddenEnable dom, KnownNat n) =>
    MemBlob (2 ^ n) m
    -> Signal dom (Unsigned n) -> Signal dom (BitVector m)
    romBlobPow2 = (Enable dom
     -> MemBlob (2 ^ n) m
     -> Signal dom (Unsigned n)
     -> Signal dom (BitVector m))
    -> MemBlob (2 ^ n) m
    -> Signal dom (Unsigned n)
    -> Signal dom (BitVector m)
    forall (dom :: Domain) r.
    HiddenEnable dom =>
    (Enable dom -> r) -> r
    hideEnable ((Clock dom
     -> Enable dom
     -> MemBlob (2 ^ n) m
     -> Signal dom (Unsigned n)
     -> Signal dom (BitVector m))
    -> Enable dom
    -> MemBlob (2 ^ n) m
    -> Signal dom (Unsigned n)
    -> Signal dom (BitVector m)
    forall (dom :: Domain) r. HiddenClock dom => (Clock dom -> r) -> r
    hideClock Clock dom
    -> Enable dom
    -> MemBlob (2 ^ n) m
    -> Signal dom (Unsigned n)
    -> Signal dom (BitVector m)
    forall (dom :: Domain) (m :: Nat) (n :: Nat).
    (KnownDomain dom, KnownNat n) =>
    Clock dom
    -> Enable dom
    -> MemBlob (2 ^ n) m
    -> Signal dom (Unsigned n)
    -> Signal dom (BitVector m)
    E.romBlobPow2)
    {-# INLINE romBlobPow2 #-}