{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module Clash.Explicit.BlockRam.Model where
#if !MIN_VERSION_base(4,18,0)
import Control.Applicative (liftA2)
#endif
import Control.Exception (throw)
import Data.Sequence (Seq)
import GHC.Stack (HasCallStack)
import GHC.TypeNats (KnownNat)
import Clash.Promoted.Nat (SNat(..), natToNum)
import Clash.Signal.Bundle (Bundle(bundle))
import Clash.Signal.Internal
(KnownDomain(..), Clock (..), Signal (..), ClockAB (..), clockTicks)
import Clash.Sized.Index (Index)
import Clash.XException (XException(..), NFDataX(..), seqX)
import Clash.XException.MaybeX (MaybeX(..), toMaybeX, andX)
import qualified Clash.XException.MaybeX as MaybeX
import qualified Data.Sequence as Seq
data Conflict = Conflict
{ cfRWA :: !(MaybeX Bool)
, cfRWB :: !(MaybeX Bool)
, cfWW :: !(MaybeX Bool)
} deriving (Show)
getConflict ::
(MaybeX Bool, MaybeX Bool, MaybeX Int) ->
(MaybeX Bool, MaybeX Bool, MaybeX Int) ->
Maybe Conflict
getConflict (enA, wenA, addrA) (enB, wenB, addrB)
| IsDefined False <- sameAddrX = Nothing
| otherwise = Just conflict
where
sameAddrX = liftA2 (==) addrA addrB
conflict = Conflict
{ cfRWA = enA `andX` (enB `andX` wenB)
, cfRWB = enB `andX` (enA `andX` wenA)
, cfWW = (enA `andX` enB) `andX` (wenA `andX` wenB)
}
cycleOne ::
forall nAddrs a writeEnable .
( HasCallStack
, NFDataX a
) =>
SNat nAddrs ->
TdpbramModelConfig writeEnable a ->
a ->
Seq a ->
(MaybeX Bool, MaybeX Int, MaybeX writeEnable, a) ->
(Seq a, a)
cycleOne SNat TdpbramModelConfig{..} prev ram0 = \case
(IsDefined False, _, _, _) ->
(ram0, prev)
(ena, addr, byteEna0, dat) ->
let
byteEna1 = tdpMergeWriteEnable ena byteEna0
(out0, !ram1) =
accessRam (SNat @nAddrs) tdpIsActiveWriteEnable tdpUpdateRam addr byteEna1 dat ram0
out1 = MaybeX.maybeX (throw . XException) (const out0) ena
in
(ram1, out1)
cycleBoth ::
forall nAddrs a writeEnable.
( NFDataX a
, HasCallStack
) =>
SNat nAddrs ->
TdpbramModelConfig writeEnable a ->
a ->
a ->
Seq a ->
(MaybeX Bool, MaybeX Int, MaybeX writeEnable, a) ->
(MaybeX Bool, MaybeX Int, MaybeX writeEnable, a) ->
(Seq a, a, a)
cycleBoth
SNat TdpbramModelConfig{..} prevA prevB ram0
(enAx, addrAx, byteEnaAx0, datA)
(enBx, addrBx, byteEnaBx0, datB) = (ram2, outA2, outB2)
where
conflict =
getConflict
(enAx, tdpIsActiveWriteEnable byteEnaAx1, addrAx)
(enBx, tdpIsActiveWriteEnable byteEnaBx1, addrBx)
writeWriteError = deepErrorX "conflicting write/write queries"
readWriteError = deepErrorX "conflicting read/write queries"
byteEnaAx1 = tdpMergeWriteEnable enAx byteEnaAx0
byteEnaBx1 = tdpMergeWriteEnable enBx byteEnaBx0
(datA1, datB1) = case conflict of
Just Conflict{cfWW=IsDefined True} -> (writeWriteError, writeWriteError)
Just Conflict{cfWW=IsX _} -> (writeWriteError, writeWriteError)
_ -> (datA, datB)
(outA0, ram1) =
accessRam (SNat @nAddrs) tdpIsActiveWriteEnable tdpUpdateRam addrAx byteEnaAx1 datA1 ram0
(outB0, ram2) =
accessRam (SNat @nAddrs) tdpIsActiveWriteEnable tdpUpdateRam addrBx byteEnaBx1 datB1 ram1
outA1 = case conflict of
Just Conflict{cfRWA=IsDefined True} -> readWriteError
Just Conflict{cfRWA=IsX _} -> readWriteError
_ -> outA0
outB1 = case conflict of
Just Conflict{cfRWB=IsDefined True} -> readWriteError
Just Conflict{cfRWB=IsX _} -> readWriteError
_ -> outB0
outA2 = if MaybeX.fromMaybeX enAx then outA1 else prevA
outB2 = if MaybeX.fromMaybeX enBx then outB1 else prevB
accessRam ::
forall nAddrs a writeEnable .
( NFDataX a
, HasCallStack ) =>
SNat nAddrs ->
(MaybeX writeEnable -> MaybeX Bool) ->
(Int -> MaybeX writeEnable -> a -> Seq a -> Seq a) ->
MaybeX Int ->
MaybeX writeEnable ->
a ->
Seq a ->
(a, Seq a)
accessRam SNat tdpIsActiveWriteEnable updateMem addrX byteEnableX dat mem0
| IsDefined False <- tdpIsActiveWriteEnable byteEnableX
= (mem0 `Seq.index` MaybeX.fromMaybeX addrX, mem0)
| IsX addrMsg <- addrX
= ( deepErrorX $ "Unknown address" <> "\nAddress error message: " <> addrMsg
, Seq.fromFunction (natToNum @nAddrs) (unknownAddr addrMsg) )
| IsDefined addr <- addrX
, mem1 <- updateMem addr byteEnableX dat mem0
= (mem1 `Seq.index` addr, mem1)
where
unknownAddr :: String -> Int -> a
unknownAddr msg n =
deepErrorX ("Write enabled or undefined, but address unknown; position " <> show n <>
"\nAddress error message: " <> msg)
data TdpbramModelConfig writeEnable a = TdpbramModelConfig
{ tdpIsActiveWriteEnable :: MaybeX writeEnable -> MaybeX Bool
, tdpMergeWriteEnable :: MaybeX Bool -> MaybeX writeEnable -> MaybeX writeEnable
, tdpUpdateRam :: Int -> MaybeX writeEnable -> a -> Seq a -> Seq a
}
tdpbramModel ::
forall nAddrs domA domB a writeEnable .
( HasCallStack
, KnownNat nAddrs
, KnownDomain domA
, KnownDomain domB
, NFDataX a
) =>
TdpbramModelConfig writeEnable a ->
Clock domA ->
Signal domA Bool ->
Signal domA (Index nAddrs) ->
Signal domA writeEnable ->
Signal domA a ->
Clock domB ->
Signal domB Bool ->
Signal domB (Index nAddrs) ->
Signal domB writeEnable ->
Signal domB a ->
(Signal domA a, Signal domB a)
tdpbramModel
config
clkA enA addrA byteEnaA datA
clkB enB addrB byteEnaB datB =
( startA :- outA
, startB :- outB )
where
(outA, outB) =
go
(Seq.fromFunction (natToNum @nAddrs) initElement)
(clockTicks clkA clkB)
(bundle (enA, byteEnaA, fromIntegral <$> addrA, datA))
(bundle (enB, byteEnaB, fromIntegral <$> addrB, datB))
startA startB
startA = deepErrorX $ "Port A: First value undefined"
startB = deepErrorX $ "Port B: First value undefined"
initElement :: Int -> a
initElement n =
deepErrorX ("Unknown initial element; position " <> show n)
go ::
Seq a ->
[ClockAB] ->
Signal domA (Bool, writeEnable, Int, a) ->
Signal domB (Bool, writeEnable, Int, a) ->
a -> a ->
(Signal domA a, Signal domB a)
go _ [] _ _ =
error "tdpbramModel#.go: `ticks` should have been an infinite list"
go ram0 (tick:ticks) as0 bs0 =
case tick of
ClockA -> goA
ClockB -> goB
ClockAB -> goBoth
where
( toMaybeX -> enAx
, toMaybeX -> byteEnaAx
, toMaybeX -> addrAx
, datA0
) :- as1 = as0
( toMaybeX -> enBx
, toMaybeX -> byteEnaBx
, toMaybeX -> addrBx
, datB0
) :- bs1 = bs0
portA = (enAx, addrAx, byteEnaAx, datA0)
portB = (enBx, addrBx, byteEnaBx, datB0)
goBoth prevA prevB = outA1 `seqX` outB1 `seqX` (outA1 :- as2, outB1 :- bs2)
where
(ram1, outA1, outB1) =
cycleBoth
(SNat @nAddrs) config
prevA prevB ram0 portA portB
(as2, bs2) = go ram1 ticks as1 bs1 outA1 outB1
goA prevA prevB = out `seqX` (out :- as2, bs2)
where
(ram1, out) = cycleOne (SNat @nAddrs) config prevA ram0 portA
(as2, bs2) = go ram1 ticks as1 bs0 out prevB
goB prevA prevB = out `seqX` (as2, out :- bs2)
where
(ram1, out) = cycleOne (SNat @nAddrs) config prevB ram0 portB
(as2, bs2) = go ram1 ticks as0 bs1 prevA out