Skip to content

Commit 3d207a5

Browse files
committed
Hedgehog Integration (WIP)
1 parent 4ded7fc commit 3d207a5

File tree

4 files changed

+236
-158
lines changed

4 files changed

+236
-158
lines changed

clash-testbench/src/Clash/Testbench/Generate.hs

+46-33
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module Clash.Testbench.Generate where
1212

1313
import Hedgehog
1414
import Hedgehog.Gen
15+
import Control.Monad.IO.Class (MonadIO)
1516
import Control.Monad.State.Lazy (liftIO, when, modify)
1617
import Data.IORef (newIORef, readIORef, writeIORef)
1718

@@ -102,7 +103,7 @@ matchIOGen ::
102103
forall dom i o.
103104
(NFDataX i, BitPack i, KnownDomain dom, Eq o, Show o) =>
104105
TBSignal dom o -> Gen (i, o) -> TB (TBSignal dom i)
105-
matchIOGen expectedOutput gen = do
106+
matchIOGen checkedOutput gen = do
106107
TBDomain{..} <- tbDomain @dom
107108

108109
vRef <- liftIO $ newIORef undefined
@@ -116,24 +117,30 @@ matchIOGen expectedOutput gen = do
116117

117118
if progress
118119
then do
119-
(i, o) <- sample gen
120+
(input, expectedOutput) <- sample gen
120121
curStep <- readIORef simStepRef
121-
signalExpect expectedOutput $ Expectation (curStep, verify o)
122-
writeIORef vRef i
123-
124-
return i
122+
signalExpect checkedOutput $ Expectation (curStep, verifier expectedOutput)
123+
writeIORef vRef input
124+
return input
125125
else
126126
readIORef vRef
127127
, signalPrint = Nothing
128128
, ..
129129
}
130130

131131
where
132-
verify x y = do
133-
when (x /= y)
134-
$ footnote
135-
$ "Expected '" <> show x <> "' but the output is '" <> show y <> "'"
136-
x === x
132+
verifier :: o -> o -> Verifier
133+
verifier expectedOutput observedOutput = Verifier $ \case
134+
Simple -> checkDifferenceWith error undefined
135+
Hedgehog -> checkDifferenceWith footnote (expectedOutput === observedOutput)
136+
where
137+
checkDifferenceWith :: MonadIO m => (String -> m ()) -> m () -> m ()
138+
checkDifferenceWith report abort =
139+
when (expectedOutput /= observedOutput) $ do
140+
report
141+
$ "Expected to see the output '" <> show expectedOutput <> "',"
142+
<> "but the observed output is '" <> show observedOutput <> "'."
143+
abort
137144

138145
-- | Extended version of 'matchIOGen', which allows to specify valid
139146
-- IO behavior over a finite amount of simulation steps. During native
@@ -168,15 +175,15 @@ matchIOGenN checkedOutput gen = mdo
168175
memorize signalHistory h
169176
writeIORef vRef ((i, o) : xr)
170177
curStep <- readIORef simStepRef
171-
signalExpect checkedOutput $ Expectation (curStep, verify s i o)
178+
signalExpect checkedOutput $ Expectation (curStep, verifier s i o)
172179
return i
173180
[(h, _)] -> do
174181
memorize signalHistory h
175182
(i, o) : xr <- sample gen
176183

177184
writeIORef vRef ((i, o) : xr)
178185
curStep <- readIORef simStepRef
179-
signalExpect checkedOutput $ Expectation (curStep, verify s i o)
186+
signalExpect checkedOutput $ Expectation (curStep, verifier s i o)
180187
return i
181188
_ -> error "unreachable"
182189
else \case
@@ -193,8 +200,14 @@ matchIOGenN checkedOutput gen = mdo
193200
return s
194201

195202
where
196-
verify generatedInput currentInput expectedOutput observedOutput = do
197-
when (expectedOutput /= observedOutput) $ do
203+
verifier :: TBSignal dom i -> i -> o -> o -> Verifier
204+
verifier generatedInput currentInput expectedOutput observedOutput =
205+
Verifier $ \case
206+
Simple -> checkDifferenceWith error undefined
207+
Hedgehog -> checkDifferenceWith footnote failure
208+
where
209+
checkDifferenceWith :: MonadIO m => (String -> m ()) -> m () -> m ()
210+
checkDifferenceWith report abort = do
198211
xs <-
199212
(<> [(currentInput, observedOutput)])
200213
<$> (zip <$> history generatedInput <*> history checkedOutput)
@@ -207,21 +220,21 @@ matchIOGenN checkedOutput gen = mdo
207220
iLen = maximum $ (length iHeading :) $ fmap (length . show . fst) xs
208221
oLen = maximum $ (length oHeading :) $ fmap (length . show . snd) xs
209222

