{-# LANGUAGE UndecidableInstances #-}
module Protocols.Experimental.Hedgehog (
ExpectOptions (..),
defExpectOptions,
StallMode (..),
Test (..),
TestType,
idWithModel,
idWithModelSingleDomain,
propWithModel,
propWithModelSingleDomain,
idWithModelT,
idWithModelSingleDomainT,
propWithModelT,
propWithModelSingleDomainT,
genStallAck,
genStallMode,
genStalls,
expectedEmptyCycles,
) where
import Control.Concurrent (threadDelay)
import Control.Monad (when)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Proxy (Proxy (Proxy))
import GHC.Stack (HasCallStack)
import Prelude
import Protocols
import Protocols.Experimental.Hedgehog.Internal
import Protocols.Experimental.Hedgehog.Types
import Protocols.Experimental.Simulate
import Clash.Prelude qualified as C
import Clash.Hedgehog.Sized.Vector (genVec)
import Hedgehog ((===))
import Hedgehog qualified as H
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Control.Concurrent.Async.Lifted
import Control.Monad.Trans.Control (MonadBaseControl)
data StallMode = NoStall | Stall
deriving (Show, Enum, Bounded)
resetGen :: (C.KnownDomain dom) => Int -> C.Reset dom
resetGen n =
C.unsafeFromActiveHigh
(C.fromList (replicate n True <> repeat False))
withTimeoutMs ::
(MonadIO m, MonadBaseControl IO m) => Int -> H.PropertyT m a -> H.PropertyT m a
withTimeoutMs timeout v = do
result <-
race
(liftIO $ threadDelay (timeout * 1000))
v
case result of
Left () -> fail "Timeout exceeded"
Right x -> pure x
propWithModel ::
forall a b.
(Test a, Test b, HasCallStack) =>
ExpectOptions ->
H.Gen (ExpectType a) ->
(ExpectType a -> ExpectType b) ->
Circuit a b ->
(ExpectType b -> ExpectType b -> H.PropertyT IO ()) ->
H.Property
propWithModel eOps gen model dut prop = H.property $ propWithModelT eOps gen model dut prop
propWithModelT ::
forall a b m.
(Test a, Test b, HasCallStack, Monad m, MonadIO m, MonadBaseControl IO m) =>
ExpectOptions ->
H.Gen (ExpectType a) ->
(ExpectType a -> ExpectType b) ->
Circuit a b ->
(ExpectType b -> ExpectType b -> H.PropertyT m ()) ->
H.PropertyT m ()
propWithModelT eOpts genData model prot prop =
maybe id withTimeoutMs (eoTimeoutMs eOpts) $ do
dat <- H.forAll genData
when (eoTrace eOpts) $ liftIO $ putStr "propWithModel: dat: " >> print dat
n <- H.forAll (Gen.integral (Range.linear 0 (eoStallsMax eOpts)))
when (eoTrace eOpts) $ liftIO $ putStr "propWithModel: n: " >> print n
let
genStall = Gen.int (Range.linear 1 eOpts.eoConsecutiveStalls)
lhsStallModes <- H.forAll (genVec genStallMode)
when (eoTrace eOpts) $
liftIO $
putStr "propWithModel: lhsStallModes: " >> print lhsStallModes
lhsStalls <- H.forAll (traverse (genStalls genStall n) lhsStallModes)
when (eoTrace eOpts) $ liftIO $ putStr "propWithModel: lhsStalls: " >> print lhsStalls
rhsStallModes <- H.forAll (genVec genStallMode)
when (eoTrace eOpts) $
liftIO $
putStr "propWithModel: rhsStallModes: " >> print rhsStallModes
rhsStalls <- H.forAll (traverse (genStalls genStall n) rhsStallModes)
when (eoTrace eOpts) $ liftIO $ putStr "propWithModel: rhsStalls: " >> print rhsStalls
let
simConfig = def{resetCycles = eoResetCycles eOpts}
simDriveConfig =
if eoDriveEarly eOpts
then def{resetCycles = max 1 (eoResetCycles eOpts - 5)}
else def{resetCycles = eoResetCycles eOpts}
expected = model dat
lhsStallC = stallC simConfig lhsStalls
rhsStallC = stallC simConfig rhsStalls
stalledProtocol =
driveC simDriveConfig (toSimulateType (Proxy @a) dat)
|> lhsStallC
|> prot
|> rhsStallC
sampled = sampleC simConfig stalledProtocol
trimmed <- expectN (Proxy @b) eOpts sampled
when (eoTrace eOpts) $ liftIO $ putStrLn "propWithModel: before forcing trimmed.."
_ <- H.evalNF trimmed
when (eoTrace eOpts) $ liftIO $ putStrLn "propWithModel: before forcing expected.."
_ <- H.evalNF expected
when (eoTrace eOpts) $
liftIO $
putStrLn "propWithModel: executing property.."
prop expected trimmed
idWithModel ::
forall a b.
(Test a, Test b, HasCallStack) =>
ExpectOptions ->
H.Gen (ExpectType a) ->
(ExpectType a -> ExpectType b) ->
Circuit a b ->
H.Property
idWithModel eOpts genData model prot = H.property $ idWithModelT eOpts genData model prot
idWithModelT ::
forall a b m.
(Test a, Test b, HasCallStack, Monad m, MonadIO m, MonadBaseControl IO m) =>
ExpectOptions ->
H.Gen (ExpectType a) ->
(ExpectType a -> ExpectType b) ->
Circuit a b ->
H.PropertyT m ()
idWithModelT eOpts genData model prot = propWithModelT eOpts genData model prot (===)
propWithModelSingleDomain ::
forall dom a b.
(Test a, Test b, C.KnownDomain dom, HasCallStack) =>
ExpectOptions ->
H.Gen (ExpectType a) ->
(C.Clock dom -> C.Reset dom -> C.Enable dom -> ExpectType a -> ExpectType b) ->
(C.Clock dom -> C.Reset dom -> C.Enable dom -> Circuit a b) ->
(ExpectType b -> ExpectType b -> H.PropertyT IO ()) ->
H.Property
propWithModelSingleDomain eOpts genData model dut prop =
H.property $
propWithModelSingleDomainT eOpts genData model dut prop
propWithModelSingleDomainT ::
forall dom a b m.
( Test a
, Test b
, C.KnownDomain dom
, HasCallStack
, Monad m
, MonadIO m
, MonadBaseControl IO m
) =>
ExpectOptions ->
H.Gen (ExpectType a) ->
(C.Clock dom -> C.Reset dom -> C.Enable dom -> ExpectType a -> ExpectType b) ->
(C.Clock dom -> C.Reset dom -> C.Enable dom -> Circuit a b) ->
(ExpectType b -> ExpectType b -> H.PropertyT m ()) ->
H.PropertyT m ()
propWithModelSingleDomainT eOpts genData model0 circuit0 =
propWithModelT eOpts genData model1 circuit1
where
clk = C.clockGen
rst = resetGen (eoResetCycles eOpts)
ena = C.enableGen
model1 = model0 clk rst ena
circuit1 = circuit0 clk rst ena
idWithModelSingleDomain ::
forall dom a b.
(Test a, Test b, C.KnownDomain dom, HasCallStack) =>
ExpectOptions ->
H.Gen (ExpectType a) ->
(C.Clock dom -> C.Reset dom -> C.Enable dom -> ExpectType a -> ExpectType b) ->
(C.Clock dom -> C.Reset dom -> C.Enable dom -> Circuit a b) ->
H.Property
idWithModelSingleDomain eOpts genData model0 circuit0 =
H.property $
idWithModelSingleDomainT eOpts genData model0 circuit0
idWithModelSingleDomainT ::
forall dom a b m.
( Test a
, Test b
, C.KnownDomain dom
, HasCallStack
, Monad m
, MonadIO m
, MonadBaseControl IO m
) =>
ExpectOptions ->
H.Gen (ExpectType a) ->
(C.Clock dom -> C.Reset dom -> C.Enable dom -> ExpectType a -> ExpectType b) ->
(C.Clock dom -> C.Reset dom -> C.Enable dom -> Circuit a b) ->
H.PropertyT m ()
idWithModelSingleDomainT eOpts genData model0 circuit0 =
propWithModelSingleDomainT eOpts genData model0 circuit0 (===)
genStallMode :: H.Gen StallMode
genStallMode = Gen.enumBounded
genStallAck :: H.Gen StallAck
genStallAck = Gen.enumBounded
genStalls :: H.Gen Int -> Int -> StallMode -> H.Gen (StallAck, [Int])
genStalls genInt n = \case
NoStall -> (,[]) <$> genStallAck
Stall -> (,) <$> genStallAck <*> Gen.list (Range.singleton n) genInt