Skip to content

Commit f34e4f1

Browse files
committed
Add Functor and Applicative instances
1 parent 2193144 commit f34e4f1

File tree

7 files changed

+115
-44
lines changed

7 files changed

+115
-44
lines changed

clash-testbench/example/Main.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import qualified Calculator (topEntity)
99
myTestbench
1010
:: TB ()
1111
myTestbench = mdo
12-
input <- inputFromList Pop [Imm 1, Push, Imm 2, Push, Pop, Pop, Pop, ADD]
12+
input <- fromList Pop [Imm 1, Push, Imm 2, Push, Pop, Pop, Pop, ADD]
1313
output <- ("topEntity" @@ Calculator.topEntity) auto auto auto input
1414
watch input
1515
watch output

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Maintainer: QBayLogic B.V. <[email protected]>
66
Input sources for simulating 'TB' defined testbenches.
77
-}
88
module Clash.Testbench.Input
9-
( inputFromList
9+
( fromList
1010
) where
1111

1212
import Control.Monad.State.Lazy
@@ -25,17 +25,16 @@ import Clash.Testbench.Internal.ID
2525
-- the list is finite and the number of simulation steps exceeds the
2626
-- length of the list, then the value of the first argument is
2727
-- used instead.
28-
inputFromList
28+
fromList
2929
:: (KnownDomain dom, BitPack a, NFDataX a) => a -> [a] -> TB (TBSignal dom a)
30-
inputFromList x xs = do
31-
FreeID i <- nextFreeID
30+
fromList x xs = do
3231
ST{..} <- get
3332

3433
listRef <- liftIO $ newIORef $ x : xs
3534
simStepCache <- liftIO (readIORef simStepRef >>= newIORef)
3635

37-
registerTBS $ IOInput
38-
{ signalId = SignalID i
36+
mindSignal $ IOInput
37+
{ signalId = NoID
3938
, signalPrint = Nothing
4039
, signalCurVal = do
4140
(r, rs) <- fromMaybe (x, []) . uncons <$> readIORef listRef

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module Clash.Testbench.Internal.ID
1414
, isClockID
1515
, isResetID
1616
, isEnableID
17+
, isNoID
1718
) where
1819

1920
import Clash.Prelude (Type)
@@ -83,6 +84,9 @@ data ID (stage :: Stage) a where
8384
ClockID :: IDSource stage CLOCK -> ID stage CLOCK
8485
ResetID :: IDSource stage RESET -> ID stage RESET
8586
EnableID :: IDSource stage ENABLE -> ID stage ENABLE
87+
-- signals that result from higher order applications may not be
88+
-- explicitly available
89+
NoID :: ID stage SIGNAL
8690
-- wrapper type for passing different ID types around. Note that IDs
8791
-- of the free id pool cannot be passed around this way.
8892
SomeID :: (a ~ IDT a) => ID stage a -> ID stage ()
@@ -100,6 +104,7 @@ instance AnyStage 'USER where
100104
ClockID x -> f $ Right x
101105
ResetID x -> f $ Right x
102106
EnableID x -> f $ Right x
107+
NoID -> f $ Left (-1)
103108
SomeID s -> mapID f s
104109

105110
instance AnyStage 'FINAL where
@@ -108,6 +113,7 @@ instance AnyStage 'FINAL where
108113
ClockID x -> f $ Left x
109114
ResetID x -> f $ Left x
110115
EnableID x -> f $ Left x
116+
NoID -> f $ Left (-1)
111117
SomeID s -> mapID f s
112118

113119
instance Num (ID 'USER Int) where
@@ -128,7 +134,9 @@ instance Show (ID s Int) where
128134
show (FreeID x) = show x
129135

130136
instance Show (ID s SIGNAL) where
131-
show (SignalID x) = 's' : show x
137+
show = \case
138+
SignalID x -> 's' : show x
139+
NoID -> "-"
132140

133141
instance AnyStage s => Show (ID s CLOCK) where
134142
show x = 'c' : mapID showEither x
@@ -145,6 +153,7 @@ instance AnyStage s => Show (ID s ()) where
145153
ClockID{} -> show x
146154
ResetID{} -> show x
147155
EnableID{} -> show x
156+
NoID{} -> show x
148157

149158
showEither :: (Show a, Show b) => Either a b -> String
150159
showEither = \case
@@ -158,17 +167,28 @@ idToInt = \case
158167
ClockID x -> x
159168
ResetID x -> x
160169
EnableID x -> x
170+
NoID -> -1
161171
SomeID s -> idToInt s
162172

163173
-- | Checks whether the given ID is a signal identifier.
164174
isSignalID :: ID s a -> Bool
165175
isSignalID = \case
166176
SignalID{} -> True
177+
NoID{} -> True
167178
SomeID s -> case s of
168179
SignalID{} -> True
180+
NoID{} -> True
169181
_ -> False
170182
_ -> False
171183

184+
isNoID :: ID s a -> Bool
185+
isNoID = \case
186+
NoID{} -> True
187+
SomeID s -> case s of
188+
NoID{} -> True
189+
_ -> False
190+
_ -> False
191+
172192
-- | Checks whether the given ID is a clock identifier.
173193
isClockID :: ID s a -> Bool
174194
isClockID = \case

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

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ module Clash.Testbench.Internal.Monad where
22

33
import Control.Arrow (second)
44
import Control.Monad.State.Lazy (StateT, liftIO, get, gets, modify, forM, evalStateT)
5-
import Data.Set (Set, toList, member, insert)
65
import Data.IORef (IORef, newIORef, readIORef, writeIORef, modifyIORef)
76

87
import qualified Data.Map as M
@@ -47,13 +46,15 @@ instance Ord DomainSpecificIDSource where
4746
compare DSEnable{} _ = LT
4847
compare _ DSEnable{} = GT
4948

49+
type KnownSignals (s :: Stage) = S.Set (SomeSignal s)
50+
5051
data ST =
5152
ST
5253
{ idCount :: ID 'USER Int
53-
, signals :: Set (SomeSignal 'USER)
54+
, signals :: KnownSignals 'USER
5455
, simStepRef :: IORef Int
5556
, simMode :: IORef Simulator
56-
, domIds :: M.Map String (Set DomainSpecificIDSource)
57+
, domIds :: M.Map String (S.Set DomainSpecificIDSource)
5758
}
5859

5960
data Testbench =
@@ -68,7 +69,7 @@ instance Show ST where
6869
show ST{..} =
6970
"ST {"
7071
<> show idCount <> ", "
71-
<> show (toList signals)
72+
<> show (S.toAscList signals)
7273
<> "}"
7374

7475
-- | The 'TB' monad defines the context in which the test bench gets
@@ -89,15 +90,24 @@ nextFreeID = do
8990
modify $ \st -> st { idCount = i + 1 }
9091
return i
9192

92-
registerTBS ::
93+
mindSignal ::
9394
(NFDataX a, BitPack a, KnownDomain dom) =>
9495
TBSignal dom a ->
9596
TB (TBSignal dom a)
96-
registerTBS s = do
97-
let s' = SomeSignal s
98-
modify $ \st@ST{..} ->
99-
st { signals = if s' `member` signals then signals else insert s' signals }
100-
return s
97+
mindSignal s = case signalId s of
98+
NoID -> do
99+
FreeID i <- nextFreeID
100+
let s' = s { signalId = SignalID i }
101+
modify $ \st@ST{..} -> st { signals = S.insert (SomeSignal s') signals }
102+
return s'
103+
_ -> do
104+
let s' = SomeSignal s
105+
modify $ \st@ST{..} ->
106+
st { signals = S.insert s' $ case S.lookupIndex s' signals of
107+
Nothing -> signals
108+
Just i -> S.deleteAt i signals
109+
}
110+
return s
101111

102112
type family ArgOf a where
103113
ArgOf (a -> b) = a
@@ -128,20 +138,16 @@ instance
128138
where
129139
(@@) = defTBLift
130140

131-
liftTB name deps exec s = do
132-
FreeID i <- nextFreeID
141+
liftTB signalName (reverse -> signalDeps) exec signal = do
133142
mode <- simMode <$> get
134143
extVal <- liftIO $ newIORef Nothing
135144

136145
ST{..} <- get
137146
(signalRef, run) <- liftIO exec
138147
simStepCache <- liftIO (readIORef simStepRef >>= newIORef)
139148

140-
registerTBS $ Internal.TBSignal
141-
{ signal = s
142-
, signalId = SignalID i
143-
, signalDeps = reverse deps
144-
, signalName = name
149+
mindSignal $ Internal.TBSignal
150+
{ signalId = NoID
145151
, signalCurVal = do
146152
readIORef mode >>= \case
147153
Internal -> do
@@ -160,9 +166,10 @@ instance
160166
External -> readIORef extVal >>= \case
161167
Nothing -> error "No Value"
162168
Just x -> return x
163-
, signalUpdate = writeIORef extVal . Just
169+
, signalUpdate = Just (writeIORef extVal . Just)
164170
, signalPrint = Nothing
165171
, vpiInstance = Nothing
172+
, ..
166173
}
167174

168175
instance
@@ -231,13 +238,21 @@ runTB mode testbench = do
231238
ST { signals, simStepRef, simMode } <- get
232239
tbSignals <- forM (S.toAscList signals) $ \case
233240
SomeSignal s -> case s of
234-
(IOInput {signalId = SignalID x, ..} :: TBSignal dom a) ->
241+
(IOInput{..} :: TBSignal dom a) ->
235242
return $ SomeSignal
236-
(IOInput { signalId = SignalID x, .. } :: Internal.TBSignal 'FINAL dom a)
237-
Internal.TBSignal {signalId = SignalID x, ..} -> do
243+
( IOInput
244+
{ signalId = case signalId of
245+
NoID -> NoID
246+
SignalID x -> SignalID x
247+
, ..
248+
} :: Internal.TBSignal 'FINAL dom a
249+
)
250+
Internal.TBSignal{..} -> do
238251
deps <- mapM fixAutoDomIds signalDeps
239252
return $ SomeSignal $ Internal.TBSignal
240-
{ signalId = SignalID x
253+
{ signalId = case signalId of
254+
NoID -> NoID
255+
SignalID x -> SignalID x
241256
, signalDeps = deps
242257
, ..
243258
}
@@ -260,6 +275,7 @@ runTB mode testbench = do
260275

261276
fixAutoDomIds :: ID 'USER () -> TB (ID 'FINAL ())
262277
fixAutoDomIds (SomeID s) = case s of
278+
NoID -> return $ SomeID $ NoID
263279
SignalID x -> return $ SomeID $ SignalID x
264280
ClockID x -> updAutoDom DSClock (SomeID . ClockID) x
265281
ResetID x -> updAutoDom DSReset (SomeID . ResetID) x

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

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ data TBSignal (s :: Stage) (dom :: Domain) a =
3737
, signalName :: String
3838
, signal :: Signal dom a
3939
, signalCurVal :: IO a
40-
, signalUpdate :: a -> IO ()
40+
, signalUpdate :: Maybe (a -> IO ())
4141
, signalPrint :: Maybe (a -> String)
4242
, vpiInstance :: Maybe VPIInstance
4343
}
@@ -65,7 +65,49 @@ instance AnyStage s => Eq (TBSignal s dom a) where
6565
instance AnyStage s => Ord (TBSignal s dom a) where
6666
compare = compare `on` signalId
6767

68-
-----------
68+
instance Functor (TBSignal 'USER dom) where
69+
fmap f = \case
70+
TBSignal{..} ->
71+
TBSignal
72+
{ signalId = NoID
73+
, signal = fmap f signal
74+
, signalCurVal = f <$> signalCurVal
75+
-- We cannot update the values of a mapped signal, which
76+
-- makes sense, since a mapped signal cannot be simulated
77+
-- externally. It is always defined as the result of
78+
-- applying 'f' to the given source signal.
79+
, signalUpdate = Nothing
80+
-- we lose printing abilities at this point. This is fine,
81+
-- since printing capabilities are recovered automatically
82+
-- once the new signal gets watched.
83+
, signalPrint = Nothing
84+
, ..
85+
}
86+
IOInput{..} ->
87+
IOInput
88+
{ signalId = NoID
89+
, signalCurVal = f <$> signalCurVal
90+
-- we lose printing abilities at this point. This is fine,
91+
-- since printing capabilities are recovered automatically
92+
-- once the new signal gets watched.
93+
, signalPrint = Nothing
94+
, ..
95+
}
96+
97+
instance Applicative (TBSignal 'USER dom) where
98+
pure x =
99+
IOInput
100+
{ signalId = NoID
101+
, signalCurVal = pure x
102+
, signalPrint = Nothing
103+
}
104+
105+
f <*> s =
106+
IOInput
107+
{ signalId = NoID
108+
, signalCurVal = signalCurVal f <*> signalCurVal s
109+
, signalPrint = Nothing
110+
}
69111

70112
data TBClock (s :: Stage) (dom :: Domain) =
71113
TBClock

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

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ module Clash.Testbench.Output
1111
, watchWith
1212
) where
1313

14-
import Control.Monad.State.Lazy (modify)
15-
import Data.Set (lookupIndex, insert, deleteAt)
14+
import Control.Monad (void)
1615

1716
import Clash.Prelude (KnownDomain(..), BitPack(..), NFDataX)
1817

@@ -33,10 +32,4 @@ watchWith ::
3332
(KnownDomain dom, BitPack a, NFDataX a) =>
3433
(a -> String) -> TBSignal dom a -> TB ()
3534
watchWith toStr tbs =
36-
modify $ \st@ST{..} ->
37-
st { signals = case lookupIndex tbs' signals of
38-
Nothing -> insert tbs' signals
39-
Just i -> insert tbs' $ deleteAt i signals
40-
}
41-
where
42-
tbs' = SomeSignal $ tbs { signalPrint = Just toStr }
35+
void $ mindSignal tbs { signalPrint = Just toStr }

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ assignInputs = do
190190
ClockID _TODO -> sendV port vpiClock
191191
ResetID _TODO -> sendV port $ boolToBit vpiInit
192192
EnableID _TODO -> sendV port high
193-
SignalID _TODO
193+
_
194194
| vpiClock == high -> return ()
195195
| otherwise ->
196196
(`onAllSignalTypes` vpiSignal sid) $ \s ->
@@ -212,8 +212,9 @@ readOutputs = do
212212
Nothing -> error "Cannot read from module"
213213
Just VPIInstance{..} ->
214214
receiveValue VectorFmt (port vpiOutputPort) >>= \case
215-
BitVectorVal SNat v ->
216-
liftIO $ signalUpdate $ unpack $ resize v
215+
BitVectorVal SNat v -> case signalUpdate of
216+
Just upd -> liftIO $ upd $ unpack $ resize v
217+
Nothing -> error "No signal update"
217218
_ -> error "Unexpected return format"
218219

219220
-- print the watched signals

0 commit comments

Comments
 (0)