{-|
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 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, errorX)

-- | An asynchronous/combinational ROM with space for @n@ elements
--
-- Additional helpful information:
--
-- * 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 might 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)
  => Vec n a
  -- ^ ROM content, also determines the size, @n@, of the ROM
  --
  -- __NB:__ must be a constant
  -> addr
  -- ^ Read address @rd@
  -> a
  -- ^ The value of the ROM at address @rd@
asyncRom :: Vec n a -> addr -> a
asyncRom = \Vec n a
content addr
rd -> Vec n a -> Int -> a
forall (n :: Nat) a. KnownNat n => Vec n a -> Int -> a
asyncRom# Vec n a
content (addr -> Int
forall a. Enum a => a -> Int
fromEnum addr
rd)
{-# INLINE asyncRom #-}

-- | An asynchronous/combinational ROM with space for 2^@n@ elements
--
-- Additional helpful information:
--
-- * 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 might 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
  => Vec (2^n) a
  -- ^ ROM content
  --
  -- __NB:__ must be a constant
  -> Unsigned n
  -- ^ Read address @rd@
  -> a
  -- ^ The value of the ROM at address @rd@
asyncRomPow2 :: Vec (2 ^ n) a -> Unsigned n -> a
asyncRomPow2 = Vec (2 ^ n) a -> Unsigned n -> a
forall (n :: Nat) addr a.
(KnownNat n, Enum addr) =>
Vec n a -> addr -> a
asyncRom
{-# INLINE asyncRomPow2 #-}

-- | asyncROM primitive
asyncRom#
  :: forall n a . KnownNat n
  => Vec n a
  -- ^ ROM content, also determines the size, @n@, of the ROM
  --
  -- __NB:__ must be a constant
  -> Int
  -- ^ Read address @rd@
  -> a
  -- ^ The value of the ROM at address @rd@
asyncRom# :: Vec n a -> Int -> a
asyncRom# Vec n a
content = Int -> a
safeAt
  where
    szI :: Int
szI = Vec n a -> Int
forall (n :: Nat) a. KnownNat n => Vec n a -> Int
length Vec n a
content
    arr :: Array Int a
arr = (Int, Int) -> [a] -> Array Int a
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) (Vec n a -> [a]
forall (n :: Nat) a. Vec n a -> [a]
toList Vec n a
content)

    safeAt :: Int -> a
    safeAt :: Int -> a
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 a -> Int -> a
forall (a :: Type -> Type -> Type) e i.
(IArray a e, Ix i) =>
a i e -> Int -> e
unsafeAt Array Int a
arr Int
i
      else
        (HasCallStack => a) -> a
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack
          (String -> a
forall a. HasCallStack => String -> a
errorX (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
")"))
{-# NOINLINE 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'
--
-- Additional helpful information:
--
-- * 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 might 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 @rd@
  -> Signal dom a
  -- ^ The value of the ROM at address @rd@
rom :: Vec n a -> Signal dom (Unsigned m) -> Signal dom a
rom = (Enable dom -> Vec n a -> Signal dom (Unsigned m) -> Signal dom a)
-> Vec n a -> Signal dom (Unsigned m) -> Signal dom a
forall (dom :: Symbol) r.
HiddenEnable dom =>
(Enable dom -> r) -> r
hideEnable ((Clock dom
 -> Enable dom
 -> Vec n a
 -> Signal dom (Unsigned m)
 -> Signal dom a)
-> Enable dom -> Vec n a -> Signal dom (Unsigned m) -> Signal dom a
forall (dom :: Symbol) r. HiddenClock dom => (Clock dom -> r) -> r
hideClock Clock dom
-> Enable dom -> Vec n a -> Signal dom (Unsigned m) -> Signal dom a
forall (dom :: Symbol) (n :: Nat) a addr.
(KnownDomain dom, KnownNat n, NFDataX a, Enum addr) =>
Clock dom
-> Enable dom -> Vec n a -> Signal dom addr -> Signal dom a
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'
--
-- Additional helpful information:
--
-- * 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 might 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 @rd@
  -> Signal dom a
  -- ^ The value of the ROM at address @rd@
romPow2 :: Vec (2 ^ n) a -> Signal dom (Unsigned n) -> Signal dom a
romPow2 = (Enable dom
 -> Vec (2 ^ n) a -> Signal dom (Unsigned n) -> Signal dom a)
-> Vec (2 ^ n) a -> Signal dom (Unsigned n) -> Signal dom a
forall (dom :: Symbol) r.
HiddenEnable dom =>
(Enable dom -> r) -> r
hideEnable ((Clock dom
 -> Enable dom
 -> Vec (2 ^ n) a
 -> Signal dom (Unsigned n)
 -> Signal dom a)
-> Enable dom
-> Vec (2 ^ n) a
-> Signal dom (Unsigned n)
-> Signal dom a
forall (dom :: Symbol) r. HiddenClock dom => (Clock dom -> r) -> r
hideClock Clock dom
-> Enable dom
-> Vec (2 ^ n) a
-> Signal dom (Unsigned n)
-> Signal dom a
forall (dom :: Symbol) (n :: Nat) a.
(KnownDomain dom, KnownNat n, NFDataX a) =>
Clock dom
-> Enable dom
-> Vec (2 ^ n) a
-> Signal dom (Unsigned n)
-> Signal dom a
E.romPow2)
{-# INLINE romPow2 #-}