{-|
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 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 :: 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 :: 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 (KnownNat n => SNat n
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# :: 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
{-# NOINLINE 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 :: 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 :: Symbol) 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 :: Symbol) 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 :: Symbol) (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 :: 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 :: Symbol) 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 :: Symbol) r. HiddenClock dom => (Clock dom -> r) -> r
hideClock Clock dom
-> Enable dom
-> FilePath
-> Signal dom (Unsigned n)
-> Signal dom (BitVector m)
forall (dom :: Symbol) (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 #-}