    {-|
    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 :: (HiddenClock dom, HiddenEnable dom)
       => Signal dom (Unsigned 3)
       -> Signal dom (Unsigned 9)
    f rd = 'Clash.Class.BitPack.unpack' '<$>' 'romFile' d7 \"memory.bin\" rd
    @

    And see that it works as expected:

    @
    __>>> import qualified Data.List as L__
    __>>> L.tail $ sampleN 4 $ f (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 :: (HiddenClock dom, HiddenEnable dom)
      => Signal dom (Unsigned 3)
      -> Signal dom (Unsigned 6,Signed 3)
    g rd = 'Clash.Class.BitPack.unpack' '<$>' 'romFile' d7 \"memory.bin\" rd
    @

    And then we would see:

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

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

    {-# LANGUAGE Unsafe #-}

    {-# OPTIONS_HADDOCK show-extensions #-}

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

    import           Data.Array                   (listArray,(!))
    import           GHC.TypeLits                 (KnownNat)
    import           System.IO.Unsafe             (unsafePerformIO)

    import           Clash.Annotations.Primitive (hasBlackBox)
    import           Clash.Explicit.BlockRam.File (initMem, memFile)
    import qualified Clash.Explicit.ROM.File      as E
    import           Clash.Promoted.Nat           (SNat (..), pow2SNat, snatToNum)
    import           Clash.Signal
    import           Clash.Sized.BitVector        (BitVector)
    import           Clash.Sized.Unsigned         (Unsigned)

    -- | An asynchronous/combinational ROM with space for @n@ elements
    --
    -- * __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.Prelude.ROM.File#usingromfiles" for more information on how
    -- to instantiate a ROM with the contents of a data file.
    -- * See "Clash.Sized.Fixed#creatingdatafiles" for ideas on how to create your
    -- own data files.
    -- * When you notice that 'asyncRomFile' is significantly slowing down your
    -- simulation, give it a /monomorphic/ type signature. So instead of leaving
    -- the type to be inferred:
    --
    --     @
    --     myRomData = asyncRomFile d512 "memory.bin"
    --     @
    --
    --     or giving it a /polymorphic/ type signature:
    --
    --     @
    --     myRomData :: Enum addr => addr -> BitVector 16
    --     myRomData = asyncRomFile d512 "memory.bin"
    --     @
    --
    --     you __should__ give it a /monomorphic/ type signature:
    --
    --     @
    --     myRomData :: Unsigned 9 -> BitVector 16
    --     myRomData = asyncRomFile d512 "memory.bin"
    --     @
    asyncRomFile
      :: (KnownNat m, Enum addr)
      => SNat n
      -- ^ Size of the ROM
      -> FilePath
      -- ^ File describing the content of the ROM
      -> addr
      -- ^ Read address @r@
      -> BitVector m
      -- ^ The value of the ROM at address @r@
    asyncRomFile :: forall (m :: Nat) addr (n :: Nat).
    (KnownNat m, Enum addr) =>
    SNat n -> FilePath -> addr -> BitVector m
    asyncRomFile SNat n
    sz FilePath
    file = SNat n -> FilePath -> Int -> BitVector m
    forall (m :: Nat) (n :: Nat).
    KnownNat m =>
    SNat n -> FilePath -> Int -> BitVector m
    asyncRomFile# SNat n
    sz FilePath
    file (Int -> BitVector m) -> (addr -> Int) -> addr -> BitVector m
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . addr -> Int
    forall a. Enum a => a -> Int
    fromEnum
    -- Leave 'asyncRomFile#' eta-reduced, see Note [Eta-reduction and unsafePerformIO initMem]
    {-# INLINE asyncRomFile #-}

    -- Note [Eta-reduction and unsafePerformIO initMem]
    --
    -- The 'initMem' function initializes a @[BitVector n]@ from file. Ideally,
    -- we want this IO action to happen only once. When we call 'unsafePerformIO'
    -- on @initMem file@, it becomes a thunk in that function, so is hence evaluated
    -- only once. However, me must ensure that any code calling using of the
    -- @unsafePerformIO (initMem file)@ thunk also becomes a thunk. We do this by
    -- eta-reducing function where needed so that a thunk is returned.
    --
    -- For example, instead of writing:
    --
    -- > asyncRomFile# sz file rd = (content ! rd)
    -- >   where
    -- >     mem = unsafePerformIO (initMem file)
    -- >     content = listArray (0,szI-1) mem
    -- >     szI     = snatToNum sz
    --
    -- We write:
    --
    -- > asyncRomFile# sz file = (content !)
    -- >   where
    -- >     mem     = unsafePerformIO (initMem file)
    -- >     content = listArray (0,szI-1) mem
    -- >     szI     = snatToNum sz
    --
    -- Where instead of returning the BitVector defined by @(content ! rd)@, we
    -- return the function (thunk) @(content !)@.

    -- | An asynchronous/combinational ROM with space for 2^@n@ elements
    --
    -- * __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.Prelude.ROM.File#usingromfiles" for more information on how
    -- to instantiate a ROM with the contents of a data file.
    -- * See "Clash.Sized.Fixed#creatingdatafiles" for ideas on how to create your
    -- own data files.
    -- * When you notice that 'asyncRomFilePow2' is significantly slowing down your
    -- simulation, give it a /monomorphic/ type signature. So instead of leaving the
    -- type to be inferred:
    --
    --     @
    --     myRomData = asyncRomFilePow2 "memory.bin"
    --     @
    --
    --     you __should__ give it a /monomorphic/ type signature:
    --
    --     @
    --     myRomData :: Unsigned 9 -> BitVector 16
    --     myRomData = asyncRomFilePow2 "memory.bin"
    --     @
    asyncRomFilePow2
      :: forall n m
       . (KnownNat m, KnownNat n)
      => FilePath
      -- ^ File describing the content of the ROM
      -> Unsigned n
      -- ^ Read address @r@
      -> BitVector m
      -- ^ The value of the ROM at address @r@
    asyncRomFilePow2 :: forall (n :: Nat) (m :: Nat).
    (KnownNat m, KnownNat n) =>
    FilePath -> Unsigned n -> BitVector m
    asyncRomFilePow2 = SNat (2 ^ n) -> FilePath -> Unsigned n -> BitVector m
    forall (m :: Nat) addr (n :: Nat).
    (KnownNat m, Enum addr) =>
    SNat n -> FilePath -> addr -> BitVector m
    asyncRomFile (SNat n -> SNat (2 ^ n)
    forall (a :: Nat). SNat a -> SNat (2 ^ a)
    pow2SNat (forall (n :: Nat). KnownNat n => SNat n
    SNat @n))
    {-# INLINE asyncRomFilePow2 #-}

    -- | asyncRomFile primitive
    asyncRomFile#
      :: KnownNat m
      => SNat n
      -- ^ Size of the ROM
      -> FilePath
      -- ^ File describing the content of the ROM
      -> Int
      -- ^ Read address @r@
      -> BitVector m
      -- ^ The value of the ROM at address @r@
    asyncRomFile# :: forall (m :: Nat) (n :: Nat).
    KnownNat m =>
    SNat n -> FilePath -> Int -> BitVector m
    asyncRomFile# SNat n
    sz FilePath
    file = (Array Int (BitVector m)
    content Array Int (BitVector m) -> Int -> BitVector m
    forall i e. Ix i => Array i e -> i -> e
    !) -- Leave "(content !)" eta-reduced, see
      where                             -- Note [Eta-reduction and unsafePerformIO initMem]
        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
    -- See: https://github.com/clash-lang/clash-compiler/pull/2511
    {-# CLASH_OPAQUE asyncRomFile# #-}
    {-# ANN asyncRomFile# 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'
    -- * __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.Prelude.ROM.File#usingromfiles" for more information on how
    -- to instantiate a ROM with the contents of a data file.
    -- * See "Clash.Sized.Fixed#creatingdatafiles" for ideas on how to create your
    -- own data files.
    romFile
      :: ( KnownNat m
         , KnownNat n
         , HiddenClock dom
         , HiddenEnable dom
         , Enum addr
         )
      => 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) (n :: Nat) (dom :: Domain) addr.
    (KnownNat m, KnownNat n, HiddenClock dom, HiddenEnable dom,
     Enum addr) =>
    SNat n -> FilePath -> Signal dom addr -> Signal dom (BitVector m)
    romFile = (Enable dom
     -> SNat n
     -> FilePath
     -> Signal dom addr
     -> Signal dom (BitVector m))
    -> SNat n
    -> FilePath
    -> Signal dom addr
    -> Signal dom (BitVector m)
    forall (dom :: Domain) r.
    HiddenEnable dom =>
    (Enable dom -> r) -> r
    hideEnable ((Clock dom
     -> Enable dom
     -> SNat n
     -> FilePath
     -> Signal dom addr
     -> Signal dom (BitVector m))
    -> Enable dom
    -> SNat n
    -> FilePath
    -> Signal dom addr
    -> Signal dom (BitVector m)
    forall (dom :: Domain) r. HiddenClock dom => (Clock dom -> r) -> r
    hideClock Clock dom
    -> Enable dom
    -> SNat n
    -> FilePath
    -> Signal dom addr
    -> 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)
    E.romFile)
    {-# INLINE romFile #-}

    -- | 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.Prelude.ROM.File#usingromfiles" for more information on how
    -- to instantiate a ROM with the contents of a data file.
    -- * See "Clash.Sized.Fixed#creatingdatafiles" for ideas on how to create your
    -- own data files.
    romFilePow2
      :: forall n m dom
       . ( KnownNat m
         , KnownNat n
         , HiddenClock dom
         , HiddenEnable dom
         )
      => 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 (n :: Nat) (m :: Nat) (dom :: Domain).
    (KnownNat m, KnownNat n, HiddenClock dom, HiddenEnable dom) =>
    FilePath -> Signal dom (Unsigned n) -> Signal dom (BitVector m)
    romFilePow2 = (Enable dom
     -> FilePath -> Signal dom (Unsigned n) -> Signal dom (BitVector m))
    -> FilePath -> Signal dom (Unsigned n) -> Signal dom (BitVector m)
    forall (dom :: Domain) r.
    HiddenEnable dom =>
    (Enable dom -> r) -> r
    hideEnable ((Clock dom
     -> Enable dom
     -> FilePath
     -> Signal dom (Unsigned n)
     -> Signal dom (BitVector m))
    -> Enable dom
    -> FilePath
    -> Signal dom (Unsigned n)
    -> Signal dom (BitVector m)
    forall (dom :: Domain) r. HiddenClock dom => (Clock dom -> r) -> r
    hideClock Clock dom
    -> Enable dom
    -> FilePath
    -> Signal dom (Unsigned n)
    -> Signal dom (BitVector m)
    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)
    E.romFilePow2)
    {-# INLINE romFilePow2 #-}