content: {-|
  Copyright  :  (C) 2015-2016, University of Twente,
                    2017     , Google Inc.
                    2019     , Myrtle Software Ltd,
                    2021-2022, QBayLogic B.V.
  License    :  BSD2 (see the file LICENSE)
  Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
  
  ROMs
  -}
  
  {-# LANGUAGE CPP #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE RankNTypes #-}
  
  {-# LANGUAGE Trustworthy #-}
  
  {-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
  {-# OPTIONS_HADDOCK show-extensions #-}
  
  module Clash.Prelude.ROM
    ( -- * Asynchronous ROM
      asyncRom
    , asyncRomPow2
      -- * Synchronous ROM synchronized to an arbitrary clock
    , rom
    , romPow2
      -- * Internal
    , asyncRom#
    )
  where
  
  import           Data.Array           (listArray)
  import           Data.Array.Base      (unsafeAt)
  import           GHC.Stack            (withFrozenCallStack)
  import           GHC.TypeLits         (KnownNat, type (^))
  import           Prelude              hiding (length)
  
  import           Clash.Annotations.Primitive (hasBlackBox)
  import qualified Clash.Explicit.ROM   as E
  import           Clash.Signal
  import           Clash.Sized.Unsigned (Unsigned)
  import           Clash.Sized.Vector   (Vec, length, toList)
  
  import           Clash.XException     (NFDataX, 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.
  -- * A large 'Vec' for the content may be too inefficient, depending on how it
  -- is constructed. See 'Clash.Prelude.ROM.File.asyncRomFile' and
  -- 'Clash.Prelude.ROM.Blob.asyncRomBlob' for different approaches that scale
  -- well.
  asyncRom
    :: ( KnownNat n
       , Enum addr
       , NFDataX a
       )
    => Vec n a
    -- ^ ROM content, also determines the size, @n@, of the ROM
    --
    -- __NB__: __MUST__ be a constant
    -> addr
    -- ^ Read address @r@
    -> a
    -- ^ The value of the ROM at address @r@
  asyncRom = \content rd -> asyncRom# content (fromEnum rd)
  {-# INLINE asyncRom #-}
  
  -- | 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.
  -- * A large 'Vec' for the content may be too inefficient, depending on how it
  -- is constructed. See 'Clash.Prelude.ROM.File.asyncRomFilePow2' and
  -- 'Clash.Prelude.ROM.Blob.asyncRomBlobPow2' for different approaches that scale
  -- well.
  asyncRomPow2
    :: ( KnownNat n
       , NFDataX a
       )
    => Vec (2^n) a
    -- ^ ROM content
    --
    -- __NB__: __MUST__ be a constant
    -> Unsigned n
    -- ^ Read address @r@
    -> a
    -- ^ The value of the ROM at address @r@
  asyncRomPow2 = asyncRom
  {-# INLINE asyncRomPow2 #-}
  
  -- | asyncRom primitive
  asyncRom#
    :: forall n a
     . ( KnownNat n
       , NFDataX a
       )
    => Vec n a
    -- ^ ROM content, also determines the size, @n@, of the ROM
    --
    -- __NB__: __MUST__ be a constant
    -> Int
    -- ^ Read address @r@
    -> a
    -- ^ The value of the ROM at address @r@
  asyncRom# content = safeAt
    where
      szI = length content
      arr = listArray (0,szI-1) (toList content)
  
      safeAt :: Int -> a
      safeAt i =
        if (0 <= i) && (i < szI) then
          unsafeAt arr i
        else
          withFrozenCallStack
            (deepErrorX ("asyncRom: address " ++ show i ++
                         " not in range [0.." ++ show szI ++ ")"))
  -- See: https://github.com/clash-lang/clash-compiler/pull/2511
  {-# CLASH_OPAQUE asyncRom# #-}
  {-# ANN asyncRom# hasBlackBox #-}
  
  -- | 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.Prelude.BlockRam#usingrams"
  -- for ideas on how to use ROMs and RAMs.
  -- * A large 'Vec' for the content may be too inefficient, depending on how it
  -- is constructed. See 'Clash.Prelude.ROM.File.romFile' and
  -- 'Clash.Prelude.ROM.Blob.romBlob' for different approaches that scale well.
  rom
    :: forall dom n m a
     . ( NFDataX a
       , KnownNat n
       , KnownNat m
       , HiddenClock dom
       , HiddenEnable dom  )
    => Vec n a
    -- ^ ROM content, also determines the size, @n@, of the ROM
    --
    -- __NB__: __MUST__ be a constant
    -> Signal dom (Unsigned m)
    -- ^ Read address @r@
    -> Signal dom a
    -- ^ The value of the ROM at address @r@ from the previous clock cycle
  rom = hideEnable (hideClock E.rom)
  {-# INLINE rom #-}
  
  -- | 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.Prelude.BlockRam#usingrams"
  -- for ideas on how to use ROMs and RAMs.
  -- * A large 'Vec' for the content may be too inefficient, depending on how it
  -- is constructed. See 'Clash.Prelude.ROM.File.romFilePow2' and
  -- 'Clash.Prelude.ROM.Blob.romBlobPow2' for different approaches that scale
  -- well.
  romPow2
    :: forall dom n a
     . ( KnownNat n
       , NFDataX a
       , HiddenClock dom
       , HiddenEnable dom  )
    => Vec (2^n) a
    -- ^ ROM content
    --
    -- __NB__: __MUST__ be a constant
    -> Signal dom (Unsigned n)
    -- ^ Read address @r@
    -> Signal dom a
    -- ^ The value of the ROM at address @r@ from the previous clock cycle
  romPow2 = hideEnable (hideClock E.romPow2)
  {-# INLINE romPow2 #-}
  