Skip to content

Commit

Permalink
BiDf: Introduce fanin
Browse files Browse the repository at this point in the history
This is a useful operation for servicing requests from a number of
sources with a single sink.
  • Loading branch information
bgamari committed Sep 9, 2024
1 parent 7faf4a7 commit 7bfa358
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 0 deletions.
39 changes: 39 additions & 0 deletions clash-protocols/src/Protocols/BiDf.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ module Protocols.BiDf (
loopback,
-- * Mapping
dimap,
-- * Fan-in
fanin
) where

import Prelude ()
Expand Down Expand Up @@ -100,3 +102,40 @@ dimap f g = circuit $ \biDf -> do
resp' <- Df.map g -< resp
(biDf', resp) <- fromDfs -< req'
idC -< biDf'

-- | Merge a number of 'BiDf's, preferring requests from the last channel.
fanin
:: forall n dom req resp.
( KnownNat n
, 1 <= n
, NFDataX req
, NFDataX resp
, HiddenClockResetEnable dom
)
=> Circuit (Vec n (BiDf dom req resp)) (BiDf dom req resp)
fanin = fromSignals $ \(upFwds, (reqAck, respData)) ->
let reqDatas :: Vec n (Signal dom (Df.Data req))
reqDatas = map fst upFwds
respAcks :: Vec n (Signal dom Ack)
respAcks = map snd upFwds

((reqAcks, respAck), (respDatas, reqData)) =
toSignals fanin' ((reqDatas, respData), (respAcks, reqAck))
in (zip reqAcks respDatas, (reqData, respAck))
where
fanin'
:: Circuit (Vec n (Df dom req), Df dom resp)
(Vec n (Df dom resp), Df dom req)
fanin' = circuit $ \(reqs, resp) -> do
[fwd0, fwd1]
<- Df.fanout
<| Df.roundrobinCollect @n Df.Parallel
<| Df.unbundleVec
<| Df.map (zip indicesI)
<| Df.bundleVec
-< reqs

activeN <- Df.map fst -< fwd1
resps <- Df.route <| Df.zip -< (activeN, resp)
req <- Df.map snd -< fwd0
idC -< (resps, req)
40 changes: 40 additions & 0 deletions clash-protocols/tests/Tests/Protocols/BiDf.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,46 @@ prop_loopback_id =
BiDf.loopback id -< biDf
idC -< resp

-- | Test that 'BiDf.fanin' on a single 'BiDf' channel behaves as an identity.
prop_fanin_id :: Property
prop_fanin_id =
idWithModelSingleDomain @System defExpectOptions gen (\_ _ _ -> id) (exposeClockResetEnable impl)
where
gen :: Gen [Int]
gen = Gen.list (Range.linear 0 10) (Gen.integral (Range.linear 0 100))

impl :: forall dom a. (HiddenClockResetEnable dom, NFDataX a)
=> Circuit (Df dom a) (Df dom a)
impl = circuit $ \req -> do
(biDf, resp) <- BiDf.fromDfs -< req
BiDf.loopback id <| BiDf.fanin @1 -< [biDf]
idC -< resp

-- | Test that 'BiDf.fanin' on a number of 'BiDf' channels behaves as an
-- identity on each channel.
prop_fanin :: Property
prop_fanin =
idWithModelSingleDomain @System expectOpts gen (\_ _ _ -> id) (exposeClockResetEnable impl)
where
expectOpts = defExpectOptions

gen :: Gen (Vec 10 [Int])
gen = genVec @Gen @10 $ Gen.list (Range.linear 0 10) (Gen.integral (Range.linear 0 100))

impl :: forall dom. (HiddenClockResetEnable dom)
=> Circuit (Vec 10 (Df dom Int)) (Vec 10 (Df dom Int))
impl = circuit $ \reqs -> do
(biDfs, resps) <- unbundleC <| repeatC BiDf.fromDfs -< reqs
BiDf.loopback id <| BiDf.fanin @10 -< biDfs
idC -< resps

unbundleC :: forall n a b. Circuit (Vec n (a, b)) (Vec n a, Vec n b)
unbundleC = fromSignals $ \(fwd, (bwdA, bwdB)) ->
let fwdA :: Vec n (Fwd a)
fwdB :: Vec n (Fwd b)
(fwdA, fwdB) = Vector.unzip fwd
in (Vector.zip bwdA bwdB, (fwdA, fwdB))

tests :: TestTree
tests =
$(testGroupGenerator)

0 comments on commit 7bfa358

Please sign in to comment.