210-
footnote $ unlines $
211-
[ "Expected to see the output '" <> show expectedOutput <> "',"
212-
, "but the observed output is '" <> show observedOutput <> "'."
213-
, ""
214-
, "I/O History:"
215-
, ""
216-
, cHeading <>
217-
replicate (iLen - length iHeading + 2) ' ' <> iHeading <>
218-
replicate (oLen - length oHeading + 2) ' ' <> oHeading
219-
, replicate (cLen + iLen + oLen + 4) '-'
220-
] <>
221-
[ replicate (cLen - length (show c)) ' ' <> show c <>
222-
replicate (iLen - length (show i) + 2) ' ' <> show i <>
223-
replicate (oLen - length (show o) + 2) ' ' <> show o
224-
| (c, (i, o)) <- zip [0 :: Int,1..] xs
225-
]
226-
227-
failure
223+
when (expectedOutput /= observedOutput) $ do
224+
report $ unlines $
225+
[ "Expected to see the output '" <> show expectedOutput <> "',"
226+
, "but the observed output is '" <> show observedOutput <> "'."
227+
, ""
228+
, "I/O History:"
229+
, ""
230+
, cHeading <>
231+
replicate (iLen - length iHeading + 2) ' ' <> iHeading <>
232+
replicate (oLen - length oHeading + 2) ' ' <> oHeading
233+
, replicate (cLen + iLen + oLen + 4) '-'
234+
] <>
235+
[ replicate (cLen - length (show c)) ' ' <> show c <>
236+
replicate (iLen - length (show i) + 2) ' ' <> show i <>
237+
replicate (oLen - length (show o) + 2) ' ' <> show o
238+
| (c, (i, o)) <- zip [0 :: Int,1..] xs
239+
]
240+
abort

clash-testbench/src/Clash/Testbench/Internal/Monad.hs

+8-10
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import Data.IORef (IORef, newIORef, readIORef, writeIORef, modifyIORef)
4242
import Data.List (partition, sort, sortBy, groupBy)
4343
import Data.Maybe (catMaybes)
4444

45-
import qualified Data.Map as M
45+
import qualified Data.Map.Strict as M
4646
import qualified Data.Set as S
4747
import qualified Data.Array as A
4848

@@ -243,18 +243,17 @@ instance
243243
, signalName = name
244244
, signalUpdate = Just (writeIORef extVal . Just)
245245
, signalExpect = modifyIORef expectations . (:)
246-
, signalVerify = \mode -> do
246+
, signalVerify = \sMode -> Verifier $ \vMode -> do
247247
curStep <- liftIO $ readIORef simStepRef
248-
value <- liftIO $ signalCurVal mode
248+
value <- liftIO $ signalCurVal sMode
249249
expct <- liftIO $ readIORef expectations
250250

251251
let
252252
(current, later) =
253253
partition (`leq` Expectation (curStep + 1, undefined)) expct
254254

255255
liftIO $ writeIORef expectations later
256-
mapM_ ((value &) . snd . expectation) current
257-
256+
mapM_ ((`verifier` vMode) . (value &) . snd . expectation) current
258257
, signalPrint = Nothing
259258
, signalPlug = Nothing
260259
, ..
@@ -449,7 +448,6 @@ initializeLiftTB name x = liftTB accInit x
449448
progressCheck :: IORef Int -> Bool -> TB (IO Bool)
450449
progressCheck simStepRef initialProgress = do
451450
simStepCache <- liftIO ((offset <$> readIORef simStepRef) >>= newIORef)
452-
453451
return $ do
454452
globalRef <- readIORef simStepRef
455453
localRef <- readIORef simStepCache
@@ -458,13 +456,12 @@ progressCheck simStepRef initialProgress = do
458456
writeIORef simStepCache globalRef
459457

460458
return $ globalRef > localRef
461-
462459
where
463460
offset
464461
| initialProgress = (+ (-1))
465462
| otherwise = id
466463

467-
464+
-- | Creates a new 'History' container.
468465
newHistory ::
469466
TB (History a)
470467
newHistory = do
@@ -474,6 +471,7 @@ newHistory = do
474471
historyBuffer <- liftIO $ newIORef Nothing
475472
return History{..}
476473

474+
-- | Memorizes a value inside the given 'History' container.
477475
memorize :: MonadIO m => History a -> a -> m ()
478476
memorize History{..} value =
479477
liftIO $ readIORef historySize >>= \case
@@ -490,6 +488,8 @@ memorize History{..} value =
490488
writeArray buf pos $ Just value
491489
writeIORef historyBufferPos $ pos + 1
492490

491+
-- | Reveals the history of a test bench signal. The returned list is
492+
-- given in temporal order.
493493
history ::
494494
(KnownDomain dom, MonadIO m) =>
495495
TBSignal dom a ->
@@ -499,11 +499,9 @@ history s = liftIO $ readIORef historyBuffer >>= \case
499499
Just buf -> do
500500
pos <- readIORef historyBufferPos
501501
catMaybes . uncurry (flip (<>)) . splitAt pos <$> getElems buf
502-
503502
where
504503
History{..} = signalHistory s
505504

506-
507505
-- | Some generalized extender for the accumulated continuation.
508506
extendVia ::
509507
Monad m =>

clash-testbench/src/Clash/Testbench/Internal/Signal.hs

+44-17
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ Lifted signal types and internal data structures for
99
module Clash.Testbench.Internal.Signal where
1010

