{-|
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 BangPatterns #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}

{-# LANGUAGE Trustworthy #-}

{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
{-# OPTIONS_HADDOCK show-extensions #-}

module Clash.Explicit.ROM
  ( -- * Synchronous ROM synchronized to an arbitrary clock
    rom
  , romPow2
    -- * Internal
  , rom#
  )
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 Clash.Signal.Internal
  (Clock (..), KnownDomain, Signal (..), Enable, fromEnable)
import Clash.Sized.Unsigned   (Unsigned)
import Clash.Sized.Vector     (Vec, length, toList)
import Clash.XException       (deepErrorX, seqX, NFDataX)

-- | 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.
-- * A large 'Vec' for the content may be too inefficient, depending on how it
-- is constructed. See 'Clash.Explicit.ROM.File.romFilePow2' and
-- 'Clash.Explicit.ROM.Blob.romBlobPow2' for different approaches that scale
-- well.
romPow2
  :: (KnownDomain dom, KnownNat n, NFDataX a)
  => Clock dom
  -- ^ 'Clock' to synchronize to
  -> Enable dom
  -- ^ 'Enable' line
  -> 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 :: Clock dom
-> Enable dom
-> Vec (2 ^ n) a
-> Signal dom (Unsigned n)
-> Signal dom a
romPow2 = Clock dom
-> Enable dom
-> Vec (2 ^ n) a
-> Signal dom (Unsigned n)
-> Signal dom a
forall (dom :: Domain) (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
rom
{-# INLINE romPow2 #-}

-- | 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.
-- * A large 'Vec' for the content may be too inefficient, depending on how it
-- is constructed. See 'Clash.Explicit.ROM.File.romFile' and
-- 'Clash.Explicit.ROM.Blob.romBlob' for different approaches that scale well.
rom
  :: (KnownDomain dom, KnownNat n, NFDataX a, Enum addr)
  => Clock dom
  -- ^ 'Clock' to synchronize to
  -> Enable dom
  -- ^ 'Enable' line
  -> Vec n a
  -- ^ ROM content, also determines the size, @n@, of the ROM
  --
  -- __NB__: __MUST__ be a constant
  -> Signal dom addr
  -- ^ Read address @r@
  -> Signal dom a
  -- ^ The value of the ROM at address @r@ from the previous clock cycle
rom :: Clock dom
-> Enable dom -> Vec n a -> Signal dom addr -> Signal dom a
rom = \Clock dom
clk Enable dom
en Vec n a
content Signal dom addr
rd -> Clock dom
-> Enable dom -> Vec n a -> Signal dom Int -> Signal dom a
forall (dom :: Domain) (n :: Nat) a.
(KnownDomain dom, KnownNat n, NFDataX a) =>
Clock dom
-> Enable dom -> Vec n a -> Signal dom Int -> Signal dom a
rom# Clock dom
clk Enable dom
en Vec n a
content (addr -> Int
forall a. Enum a => a -> Int
fromEnum (addr -> Int) -> Signal dom addr -> Signal dom Int
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
<$> Signal dom addr
rd)
{-# INLINE rom #-}

-- | ROM primitive
rom#
  :: forall dom n a
   . (KnownDomain dom, KnownNat n, NFDataX a)
  => Clock dom
  -- ^ 'Clock' to synchronize to
  -> Enable dom
  -- ^ 'Enable' line
  -> Vec n a
  -- ^ ROM content, also determines the size, @n@, of the ROM
  --
  -- __NB__: __MUST__ be a constant
  -> Signal dom Int
  -- ^ Read address @rd@
  -> Signal dom a
  -- ^ The value of the ROM at address @rd@ from the previous clock cycle
rom# :: Clock dom
-> Enable dom -> Vec n a -> Signal dom Int -> Signal dom a
rom# !Clock dom
_ Enable dom
en Vec n a
content =
  a -> Signal dom Bool -> Signal dom Int -> Signal dom a
go
    ((HasCallStack => a) -> a
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack (String -> a
forall a. (NFDataX a, HasCallStack) => String -> a
deepErrorX String
"rom: initial value undefined"))
    (Enable dom -> Signal dom Bool
forall (dom :: Domain). Enable dom -> Signal dom Bool
fromEnable Enable dom
en)
 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)

  go :: a -> Signal dom Bool -> Signal dom Int -> Signal dom a
go a
o (Bool
e :- Signal dom Bool
es) rd :: Signal dom Int
rd@(~(Int
r :- Signal dom Int
rs)) =
    let o1 :: a
o1 = if Bool
e then Int -> a
safeAt Int
r else a
o
    -- See [Note: register strictness annotations]
    in  a
o a -> Signal dom a -> Signal dom a
forall a b. a -> b -> b
`seqX` a
o a -> Signal dom a -> Signal dom a
forall (dom :: Domain) a. a -> Signal dom a -> Signal dom a
:- (Signal dom Int
rd Signal dom Int -> Signal dom a -> Signal dom a
`seq` a -> Signal dom Bool -> Signal dom Int -> Signal dom a
go a
o1 Signal dom Bool
es Signal dom Int
rs)

  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. (NFDataX a, HasCallStack) => String -> a
deepErrorX (String
"rom: 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
")"))
  {-# INLINE safeAt #-}
{-# NOINLINE rom# #-}
{-# ANN rom# hasBlackBox #-}