    {-|
    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>

    = Initializing a ROM with a data file #usingromfiles#

    ROMs initialized with a data file. The BNF grammar for this data file is simple:

    > FILE = LINE+
    > LINE = BIT+
    > BIT  = '0'
    >      | '1'

    Consecutive @LINE@s correspond to consecutive memory addresses starting at @0@.
    For example, a data file @memory.bin@ containing the 9-bit unsigned numbers
    @7@ to @13@ looks like:

    > 000000111
    > 000001000
    > 000001001
    > 000001010
    > 000001011
    > 000001100
    > 000001101

    Such a file can be produced with 'memFile':

    @
    writeFile "memory.bin" (memFile Nothing [7 :: Unsigned 9 .. 13])
    @

    We can instantiate a synchronous ROM using the contents of the file above like
    so:

    @
    f :: KnownDomain dom
      => Clock  dom
      -> Enable dom
      -> Signal dom (Unsigned 3)
      -> Signal dom (Unsigned 9)
    f clk en rd = 'Clash.Class.BitPack.unpack' '<$>' 'romFile' clk en d7 \"memory.bin\" rd
    @

    And see that it works as expected:

    @
    __>>> import qualified Data.List as L__
    __>>> L.tail $ sampleN 4 $ f systemClockGen (fromList [3..5])__
    [10,11,12]
    @

    However, we can also interpret the same data as a tuple of a 6-bit unsigned
    number, and a 3-bit signed number:

    @
    g :: KnownDomain dom
      => Clock  dom
      -> Signal dom (Unsigned 3)
      -> Signal dom (Unsigned 6,Signed 3)
    g clk en rd = 'Clash.Class.BitPack.unpack' '<$>' 'romFile' clk en d7 \"memory.bin\" rd
    @

    And then we would see:

    @
    __>>> import qualified Data.List as L__
    __>>> L.tail $ sampleN 4 $ g systemClockGen (fromList [3..5])__
    [(1,2),(1,3)(1,-4)]
    @
    -}

    {-# LANGUAGE CPP #-}
    {-# LANGUAGE FlexibleContexts #-}

    {-# LANGUAGE Unsafe #-}

    {-# OPTIONS_HADDOCK show-extensions #-}

    module Clash.Explicit.ROM.File
      ( -- * Synchronous ROM synchronized to an arbitrary clock
        romFile
      , romFilePow2
        -- * Producing files
      , memFile
        -- * Internal
      , romFile#
      )
    where

    import Data.Array                   (listArray)
    import Data.Array.Base              (unsafeAt)
    import GHC.TypeLits                 (KnownNat)
    import System.IO.Unsafe             (unsafePerformIO)

    import Clash.Annotations.Primitive (hasBlackBox)
    import Clash.Explicit.BlockRam.File (initMem, memFile)
    import Clash.Promoted.Nat           (SNat (..), pow2SNat, snatToNum)
    import Clash.Sized.BitVector        (BitVector)
    import Clash.Explicit.Signal        (Clock, Enable, Signal, KnownDomain, delay)
    import Clash.Sized.Unsigned         (Unsigned)
    import Clash.XException             (NFDataX(deepErrorX))

    -- | 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'
    -- * __NB__: This function might not work for specific combinations of
    -- code-generation backends and hardware targets. Please check the support table
    -- below:
    --
    -- +----------------+----------+----------+---------------+
    -- |                | VHDL     | Verilog  | SystemVerilog |
    -- +================+==========+==========+===============+
    -- | Altera/Quartus | Broken   | Works    | Works         |
    -- +----------------+----------+----------+---------------+
    -- | Xilinx/ISE     | Works    | Works    | Works         |
    -- +----------------+----------+----------+---------------+
    -- | ASIC           | Untested | Untested | Untested      |
    -- +----------------+----------+----------+---------------+
    --
    -- === See also:
    --
    -- * See "Clash.Explicit.ROM.File#usingromfiles" for more information on how
    -- to instantiate a ROM with the contents of a data file.
    -- * See 'memFile' for creating a data file with Clash.
    -- * See "Clash.Sized.Fixed#creatingdatafiles" for more ideas on how to create
    -- your own data files.
    romFilePow2
      :: forall dom  n m
       . (KnownNat m, KnownNat n, KnownDomain dom)
      => Clock dom
      -- ^ 'Clock' to synchronize to
      -> Enable dom
      -- ^ 'Enable' line
      -> FilePath
      -- ^ File describing the content of the ROM
      -> Signal dom (Unsigned n)
      -- ^ Read address @r@
      -> Signal dom (BitVector m)
      -- ^ The value of the ROM at address @r@ from the previous clock cycle
    romFilePow2 :: forall (dom :: Domain) (n :: Nat) (m :: Nat).
    (KnownNat m, KnownNat n, KnownDomain dom) =>
    Clock dom
    -> Enable dom
    -> FilePath
    -> Signal dom (Unsigned n)
    -> Signal dom (BitVector m)
    romFilePow2 = \Clock dom
    clk Enable dom
    en -> Clock dom
    -> Enable dom
    -> SNat (2 ^ n)
    -> FilePath
    -> Signal dom (Unsigned n)
    -> Signal dom (BitVector m)
    forall (m :: Nat) addr (dom :: Domain) (n :: Nat).
    (KnownNat m, Enum addr, KnownDomain dom) =>
    Clock dom
    -> Enable dom
    -> SNat n
    -> FilePath
    -> Signal dom addr
    -> Signal dom (BitVector m)
    romFile Clock dom
    clk Enable dom
    en (SNat n -> SNat (2 ^ n)
    forall (a :: Nat). SNat a -> SNat (2 ^ a)
    pow2SNat (forall (n :: Nat). KnownNat n => SNat n
    SNat @n))
    {-# INLINE romFilePow2 #-}

    -- | 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'
    -- * __NB__: This function might not work for specific combinations of
    -- code-generation backends and hardware targets. Please check the support table
    -- below:
    --
    -- +----------------+----------+----------+---------------+
    -- |                | VHDL     | Verilog  | SystemVerilog |
    -- +================+==========+==========+===============+
    -- | Altera/Quartus | Broken   | Works    | Works         |
    -- +----------------+----------+----------+---------------+
    -- | Xilinx/ISE     | Works    | Works    | Works         |
    -- +----------------+----------+----------+---------------+
    -- | ASIC           | Untested | Untested | Untested      |
    -- +----------------+----------+----------+---------------+
    --
    -- === See also:
    --
    -- * See "Clash.Explicit.ROM.File#usingromfiles" for more information on how
    -- to instantiate a ROM with the contents of a data file.
    -- * See 'memFile' for creating a data file with Clash.
    -- * See "Clash.Sized.Fixed#creatingdatafiles" for ideas on how to create your
    -- own data files.
    romFile
      :: (KnownNat m, Enum addr, KnownDomain dom)
      => Clock dom
      -- ^ 'Clock' to synchronize to
      -> Enable dom
      -- ^ 'Enable' line
      -> SNat n
      -- ^ Size of the ROM
      -> FilePath
      -- ^ File describing the content of the ROM
      -> Signal dom addr
      -- ^ Read address @r@
      -> Signal dom (BitVector m)
      -- ^ The value of the ROM at address @r@ from the previous clock cycle
    romFile :: forall (m :: Nat) addr (dom :: Domain) (n :: Nat).
    (KnownNat m, Enum addr, KnownDomain dom) =>
    Clock dom
    -> Enable dom
    -> SNat n
    -> FilePath
    -> Signal dom addr
    -> Signal dom (BitVector m)
    romFile = \Clock dom
    clk Enable dom
    en SNat n
    sz FilePath
    file Signal dom addr
    rd -> Clock dom
    -> Enable dom
    -> SNat n
    -> FilePath
    -> Signal dom Int
    -> Signal dom (BitVector m)
    forall (m :: Nat) (dom :: Domain) (n :: Nat).
    (KnownNat m, KnownDomain dom) =>
    Clock dom
    -> Enable dom
    -> SNat n
    -> FilePath
    -> Signal dom Int
    -> Signal dom (BitVector m)
    romFile# Clock dom
    clk Enable dom
    en SNat n
    sz FilePath
    file (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 romFile #-}

    -- | romFile primitive
    romFile#
      :: forall m dom n
       . (KnownNat m, KnownDomain dom)
      => Clock dom
      -- ^ 'Clock' to synchronize to
      -> Enable dom
      -- ^ 'Enable' line
      -> SNat n
      -- ^ Size of the ROM
      -> FilePath
      -- ^ File describing the content of the ROM
      -> Signal dom Int
      -- ^ Read address @r@
      -> Signal dom (BitVector m)
      -- ^ The value of the ROM at address @r@ from the previous clock cycle
    romFile# :: forall (m :: Nat) (dom :: Domain) (n :: Nat).
    (KnownNat m, KnownDomain dom) =>
    Clock dom
    -> Enable dom
    -> SNat n
    -> FilePath
    -> Signal dom Int
    -> Signal dom (BitVector m)
    romFile# Clock dom
    clk Enable dom
    en SNat n
    sz FilePath
    file Signal dom Int
    rd =
      Clock dom
    -> Enable dom
    -> BitVector m
    -> Signal dom (BitVector m)
    -> Signal dom (BitVector m)
    forall (dom :: Domain) a.
    (KnownDomain dom, NFDataX a) =>
    Clock dom -> Enable dom -> a -> Signal dom a -> Signal dom a
    delay Clock dom
    clk Enable dom
    en (FilePath -> BitVector m
    forall a. (NFDataX a, HasCallStack) => FilePath -> a
    deepErrorX FilePath
    "First value of romFile is undefined")
            (Int -> BitVector m
    safeAt (Int -> BitVector m) -> Signal dom Int -> Signal dom (BitVector m)
    forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
    <$> Signal dom Int
    rd)
     where
      mem :: [BitVector m]
    mem     = IO [BitVector m] -> [BitVector m]
    forall a. IO a -> a
    unsafePerformIO (FilePath -> IO [BitVector m]
    forall (n :: Nat). KnownNat n => FilePath -> IO [BitVector n]
    initMem FilePath
    file)
      content :: Array Int (BitVector m)
    content = (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]
    mem
      szI :: Int
    szI     = SNat n -> Int
    forall a (n :: Nat). Num a => SNat n -> a
    snatToNum SNat n
    sz

      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)
    content Int
    i
        else
          FilePath -> BitVector m
    forall a. (NFDataX a, HasCallStack) => FilePath -> a
    deepErrorX (FilePath
    "romFile: address " FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ Int -> FilePath
    forall a. Show a => a -> FilePath
    show Int
    i FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++
                      FilePath
    " not in range [0.." FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ Int -> FilePath
    forall a. Show a => a -> FilePath
    show Int
    szI FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    ")")
      {-# INLINE safeAt #-}
    -- See: https://github.com/clash-lang/clash-compiler/pull/2511
    {-# CLASH_OPAQUE romFile# #-}
    {-# ANN romFile# hasBlackBox #-}