1111
import Algebra.PartialOrd
12+
import Control.Monad.IO.Class (MonadIO)
13+
import Data.Array.IO (IOArray)
1214
import Data.Function (on)
13-
15+
import Data.IORef (IORef)
1416
import Hedgehog (PropertyT)
1517

16-
import Data.Array.IO (IOArray)
17-
import Data.IORef (IORef)
1818
import Clash.Prelude
1919
( KnownDomain(..), BitPack(..), SDomainConfiguration(..), NFDataX, Type
2020
, Domain, Signal, Clock, Reset, Enable
@@ -23,6 +23,7 @@ import Clash.Prelude
2323

2424
import Clash.FFI.VPI.Module (Module)
2525
import Clash.FFI.VPI.Port (Port, Direction)
26+
2627
import Clash.Testbench.Internal.ID
2728

2829
-- | Test bench design stages
@@ -38,12 +39,12 @@ data Stage :: Type where
3839
-- successfully. Post-processing also introduces the switch from
3940
-- 'USER' to 'FINAL' on the type level.
4041

41-
-- | The supported simulation modes sources.
42-
data SimMode where
43-
Internal :: SimMode
44-
-- ^ Internal pure Haskell based simulation
45-
External :: SimMode
46-
-- ^ Co-Simulation via Clash-FFI
42+
-- | Supported simulation modes sources
43+
data SimMode =
44+
Internal
45+
-- ^ Internal pure Haskell based simulation
46+
| External
47+
-- ^ Co-Simulation via Clash-FFI
4748

4849
-- | Type family for handling simulation mode dependent types.
4950
-- 'SimMode' does not have to be fixed during test bench creation, but
@@ -53,7 +54,7 @@ type family SimModeDependent (s :: Stage) a where
5354
SimModeDependent 'USER a = SimMode -> a
5455
SimModeDependent 'FINAL a = a
5556

56-
-- | Clash-FFI Port connector.
57+
-- | Clash-FFI port connector
5758
data PortInterface =
5859
PortInterface
5960
{ port :: Port
@@ -63,24 +64,25 @@ data PortInterface =
6364
, portDirection :: Direction
6465
}
6566

66-
-- | Clash-FFI Module connector.
67+
-- | Clash-FFI module connector
6768
data ModuleInterface =
6869
ModuleInterface
69-
{ module_ :: Module
70+
{ module_ :: Module
7071
, inputPort :: ID () -> PortInterface
7172
-- TODO: multiple port support vie Bundle/Unbundle
7273
, outputPort :: PortInterface
7374
}
7475

76+
-- | Size bounded signal history
7577
data History a =
7678
History
77-
{ historySize :: IORef Int
79+
{ historySize :: IORef Int
7880
, historyBufferPos :: IORef Int
79-
, historyBuffer :: IORef (Maybe (IOArray Int (Maybe a)))
81+
, historyBuffer :: IORef (Maybe (IOArray Int (Maybe a)))
8082
}
8183

82-
-- | Expectations on certain outputs at the given simulation step.
83-
newtype Expectation a = Expectation { expectation :: (Int, a -> PropertyT IO ()) }
84+
-- | Expectations on certain outputs at the given simulation step
85+
newtype Expectation a = Expectation { expectation :: (Int, a -> Verifier) }
8486

8587
-- | Expectations cannot be compared: they are always unequal.
8688
instance Eq (Expectation a) where
@@ -92,6 +94,31 @@ instance PartialOrd (Expectation a) where
9294
leq (Expectation (x, _)) (Expectation (y, _)) = x <= y
9395
comparable (Expectation (x, _)) (Expectation (y, _)) = x /= y
9496

97+
-- | The verification mode determines the environment in which a
98+
-- verifier is executed.
99+
data VerificationMode m where
100+
Simple :: VerificationMode IO
101+
Hedgehog :: VerificationMode (PropertyT IO)
102+
103+
-- | Existential quantified container for passing different
104+
-- verification environments around.
105+
data Verifier =
106+
Verifier
107+
{ verifier :: (forall m. MonadIO m => VerificationMode m -> m ())
108+
}
109+
110+
-- | Runs a verifier in a supported verification environment.
111+
class Verify m where
112+
verify :: Verifier -> m ()
113+
114+
instance Verify IO where
115+
verify = \case
116+
Verifier v -> v Simple
117+
118+
instance Verify (PropertyT IO) where
119+
verify = \case
120+
Verifier v -> v Hedgehog
121+
95122
-- | The lifted 'Clash.Signal.Signal' type to be used in
96123
-- 'Clash.Testbench.Internal.Monad.TB'.
97124
data TBSignal (s :: Stage) (dom :: Domain) a =
@@ -116,7 +143,7 @@ data TBSignal (s :: Stage) (dom :: Domain) a =
116143
, signalExpect :: Expectation a -> IO ()
117144
-- ^ Registers an expectation on the content of this signal to
118145
-- be verified during simulation
119-
, signalVerify :: SimModeDependent s (PropertyT IO ())
146+
, signalVerify :: SimModeDependent s Verifier
120147
-- ^ The expectation verifier
121148
, signalHistory :: History a
122149
-- ^ Bounded history of signal values

0 commit comments

Comments
 (0)