|
| 1 | +-- SPDX-FileCopyrightText: 2026 Google LLC |
| 2 | +-- |
| 3 | +-- SPDX-License-Identifier: Apache-2.0 |
| 4 | + |
| 5 | +{- | |
| 6 | +Contains the `ReqResp` protocol, the simplest possible protocol for request-response communication |
| 7 | +together with utilities for working with it. `ReqResp` is suitable for simple request-response |
| 8 | +interactions where pipelining is not required. If you need pipelining, you can use the `BiDf` |
| 9 | +protocol instead. |
| 10 | +-} |
| 11 | +module Protocols.ReqResp ( |
| 12 | + -- * Types |
| 13 | + ReqResp, |
| 14 | + |
| 15 | + -- * Converters |
| 16 | + fromDfs, |
| 17 | + toDfs, |
| 18 | + fromBiDf, |
| 19 | + toBiDf, |
| 20 | + requests, |
| 21 | + dropResponse, |
| 22 | + |
| 23 | + -- * Blockram interfaces |
| 24 | + fromBlockRam, |
| 25 | + fromBlockRamWithMask, |
| 26 | + |
| 27 | + -- * Other utilities |
| 28 | + partitionEithers, |
| 29 | + forceResetSanity, |
| 30 | +) where |
| 31 | + |
| 32 | +import Clash.Prelude |
| 33 | + |
| 34 | +import Data.Bifunctor (Bifunctor (..)) |
| 35 | +import Data.Maybe |
| 36 | +import Protocols |
| 37 | +import Protocols.BiDf (BiDf) |
| 38 | +import Protocols.Idle |
| 39 | + |
| 40 | +import qualified Clash.Prelude as C |
| 41 | +import qualified Protocols.BiDf as BiDf |
| 42 | + |
| 43 | +{- | |
| 44 | +Simplest possible protocol for request-response communication. |
| 45 | +
|
| 46 | +The forward channel channel has type @Signal dom (Maybe req)@ and is used to send requests and |
| 47 | +the backward channel has type @Signal dom (Maybe resp)@ and is used to send responses. This protocol |
| 48 | +can not be pipelined because the request is acknowledged by the response. If you wish to utilize |
| 49 | +pipelining, you can use the `BiDf` protocol instead. |
| 50 | +
|
| 51 | +The protocol must obey the following rules: |
| 52 | +* When the forward channel is @Just a@, it must not change until the transaction is completed. |
| 53 | +* The forward channel can not depend on the backward channel. |
| 54 | +* When the forward channel is @Nothing@, the backward channel must not be observed. |
| 55 | +-} |
| 56 | +data ReqResp (dom :: C.Domain) (req :: Type) (resp :: Type) |
| 57 | + |
| 58 | +instance Protocol (ReqResp dom req resp) where |
| 59 | + -- \| Request channel for ReqResp protocol |
| 60 | + type Fwd (ReqResp dom req resp) = C.Signal dom (Maybe req) |
| 61 | + |
| 62 | + -- \| Response channel for ReqResp protocol |
| 63 | + type Bwd (ReqResp dom req resp) = C.Signal dom (Maybe resp) |
| 64 | + |
| 65 | +instance IdleCircuit (ReqResp dom req resp) where |
| 66 | + idleFwd _ = pure Nothing |
| 67 | + idleBwd _ = pure Nothing |
| 68 | + |
| 69 | +leftToMaybe :: Either a b -> Maybe a |
| 70 | +leftToMaybe (Left x) = Just x |
| 71 | +leftToMaybe (Right _) = Nothing |
| 72 | +rightToMaybe :: Either a b -> Maybe b |
| 73 | +rightToMaybe (Left _) = Nothing |
| 74 | +rightToMaybe (Right y) = Just y |
| 75 | + |
| 76 | +-- | Partition a `ReqResp` with an `Either` request type into two `ReqResp`s, one for each side of the `Either`. |
| 77 | +partitionEithers :: |
| 78 | + forall dom a b c. Circuit (ReqResp dom (Either a b) c) (ReqResp dom a c, ReqResp dom b c) |
| 79 | +partitionEithers = Circuit goS |
| 80 | + where |
| 81 | + goS (eitherFwd, (leftBwd, rightBwd)) = (eitherBwd, (leftFwd, rightFwd)) |
| 82 | + where |
| 83 | + leftFwd = fmap (>>= leftToMaybe) eitherFwd |
| 84 | + rightFwd = fmap (>>= rightToMaybe) eitherFwd |
| 85 | + |
| 86 | + eitherBwd = selectBwd <$> eitherFwd <*> leftBwd <*> rightBwd |
| 87 | + selectBwd (Just (Left _)) leftBwd _ = leftBwd |
| 88 | + selectBwd (Just (Right _)) _ rightBwd = rightBwd |
| 89 | + selectBwd Nothing _ _ = Nothing |
| 90 | + |
| 91 | +{- | Given a 'blockRam' primitive, create a circuit that offers a 'ReqResp' interface to access |
| 92 | +the primitive using 'ReqResp' for the read channel and 'Df' for the write channel. |
| 93 | +
|
| 94 | +If you use 'ReqResp' for the write channel, you can use 'requests' to convert it to a 'Df' stream |
| 95 | +to be used with this circuit. |
| 96 | +-} |
| 97 | +fromBlockRamWithMask :: |
| 98 | + (C.KnownDomain dom, C.HiddenClock dom, C.HiddenReset dom, Num addr, C.KnownNat words) => |
| 99 | + ( C.Signal dom addr -> |
| 100 | + C.Signal dom (Maybe (addr, C.BitVector (words C.* 8))) -> |
| 101 | + C.Signal dom (C.BitVector words) -> |
| 102 | + C.Signal dom (C.BitVector (words C.* 8)) |
| 103 | + ) -> |
| 104 | + Circuit |
| 105 | + ( ReqResp dom addr (C.BitVector (words C.* 8)) |
| 106 | + , Df dom (addr, C.BitVector words, C.BitVector (words C.* 8)) |
| 107 | + ) |
| 108 | + () |
| 109 | +fromBlockRamWithMask primitive = Circuit go |
| 110 | + where |
| 111 | + writeBwd = pure $ Ack True |
| 112 | + go ((readFwd, writeFwd), _) = ((readBwd, writeBwd), ()) |
| 113 | + where |
| 114 | + -- Separate the write data and byte enables |
| 115 | + writeData = fmap (>>= \(addr, _mask, dat) -> Just (addr, dat)) writeFwd |
| 116 | + byteEnables = fmap (\case Just (_, mask, _) -> mask; Nothing -> 0) writeFwd |
| 117 | + |
| 118 | + readData = primitive (fromMaybe 0 <$> readFwd) writeData byteEnables |
| 119 | + |
| 120 | + -- Reading takes 1 cycle so we run at half speed |
| 121 | + readValid = C.withEnable C.enableGen C.register False (fmap isJust readFwd C..&&. fmap not readValid) |
| 122 | + readBwd = liftA2 (\v d -> if v then Just d else Nothing) readValid readData |
| 123 | + |
| 124 | +{- | Given a 'blockRam' primitive, create a circuit that offers a 'ReqResp' interface to access |
| 125 | +the primitive using 'ReqResp' for the read channel and 'Df' for the write channel. |
| 126 | +This is a simpler version of 'fromBlockRamWithMask' that doesn't support byte enables. |
| 127 | +-} |
| 128 | +fromBlockRam :: |
| 129 | + (C.KnownDomain dom, C.HiddenClock dom, C.HiddenReset dom, Num addr) => |
| 130 | + (C.Signal dom addr -> C.Signal dom (Maybe (addr, a)) -> C.Signal dom a) -> |
| 131 | + Circuit (ReqResp dom addr a, Df dom (addr, a)) () |
| 132 | +fromBlockRam primitive = Circuit go |
| 133 | + where |
| 134 | + writeBwd = pure $ Ack True |
| 135 | + go ((readFwd, writeFwd), _) = ((readBwd, writeBwd), ()) |
| 136 | + where |
| 137 | + readData = primitive (fromMaybe 0 <$> readFwd) writeFwd |
| 138 | + |
| 139 | + -- Reading takes 1 cycle so we run at half speed |
| 140 | + readValid = C.withEnable C.enableGen C.register False (fmap isJust readFwd C..&&. fmap not readValid) |
| 141 | + readBwd = liftA2 (\v d -> if v then Just d else Nothing) readValid readData |
| 142 | + |
| 143 | +{- | Forces a constant response on the backward channel. Useful for when you don't care |
| 144 | +about the response data. |
| 145 | +-} |
| 146 | +dropResponse :: resp -> Circuit (ReqResp dom req resp) (ReqResp dom req ()) |
| 147 | +dropResponse resp = applyC id (fmap $ fmap $ const resp) |
| 148 | + |
| 149 | +{- | Force a @Nothing@ on the backward channel and @Nothing@ on the forward |
| 150 | +channel if reset is asserted. |
| 151 | +-} |
| 152 | +forceResetSanity :: |
| 153 | + forall dom req resp. |
| 154 | + (C.HiddenReset dom) => |
| 155 | + Circuit (ReqResp dom req resp) (ReqResp dom req resp) |
| 156 | +forceResetSanity = forceResetSanityGeneric |
| 157 | + |
| 158 | +-- | Convert a `ReqResp` protocol to two `Df` streams, one for requests and one for responses. |
| 159 | +toDfs :: |
| 160 | + forall dom req resp. |
| 161 | + (C.HiddenClockResetEnable dom) => |
| 162 | + Circuit (ReqResp dom req resp, Df dom resp) (Df dom req) |
| 163 | +toDfs = ckt |
| 164 | + where |
| 165 | + ckt = Circuit (first C.unbundle . C.unbundle . C.mealy go Nothing . C.bundle . first C.bundle) |
| 166 | + go Nothing _ = (Just False, ((Nothing, Ack False), Nothing)) |
| 167 | + go (Just accepted0) ~(~(reqLeft, resp), ~(Ack reqRightAck)) = (Just accepted1, ((resp, respAck), reqRight)) |
| 168 | + where |
| 169 | + respAck = Ack True |
| 170 | + |
| 171 | + reqRight |
| 172 | + | accepted0 = Nothing |
| 173 | + | otherwise = reqLeft |
| 174 | + |
| 175 | + accepted1 |
| 176 | + | isNothing reqLeft = False -- No request to accept |
| 177 | + | isJust resp = False -- Receiving a response clears the state |
| 178 | + | isJust reqRight = reqRightAck -- A request for which we have not received a response yet |
| 179 | + | otherwise = accepted0 |
| 180 | + |
| 181 | +-- | Convert two `Df` streams for requests and responses into a `ReqResp` protocol. |
| 182 | +fromDfs :: |
| 183 | + forall dom req resp. |
| 184 | + (C.HiddenClockResetEnable dom, C.NFDataX resp) => |
| 185 | + Circuit (Df dom req) (ReqResp dom req resp, Df dom resp) |
| 186 | +fromDfs = Circuit (second C.unbundle . C.unbundle . C.mealy go Nothing . C.bundle . second C.bundle) |
| 187 | + where |
| 188 | + go Nothing ~(req, ~(resp, Ack ack)) = (nextState, (Ack (isJust resp), (req, resp))) |
| 189 | + where |
| 190 | + nextState |
| 191 | + | isJust resp && not ack = resp |
| 192 | + | otherwise = Nothing |
| 193 | + go stored (_, (_, Ack ack)) = (nextState, (Ack False, (Nothing, stored))) |
| 194 | + where |
| 195 | + nextState |
| 196 | + | ack = Nothing |
| 197 | + | otherwise = stored |
| 198 | + |
| 199 | +-- | Convert a `ReqResp` protocol to a `BiDf` protocol through `toDfs` and `BiDf.fromDfs`. |
| 200 | +toBiDf :: |
| 201 | + forall dom req resp. |
| 202 | + (C.HiddenClockResetEnable dom) => |
| 203 | + Circuit (ReqResp dom req resp) (BiDf dom req resp) |
| 204 | +toBiDf = circuit $ \reqresp -> do |
| 205 | + request <- toDfs -< (reqresp, response) |
| 206 | + (biDf, response) <- BiDf.fromDfs -< request |
| 207 | + idC -< biDf |
| 208 | + |
| 209 | +-- | Convert a `BiDf` protocol to a `ReqResp` protocol through `fromDfs` and `BiDf.toDfs`. |
| 210 | +fromBiDf :: |
| 211 | + forall dom req resp. |
| 212 | + (C.HiddenClockResetEnable dom, C.NFDataX resp) => |
| 213 | + Circuit (BiDf dom req resp) (ReqResp dom req resp) |
| 214 | +fromBiDf = circuit $ \biDf -> do |
| 215 | + request <- BiDf.toDfs -< (biDf, response) |
| 216 | + (reqresp, response) <- fromDfs -< request |
| 217 | + idC -< reqresp |
| 218 | + |
| 219 | +-- | Convert a 'ReqResp' protocol where the response type is '()' to a 'Df' stream of requests. |
| 220 | +requests :: |
| 221 | + forall dom req. |
| 222 | + (C.KnownDomain dom) => |
| 223 | + Circuit (ReqResp dom req ()) (Df dom req) |
| 224 | +requests = Circuit (C.unbundle . fmap go . C.bundle) |
| 225 | + where |
| 226 | + go ~(request, Ack ack) = (if ack then Just () else Nothing, request) |
0 commit comments