From b3b0ad65da5c7890692e9c634b476a049318354e Mon Sep 17 00:00:00 2001 From: Rowan Goemans Date: Wed, 3 Jul 2024 15:56:17 +0200 Subject: [PATCH 01/63] Add PacketStream protocol. Co-authored-by: Jasmijn Bookelmann Co-authored-by: Cato van Ojen > Co-authored-by: MatthijsMu <93450301+MatthijsMu@users.noreply.github.com> Co-authored-by: t-wallet Co-authored-by: Jasper Laumen <96011116+JLaumen@users.noreply.github.com> Co-authored-by: Mart Koster <30956441+Akribes@users.noreply.github.com> Co-authored-by: Bryan Rinders Co-authored-by: Daan Weessies --- clash-protocols/clash-protocols.cabal | 17 + .../src/Clash/Sized/Vector/Extra.hs | 59 ++ clash-protocols/src/Data/Maybe/Extra.hs | 8 + .../src/Protocols/PacketStream/AsyncFifo.hs | 60 ++ .../src/Protocols/PacketStream/Base.hs | 212 ++++++ .../src/Protocols/PacketStream/Converters.hs | 235 +++++++ .../src/Protocols/PacketStream/PacketFifo.hs | 186 +++++ .../src/Protocols/PacketStream/Packetizers.hs | 652 ++++++++++++++++++ .../src/Protocols/PacketStream/Routing.hs | 92 +++ clash-protocols/tests/Tests/Protocols.hs | 14 +- .../tests/Tests/Protocols/PacketStream.hs | 20 + .../Tests/Protocols/PacketStream/AsyncFifo.hs | 127 ++++ .../Tests/Protocols/PacketStream/Base.hs | 188 +++++ .../Protocols/PacketStream/Converters.hs | 118 ++++ .../Protocols/PacketStream/PacketFifo.hs | 182 +++++ .../Protocols/PacketStream/Packetizers.hs | 171 +++++ .../Tests/Protocols/PacketStream/Routing.hs | 159 +++++ 17 files changed, 2494 insertions(+), 6 deletions(-) create mode 100644 clash-protocols/src/Clash/Sized/Vector/Extra.hs create mode 100644 clash-protocols/src/Data/Maybe/Extra.hs create mode 100644 clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs create mode 100644 clash-protocols/src/Protocols/PacketStream/Base.hs create mode 100644 clash-protocols/src/Protocols/PacketStream/Converters.hs create mode 100644 clash-protocols/src/Protocols/PacketStream/PacketFifo.hs create mode 100644 clash-protocols/src/Protocols/PacketStream/Packetizers.hs create mode 100644 clash-protocols/src/Protocols/PacketStream/Routing.hs create mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream.hs create mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs create mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs create mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs create mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs create mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs create mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs diff --git a/clash-protocols/clash-protocols.cabal b/clash-protocols/clash-protocols.cabal index 00386f30..29673955 100644 --- a/clash-protocols/clash-protocols.cabal +++ b/clash-protocols/clash-protocols.cabal @@ -77,6 +77,7 @@ common common-options -fplugin GHC.TypeLits.Extra.Solver -fplugin GHC.TypeLits.Normalise -fplugin GHC.TypeLits.KnownNat.Solver + -fconstraint-solver-iterations=6 -- Clash needs access to the source code in compiled modules -fexpose-all-unfoldings @@ -144,6 +145,12 @@ library Protocols.Axi4.WriteAddress Protocols.Axi4.WriteData Protocols.Axi4.WriteResponse + Protocols.PacketStream.Base + Protocols.PacketStream.AsyncFifo + Protocols.PacketStream.Converters + Protocols.PacketStream.PacketFifo + Protocols.PacketStream.Packetizers + Protocols.PacketStream.Routing Protocols.Df Protocols.DfConv Protocols.Hedgehog @@ -166,6 +173,8 @@ library autogen-modules: Paths_clash_protocols other-modules: + Data.Maybe.Extra + Clash.Sized.Vector.Extra Paths_clash_protocols Protocols.Hedgehog.Types Protocols.Internal.Types @@ -187,6 +196,14 @@ test-suite unittests Tests.Protocols.Plugin Tests.Protocols.Vec Tests.Protocols.Wishbone + Tests.Protocols.PacketStream + Tests.Protocols.PacketStream.AsyncFifo + Tests.Protocols.PacketStream.Base + Tests.Protocols.PacketStream.Converters + Tests.Protocols.PacketStream.Packetizers + Tests.Protocols.PacketStream.PacketFifo + Tests.Protocols.PacketStream.Routing + Util build-depends: diff --git a/clash-protocols/src/Clash/Sized/Vector/Extra.hs b/clash-protocols/src/Clash/Sized/Vector/Extra.hs new file mode 100644 index 00000000..fc801654 --- /dev/null +++ b/clash-protocols/src/Clash/Sized/Vector/Extra.hs @@ -0,0 +1,59 @@ +{-# LANGUAGE NoImplicitPrelude #-} + +module Clash.Sized.Vector.Extra ( + dropLe, + takeLe, + appendVec, +) where + +import Clash.Prelude + +-- | Like 'drop' but uses a 'Data.Type.Ord.<=' constraint +dropLe :: + forall + (n :: Nat) + (m :: Nat) + (a :: Type). + (n <= m) => + -- | How many elements to take + SNat n -> + -- | input vector + Vec m a -> + Vec (m - n) a +dropLe SNat vs = leToPlus @n @m $ dropI vs + +-- | Like 'take' but uses a 'Data.Type.Ord.<=' constraint +takeLe :: + forall + (n :: Nat) + (m :: Nat) + (a :: Type). + (n <= m) => + -- | How many elements to take + SNat n -> + -- | input vector + Vec m a -> + Vec n a +takeLe SNat vs = leToPlus @n @m $ takeI vs + +-- | Take the first 'valid' elements of 'xs', append 'ys', then pad with 0s +appendVec :: + forall n m a. + (KnownNat n) => + (Num a) => + Index n -> + Vec n a -> + Vec m a -> + Vec (n + m) a +appendVec valid xs ys = results !! valid + where + go :: forall l. SNat l -> Vec (n + m) a + go l@SNat = + let f = addSNat l d1 + in case compareSNat f (SNat @n) of + SNatLE -> takeLe (addSNat l d1) xs ++ ys ++ extra + where + extra :: Vec (n - (l + 1)) a + extra = repeat 0 + _ -> error "appendVec: Absurd" + results = smap (\s _ -> go s) xs diff --git a/clash-protocols/src/Data/Maybe/Extra.hs b/clash-protocols/src/Data/Maybe/Extra.hs new file mode 100644 index 00000000..84847c51 --- /dev/null +++ b/clash-protocols/src/Data/Maybe/Extra.hs @@ -0,0 +1,8 @@ +module Data.Maybe.Extra ( + toMaybe, +) where + +-- | Wrap a value in a Just if True +toMaybe :: Bool -> a -> Maybe a +toMaybe True x = Just x +toMaybe False _ = Nothing diff --git a/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs new file mode 100644 index 00000000..a5bb39b9 --- /dev/null +++ b/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs @@ -0,0 +1,60 @@ +{-# LANGUAGE NoImplicitPrelude #-} + +{- | +Provides `asyncFifoC` for crossing clock domains in the packet stream protocol. +-} +module Protocols.PacketStream.AsyncFifo (asyncFifoC) where + +import Data.Maybe.Extra (toMaybe) + +import Clash.Explicit.Prelude (asyncFIFOSynchronizer) +import Clash.Prelude + +import Protocols.Internal (Circuit, fromSignals) +import Protocols.PacketStream.Base + +{- | Asynchronous FIFO circuit that can be used to safely cross clock domains. +Uses `Clash.Explicit.Prelude.asyncFIFOSynchronizer` internally. +-} +asyncFifoC :: + forall + (depth :: Nat) + (dataWidth :: Nat) + (wDom :: Domain) + (rDom :: Domain) + (metaType :: Type). + ( KnownDomain wDom + , KnownDomain rDom + , KnownNat depth + , 2 <= depth + , KnownNat dataWidth + , 1 <= dataWidth + , NFDataX metaType + ) => + -- | 2^depth is the number of elements this component can store + SNat depth -> + -- | Clock signal in the write domain + Clock wDom -> + -- | Reset signal in the write domain + Reset wDom -> + -- | Enable signal in the write domain + Enable wDom -> + -- | Clock signal in the read domain + Clock rDom -> + -- | Reset signal in the read domain + Reset rDom -> + -- | Enable signal in the read domain + Enable rDom -> + Circuit (PacketStream wDom dataWidth metaType) (PacketStream rDom dataWidth metaType) +asyncFifoC depth wClk wRst wEn rClk rRst rEn = fromSignals ckt + where + ckt (fwdIn, bwdIn) = (bwdOut, fwdOut) + where + (element, isEmpty, isFull) = asyncFIFOSynchronizer depth wClk rClk wRst rRst wEn rEn readReq fwdIn + notEmpty = not <$> isEmpty + -- If the FIFO is empty, we output Nothing. Else, we output the oldest element. + fwdOut = toMaybe <$> notEmpty <*> element + -- Assert backpressure when the FIFO is full. + bwdOut = PacketStreamS2M . not <$> isFull + -- Next component is ready to read if the fifo is not empty and it does not assert backpressure. + readReq = notEmpty .&&. _ready <$> bwdIn diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs new file mode 100644 index 00000000..d8bc6688 --- /dev/null +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -0,0 +1,212 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NoImplicitPrelude #-} + +{- | +Definitions and instances of the packet stream protocol +-} +module Protocols.PacketStream.Base ( + PacketStreamM2S (..), + PacketStreamS2M (..), + PacketStream, + unsafeToPacketStream, + fromPacketStream, + forceResetSanity, + filterMetaS, + filterMeta, + mapMetaS, + mapMeta, +) where + +import Clash.Prelude hiding (sample) +import qualified Prelude as P + +import qualified Protocols.Df as Df +import qualified Protocols.DfConv as DfConv +import Protocols.Hedgehog.Internal +import Protocols.Internal + +import Control.DeepSeq (NFData) +import Data.Coerce (coerce) +import qualified Data.Maybe as Maybe +import Data.Proxy + +{- | Data sent from manager to subordinate, a simplified AXI4-Stream like interface +with metadata that can only change on packet delineation. +_tdest, _tuser and _tid are bundled into one big _meta field which holds metadata. +There are no null or position bytes so _tstrb is replaced by a last indicator +that indicates the index of the last valid byte in the _data vector. +_tvalid is modeled via wrapping this in a `Maybe`. +-} +data PacketStreamM2S (dataWidth :: Nat) (metaType :: Type) = PacketStreamM2S + { _data :: Vec dataWidth (BitVector 8) + -- ^ The bytes to be transmitted + , _last :: Maybe (Index dataWidth) + -- ^ If Nothing, we are not yet at the last byte, otherwise index of last valid byte of _data + , _meta :: metaType + -- ^ the metadata of a packet. Must be constant during a packet. + , _abort :: Bool + -- ^ If True, the current transfer is aborted and the subordinate should ignore the current transfer + } + deriving (Eq, Generic, ShowX, Show, NFData, Bundle, Functor) + +{- | Data sent from the subordinate to the manager +The only information transmitted is whether the subordinate is ready to receive data +-} +newtype PacketStreamS2M = PacketStreamS2M + { _ready :: Bool + -- ^ Iff True, the subordinate is ready to receive data + } + deriving (Eq, Generic, ShowX, Show, NFData, Bundle, NFDataX) + +-- | The packet stream protocol for communication between components +data PacketStream (dom :: Domain) (dataWidth :: Nat) (metaType :: Type) + +deriving instance + (KnownNat dataWidth, NFDataX metaType) => + NFDataX (PacketStreamM2S dataWidth metaType) + +instance Protocol (PacketStream dom dataWidth metaType) where + type + Fwd (PacketStream dom dataWidth metaType) = + Signal dom (Maybe (PacketStreamM2S dataWidth metaType)) + type Bwd (PacketStream dom dataWidth metaType) = Signal dom PacketStreamS2M + +instance Backpressure (PacketStream dom dataWidth metaType) where + boolsToBwd _ = fromList_lazy . fmap PacketStreamS2M + +instance DfConv.DfConv (PacketStream dom dataWidth metaType) where + type Dom (PacketStream dom dataWidth metaType) = dom + type FwdPayload (PacketStream dom dataWidth metaType) = PacketStreamM2S dataWidth metaType + + toDfCircuit _ = fromSignals go + where + go (fwdIn, bwdIn) = + ( (fmap coerce bwdIn, pure undefined) + , fmap Df.dataToMaybe $ P.fst fwdIn + ) + + fromDfCircuit _ = fromSignals go + where + go (fwdIn, bwdIn) = + ( fmap coerce $ P.fst bwdIn + , (fmap Df.maybeToData fwdIn, pure undefined) + ) + +instance + (KnownDomain dom) => + Simulate (PacketStream dom dataWidth metaType) + where + type + SimulateFwdType (PacketStream dom dataWidth metaType) = + [Maybe (PacketStreamM2S dataWidth metaType)] + type SimulateBwdType (PacketStream dom dataWidth metaType) = [PacketStreamS2M] + type SimulateChannels (PacketStream dom dataWidth metaType) = 1 + + simToSigFwd _ = fromList_lazy + simToSigBwd _ = fromList_lazy + sigToSimFwd _ s = sample_lazy s + sigToSimBwd _ s = sample_lazy s + + stallC conf (head -> (stallAck, stalls)) = + withClockResetEnable clockGen resetGen enableGen + $ DfConv.stall Proxy Proxy conf stallAck stalls + +instance + (KnownDomain dom) => + Drivable (PacketStream dom dataWidth metaType) + where + type + ExpectType (PacketStream dom dataWidth metaType) = + [PacketStreamM2S dataWidth metaType] + + toSimulateType Proxy = fmap Just + fromSimulateType Proxy = Maybe.catMaybes + + driveC conf vals = + withClockResetEnable clockGen resetGen enableGen + $ DfConv.drive Proxy conf vals + sampleC conf ckt = + withClockResetEnable clockGen resetGen enableGen + $ DfConv.sample Proxy conf ckt + +instance + ( KnownNat dataWidth + , NFDataX metaType + , NFData metaType + , ShowX metaType + , Show metaType + , Eq metaType + , KnownDomain dom + ) => + Test (PacketStream dom dataWidth metaType) + where + expectN Proxy options sampled = + expectN (Proxy @(Df.Df dom _)) options + $ Df.maybeToData + <$> sampled + +-- | Circuit to convert a CSignal into a PacketStream. This is unsafe, because it drops backpressure. +unsafeToPacketStream :: + Circuit (CSignal dom (Maybe (PacketStreamM2S n a))) (PacketStream dom n a) +unsafeToPacketStream = Circuit (\(fwdInS, _) -> (pure (), fwdInS)) + +-- | Converts a PacketStream into a CSignal. +fromPacketStream :: + forall dom n meta. + (HiddenClockResetEnable dom) => + Circuit (PacketStream dom n meta) (CSignal dom (Maybe (PacketStreamM2S n meta))) +fromPacketStream = forceResetSanity |> Circuit (\(inFwd, _) -> (pure (PacketStreamS2M True), inFwd)) + +-- | Ensures a circuit does not send out ready on reset +forceResetSanity :: + forall dom n meta. + (HiddenClockResetEnable dom) => + Circuit (PacketStream dom n meta) (PacketStream dom n meta) +forceResetSanity = + Circuit (\(fwd, bwd) -> unbundle . fmap f . bundle $ (rstLow, fwd, bwd)) + where + f (True, _, _) = (PacketStreamS2M False, Nothing) + f (False, fwd, bwd) = (bwd, fwd) + rstLow = unsafeToActiveHigh hasReset + +{- | Filter a packet stream based on its metadata, + with the predicate wrapped in a @Signal@. +-} +filterMetaS :: + -- | Predicate which specifies whether to keep a fragment based on its metadata, + -- wrapped in a @Signal@ + Signal dom (meta -> Bool) -> + Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) +filterMetaS pS = Circuit $ \(fwdIn, bwdIn) -> unbundle (go <$> bundle (fwdIn, bwdIn, pS)) + where + go (Nothing, bwdIn, _) = (bwdIn, Nothing) + go (Just inPkt, bwdIn, predicate) + | predicate (_meta inPkt) = (bwdIn, Just inPkt) + -- It's illegal to look at bwdIn when sending out a Nothing. + -- So if we drive a Nothing, force an acknowledgement. + | otherwise = (PacketStreamS2M True, Nothing) + +-- | Filter a packet stream based on its metadata. +filterMeta :: + -- | Predicate which specifies whether to keep a fragment based on its metadata + (meta -> Bool) -> + Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) +filterMeta p = filterMetaS (pure p) + +{- | Map a function on the metadata of a packet stream, + with the function wrapped in a @Signal@. +-} +mapMetaS :: + -- | Function to apply on the metadata, wrapped in a @Signal@ + Signal dom (a -> b) -> + Circuit (PacketStream dom dataWidth a) (PacketStream dom dataWidth b) +mapMetaS fS = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, go <$> bundle (fwdIn, fS)) + where + go (inp, f) = (\inPkt -> inPkt{_meta = f (_meta inPkt)}) <$> inp + +-- | Map a function on the metadata of a packet stream. +mapMeta :: + -- | Function to apply on the metadata + (a -> b) -> + Circuit (PacketStream dom dataWidth a) (PacketStream dom dataWidth b) +mapMeta f = mapMetaS (pure f) diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs new file mode 100644 index 00000000..c323e82c --- /dev/null +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -0,0 +1,235 @@ +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE NoImplicitPrelude #-} + +{- | +Provides an upconverter and downconverter for changing the data width of packet streams. +-} +module Protocols.PacketStream.Converters ( + upConverterC, + downConverterC, +) where + +import Clash.Prelude + +import Protocols (Circuit (..), fromSignals, (|>)) +import Protocols.PacketStream.Base + +import Data.Maybe (isJust, isNothing) +import Data.Maybe.Extra + +-- | Upconverter state, consisting of at most p `BitVector 8`s and a vector indicating which bytes are valid +data UpConverterState (dataWidth :: Nat) = UpConverterState + { _ucBuf :: Vec dataWidth (BitVector 8) + -- ^ The buffer we are filling + , _ucIdx :: Index dataWidth + -- ^ Where in the buffer we need to write the next element + , _ucFlush :: Bool + -- ^ If this is true the current state can presented as packetstream word + , _ucFreshBuf :: Bool + -- ^ If this is true we need to start a fresh buffer + , _ucAborted :: Bool + -- ^ Current packet is aborted + , _ucLastIdx :: Maybe (Index dataWidth) + -- ^ If true the current buffer contains the last byte of the current packet + } + deriving (Generic, NFDataX) + +toPacketStream :: UpConverterState dataWidth -> Maybe (PacketStreamM2S dataWidth ()) +toPacketStream UpConverterState{..} = toMaybe _ucFlush (PacketStreamM2S _ucBuf _ucLastIdx () _ucAborted) + +nextState :: + (KnownNat dataWidth) => + UpConverterState dataWidth -> + Maybe (PacketStreamM2S 1 ()) -> + PacketStreamS2M -> + UpConverterState dataWidth +nextState st@(UpConverterState{..}) Nothing (PacketStreamS2M inReady) = + nextSt + where + outReady = not _ucFlush || inReady + -- If we can accept data we can always set _ucFlush to false, + -- since we only change state if we can transmit and receive data + nextStRaw = + st + { _ucFlush = False + , _ucAborted = isNothing _ucLastIdx && _ucAborted + , _ucLastIdx = Nothing + } + nextSt = if outReady then nextStRaw else st +nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M inReady) = + nextSt + where + inLast = isJust _last + -- We smear an abort over the entire rest of the packet + -- so the next abort is set: + -- - If fragment we are potentially flushing was not the last and we were already aborting; + -- - or if the incoming fragment is aborted + nextAbort = (isNothing _ucLastIdx && _ucAborted) || _abort + -- If we are not flushing we can accept data to be stored in _ucBuf, + -- but when we are flushing we can only accept if the current + -- output fragment is accepted by the sink + outReady = not _ucFlush || inReady + bufFull = _ucIdx == maxBound + currBuf = if _ucFreshBuf then (repeat 0) else _ucBuf + nextBuf = replace _ucIdx (head _data) currBuf + + nextFlush = inLast || bufFull + nextIdx = if nextFlush then 0 else _ucIdx + 1 + + nextStRaw = + UpConverterState + { _ucBuf = nextBuf + , _ucIdx = nextIdx + , _ucFlush = nextFlush + , _ucFreshBuf = nextFlush + , _ucAborted = nextAbort + , _ucLastIdx = toMaybe inLast _ucIdx + } + nextSt = if outReady then nextStRaw else st + +upConverter :: + forall (dataWidth :: Nat) (dom :: Domain). + (HiddenClockResetEnable dom) => + (1 <= dataWidth) => + (KnownNat dataWidth) => + -- | Input packet stream from the source + -- Input backpressure from the sink + ( Signal dom (Maybe (PacketStreamM2S 1 ())) + , Signal dom PacketStreamS2M + ) -> + -- | Output backpressure to the source + -- Output packet stream to the sink + ( Signal dom PacketStreamS2M + , Signal dom (Maybe (PacketStreamM2S dataWidth ())) + ) +upConverter = mealyB go s0 + where + s0 = UpConverterState (repeat undefined) 0 False True False Nothing + go :: + UpConverterState dataWidth -> + (Maybe (PacketStreamM2S 1 ()), PacketStreamS2M) -> + ( UpConverterState dataWidth + , (PacketStreamS2M, Maybe (PacketStreamM2S dataWidth ())) + ) + go st@(UpConverterState{..}) (fwdIn, bwdIn) = + (nextState st fwdIn bwdIn, (PacketStreamS2M outReady, toPacketStream st)) + where + outReady = not _ucFlush || (_ready bwdIn) + +{- | Converts packet streams of single bytes to packet streams of a higher data widths. +Has one cycle of latency, but optimal throughput. +-} +upConverterC :: + forall (dataWidth :: Nat) (dom :: Domain). + (HiddenClockResetEnable dom) => + (1 <= dataWidth) => + (KnownNat dataWidth) => + Circuit (PacketStream dom 1 ()) (PacketStream dom dataWidth ()) +upConverterC = forceResetSanity |> fromSignals upConverter + +data DownConverterState (dataWidth :: Nat) = DownConverterState + { _dcBuf :: Vec dataWidth (BitVector 8) + -- ^ Buffer + , _dcSize :: Index (dataWidth + 1) + -- ^ Number of valid bytes in _dcBuf + , _dcLastVec :: Bool + -- ^ True if last byte of _dcBuf was marked as last byte by incoming stream + , _dcAborted :: Bool + -- ^ If True, outgoing bytes should be marked as aborted until _dcBuf is replaced + } + deriving (Generic, NFDataX) + +-- | Computes new state from incoming data +fromPacketStreamM2S :: + forall (dataWidth :: Nat). + (KnownNat dataWidth) => + PacketStreamM2S dataWidth () -> + DownConverterState dataWidth +fromPacketStreamM2S (PacketStreamM2S vs lastIdx _ aborted) = + DownConverterState + { _dcBuf = vs + , _dcSize = maybe (natToNum @dataWidth) (succ . resize) lastIdx -- lastIdx points to the last valid byte, so the buffer size is one more + , _dcLastVec = isJust lastIdx + , _dcAborted = aborted + } + +-- | Computes output of down converter +toMaybePacketStreamM2S :: + forall (dataWidth :: Nat). + (1 <= dataWidth) => + (KnownNat dataWidth) => + DownConverterState dataWidth -> + Maybe (PacketStreamM2S 1 ()) +toMaybePacketStreamM2S DownConverterState{..} = toMaybe (_dcSize > 0) out + where + out = + PacketStreamM2S + { _data = leToPlusKN @1 @dataWidth head _dcBuf :> Nil + , _last = toMaybe (_dcSize == 1 && _dcLastVec) 0 + , _meta = () + , _abort = _dcAborted + } + +downConverter :: + forall (dataWidth :: Nat) (dom :: Domain). + (HiddenClockResetEnable dom) => + (1 <= dataWidth) => + (KnownNat dataWidth) => + -- | Input packet stream from the source and backpressure from the sink + ( Signal dom (Maybe (PacketStreamM2S dataWidth ())) + , Signal dom PacketStreamS2M + ) -> + -- | Output backpressure to the source + -- Output packet stream to the sink + ( Signal dom PacketStreamS2M + , Signal dom (Maybe (PacketStreamM2S 1 ())) + ) +downConverter = mealyB go s0 + where + s0 = + DownConverterState + { _dcBuf = errorX "downConverter: undefined initial value" + , _dcSize = 0 + , _dcLastVec = False + , _dcAborted = False + } + go :: + DownConverterState dataWidth -> + (Maybe (PacketStreamM2S dataWidth ()), PacketStreamS2M) -> + (DownConverterState dataWidth, (PacketStreamS2M, Maybe (PacketStreamM2S 1 ()))) + go st@(DownConverterState{..}) (fwdIn, PacketStreamS2M inReady) = (st', (bwdOut, fwdOut)) + where + (_dcSize', _dcBuf') = + if _dcSize > 0 && inReady + then (_dcSize - 1, _dcBuf <<+ 0) + else (_dcSize, _dcBuf) + + -- If the next buffer contains no valid bytes, + -- and the final byte was acknowledged, we can + -- acknowledge the newly received data. + -- The || is lazy, and we need this: if the output + -- of the downconverter is Nothing, we are not allowed to + -- evaluate inReady. + outReady = _dcSize == 0 || (_dcSize == 1 && inReady) + st' = case fwdIn of + Just inp | outReady -> fromPacketStreamM2S inp + _ -> + st + { _dcBuf = _dcBuf' + , _dcSize = _dcSize' + } + + bwdOut = PacketStreamS2M outReady + fwdOut = toMaybePacketStreamM2S st + +{- | Converts packet streams of arbitrary data widths to packet streams of single bytes. +Has one clock cycle of latency, but optimal throughput, i.e. a packet of n bytes is +sent out in n clock cycles, even if `_last` is set. +-} +downConverterC :: + forall (dataWidth :: Nat) (dom :: Domain). + (HiddenClockResetEnable dom) => + (1 <= dataWidth) => + (KnownNat dataWidth) => + Circuit (PacketStream dom dataWidth ()) (PacketStream dom 1 ()) +downConverterC = forceResetSanity |> fromSignals downConverter diff --git a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs new file mode 100644 index 00000000..e097733c --- /dev/null +++ b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs @@ -0,0 +1,186 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NoImplicitPrelude #-} + +{- | +Optimized Store and forward FIFO circuit for packet streams. +-} +module Protocols.PacketStream.PacketFifo ( + packetFifoC, + overflowDropPacketFifoC, +) where + +import Clash.Prelude + +import Protocols (CSignal, Circuit (..), fromSignals, (|>)) +import Protocols.PacketStream.Base + +import Data.Maybe +import Data.Maybe.Extra + +type PacketStreamContent (dataWidth :: Nat) (metaType :: Type) = + (Vec dataWidth (BitVector 8), Maybe (Index dataWidth)) + +toPacketStreamContent :: + PacketStreamM2S dataWidth metaType -> PacketStreamContent dataWidth metaType +toPacketStreamContent PacketStreamM2S{_data = d, _last = l, _meta = _, _abort = _} = (d, l) + +toPacketStreamM2S :: + PacketStreamContent dataWidth metaType -> metaType -> PacketStreamM2S dataWidth metaType +toPacketStreamM2S (d, l) m = PacketStreamM2S d l m False + +packetFifoImpl :: + forall + (dom :: Domain) + (dataWidth :: Nat) + (metaType :: Type) + (contentSizeBits :: Nat) + (metaSizeBits :: Nat). + (HiddenClockResetEnable dom) => + (KnownNat dataWidth) => + (1 <= contentSizeBits) => + (1 <= metaSizeBits) => + (NFDataX metaType) => + -- | Depth of the content of the packet buffer, this is equal to 2^contentSizeBits + SNat contentSizeBits -> + -- | Depth of the content of the meta buffer, this is equal to 2^metaSizeBits + SNat metaSizeBits -> + -- | Input packetStream + ( Signal dom (Maybe (PacketStreamM2S dataWidth metaType)) + , Signal dom PacketStreamS2M + ) -> + -- | Output CSignal s + ( Signal dom PacketStreamS2M + , Signal dom (Maybe (PacketStreamM2S dataWidth metaType)) + ) +packetFifoImpl SNat SNat (fwdIn, bwdIn) = (PacketStreamS2M . not <$> fullBuffer, fwdOut) + where + fwdOut = toMaybe <$> (not <$> emptyBuffer) <*> (toPacketStreamM2S <$> ramContent <*> ramMeta) + + -- The backing ram + ramContent = + blockRam1 + NoClearOnReset + (SNat @(2 ^ contentSizeBits)) + (errorX "initial block ram content") + cReadAddr' + writeCommand + ramMeta = + blockRam1 + NoClearOnReset + (SNat @(2 ^ metaSizeBits)) + (errorX "initial block ram meta content") + mReadAddr' + mWriteCommand + + -- The write commands to the RAM + writeCommand = + toMaybe + <$> writeEnable + <*> bundle (cWordAddr, toPacketStreamContent . fromJustX <$> fwdIn) + mWriteCommand = toMaybe <$> nextPacketIn <*> bundle (mWriteAddr, _meta . fromJustX <$> fwdIn) + + -- Addresses for the content data + cWordAddr, cPacketAddr, cReadAddr :: Signal dom (Unsigned contentSizeBits) + cWordAddr = register 0 $ mux dropping' cPacketAddr $ mux writeEnable (cWordAddr + 1) cWordAddr + cPacketAddr = register 0 $ mux nextPacketIn (cWordAddr + 1) cPacketAddr + cReadAddr' = mux readEnable (cReadAddr + 1) cReadAddr + cReadAddr = register 0 cReadAddr' + + -- Addresses for the meta data + mWriteAddr, mReadAddr :: Signal dom (Unsigned metaSizeBits) + mWriteAddr = register 0 mWriteAddr' + mWriteAddr' = mux nextPacketIn (mWriteAddr + 1) mWriteAddr + + mReadAddr' = mux mReadEnable (mReadAddr + 1) mReadAddr + mReadAddr = register 0 mReadAddr' + -- only read the next value if we've outpustted the last word of a packet + mReadEnable = lastWordOut .&&. readEnable + + -- Registers : status + dropping', dropping, emptyBuffer :: Signal dom Bool + -- start dropping packet on abort + dropping' = abortIn .||. dropping + dropping = register False $ dropping' .&&. (not <$> lastWordIn) + -- the buffer is empty if the metaBuffer is empty as the metabuffer only updates when a packet is complete + emptyBuffer = register 0 mWriteAddr .==. mReadAddr + + -- Only write if there is space and we're not dropping + writeEnable = writeRequest .&&. (not <$> fullBuffer) .&&. (not <$> dropping') + -- Read when the word has been received + readEnable = (not <$> emptyBuffer) .&&. (_ready <$> bwdIn) + + -- The status signals + fullBuffer = ((cWordAddr + 1) .==. cReadAddr) .||. ((mWriteAddr + 1) .==. mReadAddr) + writeRequest = isJust <$> fwdIn + lastWordIn = maybe False (isJust . _last) <$> fwdIn + lastWordOut = maybe False (isJust . _last) <$> fwdOut + abortIn = maybe False _abort <$> fwdIn + nextPacketIn = lastWordIn .&&. writeEnable + +-- | A circuit that sends an abort forward if there is backpressure from the forward circuit +abortOnBackPressure :: + forall (dom :: Domain) (dataWidth :: Nat) (metaType :: Type). + (HiddenClockResetEnable dom) => + (KnownNat dataWidth) => + (NFDataX metaType) => + ( Signal dom (Maybe (PacketStreamM2S dataWidth metaType)) + , Signal dom PacketStreamS2M + ) -> + -- | Does not give backpressure, sends an abort forward instead + ( Signal dom () + , Signal dom (Maybe (PacketStreamM2S dataWidth metaType)) + ) +abortOnBackPressure (fwdInS, bwdInS) = (pure (), go <$> bundle (fwdInS, bwdInS)) + where + go (fwdIn, bwdIn) = fmap (\pkt -> pkt{_abort = _abort pkt || not (_ready bwdIn)}) fwdIn + +{- | Packet buffer, a circuit which stores words in a buffer until the packet is complete +once a packet is complete it will send the entire packet out at once without stalls. +If a word in a packet has `_abort` set to true, the packetBuffer will drop the entire packet. +If a packet is equal to or larger than 2^sizeBits-1, the packetBuffer will have a deadlock, this should be avoided! +-} +packetFifoC :: + forall + (dom :: Domain) + (dataWidth :: Nat) + (metaType :: Type) + (contentSizeBits :: Nat) + (metaSizeBits :: Nat). + (HiddenClockResetEnable dom) => + (KnownNat dataWidth) => + (1 <= contentSizeBits) => + (1 <= metaSizeBits) => + (NFDataX metaType) => + -- | Depth of the content of the packet buffer, this is equal to 2^contentSizeBits + SNat contentSizeBits -> + -- | Depth for the meta of the packet buffer, this is equal to 2^metaSizeBits. + -- This can usually be smaller than contentSizeBits as for every packet we only need a single meta entry, while we usually have many words. + SNat metaSizeBits -> + Circuit (PacketStream dom dataWidth metaType) (PacketStream dom dataWidth metaType) +packetFifoC cSizeBits mSizeBits = forceResetSanity |> fromSignals (packetFifoImpl cSizeBits mSizeBits) + +-- | A packet buffer that drops packets when it is full, instead of giving backpressure, see packetBufferC for more detailed explanation +overflowDropPacketFifoC :: + forall + (dom :: Domain) + (dataWidth :: Nat) + (metaType :: Type) + (contentSizeBits :: Nat) + (metaSizeBits :: Nat). + (HiddenClockResetEnable dom) => + (KnownNat dataWidth) => + (1 <= contentSizeBits) => + (1 <= metaSizeBits) => + (NFDataX metaType) => + SNat contentSizeBits -> + SNat metaSizeBits -> + Circuit + (CSignal dom (Maybe (PacketStreamM2S dataWidth metaType))) + (PacketStream dom dataWidth metaType) +overflowDropPacketFifoC cSizeBits mSizeBits = backPressureC |> packetFifoC cSizeBits mSizeBits + where + backPressureC :: + Circuit + (CSignal dom (Maybe (PacketStreamM2S dataWidth metaType))) + (PacketStream dom dataWidth metaType) + backPressureC = fromSignals abortOnBackPressure diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs new file mode 100644 index 00000000..ed5ffced --- /dev/null +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -0,0 +1,652 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE NoImplicitPrelude #-} + +{- | +Utility circuits for appending and stripping headers to and from the beginning of packets. +-} +module Protocols.PacketStream.Packetizers ( + packetizerC, + depacketizerC, + packetizeFromDfC, + depacketizeToDfC, +) where + +import Clash.Prelude +import Clash.Sized.Vector.Extra + +import Protocols +import Protocols.Df (Data (..)) +import qualified Protocols.Df as Df +import Protocols.PacketStream.Base + +import Data.Maybe +import Data.Maybe.Extra +import Data.Type.Equality ((:~:) (Refl)) + +type HeaderBufSize (headerBytes :: Nat) (dataWidth :: Nat) = + headerBytes + dataWidth + +-- The amount of bytes that we still need to forward due to +-- @headerBytes@ not aligning with @dataWidth@. +type ForwardBufSize (headerBytes :: Nat) (dataWidth :: Nat) = + headerBytes `Mod` dataWidth + +data PacketizerState (metaOut :: Type) (headerBytes :: Nat) (dataWidth :: Nat) + = Insert + { _counter :: Index (headerBytes `DivRU` dataWidth) + , _hdrBuf :: Vec (HeaderBufSize headerBytes dataWidth) (BitVector 8) + , _aborted :: Bool + } + | Forward + { _fwdBuf :: Vec (ForwardBufSize headerBytes dataWidth) (BitVector 8) + , _aborted :: Bool + } + | LastForward + {_lastFragment :: PacketStreamM2S dataWidth metaOut} + deriving (Generic, Show, ShowX) + +deriving instance + (NFDataX metaOut, PacketizerCt headerBytes dataWidth) => + NFDataX (PacketizerState metaOut headerBytes dataWidth) + +type PacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = + ( KnownNat dataWidth + , 1 <= dataWidth + , KnownNat headerBytes + ) + +defaultByte :: BitVector 8 +defaultByte = 0x00 + +-- The initial state of our packetizer. For readability purposes, because we use this exact expression a lot. +initialState :: + forall + (metaOut :: Type) + (headerBytes :: Nat) + (dataWidth :: Nat). + (PacketizerCt headerBytes dataWidth) => + PacketizerState metaOut headerBytes dataWidth +initialState = Insert 0 (repeat defaultByte) False + +adjustLast :: + forall + (headerBytes :: Nat) + (dataWidth :: Nat). + ( headerBytes `Mod` dataWidth <= dataWidth + , KnownNat dataWidth + , 1 <= dataWidth + ) => + SNat headerBytes -> + Index dataWidth -> + Either (Index dataWidth) (Index dataWidth) +adjustLast SNat idx = if outputNow then Left nowIdx else Right nextIdx + where + outputNow = case compareSNat (SNat @(ForwardBufSize headerBytes dataWidth)) d0 of + SNatGT -> idx < natToNum @(dataWidth - ForwardBufSize headerBytes dataWidth) + _ -> True + nowIdx = idx + natToNum @(ForwardBufSize headerBytes dataWidth) + nextIdx = idx - natToNum @(dataWidth - ForwardBufSize headerBytes dataWidth) + +packetizerT :: + forall + (headerBytes :: Nat) + (dataWidth :: Nat) + (header :: Type) + (metaIn :: Type) + (metaOut :: Type). + (BitSize header ~ headerBytes * 8) => + (BitPack header) => + (PacketizerCt headerBytes dataWidth) => + (ForwardBufSize headerBytes dataWidth <= dataWidth) => + (metaIn -> metaOut) -> + (metaIn -> header) -> + PacketizerState metaOut headerBytes dataWidth -> + (Maybe (PacketStreamM2S dataWidth metaIn), PacketStreamS2M) -> + ( PacketizerState metaOut headerBytes dataWidth + , (PacketStreamS2M, Maybe (PacketStreamM2S dataWidth metaOut)) + ) +packetizerT toMetaOut toHeader st@Insert{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = + (nextStOut, (bwdOut, fwdOut)) + where + alignedCmp = compareSNat (SNat @(ForwardBufSize headerBytes dataWidth)) d0 + nextAborted = _aborted || _abort + header = bitCoerce (toHeader _meta) + metaOut = toMetaOut _meta + hdrBuf = if _counter == 0 then header ++ _data else _hdrBuf + (newHdrBuf, dataOut) = shiftOutFrom0 (SNat @dataWidth) hdrBuf + forwardBytes = snd $ shiftOutFromN (SNat @(ForwardBufSize headerBytes dataWidth)) _data + + newLast = case alignedCmp of + SNatGT -> fmap (adjustLast (SNat @headerBytes)) _last + _ -> Nothing + + fwdOut = + Just + pkt + { _data = dataOut + , _last = if _counter == maxBound then either Just (const Nothing) =<< newLast else Nothing + , _meta = metaOut + , _abort = nextAborted + } + + nextSt = case (_counter == maxBound, newLast) of + (False, _) -> Insert (succ _counter) newHdrBuf nextAborted + (True, Nothing) -> Forward forwardBytes nextAborted + (True, Just (Left _)) -> initialState + (True, Just (Right idx)) -> + LastForward + (PacketStreamM2S (take (SNat @dataWidth) newHdrBuf) (Just idx) metaOut nextAborted) + + nextStOut = if isNothing fwdOut || _ready bwdIn then nextSt else st + + -- Assert backpressure while inserting the header. If shifting needs to be done + -- and we are at the last cycle of insertion, we do not need to assert backpressure + -- because we put the rest of the data in _fwdBuf (of course, unless our subordinate asserts backpressure). + bwdOut = PacketStreamS2M $ case alignedCmp of + SNatGT -> _ready bwdIn && _counter == maxBound + _ -> False +packetizerT toMetaOut _ st@Forward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = (nextStOut, (bwdIn, Just outPkt)) + where + nextAborted = _aborted || _abort + metaOut = toMetaOut _meta + (dataOut, nextFwdBuf) = splitAt (SNat @dataWidth) (_fwdBuf ++ _data) + dataLast = nextFwdBuf ++ repeat @(dataWidth - ForwardBufSize headerBytes dataWidth) defaultByte + newLast = fmap (adjustLast (SNat @headerBytes)) _last + + outPkt = + pkt + { _data = dataOut + , _last = either Just (const Nothing) =<< newLast + , _meta = metaOut + , _abort = nextAborted + } + + nextSt = case newLast of + Nothing -> Forward nextFwdBuf nextAborted + Just (Left _) -> initialState + Just (Right idx) -> LastForward (PacketStreamM2S dataLast (Just idx) metaOut nextAborted) + + nextStOut = if _ready bwdIn then nextSt else st +packetizerT _ _ st@LastForward{..} (_, bwdIn) = (nextStOut, (PacketStreamS2M False, Just _lastFragment)) + where + nextStOut = if _ready bwdIn then initialState else st +packetizerT _ _ s (Nothing, bwdIn) = (s, (bwdIn, Nothing)) + +{- | Puts a portion of the metadata in front of the packet stream, and shifts the stream accordingly. + This portion is defined by the metadata to header transformer function. If this function is `id`, + the entire metadata is put in front of the packet stream. +-} +packetizerC :: + forall + (dom :: Domain) + (dataWidth :: Nat) + (metaIn :: Type) + (metaOut :: Type) + (header :: Type) + (headerBytes :: Nat). + ( HiddenClockResetEnable dom + , NFDataX metaOut + , BitPack header + , BitSize header ~ headerBytes * 8 + , KnownNat headerBytes + , 1 <= dataWidth + , KnownNat dataWidth + ) => + -- | Metadata transformer function + (metaIn -> metaOut) -> + -- | metaData to header that will be packetized transformer function + (metaIn -> header) -> + Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) +packetizerC toMetaOut toHeader = fromSignals outCircuit + where + outCircuit = case compareSNat (SNat @(ForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of + SNatLE -> mealyB (packetizerT @headerBytes toMetaOut toHeader) initialState + _ -> + clashCompileError + "packetizerC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + +-- Since the header might be unaligned compared to the datawidth +-- we need to store a partial fragment when forwarding. +-- The fragment we need to store depends on our "unalignedness". +-- +-- Ex. We parse a header of 17 bytes and our @dataWidth@ is 4 bytes. +-- That means at the end of the header we can have upto 3 bytes left +-- in the fragment which we may need to forward. +type DeForwardBufSize (headerBytes :: Nat) (dataWidth :: Nat) = + (dataWidth - (headerBytes `Mod` dataWidth)) `Mod` dataWidth + +type DepacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = + ( headerBytes `Mod` dataWidth <= dataWidth + , KnownNat dataWidth + , 1 <= dataWidth + , KnownNat headerBytes + ) + +data DepacketizerState (headerBytes :: Nat) (dataWidth :: Nat) + = DeParse + { _deAborted :: Bool + , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) + , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + , _deCounter :: Index (headerBytes `DivRU` dataWidth) + } + | DeForward + { _deAborted :: Bool + , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) + , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + , _deCounter :: Index (headerBytes `DivRU` dataWidth) + } + | DeLastForward + { _deAborted :: Bool + , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) + , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + , _deCounter :: Index (headerBytes `DivRU` dataWidth) + , _deLastIdx :: Index dataWidth + } + deriving (Show, ShowX, Generic) + +deriving instance + (DepacketizerCt headerBytes dataWidth) => + NFDataX (DepacketizerState headerBytes dataWidth) + +depacketizerT :: + forall + (headerBytes :: Nat) + (dataWidth :: Nat) + (header :: Type) + (metaIn :: Type) + (metaOut :: Type). + (BitSize header ~ headerBytes * 8) => + (BitPack header) => + (NFDataX metaIn) => + (DepacketizerCt headerBytes dataWidth) => + (DeForwardBufSize headerBytes dataWidth <= dataWidth) => + (headerBytes <= dataWidth * headerBytes `DivRU` dataWidth) => + (header -> metaIn -> metaOut) -> + DepacketizerState headerBytes dataWidth -> + (Maybe (PacketStreamM2S dataWidth metaIn), PacketStreamS2M) -> + ( DepacketizerState headerBytes dataWidth + , (PacketStreamS2M, Maybe (PacketStreamM2S dataWidth metaOut)) + ) +depacketizerT _ DeParse{..} (Just PacketStreamM2S{..}, _) = (nextSt, (PacketStreamS2M outReady, Nothing)) + where + nextAborted = _deAborted || _abort + nextParseBuf = fst $ shiftInAtN _deParseBuf _data + fwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + fwdBuf = dropLe (SNat @(dataWidth - DeForwardBufSize headerBytes dataWidth)) _data + + prematureEnd idx = + case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of + SNatGT -> idx < (natToNum @(headerBytes `Mod` dataWidth)) + _ -> True + + nextCounter = pred _deCounter + + nextSt = + case (_deCounter == 0, _last) of + (False, Nothing) -> + DeParse nextAborted nextParseBuf fwdBuf nextCounter + (done, Just idx) + | not done || prematureEnd idx -> + DeParse False nextParseBuf fwdBuf maxBound + (True, Just idx) -> + DeLastForward + nextAborted + nextParseBuf + fwdBuf + nextCounter + (idx - natToNum @(headerBytes `Mod` dataWidth)) + (True, Nothing) -> + DeForward nextAborted nextParseBuf fwdBuf nextCounter + _ -> + clashCompileError + "depacketizerT Parse: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + + outReady + | DeLastForward{} <- nextSt = False + | otherwise = True +depacketizerT _ st@DeParse{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) +depacketizerT toMetaOut st@DeForward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = (nextStOut, (PacketStreamS2M outReady, Just outPkt)) + where + nextAborted = _deAborted || _abort + dataOut :: Vec dataWidth (BitVector 8) + nextFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + (dataOut, nextFwdBuf) = splitAt (SNat @dataWidth) $ _deFwdBuf ++ _data + + deAdjustLast :: Index dataWidth -> Either (Index dataWidth) (Index dataWidth) + deAdjustLast idx = if outputNow then Left nowIdx else Right nextIdx + where + outputNow = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of + SNatGT -> idx < natToNum @(headerBytes `Mod` dataWidth) + _ -> True + nowIdx = idx + natToNum @(DeForwardBufSize headerBytes dataWidth) + nextIdx = idx - natToNum @(headerBytes `Mod` dataWidth) + + newLast = fmap deAdjustLast _last + outPkt = + pkt + { _abort = nextAborted + , _data = dataOut + , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _deParseBuf) _meta + , _last = either Just (const Nothing) =<< newLast + } + + nextSt = case newLast of + Nothing -> DeForward nextAborted _deParseBuf nextFwdBuf _deCounter + Just (Left _) -> DeParse False _deParseBuf nextFwdBuf maxBound + Just (Right idx) -> DeLastForward nextAborted _deParseBuf nextFwdBuf _deCounter idx + nextStOut = if _ready bwdIn then nextSt else st + + outReady + | DeLastForward{} <- nextSt = False + | otherwise = _ready bwdIn +depacketizerT _ st@DeForward{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) +depacketizerT toMetaOut st@DeLastForward{..} (fwdIn, bwdIn) = (nextStOut, (bwdIn, Just outPkt)) + where + -- We can only get in this state if the previous clock cycle we received a fwdIn + -- which was also the last fragment + inPkt = fromJustX fwdIn + outPkt = + PacketStreamM2S + { _abort = _deAborted || _abort inPkt + , _data = + _deFwdBuf ++ repeat @(dataWidth - DeForwardBufSize headerBytes dataWidth) defaultByte + , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _deParseBuf) (_meta inPkt) + , _last = Just $ fromJustX (_last inPkt) - natToNum @(headerBytes `Mod` dataWidth) + } + nextStOut = if _ready bwdIn then DeParse False _deParseBuf _deFwdBuf maxBound else st + +-- | Reads bytes at the start of each packet into metadata. +depacketizerC :: + forall + (dom :: Domain) + (dataWidth :: Nat) + (metaIn :: Type) + (metaOut :: Type) + (header :: Type) + (headerBytes :: Nat). + ( HiddenClockResetEnable dom + , NFDataX metaOut + , NFDataX metaIn + , BitPack header + , BitSize header ~ headerBytes * 8 + , KnownNat headerBytes + , 1 <= dataWidth + , KnownNat dataWidth + ) => + -- | Used to compute final metadata of outgoing packets from header and incoming metadata + (header -> metaIn -> metaOut) -> + Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) +depacketizerC toMetaOut = forceResetSanity |> fromSignals outCircuit + where + modProof = compareSNat (SNat @(headerBytes `Mod` dataWidth)) (SNat @dataWidth) + divProof = compareSNat (SNat @headerBytes) (SNat @(dataWidth * headerBytes `DivRU` dataWidth)) + + outCircuit = + case (modProof, divProof) of + (SNatLE, SNatLE) -> case compareSNat (SNat @(DeForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of + SNatLE -> + mealyB + (depacketizerT @headerBytes toMetaOut) + (DeParse False (repeat undefined) (repeat undefined) maxBound) + _ -> + clashCompileError + "depacketizerC1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + _ -> + clashCompileError + "depacketizerC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + +{- | If dataWidth >= headerBytes, we don't need a buffer because we can immediately send + the fragment. Else, we need a buffer that stores the headerBytes minus the size + of the fragment we send out immediately. +-} +type DfHeaderBufSize headerBytes dataWidth = dataWidth `Max` headerBytes - dataWidth + +data DfPacketizerState (metaOut :: Type) (headerBytes :: Nat) (dataWidth :: Nat) + = DfIdle + | DfInsert + { _dfCounter :: Index (headerBytes `DivRU` dataWidth - 1) + , _dfHdrBuf :: Vec (DfHeaderBufSize headerBytes dataWidth) (BitVector 8) + } + deriving (Generic, NFDataX, Show, ShowX) + +packetizeFromDfT :: + forall + (dataWidth :: Nat) + (a :: Type) + (metaOut :: Type) + (header :: Type) + (headerBytes :: Nat). + (NFDataX metaOut) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => + (KnownNat headerBytes) => + (KnownNat dataWidth) => + (1 <= dataWidth) => + (1 <= headerBytes `DivRU` dataWidth) => + (dataWidth `Min` headerBytes <= dataWidth) => + (dataWidth `Max` headerBytes - dataWidth + dataWidth `Min` headerBytes ~ headerBytes) => + -- | function that transforms the Df input to the output metadata. + (a -> metaOut) -> + -- | function that transforms the Df input to the header that will be packetized. + (a -> header) -> + DfPacketizerState metaOut headerBytes dataWidth -> + (Data a, PacketStreamS2M) -> + ( DfPacketizerState metaOut headerBytes dataWidth + , (Ack, Maybe (PacketStreamM2S dataWidth metaOut)) + ) +packetizeFromDfT toMetaOut toHeader DfIdle (Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) + where + rotatedHdr = + rotateRightS (bitCoerce (toHeader dataIn)) (SNat @(DfHeaderBufSize headerBytes dataWidth)) + (hdrBuf, dataOut) = splitAt (SNat @(DfHeaderBufSize headerBytes dataWidth)) rotatedHdr + dataOutPadded = dataOut ++ repeat @(dataWidth - dataWidth `Min` headerBytes) defaultByte + outPkt = PacketStreamM2S dataOutPadded newLast (toMetaOut dataIn) False + + (nextSt, bwdOut, newLast) = case compareSNat (SNat @headerBytes) (SNat @dataWidth) of + SNatLE -> (DfIdle, Ack (_ready bwdIn), Just l) + where + l = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of + SNatGT -> natToNum @(headerBytes `Mod` dataWidth - 1) + _ -> natToNum @(dataWidth - 1) + SNatGT -> (DfInsert 0 hdrBuf, Ack False, Nothing) + + nextStOut = if _ready bwdIn then nextSt else DfIdle + +-- fwdIn is always Data in this state, because we assert backpressure in Idle before we go here +-- Thus, we don't need to store the metadata in the state. +packetizeFromDfT toMetaOut _ st@DfInsert{..} (Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) + where + (dataOut, newHdrBuf) = splitAt (SNat @dataWidth) (_dfHdrBuf ++ repeat @dataWidth defaultByte) + outPkt = PacketStreamM2S dataOut newLast (toMetaOut dataIn) False + + newLast = toMaybe (_dfCounter == maxBound) $ case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of + SNatGT -> natToNum @(headerBytes `Mod` dataWidth - 1) + _ -> natToNum @(dataWidth - 1) + + bwdOut = Ack (_ready bwdIn && _dfCounter == maxBound) + nextSt = if _dfCounter == maxBound then DfIdle else DfInsert (succ _dfCounter) newHdrBuf + nextStOut = if _ready bwdIn then nextSt else st +packetizeFromDfT _ _ s (NoData, bwdIn) = (s, (Ack (_ready bwdIn), Nothing)) + +{- | Starts a packet stream upon receiving some data. + The bytes to be packetized and the output metadata + are specified by the input functions. +-} +packetizeFromDfC :: + forall + (dom :: Domain) + (dataWidth :: Nat) + (a :: Type) + (metaOut :: Type) + (header :: Type) + (headerBytes :: Nat). + (HiddenClockResetEnable dom) => + (NFDataX metaOut) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => + (KnownNat headerBytes) => + (KnownNat dataWidth) => + (1 <= dataWidth) => + -- | Function that transforms the Df input to the output metadata. + (a -> metaOut) -> + -- | Function that transforms the Df input to the header that will be packetized. + (a -> header) -> + Circuit (Df dom a) (PacketStream dom dataWidth metaOut) +packetizeFromDfC toMetaOut toHeader = fromSignals ckt + where + maxMinProof = + sameNat + (SNat @headerBytes) + (SNat @(dataWidth `Max` headerBytes - dataWidth + dataWidth `Min` headerBytes)) + minProof = compareSNat (SNat @(dataWidth `Min` headerBytes)) (SNat @dataWidth) + divRuProof = compareSNat d1 (SNat @(headerBytes `DivRU` dataWidth)) + + ckt = case (maxMinProof, minProof, divRuProof) of + (Just Refl, SNatLE, SNatLE) -> mealyB (packetizeFromDfT toMetaOut toHeader) DfIdle + _ -> + clashCompileError + "packetizeFromDfC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + +type ParseBufSize (headerBytes :: Nat) (dataWidth :: Nat) = + dataWidth * headerBytes `DivRU` dataWidth + +type DepacketizeToDfCt (headerBytes :: Nat) (dataWidth :: Nat) = + ( 1 <= headerBytes `DivRU` dataWidth + , headerBytes `Mod` dataWidth <= dataWidth + , headerBytes <= ParseBufSize headerBytes dataWidth + , KnownNat dataWidth + , 1 <= dataWidth + , KnownNat headerBytes + ) + +data DfDepacketizerState (a :: Type) (headerBytes :: Nat) (dataWidth :: Nat) + = Parse + { _dfDeAborted :: Bool + -- ^ whether any of the fragments parsed from the current packet were aborted. + , _dfDeParseBuf :: Vec (ParseBufSize headerBytes dataWidth) (BitVector 8) + -- ^ the accumulator for header bytes. + , _dfDeCounter :: Index (headerBytes `DivRU` dataWidth) + -- ^ how many of the _parseBuf bytes are currently valid (accumulation count). We flush at counter == maxBound + } + | ConsumePadding + { _dfDeAborted :: Bool + -- ^ whether any of the fragments parsed from the current packet were aborted. + , _dfDeParseBuf :: Vec (ParseBufSize headerBytes dataWidth) (BitVector 8) + } + deriving (Generic, Show, ShowX) + +deriving instance + (NFDataX a, DepacketizeToDfCt headerBytes dataWidth) => + NFDataX (DfDepacketizerState a headerBytes dataWidth) + +dfDeInitialState :: + forall (a :: Type) (headerBytes :: Nat) (dataWidth :: Nat). + (KnownNat dataWidth) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + DfDepacketizerState a headerBytes dataWidth +dfDeInitialState = Parse False (repeat undefined) maxBound + +depacketizeToDfT :: + forall + (dom :: Domain) + (dataWidth :: Nat) + (a :: Type) + (meta :: Type) + (header :: Type) + (headerBytes :: Nat). + (HiddenClockResetEnable dom) => + (NFDataX meta) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => + (DepacketizeToDfCt headerBytes dataWidth) => + (header -> meta -> a) -> + DfDepacketizerState a headerBytes dataWidth -> + (Maybe (PacketStreamM2S dataWidth meta), Ack) -> + (DfDepacketizerState a headerBytes dataWidth, (PacketStreamS2M, Df.Data a)) +depacketizeToDfT toOut st@Parse{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) + where + nextAborted = _dfDeAborted || _abort + nextParseBuf = fst (shiftInAtN _dfDeParseBuf _data) + outDf = toOut (bitCoerce (takeLe (SNat @headerBytes) nextParseBuf)) _meta + + prematureEnd idx = + case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of + SNatGT -> idx < (natToNum @(headerBytes `Mod` dataWidth - 1)) + _ -> idx < (natToNum @(dataWidth - 1)) + + (nextSt, fwdOut) = + case (_dfDeCounter == 0, _last) of + (False, Nothing) -> + (Parse nextAborted nextParseBuf (pred _dfDeCounter), Df.NoData) + (c, Just idx) + | not c || prematureEnd idx -> + (dfDeInitialState, Df.NoData) + (True, Just _) -> + (dfDeInitialState, if nextAborted then Df.NoData else Df.Data outDf) + (True, Nothing) -> + (ConsumePadding nextAborted nextParseBuf, Df.NoData) + _ -> + clashCompileError + "depacketizeToDfT Parse: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + + readyOut = isNothing (Df.dataToMaybe fwdOut) || readyIn + nextStOut = if readyOut then nextSt else st +depacketizeToDfT toOut st@ConsumePadding{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) + where + nextAborted = _dfDeAborted || _abort + outDf = toOut (bitCoerce (takeLe (SNat @headerBytes) _dfDeParseBuf)) _meta + + (nextSt, fwdOut) = + if isJust _last + then (dfDeInitialState, if nextAborted then Df.NoData else Df.Data outDf) + else (st{_dfDeAborted = nextAborted}, Df.NoData) + + readyOut = isNothing (Df.dataToMaybe fwdOut) || readyIn + nextStOut = if readyOut then nextSt else st +depacketizeToDfT _ st (Nothing, Ack ready) = (st, (PacketStreamS2M ready, Df.NoData)) + +{- | Reads bytes at the start of each packet into a dataflow. +Consumes the remainder of the packet and drops this. If a +packet ends sooner than the assumed length of the header, +`depacketizeToDfC` does not send out anything. +If any of the fragments in the packet has _abort set, it drops +the entire packet. +-} +depacketizeToDfC :: + forall + (dom :: Domain) + (dataWidth :: Nat) + (a :: Type) + (meta :: Type) + (header :: Type) + (headerBytes :: Nat). + (HiddenClockResetEnable dom) => + (NFDataX meta) => + (NFDataX a) => + (BitPack header) => + (KnownNat headerBytes) => + (KnownNat dataWidth) => + (1 <= dataWidth) => + (BitSize header ~ headerBytes * 8) => + -- | function that transforms the given meta + parsed header to the output Df + (header -> meta -> a) -> + Circuit (PacketStream dom dataWidth meta) (Df dom a) +depacketizeToDfC toOut = forceResetSanity |> fromSignals outCircuit + where + divProof = compareSNat (SNat @headerBytes) (SNat @(dataWidth * headerBytes `DivRU` dataWidth)) + modProof = compareSNat (SNat @(headerBytes `Mod` dataWidth)) (SNat @dataWidth) + + outCircuit = + case (divProof, modProof) of + (SNatLE, SNatLE) -> case compareSNat d1 (SNat @(headerBytes `DivRU` dataWidth)) of + SNatLE -> mealyB (depacketizeToDfT toOut) dfDeInitialState + _ -> + clashCompileError + "depacketizeToDfC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + _ -> + clashCompileError + "depacketizeToDfC1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" diff --git a/clash-protocols/src/Protocols/PacketStream/Routing.hs b/clash-protocols/src/Protocols/PacketStream/Routing.hs new file mode 100644 index 00000000..a39d04b8 --- /dev/null +++ b/clash-protocols/src/Protocols/PacketStream/Routing.hs @@ -0,0 +1,92 @@ +{-# LANGUAGE NoImplicitPrelude #-} + +{- | +Provides a packet arbiter and dispatcher, for merging and splitting packet streams. +-} +module Protocols.PacketStream.Routing ( + packetArbiterC, + ArbiterMode (..), + packetDispatcherC, + routeBy, +) where + +import Clash.Prelude + +import Protocols +import Protocols.PacketStream.Base + +import Data.Bifunctor (Bifunctor (second)) +import qualified Data.Bifunctor as B +import Data.Maybe + +-- | Collect mode for `packetArbiterC` +data ArbiterMode + = -- | Collect in a round-robin fashion. Fair and cheaper than `Parallel`. + RoundRobin + | -- | Check components in parallel. This mode has a higher throughput, but is + -- biased towards the last source and also slightly more expensive. + Parallel + +-- | Collects packets from all sources, respecting packet boundaries. +packetArbiterC :: + forall dom p n a. + ( HiddenClockResetEnable dom + , KnownNat p + , 1 <= p + ) => + -- | See `ArbiterMode` + ArbiterMode -> + Circuit (Vec p (PacketStream dom n a)) (PacketStream dom n a) +packetArbiterC mode = Circuit (B.first unbundle . mealyB go (maxBound, True) . B.first bundle) + where + go :: + (Index p, Bool) -> + (Vec p (Maybe (PacketStreamM2S n a)), PacketStreamS2M) -> + ((Index p, Bool), (Vec p PacketStreamS2M, Maybe (PacketStreamM2S n a))) + go (i, first) (fwds, bwd@(PacketStreamS2M ack)) = ((i', continue), (bwds, fwd)) + where + bwds = replace i bwd (repeat (PacketStreamS2M False)) + fwd = fwds !! i + continue = case fwd of + Nothing -> first -- only switch sources if this is not somewhere inside a packet + Just (PacketStreamM2S _ (Just _) _ _) -> ack -- switch source once last packet is acknowledged + _ -> False + i' = case (mode, continue) of + (_, False) -> i + (RoundRobin, _) -> satSucc SatWrap i -- next index + (Parallel, _) -> fromMaybe maxBound $ fold @(p - 1) (<|>) (zipWith (<$) indicesI fwds) -- index of last sink with data + +{- | Routes packets depending on their metadata, using given routing functions. + +Data is sent to at most one element of the output vector, for which the +dispatch function evaluates to true on the metadata of the input. If none of +the functions evaluate to true, the input is dropped. If more than one of the +predicates are true, the first one is picked. + +Sends out packets in the same clock cycle as they are received. +-} +packetDispatcherC :: + forall (dom :: Domain) (p :: Nat) (n :: Nat) (a :: Type). + ( HiddenClockResetEnable dom + , KnownNat p + ) => + -- | Dispatch function. If function at index i returns true for the metaData it + -- dispatches the current packet to that sink. + Vec p (a -> Bool) -> + Circuit (PacketStream dom n a) (Vec p (PacketStream dom n a)) +packetDispatcherC fs = Circuit (second unbundle . unbundle . fmap go . bundle . second bundle) + where + go (Just x, bwds) = case findIndex id $ zipWith ($) fs (pure $ _meta x) of + Just i -> (bwds !! i, replace i (Just x) (repeat Nothing)) + _ -> (PacketStreamS2M True, repeat Nothing) + go _ = (PacketStreamS2M False, repeat Nothing) + +{- | Routing function for `packetDispatcherC` that matches against values with +an `Eq` instance. Useful to route according to a record field. +-} +routeBy :: + (Eq b) => + (a -> b) -> + Vec p b -> + Vec p (a -> Bool) +routeBy f = fmap $ \x -> (== x) . f diff --git a/clash-protocols/tests/Tests/Protocols.hs b/clash-protocols/tests/Tests/Protocols.hs index 043732ec..d40fd5d2 100644 --- a/clash-protocols/tests/Tests/Protocols.hs +++ b/clash-protocols/tests/Tests/Protocols.hs @@ -5,6 +5,7 @@ import qualified Tests.Protocols.Avalon import qualified Tests.Protocols.Axi4 import qualified Tests.Protocols.Df import qualified Tests.Protocols.DfConv +import qualified Tests.Protocols.PacketStream import qualified Tests.Protocols.Vec import qualified Tests.Protocols.Wishbone @@ -12,12 +13,13 @@ tests :: TestTree tests = testGroup "Protocols" - [ Tests.Protocols.Df.tests - , Tests.Protocols.DfConv.tests - , Tests.Protocols.Avalon.tests - , Tests.Protocols.Axi4.tests - , Tests.Protocols.Wishbone.tests - , Tests.Protocols.Vec.tests + [ --Tests.Protocols.Df.tests + --, Tests.Protocols.DfConv.tests + --, Tests.Protocols.Avalon.tests + --, Tests.Protocols.Axi4.tests + Tests.Protocols.PacketStream.tests + --, Tests.Protocols.Wishbone.tests + --, Tests.Protocols.Vec.tests ] main :: IO () diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream.hs b/clash-protocols/tests/Tests/Protocols/PacketStream.hs new file mode 100644 index 00000000..c349afc7 --- /dev/null +++ b/clash-protocols/tests/Tests/Protocols/PacketStream.hs @@ -0,0 +1,20 @@ +module Tests.Protocols.PacketStream (tests) where + +import Test.Tasty + +import qualified Tests.Protocols.PacketStream.AsyncFifo +import qualified Tests.Protocols.PacketStream.Converters +import qualified Tests.Protocols.PacketStream.PacketFifo +import qualified Tests.Protocols.PacketStream.Packetizers +import qualified Tests.Protocols.PacketStream.Routing + +tests :: TestTree +tests = + testGroup + "PacketStream" + [ Tests.Protocols.PacketStream.AsyncFifo.tests + , Tests.Protocols.PacketStream.Converters.tests + , Tests.Protocols.PacketStream.PacketFifo.tests + , Tests.Protocols.PacketStream.Packetizers.tests + , Tests.Protocols.PacketStream.Routing.tests + ] diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs new file mode 100644 index 00000000..6be94c64 --- /dev/null +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs @@ -0,0 +1,127 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE RecordWildCards #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +module Tests.Protocols.PacketStream.AsyncFifo where + +-- base +import Prelude + +-- clash-prelude +import Clash.Prelude as C + +-- hedgehog +import Hedgehog +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range + +-- tasty +import Test.Tasty +import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) +import Test.Tasty.Hedgehog.Extra (testProperty) +import Test.Tasty.TH (testGroupGenerator) + +-- clash-protocols +import Protocols +import Protocols.Hedgehog +import Protocols.PacketStream.AsyncFifo +import Protocols.PacketStream.Base + +genVec :: (KnownNat n, 1 C.<= n) => Gen a -> Gen (Vec n a) +genVec gen = sequence (C.repeat gen) + +createDomain + vSystem + { vName = "TestDom50" + , vPeriod = 20_000 + , vActiveEdge = Rising + , vResetKind = Asynchronous + , vInitBehavior = Unknown + , vResetPolarity = ActiveHigh + } + +createDomain + vSystem + { vName = "TestDom125" + , vPeriod = 8_000 + , vActiveEdge = Rising + , vResetKind = Asynchronous + , vInitBehavior = Unknown + , vResetPolarity = ActiveHigh + } + +clk50 :: Clock TestDom50 +clk50 = clockGen + +clk125 :: Clock TestDom125 +clk125 = clockGen + +rst50 :: Reset TestDom50 +rst50 = resetGen @TestDom50 + +rst125 :: Reset TestDom125 +rst125 = resetGen @TestDom125 + +en50 :: Enable TestDom50 +en50 = enableGen + +en125 :: Enable TestDom125 +en125 = enableGen + +generateAsyncFifoIdProp :: + forall (wDom :: Domain) (rDom :: Domain). + (KnownDomain wDom, KnownDomain rDom) => + Clock wDom -> + Reset wDom -> + Enable wDom -> + Clock rDom -> + Reset rDom -> + Enable rDom -> + Property +generateAsyncFifoIdProp wClk wRst wEn rClk rRst rEn = + propWithModel + defExpectOptions + (Gen.list (Range.linear 0 100) genPackets) + id + ckt + (===) + where + ckt :: + (KnownDomain wDom, KnownDomain rDom) => + Circuit + (PacketStream wDom 1 Int) + (PacketStream rDom 1 Int) + ckt = asyncFifoC (C.SNat @8) wClk wRst wEn rClk rRst rEn + -- This is used to generate + genPackets = + PacketStreamM2S + <$> genVec Gen.enumBounded + <*> Gen.maybe Gen.enumBounded + <*> Gen.enumBounded + <*> Gen.enumBounded + +{- | The async FIFO circuit should forward all of its input data without loss and without producing extra data. + This property tests whether this is true, when the clock of the writer and reader is equally fast (50 MHz). +-} +prop_asyncfifo_writer_speed_equal_to_reader_id :: Property +prop_asyncfifo_writer_speed_equal_to_reader_id = generateAsyncFifoIdProp clk50 rst50 en50 clk50 rst50 en50 + +{- | The async FIFO circuit should forward all of its input data without loss and without producing extra data. + This property tests whether this is true, when the clock of the writer (50 MHz) is slower than the clock of the reader (125 MHz). +-} +prop_asyncfifo_writer_speed_slower_than_reader_id :: Property +prop_asyncfifo_writer_speed_slower_than_reader_id = generateAsyncFifoIdProp clk50 rst50 en50 clk125 rst125 en125 + +{- | The async FIFO circuit should forward all of its input data without loss and without producing extra data. + This property tests whether this is true, when the clock of the writer (125 MHz) is faster than the clock of the reader (50 MHz). +-} +prop_asyncfifo_writer_speed_faster_than_reader_id :: Property +prop_asyncfifo_writer_speed_faster_than_reader_id = generateAsyncFifoIdProp clk125 rst125 en125 clk50 rst50 en50 + +tests :: TestTree +tests = + localOption (mkTimeout 12_000_000 {- 12 seconds -}) $ + localOption + (HedgehogTestLimit (Just 1_000)) + $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs new file mode 100644 index 00000000..c53c7a9a --- /dev/null +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs @@ -0,0 +1,188 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE RecordWildCards #-} + +module Tests.Protocols.PacketStream.Base ( + chunkBy, + chunkByPacket, + smearAbort, + chopBy, + chunkToPacket, + chopPacket, + fullPackets, + dropAbortedPackets, + downConvert, + upConvert, + cleanPackets, + makeValid, + genValidPacket, + genValidPackets, +) where + +-- base +import qualified Data.List as L +import qualified Data.Maybe as M +import Prelude + +-- clash +import qualified Clash.Prelude as C +import qualified Clash.Sized.Vector as Vec + +-- hedgehog +import Hedgehog +import qualified Hedgehog.Gen as Gen + +-- clash-protocols +import Protocols.PacketStream.Base + +-- | Partition a list based on given function +chunkBy :: (a -> Bool) -> [a] -> [[a]] +chunkBy _ [] = [] +chunkBy predicate list = L.filter (not . null) $ chunkByHelper predicate list [] + +-- Helper function to accumulate chunks +chunkByHelper :: (a -> Bool) -> [a] -> [a] -> [[a]] +chunkByHelper _ [] acc = [L.reverse acc] +chunkByHelper predicate (x : xs) acc + | predicate x = L.reverse (x : acc) : chunkByHelper predicate xs [] + | otherwise = chunkByHelper predicate xs (x : acc) + +-- | Partition a list of PacketStreams into complete packets +chunkByPacket :: [PacketStreamM2S n meta] -> [[PacketStreamM2S n meta]] +chunkByPacket = chunkBy (M.isJust . _last) + +-- | Smear abort over the rest of a list of packets +smearAbort :: [PacketStreamM2S n meta] -> [PacketStreamM2S n meta] +smearAbort [] = [] +smearAbort (x : xs) = L.reverse $ L.foldl' go [x] xs + where + go [] _ = [] + go l@(a : _) (PacketStreamM2S dat last' meta abort) = + PacketStreamM2S dat last' meta (_abort a || abort) : l + +-- | Partition a list into groups of given size +chopBy :: Int -> [a] -> [[a]] +chopBy _ [] = [] +chopBy n xs = as : chopBy n bs where (as, bs) = splitAt n xs + +-- | Merge a list of PacketStream 1 into a PacketStream n +chunkToPacket :: (C.KnownNat n) => [PacketStreamM2S 1 meta] -> PacketStreamM2S n meta +chunkToPacket l = + PacketStreamM2S + { _last = + if M.isJust $ _last $ L.last l then M.Just (fromIntegral $ L.length l - 1) else Nothing + , _abort = or $ fmap _abort l + , _meta = _meta $ L.head l + , _data = L.foldr (C.+>>) (C.repeat 0) $ fmap (C.head . _data) l + } + +-- | Split a PacketStream n into a list of PacketStream 1 +chopPacket :: + forall n meta. + (1 C.<= n) => + (C.KnownNat n) => + PacketStreamM2S n meta -> + [PacketStreamM2S 1 meta] +chopPacket PacketStreamM2S{..} = packets + where + lasts = case _last of + Nothing -> repeat Nothing + Just in' -> replicate (fromIntegral in') Nothing ++ [Just (0 :: C.Index 1)] + + datas = case _last of + Nothing -> C.toList _data + Just in' -> take (fromIntegral in' + 1) $ C.toList _data + + packets = (\(idx, dat) -> PacketStreamM2S (pure dat) idx _meta _abort) <$> zip lasts datas + +-- | Set the _last of the last element of a list of PacketStreams to 0 +fullPackets :: (C.KnownNat n) => [PacketStreamM2S n meta] -> [PacketStreamM2S n meta] +fullPackets [] = [] +fullPackets fragments = + let lastFragment = (last fragments){_last = Just 0} + in init fragments ++ [lastFragment] + +-- | Drops packets if one of the words in the packet has the abort flag set +dropAbortedPackets :: [PacketStreamM2S n meta] -> [PacketStreamM2S n meta] +dropAbortedPackets packets = concat $ filter (not . any _abort) (chunkByPacket packets) + +-- | Split a list of PacketStream n into a list of PacketStream 1 +downConvert :: + forall n meta. + (1 C.<= n) => + (C.KnownNat n) => + [PacketStreamM2S n meta] -> + [PacketStreamM2S 1 meta] +downConvert = concatMap chopPacket + +-- | Merge a list of PacketStream 1 into a list of PacketStream n +upConvert :: + forall n meta. + (1 C.<= n) => + (C.KnownNat n) => + [PacketStreamM2S 1 meta] -> + [PacketStreamM2S n meta] +upConvert packets = chunkToPacket <$> chopBy (C.natToNum @n) packets + +-- | Set all invalid bytes to null-bytes +cleanPackets :: + forall n meta. + (1 C.<= n) => + (C.KnownNat n) => + [PacketStreamM2S n meta] -> + [PacketStreamM2S n meta] +cleanPackets = map cleanPacket + where + cleanPacket pkt@PacketStreamM2S{..} = case _last of + Nothing -> pkt + Just i -> pkt{_data = M.fromJust $ Vec.fromList datas} + where + datas = + take (1 + fromIntegral i) (C.toList _data) + ++ replicate ((C.natToNum @n) - 1 - fromIntegral i) 0 + +-- | Make an existing list of packets valid, meaning all words in a packet share the same meta value, and the list always contain full packets +makeValid :: + forall (dataWidth :: C.Nat) (metaType :: C.Type). + (C.KnownNat dataWidth) => + (1 C.<= dataWidth) => + [PacketStreamM2S dataWidth metaType] -> + [PacketStreamM2S dataWidth metaType] +makeValid packets = fullPackets $ concatMap sameMeta $ chunkByPacket packets + where + sameMeta :: [PacketStreamM2S dataWidth metaType] -> [PacketStreamM2S dataWidth metaType] + sameMeta [] = [] + sameMeta list@(x : _) = fullPackets $ fmap (\pkt -> pkt{_meta = _meta x}) list + +{- | Generates a single valid packet using the given generator, + the meta value of the first word will be that of all words, and only the last value in the packet will have Last = Just .. +-} +genValidPacket :: + forall (dataWidth :: C.Nat) (metaType :: C.Type). + (C.KnownNat dataWidth) => + (1 C.<= dataWidth) => + Range Int -> + Gen (PacketStreamM2S dataWidth metaType) -> + Gen [PacketStreamM2S dataWidth metaType] +genValidPacket range gen = do + genWords <- Gen.list range gen + return $ makeValidPacket genWords + where + makeValidPacket :: + [PacketStreamM2S dataWidth metaType] -> [PacketStreamM2S dataWidth metaType] + makeValidPacket [] = [] + makeValidPacket list@(x : _) = fullPackets $ fmap (\pkt -> pkt{_meta = _meta x, _last = Nothing}) list + +-- | Generates a list filled with valid packets, packets which have the same meta value for all words +genValidPackets :: + forall (dataWidth :: C.Nat) (metaType :: C.Type). + (C.KnownNat dataWidth) => + (1 C.<= dataWidth) => + -- | Range specifying the amount of packets to generate + Range Int -> + -- | Range specifying the size of packets to generate + Range Int -> + -- | Generator for a single packet + Gen (PacketStreamM2S dataWidth metaType) -> + Gen [PacketStreamM2S dataWidth metaType] +genValidPackets range packetRange gen = concat <$> Gen.list range (genValidPacket packetRange gen) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs new file mode 100644 index 00000000..152aba76 --- /dev/null +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs @@ -0,0 +1,118 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE RecordWildCards #-} + +module Tests.Protocols.PacketStream.Converters where + +-- base +import qualified Data.Maybe as M +import Prelude + +-- clash-prelude +import Clash.Prelude (type (<=)) +import qualified Clash.Prelude as C + +-- hedgehog +import Hedgehog +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range + +-- tasty +import Test.Tasty +import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) +import Test.Tasty.Hedgehog.Extra (testProperty) +import Test.Tasty.TH (testGroupGenerator) + +-- clash-protocols +import Protocols +import Protocols.Hedgehog +import Protocols.PacketStream.Base +import Protocols.PacketStream.Converters + +-- tests +import Tests.Protocols.PacketStream.Base + +genVec :: (C.KnownNat n, 1 <= n) => Gen a -> Gen (C.Vec n a) +genVec gen = sequence (C.repeat gen) + +ucModel :: forall n. (C.KnownNat n) => [PacketStreamM2S 1 ()] -> [PacketStreamM2S n ()] +ucModel fragments = out + where + wholePackets = smearAbort <$> chunkBy (M.isJust . _last) fragments + chunks = wholePackets >>= chopBy (C.natToNum @n) + out = fmap chunkToPacket chunks + +-- | Test the upconverter stream instance +upconverterTest :: forall n. (1 <= n) => C.SNat n -> Property +upconverterTest C.SNat = + propWithModelSingleDomain + @C.System + defExpectOptions + (fmap fullPackets (Gen.list (Range.linear 0 100) genPackets)) -- Input packets + (C.exposeClockResetEnable ucModel) -- Desired behaviour of UpConverter + (C.exposeClockResetEnable @C.System (ckt @n)) -- Implementation of UpConverter + (===) -- Property to test + where + ckt :: + forall (dataWidth :: C.Nat) (dom :: C.Domain). + (C.HiddenClockResetEnable dom) => + (1 <= dataWidth) => + (C.KnownNat dataWidth) => + Circuit (PacketStream dom 1 ()) (PacketStream dom dataWidth ()) + ckt = upConverterC + + -- This generates the packets + genPackets = + PacketStreamM2S + <$> genVec Gen.enumBounded + <*> Gen.maybe Gen.enumBounded + <*> Gen.enumBounded + <*> Gen.enumBounded + +prop_upconverter_d1, prop_upconverter_d2, prop_upconverter_d4 :: Property +prop_upconverter_d1 = upconverterTest (C.SNat @1) +prop_upconverter_d2 = upconverterTest (C.SNat @2) +prop_upconverter_d4 = upconverterTest (C.SNat @4) + +dcModel :: + forall n. (1 <= n) => (C.KnownNat n) => [PacketStreamM2S n ()] -> [PacketStreamM2S 1 ()] +dcModel = downConvert + +-- | Test the downconverter stream instance +downconverterTest :: forall n. (1 <= n) => C.SNat n -> Property +downconverterTest C.SNat = + propWithModelSingleDomain + @C.System + defExpectOptions + (Gen.list (Range.linear 0 100) genPackets) -- Input packets + (C.exposeClockResetEnable dcModel) -- Desired behaviour of DownConverter + (C.exposeClockResetEnable @C.System (ckt @n)) -- Implementation of DownConverter + (===) -- Property to test + where + ckt :: + forall (dataWidth :: C.Nat) (dom :: C.Domain). + (C.HiddenClockResetEnable dom) => + (1 <= dataWidth) => + (C.KnownNat dataWidth) => + Circuit (PacketStream dom dataWidth ()) (PacketStream dom 1 ()) + ckt = downConverterC + + -- This generates the packets + genPackets = + PacketStreamM2S + <$> genVec Gen.enumBounded + <*> Gen.maybe Gen.enumBounded + <*> Gen.enumBounded + <*> Gen.enumBounded + +prop_downconverter_d1, prop_downconverter_d2, prop_downconverter_d4 :: Property +prop_downconverter_d1 = downconverterTest (C.SNat @1) +prop_downconverter_d2 = downconverterTest (C.SNat @2) +prop_downconverter_d4 = downconverterTest (C.SNat @4) + +tests :: TestTree +tests = + localOption (mkTimeout 12_000_000 {- 12 seconds -}) $ + localOption + (HedgehogTestLimit (Just 1_000)) + $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs new file mode 100644 index 00000000..60868023 --- /dev/null +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -0,0 +1,182 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE RecordWildCards #-} + +module Tests.Protocols.PacketStream.PacketFifo where + +-- base +import Data.Int (Int16) +import Prelude + +-- clash-prelude +import Clash.Prelude hiding (drop, take, undefined, (++)) +import qualified Clash.Prelude as C + +-- hedgehog +import Hedgehog as H +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range + +-- tasty +import Test.Tasty +import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) +import Test.Tasty.Hedgehog.Extra (testProperty) +import Test.Tasty.TH (testGroupGenerator) + +-- clash-protocols +import Protocols +import Protocols.Hedgehog +import Protocols.PacketStream.Base +import Protocols.PacketStream.PacketFifo (overflowDropPacketFifoC, packetFifoC) + +-- tests +import Tests.Protocols.PacketStream.Base as U + +genVec :: (C.KnownNat n, 1 C.<= n) => Gen a -> Gen (C.Vec n a) +genVec gen = sequence (C.repeat gen) + +-- | generate a "clean" packet: a packet without an abort +genCleanWord :: Gen (PacketStreamM2S 4 Int16) +genCleanWord = + PacketStreamM2S + <$> genVec Gen.enumBounded + <*> pure Nothing + <*> Gen.enumBounded + <*> pure False + +genWord :: Gen (PacketStreamM2S 4 Int16) +genWord = + PacketStreamM2S + <$> genVec Gen.enumBounded + <*> Gen.maybe Gen.enumBounded + <*> Gen.enumBounded + <*> Gen.enumBounded + +genPackets :: Range Int -> Gen [PacketStreamM2S 4 Int16] +genPackets range = makeValid <$> Gen.list range genWord + +isSubsequenceOf :: (Eq a) => [a] -> [a] -> Bool +isSubsequenceOf [] _ = True +isSubsequenceOf _ [] = False +isSubsequenceOf (x : xs) (y : ys) + | x == y = isSubsequenceOf xs ys + | otherwise = isSubsequenceOf xs (y : ys) + +-- | test for id and proper dropping of aborted packets +prop_packetFifo_id :: Property +prop_packetFifo_id = + idWithModelSingleDomain + @C.System + defExpectOptions + (genPackets (Range.linear 0 100)) + (C.exposeClockResetEnable dropAbortedPackets) + (C.exposeClockResetEnable ckt) + where + ckt :: + (HiddenClockResetEnable System) => + Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) + ckt = packetFifoC d12 d12 + +-- test for id with a small buffer to ensure backpressure is tested +prop_packetFifo_small_buffer_id :: Property +prop_packetFifo_small_buffer_id = + idWithModelSingleDomain + @C.System + defExpectOptions + (genValidPackets (Range.linear 0 10) (Range.linear 0 31) genCleanWord) + (C.exposeClockResetEnable dropAbortedPackets) + (C.exposeClockResetEnable ckt) + where + ckt :: + (HiddenClockResetEnable System) => + Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) + ckt = packetFifoC d5 d5 + +-- | test to check if there are no gaps inside of packets +prop_packetFifo_no_gaps :: Property +prop_packetFifo_no_gaps = property $ do + let packetFifoSize = d12 + maxInputSize = 50 + ckt = + exposeClockResetEnable + (packetFifoC packetFifoSize packetFifoSize) + systemClockGen + resetGen + enableGen + gen = genPackets (Range.linear 0 100) + + packets :: [PacketStreamM2S 4 Int16] <- H.forAll gen + + let packetSize = 2 Prelude.^ snatToInteger packetFifoSize + cfg = SimulationConfig 1 (2 * packetSize) False + cktResult = simulateC ckt cfg (Just <$> packets) + + assert $ noGaps $ take (5 * maxInputSize) cktResult + where + noGaps :: [Maybe (PacketStreamM2S 4 Int16)] -> Bool + noGaps (Just (PacketStreamM2S{_last = Nothing}) : Nothing : _) = False + noGaps (_ : xs) = noGaps xs + noGaps _ = True + +-- | test for id and proper dropping of aborted packets +prop_overFlowDrop_packetFifo_id :: Property +prop_overFlowDrop_packetFifo_id = + idWithModelSingleDomain + @C.System + defExpectOptions + (genPackets (Range.linear 0 100)) + (C.exposeClockResetEnable dropAbortedPackets) + (C.exposeClockResetEnable ckt) + where + ckt :: + (HiddenClockResetEnable System) => + Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) + ckt = fromPacketStream |> overflowDropPacketFifoC d12 d12 + +-- | test for proper dropping when full +prop_overFlowDrop_packetFifo_drop :: Property +prop_overFlowDrop_packetFifo_drop = + idWithModelSingleDomain + @C.System + defExpectOptions + -- make sure the timeout is long as the packetFifo can be quiet for a while while dropping + (liftA3 (\a b c -> a ++ b ++ c) genSmall genBig genSmall) + (C.exposeClockResetEnable model) + (C.exposeClockResetEnable ckt) + where + bufferSize = d5 + + ckt :: + (HiddenClockResetEnable System) => + Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) + ckt = fromPacketStream |> overflowDropPacketFifoC bufferSize bufferSize + + model :: [PacketStreamM2S 4 Int16] -> [PacketStreamM2S 4 Int16] + model packets = Prelude.concat $ take 1 packetChunk ++ drop 2 packetChunk + where + packetChunk = chunkByPacket packets + + genSmall = genValidPacket (Range.linear 1 5) genCleanWord + genBig = genValidPacket (Range.linear 33 50) genCleanWord + +-- | test for id using a small metabuffer to ensure backpressure using the metabuffer is tested +prop_packetFifo_small_metaBuffer :: Property +prop_packetFifo_small_metaBuffer = + idWithModelSingleDomain + @C.System + defExpectOptions + (genPackets (Range.linear 0 100)) + (C.exposeClockResetEnable dropAbortedPackets) + (C.exposeClockResetEnable ckt) + where + ckt :: + (HiddenClockResetEnable System) => + Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) + ckt = packetFifoC d12 d2 + +tests :: TestTree +tests = + localOption (mkTimeout 30_000_000 {- 30 seconds -}) $ + localOption + (HedgehogTestLimit (Just 1_000)) + $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs new file mode 100644 index 00000000..c8400994 --- /dev/null +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs @@ -0,0 +1,171 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NumericUnderscores #-} + +module Tests.Protocols.PacketStream.Packetizers ( + packetizerModel, + packetizeFromDfModel, + depacketizerModel, + depacketizeToDfModel, + tests, +) where + +-- base +import qualified Data.List as L +import Prelude + +-- clash +import Clash.Prelude +import Clash.Sized.Vector (unsafeFromList) + +-- hedgehog +-- import Hedgehog +-- import qualified Hedgehog.Gen as Gen +-- import qualified Hedgehog.Range as Range + +-- tasty +import Test.Tasty +import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) + +-- import Test.Tasty.Hedgehog.Extra (testProperty) +import Test.Tasty.TH (testGroupGenerator) + +-- clash-protocols +import Protocols.PacketStream.Base + +-- tests +import Tests.Protocols.PacketStream.Base + +-- | Model of the generic `packetizerC`. +packetizerModel :: + forall + (dataWidth :: Nat) + (headerBytes :: Nat) + (metaIn :: Type) + (header :: Type) + (metaOut :: Type). + (KnownNat dataWidth) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + (1 <= headerBytes) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => + (metaIn -> metaOut) -> + (metaIn -> header) -> + [PacketStreamM2S dataWidth metaIn] -> + [PacketStreamM2S dataWidth metaOut] +packetizerModel toMetaOut toHeader ps = L.concatMap (upConvert . prependHdr) bytePackets + where + prependHdr :: [PacketStreamM2S 1 metaIn] -> [PacketStreamM2S 1 metaOut] + prependHdr fragments = hdr L.++ L.map (\f -> f{_meta = metaOut}) fragments + where + h = L.head fragments + metaOut = toMetaOut (_meta h) + hdr = L.map go (toList $ bitCoerce (toHeader (_meta h))) + go byte = PacketStreamM2S (singleton byte) Nothing metaOut (_abort h) + + bytePackets :: [[PacketStreamM2S 1 metaIn]] + bytePackets = downConvert . smearAbort <$> chunkByPacket ps + +-- | Model of the generic `packetizeFromDfC`. +packetizeFromDfModel :: + forall + (dataWidth :: Nat) + (headerBytes :: Nat) + (a :: Type) + (header :: Type) + (metaOut :: Type). + (KnownNat dataWidth) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + (1 <= headerBytes) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => + (a -> metaOut) -> + (a -> header) -> + [a] -> + [PacketStreamM2S dataWidth metaOut] +packetizeFromDfModel toMetaOut toHeader = L.concatMap (upConvert . packetize) + where + packetize :: a -> [PacketStreamM2S 1 metaOut] + packetize d = + fullPackets $ + L.map + (\byte -> PacketStreamM2S (byte :> Nil) Nothing (toMetaOut d) False) + (toList $ bitCoerce (toHeader d)) + +-- | Model of the generic `depacketizerC`. +depacketizerModel :: + forall + (dataWidth :: Nat) + (headerBytes :: Nat) + (metaIn :: Type) + (metaOut :: Type) + (header :: Type). + ( KnownNat dataWidth + , KnownNat headerBytes + , 1 <= dataWidth + , 1 <= headerBytes + , BitPack header + , BitSize header ~ headerBytes * 8 + ) => + (header -> metaIn -> metaOut) -> + [PacketStreamM2S dataWidth metaIn] -> + [PacketStreamM2S dataWidth metaOut] +depacketizerModel toMetaOut ps = L.concat dataWidthPackets + where + hdrbytes = natToNum @headerBytes + + parseHdr :: + ([PacketStreamM2S 1 metaIn], [PacketStreamM2S 1 metaIn]) -> [PacketStreamM2S 1 metaOut] + parseHdr (hdrF, fwdF) = fmap (\f -> f{_meta = metaOut}) fwdF + where + hdr = bitCoerce $ unsafeFromList @headerBytes $ _data <$> hdrF + metaOut = toMetaOut hdr (_meta $ L.head fwdF) + + bytePackets :: [[PacketStreamM2S 1 metaIn]] + bytePackets = + L.filter (\fs -> L.length fs > hdrbytes) $ + L.concatMap chopPacket . smearAbort <$> chunkByPacket ps + + parsedPackets :: [[PacketStreamM2S 1 metaOut]] + parsedPackets = parseHdr . L.splitAt hdrbytes <$> bytePackets + + dataWidthPackets :: [[PacketStreamM2S dataWidth metaOut]] + dataWidthPackets = fmap chunkToPacket . chopBy (natToNum @dataWidth) <$> parsedPackets + +-- | Model of the generic `depacketizeToDfC`. +depacketizeToDfModel :: + forall + (dataWidth :: Nat) + (headerBytes :: Nat) + (meta :: Type) + (a :: Type) + (header :: Type). + ( KnownNat dataWidth + , KnownNat headerBytes + , 1 <= dataWidth + , 1 <= headerBytes + , BitPack header + , BitSize header ~ headerBytes * 8 + ) => + (header -> meta -> a) -> + [PacketStreamM2S dataWidth meta] -> + [a] +depacketizeToDfModel toOut ps = parseHdr <$> bytePackets + where + hdrbytes = natToNum @headerBytes + + parseHdr :: [PacketStreamM2S 1 meta] -> a + parseHdr hdrF = toOut (bitCoerce $ unsafeFromList @headerBytes $ _data <$> hdrF) (_meta $ L.head hdrF) + + bytePackets :: [[PacketStreamM2S 1 meta]] + bytePackets = + L.filter (\fs -> L.length fs >= hdrbytes) $ + L.concatMap chopPacket <$> chunkByPacket (dropAbortedPackets ps) + +tests :: TestTree +tests = + localOption (mkTimeout 12_000_000 {- 12 seconds -}) $ + localOption + (HedgehogTestLimit (Just 1_000_000)) + $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs new file mode 100644 index 00000000..b1d06788 --- /dev/null +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs @@ -0,0 +1,159 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE RecordWildCards #-} + +module Tests.Protocols.PacketStream.Routing where + +-- base +import Data.List (groupBy, sortOn) +import Prelude + +-- clash-prelude +import Clash.Prelude (type (<=)) +import qualified Clash.Prelude as C + +-- hedgehog +import Hedgehog hiding (Parallel) +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range + +-- tasty +import Test.Tasty +import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) +import Test.Tasty.Hedgehog.Extra (testProperty) +import Test.Tasty.TH (testGroupGenerator) + +-- clash-protocols + +import Protocols.Hedgehog +import Protocols.PacketStream.Base +import Protocols.PacketStream.Routing + +-- tests +import Tests.Protocols.PacketStream.Base (chunkByPacket, fullPackets) + +genVec :: (C.KnownNat n, 1 <= n) => Gen a -> Gen (C.Vec n a) +genVec gen = sequence (C.repeat gen) + +-- | Tests the round-robin packet arbiter with one source; essentially an id test +prop_packetarbiter_roundrobin_id :: Property +prop_packetarbiter_roundrobin_id = makePropPacketArbiter C.d1 C.d2 RoundRobin + +-- | Tests the parallel packet arbiter with one source; essentially an id test +prop_packetarbiter_parallel_id :: Property +prop_packetarbiter_parallel_id = makePropPacketArbiter C.d1 C.d2 Parallel + +-- Tests the round-robin arbiter with five sources +prop_packetarbiter_roundrobin :: Property +prop_packetarbiter_roundrobin = makePropPacketArbiter C.d5 C.d2 RoundRobin + +-- Tests the parallel arbiter with five sources +prop_packetarbiter_parallel :: Property +prop_packetarbiter_parallel = makePropPacketArbiter C.d5 C.d2 Parallel + +{- | Tests a packet arbiter for any data width and number of sources. In particular, +tests that packets from all sources are sent out unmodified in the same order +they were in in the source streams. +-} +makePropPacketArbiter :: + forall p n. + ( C.KnownNat p + , 1 <= p + , C.KnownNat n + , 1 <= n + ) => + C.SNat p -> + C.SNat n -> + ArbiterMode -> + Property +makePropPacketArbiter _ _ mode = + propWithModelSingleDomain + @C.System + defExpectOptions + genSources + (C.exposeClockResetEnable concat) + (C.exposeClockResetEnable (packetArbiterC mode)) + (\xs ys -> partitionPackets xs === partitionPackets ys) + where + genSources = mapM (fmap fullPackets . Gen.list (Range.linear 0 100) . genPacket) (C.indicesI @p) + -- TODO use util function from client review branch + genPacket i = + PacketStreamM2S + <$> genVec @n Gen.enumBounded + <*> Gen.maybe Gen.enumBounded + <*> pure i + <*> Gen.enumBounded + + partitionPackets packets = + sortOn (_meta . head . head) $ + groupBy (\a b -> _meta a == _meta b) <$> chunkByPacket packets + +{- | Tests that the packet dispatcher works correctly with one sink that accepts +all packets; essentially an id test. +-} +prop_packetdispatcher_id :: Property +prop_packetdispatcher_id = + makePropPacketDispatcher + C.d4 + ((const True :: Int -> Bool) C.:> C.Nil) + +{- | Tests the packet dispatcher for a data width of four bytes and three +overlapping but incomplete dispatch functions, effectively testing whether +the circuit sends input to the first allowed output channel and drops input +if there are none. +-} +prop_packetdispatcher :: Property +prop_packetdispatcher = makePropPacketDispatcher C.d4 fs + where + fs :: C.Vec 3 (C.Index 4 -> Bool) + fs = + (>= 3) + C.:> (>= 2) + C.:> (>= 1) + C.:> C.Nil + +{- | Generic test function for the packet dispatcher, testing for all data widths, +dispatch functions, and some meta types +-} +makePropPacketDispatcher :: + forall (p :: C.Nat) (dataWidth :: C.Nat) (a :: C.Type). + ( C.KnownNat p + , 1 <= p + , C.KnownNat dataWidth + , 1 <= dataWidth + , TestType a + , Bounded a + , Enum a + ) => + C.SNat dataWidth -> + C.Vec p (a -> Bool) -> + Property +makePropPacketDispatcher _ fs = + idWithModelSingleDomain @C.System + defExpectOptions + (Gen.list (Range.linear 0 100) genPackets) + (C.exposeClockResetEnable (model 0)) + (C.exposeClockResetEnable (packetDispatcherC fs)) + where + model :: + C.Index p -> [PacketStreamM2S dataWidth a] -> C.Vec p [PacketStreamM2S dataWidth a] + model _ [] = pure [] + model i (y : ys) + | (fs C.!! i) (_meta y) = let next = model 0 ys in C.replace i (y : (next C.!! i)) next + | i < maxBound = model (i + 1) (y : ys) + | otherwise = model 0 ys + + -- TODO use util function from client review branch + genPackets = + PacketStreamM2S + <$> genVec @dataWidth Gen.enumBounded + <*> Gen.maybe Gen.enumBounded + <*> Gen.enumBounded + <*> Gen.enumBounded + +tests :: TestTree +tests = + localOption (mkTimeout 12_000_000 {- 12 seconds -}) $ + localOption + (HedgehogTestLimit (Just 1_000)) + $(testGroupGenerator) From 5fc350d2522bab8cc7ef08441cb36db9d110506d Mon Sep 17 00:00:00 2001 From: t-wallet Date: Thu, 18 Jul 2024 09:59:24 +0200 Subject: [PATCH 02/63] Implement IdleCircuit for PacketStream --- .../src/Protocols/PacketStream/Base.hs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index d8bc6688..13200fe6 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -71,6 +71,10 @@ instance Protocol (PacketStream dom dataWidth metaType) where Signal dom (Maybe (PacketStreamM2S dataWidth metaType)) type Bwd (PacketStream dom dataWidth metaType) = Signal dom PacketStreamS2M +instance IdleCircuit (PacketStream dom dataWidth metaType) where + idleBwd _ = pure (PacketStreamS2M False) + idleFwd _ = pure Nothing + instance Backpressure (PacketStream dom dataWidth metaType) where boolsToBwd _ = fromList_lazy . fmap PacketStreamS2M @@ -157,17 +161,14 @@ fromPacketStream :: Circuit (PacketStream dom n meta) (CSignal dom (Maybe (PacketStreamM2S n meta))) fromPacketStream = forceResetSanity |> Circuit (\(inFwd, _) -> (pure (PacketStreamS2M True), inFwd)) --- | Ensures a circuit does not send out ready on reset +{- | Force a /nack/ on the backward channel and /Nothing/ on the forward +channel if reset is asserted. +-} forceResetSanity :: forall dom n meta. (HiddenClockResetEnable dom) => Circuit (PacketStream dom n meta) (PacketStream dom n meta) -forceResetSanity = - Circuit (\(fwd, bwd) -> unbundle . fmap f . bundle $ (rstLow, fwd, bwd)) - where - f (True, _, _) = (PacketStreamS2M False, Nothing) - f (False, fwd, bwd) = (bwd, fwd) - rstLow = unsafeToActiveHigh hasReset +forceResetSanity = forceResetSanityGeneric {- | Filter a packet stream based on its metadata, with the predicate wrapped in a @Signal@. From 0ac37416ee2d6ffad922cb6e8d76ae2cb404b7a5 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Thu, 18 Jul 2024 14:05:33 +0200 Subject: [PATCH 03/63] Add top level PacketStream module --- clash-protocols/clash-protocols.cabal | 1 + clash-protocols/src/Protocols/PacketStream.hs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 clash-protocols/src/Protocols/PacketStream.hs diff --git a/clash-protocols/clash-protocols.cabal b/clash-protocols/clash-protocols.cabal index 29673955..15f11dd8 100644 --- a/clash-protocols/clash-protocols.cabal +++ b/clash-protocols/clash-protocols.cabal @@ -145,6 +145,7 @@ library Protocols.Axi4.WriteAddress Protocols.Axi4.WriteData Protocols.Axi4.WriteResponse + Protocols.PacketStream Protocols.PacketStream.Base Protocols.PacketStream.AsyncFifo Protocols.PacketStream.Converters diff --git a/clash-protocols/src/Protocols/PacketStream.hs b/clash-protocols/src/Protocols/PacketStream.hs new file mode 100644 index 00000000..980167bd --- /dev/null +++ b/clash-protocols/src/Protocols/PacketStream.hs @@ -0,0 +1,15 @@ +module Protocols.PacketStream + (module Protocols.PacketStream.AsyncFifo + ,module Protocols.PacketStream.Base + ,module Protocols.PacketStream.Converters + ,module Protocols.PacketStream.PacketFifo + ,module Protocols.PacketStream.Packetizers + ,module Protocols.PacketStream.Routing) + where + +import Protocols.PacketStream.AsyncFifo +import Protocols.PacketStream.Base +import Protocols.PacketStream.Converters +import Protocols.PacketStream.PacketFifo +import Protocols.PacketStream.Packetizers +import Protocols.PacketStream.Routing From c589106328607c151498b4d526091aceccfb6985 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Thu, 18 Jul 2024 14:41:25 +0200 Subject: [PATCH 04/63] fix haddock --- .../src/Clash/Sized/Vector/Extra.hs | 2 +- clash-protocols/src/Protocols/PacketStream.hs | 20 +++++++----- .../src/Protocols/PacketStream/AsyncFifo.hs | 21 ++++++------ .../src/Protocols/PacketStream/Packetizers.hs | 32 +++++++++---------- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/clash-protocols/src/Clash/Sized/Vector/Extra.hs b/clash-protocols/src/Clash/Sized/Vector/Extra.hs index fc801654..a0fcc40d 100644 --- a/clash-protocols/src/Clash/Sized/Vector/Extra.hs +++ b/clash-protocols/src/Clash/Sized/Vector/Extra.hs @@ -36,7 +36,7 @@ takeLe :: Vec n a takeLe SNat vs = leToPlus @n @m $ takeI vs --- | Take the first 'valid' elements of 'xs', append 'ys', then pad with 0s +-- | Take the first @valid@ elements of @xs@, append @ys@, then pad with 0s appendVec :: forall n m a. (KnownNat n) => diff --git a/clash-protocols/src/Protocols/PacketStream.hs b/clash-protocols/src/Protocols/PacketStream.hs index 980167bd..61eec3c6 100644 --- a/clash-protocols/src/Protocols/PacketStream.hs +++ b/clash-protocols/src/Protocols/PacketStream.hs @@ -1,11 +1,15 @@ -module Protocols.PacketStream - (module Protocols.PacketStream.AsyncFifo - ,module Protocols.PacketStream.Base - ,module Protocols.PacketStream.Converters - ,module Protocols.PacketStream.PacketFifo - ,module Protocols.PacketStream.Packetizers - ,module Protocols.PacketStream.Routing) - where +{- | +Top level PacketStream module which exports all components. +-} +module Protocols.PacketStream ( + module Protocols.PacketStream.AsyncFifo, + module Protocols.PacketStream.Base, + module Protocols.PacketStream.Converters, + module Protocols.PacketStream.PacketFifo, + module Protocols.PacketStream.Packetizers, + module Protocols.PacketStream.Routing, +) +where import Protocols.PacketStream.AsyncFifo import Protocols.PacketStream.Base diff --git a/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs index a5bb39b9..b7b9e9d8 100644 --- a/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs @@ -10,7 +10,7 @@ import Data.Maybe.Extra (toMaybe) import Clash.Explicit.Prelude (asyncFIFOSynchronizer) import Clash.Prelude -import Protocols.Internal (Circuit, fromSignals) +import Protocols (Circuit, fromSignals) import Protocols.PacketStream.Base {- | Asynchronous FIFO circuit that can be used to safely cross clock domains. @@ -18,19 +18,18 @@ Uses `Clash.Explicit.Prelude.asyncFIFOSynchronizer` internally. -} asyncFifoC :: forall - (depth :: Nat) - (dataWidth :: Nat) (wDom :: Domain) (rDom :: Domain) + (depth :: Nat) + (dataWidth :: Nat) (metaType :: Type). - ( KnownDomain wDom - , KnownDomain rDom - , KnownNat depth - , 2 <= depth - , KnownNat dataWidth - , 1 <= dataWidth - , NFDataX metaType - ) => + (KnownDomain wDom) => + (KnownDomain rDom) => + (KnownNat depth) => + (KnownNat dataWidth) => + (2 <= depth) => + (1 <= dataWidth) => + (NFDataX metaType) => -- | 2^depth is the number of elements this component can store SNat depth -> -- | Clock signal in the write domain diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index ed5ffced..e004b2f4 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -187,14 +187,13 @@ packetizerC :: (metaOut :: Type) (header :: Type) (headerBytes :: Nat). - ( HiddenClockResetEnable dom - , NFDataX metaOut - , BitPack header - , BitSize header ~ headerBytes * 8 - , KnownNat headerBytes - , 1 <= dataWidth - , KnownNat dataWidth - ) => + (HiddenClockResetEnable dom) => + (NFDataX metaOut) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + (KnownNat dataWidth) => -- | Metadata transformer function (metaIn -> metaOut) -> -- | metaData to header that will be packetized transformer function @@ -367,15 +366,14 @@ depacketizerC :: (metaOut :: Type) (header :: Type) (headerBytes :: Nat). - ( HiddenClockResetEnable dom - , NFDataX metaOut - , NFDataX metaIn - , BitPack header - , BitSize header ~ headerBytes * 8 - , KnownNat headerBytes - , 1 <= dataWidth - , KnownNat dataWidth - ) => + (HiddenClockResetEnable dom) => + (NFDataX metaOut) => + (NFDataX metaIn) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + (KnownNat dataWidth) => -- | Used to compute final metadata of outgoing packets from header and incoming metadata (header -> metaIn -> metaOut) -> Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) From 22db469befe2c94a3afa0c3e0812331842b28911 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 19 Jul 2024 17:09:28 +0200 Subject: [PATCH 05/63] (de)packetizer tests and split into two files --- clash-protocols/clash-protocols.cabal | 2 + clash-protocols/src/Protocols/PacketStream.hs | 2 + .../Protocols/PacketStream/Depacketizers.hs | 355 ++++++++++++++++++ .../src/Protocols/PacketStream/Packetizers.hs | 344 +---------------- .../tests/Tests/Protocols/PacketStream.hs | 2 + .../Protocols/PacketStream/Depacketizers.hs | 225 +++++++++++ .../Protocols/PacketStream/Packetizers.hs | 182 +++++---- 7 files changed, 704 insertions(+), 408 deletions(-) create mode 100644 clash-protocols/src/Protocols/PacketStream/Depacketizers.hs create mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs diff --git a/clash-protocols/clash-protocols.cabal b/clash-protocols/clash-protocols.cabal index 15f11dd8..d86ec410 100644 --- a/clash-protocols/clash-protocols.cabal +++ b/clash-protocols/clash-protocols.cabal @@ -149,6 +149,7 @@ library Protocols.PacketStream.Base Protocols.PacketStream.AsyncFifo Protocols.PacketStream.Converters + Protocols.PacketStream.Depacketizers Protocols.PacketStream.PacketFifo Protocols.PacketStream.Packetizers Protocols.PacketStream.Routing @@ -201,6 +202,7 @@ test-suite unittests Tests.Protocols.PacketStream.AsyncFifo Tests.Protocols.PacketStream.Base Tests.Protocols.PacketStream.Converters + Tests.Protocols.PacketStream.Depacketizers Tests.Protocols.PacketStream.Packetizers Tests.Protocols.PacketStream.PacketFifo Tests.Protocols.PacketStream.Routing diff --git a/clash-protocols/src/Protocols/PacketStream.hs b/clash-protocols/src/Protocols/PacketStream.hs index 61eec3c6..79098f04 100644 --- a/clash-protocols/src/Protocols/PacketStream.hs +++ b/clash-protocols/src/Protocols/PacketStream.hs @@ -5,6 +5,7 @@ module Protocols.PacketStream ( module Protocols.PacketStream.AsyncFifo, module Protocols.PacketStream.Base, module Protocols.PacketStream.Converters, + module Protocols.PacketStream.Depacketizers, module Protocols.PacketStream.PacketFifo, module Protocols.PacketStream.Packetizers, module Protocols.PacketStream.Routing, @@ -14,6 +15,7 @@ where import Protocols.PacketStream.AsyncFifo import Protocols.PacketStream.Base import Protocols.PacketStream.Converters +import Protocols.PacketStream.Depacketizers import Protocols.PacketStream.PacketFifo import Protocols.PacketStream.Packetizers import Protocols.PacketStream.Routing diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs new file mode 100644 index 00000000..9a1acb3e --- /dev/null +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -0,0 +1,355 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE NoImplicitPrelude #-} + +{- | +Utility circuits for stripping headers from the beginning of packets. +-} +module Protocols.PacketStream.Depacketizers ( + depacketizerC, + depacketizeToDfC, +) where + +import Clash.Prelude +import Clash.Sized.Vector.Extra + +import Protocols +import qualified Protocols.Df as Df +import Protocols.PacketStream.Base + +import Data.Maybe + +defaultByte :: BitVector 8 +defaultByte = 0x00 + +-- Since the header might be unaligned compared to the datawidth +-- we need to store a partial fragment when forwarding. +-- The fragment we need to store depends on our "unalignedness". +-- +-- Ex. We parse a header of 17 bytes and our @dataWidth@ is 4 bytes. +-- That means at the end of the header we can have upto 3 bytes left +-- in the fragment which we may need to forward. +type DeForwardBufSize (headerBytes :: Nat) (dataWidth :: Nat) = + (dataWidth - (headerBytes `Mod` dataWidth)) `Mod` dataWidth + +type DepacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = + ( headerBytes `Mod` dataWidth <= dataWidth + , KnownNat dataWidth + , 1 <= dataWidth + , KnownNat headerBytes + ) + +data DepacketizerState (headerBytes :: Nat) (dataWidth :: Nat) + = DeParse + { _deAborted :: Bool + , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) + , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + , _deCounter :: Index (headerBytes `DivRU` dataWidth) + } + | DeForward + { _deAborted :: Bool + , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) + , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + , _deCounter :: Index (headerBytes `DivRU` dataWidth) + } + | DeLastForward + { _deAborted :: Bool + , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) + , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + , _deCounter :: Index (headerBytes `DivRU` dataWidth) + , _deLastIdx :: Index dataWidth + } + deriving (Show, ShowX, Generic) + +deriving instance + (DepacketizerCt headerBytes dataWidth) => + NFDataX (DepacketizerState headerBytes dataWidth) + +depacketizerT :: + forall + (headerBytes :: Nat) + (dataWidth :: Nat) + (header :: Type) + (metaIn :: Type) + (metaOut :: Type). + (BitSize header ~ headerBytes * 8) => + (BitPack header) => + (NFDataX metaIn) => + (DepacketizerCt headerBytes dataWidth) => + (DeForwardBufSize headerBytes dataWidth <= dataWidth) => + (headerBytes <= dataWidth * headerBytes `DivRU` dataWidth) => + (header -> metaIn -> metaOut) -> + DepacketizerState headerBytes dataWidth -> + (Maybe (PacketStreamM2S dataWidth metaIn), PacketStreamS2M) -> + ( DepacketizerState headerBytes dataWidth + , (PacketStreamS2M, Maybe (PacketStreamM2S dataWidth metaOut)) + ) +depacketizerT _ DeParse{..} (Just PacketStreamM2S{..}, _) = (nextSt, (PacketStreamS2M outReady, Nothing)) + where + nextAborted = _deAborted || _abort + nextParseBuf = fst $ shiftInAtN _deParseBuf _data + fwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + fwdBuf = dropLe (SNat @(dataWidth - DeForwardBufSize headerBytes dataWidth)) _data + + prematureEnd idx = + case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of + SNatGT -> idx < (natToNum @(headerBytes `Mod` dataWidth)) + _ -> True + + nextCounter = pred _deCounter + + nextSt = + case (_deCounter == 0, _last) of + (False, Nothing) -> + DeParse nextAborted nextParseBuf fwdBuf nextCounter + (done, Just idx) + | not done || prematureEnd idx -> + DeParse False nextParseBuf fwdBuf maxBound + (True, Just idx) -> + DeLastForward + nextAborted + nextParseBuf + fwdBuf + nextCounter + (idx - natToNum @(headerBytes `Mod` dataWidth)) + (True, Nothing) -> + DeForward nextAborted nextParseBuf fwdBuf nextCounter + _ -> + clashCompileError + "depacketizerT Parse: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + + outReady + | DeLastForward{} <- nextSt = False + | otherwise = True +depacketizerT _ st@DeParse{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) +depacketizerT toMetaOut st@DeForward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = (nextStOut, (PacketStreamS2M outReady, Just outPkt)) + where + nextAborted = _deAborted || _abort + dataOut :: Vec dataWidth (BitVector 8) + nextFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + (dataOut, nextFwdBuf) = splitAt (SNat @dataWidth) $ _deFwdBuf ++ _data + + deAdjustLast :: Index dataWidth -> Either (Index dataWidth) (Index dataWidth) + deAdjustLast idx = if outputNow then Left nowIdx else Right nextIdx + where + outputNow = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of + SNatGT -> idx < natToNum @(headerBytes `Mod` dataWidth) + _ -> True + nowIdx = idx + natToNum @(DeForwardBufSize headerBytes dataWidth) + nextIdx = idx - natToNum @(headerBytes `Mod` dataWidth) + + newLast = fmap deAdjustLast _last + outPkt = + pkt + { _abort = nextAborted + , _data = dataOut + , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _deParseBuf) _meta + , _last = either Just (const Nothing) =<< newLast + } + + nextSt = case newLast of + Nothing -> DeForward nextAborted _deParseBuf nextFwdBuf _deCounter + Just (Left _) -> DeParse False _deParseBuf nextFwdBuf maxBound + Just (Right idx) -> DeLastForward nextAborted _deParseBuf nextFwdBuf _deCounter idx + nextStOut = if _ready bwdIn then nextSt else st + + outReady + | DeLastForward{} <- nextSt = False + | otherwise = _ready bwdIn +depacketizerT _ st@DeForward{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) +depacketizerT toMetaOut st@DeLastForward{..} (fwdIn, bwdIn) = (nextStOut, (bwdIn, Just outPkt)) + where + -- We can only get in this state if the previous clock cycle we received a fwdIn + -- which was also the last fragment + inPkt = fromJustX fwdIn + outPkt = + PacketStreamM2S + { _abort = _deAborted || _abort inPkt + , _data = + _deFwdBuf ++ repeat @(dataWidth - DeForwardBufSize headerBytes dataWidth) defaultByte + , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _deParseBuf) (_meta inPkt) + , _last = Just $ fromJustX (_last inPkt) - natToNum @(headerBytes `Mod` dataWidth) + } + nextStOut = if _ready bwdIn then DeParse False _deParseBuf _deFwdBuf maxBound else st + +-- | Reads bytes at the start of each packet into metadata. +depacketizerC :: + forall + (dom :: Domain) + (dataWidth :: Nat) + (metaIn :: Type) + (metaOut :: Type) + (header :: Type) + (headerBytes :: Nat). + (HiddenClockResetEnable dom) => + (NFDataX metaOut) => + (NFDataX metaIn) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + (KnownNat dataWidth) => + -- | Used to compute final metadata of outgoing packets from header and incoming metadata + (header -> metaIn -> metaOut) -> + Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) +depacketizerC toMetaOut = forceResetSanity |> fromSignals outCircuit + where + modProof = compareSNat (SNat @(headerBytes `Mod` dataWidth)) (SNat @dataWidth) + divProof = compareSNat (SNat @headerBytes) (SNat @(dataWidth * headerBytes `DivRU` dataWidth)) + + outCircuit = + case (modProof, divProof) of + (SNatLE, SNatLE) -> case compareSNat (SNat @(DeForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of + SNatLE -> + mealyB + (depacketizerT @headerBytes toMetaOut) + (DeParse False (repeat undefined) (repeat undefined) maxBound) + _ -> + clashCompileError + "depacketizerC1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + _ -> + clashCompileError + "depacketizerC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + +type ParseBufSize (headerBytes :: Nat) (dataWidth :: Nat) = + dataWidth * headerBytes `DivRU` dataWidth + +type DepacketizeToDfCt (headerBytes :: Nat) (dataWidth :: Nat) = + ( 1 <= headerBytes `DivRU` dataWidth + , headerBytes `Mod` dataWidth <= dataWidth + , headerBytes <= ParseBufSize headerBytes dataWidth + , KnownNat dataWidth + , 1 <= dataWidth + , KnownNat headerBytes + ) + +data DfDepacketizerState (a :: Type) (headerBytes :: Nat) (dataWidth :: Nat) + = Parse + { _dfDeAborted :: Bool + -- ^ whether any of the fragments parsed from the current packet were aborted. + , _dfDeParseBuf :: Vec (ParseBufSize headerBytes dataWidth) (BitVector 8) + -- ^ the accumulator for header bytes. + , _dfDeCounter :: Index (headerBytes `DivRU` dataWidth) + -- ^ how many of the _parseBuf bytes are currently valid (accumulation count). We flush at counter == maxBound + } + | ConsumePadding + { _dfDeAborted :: Bool + -- ^ whether any of the fragments parsed from the current packet were aborted. + , _dfDeParseBuf :: Vec (ParseBufSize headerBytes dataWidth) (BitVector 8) + } + deriving (Generic, Show, ShowX) + +deriving instance + (NFDataX a, DepacketizeToDfCt headerBytes dataWidth) => + NFDataX (DfDepacketizerState a headerBytes dataWidth) + +dfDeInitialState :: + forall (a :: Type) (headerBytes :: Nat) (dataWidth :: Nat). + (KnownNat dataWidth) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + DfDepacketizerState a headerBytes dataWidth +dfDeInitialState = Parse False (repeat undefined) maxBound + +depacketizeToDfT :: + forall + (dom :: Domain) + (dataWidth :: Nat) + (a :: Type) + (meta :: Type) + (header :: Type) + (headerBytes :: Nat). + (HiddenClockResetEnable dom) => + (NFDataX meta) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => + (DepacketizeToDfCt headerBytes dataWidth) => + (header -> meta -> a) -> + DfDepacketizerState a headerBytes dataWidth -> + (Maybe (PacketStreamM2S dataWidth meta), Ack) -> + (DfDepacketizerState a headerBytes dataWidth, (PacketStreamS2M, Df.Data a)) +depacketizeToDfT toOut st@Parse{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) + where + nextAborted = _dfDeAborted || _abort + nextParseBuf = fst (shiftInAtN _dfDeParseBuf _data) + outDf = toOut (bitCoerce (takeLe (SNat @headerBytes) nextParseBuf)) _meta + + prematureEnd idx = + case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of + SNatGT -> idx < (natToNum @(headerBytes `Mod` dataWidth - 1)) + _ -> idx < (natToNum @(dataWidth - 1)) + + (nextSt, fwdOut) = + case (_dfDeCounter == 0, _last) of + (False, Nothing) -> + (Parse nextAborted nextParseBuf (pred _dfDeCounter), Df.NoData) + (c, Just idx) + | not c || prematureEnd idx -> + (dfDeInitialState, Df.NoData) + (True, Just _) -> + (dfDeInitialState, if nextAborted then Df.NoData else Df.Data outDf) + (True, Nothing) -> + (ConsumePadding nextAborted nextParseBuf, Df.NoData) + _ -> + clashCompileError + "depacketizeToDfT Parse: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + + readyOut = isNothing (Df.dataToMaybe fwdOut) || readyIn + nextStOut = if readyOut then nextSt else st +depacketizeToDfT toOut st@ConsumePadding{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) + where + nextAborted = _dfDeAborted || _abort + outDf = toOut (bitCoerce (takeLe (SNat @headerBytes) _dfDeParseBuf)) _meta + + (nextSt, fwdOut) = + if isJust _last + then (dfDeInitialState, if nextAborted then Df.NoData else Df.Data outDf) + else (st{_dfDeAborted = nextAborted}, Df.NoData) + + readyOut = isNothing (Df.dataToMaybe fwdOut) || readyIn + nextStOut = if readyOut then nextSt else st +depacketizeToDfT _ st (Nothing, Ack ready) = (st, (PacketStreamS2M ready, Df.NoData)) + +{- | Reads bytes at the start of each packet into a dataflow. +Consumes the remainder of the packet and drops this. If a +packet ends sooner than the assumed length of the header, +`depacketizeToDfC` does not send out anything. +If any of the fragments in the packet has _abort set, it drops +the entire packet. +-} +depacketizeToDfC :: + forall + (dom :: Domain) + (dataWidth :: Nat) + (a :: Type) + (meta :: Type) + (header :: Type) + (headerBytes :: Nat). + (HiddenClockResetEnable dom) => + (NFDataX meta) => + (NFDataX a) => + (BitPack header) => + (KnownNat headerBytes) => + (KnownNat dataWidth) => + (1 <= dataWidth) => + (BitSize header ~ headerBytes * 8) => + -- | function that transforms the given meta + parsed header to the output Df + (header -> meta -> a) -> + Circuit (PacketStream dom dataWidth meta) (Df dom a) +depacketizeToDfC toOut = forceResetSanity |> fromSignals outCircuit + where + divProof = compareSNat (SNat @headerBytes) (SNat @(dataWidth * headerBytes `DivRU` dataWidth)) + modProof = compareSNat (SNat @(headerBytes `Mod` dataWidth)) (SNat @dataWidth) + + outCircuit = + case (divProof, modProof) of + (SNatLE, SNatLE) -> case compareSNat d1 (SNat @(headerBytes `DivRU` dataWidth)) of + SNatLE -> mealyB (depacketizeToDfT toOut) dfDeInitialState + _ -> + clashCompileError + "depacketizeToDfC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + _ -> + clashCompileError + "depacketizeToDfC1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index e004b2f4..c5730058 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -5,20 +5,16 @@ {-# LANGUAGE NoImplicitPrelude #-} {- | -Utility circuits for appending and stripping headers to and from the beginning of packets. +Utility circuits for appending headers to the beginning of packets. -} module Protocols.PacketStream.Packetizers ( packetizerC, - depacketizerC, packetizeFromDfC, - depacketizeToDfC, ) where import Clash.Prelude -import Clash.Sized.Vector.Extra import Protocols -import Protocols.Df (Data (..)) import qualified Protocols.Df as Df import Protocols.PacketStream.Base @@ -207,195 +203,6 @@ packetizerC toMetaOut toHeader = fromSignals outCircuit clashCompileError "packetizerC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" --- Since the header might be unaligned compared to the datawidth --- we need to store a partial fragment when forwarding. --- The fragment we need to store depends on our "unalignedness". --- --- Ex. We parse a header of 17 bytes and our @dataWidth@ is 4 bytes. --- That means at the end of the header we can have upto 3 bytes left --- in the fragment which we may need to forward. -type DeForwardBufSize (headerBytes :: Nat) (dataWidth :: Nat) = - (dataWidth - (headerBytes `Mod` dataWidth)) `Mod` dataWidth - -type DepacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = - ( headerBytes `Mod` dataWidth <= dataWidth - , KnownNat dataWidth - , 1 <= dataWidth - , KnownNat headerBytes - ) - -data DepacketizerState (headerBytes :: Nat) (dataWidth :: Nat) - = DeParse - { _deAborted :: Bool - , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - , _deCounter :: Index (headerBytes `DivRU` dataWidth) - } - | DeForward - { _deAborted :: Bool - , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - , _deCounter :: Index (headerBytes `DivRU` dataWidth) - } - | DeLastForward - { _deAborted :: Bool - , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - , _deCounter :: Index (headerBytes `DivRU` dataWidth) - , _deLastIdx :: Index dataWidth - } - deriving (Show, ShowX, Generic) - -deriving instance - (DepacketizerCt headerBytes dataWidth) => - NFDataX (DepacketizerState headerBytes dataWidth) - -depacketizerT :: - forall - (headerBytes :: Nat) - (dataWidth :: Nat) - (header :: Type) - (metaIn :: Type) - (metaOut :: Type). - (BitSize header ~ headerBytes * 8) => - (BitPack header) => - (NFDataX metaIn) => - (DepacketizerCt headerBytes dataWidth) => - (DeForwardBufSize headerBytes dataWidth <= dataWidth) => - (headerBytes <= dataWidth * headerBytes `DivRU` dataWidth) => - (header -> metaIn -> metaOut) -> - DepacketizerState headerBytes dataWidth -> - (Maybe (PacketStreamM2S dataWidth metaIn), PacketStreamS2M) -> - ( DepacketizerState headerBytes dataWidth - , (PacketStreamS2M, Maybe (PacketStreamM2S dataWidth metaOut)) - ) -depacketizerT _ DeParse{..} (Just PacketStreamM2S{..}, _) = (nextSt, (PacketStreamS2M outReady, Nothing)) - where - nextAborted = _deAborted || _abort - nextParseBuf = fst $ shiftInAtN _deParseBuf _data - fwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - fwdBuf = dropLe (SNat @(dataWidth - DeForwardBufSize headerBytes dataWidth)) _data - - prematureEnd idx = - case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of - SNatGT -> idx < (natToNum @(headerBytes `Mod` dataWidth)) - _ -> True - - nextCounter = pred _deCounter - - nextSt = - case (_deCounter == 0, _last) of - (False, Nothing) -> - DeParse nextAborted nextParseBuf fwdBuf nextCounter - (done, Just idx) - | not done || prematureEnd idx -> - DeParse False nextParseBuf fwdBuf maxBound - (True, Just idx) -> - DeLastForward - nextAborted - nextParseBuf - fwdBuf - nextCounter - (idx - natToNum @(headerBytes `Mod` dataWidth)) - (True, Nothing) -> - DeForward nextAborted nextParseBuf fwdBuf nextCounter - _ -> - clashCompileError - "depacketizerT Parse: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" - - outReady - | DeLastForward{} <- nextSt = False - | otherwise = True -depacketizerT _ st@DeParse{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) -depacketizerT toMetaOut st@DeForward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = (nextStOut, (PacketStreamS2M outReady, Just outPkt)) - where - nextAborted = _deAborted || _abort - dataOut :: Vec dataWidth (BitVector 8) - nextFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - (dataOut, nextFwdBuf) = splitAt (SNat @dataWidth) $ _deFwdBuf ++ _data - - deAdjustLast :: Index dataWidth -> Either (Index dataWidth) (Index dataWidth) - deAdjustLast idx = if outputNow then Left nowIdx else Right nextIdx - where - outputNow = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of - SNatGT -> idx < natToNum @(headerBytes `Mod` dataWidth) - _ -> True - nowIdx = idx + natToNum @(DeForwardBufSize headerBytes dataWidth) - nextIdx = idx - natToNum @(headerBytes `Mod` dataWidth) - - newLast = fmap deAdjustLast _last - outPkt = - pkt - { _abort = nextAborted - , _data = dataOut - , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _deParseBuf) _meta - , _last = either Just (const Nothing) =<< newLast - } - - nextSt = case newLast of - Nothing -> DeForward nextAborted _deParseBuf nextFwdBuf _deCounter - Just (Left _) -> DeParse False _deParseBuf nextFwdBuf maxBound - Just (Right idx) -> DeLastForward nextAborted _deParseBuf nextFwdBuf _deCounter idx - nextStOut = if _ready bwdIn then nextSt else st - - outReady - | DeLastForward{} <- nextSt = False - | otherwise = _ready bwdIn -depacketizerT _ st@DeForward{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) -depacketizerT toMetaOut st@DeLastForward{..} (fwdIn, bwdIn) = (nextStOut, (bwdIn, Just outPkt)) - where - -- We can only get in this state if the previous clock cycle we received a fwdIn - -- which was also the last fragment - inPkt = fromJustX fwdIn - outPkt = - PacketStreamM2S - { _abort = _deAborted || _abort inPkt - , _data = - _deFwdBuf ++ repeat @(dataWidth - DeForwardBufSize headerBytes dataWidth) defaultByte - , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _deParseBuf) (_meta inPkt) - , _last = Just $ fromJustX (_last inPkt) - natToNum @(headerBytes `Mod` dataWidth) - } - nextStOut = if _ready bwdIn then DeParse False _deParseBuf _deFwdBuf maxBound else st - --- | Reads bytes at the start of each packet into metadata. -depacketizerC :: - forall - (dom :: Domain) - (dataWidth :: Nat) - (metaIn :: Type) - (metaOut :: Type) - (header :: Type) - (headerBytes :: Nat). - (HiddenClockResetEnable dom) => - (NFDataX metaOut) => - (NFDataX metaIn) => - (BitPack header) => - (BitSize header ~ headerBytes * 8) => - (KnownNat headerBytes) => - (1 <= dataWidth) => - (KnownNat dataWidth) => - -- | Used to compute final metadata of outgoing packets from header and incoming metadata - (header -> metaIn -> metaOut) -> - Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) -depacketizerC toMetaOut = forceResetSanity |> fromSignals outCircuit - where - modProof = compareSNat (SNat @(headerBytes `Mod` dataWidth)) (SNat @dataWidth) - divProof = compareSNat (SNat @headerBytes) (SNat @(dataWidth * headerBytes `DivRU` dataWidth)) - - outCircuit = - case (modProof, divProof) of - (SNatLE, SNatLE) -> case compareSNat (SNat @(DeForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of - SNatLE -> - mealyB - (depacketizerT @headerBytes toMetaOut) - (DeParse False (repeat undefined) (repeat undefined) maxBound) - _ -> - clashCompileError - "depacketizerC1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" - _ -> - clashCompileError - "depacketizerC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" - {- | If dataWidth >= headerBytes, we don't need a buffer because we can immediately send the fragment. Else, we need a buffer that stores the headerBytes minus the size of the fragment we send out immediately. @@ -431,11 +238,11 @@ packetizeFromDfT :: -- | function that transforms the Df input to the header that will be packetized. (a -> header) -> DfPacketizerState metaOut headerBytes dataWidth -> - (Data a, PacketStreamS2M) -> + (Df.Data a, PacketStreamS2M) -> ( DfPacketizerState metaOut headerBytes dataWidth , (Ack, Maybe (PacketStreamM2S dataWidth metaOut)) ) -packetizeFromDfT toMetaOut toHeader DfIdle (Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) +packetizeFromDfT toMetaOut toHeader DfIdle (Df.Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) where rotatedHdr = rotateRightS (bitCoerce (toHeader dataIn)) (SNat @(DfHeaderBufSize headerBytes dataWidth)) @@ -455,7 +262,7 @@ packetizeFromDfT toMetaOut toHeader DfIdle (Data dataIn, bwdIn) = (nextStOut, (b -- fwdIn is always Data in this state, because we assert backpressure in Idle before we go here -- Thus, we don't need to store the metadata in the state. -packetizeFromDfT toMetaOut _ st@DfInsert{..} (Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) +packetizeFromDfT toMetaOut _ st@DfInsert{..} (Df.Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) where (dataOut, newHdrBuf) = splitAt (SNat @dataWidth) (_dfHdrBuf ++ repeat @dataWidth defaultByte) outPkt = PacketStreamM2S dataOut newLast (toMetaOut dataIn) False @@ -467,7 +274,7 @@ packetizeFromDfT toMetaOut _ st@DfInsert{..} (Data dataIn, bwdIn) = (nextStOut, bwdOut = Ack (_ready bwdIn && _dfCounter == maxBound) nextSt = if _dfCounter == maxBound then DfIdle else DfInsert (succ _dfCounter) newHdrBuf nextStOut = if _ready bwdIn then nextSt else st -packetizeFromDfT _ _ s (NoData, bwdIn) = (s, (Ack (_ready bwdIn), Nothing)) +packetizeFromDfT _ _ s (Df.NoData, bwdIn) = (s, (Ack (_ready bwdIn), Nothing)) {- | Starts a packet stream upon receiving some data. The bytes to be packetized and the output metadata @@ -507,144 +314,3 @@ packetizeFromDfC toMetaOut toHeader = fromSignals ckt _ -> clashCompileError "packetizeFromDfC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" - -type ParseBufSize (headerBytes :: Nat) (dataWidth :: Nat) = - dataWidth * headerBytes `DivRU` dataWidth - -type DepacketizeToDfCt (headerBytes :: Nat) (dataWidth :: Nat) = - ( 1 <= headerBytes `DivRU` dataWidth - , headerBytes `Mod` dataWidth <= dataWidth - , headerBytes <= ParseBufSize headerBytes dataWidth - , KnownNat dataWidth - , 1 <= dataWidth - , KnownNat headerBytes - ) - -data DfDepacketizerState (a :: Type) (headerBytes :: Nat) (dataWidth :: Nat) - = Parse - { _dfDeAborted :: Bool - -- ^ whether any of the fragments parsed from the current packet were aborted. - , _dfDeParseBuf :: Vec (ParseBufSize headerBytes dataWidth) (BitVector 8) - -- ^ the accumulator for header bytes. - , _dfDeCounter :: Index (headerBytes `DivRU` dataWidth) - -- ^ how many of the _parseBuf bytes are currently valid (accumulation count). We flush at counter == maxBound - } - | ConsumePadding - { _dfDeAborted :: Bool - -- ^ whether any of the fragments parsed from the current packet were aborted. - , _dfDeParseBuf :: Vec (ParseBufSize headerBytes dataWidth) (BitVector 8) - } - deriving (Generic, Show, ShowX) - -deriving instance - (NFDataX a, DepacketizeToDfCt headerBytes dataWidth) => - NFDataX (DfDepacketizerState a headerBytes dataWidth) - -dfDeInitialState :: - forall (a :: Type) (headerBytes :: Nat) (dataWidth :: Nat). - (KnownNat dataWidth) => - (KnownNat headerBytes) => - (1 <= dataWidth) => - DfDepacketizerState a headerBytes dataWidth -dfDeInitialState = Parse False (repeat undefined) maxBound - -depacketizeToDfT :: - forall - (dom :: Domain) - (dataWidth :: Nat) - (a :: Type) - (meta :: Type) - (header :: Type) - (headerBytes :: Nat). - (HiddenClockResetEnable dom) => - (NFDataX meta) => - (BitPack header) => - (BitSize header ~ headerBytes * 8) => - (DepacketizeToDfCt headerBytes dataWidth) => - (header -> meta -> a) -> - DfDepacketizerState a headerBytes dataWidth -> - (Maybe (PacketStreamM2S dataWidth meta), Ack) -> - (DfDepacketizerState a headerBytes dataWidth, (PacketStreamS2M, Df.Data a)) -depacketizeToDfT toOut st@Parse{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) - where - nextAborted = _dfDeAborted || _abort - nextParseBuf = fst (shiftInAtN _dfDeParseBuf _data) - outDf = toOut (bitCoerce (takeLe (SNat @headerBytes) nextParseBuf)) _meta - - prematureEnd idx = - case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of - SNatGT -> idx < (natToNum @(headerBytes `Mod` dataWidth - 1)) - _ -> idx < (natToNum @(dataWidth - 1)) - - (nextSt, fwdOut) = - case (_dfDeCounter == 0, _last) of - (False, Nothing) -> - (Parse nextAborted nextParseBuf (pred _dfDeCounter), Df.NoData) - (c, Just idx) - | not c || prematureEnd idx -> - (dfDeInitialState, Df.NoData) - (True, Just _) -> - (dfDeInitialState, if nextAborted then Df.NoData else Df.Data outDf) - (True, Nothing) -> - (ConsumePadding nextAborted nextParseBuf, Df.NoData) - _ -> - clashCompileError - "depacketizeToDfT Parse: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" - - readyOut = isNothing (Df.dataToMaybe fwdOut) || readyIn - nextStOut = if readyOut then nextSt else st -depacketizeToDfT toOut st@ConsumePadding{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) - where - nextAborted = _dfDeAborted || _abort - outDf = toOut (bitCoerce (takeLe (SNat @headerBytes) _dfDeParseBuf)) _meta - - (nextSt, fwdOut) = - if isJust _last - then (dfDeInitialState, if nextAborted then Df.NoData else Df.Data outDf) - else (st{_dfDeAborted = nextAborted}, Df.NoData) - - readyOut = isNothing (Df.dataToMaybe fwdOut) || readyIn - nextStOut = if readyOut then nextSt else st -depacketizeToDfT _ st (Nothing, Ack ready) = (st, (PacketStreamS2M ready, Df.NoData)) - -{- | Reads bytes at the start of each packet into a dataflow. -Consumes the remainder of the packet and drops this. If a -packet ends sooner than the assumed length of the header, -`depacketizeToDfC` does not send out anything. -If any of the fragments in the packet has _abort set, it drops -the entire packet. --} -depacketizeToDfC :: - forall - (dom :: Domain) - (dataWidth :: Nat) - (a :: Type) - (meta :: Type) - (header :: Type) - (headerBytes :: Nat). - (HiddenClockResetEnable dom) => - (NFDataX meta) => - (NFDataX a) => - (BitPack header) => - (KnownNat headerBytes) => - (KnownNat dataWidth) => - (1 <= dataWidth) => - (BitSize header ~ headerBytes * 8) => - -- | function that transforms the given meta + parsed header to the output Df - (header -> meta -> a) -> - Circuit (PacketStream dom dataWidth meta) (Df dom a) -depacketizeToDfC toOut = forceResetSanity |> fromSignals outCircuit - where - divProof = compareSNat (SNat @headerBytes) (SNat @(dataWidth * headerBytes `DivRU` dataWidth)) - modProof = compareSNat (SNat @(headerBytes `Mod` dataWidth)) (SNat @dataWidth) - - outCircuit = - case (divProof, modProof) of - (SNatLE, SNatLE) -> case compareSNat d1 (SNat @(headerBytes `DivRU` dataWidth)) of - SNatLE -> mealyB (depacketizeToDfT toOut) dfDeInitialState - _ -> - clashCompileError - "depacketizeToDfC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" - _ -> - clashCompileError - "depacketizeToDfC1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream.hs b/clash-protocols/tests/Tests/Protocols/PacketStream.hs index c349afc7..b7b7392a 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream.hs @@ -4,6 +4,7 @@ import Test.Tasty import qualified Tests.Protocols.PacketStream.AsyncFifo import qualified Tests.Protocols.PacketStream.Converters +import qualified Tests.Protocols.PacketStream.Depacketizers import qualified Tests.Protocols.PacketStream.PacketFifo import qualified Tests.Protocols.PacketStream.Packetizers import qualified Tests.Protocols.PacketStream.Routing @@ -14,6 +15,7 @@ tests = "PacketStream" [ Tests.Protocols.PacketStream.AsyncFifo.tests , Tests.Protocols.PacketStream.Converters.tests + , Tests.Protocols.PacketStream.Depacketizers.tests , Tests.Protocols.PacketStream.PacketFifo.tests , Tests.Protocols.PacketStream.Packetizers.tests , Tests.Protocols.PacketStream.Routing.tests diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs new file mode 100644 index 00000000..4f1f3ce6 --- /dev/null +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -0,0 +1,225 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NumericUnderscores #-} + +module Tests.Protocols.PacketStream.Depacketizers ( + depacketizerModel, + depacketizeToDfModel, + tests, +) where + +-- base +import qualified Data.List as L +import Prelude + +-- clash +import Clash.Prelude +import qualified Clash.Prelude as C +import Clash.Sized.Vector (unsafeFromList) + +-- hedgehog +import Hedgehog +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range + +-- tasty +import Test.Tasty +import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) + +import Test.Tasty.Hedgehog.Extra (testProperty) +import Test.Tasty.TH (testGroupGenerator) + +-- clash-protocols + +import Protocols.Hedgehog +import Protocols.PacketStream.Base + +-- tests +import Protocols +import Protocols.PacketStream (depacketizeToDfC, depacketizerC) +import Tests.Protocols.PacketStream.Base + +genVec :: (KnownNat n, 1 <= n) => Gen a -> Gen (Vec n a) +genVec gen = sequence (C.repeat gen) + +-- | Model of the generic `depacketizerC`. +depacketizerModel :: + forall + (dataWidth :: Nat) + (headerBytes :: Nat) + (metaIn :: Type) + (metaOut :: Type) + (header :: Type). + ( KnownNat dataWidth + , KnownNat headerBytes + , 1 <= dataWidth + , 1 <= headerBytes + , BitPack header + , BitSize header ~ headerBytes * 8 + ) => + (header -> metaIn -> metaOut) -> + [PacketStreamM2S dataWidth metaIn] -> + [PacketStreamM2S dataWidth metaOut] +depacketizerModel toMetaOut ps = L.concat dataWidthPackets + where + hdrbytes = natToNum @headerBytes + + parseHdr :: + ([PacketStreamM2S 1 metaIn], [PacketStreamM2S 1 metaIn]) -> [PacketStreamM2S 1 metaOut] + parseHdr (hdrF, fwdF) = fmap (\f -> f{_meta = metaOut}) fwdF + where + hdr = bitCoerce $ unsafeFromList @headerBytes $ _data <$> hdrF + metaOut = toMetaOut hdr (_meta $ L.head fwdF) + + bytePackets :: [[PacketStreamM2S 1 metaIn]] + bytePackets = + L.filter (\fs -> L.length fs > hdrbytes) $ + L.concatMap chopPacket . smearAbort <$> chunkByPacket ps + + parsedPackets :: [[PacketStreamM2S 1 metaOut]] + parsedPackets = parseHdr . L.splitAt hdrbytes <$> bytePackets + + dataWidthPackets :: [[PacketStreamM2S dataWidth metaOut]] + dataWidthPackets = fmap chunkToPacket . chopBy (natToNum @dataWidth) <$> parsedPackets + +-- | Model of the generic `depacketizeToDfC`. +depacketizeToDfModel :: + forall + (dataWidth :: Nat) + (headerBytes :: Nat) + (meta :: Type) + (a :: Type) + (header :: Type). + ( KnownNat dataWidth + , KnownNat headerBytes + , 1 <= dataWidth + , 1 <= headerBytes + , BitPack header + , BitSize header ~ headerBytes * 8 + ) => + (header -> meta -> a) -> + [PacketStreamM2S dataWidth meta] -> + [a] +depacketizeToDfModel toOut ps = parseHdr <$> bytePackets + where + hdrbytes = natToNum @headerBytes + + parseHdr :: [PacketStreamM2S 1 meta] -> a + parseHdr hdrF = toOut (bitCoerce $ unsafeFromList @headerBytes $ _data <$> hdrF) (_meta $ L.head hdrF) + + bytePackets :: [[PacketStreamM2S 1 meta]] + bytePackets = + L.filter (\fs -> L.length fs >= hdrbytes) $ + L.concatMap chopPacket <$> chunkByPacket (dropAbortedPackets ps) + +{- | Test the depacketizer with varying datawidth and number of bytes in the header, + with metaIn = () and toMetaOut = const. +-} +depacketizerPropertyGenerator :: + forall + (dataWidth :: Nat) + (headerBytes :: Nat). + (1 <= dataWidth) => + (1 <= headerBytes) => + SNat dataWidth -> + SNat headerBytes -> + Property +depacketizerPropertyGenerator SNat SNat = + idWithModelSingleDomain + @System + defExpectOptions + (fmap (cleanPackets . fullPackets) (Gen.list (Range.linear 1 100) genPackets)) + (exposeClockResetEnable model) + (exposeClockResetEnable ckt) + where + model :: + [PacketStreamM2S dataWidth ()] -> + [PacketStreamM2S dataWidth (Vec headerBytes (BitVector 8))] + model = depacketizerModel const + + ckt :: + (HiddenClockResetEnable System) => + Circuit + (PacketStream System dataWidth ()) + (PacketStream System dataWidth (Vec headerBytes (BitVector 8))) + ckt = depacketizerC const + + genPackets = + PacketStreamM2S + <$> genVec Gen.enumBounded + <*> Gen.maybe Gen.enumBounded + <*> Gen.enumBounded + <*> Gen.enumBounded + +-- | headerBytes % dataWidth ~ 0 +prop_const_depacketizer_d1_d14 :: Property +prop_const_depacketizer_d1_d14 = depacketizerPropertyGenerator d1 d14 + +-- | dataWidth < headerBytes +prop_const_depacketizer_d3_d11 :: Property +prop_const_depacketizer_d3_d11 = depacketizerPropertyGenerator d3 d11 + +-- | dataWidth ~ header byte size +prop_const_depacketizer_d7_d7 :: Property +prop_const_depacketizer_d7_d7 = depacketizerPropertyGenerator d7 d7 + +-- | dataWidth > header byte size +prop_const_depacketizer_d5_d4 :: Property +prop_const_depacketizer_d5_d4 = depacketizerPropertyGenerator d5 d4 + +{- | Test depacketizeToDf with varying datawidth and number of bytes in the header, + with metaIn = () and toMetaOut = const. +-} +depacketizeToDfPropertyGenerator :: + forall + (dataWidth :: Nat) + (headerBytes :: Nat). + (1 <= dataWidth) => + (1 <= headerBytes) => + SNat dataWidth -> + SNat headerBytes -> + Property +depacketizeToDfPropertyGenerator SNat SNat = + idWithModelSingleDomain + @System + defExpectOptions + (fmap (cleanPackets . fullPackets) (Gen.list (Range.linear 1 100) genPackets)) + (exposeClockResetEnable model) + (exposeClockResetEnable ckt) + where + model :: [PacketStreamM2S dataWidth ()] -> [Vec headerBytes (BitVector 8)] + model = depacketizeToDfModel const + + ckt :: + (HiddenClockResetEnable System) => + Circuit (PacketStream System dataWidth ()) (Df System (Vec headerBytes (BitVector 8))) + ckt = depacketizeToDfC const + + genPackets = + PacketStreamM2S + <$> genVec Gen.enumBounded + <*> Gen.maybe Gen.enumBounded + <*> Gen.enumBounded + <*> Gen.enumBounded + +-- | headerBytes % dataWidth ~ 0 +prop_const_depacketize_to_df_d1_d14 :: Property +prop_const_depacketize_to_df_d1_d14 = depacketizeToDfPropertyGenerator d1 d14 + +-- | dataWidth < headerBytes +prop_const_depacketize_to_df_d3_d11 :: Property +prop_const_depacketize_to_df_d3_d11 = depacketizeToDfPropertyGenerator d3 d11 + +-- | dataWidth ~ header byte size +prop_const_depacketize_to_df_d7_d7 :: Property +prop_const_depacketize_to_df_d7_d7 = depacketizeToDfPropertyGenerator d7 d7 + +-- | dataWidth > header byte size +prop_const_depacketize_to_df_d5_d4 :: Property +prop_const_depacketize_to_df_d5_d4 = depacketizeToDfPropertyGenerator d5 d4 + +tests :: TestTree +tests = + localOption (mkTimeout 12_000_000 {- 12 seconds -}) $ + localOption + (HedgehogTestLimit (Just 1_000)) + $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs index c8400994..b107dc39 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs @@ -4,8 +4,6 @@ module Tests.Protocols.PacketStream.Packetizers ( packetizerModel, packetizeFromDfModel, - depacketizerModel, - depacketizeToDfModel, tests, ) where @@ -15,26 +13,43 @@ import Prelude -- clash import Clash.Prelude -import Clash.Sized.Vector (unsafeFromList) +import qualified Clash.Prelude as C -- hedgehog --- import Hedgehog --- import qualified Hedgehog.Gen as Gen --- import qualified Hedgehog.Range as Range +import Hedgehog +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range -- tasty import Test.Tasty import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) --- import Test.Tasty.Hedgehog.Extra (testProperty) +import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) -- clash-protocols +import Protocols +import qualified Protocols.Df as Df +import Protocols.Hedgehog import Protocols.PacketStream.Base -- tests + +import Protocols.PacketStream (packetizeFromDfC, packetizerC) import Tests.Protocols.PacketStream.Base +genVec :: (KnownNat n, 1 <= n) => Gen a -> Gen (Vec n a) +genVec gen = sequence (C.repeat gen) + +genMeta :: + forall (meta :: Type) (metaBytes :: Nat). + (KnownNat metaBytes) => + (1 <= metaBytes) => + (BitPack meta) => + (BitSize meta ~ metaBytes * 8) => + Gen meta +genMeta = fmap bitCoerce (genVec Gen.enumBounded :: Gen (Vec metaBytes (BitVector 8))) + -- | Model of the generic `packetizerC`. packetizerModel :: forall @@ -93,79 +108,108 @@ packetizeFromDfModel toMetaOut toHeader = L.concatMap (upConvert . packetize) (\byte -> PacketStreamM2S (byte :> Nil) Nothing (toMetaOut d) False) (toList $ bitCoerce (toHeader d)) --- | Model of the generic `depacketizerC`. -depacketizerModel :: +{- | Test the packetizer with varying datawidth and number of bytes in the header, + with metaOut = (). +-} +packetizerPropertyGenerator :: forall (dataWidth :: Nat) - (headerBytes :: Nat) - (metaIn :: Type) - (metaOut :: Type) - (header :: Type). - ( KnownNat dataWidth - , KnownNat headerBytes - , 1 <= dataWidth - , 1 <= headerBytes - , BitPack header - , BitSize header ~ headerBytes * 8 - ) => - (header -> metaIn -> metaOut) -> - [PacketStreamM2S dataWidth metaIn] -> - [PacketStreamM2S dataWidth metaOut] -depacketizerModel toMetaOut ps = L.concat dataWidthPackets + (headerBytes :: Nat). + (1 <= dataWidth) => + (1 <= headerBytes) => + SNat dataWidth -> + SNat headerBytes -> + Property +packetizerPropertyGenerator SNat SNat = + idWithModelSingleDomain + @System + defExpectOptions + (fmap (cleanPackets . fullPackets) (Gen.list (Range.linear 1 100) genPackets)) + (exposeClockResetEnable model) + (exposeClockResetEnable ckt) where - hdrbytes = natToNum @headerBytes - - parseHdr :: - ([PacketStreamM2S 1 metaIn], [PacketStreamM2S 1 metaIn]) -> [PacketStreamM2S 1 metaOut] - parseHdr (hdrF, fwdF) = fmap (\f -> f{_meta = metaOut}) fwdF - where - hdr = bitCoerce $ unsafeFromList @headerBytes $ _data <$> hdrF - metaOut = toMetaOut hdr (_meta $ L.head fwdF) - - bytePackets :: [[PacketStreamM2S 1 metaIn]] - bytePackets = - L.filter (\fs -> L.length fs > hdrbytes) $ - L.concatMap chopPacket . smearAbort <$> chunkByPacket ps - - parsedPackets :: [[PacketStreamM2S 1 metaOut]] - parsedPackets = parseHdr . L.splitAt hdrbytes <$> bytePackets - - dataWidthPackets :: [[PacketStreamM2S dataWidth metaOut]] - dataWidthPackets = fmap chunkToPacket . chopBy (natToNum @dataWidth) <$> parsedPackets - --- | Model of the generic `depacketizeToDfC`. -depacketizeToDfModel :: + model :: + [PacketStreamM2S dataWidth (Vec headerBytes (BitVector 8))] -> + [PacketStreamM2S dataWidth ()] + model = packetizerModel (const ()) id + + ckt :: + (HiddenClockResetEnable System) => + Circuit + (PacketStream System dataWidth (Vec headerBytes (BitVector 8))) + (PacketStream System dataWidth ()) + ckt = packetizerC (const ()) id + + genPackets = + PacketStreamM2S + <$> genVec Gen.enumBounded + <*> Gen.maybe Gen.enumBounded + <*> genMeta + <*> Gen.enumBounded + +-- | headerBytes % dataWidth ~ 0 +prop_const_packetizer_d1_d14 :: Property +prop_const_packetizer_d1_d14 = packetizerPropertyGenerator d1 d14 + +-- | dataWidth < headerBytes +prop_const_packetizer_d3_d11 :: Property +prop_const_packetizer_d3_d11 = packetizerPropertyGenerator d3 d11 + +-- | dataWidth ~ header byte size +prop_const_packetizer_d7_d7 :: Property +prop_const_packetizer_d7_d7 = packetizerPropertyGenerator d7 d7 + +-- | dataWidth > header byte size +prop_const_packetizer_d5_d4 :: Property +prop_const_packetizer_d5_d4 = packetizerPropertyGenerator d5 d4 + +{- | Test packetizeFromDf with varying datawidth and number of bytes in the header + , with metaOut = (). +-} +packetizeFromDfPropertyGenerator :: forall (dataWidth :: Nat) - (headerBytes :: Nat) - (meta :: Type) - (a :: Type) - (header :: Type). - ( KnownNat dataWidth - , KnownNat headerBytes - , 1 <= dataWidth - , 1 <= headerBytes - , BitPack header - , BitSize header ~ headerBytes * 8 - ) => - (header -> meta -> a) -> - [PacketStreamM2S dataWidth meta] -> - [a] -depacketizeToDfModel toOut ps = parseHdr <$> bytePackets + (headerBytes :: Nat). + (1 <= dataWidth) => + (1 <= headerBytes) => + SNat dataWidth -> + SNat headerBytes -> + Property +packetizeFromDfPropertyGenerator SNat SNat = + idWithModelSingleDomain + @System + defExpectOptions + (Gen.list (Range.linear 1 100) (genVec Gen.enumBounded)) + (exposeClockResetEnable model) + (exposeClockResetEnable ckt) where - hdrbytes = natToNum @headerBytes + model :: [Vec headerBytes (BitVector 8)] -> [PacketStreamM2S dataWidth ()] + model = packetizeFromDfModel (const ()) id + + ckt :: + (HiddenClockResetEnable System) => + Circuit (Df.Df System (Vec headerBytes (BitVector 8))) (PacketStream System dataWidth ()) + ckt = packetizeFromDfC (const ()) id + +-- | headerBytes % dataWidth ~ 0 +prop_const_packetizeFromDf_d1_d14 :: Property +prop_const_packetizeFromDf_d1_d14 = packetizeFromDfPropertyGenerator d1 d14 + +-- | dataWidth < headerBytes +prop_const_packetizeFromDf_d3_d11 :: Property +prop_const_packetizeFromDf_d3_d11 = packetizeFromDfPropertyGenerator d3 d11 - parseHdr :: [PacketStreamM2S 1 meta] -> a - parseHdr hdrF = toOut (bitCoerce $ unsafeFromList @headerBytes $ _data <$> hdrF) (_meta $ L.head hdrF) +-- | dataWidth ~ header byte size +prop_const_packetizeFromDf_d7_d7 :: Property +prop_const_packetizeFromDf_d7_d7 = packetizeFromDfPropertyGenerator d7 d7 - bytePackets :: [[PacketStreamM2S 1 meta]] - bytePackets = - L.filter (\fs -> L.length fs >= hdrbytes) $ - L.concatMap chopPacket <$> chunkByPacket (dropAbortedPackets ps) +-- | dataWidth > header byte size +prop_const_packetizeFromDf_d5_d4 :: Property +prop_const_packetizeFromDf_d5_d4 = packetizeFromDfPropertyGenerator d5 d4 tests :: TestTree tests = localOption (mkTimeout 12_000_000 {- 12 seconds -}) $ localOption - (HedgehogTestLimit (Just 1_000_000)) + (HedgehogTestLimit (Just 1_000)) $(testGroupGenerator) From 2c3150c8ba5c838a6a5dec515490f28782b51622 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 22 Jul 2024 11:03:35 +0200 Subject: [PATCH 06/63] Hide submodules and improve documentation --- clash-protocols/src/Protocols/PacketStream.hs | 19 +- .../src/Protocols/PacketStream/AsyncFifo.hs | 6 +- .../src/Protocols/PacketStream/Base.hs | 58 ++++-- .../src/Protocols/PacketStream/Converters.hs | 1 + .../Protocols/PacketStream/Depacketizers.hs | 172 +++++++++--------- .../src/Protocols/PacketStream/PacketFifo.hs | 1 + .../src/Protocols/PacketStream/Packetizers.hs | 41 ++--- .../src/Protocols/PacketStream/Routing.hs | 1 + 8 files changed, 170 insertions(+), 129 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream.hs b/clash-protocols/src/Protocols/PacketStream.hs index 79098f04..2dbfbbd8 100644 --- a/clash-protocols/src/Protocols/PacketStream.hs +++ b/clash-protocols/src/Protocols/PacketStream.hs @@ -1,12 +1,25 @@ {- | -Top level PacketStream module which exports all components. + Copyright : (C) 2024, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + +Provides the PacketStream protocol, a simple streaming protocol for transferring packets of data between components. + +Apart from the protocol definition, some components, all of which are generic in @dataWidth@, are also provided: + +1. Several small utilities such as filtering a stream based on its metadata. +2. Fifos +3. Components which upsize or downsize @dataWidth@ +4. Components which read from the stream (depacketizers) +5. Components which write to the stream (packetizers) +6. Components which split and merge a stream based on its metadata -} module Protocols.PacketStream ( - module Protocols.PacketStream.AsyncFifo, module Protocols.PacketStream.Base, + module Protocols.PacketStream.PacketFifo, + module Protocols.PacketStream.AsyncFifo, module Protocols.PacketStream.Converters, module Protocols.PacketStream.Depacketizers, - module Protocols.PacketStream.PacketFifo, module Protocols.PacketStream.Packetizers, module Protocols.PacketStream.Routing, ) diff --git a/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs index b7b9e9d8..2fda483c 100644 --- a/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs @@ -1,4 +1,5 @@ {-# LANGUAGE NoImplicitPrelude #-} +{-# OPTIONS_HADDOCK hide #-} {- | Provides `asyncFifoC` for crossing clock domains in the packet stream protocol. @@ -10,7 +11,7 @@ import Data.Maybe.Extra (toMaybe) import Clash.Explicit.Prelude (asyncFIFOSynchronizer) import Clash.Prelude -import Protocols (Circuit, fromSignals) +import Protocols import Protocols.PacketStream.Base {- | Asynchronous FIFO circuit that can be used to safely cross clock domains. @@ -45,7 +46,8 @@ asyncFifoC :: -- | Enable signal in the read domain Enable rDom -> Circuit (PacketStream wDom dataWidth metaType) (PacketStream rDom dataWidth metaType) -asyncFifoC depth wClk wRst wEn rClk rRst rEn = fromSignals ckt +asyncFifoC depth wClk wRst wEn rClk rRst rEn = + exposeClockResetEnable forceResetSanity wClk wRst wEn |> fromSignals ckt where ckt (fwdIn, bwdIn) = (bwdOut, fwdOut) where diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 13200fe6..769755a2 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -1,8 +1,9 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoImplicitPrelude #-} +{-# OPTIONS_HADDOCK hide #-} {- | -Definitions and instances of the packet stream protocol +Definitions and instances of the PacketStream protocol -} module Protocols.PacketStream.Base ( PacketStreamM2S (..), @@ -30,35 +31,56 @@ import Data.Coerce (coerce) import qualified Data.Maybe as Maybe import Data.Proxy -{- | Data sent from manager to subordinate, a simplified AXI4-Stream like interface -with metadata that can only change on packet delineation. -_tdest, _tuser and _tid are bundled into one big _meta field which holds metadata. -There are no null or position bytes so _tstrb is replaced by a last indicator -that indicates the index of the last valid byte in the _data vector. -_tvalid is modeled via wrapping this in a `Maybe`. +{- | Data sent from manager to subordinate. + +Heavily inspired by the M2S data of AMBA AXI4-Stream, but simplified: + +- @_tdata@ is moved into @_data@, which serves the exact same purpose: the actual + data of the transfer. +- @_tkeep@ is changed to `_last`. +- @_tstrb@ is removed as there are no position bytes. +- @_tid@ is removed, because packets may not be interrupted by other packets. +- @_tdest@ is moved into `_meta`. +- @_tuser@ is moved into `_meta`. +- @_tvalid@ is modeled by wrapping this type into a @Maybe@. -} data PacketStreamM2S (dataWidth :: Nat) (metaType :: Type) = PacketStreamM2S { _data :: Vec dataWidth (BitVector 8) - -- ^ The bytes to be transmitted + -- ^ The bytes to be transmitted. , _last :: Maybe (Index dataWidth) - -- ^ If Nothing, we are not yet at the last byte, otherwise index of last valid byte of _data + -- ^ If this is @Just@ then it signals that this transfer + -- is the end of a packet and contains the index of the last valid byte in `_data`. + -- If it is @Nothing@ then this transfer is not yet the end of a packet and all + -- bytes are valid. This implies that no null bytes are allowed in the middle of + -- a packet, only after a packet. , _meta :: metaType - -- ^ the metadata of a packet. Must be constant during a packet. + -- ^ Metadata of a packet. Must be constant during a packet. , _abort :: Bool - -- ^ If True, the current transfer is aborted and the subordinate should ignore the current transfer + -- ^ Iff true, the packet corresponding to this transfer is invalid. The subordinate + -- must either drop the packet or forward the `_abort`. } deriving (Eq, Generic, ShowX, Show, NFData, Bundle, Functor) -{- | Data sent from the subordinate to the manager -The only information transmitted is whether the subordinate is ready to receive data +{- | Data sent from the subordinate to manager. + +The only information transmitted is whether the subordinate is ready to receive data. -} newtype PacketStreamS2M = PacketStreamS2M { _ready :: Bool - -- ^ Iff True, the subordinate is ready to receive data + -- ^ Iff True, the subordinate is ready to receive data. } deriving (Eq, Generic, ShowX, Show, NFData, Bundle, NFDataX) --- | The packet stream protocol for communication between components +{- | Simple valid-ready streaming protocol for transferring packets between components. + +Invariants: + +1. A manager must not check the `Bwd` channel when it is sending @Nothing@ over the `Fwd` channel. +2. A manager must keep sending the same data until the subordinate has acknowledged it, i.e. upon observing `_ready` as @True@. +3. A manager must keep the metadata (`_meta`) of an entire packet it sends constant. +4. A subordinate which receives a transfer with `_abort` asserted must either forward this `_abort` or drop the packet. +5. A packet may not be interrupted by another packet. +-} data PacketStream (dom :: Domain) (dataWidth :: Nat) (metaType :: Type) deriving instance @@ -86,13 +108,13 @@ instance DfConv.DfConv (PacketStream dom dataWidth metaType) where where go (fwdIn, bwdIn) = ( (fmap coerce bwdIn, pure undefined) - , fmap Df.dataToMaybe $ P.fst fwdIn + , Df.dataToMaybe <$> P.fst fwdIn ) fromDfCircuit _ = fromSignals go where go (fwdIn, bwdIn) = - ( fmap coerce $ P.fst bwdIn + ( coerce <$> P.fst bwdIn , (fmap Df.maybeToData fwdIn, pure undefined) ) @@ -183,8 +205,6 @@ filterMetaS pS = Circuit $ \(fwdIn, bwdIn) -> unbundle (go <$> bundle (fwdIn, bw go (Nothing, bwdIn, _) = (bwdIn, Nothing) go (Just inPkt, bwdIn, predicate) | predicate (_meta inPkt) = (bwdIn, Just inPkt) - -- It's illegal to look at bwdIn when sending out a Nothing. - -- So if we drive a Nothing, force an acknowledgement. | otherwise = (PacketStreamS2M True, Nothing) -- | Filter a packet stream based on its metadata. diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index c323e82c..3a283e0b 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -1,5 +1,6 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE NoImplicitPrelude #-} +{-# OPTIONS_HADDOCK hide #-} {- | Provides an upconverter and downconverter for changing the data width of packet streams. diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 9a1acb3e..63c54cdd 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -3,6 +3,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoImplicitPrelude #-} +{-# OPTIONS_HADDOCK hide #-} {- | Utility circuits for stripping headers from the beginning of packets. @@ -42,24 +43,24 @@ type DepacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = ) data DepacketizerState (headerBytes :: Nat) (dataWidth :: Nat) - = DeParse - { _deAborted :: Bool - , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - , _deCounter :: Index (headerBytes `DivRU` dataWidth) + = Parse + { _aborted :: Bool + , _parseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) + , _fwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + , _counter :: Index (headerBytes `DivRU` dataWidth) } - | DeForward - { _deAborted :: Bool - , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - , _deCounter :: Index (headerBytes `DivRU` dataWidth) + | Forward + { _aborted :: Bool + , _parseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) + , _fwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + , _counter :: Index (headerBytes `DivRU` dataWidth) } - | DeLastForward - { _deAborted :: Bool - , _deParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - , _deFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - , _deCounter :: Index (headerBytes `DivRU` dataWidth) - , _deLastIdx :: Index dataWidth + | LastForward + { _aborted :: Bool + , _parseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) + , _fwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + , _counter :: Index (headerBytes `DivRU` dataWidth) + , _lastIdx :: Index dataWidth } deriving (Show, ShowX, Generic) @@ -67,6 +68,14 @@ deriving instance (DepacketizerCt headerBytes dataWidth) => NFDataX (DepacketizerState headerBytes dataWidth) +-- | Initial state of @depacketizerT@ +instance + (DepacketizerCt headerBytes dataWidth) => + Default (DepacketizerState headerBytes dataWidth) + where + def :: DepacketizerState headerBytes dataWidth + def = Parse False (repeat undefined) (repeat undefined) maxBound + depacketizerT :: forall (headerBytes :: Nat) @@ -86,10 +95,10 @@ depacketizerT :: ( DepacketizerState headerBytes dataWidth , (PacketStreamS2M, Maybe (PacketStreamM2S dataWidth metaOut)) ) -depacketizerT _ DeParse{..} (Just PacketStreamM2S{..}, _) = (nextSt, (PacketStreamS2M outReady, Nothing)) +depacketizerT _ Parse{..} (Just PacketStreamM2S{..}, _) = (nextSt, (PacketStreamS2M outReady, Nothing)) where - nextAborted = _deAborted || _abort - nextParseBuf = fst $ shiftInAtN _deParseBuf _data + nextAborted = _aborted || _abort + nextParseBuf = fst $ shiftInAtN _parseBuf _data fwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) fwdBuf = dropLe (SNat @(dataWidth - DeForwardBufSize headerBytes dataWidth)) _data @@ -98,41 +107,41 @@ depacketizerT _ DeParse{..} (Just PacketStreamM2S{..}, _) = (nextSt, (PacketStre SNatGT -> idx < (natToNum @(headerBytes `Mod` dataWidth)) _ -> True - nextCounter = pred _deCounter + nextCounter = pred _counter nextSt = - case (_deCounter == 0, _last) of + case (_counter == 0, _last) of (False, Nothing) -> - DeParse nextAborted nextParseBuf fwdBuf nextCounter + Parse nextAborted nextParseBuf fwdBuf nextCounter (done, Just idx) | not done || prematureEnd idx -> - DeParse False nextParseBuf fwdBuf maxBound + Parse False nextParseBuf fwdBuf maxBound (True, Just idx) -> - DeLastForward + LastForward nextAborted nextParseBuf fwdBuf nextCounter (idx - natToNum @(headerBytes `Mod` dataWidth)) (True, Nothing) -> - DeForward nextAborted nextParseBuf fwdBuf nextCounter + Forward nextAborted nextParseBuf fwdBuf nextCounter _ -> clashCompileError "depacketizerT Parse: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" outReady - | DeLastForward{} <- nextSt = False + | LastForward{} <- nextSt = False | otherwise = True -depacketizerT _ st@DeParse{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) -depacketizerT toMetaOut st@DeForward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = (nextStOut, (PacketStreamS2M outReady, Just outPkt)) +depacketizerT _ st@Parse{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) +depacketizerT toMetaOut st@Forward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = (nextStOut, (PacketStreamS2M outReady, Just outPkt)) where - nextAborted = _deAborted || _abort + nextAborted = _aborted || _abort dataOut :: Vec dataWidth (BitVector 8) nextFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - (dataOut, nextFwdBuf) = splitAt (SNat @dataWidth) $ _deFwdBuf ++ _data + (dataOut, nextFwdBuf) = splitAt (SNat @dataWidth) $ _fwdBuf ++ _data - deAdjustLast :: Index dataWidth -> Either (Index dataWidth) (Index dataWidth) - deAdjustLast idx = if outputNow then Left nowIdx else Right nextIdx + adjustLast :: Index dataWidth -> Either (Index dataWidth) (Index dataWidth) + adjustLast idx = if outputNow then Left nowIdx else Right nextIdx where outputNow = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of SNatGT -> idx < natToNum @(headerBytes `Mod` dataWidth) @@ -140,39 +149,39 @@ depacketizerT toMetaOut st@DeForward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = nowIdx = idx + natToNum @(DeForwardBufSize headerBytes dataWidth) nextIdx = idx - natToNum @(headerBytes `Mod` dataWidth) - newLast = fmap deAdjustLast _last + newLast = fmap adjustLast _last outPkt = pkt { _abort = nextAborted , _data = dataOut - , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _deParseBuf) _meta + , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _parseBuf) _meta , _last = either Just (const Nothing) =<< newLast } nextSt = case newLast of - Nothing -> DeForward nextAborted _deParseBuf nextFwdBuf _deCounter - Just (Left _) -> DeParse False _deParseBuf nextFwdBuf maxBound - Just (Right idx) -> DeLastForward nextAborted _deParseBuf nextFwdBuf _deCounter idx + Nothing -> Forward nextAborted _parseBuf nextFwdBuf _counter + Just (Left _) -> Parse False _parseBuf nextFwdBuf maxBound + Just (Right idx) -> LastForward nextAborted _parseBuf nextFwdBuf _counter idx nextStOut = if _ready bwdIn then nextSt else st outReady - | DeLastForward{} <- nextSt = False + | LastForward{} <- nextSt = False | otherwise = _ready bwdIn -depacketizerT _ st@DeForward{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) -depacketizerT toMetaOut st@DeLastForward{..} (fwdIn, bwdIn) = (nextStOut, (bwdIn, Just outPkt)) +depacketizerT _ st@Forward{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) +depacketizerT toMetaOut st@LastForward{..} (fwdIn, bwdIn) = (nextStOut, (bwdIn, Just outPkt)) where -- We can only get in this state if the previous clock cycle we received a fwdIn -- which was also the last fragment inPkt = fromJustX fwdIn outPkt = PacketStreamM2S - { _abort = _deAborted || _abort inPkt + { _abort = _aborted || _abort inPkt , _data = - _deFwdBuf ++ repeat @(dataWidth - DeForwardBufSize headerBytes dataWidth) defaultByte - , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _deParseBuf) (_meta inPkt) + _fwdBuf ++ repeat @(dataWidth - DeForwardBufSize headerBytes dataWidth) defaultByte + , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _parseBuf) (_meta inPkt) , _last = Just $ fromJustX (_last inPkt) - natToNum @(headerBytes `Mod` dataWidth) } - nextStOut = if _ready bwdIn then DeParse False _deParseBuf _deFwdBuf maxBound else st + nextStOut = if _ready bwdIn then Parse False _parseBuf _fwdBuf maxBound else st -- | Reads bytes at the start of each packet into metadata. depacketizerC :: @@ -203,9 +212,7 @@ depacketizerC toMetaOut = forceResetSanity |> fromSignals outCircuit case (modProof, divProof) of (SNatLE, SNatLE) -> case compareSNat (SNat @(DeForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of SNatLE -> - mealyB - (depacketizerT @headerBytes toMetaOut) - (DeParse False (repeat undefined) (repeat undefined) maxBound) + mealyB (depacketizerT @headerBytes toMetaOut) def _ -> clashCompileError "depacketizerC1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" @@ -213,45 +220,42 @@ depacketizerC toMetaOut = forceResetSanity |> fromSignals outCircuit clashCompileError "depacketizerC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" -type ParseBufSize (headerBytes :: Nat) (dataWidth :: Nat) = - dataWidth * headerBytes `DivRU` dataWidth - type DepacketizeToDfCt (headerBytes :: Nat) (dataWidth :: Nat) = ( 1 <= headerBytes `DivRU` dataWidth , headerBytes `Mod` dataWidth <= dataWidth - , headerBytes <= ParseBufSize headerBytes dataWidth + , headerBytes <= dataWidth * headerBytes `DivRU` dataWidth , KnownNat dataWidth , 1 <= dataWidth , KnownNat headerBytes ) -data DfDepacketizerState (a :: Type) (headerBytes :: Nat) (dataWidth :: Nat) - = Parse - { _dfDeAborted :: Bool +data DfDepacketizerState (headerBytes :: Nat) (dataWidth :: Nat) + = DfParse + { _dfAborted :: Bool -- ^ whether any of the fragments parsed from the current packet were aborted. - , _dfDeParseBuf :: Vec (ParseBufSize headerBytes dataWidth) (BitVector 8) + , _dfParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) -- ^ the accumulator for header bytes. - , _dfDeCounter :: Index (headerBytes `DivRU` dataWidth) + , _dfCounter :: Index (headerBytes `DivRU` dataWidth) -- ^ how many of the _parseBuf bytes are currently valid (accumulation count). We flush at counter == maxBound } - | ConsumePadding - { _dfDeAborted :: Bool + | DfConsumePadding + { _dfAborted :: Bool -- ^ whether any of the fragments parsed from the current packet were aborted. - , _dfDeParseBuf :: Vec (ParseBufSize headerBytes dataWidth) (BitVector 8) + , _dfParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) } deriving (Generic, Show, ShowX) deriving instance - (NFDataX a, DepacketizeToDfCt headerBytes dataWidth) => - NFDataX (DfDepacketizerState a headerBytes dataWidth) + (DepacketizeToDfCt headerBytes dataWidth) => + (NFDataX (DfDepacketizerState headerBytes dataWidth)) -dfDeInitialState :: - forall (a :: Type) (headerBytes :: Nat) (dataWidth :: Nat). - (KnownNat dataWidth) => - (KnownNat headerBytes) => - (1 <= dataWidth) => - DfDepacketizerState a headerBytes dataWidth -dfDeInitialState = Parse False (repeat undefined) maxBound +-- | Initial state of @depacketizeToDfT@ +instance + (DepacketizeToDfCt headerBytes dataWidth) => + Default (DfDepacketizerState headerBytes dataWidth) + where + def :: DfDepacketizerState headerBytes dataWidth + def = DfParse False (repeat undefined) maxBound depacketizeToDfT :: forall @@ -267,13 +271,13 @@ depacketizeToDfT :: (BitSize header ~ headerBytes * 8) => (DepacketizeToDfCt headerBytes dataWidth) => (header -> meta -> a) -> - DfDepacketizerState a headerBytes dataWidth -> + DfDepacketizerState headerBytes dataWidth -> (Maybe (PacketStreamM2S dataWidth meta), Ack) -> - (DfDepacketizerState a headerBytes dataWidth, (PacketStreamS2M, Df.Data a)) -depacketizeToDfT toOut st@Parse{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) + (DfDepacketizerState headerBytes dataWidth, (PacketStreamS2M, Df.Data a)) +depacketizeToDfT toOut st@DfParse{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) where - nextAborted = _dfDeAborted || _abort - nextParseBuf = fst (shiftInAtN _dfDeParseBuf _data) + nextAborted = _dfAborted || _abort + nextParseBuf = fst (shiftInAtN _dfParseBuf _data) outDf = toOut (bitCoerce (takeLe (SNat @headerBytes) nextParseBuf)) _meta prematureEnd idx = @@ -282,31 +286,31 @@ depacketizeToDfT toOut st@Parse{..} (Just (PacketStreamM2S{..}), Ack readyIn) = _ -> idx < (natToNum @(dataWidth - 1)) (nextSt, fwdOut) = - case (_dfDeCounter == 0, _last) of + case (_dfCounter == 0, _last) of (False, Nothing) -> - (Parse nextAborted nextParseBuf (pred _dfDeCounter), Df.NoData) + (DfParse nextAborted nextParseBuf (pred _dfCounter), Df.NoData) (c, Just idx) | not c || prematureEnd idx -> - (dfDeInitialState, Df.NoData) + (def, Df.NoData) (True, Just _) -> - (dfDeInitialState, if nextAborted then Df.NoData else Df.Data outDf) + (def, if nextAborted then Df.NoData else Df.Data outDf) (True, Nothing) -> - (ConsumePadding nextAborted nextParseBuf, Df.NoData) + (DfConsumePadding nextAborted nextParseBuf, Df.NoData) _ -> clashCompileError "depacketizeToDfT Parse: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" readyOut = isNothing (Df.dataToMaybe fwdOut) || readyIn nextStOut = if readyOut then nextSt else st -depacketizeToDfT toOut st@ConsumePadding{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) +depacketizeToDfT toOut st@DfConsumePadding{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) where - nextAborted = _dfDeAborted || _abort - outDf = toOut (bitCoerce (takeLe (SNat @headerBytes) _dfDeParseBuf)) _meta + nextAborted = _dfAborted || _abort + outDf = toOut (bitCoerce (takeLe (SNat @headerBytes) _dfParseBuf)) _meta (nextSt, fwdOut) = if isJust _last - then (dfDeInitialState, if nextAborted then Df.NoData else Df.Data outDf) - else (st{_dfDeAborted = nextAborted}, Df.NoData) + then (def, if nextAborted then Df.NoData else Df.Data outDf) + else (st{_dfAborted = nextAborted}, Df.NoData) readyOut = isNothing (Df.dataToMaybe fwdOut) || readyIn nextStOut = if readyOut then nextSt else st @@ -346,7 +350,7 @@ depacketizeToDfC toOut = forceResetSanity |> fromSignals outCircuit outCircuit = case (divProof, modProof) of (SNatLE, SNatLE) -> case compareSNat d1 (SNat @(headerBytes `DivRU` dataWidth)) of - SNatLE -> mealyB (depacketizeToDfT toOut) dfDeInitialState + SNatLE -> mealyB (depacketizeToDfT toOut) def _ -> clashCompileError "depacketizeToDfC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" diff --git a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs index e097733c..27694f01 100644 --- a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs @@ -1,5 +1,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoImplicitPrelude #-} +{-# OPTIONS_HADDOCK hide #-} {- | Optimized Store and forward FIFO circuit for packet streams. diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index c5730058..20195737 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -3,6 +3,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoImplicitPrelude #-} +{-# OPTIONS_HADDOCK hide #-} {- | Utility circuits for appending headers to the beginning of packets. @@ -22,6 +23,9 @@ import Data.Maybe import Data.Maybe.Extra import Data.Type.Equality ((:~:) (Refl)) +defaultByte :: BitVector 8 +defaultByte = 0x00 + type HeaderBufSize (headerBytes :: Nat) (dataWidth :: Nat) = headerBytes + dataWidth @@ -30,6 +34,12 @@ type HeaderBufSize (headerBytes :: Nat) (dataWidth :: Nat) = type ForwardBufSize (headerBytes :: Nat) (dataWidth :: Nat) = headerBytes `Mod` dataWidth +type PacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = + ( KnownNat dataWidth + , 1 <= dataWidth + , KnownNat headerBytes + ) + data PacketizerState (metaOut :: Type) (headerBytes :: Nat) (dataWidth :: Nat) = Insert { _counter :: Index (headerBytes `DivRU` dataWidth) @@ -48,24 +58,13 @@ deriving instance (NFDataX metaOut, PacketizerCt headerBytes dataWidth) => NFDataX (PacketizerState metaOut headerBytes dataWidth) -type PacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = - ( KnownNat dataWidth - , 1 <= dataWidth - , KnownNat headerBytes - ) - -defaultByte :: BitVector 8 -defaultByte = 0x00 - --- The initial state of our packetizer. For readability purposes, because we use this exact expression a lot. -initialState :: - forall - (metaOut :: Type) - (headerBytes :: Nat) - (dataWidth :: Nat). +-- | Initial state of @packetizerT@ +instance (PacketizerCt headerBytes dataWidth) => - PacketizerState metaOut headerBytes dataWidth -initialState = Insert 0 (repeat defaultByte) False + Default (PacketizerState metaOut headerBytes dataWidth) + where + def :: PacketizerState metaOut headerBytes dataWidth + def = Insert 0 (repeat defaultByte) False adjustLast :: forall @@ -131,7 +130,7 @@ packetizerT toMetaOut toHeader st@Insert{..} (Just pkt@PacketStreamM2S{..}, bwdI nextSt = case (_counter == maxBound, newLast) of (False, _) -> Insert (succ _counter) newHdrBuf nextAborted (True, Nothing) -> Forward forwardBytes nextAborted - (True, Just (Left _)) -> initialState + (True, Just (Left _)) -> def (True, Just (Right idx)) -> LastForward (PacketStreamM2S (take (SNat @dataWidth) newHdrBuf) (Just idx) metaOut nextAborted) @@ -162,13 +161,13 @@ packetizerT toMetaOut _ st@Forward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = ( nextSt = case newLast of Nothing -> Forward nextFwdBuf nextAborted - Just (Left _) -> initialState + Just (Left _) -> def Just (Right idx) -> LastForward (PacketStreamM2S dataLast (Just idx) metaOut nextAborted) nextStOut = if _ready bwdIn then nextSt else st packetizerT _ _ st@LastForward{..} (_, bwdIn) = (nextStOut, (PacketStreamS2M False, Just _lastFragment)) where - nextStOut = if _ready bwdIn then initialState else st + nextStOut = if _ready bwdIn then def else st packetizerT _ _ s (Nothing, bwdIn) = (s, (bwdIn, Nothing)) {- | Puts a portion of the metadata in front of the packet stream, and shifts the stream accordingly. @@ -198,7 +197,7 @@ packetizerC :: packetizerC toMetaOut toHeader = fromSignals outCircuit where outCircuit = case compareSNat (SNat @(ForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of - SNatLE -> mealyB (packetizerT @headerBytes toMetaOut toHeader) initialState + SNatLE -> mealyB (packetizerT @headerBytes toMetaOut toHeader) def _ -> clashCompileError "packetizerC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" diff --git a/clash-protocols/src/Protocols/PacketStream/Routing.hs b/clash-protocols/src/Protocols/PacketStream/Routing.hs index a39d04b8..4bf4b146 100644 --- a/clash-protocols/src/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/src/Protocols/PacketStream/Routing.hs @@ -1,4 +1,5 @@ {-# LANGUAGE NoImplicitPrelude #-} +{-# OPTIONS_HADDOCK hide #-} {- | Provides a packet arbiter and dispatcher, for merging and splitting packet streams. From 61db3b6da70703cb308ed0bb44fe0141e3494f69 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 22 Jul 2024 11:18:21 +0200 Subject: [PATCH 07/63] optimize packetizeFromDfT --- .../src/Protocols/PacketStream/Packetizers.hs | 74 ++++++++----------- 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index 20195737..d17bf5fe 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -1,5 +1,3 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoImplicitPrelude #-} @@ -21,7 +19,6 @@ import Protocols.PacketStream.Base import Data.Maybe import Data.Maybe.Extra -import Data.Type.Equality ((:~:) (Refl)) defaultByte :: BitVector 8 defaultByte = 0x00 @@ -202,20 +199,19 @@ packetizerC toMetaOut toHeader = fromSignals outCircuit clashCompileError "packetizerC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" -{- | If dataWidth >= headerBytes, we don't need a buffer because we can immediately send - the fragment. Else, we need a buffer that stores the headerBytes minus the size - of the fragment we send out immediately. --} -type DfHeaderBufSize headerBytes dataWidth = dataWidth `Max` headerBytes - dataWidth - data DfPacketizerState (metaOut :: Type) (headerBytes :: Nat) (dataWidth :: Nat) = DfIdle | DfInsert { _dfCounter :: Index (headerBytes `DivRU` dataWidth - 1) - , _dfHdrBuf :: Vec (DfHeaderBufSize headerBytes dataWidth) (BitVector 8) + , _dfHdrBuf :: Vec (headerBytes - dataWidth) (BitVector 8) } - deriving (Generic, NFDataX, Show, ShowX) + deriving (Generic, Show, ShowX) +deriving instance + (dataWidth <= headerBytes, KnownNat headerBytes, KnownNat dataWidth) => + NFDataX (DfPacketizerState metaOut headerBytes dataWidth) + +-- | packetizeFromDf state transition function in case dataWidth < headerBytes. packetizeFromDfT :: forall (dataWidth :: Nat) @@ -230,8 +226,7 @@ packetizeFromDfT :: (KnownNat dataWidth) => (1 <= dataWidth) => (1 <= headerBytes `DivRU` dataWidth) => - (dataWidth `Min` headerBytes <= dataWidth) => - (dataWidth `Max` headerBytes - dataWidth + dataWidth `Min` headerBytes ~ headerBytes) => + ((dataWidth + 1) <= headerBytes) => -- | function that transforms the Df input to the output metadata. (a -> metaOut) -> -- | function that transforms the Df input to the header that will be packetized. @@ -241,26 +236,15 @@ packetizeFromDfT :: ( DfPacketizerState metaOut headerBytes dataWidth , (Ack, Maybe (PacketStreamM2S dataWidth metaOut)) ) -packetizeFromDfT toMetaOut toHeader DfIdle (Df.Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) +packetizeFromDfT toMetaOut toHeader DfIdle (Df.Data dataIn, bwdIn) = (nextStOut, (Ack False, Just outPkt)) where - rotatedHdr = - rotateRightS (bitCoerce (toHeader dataIn)) (SNat @(DfHeaderBufSize headerBytes dataWidth)) - (hdrBuf, dataOut) = splitAt (SNat @(DfHeaderBufSize headerBytes dataWidth)) rotatedHdr - dataOutPadded = dataOut ++ repeat @(dataWidth - dataWidth `Min` headerBytes) defaultByte - outPkt = PacketStreamM2S dataOutPadded newLast (toMetaOut dataIn) False - - (nextSt, bwdOut, newLast) = case compareSNat (SNat @headerBytes) (SNat @dataWidth) of - SNatLE -> (DfIdle, Ack (_ready bwdIn), Just l) - where - l = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of - SNatGT -> natToNum @(headerBytes `Mod` dataWidth - 1) - _ -> natToNum @(dataWidth - 1) - SNatGT -> (DfInsert 0 hdrBuf, Ack False, Nothing) - - nextStOut = if _ready bwdIn then nextSt else DfIdle + (dataOut, hdrBuf) = splitAt (SNat @dataWidth) (bitCoerce (toHeader dataIn)) + outPkt = PacketStreamM2S dataOut Nothing (toMetaOut dataIn) False + nextStOut = if _ready bwdIn then DfInsert 0 hdrBuf else DfIdle -- fwdIn is always Data in this state, because we assert backpressure in Idle before we go here -- Thus, we don't need to store the metadata in the state. +-- explicitly eliminates the state machine because synthesis tool is not smart enough to do it in this case packetizeFromDfT toMetaOut _ st@DfInsert{..} (Df.Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) where (dataOut, newHdrBuf) = splitAt (SNat @dataWidth) (_dfHdrBuf ++ repeat @dataWidth defaultByte) @@ -299,17 +283,21 @@ packetizeFromDfC :: -- | Function that transforms the Df input to the header that will be packetized. (a -> header) -> Circuit (Df dom a) (PacketStream dom dataWidth metaOut) -packetizeFromDfC toMetaOut toHeader = fromSignals ckt - where - maxMinProof = - sameNat - (SNat @headerBytes) - (SNat @(dataWidth `Max` headerBytes - dataWidth + dataWidth `Min` headerBytes)) - minProof = compareSNat (SNat @(dataWidth `Min` headerBytes)) (SNat @dataWidth) - divRuProof = compareSNat d1 (SNat @(headerBytes `DivRU` dataWidth)) - - ckt = case (maxMinProof, minProof, divRuProof) of - (Just Refl, SNatLE, SNatLE) -> mealyB (packetizeFromDfT toMetaOut toHeader) DfIdle - _ -> - clashCompileError - "packetizeFromDfC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" +packetizeFromDfC toMetaOut toHeader = case compareSNat d1 (SNat @(headerBytes `DivRU` dataWidth)) of + SNatLE -> case compareSNat (SNat @headerBytes) (SNat @dataWidth) of + -- We don't need a state machine in this case, as we are able to packetize + -- the entire payload in one clock cycle. + SNatLE -> Circuit (unbundle . fmap go . bundle) + where + go (Df.NoData, _) = (Ack False, Nothing) + go (Df.Data dataIn, bwdIn) = (Ack (_ready bwdIn), Just outPkt) + where + outPkt = PacketStreamM2S dataOut (Just l) (toMetaOut dataIn) False + dataOut = bitCoerce (toHeader dataIn) ++ repeat @(dataWidth - headerBytes) defaultByte + l = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of + SNatGT -> natToNum @(headerBytes `Mod` dataWidth - 1) + _ -> natToNum @(dataWidth - 1) + SNatGT -> fromSignals (mealyB (packetizeFromDfT toMetaOut toHeader) DfIdle) + SNatGT -> + clashCompileError + "packetizeFromDfC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" From 6c5b6ddf76325db7f77f16b50e6cffa65b75e285 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 22 Jul 2024 11:20:22 +0200 Subject: [PATCH 08/63] Remove outdated comment --- clash-protocols/src/Protocols/PacketStream/Packetizers.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index d17bf5fe..0d8ff568 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -244,7 +244,6 @@ packetizeFromDfT toMetaOut toHeader DfIdle (Df.Data dataIn, bwdIn) = (nextStOut, -- fwdIn is always Data in this state, because we assert backpressure in Idle before we go here -- Thus, we don't need to store the metadata in the state. --- explicitly eliminates the state machine because synthesis tool is not smart enough to do it in this case packetizeFromDfT toMetaOut _ st@DfInsert{..} (Df.Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) where (dataOut, newHdrBuf) = splitAt (SNat @dataWidth) (_dfHdrBuf ++ repeat @dataWidth defaultByte) From 4971dc7e23ce8b89898fd42675e0c7b733fd2be6 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 22 Jul 2024 15:52:33 +0200 Subject: [PATCH 09/63] Clean up tests and add sensible generators --- .../src/Protocols/PacketStream/Base.hs | 3 +- .../Tests/Protocols/PacketStream/AsyncFifo.hs | 20 +-- .../Tests/Protocols/PacketStream/Base.hs | 160 +++++++++++------- .../Protocols/PacketStream/Converters.hs | 66 ++------ .../Protocols/PacketStream/Depacketizers.hs | 31 +--- .../Protocols/PacketStream/PacketFifo.hs | 55 ++---- .../Protocols/PacketStream/Packetizers.hs | 46 ++--- .../Tests/Protocols/PacketStream/Routing.hs | 33 ++-- 8 files changed, 157 insertions(+), 257 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 769755a2..261e0e33 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -80,6 +80,7 @@ Invariants: 3. A manager must keep the metadata (`_meta`) of an entire packet it sends constant. 4. A subordinate which receives a transfer with `_abort` asserted must either forward this `_abort` or drop the packet. 5. A packet may not be interrupted by another packet. +6. All bytes in `_data` which are not enabled must be 0x00. -} data PacketStream (dom :: Domain) (dataWidth :: Nat) (metaType :: Type) @@ -223,7 +224,7 @@ mapMetaS :: Circuit (PacketStream dom dataWidth a) (PacketStream dom dataWidth b) mapMetaS fS = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, go <$> bundle (fwdIn, fS)) where - go (inp, f) = (\inPkt -> inPkt{_meta = f (_meta inPkt)}) <$> inp + go (inp, f) = (\inPkt -> inPkt {_meta = f (_meta inPkt)} ) <$> inp -- | Map a function on the metadata of a packet stream. mapMeta :: diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs index 6be94c64..3135f094 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs @@ -13,7 +13,6 @@ import Clash.Prelude as C -- hedgehog import Hedgehog -import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range -- tasty @@ -28,8 +27,9 @@ import Protocols.Hedgehog import Protocols.PacketStream.AsyncFifo import Protocols.PacketStream.Base -genVec :: (KnownNat n, 1 C.<= n) => Gen a -> Gen (Vec n a) -genVec gen = sequence (C.repeat gen) +-- tests +import Tests.Protocols.PacketStream.Base + createDomain vSystem @@ -80,12 +80,11 @@ generateAsyncFifoIdProp :: Enable rDom -> Property generateAsyncFifoIdProp wClk wRst wEn rClk rRst rEn = - propWithModel + idWithModel defExpectOptions - (Gen.list (Range.linear 0 100) genPackets) + (genValidPackets (Range.linear 1 10) (Range.linear 1 30) Abort) id ckt - (===) where ckt :: (KnownDomain wDom, KnownDomain rDom) => @@ -93,13 +92,6 @@ generateAsyncFifoIdProp wClk wRst wEn rClk rRst rEn = (PacketStream wDom 1 Int) (PacketStream rDom 1 Int) ckt = asyncFifoC (C.SNat @8) wClk wRst wEn rClk rRst rEn - -- This is used to generate - genPackets = - PacketStreamM2S - <$> genVec Gen.enumBounded - <*> Gen.maybe Gen.enumBounded - <*> Gen.enumBounded - <*> Gen.enumBounded {- | The async FIFO circuit should forward all of its input data without loss and without producing extra data. This property tests whether this is true, when the clock of the writer and reader is equally fast (50 MHz). @@ -121,7 +113,7 @@ prop_asyncfifo_writer_speed_faster_than_reader_id = generateAsyncFifoIdProp clk1 tests :: TestTree tests = - localOption (mkTimeout 12_000_000 {- 12 seconds -}) $ + localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption (HedgehogTestLimit (Just 1_000)) $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs index c53c7a9a..1beb9166 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs @@ -1,5 +1,4 @@ {-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE RecordWildCards #-} module Tests.Protocols.PacketStream.Base ( @@ -13,10 +12,10 @@ module Tests.Protocols.PacketStream.Base ( dropAbortedPackets, downConvert, upConvert, - cleanPackets, - makeValid, + AbortMode(..), genValidPacket, genValidPackets, + genVec, ) where -- base @@ -71,9 +70,9 @@ chunkToPacket l = PacketStreamM2S { _last = if M.isJust $ _last $ L.last l then M.Just (fromIntegral $ L.length l - 1) else Nothing - , _abort = or $ fmap _abort l + , _abort = any _abort l , _meta = _meta $ L.head l - , _data = L.foldr (C.+>>) (C.repeat 0) $ fmap (C.head . _data) l + , _data = foldr ((C.+>>) . C.head . _data) (C.repeat 0) l } -- | Split a PacketStream n into a list of PacketStream 1 @@ -102,7 +101,7 @@ fullPackets fragments = let lastFragment = (last fragments){_last = Just 0} in init fragments ++ [lastFragment] --- | Drops packets if one of the words in the packet has the abort flag set +-- | Drops packets if one of the transfers in the packet has the abort flag set dropAbortedPackets :: [PacketStreamM2S n meta] -> [PacketStreamM2S n meta] dropAbortedPackets packets = concat $ filter (not . any _abort) (chunkByPacket packets) @@ -124,65 +123,104 @@ upConvert :: [PacketStreamM2S n meta] upConvert packets = chunkToPacket <$> chopBy (C.natToNum @n) packets --- | Set all invalid bytes to null-bytes -cleanPackets :: - forall n meta. - (1 C.<= n) => - (C.KnownNat n) => - [PacketStreamM2S n meta] -> - [PacketStreamM2S n meta] -cleanPackets = map cleanPacket - where - cleanPacket pkt@PacketStreamM2S{..} = case _last of - Nothing -> pkt - Just i -> pkt{_data = M.fromJust $ Vec.fromList datas} - where - datas = - take (1 + fromIntegral i) (C.toList _data) - ++ replicate ((C.natToNum @n) - 1 - fromIntegral i) 0 - --- | Make an existing list of packets valid, meaning all words in a packet share the same meta value, and the list always contain full packets -makeValid :: - forall (dataWidth :: C.Nat) (metaType :: C.Type). - (C.KnownNat dataWidth) => - (1 C.<= dataWidth) => - [PacketStreamM2S dataWidth metaType] -> - [PacketStreamM2S dataWidth metaType] -makeValid packets = fullPackets $ concatMap sameMeta $ chunkByPacket packets - where - sameMeta :: [PacketStreamM2S dataWidth metaType] -> [PacketStreamM2S dataWidth metaType] - sameMeta [] = [] - sameMeta list@(x : _) = fullPackets $ fmap (\pkt -> pkt{_meta = _meta x}) list +-- | If set to @NoAbort@, packets will never contain a transfer with _abort set. +-- Otherwise, transfers of roughly 50% of the packets will randomly have _abort set. +data AbortMode = Abort | NoAbort -{- | Generates a single valid packet using the given generator, - the meta value of the first word will be that of all words, and only the last value in the packet will have Last = Just .. --} -genValidPacket :: +-- | Generate valid packets, i.e. packets of which all transfers carry the same +-- @_meta@ and with all unenabled bytes in @_data@ set to 0x00. +genValidPackets :: forall (dataWidth :: C.Nat) (metaType :: C.Type). - (C.KnownNat dataWidth) => - (1 C.<= dataWidth) => + 1 C.<= dataWidth => + C.KnownNat dataWidth => + C.BitPack metaType => + -- | The amount of packets to generate. Range Int -> - Gen (PacketStreamM2S dataWidth metaType) -> + -- | The amount of transfers to generate. + Range Int -> + -- | If set to @NoAbort@, no generated packets will have @_abort@ set in + -- any of their transfers. Else, roughly 50% of packets will contain + -- fragments with their @_abort@ randomly set. + AbortMode -> Gen [PacketStreamM2S dataWidth metaType] -genValidPacket range gen = do - genWords <- Gen.list range gen - return $ makeValidPacket genWords - where - makeValidPacket :: - [PacketStreamM2S dataWidth metaType] -> [PacketStreamM2S dataWidth metaType] - makeValidPacket [] = [] - makeValidPacket list@(x : _) = fullPackets $ fmap (\pkt -> pkt{_meta = _meta x, _last = Nothing}) list - --- | Generates a list filled with valid packets, packets which have the same meta value for all words -genValidPackets :: +genValidPackets r1 r2 Abort = concat <$> Gen.list r1 x + where + x = do + abortPacket <- Gen.bool + genValidPacket r2 (if abortPacket then Abort else NoAbort) +genValidPackets r1 r2 NoAbort = concat <$> Gen.list r1 (genValidPacket r2 NoAbort) + +-- | Generate a valid packet, i.e. a packet of which all transfers carry the same +-- @_meta@ and with all unenabled bytes in @_data@ set to 0x00. +genValidPacket :: forall (dataWidth :: C.Nat) (metaType :: C.Type). - (C.KnownNat dataWidth) => - (1 C.<= dataWidth) => - -- | Range specifying the amount of packets to generate + 1 C.<= dataWidth => + C.KnownNat dataWidth => + C.BitPack metaType => + -- | The amount of transfers to generate. Range Int -> - -- | Range specifying the size of packets to generate - Range Int -> - -- | Generator for a single packet - Gen (PacketStreamM2S dataWidth metaType) -> + -- | If set to @NoAbort@, no transfers in the packet will have @_abort@ set. + -- Else, they will randomly have @_abort@ set. + AbortMode -> Gen [PacketStreamM2S dataWidth metaType] -genValidPackets range packetRange gen = concat <$> Gen.list range (genValidPacket packetRange gen) +genValidPacket r abortMode = do + meta <- C.unpack <$> Gen.enumBounded + transfers <- Gen.list r (genTransfer @dataWidth meta abortMode) + lastTransfer <- genLastTransfer @dataWidth meta abortMode + pure (transfers ++ [lastTransfer]) + +-- | Generate a single transfer which is not yet the end of a packet. +genTransfer :: + forall (dataWidth :: C.Nat) (metaType :: C.Type). + 1 C.<= dataWidth => + C.KnownNat dataWidth => + -- | We need to use the same metadata + -- for every transfer in a packet to satisfy the protocol + -- invariant that metadata is constant for an entire packet. + metaType -> + -- | If set to @NoAbort@, hardcode @_abort@ to @False@. Else, + -- randomly generate it. + AbortMode -> + Gen (PacketStreamM2S dataWidth metaType) +genTransfer meta abortMode = PacketStreamM2S + <$> genVec Gen.enumBounded + <*> Gen.constant Nothing + <*> Gen.constant meta + <*> case abortMode of + Abort -> Gen.enumBounded + NoAbort -> Gen.constant False + +-- | Generate the last transfer of a packet, i.e. a transfer with @_last@ set as @Just@. +-- All bytes which are not enabled are set to 0x00. +genLastTransfer :: + forall (dataWidth :: C.Nat) (metaType :: C.Type). + 1 C.<= dataWidth => + C.KnownNat dataWidth => + -- | We need to use the same metadata + -- for every transfer in a packet to satisfy the protocol + -- invariant that metadata is constant for an entire packet. + metaType -> + -- | If set to @NoAbort@, hardcode @_abort@ to @False@. Else, + -- randomly generate it. + AbortMode -> + Gen (PacketStreamM2S dataWidth metaType) +genLastTransfer meta abortMode = setNull <$> (PacketStreamM2S + <$> genVec Gen.enumBounded + <*> (Just <$> Gen.enumBounded) + <*> Gen.constant meta + <*> case abortMode of + Abort -> Gen.enumBounded + NoAbort -> Gen.constant False) + where + setNull transfer = let i = M.fromJust (_last transfer) in + transfer { + _data = M.fromJust (Vec.fromList $ + take (1 + fromIntegral i) (C.toList (_data transfer)) + ++ replicate ((C.natToNum @dataWidth) - 1 - fromIntegral i) 0x00) + } + +-- | Randomly generate a vector of length @n@. +genVec :: + forall (n :: C.Nat) (a :: C.Type). + (C.KnownNat n, 1 C.<= n) => Gen a -> Gen (C.Vec n a) +genVec gen = sequence (C.repeat gen) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs index 152aba76..cd6e871f 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs @@ -14,7 +14,6 @@ import qualified Clash.Prelude as C -- hedgehog import Hedgehog -import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range -- tasty @@ -24,7 +23,6 @@ import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) -- clash-protocols -import Protocols import Protocols.Hedgehog import Protocols.PacketStream.Base import Protocols.PacketStream.Converters @@ -32,8 +30,6 @@ import Protocols.PacketStream.Converters -- tests import Tests.Protocols.PacketStream.Base -genVec :: (C.KnownNat n, 1 <= n) => Gen a -> Gen (C.Vec n a) -genVec gen = sequence (C.repeat gen) ucModel :: forall n. (C.KnownNat n) => [PacketStreamM2S 1 ()] -> [PacketStreamM2S n ()] ucModel fragments = out @@ -45,65 +41,27 @@ ucModel fragments = out -- | Test the upconverter stream instance upconverterTest :: forall n. (1 <= n) => C.SNat n -> Property upconverterTest C.SNat = - propWithModelSingleDomain + idWithModelSingleDomain @C.System defExpectOptions - (fmap fullPackets (Gen.list (Range.linear 0 100) genPackets)) -- Input packets - (C.exposeClockResetEnable ucModel) -- Desired behaviour of UpConverter - (C.exposeClockResetEnable @C.System (ckt @n)) -- Implementation of UpConverter - (===) -- Property to test - where - ckt :: - forall (dataWidth :: C.Nat) (dom :: C.Domain). - (C.HiddenClockResetEnable dom) => - (1 <= dataWidth) => - (C.KnownNat dataWidth) => - Circuit (PacketStream dom 1 ()) (PacketStream dom dataWidth ()) - ckt = upConverterC - - -- This generates the packets - genPackets = - PacketStreamM2S - <$> genVec Gen.enumBounded - <*> Gen.maybe Gen.enumBounded - <*> Gen.enumBounded - <*> Gen.enumBounded + (genValidPackets (Range.linear 1 30) (Range.linear 1 30) Abort) + (C.exposeClockResetEnable ucModel) + (C.exposeClockResetEnable @C.System (upConverterC @n)) prop_upconverter_d1, prop_upconverter_d2, prop_upconverter_d4 :: Property -prop_upconverter_d1 = upconverterTest (C.SNat @1) -prop_upconverter_d2 = upconverterTest (C.SNat @2) -prop_upconverter_d4 = upconverterTest (C.SNat @4) - -dcModel :: - forall n. (1 <= n) => (C.KnownNat n) => [PacketStreamM2S n ()] -> [PacketStreamM2S 1 ()] -dcModel = downConvert +prop_upconverter_d1 = upconverterTest C.d1 +prop_upconverter_d2 = upconverterTest C.d2 +prop_upconverter_d4 = upconverterTest C.d4 -- | Test the downconverter stream instance downconverterTest :: forall n. (1 <= n) => C.SNat n -> Property downconverterTest C.SNat = - propWithModelSingleDomain + idWithModelSingleDomain @C.System defExpectOptions - (Gen.list (Range.linear 0 100) genPackets) -- Input packets - (C.exposeClockResetEnable dcModel) -- Desired behaviour of DownConverter - (C.exposeClockResetEnable @C.System (ckt @n)) -- Implementation of DownConverter - (===) -- Property to test - where - ckt :: - forall (dataWidth :: C.Nat) (dom :: C.Domain). - (C.HiddenClockResetEnable dom) => - (1 <= dataWidth) => - (C.KnownNat dataWidth) => - Circuit (PacketStream dom dataWidth ()) (PacketStream dom 1 ()) - ckt = downConverterC - - -- This generates the packets - genPackets = - PacketStreamM2S - <$> genVec Gen.enumBounded - <*> Gen.maybe Gen.enumBounded - <*> Gen.enumBounded - <*> Gen.enumBounded + (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) + (C.exposeClockResetEnable downConvert) + (C.exposeClockResetEnable @C.System (downConverterC @n)) prop_downconverter_d1, prop_downconverter_d2, prop_downconverter_d4 :: Property prop_downconverter_d1 = downconverterTest (C.SNat @1) @@ -112,7 +70,7 @@ prop_downconverter_d4 = downconverterTest (C.SNat @4) tests :: TestTree tests = - localOption (mkTimeout 12_000_000 {- 12 seconds -}) $ + localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption (HedgehogTestLimit (Just 1_000)) $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index 4f1f3ce6..8ecaffea 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -13,12 +13,10 @@ import Prelude -- clash import Clash.Prelude -import qualified Clash.Prelude as C import Clash.Sized.Vector (unsafeFromList) -- hedgehog import Hedgehog -import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range -- tasty @@ -29,7 +27,6 @@ import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) -- clash-protocols - import Protocols.Hedgehog import Protocols.PacketStream.Base @@ -38,8 +35,6 @@ import Protocols import Protocols.PacketStream (depacketizeToDfC, depacketizerC) import Tests.Protocols.PacketStream.Base -genVec :: (KnownNat n, 1 <= n) => Gen a -> Gen (Vec n a) -genVec gen = sequence (C.repeat gen) -- | Model of the generic `depacketizerC`. depacketizerModel :: @@ -127,15 +122,11 @@ depacketizerPropertyGenerator SNat SNat = idWithModelSingleDomain @System defExpectOptions - (fmap (cleanPackets . fullPackets) (Gen.list (Range.linear 1 100) genPackets)) + (genValidPackets (Range.linear 1 10) (Range.linear 1 50) Abort) (exposeClockResetEnable model) (exposeClockResetEnable ckt) where - model :: - [PacketStreamM2S dataWidth ()] -> - [PacketStreamM2S dataWidth (Vec headerBytes (BitVector 8))] model = depacketizerModel const - ckt :: (HiddenClockResetEnable System) => Circuit @@ -143,13 +134,6 @@ depacketizerPropertyGenerator SNat SNat = (PacketStream System dataWidth (Vec headerBytes (BitVector 8))) ckt = depacketizerC const - genPackets = - PacketStreamM2S - <$> genVec Gen.enumBounded - <*> Gen.maybe Gen.enumBounded - <*> Gen.enumBounded - <*> Gen.enumBounded - -- | headerBytes % dataWidth ~ 0 prop_const_depacketizer_d1_d14 :: Property prop_const_depacketizer_d1_d14 = depacketizerPropertyGenerator d1 d14 @@ -182,25 +166,16 @@ depacketizeToDfPropertyGenerator SNat SNat = idWithModelSingleDomain @System defExpectOptions - (fmap (cleanPackets . fullPackets) (Gen.list (Range.linear 1 100) genPackets)) + (genValidPackets (Range.linear 1 10) (Range.linear 1 50) NoAbort) (exposeClockResetEnable model) (exposeClockResetEnable ckt) where - model :: [PacketStreamM2S dataWidth ()] -> [Vec headerBytes (BitVector 8)] model = depacketizeToDfModel const - ckt :: (HiddenClockResetEnable System) => Circuit (PacketStream System dataWidth ()) (Df System (Vec headerBytes (BitVector 8))) ckt = depacketizeToDfC const - genPackets = - PacketStreamM2S - <$> genVec Gen.enumBounded - <*> Gen.maybe Gen.enumBounded - <*> Gen.enumBounded - <*> Gen.enumBounded - -- | headerBytes % dataWidth ~ 0 prop_const_depacketize_to_df_d1_d14 :: Property prop_const_depacketize_to_df_d1_d14 = depacketizeToDfPropertyGenerator d1 d14 @@ -219,7 +194,7 @@ prop_const_depacketize_to_df_d5_d4 = depacketizeToDfPropertyGenerator d5 d4 tests :: TestTree tests = - localOption (mkTimeout 12_000_000 {- 12 seconds -}) $ + localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption (HedgehogTestLimit (Just 1_000)) $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index 60868023..d1b23a67 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -14,7 +14,6 @@ import qualified Clash.Prelude as C -- hedgehog import Hedgehog as H -import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range -- tasty @@ -32,28 +31,6 @@ import Protocols.PacketStream.PacketFifo (overflowDropPacketFifoC, packetFifoC) -- tests import Tests.Protocols.PacketStream.Base as U -genVec :: (C.KnownNat n, 1 C.<= n) => Gen a -> Gen (C.Vec n a) -genVec gen = sequence (C.repeat gen) - --- | generate a "clean" packet: a packet without an abort -genCleanWord :: Gen (PacketStreamM2S 4 Int16) -genCleanWord = - PacketStreamM2S - <$> genVec Gen.enumBounded - <*> pure Nothing - <*> Gen.enumBounded - <*> pure False - -genWord :: Gen (PacketStreamM2S 4 Int16) -genWord = - PacketStreamM2S - <$> genVec Gen.enumBounded - <*> Gen.maybe Gen.enumBounded - <*> Gen.enumBounded - <*> Gen.enumBounded - -genPackets :: Range Int -> Gen [PacketStreamM2S 4 Int16] -genPackets range = makeValid <$> Gen.list range genWord isSubsequenceOf :: (Eq a) => [a] -> [a] -> Bool isSubsequenceOf [] _ = True @@ -68,22 +45,22 @@ prop_packetFifo_id = idWithModelSingleDomain @C.System defExpectOptions - (genPackets (Range.linear 0 100)) + (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) - where - ckt :: - (HiddenClockResetEnable System) => - Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = packetFifoC d12 d12 + where + ckt :: + (HiddenClockResetEnable System) => + Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) + ckt = packetFifoC d12 d12 --- test for id with a small buffer to ensure backpressure is tested +-- | test for id with a small buffer to ensure backpressure is tested prop_packetFifo_small_buffer_id :: Property prop_packetFifo_small_buffer_id = idWithModelSingleDomain @C.System defExpectOptions - (genValidPackets (Range.linear 0 10) (Range.linear 0 31) genCleanWord) + (genValidPackets (Range.linear 1 10) (Range.linear 1 30) NoAbort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) where @@ -103,7 +80,7 @@ prop_packetFifo_no_gaps = property $ do systemClockGen resetGen enableGen - gen = genPackets (Range.linear 0 100) + gen = genValidPackets (Range.linear 1 10) (Range.linear 1 10) NoAbort packets :: [PacketStreamM2S 4 Int16] <- H.forAll gen @@ -124,7 +101,7 @@ prop_overFlowDrop_packetFifo_id = idWithModelSingleDomain @C.System defExpectOptions - (genPackets (Range.linear 0 100)) + (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) where @@ -144,20 +121,18 @@ prop_overFlowDrop_packetFifo_drop = (C.exposeClockResetEnable model) (C.exposeClockResetEnable ckt) where - bufferSize = d5 - ckt :: (HiddenClockResetEnable System) => Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = fromPacketStream |> overflowDropPacketFifoC bufferSize bufferSize + ckt = fromPacketStream |> overflowDropPacketFifoC d5 d5 model :: [PacketStreamM2S 4 Int16] -> [PacketStreamM2S 4 Int16] model packets = Prelude.concat $ take 1 packetChunk ++ drop 2 packetChunk where packetChunk = chunkByPacket packets - genSmall = genValidPacket (Range.linear 1 5) genCleanWord - genBig = genValidPacket (Range.linear 33 50) genCleanWord + genSmall = genValidPacket (Range.linear 1 5) NoAbort + genBig = genValidPacket (Range.linear 33 50) NoAbort -- | test for id using a small metabuffer to ensure backpressure using the metabuffer is tested prop_packetFifo_small_metaBuffer :: Property @@ -165,7 +140,7 @@ prop_packetFifo_small_metaBuffer = idWithModelSingleDomain @C.System defExpectOptions - (genPackets (Range.linear 0 100)) + (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) where @@ -176,7 +151,7 @@ prop_packetFifo_small_metaBuffer = tests :: TestTree tests = - localOption (mkTimeout 30_000_000 {- 30 seconds -}) $ + localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption (HedgehogTestLimit (Just 1_000)) $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs index b107dc39..cc6ddaff 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs @@ -13,7 +13,6 @@ import Prelude -- clash import Clash.Prelude -import qualified Clash.Prelude as C -- hedgehog import Hedgehog @@ -34,21 +33,9 @@ import Protocols.Hedgehog import Protocols.PacketStream.Base -- tests - import Protocols.PacketStream (packetizeFromDfC, packetizerC) import Tests.Protocols.PacketStream.Base -genVec :: (KnownNat n, 1 <= n) => Gen a -> Gen (Vec n a) -genVec gen = sequence (C.repeat gen) - -genMeta :: - forall (meta :: Type) (metaBytes :: Nat). - (KnownNat metaBytes) => - (1 <= metaBytes) => - (BitPack meta) => - (BitSize meta ~ metaBytes * 8) => - Gen meta -genMeta = fmap bitCoerce (genVec Gen.enumBounded :: Gen (Vec metaBytes (BitVector 8))) -- | Model of the generic `packetizerC`. packetizerModel :: @@ -124,28 +111,17 @@ packetizerPropertyGenerator SNat SNat = idWithModelSingleDomain @System defExpectOptions - (fmap (cleanPackets . fullPackets) (Gen.list (Range.linear 1 100) genPackets)) + (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) (exposeClockResetEnable model) (exposeClockResetEnable ckt) - where - model :: - [PacketStreamM2S dataWidth (Vec headerBytes (BitVector 8))] -> - [PacketStreamM2S dataWidth ()] - model = packetizerModel (const ()) id - - ckt :: - (HiddenClockResetEnable System) => - Circuit - (PacketStream System dataWidth (Vec headerBytes (BitVector 8))) - (PacketStream System dataWidth ()) - ckt = packetizerC (const ()) id - - genPackets = - PacketStreamM2S - <$> genVec Gen.enumBounded - <*> Gen.maybe Gen.enumBounded - <*> genMeta - <*> Gen.enumBounded + where + model = packetizerModel (const ()) id + ckt :: + (HiddenClockResetEnable System) => + Circuit + (PacketStream System dataWidth (Vec headerBytes (BitVector 8))) + (PacketStream System dataWidth ()) + ckt = packetizerC (const ()) id -- | headerBytes % dataWidth ~ 0 prop_const_packetizer_d1_d14 :: Property @@ -183,9 +159,7 @@ packetizeFromDfPropertyGenerator SNat SNat = (exposeClockResetEnable model) (exposeClockResetEnable ckt) where - model :: [Vec headerBytes (BitVector 8)] -> [PacketStreamM2S dataWidth ()] model = packetizeFromDfModel (const ()) id - ckt :: (HiddenClockResetEnable System) => Circuit (Df.Df System (Vec headerBytes (BitVector 8))) (PacketStream System dataWidth ()) @@ -209,7 +183,7 @@ prop_const_packetizeFromDf_d5_d4 = packetizeFromDfPropertyGenerator d5 d4 tests :: TestTree tests = - localOption (mkTimeout 12_000_000 {- 12 seconds -}) $ + localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption (HedgehogTestLimit (Just 1_000)) $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs index b1d06788..7a579b50 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs @@ -14,7 +14,6 @@ import qualified Clash.Prelude as C -- hedgehog import Hedgehog hiding (Parallel) -import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range -- tasty @@ -30,10 +29,10 @@ import Protocols.PacketStream.Base import Protocols.PacketStream.Routing -- tests -import Tests.Protocols.PacketStream.Base (chunkByPacket, fullPackets) +import Tests.Protocols.PacketStream.Base + +import qualified Data.List as L -genVec :: (C.KnownNat n, 1 <= n) => Gen a -> Gen (C.Vec n a) -genVec gen = sequence (C.repeat gen) -- | Tests the round-robin packet arbiter with one source; essentially an id test prop_packetarbiter_roundrobin_id :: Property @@ -75,14 +74,10 @@ makePropPacketArbiter _ _ mode = (C.exposeClockResetEnable (packetArbiterC mode)) (\xs ys -> partitionPackets xs === partitionPackets ys) where - genSources = mapM (fmap fullPackets . Gen.list (Range.linear 0 100) . genPacket) (C.indicesI @p) - -- TODO use util function from client review branch - genPacket i = - PacketStreamM2S - <$> genVec @n Gen.enumBounded - <*> Gen.maybe Gen.enumBounded - <*> pure i - <*> Gen.enumBounded + genSources = mapM setMeta (C.indicesI @p) + setMeta j = do + pkts <- genValidPackets @n @() (Range.linear 1 10) (Range.linear 1 10) Abort + pure $ L.map (\pkt -> pkt {_meta = j}) pkts partitionPackets packets = sortOn (_meta . head . head) $ @@ -123,7 +118,7 @@ makePropPacketDispatcher :: , 1 <= dataWidth , TestType a , Bounded a - , Enum a + , C.BitPack a ) => C.SNat dataWidth -> C.Vec p (a -> Bool) -> @@ -131,7 +126,7 @@ makePropPacketDispatcher :: makePropPacketDispatcher _ fs = idWithModelSingleDomain @C.System defExpectOptions - (Gen.list (Range.linear 0 100) genPackets) + (genValidPackets (Range.linear 1 10) (Range.linear 1 10) Abort) (C.exposeClockResetEnable (model 0)) (C.exposeClockResetEnable (packetDispatcherC fs)) where @@ -143,17 +138,9 @@ makePropPacketDispatcher _ fs = | i < maxBound = model (i + 1) (y : ys) | otherwise = model 0 ys - -- TODO use util function from client review branch - genPackets = - PacketStreamM2S - <$> genVec @dataWidth Gen.enumBounded - <*> Gen.maybe Gen.enumBounded - <*> Gen.enumBounded - <*> Gen.enumBounded - tests :: TestTree tests = - localOption (mkTimeout 12_000_000 {- 12 seconds -}) $ + localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption (HedgehogTestLimit (Just 1_000)) $(testGroupGenerator) From 9792d7d4d8d6bbe4b4ec43891a709999a745a8ae Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 22 Jul 2024 15:53:32 +0200 Subject: [PATCH 10/63] formatting --- .../src/Protocols/PacketStream/Base.hs | 2 +- .../Tests/Protocols/PacketStream/AsyncFifo.hs | 1 - .../Tests/Protocols/PacketStream/Base.hs | 106 ++++++++++-------- .../Protocols/PacketStream/Converters.hs | 1 - .../Protocols/PacketStream/Depacketizers.hs | 1 - .../Protocols/PacketStream/PacketFifo.hs | 11 +- .../Protocols/PacketStream/Packetizers.hs | 17 ++- .../Tests/Protocols/PacketStream/Routing.hs | 3 +- 8 files changed, 75 insertions(+), 67 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 261e0e33..3e290910 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -224,7 +224,7 @@ mapMetaS :: Circuit (PacketStream dom dataWidth a) (PacketStream dom dataWidth b) mapMetaS fS = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, go <$> bundle (fwdIn, fS)) where - go (inp, f) = (\inPkt -> inPkt {_meta = f (_meta inPkt)} ) <$> inp + go (inp, f) = (\inPkt -> inPkt{_meta = f (_meta inPkt)}) <$> inp -- | Map a function on the metadata of a packet stream. mapMeta :: diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs index 3135f094..633cfe73 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs @@ -30,7 +30,6 @@ import Protocols.PacketStream.Base -- tests import Tests.Protocols.PacketStream.Base - createDomain vSystem { vName = "TestDom50" diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs index 1beb9166..20903b38 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs @@ -12,7 +12,7 @@ module Tests.Protocols.PacketStream.Base ( dropAbortedPackets, downConvert, upConvert, - AbortMode(..), + AbortMode (..), genValidPacket, genValidPackets, genVec, @@ -123,17 +123,19 @@ upConvert :: [PacketStreamM2S n meta] upConvert packets = chunkToPacket <$> chopBy (C.natToNum @n) packets --- | If set to @NoAbort@, packets will never contain a transfer with _abort set. --- Otherwise, transfers of roughly 50% of the packets will randomly have _abort set. +{- | If set to @NoAbort@, packets will never contain a transfer with _abort set. + Otherwise, transfers of roughly 50% of the packets will randomly have _abort set. +-} data AbortMode = Abort | NoAbort --- | Generate valid packets, i.e. packets of which all transfers carry the same --- @_meta@ and with all unenabled bytes in @_data@ set to 0x00. +{- | Generate valid packets, i.e. packets of which all transfers carry the same + @_meta@ and with all unenabled bytes in @_data@ set to 0x00. +-} genValidPackets :: forall (dataWidth :: C.Nat) (metaType :: C.Type). - 1 C.<= dataWidth => - C.KnownNat dataWidth => - C.BitPack metaType => + (1 C.<= dataWidth) => + (C.KnownNat dataWidth) => + (C.BitPack metaType) => -- | The amount of packets to generate. Range Int -> -- | The amount of transfers to generate. @@ -144,19 +146,20 @@ genValidPackets :: AbortMode -> Gen [PacketStreamM2S dataWidth metaType] genValidPackets r1 r2 Abort = concat <$> Gen.list r1 x - where - x = do - abortPacket <- Gen.bool - genValidPacket r2 (if abortPacket then Abort else NoAbort) + where + x = do + abortPacket <- Gen.bool + genValidPacket r2 (if abortPacket then Abort else NoAbort) genValidPackets r1 r2 NoAbort = concat <$> Gen.list r1 (genValidPacket r2 NoAbort) --- | Generate a valid packet, i.e. a packet of which all transfers carry the same --- @_meta@ and with all unenabled bytes in @_data@ set to 0x00. +{- | Generate a valid packet, i.e. a packet of which all transfers carry the same + @_meta@ and with all unenabled bytes in @_data@ set to 0x00. +-} genValidPacket :: forall (dataWidth :: C.Nat) (metaType :: C.Type). - 1 C.<= dataWidth => - C.KnownNat dataWidth => - C.BitPack metaType => + (1 C.<= dataWidth) => + (C.KnownNat dataWidth) => + (C.BitPack metaType) => -- | The amount of transfers to generate. Range Int -> -- | If set to @NoAbort@, no transfers in the packet will have @_abort@ set. @@ -172,8 +175,8 @@ genValidPacket r abortMode = do -- | Generate a single transfer which is not yet the end of a packet. genTransfer :: forall (dataWidth :: C.Nat) (metaType :: C.Type). - 1 C.<= dataWidth => - C.KnownNat dataWidth => + (1 C.<= dataWidth) => + (C.KnownNat dataWidth) => -- | We need to use the same metadata -- for every transfer in a packet to satisfy the protocol -- invariant that metadata is constant for an entire packet. @@ -182,20 +185,22 @@ genTransfer :: -- randomly generate it. AbortMode -> Gen (PacketStreamM2S dataWidth metaType) -genTransfer meta abortMode = PacketStreamM2S - <$> genVec Gen.enumBounded - <*> Gen.constant Nothing - <*> Gen.constant meta - <*> case abortMode of - Abort -> Gen.enumBounded - NoAbort -> Gen.constant False - --- | Generate the last transfer of a packet, i.e. a transfer with @_last@ set as @Just@. --- All bytes which are not enabled are set to 0x00. +genTransfer meta abortMode = + PacketStreamM2S + <$> genVec Gen.enumBounded + <*> Gen.constant Nothing + <*> Gen.constant meta + <*> case abortMode of + Abort -> Gen.enumBounded + NoAbort -> Gen.constant False + +{- | Generate the last transfer of a packet, i.e. a transfer with @_last@ set as @Just@. + All bytes which are not enabled are set to 0x00. +-} genLastTransfer :: forall (dataWidth :: C.Nat) (metaType :: C.Type). - 1 C.<= dataWidth => - C.KnownNat dataWidth => + (1 C.<= dataWidth) => + (C.KnownNat dataWidth) => -- | We need to use the same metadata -- for every transfer in a packet to satisfy the protocol -- invariant that metadata is constant for an entire packet. @@ -204,23 +209,32 @@ genLastTransfer :: -- randomly generate it. AbortMode -> Gen (PacketStreamM2S dataWidth metaType) -genLastTransfer meta abortMode = setNull <$> (PacketStreamM2S - <$> genVec Gen.enumBounded - <*> (Just <$> Gen.enumBounded) - <*> Gen.constant meta - <*> case abortMode of - Abort -> Gen.enumBounded - NoAbort -> Gen.constant False) - where - setNull transfer = let i = M.fromJust (_last transfer) in - transfer { - _data = M.fromJust (Vec.fromList $ - take (1 + fromIntegral i) (C.toList (_data transfer)) - ++ replicate ((C.natToNum @dataWidth) - 1 - fromIntegral i) 0x00) - } +genLastTransfer meta abortMode = + setNull + <$> ( PacketStreamM2S + <$> genVec Gen.enumBounded + <*> (Just <$> Gen.enumBounded) + <*> Gen.constant meta + <*> case abortMode of + Abort -> Gen.enumBounded + NoAbort -> Gen.constant False + ) + where + setNull transfer = + let i = M.fromJust (_last transfer) + in transfer + { _data = + M.fromJust + ( Vec.fromList $ + take (1 + fromIntegral i) (C.toList (_data transfer)) + ++ replicate ((C.natToNum @dataWidth) - 1 - fromIntegral i) 0x00 + ) + } -- | Randomly generate a vector of length @n@. genVec :: forall (n :: C.Nat) (a :: C.Type). - (C.KnownNat n, 1 C.<= n) => Gen a -> Gen (C.Vec n a) + (C.KnownNat n, 1 C.<= n) => + Gen a -> + Gen (C.Vec n a) genVec gen = sequence (C.repeat gen) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs index cd6e871f..c2692c9f 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs @@ -30,7 +30,6 @@ import Protocols.PacketStream.Converters -- tests import Tests.Protocols.PacketStream.Base - ucModel :: forall n. (C.KnownNat n) => [PacketStreamM2S 1 ()] -> [PacketStreamM2S n ()] ucModel fragments = out where diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index 8ecaffea..48554766 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -35,7 +35,6 @@ import Protocols import Protocols.PacketStream (depacketizeToDfC, depacketizerC) import Tests.Protocols.PacketStream.Base - -- | Model of the generic `depacketizerC`. depacketizerModel :: forall diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index d1b23a67..c8761ddf 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -31,7 +31,6 @@ import Protocols.PacketStream.PacketFifo (overflowDropPacketFifoC, packetFifoC) -- tests import Tests.Protocols.PacketStream.Base as U - isSubsequenceOf :: (Eq a) => [a] -> [a] -> Bool isSubsequenceOf [] _ = True isSubsequenceOf _ [] = False @@ -48,11 +47,11 @@ prop_packetFifo_id = (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) - where - ckt :: - (HiddenClockResetEnable System) => - Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = packetFifoC d12 d12 + where + ckt :: + (HiddenClockResetEnable System) => + Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) + ckt = packetFifoC d12 d12 -- | test for id with a small buffer to ensure backpressure is tested prop_packetFifo_small_buffer_id :: Property diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs index cc6ddaff..aff5d398 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs @@ -36,7 +36,6 @@ import Protocols.PacketStream.Base import Protocols.PacketStream (packetizeFromDfC, packetizerC) import Tests.Protocols.PacketStream.Base - -- | Model of the generic `packetizerC`. packetizerModel :: forall @@ -114,14 +113,14 @@ packetizerPropertyGenerator SNat SNat = (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) (exposeClockResetEnable model) (exposeClockResetEnable ckt) - where - model = packetizerModel (const ()) id - ckt :: - (HiddenClockResetEnable System) => - Circuit - (PacketStream System dataWidth (Vec headerBytes (BitVector 8))) - (PacketStream System dataWidth ()) - ckt = packetizerC (const ()) id + where + model = packetizerModel (const ()) id + ckt :: + (HiddenClockResetEnable System) => + Circuit + (PacketStream System dataWidth (Vec headerBytes (BitVector 8))) + (PacketStream System dataWidth ()) + ckt = packetizerC (const ()) id -- | headerBytes % dataWidth ~ 0 prop_const_packetizer_d1_d14 :: Property diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs index 7a579b50..dc772681 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs @@ -33,7 +33,6 @@ import Tests.Protocols.PacketStream.Base import qualified Data.List as L - -- | Tests the round-robin packet arbiter with one source; essentially an id test prop_packetarbiter_roundrobin_id :: Property prop_packetarbiter_roundrobin_id = makePropPacketArbiter C.d1 C.d2 RoundRobin @@ -77,7 +76,7 @@ makePropPacketArbiter _ _ mode = genSources = mapM setMeta (C.indicesI @p) setMeta j = do pkts <- genValidPackets @n @() (Range.linear 1 10) (Range.linear 1 10) Abort - pure $ L.map (\pkt -> pkt {_meta = j}) pkts + pure $ L.map (\pkt -> pkt{_meta = j}) pkts partitionPackets packets = sortOn (_meta . head . head) $ From 6c1f3491864afa6d3057af037093b86e6fe2e609 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 22 Jul 2024 15:55:03 +0200 Subject: [PATCH 11/63] re-add tests --- clash-protocols/tests/Tests/Protocols.hs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/clash-protocols/tests/Tests/Protocols.hs b/clash-protocols/tests/Tests/Protocols.hs index d40fd5d2..aa9bf1ba 100644 --- a/clash-protocols/tests/Tests/Protocols.hs +++ b/clash-protocols/tests/Tests/Protocols.hs @@ -13,13 +13,13 @@ tests :: TestTree tests = testGroup "Protocols" - [ --Tests.Protocols.Df.tests - --, Tests.Protocols.DfConv.tests - --, Tests.Protocols.Avalon.tests - --, Tests.Protocols.Axi4.tests - Tests.Protocols.PacketStream.tests - --, Tests.Protocols.Wishbone.tests - --, Tests.Protocols.Vec.tests + [ Tests.Protocols.Df.tests + , Tests.Protocols.DfConv.tests + , Tests.Protocols.Avalon.tests + , Tests.Protocols.Axi4.tests + , Tests.Protocols.PacketStream.tests + , Tests.Protocols.Wishbone.tests + , Tests.Protocols.Vec.tests ] main :: IO () From 0b486f405cc791662e8f1e3fbbf78a0ea12f248e Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 26 Jul 2024 16:22:31 +0200 Subject: [PATCH 12/63] Optimize packetizer --- .../src/Protocols/PacketStream/Packetizers.hs | 366 ++++++++++++------ 1 file changed, 247 insertions(+), 119 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index 0d8ff568..84d9ec3f 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -17,155 +17,273 @@ import Protocols import qualified Protocols.Df as Df import Protocols.PacketStream.Base +import Clash.Sized.Vector.Extra (takeLe) +import Data.Data ((:~:) (Refl)) import Data.Maybe import Data.Maybe.Extra defaultByte :: BitVector 8 defaultByte = 0x00 -type HeaderBufSize (headerBytes :: Nat) (dataWidth :: Nat) = - headerBytes + dataWidth - --- The amount of bytes that we still need to forward due to --- @headerBytes@ not aligning with @dataWidth@. -type ForwardBufSize (headerBytes :: Nat) (dataWidth :: Nat) = - headerBytes `Mod` dataWidth - -type PacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = - ( KnownNat dataWidth - , 1 <= dataWidth +type PacketizerCt (header :: Type) (headerBytes :: Nat) (dataWidth :: Nat) = + ( BitPack header + , BitSize header ~ headerBytes * 8 , KnownNat headerBytes + , KnownNat dataWidth + , 1 <= headerBytes + , 1 <= dataWidth ) -data PacketizerState (metaOut :: Type) (headerBytes :: Nat) (dataWidth :: Nat) - = Insert - { _counter :: Index (headerBytes `DivRU` dataWidth) - , _hdrBuf :: Vec (HeaderBufSize headerBytes dataWidth) (BitVector 8) - , _aborted :: Bool +data PacketizerState1 (metaOut :: Type) (headerBytes :: Nat) (dataWidth :: Nat) + = Insert1 + { _aborted1 :: Bool } - | Forward - { _fwdBuf :: Vec (ForwardBufSize headerBytes dataWidth) (BitVector 8) - , _aborted :: Bool + | Forward1 + { _aborted1 :: Bool + , _hdrBuf1 :: Vec headerBytes (BitVector 8) } - | LastForward - {_lastFragment :: PacketStreamM2S dataWidth metaOut} - deriving (Generic, Show, ShowX) - -deriving instance - (NFDataX metaOut, PacketizerCt headerBytes dataWidth) => - NFDataX (PacketizerState metaOut headerBytes dataWidth) - --- | Initial state of @packetizerT@ -instance - (PacketizerCt headerBytes dataWidth) => - Default (PacketizerState metaOut headerBytes dataWidth) - where - def :: PacketizerState metaOut headerBytes dataWidth - def = Insert 0 (repeat defaultByte) False + | LastForward1 + { _aborted1 :: Bool + , _hdrBuf1 :: Vec headerBytes (BitVector 8) + } + deriving (Generic, NFDataX, Show, ShowX) -adjustLast :: +-- | Packetizer transition function in case @dataWidth > headerBytes@ +packetizerT1 :: forall (headerBytes :: Nat) - (dataWidth :: Nat). - ( headerBytes `Mod` dataWidth <= dataWidth - , KnownNat dataWidth - , 1 <= dataWidth - ) => - SNat headerBytes -> - Index dataWidth -> - Either (Index dataWidth) (Index dataWidth) -adjustLast SNat idx = if outputNow then Left nowIdx else Right nextIdx - where - outputNow = case compareSNat (SNat @(ForwardBufSize headerBytes dataWidth)) d0 of - SNatGT -> idx < natToNum @(dataWidth - ForwardBufSize headerBytes dataWidth) - _ -> True - nowIdx = idx + natToNum @(ForwardBufSize headerBytes dataWidth) - nextIdx = idx - natToNum @(dataWidth - ForwardBufSize headerBytes dataWidth) + (dataWidth :: Nat) + (header :: Type) + (metaIn :: Type) + (metaOut :: Type). + (PacketizerCt header headerBytes dataWidth) => + (headerBytes + 1 <= dataWidth) => + (metaIn -> metaOut) -> + (metaIn -> header) -> + PacketizerState1 metaOut headerBytes dataWidth -> + (Maybe (PacketStreamM2S dataWidth metaIn), PacketStreamS2M) -> + ( PacketizerState1 metaOut headerBytes dataWidth + , (PacketStreamS2M, Maybe (PacketStreamM2S dataWidth metaOut)) + ) +packetizerT1 toMetaOut toHeader st (Just inPkt, bwdIn) = + let + go buf = (nextStOut, (bwdOut, Just outPkt)) + where + bytesOut :: Vec (dataWidth - headerBytes) (BitVector 8) + (bytesOut, newBuf) = + leToPlus @headerBytes @dataWidth $ splitAt (SNat @(dataWidth - headerBytes)) (_data inPkt) + nextAborted = _aborted1 st || _abort inPkt + + outPkt = + inPkt + { _data = buf ++ bytesOut + , _last = newLast + , _meta = toMetaOut (_meta inPkt) + , _abort = nextAborted + } + + (nextSt, newLast) = case _last inPkt of + Nothing -> (Forward1 nextAborted newBuf, Nothing) + Just i + | i < natToNum @(dataWidth - headerBytes) -> + (Insert1 False, Just (i + natToNum @headerBytes)) + | otherwise -> (LastForward1 nextAborted newBuf, Nothing) + + nextStOut = if _ready bwdIn then nextSt else st + bwdOut = case nextStOut of + LastForward1{} -> PacketStreamS2M False + _ -> bwdIn + in + case st of + Insert1{} -> go (bitCoerce (toHeader (_meta inPkt))) + Forward1{..} -> go _hdrBuf1 + LastForward1{..} -> (nextStOut, (bwdIn, Just outPkt)) + where + outPkt = + inPkt + { _data = _hdrBuf1 ++ repeat defaultByte + , _last = (\i -> i - natToNum @(dataWidth - headerBytes)) <$> _last inPkt + , _meta = toMetaOut (_meta inPkt) + , _abort = _aborted1 || _abort inPkt + } + nextStOut = if _ready bwdIn then Insert1 False else st +packetizerT1 _ _ s (Nothing, bwdIn) = (s, (bwdIn, Nothing)) + +data PacketizerState2 (metaOut :: Type) (headerBytes :: Nat) (dataWidth :: Nat) + = LoadHeader2 + | Insert2 + { _aborted2 :: Bool + , _hdrBuf2 :: Vec headerBytes (BitVector 8) + , _counter2 :: Index (headerBytes `DivRU` dataWidth) + } + | Forward2 + { _aborted2 :: Bool + } + deriving (Generic, NFDataX, Show, ShowX) -packetizerT :: +-- | Packetizer transition function in case @dataWidth <= headerBytes@ and @headerBytes % dataWidth ~ 0@ +packetizerT2 :: forall (headerBytes :: Nat) (dataWidth :: Nat) (header :: Type) (metaIn :: Type) (metaOut :: Type). - (BitSize header ~ headerBytes * 8) => - (BitPack header) => - (PacketizerCt headerBytes dataWidth) => - (ForwardBufSize headerBytes dataWidth <= dataWidth) => + (PacketizerCt header headerBytes dataWidth) => + (headerBytes `Mod` dataWidth ~ 0) => + (dataWidth <= headerBytes) => (metaIn -> metaOut) -> (metaIn -> header) -> - PacketizerState metaOut headerBytes dataWidth -> + PacketizerState2 metaOut headerBytes dataWidth -> (Maybe (PacketStreamM2S dataWidth metaIn), PacketStreamS2M) -> - ( PacketizerState metaOut headerBytes dataWidth + ( PacketizerState2 metaOut headerBytes dataWidth , (PacketStreamS2M, Maybe (PacketStreamM2S dataWidth metaOut)) ) -packetizerT toMetaOut toHeader st@Insert{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = - (nextStOut, (bwdOut, fwdOut)) +-- Load the metadata into a buffer. This costs one extra cycle of latency, but it reduces resource usage. +packetizerT2 _ toHeader LoadHeader2 (Just inPkt, _) = + (Insert2 False (bitCoerce (toHeader (_meta inPkt))) 0, (PacketStreamS2M False, Nothing)) +packetizerT2 toMetaOut _ st@Insert2{..} (Just inPkt, bwdIn) = + (nextStOut, (PacketStreamS2M False, Just outPkt)) where - alignedCmp = compareSNat (SNat @(ForwardBufSize headerBytes dataWidth)) d0 - nextAborted = _aborted || _abort - header = bitCoerce (toHeader _meta) - metaOut = toMetaOut _meta - hdrBuf = if _counter == 0 then header ++ _data else _hdrBuf - (newHdrBuf, dataOut) = shiftOutFrom0 (SNat @dataWidth) hdrBuf - forwardBytes = snd $ shiftOutFromN (SNat @(ForwardBufSize headerBytes dataWidth)) _data + (newBuf, dataOut) = leToPlus @dataWidth @headerBytes $ shiftOutFrom0 (SNat @dataWidth) _hdrBuf2 + nextAborted = _aborted2 || _abort inPkt + outPkt = + inPkt + { _data = dataOut + , _last = Nothing + , _meta = toMetaOut (_meta inPkt) + , _abort = nextAborted + } - newLast = case alignedCmp of - SNatGT -> fmap (adjustLast (SNat @headerBytes)) _last - _ -> Nothing + nextSt + | _counter2 == maxBound = Forward2 nextAborted + | otherwise = Insert2 nextAborted newBuf (succ _counter2) - fwdOut = - Just - pkt - { _data = dataOut - , _last = if _counter == maxBound then either Just (const Nothing) =<< newLast else Nothing - , _meta = metaOut - , _abort = nextAborted - } + nextStOut = if _ready bwdIn then nextSt else st +packetizerT2 toMetaOut _ Forward2{..} (Just inPkt, bwdIn) = + (nextStOut, (bwdIn, Just outPkt)) + where + nextAborted = _aborted2 || _abort inPkt + outPkt = + inPkt + { _meta = toMetaOut (_meta inPkt) + , _abort = nextAborted + } + nextStOut + | isJust (_last inPkt) && _ready bwdIn = LoadHeader2 + | otherwise = Forward2 nextAborted +packetizerT2 _ _ s (Nothing, bwdIn) = (s, (bwdIn, Nothing)) - nextSt = case (_counter == maxBound, newLast) of - (False, _) -> Insert (succ _counter) newHdrBuf nextAborted - (True, Nothing) -> Forward forwardBytes nextAborted - (True, Just (Left _)) -> def - (True, Just (Right idx)) -> - LastForward - (PacketStreamM2S (take (SNat @dataWidth) newHdrBuf) (Just idx) metaOut nextAborted) +data PacketizerState3 (headerBytes :: Nat) (dataWidth :: Nat) + = LoadHeader3 + | Insert3 + { _aborted3 :: Bool + , _hdrBuf3 :: Vec (headerBytes + dataWidth) (BitVector 8) + , _counter3 :: Index (headerBytes `DivRU` dataWidth) + } + | Forward3 + { _aborted3 :: Bool + , _hdrBuf3 :: Vec (headerBytes + dataWidth) (BitVector 8) + } + | LastForward3 + { _aborted3 :: Bool + , _hdrBuf3 :: Vec (headerBytes + dataWidth) (BitVector 8) + } + deriving (Generic, Show, ShowX) - nextStOut = if isNothing fwdOut || _ready bwdIn then nextSt else st +deriving instance + (KnownNat headerBytes, KnownNat dataWidth) => + NFDataX (PacketizerState3 headerBytes dataWidth) - -- Assert backpressure while inserting the header. If shifting needs to be done - -- and we are at the last cycle of insertion, we do not need to assert backpressure - -- because we put the rest of the data in _fwdBuf (of course, unless our subordinate asserts backpressure). - bwdOut = PacketStreamS2M $ case alignedCmp of - SNatGT -> _ready bwdIn && _counter == maxBound - _ -> False -packetizerT toMetaOut _ st@Forward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = (nextStOut, (bwdIn, Just outPkt)) +-- | Packetizer transition function in case @dataWidth <= headerBytes@ and @headerBytes % dataWidth > 0@ +packetizerT3 :: + forall + (headerBytes :: Nat) + (dataWidth :: Nat) + (header :: Type) + (metaIn :: Type) + (metaOut :: Type). + (PacketizerCt header headerBytes dataWidth) => + (1 <= headerBytes `Mod` dataWidth) => + (headerBytes `Mod` dataWidth <= dataWidth) => + (dataWidth <= headerBytes) => + (metaIn -> metaOut) -> + (metaIn -> header) -> + PacketizerState3 headerBytes dataWidth -> + (Maybe (PacketStreamM2S dataWidth metaIn), PacketStreamS2M) -> + ( PacketizerState3 headerBytes dataWidth + , (PacketStreamS2M, Maybe (PacketStreamM2S dataWidth metaOut)) + ) +packetizerT3 _ toHeader LoadHeader3 (Just inPkt, _bwdIn) = + (nextStOut, (PacketStreamS2M False, Nothing)) + where + nextStOut = Insert3 False (bitCoerce (toHeader (_meta inPkt)) ++ _data inPkt) 0 +packetizerT3 toMetaOut _ st@Insert3{..} (Just inPkt, bwdIn) = + (nextStOut, (bwdOut, Just outPkt)) where - nextAborted = _aborted || _abort - metaOut = toMetaOut _meta - (dataOut, nextFwdBuf) = splitAt (SNat @dataWidth) (_fwdBuf ++ _data) - dataLast = nextFwdBuf ++ repeat @(dataWidth - ForwardBufSize headerBytes dataWidth) defaultByte - newLast = fmap (adjustLast (SNat @headerBytes)) _last + nextAborted = _aborted3 || _abort inPkt + (newHdrBuf, dataOut') = shiftOutFrom0 (SNat @dataWidth) _hdrBuf3 outPkt = - pkt - { _data = dataOut - , _last = either Just (const Nothing) =<< newLast - , _meta = metaOut - , _abort = nextAborted + inPkt + { _data = dataOut' + , _last = lastOut + , _meta = toMetaOut (_meta inPkt) } - nextSt = case newLast of - Nothing -> Forward nextFwdBuf nextAborted - Just (Left _) -> def - Just (Right idx) -> LastForward (PacketStreamM2S dataLast (Just idx) metaOut nextAborted) + (lastOut, nextSt) = case (_counter3 == maxBound, _last inPkt) of + (False, _) -> (Nothing, Insert3 nextAborted newHdrBuf (succ _counter3)) + (True, Nothing) -> (Nothing, Forward3 nextAborted newHdrBuf) + (True, Just i) -> + if i < natToNum @(dataWidth - headerBytes `Mod` dataWidth) + then (Just (i + natToNum @(headerBytes `Mod` dataWidth)), LoadHeader3) + else (Nothing, LastForward3 nextAborted newHdrBuf) + nextStOut = if _ready bwdIn then nextSt else st + bwdOut = case nextSt of + LoadHeader3 -> bwdIn + Forward3{} -> bwdIn + _ -> PacketStreamS2M False +packetizerT3 toMetaOut _ st@Forward3{..} (Just inPkt, bwdIn) = + (nextStOut, (bwdOut, Just outPkt)) + where + bytesOut :: Vec (dataWidth - headerBytes `Mod` dataWidth) (BitVector 8) + buf :: Vec (headerBytes `Mod` dataWidth) (BitVector 8) + (bytesOut, buf) = splitAt (SNat @(dataWidth - headerBytes `Mod` dataWidth)) (_data inPkt) + newBuf :: Vec (headerBytes + dataWidth) (BitVector 8) + newBuf = buf ++ repeat @(headerBytes + dataWidth - headerBytes `Mod` dataWidth) defaultByte + nextAborted = _aborted3 || _abort inPkt + + outPkt = + inPkt + { _data = take (SNat @(headerBytes `Mod` dataWidth)) _hdrBuf3 ++ bytesOut + , _last = lastOut + , _meta = toMetaOut (_meta inPkt) + , _abort = nextAborted + } + (lastOut, nextSt) = case _last inPkt of + Nothing -> (Nothing, Forward3 nextAborted newBuf) + Just i -> + if i < natToNum @(dataWidth - headerBytes `Mod` dataWidth) + then (Just (i + natToNum @(headerBytes `Mod` dataWidth)), LoadHeader3) + else (Nothing, LastForward3 nextAborted newBuf) nextStOut = if _ready bwdIn then nextSt else st -packetizerT _ _ st@LastForward{..} (_, bwdIn) = (nextStOut, (PacketStreamS2M False, Just _lastFragment)) + + bwdOut = case nextSt of + LastForward3{} -> PacketStreamS2M False + _ -> bwdIn +packetizerT3 toMetaOut _ st@LastForward3{..} (Just inPkt, bwdIn) = + (nextStOut, (bwdIn, Just outPkt)) where - nextStOut = if _ready bwdIn then def else st -packetizerT _ _ s (Nothing, bwdIn) = (s, (bwdIn, Nothing)) + outPkt = + inPkt + { _data = takeLe (SNat @dataWidth) _hdrBuf3 + , _last = (\i -> i - natToNum @(dataWidth - headerBytes `Mod` dataWidth)) <$> _last inPkt + , _meta = toMetaOut (_meta inPkt) + , _abort = _aborted3 || _abort inPkt + } + nextStOut = if _ready bwdIn then LoadHeader3 else st +packetizerT3 _ _ s (Nothing, bwdIn) = (s, (bwdIn, Nothing)) {- | Puts a portion of the metadata in front of the packet stream, and shifts the stream accordingly. This portion is defined by the metadata to header transformer function. If this function is `id`, @@ -179,13 +297,15 @@ packetizerC :: (metaOut :: Type) (header :: Type) (headerBytes :: Nat). - (HiddenClockResetEnable dom) => - (NFDataX metaOut) => - (BitPack header) => - (BitSize header ~ headerBytes * 8) => - (KnownNat headerBytes) => - (1 <= dataWidth) => - (KnownNat dataWidth) => + ( HiddenClockResetEnable dom + , NFDataX metaOut + , BitPack header + , BitSize header ~ headerBytes * 8 + , KnownNat headerBytes + , 1 <= dataWidth + , 1 <= headerBytes + , KnownNat dataWidth + ) => -- | Metadata transformer function (metaIn -> metaOut) -> -- | metaData to header that will be packetized transformer function @@ -193,11 +313,19 @@ packetizerC :: Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) packetizerC toMetaOut toHeader = fromSignals outCircuit where - outCircuit = case compareSNat (SNat @(ForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of - SNatLE -> mealyB (packetizerT @headerBytes toMetaOut toHeader) def + outCircuit = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) (SNat @dataWidth) of + SNatLE -> case compareSNat (SNat @(headerBytes + 1)) (SNat @dataWidth) of + SNatLE -> mealyB (packetizerT1 @headerBytes toMetaOut toHeader) (Insert1 False) + SNatGT -> case sameNat (SNat @(headerBytes `Mod` dataWidth)) d0 of + Just Refl -> mealyB (packetizerT2 @headerBytes toMetaOut toHeader) LoadHeader2 + _ -> case compareSNat d1 (SNat @(headerBytes `Mod` dataWidth)) of + SNatLE -> mealyB (packetizerT3 @headerBytes toMetaOut toHeader) LoadHeader3 + SNatGT -> + clashCompileError + "packetizerC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" _ -> clashCompileError - "packetizerC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + "packetizerC1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" data DfPacketizerState (metaOut :: Type) (headerBytes :: Nat) (dataWidth :: Nat) = DfIdle From 18ca61c9c7f858a047ff853e411575cccde50406 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 29 Jul 2024 11:43:15 +0200 Subject: [PATCH 13/63] Simply/optimize downConverter --- .../src/Protocols/PacketStream/Converters.hs | 158 +++++++----------- 1 file changed, 57 insertions(+), 101 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index 3a283e0b..c88f3680 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -128,109 +128,65 @@ upConverterC :: Circuit (PacketStream dom 1 ()) (PacketStream dom dataWidth ()) upConverterC = forceResetSanity |> fromSignals upConverter -data DownConverterState (dataWidth :: Nat) = DownConverterState - { _dcBuf :: Vec dataWidth (BitVector 8) - -- ^ Buffer - , _dcSize :: Index (dataWidth + 1) - -- ^ Number of valid bytes in _dcBuf - , _dcLastVec :: Bool - -- ^ True if last byte of _dcBuf was marked as last byte by incoming stream - , _dcAborted :: Bool - -- ^ If True, outgoing bytes should be marked as aborted until _dcBuf is replaced - } - deriving (Generic, NFDataX) - --- | Computes new state from incoming data -fromPacketStreamM2S :: - forall (dataWidth :: Nat). - (KnownNat dataWidth) => - PacketStreamM2S dataWidth () -> - DownConverterState dataWidth -fromPacketStreamM2S (PacketStreamM2S vs lastIdx _ aborted) = +data DownConverterState (dataWidth :: Nat) = DownConverterState - { _dcBuf = vs - , _dcSize = maybe (natToNum @dataWidth) (succ . resize) lastIdx -- lastIdx points to the last valid byte, so the buffer size is one more - , _dcLastVec = isJust lastIdx - , _dcAborted = aborted + { _dcBuf :: Vec dataWidth (BitVector 8) + -- ^ Buffer + , _dcSize :: Index (dataWidth + 1) + -- ^ Number of valid bytes in _dcBuf } - --- | Computes output of down converter -toMaybePacketStreamM2S :: - forall (dataWidth :: Nat). - (1 <= dataWidth) => - (KnownNat dataWidth) => - DownConverterState dataWidth -> - Maybe (PacketStreamM2S 1 ()) -toMaybePacketStreamM2S DownConverterState{..} = toMaybe (_dcSize > 0) out - where - out = - PacketStreamM2S - { _data = leToPlusKN @1 @dataWidth head _dcBuf :> Nil - , _last = toMaybe (_dcSize == 1 && _dcLastVec) 0 - , _meta = () - , _abort = _dcAborted - } - -downConverter :: - forall (dataWidth :: Nat) (dom :: Domain). - (HiddenClockResetEnable dom) => - (1 <= dataWidth) => - (KnownNat dataWidth) => - -- | Input packet stream from the source and backpressure from the sink - ( Signal dom (Maybe (PacketStreamM2S dataWidth ())) - , Signal dom PacketStreamS2M - ) -> - -- | Output backpressure to the source - -- Output packet stream to the sink - ( Signal dom PacketStreamS2M - , Signal dom (Maybe (PacketStreamM2S 1 ())) - ) -downConverter = mealyB go s0 - where - s0 = - DownConverterState + deriving (Generic, NFDataX) + +downConverterT + :: forall (dataWidth :: Nat). + 1 <= dataWidth + => KnownNat dataWidth + => DownConverterState dataWidth + -> (Maybe (PacketStreamM2S dataWidth ()), PacketStreamS2M) + -> (DownConverterState dataWidth, (PacketStreamS2M, Maybe (PacketStreamM2S 1 ()))) +downConverterT st (Nothing, _) = (st, (PacketStreamS2M True, Nothing)) +downConverterT st@DownConverterState{..} (Just inPkt, bwdIn) = (nextSt, (PacketStreamS2M outReady, Just outPkt)) + where + -- If _dcSize == 0, then we have received a new transfer and should use + -- its corresponding _data. Else, we should use our stored buffer. + (nextSize, buf) = case (_dcSize == 0, _last inPkt) of + (True, Nothing) -> (natToNum @(dataWidth - 1), _data inPkt) + (True, Just i) -> (resize i, _data inPkt) + (False, _) -> (pred _dcSize, _dcBuf) + + outPkt = + PacketStreamM2S + { _data = singleton (leToPlus @1 @dataWidth head buf) + , _last = outLast + , _meta = () + , _abort = _abort inPkt + } + + (outReady, outLast) + | nextSize == 0 = (_ready bwdIn, 0 <$ _last inPkt) + | otherwise = (False, Nothing) + + -- Keep the buffer in the state and rotate it once the byte is acknowledged to avoid + -- dynamic indexing. + nextSt + | _ready bwdIn = DownConverterState (rotateLeftS buf d1) nextSize + | otherwise = st + +-- | Converts packet streams of arbitrary data widths to packet streams of single bytes. +-- If @_abort@ is asserted on an input transfer, it will be asserted on all +-- corresponding output transfers as well. +-- +-- Has one clock cycle of latency, but optimal throughput, i.e. a packet of n bytes is +-- sent out in n clock cycles, even if `_last` is set. +downConverterC + :: forall (dataWidth :: Nat) (dom :: Domain). + HiddenClockResetEnable dom + => 1 <= dataWidth + => KnownNat dataWidth + => Circuit (PacketStream dom dataWidth ()) (PacketStream dom 1 ()) +downConverterC = forceResetSanity |> fromSignals (mealyB downConverterT s0) + where + s0 = DownConverterState { _dcBuf = errorX "downConverter: undefined initial value" , _dcSize = 0 - , _dcLastVec = False - , _dcAborted = False } - go :: - DownConverterState dataWidth -> - (Maybe (PacketStreamM2S dataWidth ()), PacketStreamS2M) -> - (DownConverterState dataWidth, (PacketStreamS2M, Maybe (PacketStreamM2S 1 ()))) - go st@(DownConverterState{..}) (fwdIn, PacketStreamS2M inReady) = (st', (bwdOut, fwdOut)) - where - (_dcSize', _dcBuf') = - if _dcSize > 0 && inReady - then (_dcSize - 1, _dcBuf <<+ 0) - else (_dcSize, _dcBuf) - - -- If the next buffer contains no valid bytes, - -- and the final byte was acknowledged, we can - -- acknowledge the newly received data. - -- The || is lazy, and we need this: if the output - -- of the downconverter is Nothing, we are not allowed to - -- evaluate inReady. - outReady = _dcSize == 0 || (_dcSize == 1 && inReady) - st' = case fwdIn of - Just inp | outReady -> fromPacketStreamM2S inp - _ -> - st - { _dcBuf = _dcBuf' - , _dcSize = _dcSize' - } - - bwdOut = PacketStreamS2M outReady - fwdOut = toMaybePacketStreamM2S st - -{- | Converts packet streams of arbitrary data widths to packet streams of single bytes. -Has one clock cycle of latency, but optimal throughput, i.e. a packet of n bytes is -sent out in n clock cycles, even if `_last` is set. --} -downConverterC :: - forall (dataWidth :: Nat) (dom :: Domain). - (HiddenClockResetEnable dom) => - (1 <= dataWidth) => - (KnownNat dataWidth) => - Circuit (PacketStream dom dataWidth ()) (PacketStream dom 1 ()) -downConverterC = forceResetSanity |> fromSignals downConverter From 9758a61ff4e5c098080d0af150562ce66b56fc0d Mon Sep 17 00:00:00 2001 From: t-wallet Date: Wed, 31 Jul 2024 15:11:58 +0200 Subject: [PATCH 14/63] Run formatter --- .../src/Protocols/PacketStream/Converters.hs | 113 +++++++++--------- 1 file changed, 57 insertions(+), 56 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index c88f3680..07699f10 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -128,65 +128,66 @@ upConverterC :: Circuit (PacketStream dom 1 ()) (PacketStream dom dataWidth ()) upConverterC = forceResetSanity |> fromSignals upConverter -data DownConverterState (dataWidth :: Nat) = - DownConverterState - { _dcBuf :: Vec dataWidth (BitVector 8) - -- ^ Buffer - , _dcSize :: Index (dataWidth + 1) - -- ^ Number of valid bytes in _dcBuf - } - deriving (Generic, NFDataX) - -downConverterT - :: forall (dataWidth :: Nat). - 1 <= dataWidth - => KnownNat dataWidth - => DownConverterState dataWidth - -> (Maybe (PacketStreamM2S dataWidth ()), PacketStreamS2M) - -> (DownConverterState dataWidth, (PacketStreamS2M, Maybe (PacketStreamM2S 1 ()))) +data DownConverterState (dataWidth :: Nat) = DownConverterState + { _dcBuf :: Vec dataWidth (BitVector 8) + -- ^ Buffer + , _dcSize :: Index (dataWidth + 1) + -- ^ Number of valid bytes in _dcBuf + } + deriving (Generic, NFDataX) + +downConverterT :: + forall (dataWidth :: Nat). + (1 <= dataWidth) => + (KnownNat dataWidth) => + DownConverterState dataWidth -> + (Maybe (PacketStreamM2S dataWidth ()), PacketStreamS2M) -> + (DownConverterState dataWidth, (PacketStreamS2M, Maybe (PacketStreamM2S 1 ()))) downConverterT st (Nothing, _) = (st, (PacketStreamS2M True, Nothing)) downConverterT st@DownConverterState{..} (Just inPkt, bwdIn) = (nextSt, (PacketStreamS2M outReady, Just outPkt)) - where - -- If _dcSize == 0, then we have received a new transfer and should use - -- its corresponding _data. Else, we should use our stored buffer. - (nextSize, buf) = case (_dcSize == 0, _last inPkt) of - (True, Nothing) -> (natToNum @(dataWidth - 1), _data inPkt) - (True, Just i) -> (resize i, _data inPkt) - (False, _) -> (pred _dcSize, _dcBuf) - - outPkt = - PacketStreamM2S - { _data = singleton (leToPlus @1 @dataWidth head buf) - , _last = outLast - , _meta = () - , _abort = _abort inPkt - } - - (outReady, outLast) - | nextSize == 0 = (_ready bwdIn, 0 <$ _last inPkt) - | otherwise = (False, Nothing) - - -- Keep the buffer in the state and rotate it once the byte is acknowledged to avoid - -- dynamic indexing. - nextSt - | _ready bwdIn = DownConverterState (rotateLeftS buf d1) nextSize - | otherwise = st - --- | Converts packet streams of arbitrary data widths to packet streams of single bytes. --- If @_abort@ is asserted on an input transfer, it will be asserted on all --- corresponding output transfers as well. --- --- Has one clock cycle of latency, but optimal throughput, i.e. a packet of n bytes is --- sent out in n clock cycles, even if `_last` is set. -downConverterC - :: forall (dataWidth :: Nat) (dom :: Domain). - HiddenClockResetEnable dom - => 1 <= dataWidth - => KnownNat dataWidth - => Circuit (PacketStream dom dataWidth ()) (PacketStream dom 1 ()) + where + -- If _dcSize == 0, then we have received a new transfer and should use + -- its corresponding _data. Else, we should use our stored buffer. + (nextSize, buf) = case (_dcSize == 0, _last inPkt) of + (True, Nothing) -> (natToNum @(dataWidth - 1), _data inPkt) + (True, Just i) -> (resize i, _data inPkt) + (False, _) -> (pred _dcSize, _dcBuf) + + outPkt = + PacketStreamM2S + { _data = singleton (leToPlus @1 @dataWidth head buf) + , _last = outLast + , _meta = () + , _abort = _abort inPkt + } + + (outReady, outLast) + | nextSize == 0 = (_ready bwdIn, 0 <$ _last inPkt) + | otherwise = (False, Nothing) + + -- Keep the buffer in the state and rotate it once the byte is acknowledged to avoid + -- dynamic indexing. + nextSt + | _ready bwdIn = DownConverterState (rotateLeftS buf d1) nextSize + | otherwise = st + +{- | Converts packet streams of arbitrary data widths to packet streams of single bytes. +If @_abort@ is asserted on an input transfer, it will be asserted on all +corresponding output transfers as well. + +Has one clock cycle of latency, but optimal throughput, i.e. a packet of n bytes is +sent out in n clock cycles, even if `_last` is set. +-} +downConverterC :: + forall (dataWidth :: Nat) (dom :: Domain). + (HiddenClockResetEnable dom) => + (1 <= dataWidth) => + (KnownNat dataWidth) => + Circuit (PacketStream dom dataWidth ()) (PacketStream dom 1 ()) downConverterC = forceResetSanity |> fromSignals (mealyB downConverterT s0) - where - s0 = DownConverterState + where + s0 = + DownConverterState { _dcBuf = errorX "downConverter: undefined initial value" , _dcSize = 0 } From df192db6c1e360e9da3cd482687d9b13cbc2f2a4 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Wed, 31 Jul 2024 16:04:03 +0200 Subject: [PATCH 15/63] Generate less input for PacketStream tests --- .../Tests/Protocols/PacketStream/AsyncFifo.hs | 17 ++++++----------- .../Tests/Protocols/PacketStream/Converters.hs | 14 +++++++------- .../Protocols/PacketStream/Depacketizers.hs | 10 +++++----- .../Tests/Protocols/PacketStream/PacketFifo.hs | 18 +++++++++--------- .../Protocols/PacketStream/Packetizers.hs | 4 ++-- .../Tests/Protocols/PacketStream/Routing.hs | 8 ++++---- 6 files changed, 33 insertions(+), 38 deletions(-) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs index 633cfe73..942b119c 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs @@ -56,11 +56,13 @@ clk50 = clockGen clk125 :: Clock TestDom125 clk125 = clockGen +-- Assert the reset for a different amount of cycles in each domain +-- to properly test the async fifo. rst50 :: Reset TestDom50 -rst50 = resetGen @TestDom50 +rst50 = resetGenN d50 rst125 :: Reset TestDom125 -rst125 = resetGen @TestDom125 +rst125 = resetGenN d60 en50 :: Enable TestDom50 en50 = enableGen @@ -83,14 +85,7 @@ generateAsyncFifoIdProp wClk wRst wEn rClk rRst rEn = defExpectOptions (genValidPackets (Range.linear 1 10) (Range.linear 1 30) Abort) id - ckt - where - ckt :: - (KnownDomain wDom, KnownDomain rDom) => - Circuit - (PacketStream wDom 1 Int) - (PacketStream rDom 1 Int) - ckt = asyncFifoC (C.SNat @8) wClk wRst wEn rClk rRst rEn + (asyncFifoC @wDom @rDom @4 @1 @Int d4 wClk wRst wEn rClk rRst rEn) {- | The async FIFO circuit should forward all of its input data without loss and without producing extra data. This property tests whether this is true, when the clock of the writer and reader is equally fast (50 MHz). @@ -114,5 +109,5 @@ tests :: TestTree tests = localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption - (HedgehogTestLimit (Just 1_000)) + (HedgehogTestLimit (Just 100)) $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs index c2692c9f..a37d7d79 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs @@ -43,7 +43,7 @@ upconverterTest C.SNat = idWithModelSingleDomain @C.System defExpectOptions - (genValidPackets (Range.linear 1 30) (Range.linear 1 30) Abort) + (genValidPackets (Range.linear 1 10) (Range.linear 1 20) Abort) (C.exposeClockResetEnable ucModel) (C.exposeClockResetEnable @C.System (upConverterC @n)) @@ -57,19 +57,19 @@ downconverterTest :: forall n. (1 <= n) => C.SNat n -> Property downconverterTest C.SNat = idWithModelSingleDomain @C.System - defExpectOptions - (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) + defExpectOptions{eoSampleMax = 1000} + (genValidPackets (Range.linear 1 8) (Range.linear 1 10) Abort) (C.exposeClockResetEnable downConvert) (C.exposeClockResetEnable @C.System (downConverterC @n)) prop_downconverter_d1, prop_downconverter_d2, prop_downconverter_d4 :: Property -prop_downconverter_d1 = downconverterTest (C.SNat @1) -prop_downconverter_d2 = downconverterTest (C.SNat @2) -prop_downconverter_d4 = downconverterTest (C.SNat @4) +prop_downconverter_d1 = downconverterTest C.d1 +prop_downconverter_d2 = downconverterTest C.d2 +prop_downconverter_d4 = downconverterTest C.d4 tests :: TestTree tests = localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption - (HedgehogTestLimit (Just 1_000)) + (HedgehogTestLimit (Just 500)) $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index 48554766..582a6847 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -120,8 +120,8 @@ depacketizerPropertyGenerator :: depacketizerPropertyGenerator SNat SNat = idWithModelSingleDomain @System - defExpectOptions - (genValidPackets (Range.linear 1 10) (Range.linear 1 50) Abort) + defExpectOptions{eoSampleMax = 1000} + (genValidPackets (Range.linear 1 4) (Range.linear 1 30) Abort) (exposeClockResetEnable model) (exposeClockResetEnable ckt) where @@ -164,8 +164,8 @@ depacketizeToDfPropertyGenerator :: depacketizeToDfPropertyGenerator SNat SNat = idWithModelSingleDomain @System - defExpectOptions - (genValidPackets (Range.linear 1 10) (Range.linear 1 50) NoAbort) + defExpectOptions{eoSampleMax = 1000} + (genValidPackets (Range.linear 1 4) (Range.linear 1 30) NoAbort) (exposeClockResetEnable model) (exposeClockResetEnable ckt) where @@ -195,5 +195,5 @@ tests :: TestTree tests = localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption - (HedgehogTestLimit (Just 1_000)) + (HedgehogTestLimit (Just 100)) $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index c8761ddf..0efa64f5 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -43,8 +43,8 @@ prop_packetFifo_id :: Property prop_packetFifo_id = idWithModelSingleDomain @C.System - defExpectOptions - (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) + defExpectOptions{eoSampleMax = 1000} + (genValidPackets (Range.linear 1 30) (Range.linear 1 10) Abort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) where @@ -58,7 +58,7 @@ prop_packetFifo_small_buffer_id :: Property prop_packetFifo_small_buffer_id = idWithModelSingleDomain @C.System - defExpectOptions + defExpectOptions{eoSampleMax = 1000} (genValidPackets (Range.linear 1 10) (Range.linear 1 30) NoAbort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) @@ -99,8 +99,8 @@ prop_overFlowDrop_packetFifo_id :: Property prop_overFlowDrop_packetFifo_id = idWithModelSingleDomain @C.System - defExpectOptions - (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) + defExpectOptions{eoSampleMax = 2000} + (genValidPackets (Range.linear 1 30) (Range.linear 1 10) Abort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) where @@ -114,7 +114,7 @@ prop_overFlowDrop_packetFifo_drop :: Property prop_overFlowDrop_packetFifo_drop = idWithModelSingleDomain @C.System - defExpectOptions + defExpectOptions{eoSampleMax = 1000} -- make sure the timeout is long as the packetFifo can be quiet for a while while dropping (liftA3 (\a b c -> a ++ b ++ c) genSmall genBig genSmall) (C.exposeClockResetEnable model) @@ -131,7 +131,7 @@ prop_overFlowDrop_packetFifo_drop = packetChunk = chunkByPacket packets genSmall = genValidPacket (Range.linear 1 5) NoAbort - genBig = genValidPacket (Range.linear 33 50) NoAbort + genBig = genValidPacket (Range.linear 33 33) NoAbort -- | test for id using a small metabuffer to ensure backpressure using the metabuffer is tested prop_packetFifo_small_metaBuffer :: Property @@ -139,7 +139,7 @@ prop_packetFifo_small_metaBuffer = idWithModelSingleDomain @C.System defExpectOptions - (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) + (genValidPackets (Range.linear 1 30) (Range.linear 1 4) Abort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) where @@ -152,5 +152,5 @@ tests :: TestTree tests = localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption - (HedgehogTestLimit (Just 1_000)) + (HedgehogTestLimit (Just 100)) $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs index aff5d398..e85e0dec 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs @@ -110,7 +110,7 @@ packetizerPropertyGenerator SNat SNat = idWithModelSingleDomain @System defExpectOptions - (genValidPackets (Range.linear 1 50) (Range.linear 1 10) Abort) + (genValidPackets (Range.linear 1 10) (Range.linear 1 10) Abort) (exposeClockResetEnable model) (exposeClockResetEnable ckt) where @@ -154,7 +154,7 @@ packetizeFromDfPropertyGenerator SNat SNat = idWithModelSingleDomain @System defExpectOptions - (Gen.list (Range.linear 1 100) (genVec Gen.enumBounded)) + (Gen.list (Range.linear 1 10) (genVec Gen.enumBounded)) (exposeClockResetEnable model) (exposeClockResetEnable ckt) where diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs index dc772681..fcf5bff1 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs @@ -67,7 +67,7 @@ makePropPacketArbiter :: makePropPacketArbiter _ _ mode = propWithModelSingleDomain @C.System - defExpectOptions + defExpectOptions{eoSampleMax = 1000} genSources (C.exposeClockResetEnable concat) (C.exposeClockResetEnable (packetArbiterC mode)) @@ -124,8 +124,8 @@ makePropPacketDispatcher :: Property makePropPacketDispatcher _ fs = idWithModelSingleDomain @C.System - defExpectOptions - (genValidPackets (Range.linear 1 10) (Range.linear 1 10) Abort) + defExpectOptions{eoSampleMax = 2000} + (genValidPackets (Range.linear 1 10) (Range.linear 1 6) Abort) (C.exposeClockResetEnable (model 0)) (C.exposeClockResetEnable (packetDispatcherC fs)) where @@ -141,5 +141,5 @@ tests :: TestTree tests = localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption - (HedgehogTestLimit (Just 1_000)) + (HedgehogTestLimit (Just 100)) $(testGroupGenerator) From 48af73204fc456d692564af8b32edf92e7e6dcda Mon Sep 17 00:00:00 2001 From: t-wallet Date: Wed, 31 Jul 2024 16:08:37 +0200 Subject: [PATCH 16/63] Remove redundant imports --- clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs index 942b119c..b3573868 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs @@ -22,10 +22,8 @@ import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) -- clash-protocols -import Protocols import Protocols.Hedgehog import Protocols.PacketStream.AsyncFifo -import Protocols.PacketStream.Base -- tests import Tests.Protocols.PacketStream.Base From 0c64db80795f2b612be3e10cab31e9e28b781949 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 2 Aug 2024 09:57:50 +0200 Subject: [PATCH 17/63] Add first version of generic PacketStream delay circuit --- clash-protocols/clash-protocols.cabal | 2 + clash-protocols/src/Protocols/PacketStream.hs | 2 + .../src/Protocols/PacketStream/Delay.hs | 46 +++++++++++++++++ .../Tests/Protocols/PacketStream/Delay.hs | 50 +++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 clash-protocols/src/Protocols/PacketStream/Delay.hs create mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs diff --git a/clash-protocols/clash-protocols.cabal b/clash-protocols/clash-protocols.cabal index d86ec410..ef224de3 100644 --- a/clash-protocols/clash-protocols.cabal +++ b/clash-protocols/clash-protocols.cabal @@ -149,6 +149,7 @@ library Protocols.PacketStream.Base Protocols.PacketStream.AsyncFifo Protocols.PacketStream.Converters + Protocols.PacketStream.Delay Protocols.PacketStream.Depacketizers Protocols.PacketStream.PacketFifo Protocols.PacketStream.Packetizers @@ -202,6 +203,7 @@ test-suite unittests Tests.Protocols.PacketStream.AsyncFifo Tests.Protocols.PacketStream.Base Tests.Protocols.PacketStream.Converters + Tests.Protocols.PacketStream.Delay Tests.Protocols.PacketStream.Depacketizers Tests.Protocols.PacketStream.Packetizers Tests.Protocols.PacketStream.PacketFifo diff --git a/clash-protocols/src/Protocols/PacketStream.hs b/clash-protocols/src/Protocols/PacketStream.hs index 2dbfbbd8..8ee43ffe 100644 --- a/clash-protocols/src/Protocols/PacketStream.hs +++ b/clash-protocols/src/Protocols/PacketStream.hs @@ -19,6 +19,7 @@ module Protocols.PacketStream ( module Protocols.PacketStream.PacketFifo, module Protocols.PacketStream.AsyncFifo, module Protocols.PacketStream.Converters, + module Protocols.PacketStream.Delay, module Protocols.PacketStream.Depacketizers, module Protocols.PacketStream.Packetizers, module Protocols.PacketStream.Routing, @@ -28,6 +29,7 @@ where import Protocols.PacketStream.AsyncFifo import Protocols.PacketStream.Base import Protocols.PacketStream.Converters +import Protocols.PacketStream.Delay import Protocols.PacketStream.Depacketizers import Protocols.PacketStream.PacketFifo import Protocols.PacketStream.Packetizers diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs new file mode 100644 index 00000000..c81167cc --- /dev/null +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -0,0 +1,46 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# OPTIONS_HADDOCK hide #-} + +{- | +Provides a circuit that delays a stream by a configurable amount of clock cycles. +-} +module Protocols.PacketStream.Delay ( + delayStream, +) where + +import Clash.Prelude + +import Protocols +import Protocols.PacketStream.Base + +import Data.Maybe + +-- TODO Optimization: _meta only needs to be buffered once because it is constant per packet +data DelayState cycles dataWidth meta = DelayState + { _buf :: Vec cycles (Maybe (PacketStreamM2S dataWidth meta)) + -- ^ Transfer buffer + } + deriving (Generic, NFDataX, Show, ShowX) + +-- | Forwards incoming packets with `cycles` clock cycles latency. +delayStream :: + forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type) (cycles :: Nat). + (HiddenClockResetEnable dom) => + (KnownNat dataWidth) => + (1 <= cycles) => + (1 <= dataWidth) => + (NFDataX meta) => + SNat cycles -> + Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) +delayStream SNat = forceResetSanity |> fromSignals (mealyB go (DelayState @cycles (repeat Nothing))) + where + go st (fwdIn, bwdIn) = (nextStOut, (PacketStreamS2M outReady, fwdOut)) + where + (newBuf, out) = shiftInAtN (_buf st) (singleton fwdIn) + fwdOut = head out + + nextSt = DelayState newBuf + + (nextStOut, outReady) + | isJust fwdOut && not (_ready bwdIn) = (st, False) + | otherwise = (nextSt, True) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs new file mode 100644 index 00000000..f8b3f7e5 --- /dev/null +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs @@ -0,0 +1,50 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NumericUnderscores #-} + +module Tests.Protocols.PacketStream.Delay where + +-- base +import Prelude + +-- clash-prelude +import Clash.Prelude (type (<=)) +import qualified Clash.Prelude as C + +-- hedgehog +import Hedgehog +import qualified Hedgehog.Range as Range + +-- tasty +import Test.Tasty +import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) +import Test.Tasty.Hedgehog.Extra (testProperty) +import Test.Tasty.TH (testGroupGenerator) + +-- clash-protocols +import Protocols +import Protocols.Hedgehog +import Protocols.PacketStream + +-- tests +import Tests.Protocols.PacketStream.Base + +prop_delaystream_id :: Property +prop_delaystream_id = + idWithModelSingleDomain + @C.System + defExpectOptions + (genValidPackets (Range.linear 1 10) (Range.linear 1 6) Abort) + (C.exposeClockResetEnable id) + (C.exposeClockResetEnable ckt) + where + ckt :: + (C.HiddenClockResetEnable C.System) => + Circuit (PacketStream C.System 2 ()) (PacketStream C.System 2 ()) + ckt = delayStream @C.System @2 @() @4 C.d4 + +tests :: TestTree +tests = + localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ + localOption + (HedgehogTestLimit (Just 1_000)) + $(testGroupGenerator) From 589de450b615daa8f6652c903bc67652fcabbe23 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 2 Aug 2024 15:19:24 +0200 Subject: [PATCH 18/63] DownConverter arbitrary metadata support + output data width may be any divisor of input data width --- .../src/Protocols/PacketStream/Converters.hs | 127 ++++++++---------- .../src/Protocols/PacketStream/Delay.hs | 2 +- .../Tests/Protocols/PacketStream/Base.hs | 8 +- .../Protocols/PacketStream/Converters.hs | 51 ++++--- .../Tests/Protocols/PacketStream/Delay.hs | 1 - 5 files changed, 96 insertions(+), 93 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index 07699f10..b45507fc 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -12,10 +12,11 @@ module Protocols.PacketStream.Converters ( import Clash.Prelude -import Protocols (Circuit (..), fromSignals, (|>)) +import Protocols (Circuit (..), fromSignals, idC, (|>)) import Protocols.PacketStream.Base -import Data.Maybe (isJust, isNothing) +import Data.Data ((:~:) (Refl)) +import Data.Maybe (isJust) import Data.Maybe.Extra -- | Upconverter state, consisting of at most p `BitVector 8`s and a vector indicating which bytes are valid @@ -53,7 +54,7 @@ nextState st@(UpConverterState{..}) Nothing (PacketStreamS2M inReady) = nextStRaw = st { _ucFlush = False - , _ucAborted = isNothing _ucLastIdx && _ucAborted + , _ucAborted = not _ucFlush && _ucAborted , _ucLastIdx = Nothing } nextSt = if outReady then nextStRaw else st @@ -61,17 +62,13 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M nextSt where inLast = isJust _last - -- We smear an abort over the entire rest of the packet - -- so the next abort is set: - -- - If fragment we are potentially flushing was not the last and we were already aborting; - -- - or if the incoming fragment is aborted - nextAbort = (isNothing _ucLastIdx && _ucAborted) || _abort + nextAbort = (not _ucFlush && _ucAborted) || _abort -- If we are not flushing we can accept data to be stored in _ucBuf, -- but when we are flushing we can only accept if the current -- output fragment is accepted by the sink outReady = not _ucFlush || inReady bufFull = _ucIdx == maxBound - currBuf = if _ucFreshBuf then (repeat 0) else _ucBuf + currBuf = if _ucFreshBuf then repeat 0 else _ucBuf nextBuf = replace _ucIdx (head _data) currBuf nextFlush = inLast || bufFull @@ -88,35 +85,6 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M } nextSt = if outReady then nextStRaw else st -upConverter :: - forall (dataWidth :: Nat) (dom :: Domain). - (HiddenClockResetEnable dom) => - (1 <= dataWidth) => - (KnownNat dataWidth) => - -- | Input packet stream from the source - -- Input backpressure from the sink - ( Signal dom (Maybe (PacketStreamM2S 1 ())) - , Signal dom PacketStreamS2M - ) -> - -- | Output backpressure to the source - -- Output packet stream to the sink - ( Signal dom PacketStreamS2M - , Signal dom (Maybe (PacketStreamM2S dataWidth ())) - ) -upConverter = mealyB go s0 - where - s0 = UpConverterState (repeat undefined) 0 False True False Nothing - go :: - UpConverterState dataWidth -> - (Maybe (PacketStreamM2S 1 ()), PacketStreamS2M) -> - ( UpConverterState dataWidth - , (PacketStreamS2M, Maybe (PacketStreamM2S dataWidth ())) - ) - go st@(UpConverterState{..}) (fwdIn, bwdIn) = - (nextState st fwdIn bwdIn, (PacketStreamS2M outReady, toPacketStream st)) - where - outReady = not _ucFlush || (_ready bwdIn) - {- | Converts packet streams of single bytes to packet streams of a higher data widths. Has one cycle of latency, but optimal throughput. -} @@ -126,68 +94,91 @@ upConverterC :: (1 <= dataWidth) => (KnownNat dataWidth) => Circuit (PacketStream dom 1 ()) (PacketStream dom dataWidth ()) -upConverterC = forceResetSanity |> fromSignals upConverter +upConverterC = forceResetSanity |> fromSignals (mealyB go s0) + where + s0 = UpConverterState (repeat undefined) 0 False True False Nothing + go st@(UpConverterState{..}) (fwdIn, bwdIn) = + (nextState st fwdIn bwdIn, (PacketStreamS2M outReady, toPacketStream st)) + where + outReady = not _ucFlush || _ready bwdIn -data DownConverterState (dataWidth :: Nat) = DownConverterState - { _dcBuf :: Vec dataWidth (BitVector 8) +data DownConverterState (dwIn :: Nat) = DownConverterState + { _dcBuf :: Vec dwIn (BitVector 8) -- ^ Buffer - , _dcSize :: Index (dataWidth + 1) + , _dcSize :: Index (dwIn + 1) -- ^ Number of valid bytes in _dcBuf } deriving (Generic, NFDataX) downConverterT :: - forall (dataWidth :: Nat). - (1 <= dataWidth) => - (KnownNat dataWidth) => - DownConverterState dataWidth -> - (Maybe (PacketStreamM2S dataWidth ()), PacketStreamS2M) -> - (DownConverterState dataWidth, (PacketStreamS2M, Maybe (PacketStreamM2S 1 ()))) + forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (n :: Nat). + (1 <= dwIn) => + (1 <= dwOut) => + (1 <= n) => + (KnownNat dwIn) => + (KnownNat dwOut) => + (dwIn ~ dwOut * n) => + DownConverterState dwIn -> + (Maybe (PacketStreamM2S dwIn meta), PacketStreamS2M) -> + ( DownConverterState dwIn + , (PacketStreamS2M, Maybe (PacketStreamM2S dwOut meta)) + ) downConverterT st (Nothing, _) = (st, (PacketStreamS2M True, Nothing)) downConverterT st@DownConverterState{..} (Just inPkt, bwdIn) = (nextSt, (PacketStreamS2M outReady, Just outPkt)) where -- If _dcSize == 0, then we have received a new transfer and should use -- its corresponding _data. Else, we should use our stored buffer. (nextSize, buf) = case (_dcSize == 0, _last inPkt) of - (True, Nothing) -> (natToNum @(dataWidth - 1), _data inPkt) - (True, Just i) -> (resize i, _data inPkt) - (False, _) -> (pred _dcSize, _dcBuf) + (True, Nothing) -> (maxBound - natToNum @dwOut, _data inPkt) + (True, Just i) -> (satSub SatBound (resize i + 1) (natToNum @dwOut), _data inPkt) + (False, _) -> (satSub SatBound _dcSize (natToNum @dwOut), _dcBuf) + + (newBuf, dataOut) = leToPlus @dwOut @dwIn shiftOutFrom0 (SNat @dwOut) buf outPkt = PacketStreamM2S - { _data = singleton (leToPlus @1 @dataWidth head buf) + { _data = dataOut , _last = outLast - , _meta = () + , _meta = _meta inPkt , _abort = _abort inPkt } (outReady, outLast) - | nextSize == 0 = (_ready bwdIn, 0 <$ _last inPkt) + | nextSize == 0 = + (_ready bwdIn, resize . (\i -> i `mod` natToNum @dwOut) <$> _last inPkt) | otherwise = (False, Nothing) -- Keep the buffer in the state and rotate it once the byte is acknowledged to avoid -- dynamic indexing. nextSt - | _ready bwdIn = DownConverterState (rotateLeftS buf d1) nextSize + | _ready bwdIn = DownConverterState newBuf nextSize | otherwise = st -{- | Converts packet streams of arbitrary data widths to packet streams of single bytes. +{- | Converts packet streams of arbitrary data width `dwIn` to packet streams of +a smaller data width, `dwOut`, where `dwOut` must divide `dwIn`. + If @_abort@ is asserted on an input transfer, it will be asserted on all corresponding output transfers as well. -Has one clock cycle of latency, but optimal throughput, i.e. a packet of n bytes is +Provides zero latency and optimal throughput, i.e. a packet of n bytes is sent out in n clock cycles, even if `_last` is set. -} downConverterC :: - forall (dataWidth :: Nat) (dom :: Domain). + forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). (HiddenClockResetEnable dom) => - (1 <= dataWidth) => - (KnownNat dataWidth) => - Circuit (PacketStream dom dataWidth ()) (PacketStream dom 1 ()) -downConverterC = forceResetSanity |> fromSignals (mealyB downConverterT s0) - where - s0 = - DownConverterState - { _dcBuf = errorX "downConverter: undefined initial value" - , _dcSize = 0 - } + (1 <= dwIn) => + (1 <= dwOut) => + (1 <= n) => + (KnownNat dwIn) => + (KnownNat dwOut) => + (dwIn ~ dwOut * n) => + Circuit (PacketStream dom dwIn meta) (PacketStream dom dwOut meta) +downConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of + Just Refl -> idC + _ -> forceResetSanity |> fromSignals (mealyB downConverterT s0) + where + s0 = + DownConverterState + { _dcBuf = errorX "downConverterC: undefined initial value" + , _dcSize = 0 + } diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs index c81167cc..25f5846c 100644 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -16,7 +16,7 @@ import Protocols.PacketStream.Base import Data.Maybe -- TODO Optimization: _meta only needs to be buffered once because it is constant per packet -data DelayState cycles dataWidth meta = DelayState +newtype DelayState cycles dataWidth meta = DelayState { _buf :: Vec cycles (Maybe (PacketStreamM2S dataWidth meta)) -- ^ Transfer buffer } diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs index 20903b38..ff7f8af6 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs @@ -69,11 +69,13 @@ chunkToPacket :: (C.KnownNat n) => [PacketStreamM2S 1 meta] -> PacketStreamM2S n chunkToPacket l = PacketStreamM2S { _last = - if M.isJust $ _last $ L.last l then M.Just (fromIntegral $ L.length l - 1) else Nothing + if M.isJust (_last lastTransfer) then M.Just (fromIntegral $ L.length l - 1) else Nothing , _abort = any _abort l - , _meta = _meta $ L.head l + , _meta = _meta lastTransfer , _data = foldr ((C.+>>) . C.head . _data) (C.repeat 0) l } + where + lastTransfer = L.last l -- | Split a PacketStream n into a list of PacketStream 1 chopPacket :: @@ -121,7 +123,7 @@ upConvert :: (C.KnownNat n) => [PacketStreamM2S 1 meta] -> [PacketStreamM2S n meta] -upConvert packets = chunkToPacket <$> chopBy (C.natToNum @n) packets +upConvert packets = map chunkToPacket (chunkByPacket packets >>= chopBy (C.natToNum @n)) {- | If set to @NoAbort@, packets will never contain a transfer with _abort set. Otherwise, transfers of roughly 50% of the packets will randomly have _abort set. diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs index a37d7d79..dafed041 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs @@ -5,7 +5,6 @@ module Tests.Protocols.PacketStream.Converters where -- base -import qualified Data.Maybe as M import Prelude -- clash-prelude @@ -24,19 +23,11 @@ import Test.Tasty.TH (testGroupGenerator) -- clash-protocols import Protocols.Hedgehog -import Protocols.PacketStream.Base import Protocols.PacketStream.Converters -- tests import Tests.Protocols.PacketStream.Base -ucModel :: forall n. (C.KnownNat n) => [PacketStreamM2S 1 ()] -> [PacketStreamM2S n ()] -ucModel fragments = out - where - wholePackets = smearAbort <$> chunkBy (M.isJust . _last) fragments - chunks = wholePackets >>= chopBy (C.natToNum @n) - out = fmap chunkToPacket chunks - -- | Test the upconverter stream instance upconverterTest :: forall n. (1 <= n) => C.SNat n -> Property upconverterTest C.SNat = @@ -44,7 +35,7 @@ upconverterTest C.SNat = @C.System defExpectOptions (genValidPackets (Range.linear 1 10) (Range.linear 1 20) Abort) - (C.exposeClockResetEnable ucModel) + (C.exposeClockResetEnable upConvert) (C.exposeClockResetEnable @C.System (upConverterC @n)) prop_upconverter_d1, prop_upconverter_d2, prop_upconverter_d4 :: Property @@ -52,20 +43,40 @@ prop_upconverter_d1 = upconverterTest C.d1 prop_upconverter_d2 = upconverterTest C.d2 prop_upconverter_d4 = upconverterTest C.d4 --- | Test the downconverter stream instance -downconverterTest :: forall n. (1 <= n) => C.SNat n -> Property -downconverterTest C.SNat = +generateDownConverterProperty :: + forall (dwIn :: C.Nat) (dwOut :: C.Nat) (n :: C.Nat). + (1 <= dwIn) => + (1 <= dwOut) => + (1 <= n) => + (C.KnownNat n) => + (dwIn ~ n C.* dwOut) => + C.SNat dwIn -> + C.SNat dwOut -> + Property +generateDownConverterProperty C.SNat C.SNat = idWithModelSingleDomain - @C.System defExpectOptions{eoSampleMax = 1000} (genValidPackets (Range.linear 1 8) (Range.linear 1 10) Abort) - (C.exposeClockResetEnable downConvert) - (C.exposeClockResetEnable @C.System (downConverterC @n)) + (C.exposeClockResetEnable (upConvert . downConvert)) + (C.exposeClockResetEnable @C.System (downConverterC @dwIn @dwOut @Int)) + +prop_downConverter8to4 :: Property +prop_downConverter8to4 = generateDownConverterProperty C.d8 C.d4 + +prop_downConverter6to3 :: Property +prop_downConverter6to3 = generateDownConverterProperty C.d6 C.d3 + +prop_downConverter4to2 :: Property +prop_downConverter4to2 = generateDownConverterProperty C.d4 C.d2 + +prop_downConverter4to1 :: Property +prop_downConverter4to1 = generateDownConverterProperty C.d4 C.d1 + +prop_downConverter2to1 :: Property +prop_downConverter2to1 = generateDownConverterProperty C.d2 C.d1 -prop_downconverter_d1, prop_downconverter_d2, prop_downconverter_d4 :: Property -prop_downconverter_d1 = downconverterTest C.d1 -prop_downconverter_d2 = downconverterTest C.d2 -prop_downconverter_d4 = downconverterTest C.d4 +prop_downConverter1to1 :: Property +prop_downConverter1to1 = generateDownConverterProperty C.d1 C.d1 tests :: TestTree tests = diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs index f8b3f7e5..8b1b3f35 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs @@ -7,7 +7,6 @@ module Tests.Protocols.PacketStream.Delay where import Prelude -- clash-prelude -import Clash.Prelude (type (<=)) import qualified Clash.Prelude as C -- hedgehog From d33ab4cf7dc9862c088df8481554398e86266858 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 2 Aug 2024 16:17:00 +0200 Subject: [PATCH 19/63] UpConverter arbitrary metadata support + output data width may be any divisor of input data width --- .../src/Protocols/PacketStream/Converters.hs | 71 ++++++++++++------- .../Protocols/PacketStream/Converters.hs | 40 ++++++++--- .../Protocols/PacketStream/Depacketizers.hs | 2 +- .../Protocols/PacketStream/PacketFifo.hs | 2 +- .../Tests/Protocols/PacketStream/Routing.hs | 2 +- 5 files changed, 80 insertions(+), 37 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index b45507fc..2098c67a 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -20,10 +20,10 @@ import Data.Maybe (isJust) import Data.Maybe.Extra -- | Upconverter state, consisting of at most p `BitVector 8`s and a vector indicating which bytes are valid -data UpConverterState (dataWidth :: Nat) = UpConverterState - { _ucBuf :: Vec dataWidth (BitVector 8) +data UpConverterState (dwOut :: Nat) (n :: Nat) (meta :: Type) = UpConverterState + { _ucBuf :: Vec dwOut (BitVector 8) -- ^ The buffer we are filling - , _ucIdx :: Index dataWidth + , _ucIdx :: Index n -- ^ Where in the buffer we need to write the next element , _ucFlush :: Bool -- ^ If this is true the current state can presented as packetstream word @@ -31,20 +31,29 @@ data UpConverterState (dataWidth :: Nat) = UpConverterState -- ^ If this is true we need to start a fresh buffer , _ucAborted :: Bool -- ^ Current packet is aborted - , _ucLastIdx :: Maybe (Index dataWidth) + , _ucLastIdx :: Maybe (Index dwOut) -- ^ If true the current buffer contains the last byte of the current packet + , _ucMeta :: meta } deriving (Generic, NFDataX) -toPacketStream :: UpConverterState dataWidth -> Maybe (PacketStreamM2S dataWidth ()) -toPacketStream UpConverterState{..} = toMaybe _ucFlush (PacketStreamM2S _ucBuf _ucLastIdx () _ucAborted) +toPacketStream :: UpConverterState dwOut n meta -> Maybe (PacketStreamM2S dwOut meta) +toPacketStream UpConverterState{..} = toMaybe _ucFlush (PacketStreamM2S _ucBuf _ucLastIdx _ucMeta _ucAborted) nextState :: - (KnownNat dataWidth) => - UpConverterState dataWidth -> - Maybe (PacketStreamM2S 1 ()) -> + forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (n :: Nat). + (1 <= dwIn) => + (1 <= dwOut) => + (1 <= n) => + (KnownNat dwIn) => + (KnownNat dwOut) => + (KnownNat n) => + (NFDataX meta) => + (dwOut ~ dwIn * n) => + UpConverterState dwOut n meta -> + Maybe (PacketStreamM2S dwIn meta) -> PacketStreamS2M -> - UpConverterState dataWidth + UpConverterState dwOut n meta nextState st@(UpConverterState{..}) Nothing (PacketStreamS2M inReady) = nextSt where @@ -61,7 +70,6 @@ nextState st@(UpConverterState{..}) Nothing (PacketStreamS2M inReady) = nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M inReady) = nextSt where - inLast = isJust _last nextAbort = (not _ucFlush && _ucAborted) || _abort -- If we are not flushing we can accept data to be stored in _ucBuf, -- but when we are flushing we can only accept if the current @@ -69,9 +77,15 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M outReady = not _ucFlush || inReady bufFull = _ucIdx == maxBound currBuf = if _ucFreshBuf then repeat 0 else _ucBuf - nextBuf = replace _ucIdx (head _data) currBuf - nextFlush = inLast || bufFull + nextBuf = + bitCoerce + $ replace + _ucIdx + (bitCoerce _data :: BitVector (8 * dwIn)) + (bitCoerce currBuf :: Vec n (BitVector (8 * dwIn))) + + nextFlush = isJust _last || bufFull nextIdx = if nextFlush then 0 else _ucIdx + 1 nextStRaw = @@ -81,7 +95,8 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M , _ucFlush = nextFlush , _ucFreshBuf = nextFlush , _ucAborted = nextAbort - , _ucLastIdx = toMaybe inLast _ucIdx + , _ucLastIdx = (\i -> resize _ucIdx * natToNum @dwIn + resize i) <$> _last + , _ucMeta = _meta } nextSt = if outReady then nextStRaw else st @@ -89,18 +104,26 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M Has one cycle of latency, but optimal throughput. -} upConverterC :: - forall (dataWidth :: Nat) (dom :: Domain). + forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). (HiddenClockResetEnable dom) => - (1 <= dataWidth) => - (KnownNat dataWidth) => - Circuit (PacketStream dom 1 ()) (PacketStream dom dataWidth ()) -upConverterC = forceResetSanity |> fromSignals (mealyB go s0) - where - s0 = UpConverterState (repeat undefined) 0 False True False Nothing - go st@(UpConverterState{..}) (fwdIn, bwdIn) = - (nextState st fwdIn bwdIn, (PacketStreamS2M outReady, toPacketStream st)) + (1 <= dwIn) => + (1 <= dwOut) => + (1 <= n) => + (KnownNat dwIn) => + (KnownNat dwOut) => + (KnownNat n) => + (dwOut ~ dwIn * n) => + (NFDataX meta) => + Circuit (PacketStream dom dwIn meta) (PacketStream dom dwOut meta) +upConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of + Just Refl -> idC + _ -> forceResetSanity |> fromSignals (mealyB go s0) where - outReady = not _ucFlush || _ready bwdIn + s0 = UpConverterState (repeat undefined) 0 False True False Nothing undefined + go st@(UpConverterState{..}) (fwdIn, bwdIn) = + (nextState st fwdIn bwdIn, (PacketStreamS2M outReady, toPacketStream st)) + where + outReady = not _ucFlush || _ready bwdIn data DownConverterState (dwIn :: Nat) = DownConverterState { _dcBuf :: Vec dwIn (BitVector 8) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs index dafed041..c87563a8 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs @@ -28,20 +28,40 @@ import Protocols.PacketStream.Converters -- tests import Tests.Protocols.PacketStream.Base --- | Test the upconverter stream instance -upconverterTest :: forall n. (1 <= n) => C.SNat n -> Property -upconverterTest C.SNat = +generateUpConverterProperty :: + forall (dwIn :: C.Nat) (dwOut :: C.Nat) (n :: C.Nat). + (1 <= dwIn) => + (1 <= dwOut) => + (1 <= n) => + (C.KnownNat n) => + (dwOut ~ n C.* dwIn) => + C.SNat dwIn -> + C.SNat dwOut -> + Property +generateUpConverterProperty C.SNat C.SNat = idWithModelSingleDomain - @C.System defExpectOptions (genValidPackets (Range.linear 1 10) (Range.linear 1 20) Abort) - (C.exposeClockResetEnable upConvert) - (C.exposeClockResetEnable @C.System (upConverterC @n)) + (C.exposeClockResetEnable (upConvert . downConvert)) + (C.exposeClockResetEnable @C.System (upConverterC @dwIn @dwOut @Int)) + +prop_upConverter4to8 :: Property +prop_upConverter4to8 = generateUpConverterProperty C.d4 C.d8 + +prop_upConverter3to6 :: Property +prop_upConverter3to6 = generateUpConverterProperty C.d3 C.d6 + +prop_upConverter2to4 :: Property +prop_upConverter2to4 = generateUpConverterProperty C.d2 C.d4 + +prop_upConverter1to4 :: Property +prop_upConverter1to4 = generateUpConverterProperty C.d1 C.d4 + +prop_upConverter1to2 :: Property +prop_upConverter1to2 = generateUpConverterProperty C.d1 C.d2 -prop_upconverter_d1, prop_upconverter_d2, prop_upconverter_d4 :: Property -prop_upconverter_d1 = upconverterTest C.d1 -prop_upconverter_d2 = upconverterTest C.d2 -prop_upconverter_d4 = upconverterTest C.d4 +prop_upConverter1to1 :: Property +prop_upConverter1to1 = generateUpConverterProperty C.d1 C.d1 generateDownConverterProperty :: forall (dwIn :: C.Nat) (dwOut :: C.Nat) (n :: C.Nat). diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index 582a6847..83fd1ec2 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -120,7 +120,7 @@ depacketizerPropertyGenerator :: depacketizerPropertyGenerator SNat SNat = idWithModelSingleDomain @System - defExpectOptions{eoSampleMax = 1000} + defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} (genValidPackets (Range.linear 1 4) (Range.linear 1 30) Abort) (exposeClockResetEnable model) (exposeClockResetEnable ckt) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index 0efa64f5..9bf7cd07 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -99,7 +99,7 @@ prop_overFlowDrop_packetFifo_id :: Property prop_overFlowDrop_packetFifo_id = idWithModelSingleDomain @C.System - defExpectOptions{eoSampleMax = 2000} + defExpectOptions{eoSampleMax = 2000, eoStopAfterEmpty = 1000} (genValidPackets (Range.linear 1 30) (Range.linear 1 10) Abort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs index fcf5bff1..26bb410e 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs @@ -124,7 +124,7 @@ makePropPacketDispatcher :: Property makePropPacketDispatcher _ fs = idWithModelSingleDomain @C.System - defExpectOptions{eoSampleMax = 2000} + defExpectOptions{eoSampleMax = 2000, eoStopAfterEmpty = 1000} (genValidPackets (Range.linear 1 10) (Range.linear 1 6) Abort) (C.exposeClockResetEnable (model 0)) (C.exposeClockResetEnable (packetDispatcherC fs)) From 075134a548435a81f082b9957547499854c8530d Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 2 Aug 2024 16:29:55 +0200 Subject: [PATCH 20/63] Fix haddock --- clash-protocols/src/Protocols/PacketStream/Converters.hs | 6 +++--- clash-protocols/src/Protocols/PacketStream/Delay.hs | 2 +- .../tests/Tests/Protocols/PacketStream/Depacketizers.hs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index 2098c67a..1a0c75a2 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -177,10 +177,10 @@ downConverterT st@DownConverterState{..} (Just inPkt, bwdIn) = (nextSt, (PacketS | _ready bwdIn = DownConverterState newBuf nextSize | otherwise = st -{- | Converts packet streams of arbitrary data width `dwIn` to packet streams of -a smaller data width, `dwOut`, where `dwOut` must divide `dwIn`. +{- | Converts packet streams of arbitrary data width @dwIn@ to packet streams of +a smaller data width, @dwOut@, where @dwOut@ must divide @dwIn@. -If @_abort@ is asserted on an input transfer, it will be asserted on all +If `_abort` is asserted on an input transfer, it will be asserted on all corresponding output transfers as well. Provides zero latency and optimal throughput, i.e. a packet of n bytes is diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs index 25f5846c..9c8a67ac 100644 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -22,7 +22,7 @@ newtype DelayState cycles dataWidth meta = DelayState } deriving (Generic, NFDataX, Show, ShowX) --- | Forwards incoming packets with `cycles` clock cycles latency. +-- | Forwards incoming packets with @cycles@ clock cycles latency. delayStream :: forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type) (cycles :: Nat). (HiddenClockResetEnable dom) => diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index 83fd1ec2..be9572de 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -164,7 +164,7 @@ depacketizeToDfPropertyGenerator :: depacketizeToDfPropertyGenerator SNat SNat = idWithModelSingleDomain @System - defExpectOptions{eoSampleMax = 1000} + defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} (genValidPackets (Range.linear 1 4) (Range.linear 1 30) NoAbort) (exposeClockResetEnable model) (exposeClockResetEnable ckt) From 2e8720bb500bfca96890cab3e81d7c0632c70b5d Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 2 Aug 2024 16:56:10 +0200 Subject: [PATCH 21/63] Fix problematic tests (?) --- .../tests/Tests/Protocols/PacketStream/PacketFifo.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index 9bf7cd07..a9008347 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -43,7 +43,7 @@ prop_packetFifo_id :: Property prop_packetFifo_id = idWithModelSingleDomain @C.System - defExpectOptions{eoSampleMax = 1000} + defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} (genValidPackets (Range.linear 1 30) (Range.linear 1 10) Abort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) @@ -58,7 +58,7 @@ prop_packetFifo_small_buffer_id :: Property prop_packetFifo_small_buffer_id = idWithModelSingleDomain @C.System - defExpectOptions{eoSampleMax = 1000} + defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} (genValidPackets (Range.linear 1 10) (Range.linear 1 30) NoAbort) (C.exposeClockResetEnable dropAbortedPackets) (C.exposeClockResetEnable ckt) @@ -114,7 +114,7 @@ prop_overFlowDrop_packetFifo_drop :: Property prop_overFlowDrop_packetFifo_drop = idWithModelSingleDomain @C.System - defExpectOptions{eoSampleMax = 1000} + defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} -- make sure the timeout is long as the packetFifo can be quiet for a while while dropping (liftA3 (\a b c -> a ++ b ++ c) genSmall genBig genSmall) (C.exposeClockResetEnable model) From a2c3e4f6afe9c03d94faa5bd6cda4dd0f8f6d2c1 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Wed, 14 Aug 2024 15:08:34 +0200 Subject: [PATCH 22/63] Add PacketStream generic transfer delay circuit --- .../src/Protocols/PacketStream/Delay.hs | 75 +++++++++++++++---- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs index 9c8a67ac..d000d78f 100644 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -1,8 +1,9 @@ +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_HADDOCK hide #-} {- | -Provides a circuit that delays a stream by a configurable amount of clock cycles. +Provides a circuit that delays a stream by a configurable amount of transfers. -} module Protocols.PacketStream.Delay ( delayStream, @@ -16,31 +17,75 @@ import Protocols.PacketStream.Base import Data.Maybe -- TODO Optimization: _meta only needs to be buffered once because it is constant per packet -newtype DelayState cycles dataWidth meta = DelayState - { _buf :: Vec cycles (Maybe (PacketStreamM2S dataWidth meta)) +-- Holds for _abort and _last too +data DelayState n dataWidth meta = DelayState + { _buf :: Vec n (PacketStreamM2S dataWidth meta) -- ^ Transfer buffer + , _size :: Index (n + 1) + , _flush :: Bool + , _readPtr :: Index n + , _writePtr :: Index n } deriving (Generic, NFDataX, Show, ShowX) --- | Forwards incoming packets with @cycles@ clock cycles latency. +-- | Forwards incoming packets with @n@ fragments latency. delayStream :: - forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type) (cycles :: Nat). + forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type) (n :: Nat). (HiddenClockResetEnable dom) => (KnownNat dataWidth) => - (1 <= cycles) => + (1 <= n) => (1 <= dataWidth) => (NFDataX meta) => - SNat cycles -> + SNat n -> Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) -delayStream SNat = forceResetSanity |> fromSignals (mealyB go (DelayState @cycles (repeat Nothing))) +-- TODO this component is very unoptimized/ugly. The most important thing is +-- that it works now, but it should be improved in the future by removing. +-- dynamic indexing and perhaps using blockram. +delayStream SNat = + forceResetSanity + |> fromSignals + (mealyB go (DelayState @n (repeat (errorX "undefined initial contents")) 0 False 0 0)) where - go st (fwdIn, bwdIn) = (nextStOut, (PacketStreamS2M outReady, fwdOut)) + go st@DelayState{..} (Nothing, bwdIn) = (nextStOut, (bwdOut, fwdOut)) where - (newBuf, out) = shiftInAtN (_buf st) (singleton fwdIn) - fwdOut = head out + out = _buf !! _readPtr - nextSt = DelayState newBuf + readEn = _size > 0 && _flush - (nextStOut, outReady) - | isJust fwdOut && not (_ready bwdIn) = (st, False) - | otherwise = (nextSt, True) + fwdOut = + if readEn + then Just out + else Nothing + + bwdOut = PacketStreamS2M True + + nextSt = + st + { _size = _size - 1 + , _flush = _size - 1 > 0 + , _readPtr = satSucc SatWrap _readPtr + } + nextStOut = if readEn && _ready bwdIn then nextSt else st + go st@DelayState{..} (Just inPkt, bwdIn) = (nextStOut, (bwdOut, fwdOut)) + where + readEn = _flush || _size == maxBound + + out = _buf !! _readPtr + newBuf = replace _writePtr inPkt _buf + + fwdOut = + if readEn + then Just out + else Nothing + + bwdOut = PacketStreamS2M $ not _flush && (not readEn || _ready bwdIn) + + nextReadPtr = if readEn then satSucc SatWrap _readPtr else _readPtr + + (nextBuf, nextSize, nextFlush, nextWritePtr) + | _flush = (_buf, satPred SatBound _size, satPred SatBound _size > 0, _writePtr) + | otherwise = + (newBuf, satSucc SatBound _size, isJust (_last inPkt), satSucc SatWrap _writePtr) + + nextSt = DelayState nextBuf nextSize nextFlush nextReadPtr nextWritePtr + nextStOut = if isNothing fwdOut || _ready bwdIn then nextSt else st From 9bafbe49f001d25a6459f19269ac045482329b73 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Wed, 14 Aug 2024 15:14:26 +0200 Subject: [PATCH 23/63] Add dropTailC --- .../src/Protocols/PacketStream/Delay.hs | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs index d000d78f..21c6663d 100644 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -2,19 +2,27 @@ {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_HADDOCK hide #-} +{-# OPTIONS -fplugin=Protocols.Plugin #-} + {- | Provides a circuit that delays a stream by a configurable amount of transfers. -} module Protocols.PacketStream.Delay ( delayStream, + dropTailC, ) where import Clash.Prelude import Protocols +import qualified Protocols.Df as Df +import qualified Protocols.DfConv as DfConv import Protocols.PacketStream.Base +import Data.Bifunctor +import Data.Data ((:~:) (Refl)) import Data.Maybe +import Data.Proxy -- TODO Optimization: _meta only needs to be buffered once because it is constant per packet -- Holds for _abort and _last too @@ -89,3 +97,139 @@ delayStream SNat = nextSt = DelayState nextBuf nextSize nextFlush nextReadPtr nextWritePtr nextStOut = if isNothing fwdOut || _ready bwdIn then nextSt else st + +{- | +Gets a delayed @PacketStream@ as input together with a non-delayed +@DropTailInfo@, so that dropping can be done while correctly preserving +@_abort@ and adjusting @_last@. +-} +dropTailC' :: + forall (dom :: Domain) (dataWidth :: Nat) (delayCycles :: Nat) (meta :: Type). + (KnownDomain dom) => + (KnownNat dataWidth) => + (KnownNat delayCycles) => + (HiddenClockResetEnable dom) => + Circuit + (PacketStream dom dataWidth meta, Df.Df dom (DropTailInfo dataWidth delayCycles)) + (PacketStream dom dataWidth meta) +dropTailC' = fromSignals (first unbundle . mealyB go (0, Nothing) . first bundle) + where + go (0, cache) ((fwdIn1, fwdIn2), bwdIn) = (nextStOut, ((bwdOut1, bwdOut2), fwdOut)) + where + (bwdOut1, bwdOut2) + | isNothing fwdOut = (PacketStreamS2M True, Ack True) + | otherwise = (bwdIn, Ack (_ready bwdIn)) + + (nextSt, fwdOut) = case (fwdIn1, fwdIn2) of + (Nothing, Df.NoData) -> ((0, cache), Nothing) + (Nothing, Df.Data inf) -> ((_transferDrop inf, cache), Nothing) + (Just pkt, Df.NoData) -> case cache of + Nothing -> ((0, Nothing), Just pkt) + Just (abb, newIddx, delayy) -> ((delayy, Nothing), Just pkt{_abort = abb, _last = Just newIddx}) + (Just pkt, Df.Data inf) -> + if _wait inf + then ((0, Just (_aborted inf, _newIdx inf, _transferDrop inf)), Just pkt) + else + ( (_transferDrop inf, Nothing) + , Just pkt{_last = Just (_newIdx inf), _abort = _aborted inf} + ) + + nextStOut = if isNothing fwdOut || _ready bwdIn then nextSt else (0, cache) + go (st, cache) ((_, info), _) = ((nextSt, cache), ((PacketStreamS2M True, Ack True), Nothing)) + where + nextSt = case info of + Df.NoData -> st - 1 + Df.Data d -> _transferDrop d + +-- | Information about the tail of a packet, for dropping purposes. +data DropTailInfo dataWidth delayCycles = DropTailInfo + { _aborted :: Bool + -- ^ Whether any fragment of the packet was aborted + , _newIdx :: Index dataWidth + -- ^ The adjusted byte enable + , _transferDrop :: Index (delayCycles + 1) + -- ^ The amount of transfers to drop from the tail + , _wait :: Bool + -- ^ Iff true, apply changes to transfer the next clock cycle instead + } + +{- | +Transmits information about a single packet upon seeing its last transfer. +-} +transmitDropInfoC :: + forall (dom :: Domain) (n :: Nat) (dataWidth :: Nat) (delayCycles :: Nat) (meta :: Type). + (KnownDomain dom) => + (KnownNat dataWidth) => + (KnownNat delayCycles) => + (1 <= dataWidth) => + (1 <= n) => + (HiddenClockResetEnable dom) => + SNat n -> + Circuit + (PacketStream dom dataWidth meta) + (Df.Df dom (DropTailInfo dataWidth delayCycles)) +transmitDropInfoC SNat = forceResetSanity |> fromSignals (mealyB go False) + where + go aborted (Nothing, Ack readyIn) = (aborted, (PacketStreamS2M readyIn, Df.NoData)) + go aborted (Just PacketStreamM2S{..}, Ack readyIn) = (nextAborted, (PacketStreamS2M readyIn, fwdOut)) + where + x = natToNum @(n `Mod` dataWidth) + abortOut = aborted || _abort + + (nextAborted, fwdOut) = case _last of + Nothing -> (aborted || _abort, Df.NoData) + Just i -> + ( False + , Df.Data + DropTailInfo + { _aborted = abortOut + , _newIdx = satSub SatWrap i x + , _transferDrop = case compareSNat (SNat @dataWidth) (SNat @n) of + SNatLE -> case sameNat d0 (SNat @(n `Mod` dataWidth)) of + Just Refl -> natToNum @(n `Div` dataWidth) + _ -> + if (resize i :: Index n) < natToNum @(n - dataWidth) + then natToNum @(n `DivRU` dataWidth) + else natToNum @(n `Div` dataWidth) + SNatGT -> if i >= natToNum @n then 0 else 1 + , _wait = case compareSNat (SNat @dataWidth) (SNat @n) of + SNatLE -> case sameNat d0 (SNat @(n `Mod` dataWidth)) of + Just Refl -> False + _ -> (resize i :: Index n) >= natToNum @(n - dataWidth) + SNatGT -> i >= natToNum @n + } + ) + +{- | +Removes the last @n@ bytes from each packet in a @PacketStream@. +If any dropped transfers had @_abort@ set, this will be preserved by +setting the @_abort@ of an earlier transfer that is not dropped. + +__NB__: assumes that packets are at least @n@ bytes long. If this is +not the case, this component will break. +-} +dropTailC :: + forall (dom :: Domain) (n :: Nat) (dataWidth :: Nat) (meta :: Type). + (KnownDomain dom) => + (KnownNat dataWidth) => + (1 <= dataWidth) => + (1 <= n) => + (NFDataX meta) => + (HiddenClockResetEnable dom) => + SNat n -> + Circuit + (PacketStream dom dataWidth meta) + (PacketStream dom dataWidth meta) +dropTailC SNat = case compareSNat d1 (SNat @(n `DivRU` dataWidth)) of + SNatLE -> + forceResetSanity + |> circuit + ( \stream -> do + [s1, s2] <- DfConv.fanout Proxy Proxy -< stream + delayed <- delayStream (SNat @(n `DivRU` dataWidth)) -< s1 + info <- transmitDropInfoC @dom @n @dataWidth @(n `DivRU` dataWidth) (SNat @n) -< s2 + dropTailC' @dom @dataWidth @(n `DivRU` dataWidth) @meta -< (delayed, info) + ) + _ -> + clashCompileError + "dropTailC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" From c23be407cf7faccccc58c0c5e03e8519f1ab2daf Mon Sep 17 00:00:00 2001 From: t-wallet Date: Wed, 14 Aug 2024 17:16:06 +0200 Subject: [PATCH 24/63] Add component that zeroes out all invalid bytes --- .../src/Protocols/PacketStream/Base.hs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 3e290910..e0c206e4 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -232,3 +232,25 @@ mapMeta :: (a -> b) -> Circuit (PacketStream dom dataWidth a) (PacketStream dom dataWidth b) mapMeta f = mapMetaS (pure f) + +{- | +Sets data bytes that are not enabled in a @PacketStream@ to @0x00@. +-} +zeroOutInvalidBytesC :: + forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type). + (KnownNat dataWidth) => + (1 <= dataWidth) => + Circuit + (PacketStream dom dataWidth meta) + (PacketStream dom dataWidth meta) +zeroOutInvalidBytesC = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, fmap (go <$>) fwdIn) + where + go transferIn = transferIn{_data = dataOut} + where + dataOut = case _last transferIn of + Nothing -> _data transferIn + Just i -> a ++ b + where + -- The first byte is always valid, so we only map over the rest. + (a, b') = splitAt d1 (_data transferIn) + b = imap (\(j :: Index (dataWidth - 1)) byte -> if resize j < i then byte else 0x00) b' From 2e8ee546ba0746d1bc55c073205e01bc6236e90b Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 19 Aug 2024 09:41:48 +0200 Subject: [PATCH 25/63] Temporarily remove dropTailC until Protocols.Plugin is moved --- .../src/Protocols/PacketStream/Delay.hs | 144 ------------------ 1 file changed, 144 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs index 21c6663d..d000d78f 100644 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -2,27 +2,19 @@ {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_HADDOCK hide #-} -{-# OPTIONS -fplugin=Protocols.Plugin #-} - {- | Provides a circuit that delays a stream by a configurable amount of transfers. -} module Protocols.PacketStream.Delay ( delayStream, - dropTailC, ) where import Clash.Prelude import Protocols -import qualified Protocols.Df as Df -import qualified Protocols.DfConv as DfConv import Protocols.PacketStream.Base -import Data.Bifunctor -import Data.Data ((:~:) (Refl)) import Data.Maybe -import Data.Proxy -- TODO Optimization: _meta only needs to be buffered once because it is constant per packet -- Holds for _abort and _last too @@ -97,139 +89,3 @@ delayStream SNat = nextSt = DelayState nextBuf nextSize nextFlush nextReadPtr nextWritePtr nextStOut = if isNothing fwdOut || _ready bwdIn then nextSt else st - -{- | -Gets a delayed @PacketStream@ as input together with a non-delayed -@DropTailInfo@, so that dropping can be done while correctly preserving -@_abort@ and adjusting @_last@. --} -dropTailC' :: - forall (dom :: Domain) (dataWidth :: Nat) (delayCycles :: Nat) (meta :: Type). - (KnownDomain dom) => - (KnownNat dataWidth) => - (KnownNat delayCycles) => - (HiddenClockResetEnable dom) => - Circuit - (PacketStream dom dataWidth meta, Df.Df dom (DropTailInfo dataWidth delayCycles)) - (PacketStream dom dataWidth meta) -dropTailC' = fromSignals (first unbundle . mealyB go (0, Nothing) . first bundle) - where - go (0, cache) ((fwdIn1, fwdIn2), bwdIn) = (nextStOut, ((bwdOut1, bwdOut2), fwdOut)) - where - (bwdOut1, bwdOut2) - | isNothing fwdOut = (PacketStreamS2M True, Ack True) - | otherwise = (bwdIn, Ack (_ready bwdIn)) - - (nextSt, fwdOut) = case (fwdIn1, fwdIn2) of - (Nothing, Df.NoData) -> ((0, cache), Nothing) - (Nothing, Df.Data inf) -> ((_transferDrop inf, cache), Nothing) - (Just pkt, Df.NoData) -> case cache of - Nothing -> ((0, Nothing), Just pkt) - Just (abb, newIddx, delayy) -> ((delayy, Nothing), Just pkt{_abort = abb, _last = Just newIddx}) - (Just pkt, Df.Data inf) -> - if _wait inf - then ((0, Just (_aborted inf, _newIdx inf, _transferDrop inf)), Just pkt) - else - ( (_transferDrop inf, Nothing) - , Just pkt{_last = Just (_newIdx inf), _abort = _aborted inf} - ) - - nextStOut = if isNothing fwdOut || _ready bwdIn then nextSt else (0, cache) - go (st, cache) ((_, info), _) = ((nextSt, cache), ((PacketStreamS2M True, Ack True), Nothing)) - where - nextSt = case info of - Df.NoData -> st - 1 - Df.Data d -> _transferDrop d - --- | Information about the tail of a packet, for dropping purposes. -data DropTailInfo dataWidth delayCycles = DropTailInfo - { _aborted :: Bool - -- ^ Whether any fragment of the packet was aborted - , _newIdx :: Index dataWidth - -- ^ The adjusted byte enable - , _transferDrop :: Index (delayCycles + 1) - -- ^ The amount of transfers to drop from the tail - , _wait :: Bool - -- ^ Iff true, apply changes to transfer the next clock cycle instead - } - -{- | -Transmits information about a single packet upon seeing its last transfer. --} -transmitDropInfoC :: - forall (dom :: Domain) (n :: Nat) (dataWidth :: Nat) (delayCycles :: Nat) (meta :: Type). - (KnownDomain dom) => - (KnownNat dataWidth) => - (KnownNat delayCycles) => - (1 <= dataWidth) => - (1 <= n) => - (HiddenClockResetEnable dom) => - SNat n -> - Circuit - (PacketStream dom dataWidth meta) - (Df.Df dom (DropTailInfo dataWidth delayCycles)) -transmitDropInfoC SNat = forceResetSanity |> fromSignals (mealyB go False) - where - go aborted (Nothing, Ack readyIn) = (aborted, (PacketStreamS2M readyIn, Df.NoData)) - go aborted (Just PacketStreamM2S{..}, Ack readyIn) = (nextAborted, (PacketStreamS2M readyIn, fwdOut)) - where - x = natToNum @(n `Mod` dataWidth) - abortOut = aborted || _abort - - (nextAborted, fwdOut) = case _last of - Nothing -> (aborted || _abort, Df.NoData) - Just i -> - ( False - , Df.Data - DropTailInfo - { _aborted = abortOut - , _newIdx = satSub SatWrap i x - , _transferDrop = case compareSNat (SNat @dataWidth) (SNat @n) of - SNatLE -> case sameNat d0 (SNat @(n `Mod` dataWidth)) of - Just Refl -> natToNum @(n `Div` dataWidth) - _ -> - if (resize i :: Index n) < natToNum @(n - dataWidth) - then natToNum @(n `DivRU` dataWidth) - else natToNum @(n `Div` dataWidth) - SNatGT -> if i >= natToNum @n then 0 else 1 - , _wait = case compareSNat (SNat @dataWidth) (SNat @n) of - SNatLE -> case sameNat d0 (SNat @(n `Mod` dataWidth)) of - Just Refl -> False - _ -> (resize i :: Index n) >= natToNum @(n - dataWidth) - SNatGT -> i >= natToNum @n - } - ) - -{- | -Removes the last @n@ bytes from each packet in a @PacketStream@. -If any dropped transfers had @_abort@ set, this will be preserved by -setting the @_abort@ of an earlier transfer that is not dropped. - -__NB__: assumes that packets are at least @n@ bytes long. If this is -not the case, this component will break. --} -dropTailC :: - forall (dom :: Domain) (n :: Nat) (dataWidth :: Nat) (meta :: Type). - (KnownDomain dom) => - (KnownNat dataWidth) => - (1 <= dataWidth) => - (1 <= n) => - (NFDataX meta) => - (HiddenClockResetEnable dom) => - SNat n -> - Circuit - (PacketStream dom dataWidth meta) - (PacketStream dom dataWidth meta) -dropTailC SNat = case compareSNat d1 (SNat @(n `DivRU` dataWidth)) of - SNatLE -> - forceResetSanity - |> circuit - ( \stream -> do - [s1, s2] <- DfConv.fanout Proxy Proxy -< stream - delayed <- delayStream (SNat @(n `DivRU` dataWidth)) -< s1 - info <- transmitDropInfoC @dom @n @dataWidth @(n `DivRU` dataWidth) (SNat @n) -< s2 - dropTailC' @dom @dataWidth @(n `DivRU` dataWidth) @meta -< (delayed, info) - ) - _ -> - clashCompileError - "dropTailC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" From 3ed90f2baf3fc350222f176518439352fec31734 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 19 Aug 2024 14:17:13 +0200 Subject: [PATCH 26/63] Optimize depacketizer --- .../src/Protocols/PacketStream/Base.hs | 1 + .../Protocols/PacketStream/Depacketizers.hs | 191 +++++++++--------- 2 files changed, 95 insertions(+), 97 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index e0c206e4..4b8cbcb0 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -16,6 +16,7 @@ module Protocols.PacketStream.Base ( filterMeta, mapMetaS, mapMeta, + zeroOutInvalidBytesC, ) where import Clash.Prelude hiding (sample) diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 63c54cdd..93b59abe 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE UndecidableInstances #-} @@ -20,6 +19,7 @@ import Protocols import qualified Protocols.Df as Df import Protocols.PacketStream.Base +import Data.Data ((:~:) (Refl)) import Data.Maybe defaultByte :: BitVector 8 @@ -27,12 +27,12 @@ defaultByte = 0x00 -- Since the header might be unaligned compared to the datawidth -- we need to store a partial fragment when forwarding. --- The fragment we need to store depends on our "unalignedness". +-- The number of bytes we need to store depends on our "unalignedness". -- -- Ex. We parse a header of 17 bytes and our @dataWidth@ is 4 bytes. -- That means at the end of the header we can have upto 3 bytes left -- in the fragment which we may need to forward. -type DeForwardBufSize (headerBytes :: Nat) (dataWidth :: Nat) = +type ForwardBufSize (headerBytes :: Nat) (dataWidth :: Nat) = (dataWidth - (headerBytes `Mod` dataWidth)) `Mod` dataWidth type DepacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = @@ -42,25 +42,29 @@ type DepacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = , KnownNat headerBytes ) +-- TODO remove _fwdBuf and just use the last ForwardBufSize bytes of _parseBuf instead +-- See https://github.com/clash-lang/clash-protocols/issues/105 data DepacketizerState (headerBytes :: Nat) (dataWidth :: Nat) = Parse { _aborted :: Bool + -- ^ Whether the packet is aborted. We need this, because _abort might + -- have been set in the bytes to be parsed. , _parseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - , _fwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + -- ^ Parse buffer. + , _fwdBuf :: Vec (ForwardBufSize headerBytes dataWidth) (BitVector 8) + -- ^ Buffer containing data bytes that could not be sent immediately + -- due to misalignment of @dataWidth@ and @headerBytes@. , _counter :: Index (headerBytes `DivRU` dataWidth) + -- ^ @maxBound + 1@ is the number of fragments we need to parse. } | Forward { _aborted :: Bool , _parseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - , _fwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) + , _fwdBuf :: Vec (ForwardBufSize headerBytes dataWidth) (BitVector 8) , _counter :: Index (headerBytes `DivRU` dataWidth) - } - | LastForward - { _aborted :: Bool - , _parseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - , _fwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - , _counter :: Index (headerBytes `DivRU` dataWidth) - , _lastIdx :: Index dataWidth + , _lastFwd :: Bool + -- ^ True iff we have seen @_last@ set but the number of data bytes was too + -- big to send immediately along with our buffered bytes. } deriving (Show, ShowX, Generic) @@ -85,9 +89,9 @@ depacketizerT :: (metaOut :: Type). (BitSize header ~ headerBytes * 8) => (BitPack header) => - (NFDataX metaIn) => (DepacketizerCt headerBytes dataWidth) => - (DeForwardBufSize headerBytes dataWidth <= dataWidth) => + (NFDataX metaIn) => + (ForwardBufSize headerBytes dataWidth <= dataWidth) => (headerBytes <= dataWidth * headerBytes `DivRU` dataWidth) => (header -> metaIn -> metaOut) -> DepacketizerState headerBytes dataWidth -> @@ -95,93 +99,86 @@ depacketizerT :: ( DepacketizerState headerBytes dataWidth , (PacketStreamS2M, Maybe (PacketStreamM2S dataWidth metaOut)) ) -depacketizerT _ Parse{..} (Just PacketStreamM2S{..}, _) = (nextSt, (PacketStreamS2M outReady, Nothing)) +depacketizerT _ Parse{..} (Just PacketStreamM2S{..}, _) = (nextStOut, (PacketStreamS2M outReady, Nothing)) where nextAborted = _aborted || _abort - nextParseBuf = fst $ shiftInAtN _parseBuf _data - fwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - fwdBuf = dropLe (SNat @(dataWidth - DeForwardBufSize headerBytes dataWidth)) _data - - prematureEnd idx = - case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of - SNatGT -> idx < (natToNum @(headerBytes `Mod` dataWidth)) - _ -> True - nextCounter = pred _counter - - nextSt = - case (_counter == 0, _last) of - (False, Nothing) -> - Parse nextAborted nextParseBuf fwdBuf nextCounter - (done, Just idx) - | not done || prematureEnd idx -> - Parse False nextParseBuf fwdBuf maxBound - (True, Just idx) -> - LastForward - nextAborted - nextParseBuf - fwdBuf - nextCounter - (idx - natToNum @(headerBytes `Mod` dataWidth)) - (True, Nothing) -> - Forward nextAborted nextParseBuf fwdBuf nextCounter - _ -> - clashCompileError - "depacketizerT Parse: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + nextParseBuf = fst (shiftInAtN _parseBuf _data) + nextFwdBuf = dropLe (SNat @(dataWidth - ForwardBufSize headerBytes dataWidth)) _data + + prematureEnd idx = case sameNat d0 (SNat @(headerBytes `Mod` dataWidth)) of + Just Refl -> True + _ -> idx < natToNum @(headerBytes `Mod` dataWidth) + + -- Upon seeing _last being set, move back to the initial state if the + -- right amount of bytes were not parsed yet, or if they were, but there + -- were no data bytes after that. In any case, we pass the same buffers + -- for efficiency reasons, because their initial contents are undefined + -- anyway. + nextStOut = case (_counter == 0, _last) of + (False, Nothing) -> + Parse nextAborted nextParseBuf nextFwdBuf nextCounter + (False, Just _) -> + def + (True, Just idx) + | prematureEnd idx -> + def + (True, _) -> + Forward nextAborted nextParseBuf nextFwdBuf nextCounter (isJust _last) outReady - | LastForward{} <- nextSt = False + | Forward{_lastFwd = True} <- nextStOut = False | otherwise = True -depacketizerT _ st@Parse{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) depacketizerT toMetaOut st@Forward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = (nextStOut, (PacketStreamS2M outReady, Just outPkt)) where nextAborted = _aborted || _abort - dataOut :: Vec dataWidth (BitVector 8) - nextFwdBuf :: Vec (DeForwardBufSize headerBytes dataWidth) (BitVector 8) - (dataOut, nextFwdBuf) = splitAt (SNat @dataWidth) $ _fwdBuf ++ _data + newLast = adjustLast <$> _last + (dataOut, nextFwdBuf) = splitAt (SNat @dataWidth) (_fwdBuf ++ _data) + -- Only use if headerBytes `Mod` dataWidth > 0. adjustLast :: Index dataWidth -> Either (Index dataWidth) (Index dataWidth) - adjustLast idx = if outputNow then Left nowIdx else Right nextIdx + adjustLast idx = if idx < x then Left (idx + y) else Right (idx - x) where - outputNow = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of - SNatGT -> idx < natToNum @(headerBytes `Mod` dataWidth) - _ -> True - nowIdx = idx + natToNum @(DeForwardBufSize headerBytes dataWidth) - nextIdx = idx - natToNum @(headerBytes `Mod` dataWidth) - - newLast = fmap adjustLast _last - outPkt = - pkt - { _abort = nextAborted - , _data = dataOut - , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _parseBuf) _meta - , _last = either Just (const Nothing) =<< newLast - } + x = natToNum @(headerBytes `Mod` dataWidth) + y = natToNum @(ForwardBufSize headerBytes dataWidth) + + outPkt = case sameNat d0 (SNat @(headerBytes `Mod` dataWidth)) of + Just Refl -> + pkt + { _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _parseBuf) _meta + , _abort = nextAborted + } + Nothing -> + pkt + { _data = + if _lastFwd + then _fwdBuf ++ repeat @(dataWidth - ForwardBufSize headerBytes dataWidth) defaultByte + else dataOut + , _last = + if _lastFwd + then either Just Just =<< newLast + else either Just (const Nothing) =<< newLast + , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _parseBuf) _meta + , _abort = nextAborted + } + + nextForwardSt = Forward nextAborted _parseBuf nextFwdBuf maxBound + nextSt = case sameNat d0 (SNat @(headerBytes `Mod` dataWidth)) of + Just Refl + | isJust _last -> def + | otherwise -> nextForwardSt False + Nothing -> + case (_lastFwd, newLast) of + (False, Nothing) -> nextForwardSt False + (False, Just (Right _)) -> nextForwardSt True + _ -> def - nextSt = case newLast of - Nothing -> Forward nextAborted _parseBuf nextFwdBuf _counter - Just (Left _) -> Parse False _parseBuf nextFwdBuf maxBound - Just (Right idx) -> LastForward nextAborted _parseBuf nextFwdBuf _counter idx nextStOut = if _ready bwdIn then nextSt else st outReady - | LastForward{} <- nextSt = False + | Forward{_lastFwd = True} <- nextStOut = False | otherwise = _ready bwdIn -depacketizerT _ st@Forward{} (Nothing, bwdIn) = (st, (bwdIn, Nothing)) -depacketizerT toMetaOut st@LastForward{..} (fwdIn, bwdIn) = (nextStOut, (bwdIn, Just outPkt)) - where - -- We can only get in this state if the previous clock cycle we received a fwdIn - -- which was also the last fragment - inPkt = fromJustX fwdIn - outPkt = - PacketStreamM2S - { _abort = _aborted || _abort inPkt - , _data = - _fwdBuf ++ repeat @(dataWidth - DeForwardBufSize headerBytes dataWidth) defaultByte - , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _parseBuf) (_meta inPkt) - , _last = Just $ fromJustX (_last inPkt) - natToNum @(headerBytes `Mod` dataWidth) - } - nextStOut = if _ready bwdIn then Parse False _parseBuf _fwdBuf maxBound else st +depacketizerT _ st (Nothing, bwdIn) = (st, (bwdIn, Nothing)) -- | Reads bytes at the start of each packet into metadata. depacketizerC :: @@ -192,14 +189,15 @@ depacketizerC :: (metaOut :: Type) (header :: Type) (headerBytes :: Nat). - (HiddenClockResetEnable dom) => - (NFDataX metaOut) => - (NFDataX metaIn) => - (BitPack header) => - (BitSize header ~ headerBytes * 8) => - (KnownNat headerBytes) => - (1 <= dataWidth) => - (KnownNat dataWidth) => + ( HiddenClockResetEnable dom + , NFDataX metaOut + , NFDataX metaIn + , BitPack header + , BitSize header ~ headerBytes * 8 + , KnownNat headerBytes + , 1 <= dataWidth + , KnownNat dataWidth + ) => -- | Used to compute final metadata of outgoing packets from header and incoming metadata (header -> metaIn -> metaOut) -> Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) @@ -210,15 +208,14 @@ depacketizerC toMetaOut = forceResetSanity |> fromSignals outCircuit outCircuit = case (modProof, divProof) of - (SNatLE, SNatLE) -> case compareSNat (SNat @(DeForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of - SNatLE -> - mealyB (depacketizerT @headerBytes toMetaOut) def + (SNatLE, SNatLE) -> case compareSNat (SNat @(ForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of + SNatLE -> mealyB (depacketizerT @headerBytes toMetaOut) def _ -> clashCompileError - "depacketizerC1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + "depacketizer1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" _ -> clashCompileError - "depacketizerC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + "depacketizer0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" type DepacketizeToDfCt (headerBytes :: Nat) (dataWidth :: Nat) = ( 1 <= headerBytes `DivRU` dataWidth From 8715163a56561fe5d9477c555ca67ca19f00a089 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Tue, 20 Aug 2024 12:16:45 +0200 Subject: [PATCH 27/63] Add haxiom module --- clash-protocols/clash-protocols.cabal | 3 + .../src/Data/Constraint/Nat/Extra.hs | 35 +++++++ .../Protocols/PacketStream/Depacketizers.hs | 56 +++++------ .../src/Protocols/PacketStream/Packetizers.hs | 30 +++--- clash-protocols/tests/Tests/Haxioms.hs | 99 +++++++++++++++++++ clash-protocols/tests/unittests.hs | 7 +- 6 files changed, 175 insertions(+), 55 deletions(-) create mode 100644 clash-protocols/src/Data/Constraint/Nat/Extra.hs create mode 100644 clash-protocols/tests/Tests/Haxioms.hs diff --git a/clash-protocols/clash-protocols.cabal b/clash-protocols/clash-protocols.cabal index ef224de3..6271f0d1 100644 --- a/clash-protocols/clash-protocols.cabal +++ b/clash-protocols/clash-protocols.cabal @@ -117,6 +117,7 @@ library , clash-protocols-base , circuit-notation , clash-prelude-hedgehog + , constraints , data-default ^>= 0.7.1.1 , deepseq , extra @@ -176,6 +177,7 @@ library autogen-modules: Paths_clash_protocols other-modules: + Data.Constraint.Nat.Extra Data.Maybe.Extra Clash.Sized.Vector.Extra Paths_clash_protocols @@ -191,6 +193,7 @@ test-suite unittests ghc-options: -threaded -with-rtsopts=-N main-is: unittests.hs other-modules: + Tests.Haxioms Tests.Protocols Tests.Protocols.Df Tests.Protocols.DfConv diff --git a/clash-protocols/src/Data/Constraint/Nat/Extra.hs b/clash-protocols/src/Data/Constraint/Nat/Extra.hs new file mode 100644 index 00000000..77ae41ea --- /dev/null +++ b/clash-protocols/src/Data/Constraint/Nat/Extra.hs @@ -0,0 +1,35 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} + +{- +NOTE [constraint solver addition] + +The functions in this module enable us introduce trivial constraints that are not +solved by the constraint solver. +-} +module Data.Constraint.Nat.Extra where + +import Clash.Prelude +import Data.Constraint +import Unsafe.Coerce (unsafeCoerce) + +{- | Postulates that multiplying some number /a/ by some constant /b/, and +subsequently dividing that result by /b/ equals /a/. +-} +cancelMulDiv :: forall a b. (1 <= b) => Dict (DivRU (a * b) b ~ a) +cancelMulDiv = unsafeCoerce (Dict :: Dict (0 ~ 0)) + +-- | if (1 <= b) then (Mod a b + 1 <= b) +leModulusDivisor :: forall a b. 1 <= b => Dict (Mod a b + 1 <= b) +leModulusDivisor = unsafeCoerce (Dict :: Dict (0 <= 0)) + +-- | if (a <= 0) then (a ~ 0) +leZeroIsZero :: forall a. (a <= 0) => Dict (a ~ 0) +leZeroIsZero = unsafeCoerce (Dict :: Dict (0 ~ 0)) + +-- | if (1 <= a) and (1 <= b) then (1 <= DivRU a b) +strictlyPositiveDivRu :: forall a b. (1 <= a, 1 <= b) => Dict (1 <= DivRU a b) +strictlyPositiveDivRu = unsafeCoerce (Dict :: Dict (0 <= 0)) + +-- | if (1 <= a) then (b <= ceiling(b/a) * a) +timesDivRu :: forall a b. (1 <= a) => Dict (b <= Div (b + (a - 1)) a * a) +timesDivRu = unsafeCoerce (Dict :: Dict (0 <= 0)) diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 93b59abe..1c1ef3a2 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -1,7 +1,7 @@ -{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoImplicitPrelude #-} +{-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-} {-# OPTIONS_HADDOCK hide #-} {- | @@ -21,6 +21,8 @@ import Protocols.PacketStream.Base import Data.Data ((:~:) (Refl)) import Data.Maybe +import Data.Constraint (Dict(Dict)) +import Data.Constraint.Nat.Extra (timesDivRu, leModulusDivisor) defaultByte :: BitVector 8 defaultByte = 0x00 @@ -36,10 +38,12 @@ type ForwardBufSize (headerBytes :: Nat) (dataWidth :: Nat) = (dataWidth - (headerBytes `Mod` dataWidth)) `Mod` dataWidth type DepacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = - ( headerBytes `Mod` dataWidth <= dataWidth + ( KnownNat headerBytes , KnownNat dataWidth + , 1 <= headerBytes , 1 <= dataWidth - , KnownNat headerBytes + , headerBytes <= headerBytes `DivRU` dataWidth * dataWidth + , headerBytes `Mod` dataWidth <= dataWidth ) -- TODO remove _fwdBuf and just use the last ForwardBufSize bytes of _parseBuf instead @@ -92,7 +96,6 @@ depacketizerT :: (DepacketizerCt headerBytes dataWidth) => (NFDataX metaIn) => (ForwardBufSize headerBytes dataWidth <= dataWidth) => - (headerBytes <= dataWidth * headerBytes `DivRU` dataWidth) => (header -> metaIn -> metaOut) -> DepacketizerState headerBytes dataWidth -> (Maybe (PacketStreamM2S dataWidth metaIn), PacketStreamS2M) -> @@ -196,34 +199,29 @@ depacketizerC :: , BitSize header ~ headerBytes * 8 , KnownNat headerBytes , 1 <= dataWidth + , 1 <= headerBytes , KnownNat dataWidth ) => -- | Used to compute final metadata of outgoing packets from header and incoming metadata (header -> metaIn -> metaOut) -> Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) -depacketizerC toMetaOut = forceResetSanity |> fromSignals outCircuit +depacketizerC toMetaOut = forceResetSanity |> fromSignals ckt where - modProof = compareSNat (SNat @(headerBytes `Mod` dataWidth)) (SNat @dataWidth) - divProof = compareSNat (SNat @headerBytes) (SNat @(dataWidth * headerBytes `DivRU` dataWidth)) - - outCircuit = - case (modProof, divProof) of - (SNatLE, SNatLE) -> case compareSNat (SNat @(ForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of - SNatLE -> mealyB (depacketizerT @headerBytes toMetaOut) def - _ -> - clashCompileError - "depacketizer1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + ckt = case ( timesDivRu @dataWidth @headerBytes + , leModulusDivisor @headerBytes @dataWidth + ) of + (Dict, Dict) -> case compareSNat (SNat @(ForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of + SNatLE -> mealyB (depacketizerT toMetaOut) def _ -> clashCompileError - "depacketizer0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + "depacketizer1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" type DepacketizeToDfCt (headerBytes :: Nat) (dataWidth :: Nat) = - ( 1 <= headerBytes `DivRU` dataWidth - , headerBytes `Mod` dataWidth <= dataWidth - , headerBytes <= dataWidth * headerBytes `DivRU` dataWidth + ( KnownNat headerBytes , KnownNat dataWidth + , 1 <= headerBytes , 1 <= dataWidth - , KnownNat headerBytes + , headerBytes <= headerBytes `DivRU` dataWidth * dataWidth ) data DfDepacketizerState (headerBytes :: Nat) (dataWidth :: Nat) @@ -334,23 +332,13 @@ depacketizeToDfC :: (BitPack header) => (KnownNat headerBytes) => (KnownNat dataWidth) => + (1 <= headerBytes) => (1 <= dataWidth) => (BitSize header ~ headerBytes * 8) => -- | function that transforms the given meta + parsed header to the output Df (header -> meta -> a) -> Circuit (PacketStream dom dataWidth meta) (Df dom a) -depacketizeToDfC toOut = forceResetSanity |> fromSignals outCircuit +depacketizeToDfC toOut = forceResetSanity |> fromSignals ckt where - divProof = compareSNat (SNat @headerBytes) (SNat @(dataWidth * headerBytes `DivRU` dataWidth)) - modProof = compareSNat (SNat @(headerBytes `Mod` dataWidth)) (SNat @dataWidth) - - outCircuit = - case (divProof, modProof) of - (SNatLE, SNatLE) -> case compareSNat d1 (SNat @(headerBytes `DivRU` dataWidth)) of - SNatLE -> mealyB (depacketizeToDfT toOut) def - _ -> - clashCompileError - "depacketizeToDfC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" - _ -> - clashCompileError - "depacketizeToDfC1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + ckt = case timesDivRu @dataWidth @headerBytes of + Dict -> mealyB (depacketizeToDfT toOut) def diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index 84d9ec3f..9b751231 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -1,6 +1,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoImplicitPrelude #-} +{-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-} {-# OPTIONS_HADDOCK hide #-} {- | @@ -18,9 +19,10 @@ import qualified Protocols.Df as Df import Protocols.PacketStream.Base import Clash.Sized.Vector.Extra (takeLe) -import Data.Data ((:~:) (Refl)) import Data.Maybe import Data.Maybe.Extra +import Data.Constraint.Nat.Extra (leModulusDivisor, strictlyPositiveDivRu, leZeroIsZero) +import Data.Constraint (Dict(Dict)) defaultByte :: BitVector 8 defaultByte = 0x00 @@ -313,19 +315,13 @@ packetizerC :: Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) packetizerC toMetaOut toHeader = fromSignals outCircuit where - outCircuit = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) (SNat @dataWidth) of - SNatLE -> case compareSNat (SNat @(headerBytes + 1)) (SNat @dataWidth) of + outCircuit = case leModulusDivisor @headerBytes @dataWidth of + Dict -> case compareSNat (SNat @(headerBytes + 1)) (SNat @dataWidth) of SNatLE -> mealyB (packetizerT1 @headerBytes toMetaOut toHeader) (Insert1 False) - SNatGT -> case sameNat (SNat @(headerBytes `Mod` dataWidth)) d0 of - Just Refl -> mealyB (packetizerT2 @headerBytes toMetaOut toHeader) LoadHeader2 - _ -> case compareSNat d1 (SNat @(headerBytes `Mod` dataWidth)) of - SNatLE -> mealyB (packetizerT3 @headerBytes toMetaOut toHeader) LoadHeader3 - SNatGT -> - clashCompileError - "packetizerC0: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" - _ -> - clashCompileError - "packetizerC1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + SNatGT -> case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of + SNatLE -> case leZeroIsZero @(headerBytes `Mod` dataWidth) of + Dict -> mealyB (packetizerT2 @headerBytes toMetaOut toHeader) LoadHeader2 + SNatGT -> mealyB (packetizerT3 @headerBytes toMetaOut toHeader) LoadHeader3 data DfPacketizerState (metaOut :: Type) (headerBytes :: Nat) (dataWidth :: Nat) = DfIdle @@ -404,14 +400,15 @@ packetizeFromDfC :: (BitSize header ~ headerBytes * 8) => (KnownNat headerBytes) => (KnownNat dataWidth) => + (1 <= headerBytes) => (1 <= dataWidth) => -- | Function that transforms the Df input to the output metadata. (a -> metaOut) -> -- | Function that transforms the Df input to the header that will be packetized. (a -> header) -> Circuit (Df dom a) (PacketStream dom dataWidth metaOut) -packetizeFromDfC toMetaOut toHeader = case compareSNat d1 (SNat @(headerBytes `DivRU` dataWidth)) of - SNatLE -> case compareSNat (SNat @headerBytes) (SNat @dataWidth) of +packetizeFromDfC toMetaOut toHeader = case strictlyPositiveDivRu @headerBytes @dataWidth of + Dict -> case compareSNat (SNat @headerBytes) (SNat @dataWidth) of -- We don't need a state machine in this case, as we are able to packetize -- the entire payload in one clock cycle. SNatLE -> Circuit (unbundle . fmap go . bundle) @@ -425,6 +422,3 @@ packetizeFromDfC toMetaOut toHeader = case compareSNat d1 (SNat @(headerBytes `D SNatGT -> natToNum @(headerBytes `Mod` dataWidth - 1) _ -> natToNum @(dataWidth - 1) SNatGT -> fromSignals (mealyB (packetizeFromDfT toMetaOut toHeader) DfIdle) - SNatGT -> - clashCompileError - "packetizeFromDfC: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" diff --git a/clash-protocols/tests/Tests/Haxioms.hs b/clash-protocols/tests/Tests/Haxioms.hs new file mode 100644 index 00000000..5820d6bc --- /dev/null +++ b/clash-protocols/tests/Tests/Haxioms.hs @@ -0,0 +1,99 @@ +{-# LANGUAGE NumericUnderscores #-} + +module Tests.Haxioms where + +import Prelude +import Numeric.Natural + +import Hedgehog +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range + +import Test.Tasty +import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) +import Test.Tasty.Hedgehog.Extra (testProperty) +import Test.Tasty.TH (testGroupGenerator) + +-- | Generate a 'Natural' greater than or equal to /n/. Can generate 'Natural's +-- up to /n+1000/. This should be enough, given that naturals in this module are +-- used in proofs. +genNatural :: Natural -> Gen Natural +genNatural min_ = Gen.integral (Range.linear min_ (1000 + min_)) + +-- | Like 'DivRU', but at term-level. +divRU :: Natural -> Natural -> Natural +divRU dividend divider = + case dividend `divMod` divider of + (n, 0) -> n + (n, _) -> n + 1 + +-- | Test whether the following equation holds: +-- +-- DivRU (a * b) b ~ a +-- +-- Given: +-- +-- 1 <= b +-- +-- Tests: 'Data.Constraint.Nat.Extra.cancelMulDiv'. +-- +prop_cancelMulDiv :: Property +prop_cancelMulDiv = property $ do + a <- forAll (genNatural 0) + b <- forAll (genNatural 1) + divRU (a * b) b === a + +-- | Test whether the following equation holds: +-- +-- Mod a b + 1 <= b +-- +-- Given: +-- +-- 1 <= b +-- +-- Tests: 'Data.Constraint.Nat.Extra.leModulusDivisor'. +-- +prop_leModulusDivisor :: Property +prop_leModulusDivisor = property $ do + a <- forAll (genNatural 0) + b <- forAll (genNatural 1) + assert (a `mod` b + 1 <= b) + +-- | Test whether the following equation holds: +-- +-- 1 <= DivRU a b +-- +-- Given: +-- +-- 1 <= a, 1 <= b +-- +-- Tests: 'Data.Constraint.Nat.Extra.strictlyPositiveDivRu'. +-- +prop_strictlyPositiveDivRu :: Property +prop_strictlyPositiveDivRu = property $ do + a <- forAll (genNatural 1) + b <- forAll (genNatural 1) + assert (1 <= divRU a b) + +-- | Test whether the following equation holds: +-- +-- b <= Div (b + (a - 1)) a * a +-- +-- Given: +-- +-- 1 <= a +-- +-- Tests: 'Data.Constraint.Nat.Extra.timesDivRU'. +-- +prop_timesDivRU :: Property +prop_timesDivRU = property $ do + a <- forAll (genNatural 1) + b <- forAll (genNatural 0) + assert (b <= (b + (a - 1) `div` a) * a) + +tests :: TestTree +tests = + localOption (mkTimeout 10_000_000 {- 10 seconds -}) $ + localOption + (HedgehogTestLimit (Just 100_000)) + $(testGroupGenerator) diff --git a/clash-protocols/tests/unittests.hs b/clash-protocols/tests/unittests.hs index 3bb89bbc..364ff467 100644 --- a/clash-protocols/tests/unittests.hs +++ b/clash-protocols/tests/unittests.hs @@ -1,12 +1,12 @@ module Main where import Control.Concurrent (setNumCapabilities) -import Control.Monad (join) import System.Environment (lookupEnv, setEnv) import Test.Tasty import Text.Read (readMaybe) import Prelude +import qualified Tests.Haxioms import qualified Tests.Protocols main :: IO () @@ -15,7 +15,7 @@ main = do setEnv "TASTY_NUM_THREADS" "2" -- Detect "THREADS" environment variable on CI - nThreads <- join . fmap readMaybe <$> lookupEnv "THREADS" + nThreads <- (readMaybe =<<) <$> lookupEnv "THREADS" case nThreads of Nothing -> pure () Just n -> do @@ -27,5 +27,6 @@ tests :: TestTree tests = testGroup "Tests" - [ Tests.Protocols.tests + [ Tests.Haxioms.tests + , Tests.Protocols.tests ] From a8314d30cc0245de5395eb9214e7fc7b974b73bf Mon Sep 17 00:00:00 2001 From: t-wallet Date: Tue, 20 Aug 2024 12:17:21 +0200 Subject: [PATCH 28/63] formatting --- .../src/Data/Constraint/Nat/Extra.hs | 2 +- .../Protocols/PacketStream/Depacketizers.hs | 4 +- .../src/Protocols/PacketStream/Packetizers.hs | 4 +- clash-protocols/tests/Tests/Haxioms.hs | 89 ++++++++++--------- 4 files changed, 50 insertions(+), 49 deletions(-) diff --git a/clash-protocols/src/Data/Constraint/Nat/Extra.hs b/clash-protocols/src/Data/Constraint/Nat/Extra.hs index 77ae41ea..9af80935 100644 --- a/clash-protocols/src/Data/Constraint/Nat/Extra.hs +++ b/clash-protocols/src/Data/Constraint/Nat/Extra.hs @@ -19,7 +19,7 @@ cancelMulDiv :: forall a b. (1 <= b) => Dict (DivRU (a * b) b ~ a) cancelMulDiv = unsafeCoerce (Dict :: Dict (0 ~ 0)) -- | if (1 <= b) then (Mod a b + 1 <= b) -leModulusDivisor :: forall a b. 1 <= b => Dict (Mod a b + 1 <= b) +leModulusDivisor :: forall a b. (1 <= b) => Dict (Mod a b + 1 <= b) leModulusDivisor = unsafeCoerce (Dict :: Dict (0 <= 0)) -- | if (a <= 0) then (a ~ 0) diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 1c1ef3a2..b45530e2 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -19,10 +19,10 @@ import Protocols import qualified Protocols.Df as Df import Protocols.PacketStream.Base +import Data.Constraint (Dict (Dict)) +import Data.Constraint.Nat.Extra (leModulusDivisor, timesDivRu) import Data.Data ((:~:) (Refl)) import Data.Maybe -import Data.Constraint (Dict(Dict)) -import Data.Constraint.Nat.Extra (timesDivRu, leModulusDivisor) defaultByte :: BitVector 8 defaultByte = 0x00 diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index 9b751231..5f25aad9 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -19,10 +19,10 @@ import qualified Protocols.Df as Df import Protocols.PacketStream.Base import Clash.Sized.Vector.Extra (takeLe) +import Data.Constraint (Dict (Dict)) +import Data.Constraint.Nat.Extra (leModulusDivisor, leZeroIsZero, strictlyPositiveDivRu) import Data.Maybe import Data.Maybe.Extra -import Data.Constraint.Nat.Extra (leModulusDivisor, strictlyPositiveDivRu, leZeroIsZero) -import Data.Constraint (Dict(Dict)) defaultByte :: BitVector 8 defaultByte = 0x00 diff --git a/clash-protocols/tests/Tests/Haxioms.hs b/clash-protocols/tests/Tests/Haxioms.hs index 5820d6bc..68188c61 100644 --- a/clash-protocols/tests/Tests/Haxioms.hs +++ b/clash-protocols/tests/Tests/Haxioms.hs @@ -2,8 +2,8 @@ module Tests.Haxioms where -import Prelude import Numeric.Natural +import Prelude import Hedgehog import qualified Hedgehog.Gen as Gen @@ -14,9 +14,10 @@ import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) --- | Generate a 'Natural' greater than or equal to /n/. Can generate 'Natural's --- up to /n+1000/. This should be enough, given that naturals in this module are --- used in proofs. +{- | Generate a 'Natural' greater than or equal to /n/. Can generate 'Natural's +up to /n+1000/. This should be enough, given that naturals in this module are +used in proofs. +-} genNatural :: Natural -> Gen Natural genNatural min_ = Gen.integral (Range.linear min_ (1000 + min_)) @@ -27,64 +28,64 @@ divRU dividend divider = (n, 0) -> n (n, _) -> n + 1 --- | Test whether the following equation holds: --- --- DivRU (a * b) b ~ a --- --- Given: --- --- 1 <= b --- --- Tests: 'Data.Constraint.Nat.Extra.cancelMulDiv'. --- +{- | Test whether the following equation holds: + + DivRU (a * b) b ~ a + +Given: + + 1 <= b + +Tests: 'Data.Constraint.Nat.Extra.cancelMulDiv'. +-} prop_cancelMulDiv :: Property prop_cancelMulDiv = property $ do a <- forAll (genNatural 0) b <- forAll (genNatural 1) divRU (a * b) b === a --- | Test whether the following equation holds: --- --- Mod a b + 1 <= b --- --- Given: --- --- 1 <= b --- --- Tests: 'Data.Constraint.Nat.Extra.leModulusDivisor'. --- +{- | Test whether the following equation holds: + + Mod a b + 1 <= b + +Given: + + 1 <= b + +Tests: 'Data.Constraint.Nat.Extra.leModulusDivisor'. +-} prop_leModulusDivisor :: Property prop_leModulusDivisor = property $ do a <- forAll (genNatural 0) b <- forAll (genNatural 1) assert (a `mod` b + 1 <= b) --- | Test whether the following equation holds: --- --- 1 <= DivRU a b --- --- Given: --- --- 1 <= a, 1 <= b --- --- Tests: 'Data.Constraint.Nat.Extra.strictlyPositiveDivRu'. --- +{- | Test whether the following equation holds: + + 1 <= DivRU a b + +Given: + + 1 <= a, 1 <= b + +Tests: 'Data.Constraint.Nat.Extra.strictlyPositiveDivRu'. +-} prop_strictlyPositiveDivRu :: Property prop_strictlyPositiveDivRu = property $ do a <- forAll (genNatural 1) b <- forAll (genNatural 1) assert (1 <= divRU a b) --- | Test whether the following equation holds: --- --- b <= Div (b + (a - 1)) a * a --- --- Given: --- --- 1 <= a --- --- Tests: 'Data.Constraint.Nat.Extra.timesDivRU'. --- +{- | Test whether the following equation holds: + + b <= Div (b + (a - 1)) a * a + +Given: + + 1 <= a + +Tests: 'Data.Constraint.Nat.Extra.timesDivRU'. +-} prop_timesDivRU :: Property prop_timesDivRU = property $ do a <- forAll (genNatural 1) From 8d9d148e645b8b242e65f641b83275ab8ae0697a Mon Sep 17 00:00:00 2001 From: t-wallet Date: Tue, 20 Aug 2024 15:44:27 +0200 Subject: [PATCH 29/63] Depacketizer: remove redundant buffer --- .../src/Data/Constraint/Nat/Extra.hs | 12 +- .../Protocols/PacketStream/Depacketizers.hs | 129 ++++++++++-------- clash-protocols/tests/Tests/Haxioms.hs | 26 +++- 3 files changed, 98 insertions(+), 69 deletions(-) diff --git a/clash-protocols/src/Data/Constraint/Nat/Extra.hs b/clash-protocols/src/Data/Constraint/Nat/Extra.hs index 9af80935..2344569e 100644 --- a/clash-protocols/src/Data/Constraint/Nat/Extra.hs +++ b/clash-protocols/src/Data/Constraint/Nat/Extra.hs @@ -23,13 +23,17 @@ leModulusDivisor :: forall a b. (1 <= b) => Dict (Mod a b + 1 <= b) leModulusDivisor = unsafeCoerce (Dict :: Dict (0 <= 0)) -- | if (a <= 0) then (a ~ 0) -leZeroIsZero :: forall a. (a <= 0) => Dict (a ~ 0) +leZeroIsZero :: forall (a :: Nat). (a <= 0) => Dict (a ~ 0) leZeroIsZero = unsafeCoerce (Dict :: Dict (0 ~ 0)) -- | if (1 <= a) and (1 <= b) then (1 <= DivRU a b) strictlyPositiveDivRu :: forall a b. (1 <= a, 1 <= b) => Dict (1 <= DivRU a b) strictlyPositiveDivRu = unsafeCoerce (Dict :: Dict (0 <= 0)) --- | if (1 <= a) then (b <= ceiling(b/a) * a) -timesDivRu :: forall a b. (1 <= a) => Dict (b <= Div (b + (a - 1)) a * a) -timesDivRu = unsafeCoerce (Dict :: Dict (0 <= 0)) +-- | if (1 <= a) then (b <= ceil(b/a) * a) +leTimesDivRu :: forall a b. (1 <= a) => Dict (b <= a * DivRU b a) +leTimesDivRu = unsafeCoerce (Dict :: Dict (0 <= 0)) + +-- | if (1 <= a) then (a * ceil(b/a) ~ b + Mod (a - Mod b a) a) +eqTimesDivRu :: forall a b. (1 <= a) => Dict (a * DivRU b a ~ b + Mod (a - Mod b a) a) +eqTimesDivRu = unsafeCoerce (Dict :: Dict (0 ~ 0)) diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index b45530e2..5953ea4a 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -20,51 +20,59 @@ import qualified Protocols.Df as Df import Protocols.PacketStream.Base import Data.Constraint (Dict (Dict)) -import Data.Constraint.Nat.Extra (leModulusDivisor, timesDivRu) +import Data.Constraint.Nat.Extra import Data.Data ((:~:) (Refl)) import Data.Maybe defaultByte :: BitVector 8 defaultByte = 0x00 --- Since the header might be unaligned compared to the datawidth --- we need to store a partial fragment when forwarding. --- The number of bytes we need to store depends on our "unalignedness". --- --- Ex. We parse a header of 17 bytes and our @dataWidth@ is 4 bytes. --- That means at the end of the header we can have upto 3 bytes left --- in the fragment which we may need to forward. -type ForwardBufSize (headerBytes :: Nat) (dataWidth :: Nat) = +{- | Vectors of this size are able to hold @headerBytes `DivRU` dataWidth@ + transfers of size @dataWidth@, which is bigger than or equal to @headerBytes@. +-} +type BufSize (headerBytes :: Nat) (dataWidth :: Nat) = + dataWidth * headerBytes `DivRU` dataWidth + +{- | Since the header might be unaligned compared to the datawidth + we need to store a partial fragment when forwarding. + The number of bytes we need to store depends on our "unalignedness". + + Ex. We parse a header of 17 bytes and our @dataWidth@ is 4 bytes. + That means at the end of the header we can have upto 3 bytes left + in the fragment which we may need to forward. +-} +type ForwardBytes (headerBytes :: Nat) (dataWidth :: Nat) = (dataWidth - (headerBytes `Mod` dataWidth)) `Mod` dataWidth +-- | Depacketizer constraints. type DepacketizerCt (headerBytes :: Nat) (dataWidth :: Nat) = ( KnownNat headerBytes , KnownNat dataWidth , 1 <= headerBytes , 1 <= dataWidth - , headerBytes <= headerBytes `DivRU` dataWidth * dataWidth + , BufSize headerBytes dataWidth ~ headerBytes + ForwardBytes headerBytes dataWidth , headerBytes `Mod` dataWidth <= dataWidth + , ForwardBytes headerBytes dataWidth <= dataWidth ) --- TODO remove _fwdBuf and just use the last ForwardBufSize bytes of _parseBuf instead --- See https://github.com/clash-lang/clash-protocols/issues/105 +{- | Depacketizer state. Either we are parsing a header, or we are forwarding + the rest of the packet along with the parsed header in its metadata. +-} data DepacketizerState (headerBytes :: Nat) (dataWidth :: Nat) = Parse { _aborted :: Bool -- ^ Whether the packet is aborted. We need this, because _abort might -- have been set in the bytes to be parsed. - , _parseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - -- ^ Parse buffer. - , _fwdBuf :: Vec (ForwardBufSize headerBytes dataWidth) (BitVector 8) - -- ^ Buffer containing data bytes that could not be sent immediately + , _buf :: Vec (BufSize headerBytes dataWidth) (BitVector 8) + -- ^ The first @headerBytes@ of this buffer are for the parsed header. + -- The bytes after that are data bytes that could not be sent immediately -- due to misalignment of @dataWidth@ and @headerBytes@. , _counter :: Index (headerBytes `DivRU` dataWidth) -- ^ @maxBound + 1@ is the number of fragments we need to parse. } | Forward { _aborted :: Bool - , _parseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - , _fwdBuf :: Vec (ForwardBufSize headerBytes dataWidth) (BitVector 8) + , _buf :: Vec (BufSize headerBytes dataWidth) (BitVector 8) , _counter :: Index (headerBytes `DivRU` dataWidth) , _lastFwd :: Bool -- ^ True iff we have seen @_last@ set but the number of data bytes was too @@ -76,26 +84,26 @@ deriving instance (DepacketizerCt headerBytes dataWidth) => NFDataX (DepacketizerState headerBytes dataWidth) --- | Initial state of @depacketizerT@ +-- | Initial state of @depacketizerT@. instance (DepacketizerCt headerBytes dataWidth) => Default (DepacketizerState headerBytes dataWidth) where def :: DepacketizerState headerBytes dataWidth - def = Parse False (repeat undefined) (repeat undefined) maxBound + def = Parse False (repeat undefined) maxBound +-- | Depacketizer state transition function. depacketizerT :: forall (headerBytes :: Nat) - (dataWidth :: Nat) (header :: Type) (metaIn :: Type) - (metaOut :: Type). - (BitSize header ~ headerBytes * 8) => + (metaOut :: Type) + (dataWidth :: Nat). (BitPack header) => - (DepacketizerCt headerBytes dataWidth) => + (BitSize header ~ headerBytes * 8) => (NFDataX metaIn) => - (ForwardBufSize headerBytes dataWidth <= dataWidth) => + (DepacketizerCt headerBytes dataWidth) => (header -> metaIn -> metaOut) -> DepacketizerState headerBytes dataWidth -> (Maybe (PacketStreamM2S dataWidth metaIn), PacketStreamS2M) -> @@ -106,8 +114,7 @@ depacketizerT _ Parse{..} (Just PacketStreamM2S{..}, _) = (nextStOut, (PacketStr where nextAborted = _aborted || _abort nextCounter = pred _counter - nextParseBuf = fst (shiftInAtN _parseBuf _data) - nextFwdBuf = dropLe (SNat @(dataWidth - ForwardBufSize headerBytes dataWidth)) _data + nextParseBuf = fst (shiftInAtN _buf _data) prematureEnd idx = case sameNat d0 (SNat @(headerBytes `Mod` dataWidth)) of Just Refl -> True @@ -115,19 +122,17 @@ depacketizerT _ Parse{..} (Just PacketStreamM2S{..}, _) = (nextStOut, (PacketStr -- Upon seeing _last being set, move back to the initial state if the -- right amount of bytes were not parsed yet, or if they were, but there - -- were no data bytes after that. In any case, we pass the same buffers - -- for efficiency reasons, because their initial contents are undefined - -- anyway. + -- were no data bytes after that. nextStOut = case (_counter == 0, _last) of (False, Nothing) -> - Parse nextAborted nextParseBuf nextFwdBuf nextCounter + Parse nextAborted nextParseBuf nextCounter (False, Just _) -> def (True, Just idx) | prematureEnd idx -> def (True, _) -> - Forward nextAborted nextParseBuf nextFwdBuf nextCounter (isJust _last) + Forward nextAborted nextParseBuf nextCounter (isJust _last) outReady | Forward{_lastFwd = True} <- nextStOut = False @@ -135,37 +140,39 @@ depacketizerT _ Parse{..} (Just PacketStreamM2S{..}, _) = (nextStOut, (PacketStr depacketizerT toMetaOut st@Forward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = (nextStOut, (PacketStreamS2M outReady, Just outPkt)) where nextAborted = _aborted || _abort + nextBuf = header ++ nextFwdBytes newLast = adjustLast <$> _last - (dataOut, nextFwdBuf) = splitAt (SNat @dataWidth) (_fwdBuf ++ _data) + (header, fwdBytes) = splitAt (SNat @headerBytes) _buf + (dataOut, nextFwdBytes) = splitAt (SNat @dataWidth) (fwdBytes ++ _data) -- Only use if headerBytes `Mod` dataWidth > 0. adjustLast :: Index dataWidth -> Either (Index dataWidth) (Index dataWidth) adjustLast idx = if idx < x then Left (idx + y) else Right (idx - x) where x = natToNum @(headerBytes `Mod` dataWidth) - y = natToNum @(ForwardBufSize headerBytes dataWidth) + y = natToNum @(ForwardBytes headerBytes dataWidth) outPkt = case sameNat d0 (SNat @(headerBytes `Mod` dataWidth)) of Just Refl -> pkt - { _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _parseBuf) _meta + { _meta = toMetaOut (bitCoerce header) _meta , _abort = nextAborted } Nothing -> pkt { _data = if _lastFwd - then _fwdBuf ++ repeat @(dataWidth - ForwardBufSize headerBytes dataWidth) defaultByte + then fwdBytes ++ repeat @(dataWidth - ForwardBytes headerBytes dataWidth) defaultByte else dataOut , _last = if _lastFwd then either Just Just =<< newLast else either Just (const Nothing) =<< newLast - , _meta = toMetaOut (bitCoerce $ takeLe (SNat @headerBytes) _parseBuf) _meta + , _meta = toMetaOut (bitCoerce header) _meta , _abort = nextAborted } - nextForwardSt = Forward nextAborted _parseBuf nextFwdBuf maxBound + nextForwardSt = Forward nextAborted nextBuf maxBound nextSt = case sameNat d0 (SNat @(headerBytes `Mod` dataWidth)) of Just Refl | isJust _last -> def @@ -183,38 +190,40 @@ depacketizerT toMetaOut st@Forward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = ( | otherwise = _ready bwdIn depacketizerT _ st (Nothing, bwdIn) = (st, (bwdIn, Nothing)) --- | Reads bytes at the start of each packet into metadata. +{- | +Reads bytes at the start of each packet into @_meta@. If a packet contains +less valid bytes than @headerBytes + 1@, it does not send out anything. + +If @dataWidth@ divides @headerBytes@, this component runs at full throughput. +Otherwise, it gives backpressure for one clock cycle per packet larger than +@headerBytes + 1@ valid bytes. +-} depacketizerC :: forall - (dom :: Domain) - (dataWidth :: Nat) + (headerBytes :: Nat) + (header :: Type) (metaIn :: Type) (metaOut :: Type) - (header :: Type) - (headerBytes :: Nat). - ( HiddenClockResetEnable dom - , NFDataX metaOut - , NFDataX metaIn - , BitPack header - , BitSize header ~ headerBytes * 8 - , KnownNat headerBytes - , 1 <= dataWidth - , 1 <= headerBytes - , KnownNat dataWidth - ) => + (dataWidth :: Nat) + (dom :: Domain). + (HiddenClockResetEnable dom) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => + (NFDataX metaIn) => + (KnownNat headerBytes) => + (KnownNat dataWidth) => + (1 <= headerBytes) => + (1 <= dataWidth) => -- | Used to compute final metadata of outgoing packets from header and incoming metadata (header -> metaIn -> metaOut) -> Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) depacketizerC toMetaOut = forceResetSanity |> fromSignals ckt where - ckt = case ( timesDivRu @dataWidth @headerBytes + ckt = case ( eqTimesDivRu @dataWidth @headerBytes , leModulusDivisor @headerBytes @dataWidth + , leModulusDivisor @(dataWidth - (headerBytes `Mod` dataWidth)) @dataWidth ) of - (Dict, Dict) -> case compareSNat (SNat @(ForwardBufSize headerBytes dataWidth)) (SNat @dataWidth) of - SNatLE -> mealyB (depacketizerT toMetaOut) def - _ -> - clashCompileError - "depacketizer1: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" + (Dict, Dict, Dict) -> mealyB (depacketizerT toMetaOut) def type DepacketizeToDfCt (headerBytes :: Nat) (dataWidth :: Nat) = ( KnownNat headerBytes @@ -340,5 +349,5 @@ depacketizeToDfC :: Circuit (PacketStream dom dataWidth meta) (Df dom a) depacketizeToDfC toOut = forceResetSanity |> fromSignals ckt where - ckt = case timesDivRu @dataWidth @headerBytes of + ckt = case leTimesDivRu @dataWidth @headerBytes of Dict -> mealyB (depacketizeToDfT toOut) def diff --git a/clash-protocols/tests/Tests/Haxioms.hs b/clash-protocols/tests/Tests/Haxioms.hs index 68188c61..93a18a18 100644 --- a/clash-protocols/tests/Tests/Haxioms.hs +++ b/clash-protocols/tests/Tests/Haxioms.hs @@ -78,19 +78,35 @@ prop_strictlyPositiveDivRu = property $ do {- | Test whether the following equation holds: - b <= Div (b + (a - 1)) a * a + b <= a * DivRU b a Given: 1 <= a -Tests: 'Data.Constraint.Nat.Extra.timesDivRU'. +Tests: 'Data.Constraint.Nat.Extra.leTimesDivRu'. -} -prop_timesDivRU :: Property -prop_timesDivRU = property $ do +prop_leTimesDivRu :: Property +prop_leTimesDivRu = property $ do a <- forAll (genNatural 1) b <- forAll (genNatural 0) - assert (b <= (b + (a - 1) `div` a) * a) + assert (b <= a * divRU b a) + +{- | Test whether the following equation holds: + + a * DivRU b a ~ b + Mod (a - Mod b a) a + +Given: + + 1 <= a + +Tests: 'Data.Constraint.Nat.Extra.eqTimesDivRu'. +-} +prop_eqTimesDivRu :: Property +prop_eqTimesDivRu = property $ do + a <- forAll (genNatural 1) + b <- forAll (genNatural 0) + a * (b `divRU` a) === b + (a - b `mod` a) `mod` a tests :: TestTree tests = From 47188c519a1a239b186f5ebe68b7316ee03a1abf Mon Sep 17 00:00:00 2001 From: t-wallet Date: Wed, 21 Aug 2024 10:12:54 +0200 Subject: [PATCH 30/63] Optimize depacketizeToDfC --- .../Protocols/PacketStream/Depacketizers.hs | 85 ++++++++++--------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 5953ea4a..c6292b10 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -225,6 +225,7 @@ depacketizerC toMetaOut = forceResetSanity |> fromSignals ckt ) of (Dict, Dict, Dict) -> mealyB (depacketizerT toMetaOut) def +-- | Df depacketizer constraints. type DepacketizeToDfCt (headerBytes :: Nat) (dataWidth :: Nat) = ( KnownNat headerBytes , KnownNat dataWidth @@ -236,16 +237,16 @@ type DepacketizeToDfCt (headerBytes :: Nat) (dataWidth :: Nat) = data DfDepacketizerState (headerBytes :: Nat) (dataWidth :: Nat) = DfParse { _dfAborted :: Bool - -- ^ whether any of the fragments parsed from the current packet were aborted. - , _dfParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) - -- ^ the accumulator for header bytes. + -- ^ Whether the current packet is aborted. We need this, because _abort + -- might have been set in the bytes to be parsed. + , _dfParseBuf :: Vec (BufSize headerBytes dataWidth) (BitVector 8) + -- ^ Buffer for the header that we need to parse. , _dfCounter :: Index (headerBytes `DivRU` dataWidth) - -- ^ how many of the _parseBuf bytes are currently valid (accumulation count). We flush at counter == maxBound + -- ^ @maxBound + 1@ is the number of fragments we need to parse. } | DfConsumePadding { _dfAborted :: Bool - -- ^ whether any of the fragments parsed from the current packet were aborted. - , _dfParseBuf :: Vec (dataWidth * headerBytes `DivRU` dataWidth) (BitVector 8) + , _dfParseBuf :: Vec (BufSize headerBytes dataWidth) (BitVector 8) } deriving (Generic, Show, ShowX) @@ -253,7 +254,7 @@ deriving instance (DepacketizeToDfCt headerBytes dataWidth) => (NFDataX (DfDepacketizerState headerBytes dataWidth)) --- | Initial state of @depacketizeToDfT@ +-- | Initial state of @depacketizeToDfT@. instance (DepacketizeToDfCt headerBytes dataWidth) => Default (DfDepacketizerState headerBytes dataWidth) @@ -261,15 +262,14 @@ instance def :: DfDepacketizerState headerBytes dataWidth def = DfParse False (repeat undefined) maxBound +-- | Df depacketizer transition function. depacketizeToDfT :: forall - (dom :: Domain) - (dataWidth :: Nat) - (a :: Type) - (meta :: Type) + (headerBytes :: Nat) (header :: Type) - (headerBytes :: Nat). - (HiddenClockResetEnable dom) => + (meta :: Type) + (a :: Type) + (dataWidth :: Nat). (NFDataX meta) => (BitPack header) => (BitSize header ~ headerBytes * 8) => @@ -278,34 +278,28 @@ depacketizeToDfT :: DfDepacketizerState headerBytes dataWidth -> (Maybe (PacketStreamM2S dataWidth meta), Ack) -> (DfDepacketizerState headerBytes dataWidth, (PacketStreamS2M, Df.Data a)) -depacketizeToDfT toOut st@DfParse{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) +depacketizeToDfT _ DfParse{..} (Just (PacketStreamM2S{..}), _) = (nextStOut, (PacketStreamS2M readyOut, Df.NoData)) where nextAborted = _dfAborted || _abort nextParseBuf = fst (shiftInAtN _dfParseBuf _data) - outDf = toOut (bitCoerce (takeLe (SNat @headerBytes) nextParseBuf)) _meta prematureEnd idx = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of SNatGT -> idx < (natToNum @(headerBytes `Mod` dataWidth - 1)) _ -> idx < (natToNum @(dataWidth - 1)) - (nextSt, fwdOut) = + (nextStOut, readyOut) = case (_dfCounter == 0, _last) of (False, Nothing) -> - (DfParse nextAborted nextParseBuf (pred _dfCounter), Df.NoData) - (c, Just idx) - | not c || prematureEnd idx -> - (def, Df.NoData) - (True, Just _) -> - (def, if nextAborted then Df.NoData else Df.Data outDf) + (DfParse nextAborted nextParseBuf (pred _dfCounter), True) + (False, Just _) -> + (def, True) + (True, Just idx) -> + if nextAborted || prematureEnd idx + then (def, True) + else (DfConsumePadding nextAborted nextParseBuf, False) (True, Nothing) -> - (DfConsumePadding nextAborted nextParseBuf, Df.NoData) - _ -> - clashCompileError - "depacketizeToDfT Parse: Absurd, Report this to the Clash compiler team: https://github.com/clash-lang/clash-compiler/issues" - - readyOut = isNothing (Df.dataToMaybe fwdOut) || readyIn - nextStOut = if readyOut then nextSt else st + (DfConsumePadding nextAborted nextParseBuf, True) depacketizeToDfT toOut st@DfConsumePadding{..} (Just (PacketStreamM2S{..}), Ack readyIn) = (nextStOut, (PacketStreamS2M readyOut, fwdOut)) where nextAborted = _dfAborted || _abort @@ -318,23 +312,30 @@ depacketizeToDfT toOut st@DfConsumePadding{..} (Just (PacketStreamM2S{..}), Ack readyOut = isNothing (Df.dataToMaybe fwdOut) || readyIn nextStOut = if readyOut then nextSt else st -depacketizeToDfT _ st (Nothing, Ack ready) = (st, (PacketStreamS2M ready, Df.NoData)) - -{- | Reads bytes at the start of each packet into a dataflow. -Consumes the remainder of the packet and drops this. If a -packet ends sooner than the assumed length of the header, -`depacketizeToDfC` does not send out anything. -If any of the fragments in the packet has _abort set, it drops -the entire packet. +depacketizeToDfT _ st (Nothing, Ack readyIn) = (st, (PacketStreamS2M readyIn, Df.NoData)) + +{- | +Reads bytes at the start of each packet into a header structure, and +transforms this header structure along with the input metadata to an output +structure @a@, which is transmitted over a @Df@ channel. Such a structure +is sent once per packet over the @Df@ channel, but only if the packet was big +enough (contained at least @headerBytes@ valid data bytes) and did not +contain any transfer with @_abort@ set. +The remainder of the packet (padding) is dropped. + +If the packet is not padded, or if the packet is padded but the padding fits +in the last transfer of the packet together with valid data bytes, this +component will give one clock cycle of backpressure per packet (for efficiency +reasons). Otherwise, it runs at full throughput. -} depacketizeToDfC :: forall - (dom :: Domain) - (dataWidth :: Nat) - (a :: Type) - (meta :: Type) + (headerBytes :: Nat) (header :: Type) - (headerBytes :: Nat). + (meta :: Type) + (a :: Type) + (dataWidth :: Nat) + (dom :: Domain). (HiddenClockResetEnable dom) => (NFDataX meta) => (NFDataX a) => From cf67944fefa8eb71aa5dabeaa19783c6958c946b Mon Sep 17 00:00:00 2001 From: t-wallet Date: Wed, 21 Aug 2024 10:13:20 +0200 Subject: [PATCH 31/63] Formatting --- clash-protocols/src/Protocols/PacketStream/Depacketizers.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index c6292b10..b1431158 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -246,7 +246,7 @@ data DfDepacketizerState (headerBytes :: Nat) (dataWidth :: Nat) } | DfConsumePadding { _dfAborted :: Bool - , _dfParseBuf :: Vec (BufSize headerBytes dataWidth) (BitVector 8) + , _dfParseBuf :: Vec (BufSize headerBytes dataWidth) (BitVector 8) } deriving (Generic, Show, ShowX) From 9bae9d86e39d5b212aa83bea2a05074377021044 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Thu, 22 Aug 2024 14:13:37 +0200 Subject: [PATCH 32/63] Improve documentation --- .../src/Protocols/PacketStream/AsyncFifo.hs | 6 +- .../src/Protocols/PacketStream/Base.hs | 121 +++++++++++------- .../src/Protocols/PacketStream/Converters.hs | 16 ++- .../src/Protocols/PacketStream/Delay.hs | 1 + .../Protocols/PacketStream/Depacketizers.hs | 8 +- .../src/Protocols/PacketStream/PacketFifo.hs | 101 ++++++--------- .../src/Protocols/PacketStream/Packetizers.hs | 44 ++++--- .../src/Protocols/PacketStream/Routing.hs | 70 +++++----- .../Protocols/PacketStream/PacketFifo.hs | 14 +- 9 files changed, 193 insertions(+), 188 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs index 2fda483c..b4f80dd2 100644 --- a/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs @@ -23,14 +23,14 @@ asyncFifoC :: (rDom :: Domain) (depth :: Nat) (dataWidth :: Nat) - (metaType :: Type). + (meta :: Type). (KnownDomain wDom) => (KnownDomain rDom) => (KnownNat depth) => (KnownNat dataWidth) => (2 <= depth) => (1 <= dataWidth) => - (NFDataX metaType) => + (NFDataX meta) => -- | 2^depth is the number of elements this component can store SNat depth -> -- | Clock signal in the write domain @@ -45,7 +45,7 @@ asyncFifoC :: Reset rDom -> -- | Enable signal in the read domain Enable rDom -> - Circuit (PacketStream wDom dataWidth metaType) (PacketStream rDom dataWidth metaType) + Circuit (PacketStream wDom dataWidth meta) (PacketStream rDom dataWidth meta) asyncFifoC depth wClk wRst wEn rClk rRst rEn = exposeClockResetEnable forceResetSanity wClk wRst wEn |> fromSignals ckt where diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 4b8cbcb0..f78d7eaa 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -9,6 +9,7 @@ module Protocols.PacketStream.Base ( PacketStreamM2S (..), PacketStreamS2M (..), PacketStream, + abortOnBackPressureC, unsafeToPacketStream, fromPacketStream, forceResetSanity, @@ -32,7 +33,8 @@ import Data.Coerce (coerce) import qualified Data.Maybe as Maybe import Data.Proxy -{- | Data sent from manager to subordinate. +{- | +Data sent from manager to subordinate. Heavily inspired by the M2S data of AMBA AXI4-Stream, but simplified: @@ -45,7 +47,7 @@ Heavily inspired by the M2S data of AMBA AXI4-Stream, but simplified: - @_tuser@ is moved into `_meta`. - @_tvalid@ is modeled by wrapping this type into a @Maybe@. -} -data PacketStreamM2S (dataWidth :: Nat) (metaType :: Type) = PacketStreamM2S +data PacketStreamM2S (dataWidth :: Nat) (meta :: Type) = PacketStreamM2S { _data :: Vec dataWidth (BitVector 8) -- ^ The bytes to be transmitted. , _last :: Maybe (Index dataWidth) @@ -54,7 +56,7 @@ data PacketStreamM2S (dataWidth :: Nat) (metaType :: Type) = PacketStreamM2S -- If it is @Nothing@ then this transfer is not yet the end of a packet and all -- bytes are valid. This implies that no null bytes are allowed in the middle of -- a packet, only after a packet. - , _meta :: metaType + , _meta :: meta -- ^ Metadata of a packet. Must be constant during a packet. , _abort :: Bool -- ^ Iff true, the packet corresponding to this transfer is invalid. The subordinate @@ -62,7 +64,8 @@ data PacketStreamM2S (dataWidth :: Nat) (metaType :: Type) = PacketStreamM2S } deriving (Eq, Generic, ShowX, Show, NFData, Bundle, Functor) -{- | Data sent from the subordinate to manager. +{- | +Data sent from the subordinate to manager. The only information transmitted is whether the subordinate is ready to receive data. -} @@ -72,7 +75,8 @@ newtype PacketStreamS2M = PacketStreamS2M } deriving (Eq, Generic, ShowX, Show, NFData, Bundle, NFDataX) -{- | Simple valid-ready streaming protocol for transferring packets between components. +{- | +Simple valid-ready streaming protocol for transferring packets between components. Invariants: @@ -83,28 +87,28 @@ Invariants: 5. A packet may not be interrupted by another packet. 6. All bytes in `_data` which are not enabled must be 0x00. -} -data PacketStream (dom :: Domain) (dataWidth :: Nat) (metaType :: Type) +data PacketStream (dom :: Domain) (dataWidth :: Nat) (meta :: Type) deriving instance - (KnownNat dataWidth, NFDataX metaType) => - NFDataX (PacketStreamM2S dataWidth metaType) + (KnownNat dataWidth, NFDataX meta) => + NFDataX (PacketStreamM2S dataWidth meta) -instance Protocol (PacketStream dom dataWidth metaType) where +instance Protocol (PacketStream dom dataWidth meta) where type - Fwd (PacketStream dom dataWidth metaType) = - Signal dom (Maybe (PacketStreamM2S dataWidth metaType)) - type Bwd (PacketStream dom dataWidth metaType) = Signal dom PacketStreamS2M + Fwd (PacketStream dom dataWidth meta) = + Signal dom (Maybe (PacketStreamM2S dataWidth meta)) + type Bwd (PacketStream dom dataWidth meta) = Signal dom PacketStreamS2M -instance IdleCircuit (PacketStream dom dataWidth metaType) where +instance IdleCircuit (PacketStream dom dataWidth meta) where idleBwd _ = pure (PacketStreamS2M False) idleFwd _ = pure Nothing -instance Backpressure (PacketStream dom dataWidth metaType) where +instance Backpressure (PacketStream dom dataWidth meta) where boolsToBwd _ = fromList_lazy . fmap PacketStreamS2M -instance DfConv.DfConv (PacketStream dom dataWidth metaType) where - type Dom (PacketStream dom dataWidth metaType) = dom - type FwdPayload (PacketStream dom dataWidth metaType) = PacketStreamM2S dataWidth metaType +instance DfConv.DfConv (PacketStream dom dataWidth meta) where + type Dom (PacketStream dom dataWidth meta) = dom + type FwdPayload (PacketStream dom dataWidth meta) = PacketStreamM2S dataWidth meta toDfCircuit _ = fromSignals go where @@ -122,13 +126,13 @@ instance DfConv.DfConv (PacketStream dom dataWidth metaType) where instance (KnownDomain dom) => - Simulate (PacketStream dom dataWidth metaType) + Simulate (PacketStream dom dataWidth meta) where type - SimulateFwdType (PacketStream dom dataWidth metaType) = - [Maybe (PacketStreamM2S dataWidth metaType)] - type SimulateBwdType (PacketStream dom dataWidth metaType) = [PacketStreamS2M] - type SimulateChannels (PacketStream dom dataWidth metaType) = 1 + SimulateFwdType (PacketStream dom dataWidth meta) = + [Maybe (PacketStreamM2S dataWidth meta)] + type SimulateBwdType (PacketStream dom dataWidth meta) = [PacketStreamS2M] + type SimulateChannels (PacketStream dom dataWidth meta) = 1 simToSigFwd _ = fromList_lazy simToSigBwd _ = fromList_lazy @@ -141,11 +145,11 @@ instance instance (KnownDomain dom) => - Drivable (PacketStream dom dataWidth metaType) + Drivable (PacketStream dom dataWidth meta) where type - ExpectType (PacketStream dom dataWidth metaType) = - [PacketStreamM2S dataWidth metaType] + ExpectType (PacketStream dom dataWidth meta) = + [PacketStreamM2S dataWidth meta] toSimulateType Proxy = fmap Just fromSimulateType Proxy = Maybe.catMaybes @@ -159,14 +163,14 @@ instance instance ( KnownNat dataWidth - , NFDataX metaType - , NFData metaType - , ShowX metaType - , Show metaType - , Eq metaType + , NFDataX meta + , NFData meta + , ShowX meta + , Show meta + , Eq meta , KnownDomain dom ) => - Test (PacketStream dom dataWidth metaType) + Test (PacketStream dom dataWidth meta) where expectN Proxy options sampled = expectN (Proxy @(Df.Df dom _)) options @@ -175,27 +179,47 @@ instance -- | Circuit to convert a CSignal into a PacketStream. This is unsafe, because it drops backpressure. unsafeToPacketStream :: - Circuit (CSignal dom (Maybe (PacketStreamM2S n a))) (PacketStream dom n a) + forall dom dataWidth meta. + Circuit + (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) + (PacketStream dom dataWidth meta) unsafeToPacketStream = Circuit (\(fwdInS, _) -> (pure (), fwdInS)) -- | Converts a PacketStream into a CSignal. fromPacketStream :: - forall dom n meta. + forall dom dataWidth meta. + (HiddenClockResetEnable dom) => + Circuit + (PacketStream dom dataWidth meta) + (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) +fromPacketStream = forceResetSanity |> Circuit (\(fwdIn, _) -> (pure (PacketStreamS2M True), fwdIn)) + +-- | A circuit that sets `_abort` upon backpressure from the forward circuit. +abortOnBackPressureC :: + forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type). (HiddenClockResetEnable dom) => - Circuit (PacketStream dom n meta) (CSignal dom (Maybe (PacketStreamM2S n meta))) -fromPacketStream = forceResetSanity |> Circuit (\(inFwd, _) -> (pure (PacketStreamS2M True), inFwd)) + (KnownNat dataWidth) => + (NFDataX meta) => + Circuit + (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) + (PacketStream dom dataWidth meta) +abortOnBackPressureC = Circuit $ \(fwdInS, bwdInS) -> (pure (), go <$> bundle (fwdInS, bwdInS)) + where + go (fwdIn, bwdIn) = fmap (\pkt -> pkt{_abort = _abort pkt || not (_ready bwdIn)}) fwdIn -{- | Force a /nack/ on the backward channel and /Nothing/ on the forward +{- | +Force a /nack/ on the backward channel and /Nothing/ on the forward channel if reset is asserted. -} forceResetSanity :: - forall dom n meta. + forall dom dataWidth meta. (HiddenClockResetEnable dom) => - Circuit (PacketStream dom n meta) (PacketStream dom n meta) + Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) forceResetSanity = forceResetSanityGeneric -{- | Filter a packet stream based on its metadata, - with the predicate wrapped in a @Signal@. +{- | +Filter a packet stream based on its metadata, +with the predicate wrapped in a @Signal@. -} filterMetaS :: -- | Predicate which specifies whether to keep a fragment based on its metadata, @@ -216,13 +240,14 @@ filterMeta :: Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) filterMeta p = filterMetaS (pure p) -{- | Map a function on the metadata of a packet stream, - with the function wrapped in a @Signal@. +{- | +Map a function on the metadata of a packet stream, +with the function wrapped in a @Signal@. -} mapMetaS :: -- | Function to apply on the metadata, wrapped in a @Signal@ - Signal dom (a -> b) -> - Circuit (PacketStream dom dataWidth a) (PacketStream dom dataWidth b) + Signal dom (metaIn -> metaOut) -> + Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) mapMetaS fS = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, go <$> bundle (fwdIn, fS)) where go (inp, f) = (\inPkt -> inPkt{_meta = f (_meta inPkt)}) <$> inp @@ -230,13 +255,11 @@ mapMetaS fS = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, go <$> bundle (fwdIn, fS)) -- | Map a function on the metadata of a packet stream. mapMeta :: -- | Function to apply on the metadata - (a -> b) -> - Circuit (PacketStream dom dataWidth a) (PacketStream dom dataWidth b) + (metaIn -> metaOut) -> + Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) mapMeta f = mapMetaS (pure f) -{- | -Sets data bytes that are not enabled in a @PacketStream@ to @0x00@. --} +-- | Sets data bytes that are not enabled in a @PacketStream@ to @0x00@. zeroOutInvalidBytesC :: forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type). (KnownNat dataWidth) => diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index 1a0c75a2..ec4291f6 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -100,8 +100,12 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M } nextSt = if outReady then nextStRaw else st -{- | Converts packet streams of single bytes to packet streams of a higher data widths. -Has one cycle of latency, but optimal throughput. +{- | +Converts packet streams of arbitrary data width @dwIn@ to packet streams of +a bigger data width @dwOut@, where @dwIn@ must divide @dwOut@. When @dwIn ~ dwOut@, +this component is set to be `idC`. + +Has one cycle of latency, but full throughput. -} upConverterC :: forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). @@ -114,6 +118,7 @@ upConverterC :: (KnownNat n) => (dwOut ~ dwIn * n) => (NFDataX meta) => + -- | Upconverter circuit Circuit (PacketStream dom dwIn meta) (PacketStream dom dwOut meta) upConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of Just Refl -> idC @@ -178,13 +183,13 @@ downConverterT st@DownConverterState{..} (Just inPkt, bwdIn) = (nextSt, (PacketS | otherwise = st {- | Converts packet streams of arbitrary data width @dwIn@ to packet streams of -a smaller data width, @dwOut@, where @dwOut@ must divide @dwIn@. +a smaller data width @dwOut@, where @dwOut@ must divide @dwIn@. When @dwIn ~ dwOut@, +this component is set to be `idC`. If `_abort` is asserted on an input transfer, it will be asserted on all corresponding output transfers as well. -Provides zero latency and optimal throughput, i.e. a packet of n bytes is -sent out in n clock cycles, even if `_last` is set. +Provides zero latency and full throughput. -} downConverterC :: forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). @@ -195,6 +200,7 @@ downConverterC :: (KnownNat dwIn) => (KnownNat dwOut) => (dwIn ~ dwOut * n) => + -- | Downconverter circuit Circuit (PacketStream dom dwIn meta) (PacketStream dom dwOut meta) downConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of Just Refl -> idC diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs index d000d78f..06b2b941 100644 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -36,6 +36,7 @@ delayStream :: (1 <= n) => (1 <= dataWidth) => (NFDataX meta) => + -- | The number of fragments to delay SNat n -> Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) -- TODO this component is very unoptimized/ugly. The most important thing is diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index b1431158..d36bab18 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -191,8 +191,8 @@ depacketizerT toMetaOut st@Forward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = ( depacketizerT _ st (Nothing, bwdIn) = (st, (bwdIn, Nothing)) {- | -Reads bytes at the start of each packet into @_meta@. If a packet contains -less valid bytes than @headerBytes + 1@, it does not send out anything. +Reads bytes at the start of each packet into `_meta`. If a packet contains +less valid bytes than @headerBytes + 1@, it will silently drop that packet. If @dataWidth@ divides @headerBytes@, this component runs at full throughput. Otherwise, it gives backpressure for one clock cycle per packet larger than @@ -214,7 +214,7 @@ depacketizerC :: (KnownNat dataWidth) => (1 <= headerBytes) => (1 <= dataWidth) => - -- | Used to compute final metadata of outgoing packets from header and incoming metadata + -- | Mapping from the parsed header and input `_meta` to the output `_meta` (header -> metaIn -> metaOut) -> Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) depacketizerC toMetaOut = forceResetSanity |> fromSignals ckt @@ -345,7 +345,7 @@ depacketizeToDfC :: (1 <= headerBytes) => (1 <= dataWidth) => (BitSize header ~ headerBytes * 8) => - -- | function that transforms the given meta + parsed header to the output Df + -- | Mapping from the parsed header and input `_meta` to the `Df` output (header -> meta -> a) -> Circuit (PacketStream dom dataWidth meta) (Df dom a) depacketizeToDfC toOut = forceResetSanity |> fromSignals ckt diff --git a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs index 27694f01..29ee4475 100644 --- a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs @@ -7,51 +7,51 @@ Optimized Store and forward FIFO circuit for packet streams. -} module Protocols.PacketStream.PacketFifo ( packetFifoC, - overflowDropPacketFifoC, + FullMode (..), ) where import Clash.Prelude -import Protocols (CSignal, Circuit (..), fromSignals, (|>)) +import Protocols import Protocols.PacketStream.Base import Data.Maybe import Data.Maybe.Extra -type PacketStreamContent (dataWidth :: Nat) (metaType :: Type) = +type PacketStreamContent (dataWidth :: Nat) (meta :: Type) = (Vec dataWidth (BitVector 8), Maybe (Index dataWidth)) toPacketStreamContent :: - PacketStreamM2S dataWidth metaType -> PacketStreamContent dataWidth metaType + PacketStreamM2S dataWidth meta -> PacketStreamContent dataWidth meta toPacketStreamContent PacketStreamM2S{_data = d, _last = l, _meta = _, _abort = _} = (d, l) toPacketStreamM2S :: - PacketStreamContent dataWidth metaType -> metaType -> PacketStreamM2S dataWidth metaType + PacketStreamContent dataWidth meta -> meta -> PacketStreamM2S dataWidth meta toPacketStreamM2S (d, l) m = PacketStreamM2S d l m False packetFifoImpl :: forall (dom :: Domain) (dataWidth :: Nat) - (metaType :: Type) + (meta :: Type) (contentSizeBits :: Nat) (metaSizeBits :: Nat). (HiddenClockResetEnable dom) => (KnownNat dataWidth) => (1 <= contentSizeBits) => (1 <= metaSizeBits) => - (NFDataX metaType) => + (NFDataX meta) => -- | Depth of the content of the packet buffer, this is equal to 2^contentSizeBits SNat contentSizeBits -> -- | Depth of the content of the meta buffer, this is equal to 2^metaSizeBits SNat metaSizeBits -> -- | Input packetStream - ( Signal dom (Maybe (PacketStreamM2S dataWidth metaType)) + ( Signal dom (Maybe (PacketStreamM2S dataWidth meta)) , Signal dom PacketStreamS2M ) -> -- | Output CSignal s ( Signal dom PacketStreamS2M - , Signal dom (Maybe (PacketStreamM2S dataWidth metaType)) + , Signal dom (Maybe (PacketStreamM2S dataWidth meta)) ) packetFifoImpl SNat SNat (fwdIn, bwdIn) = (PacketStreamS2M . not <$> fullBuffer, fwdOut) where @@ -118,70 +118,47 @@ packetFifoImpl SNat SNat (fwdIn, bwdIn) = (PacketStreamS2M . not <$> fullBuffer, abortIn = maybe False _abort <$> fwdIn nextPacketIn = lastWordIn .&&. writeEnable --- | A circuit that sends an abort forward if there is backpressure from the forward circuit -abortOnBackPressure :: - forall (dom :: Domain) (dataWidth :: Nat) (metaType :: Type). - (HiddenClockResetEnable dom) => - (KnownNat dataWidth) => - (NFDataX metaType) => - ( Signal dom (Maybe (PacketStreamM2S dataWidth metaType)) - , Signal dom PacketStreamS2M - ) -> - -- | Does not give backpressure, sends an abort forward instead - ( Signal dom () - , Signal dom (Maybe (PacketStreamM2S dataWidth metaType)) - ) -abortOnBackPressure (fwdInS, bwdInS) = (pure (), go <$> bundle (fwdInS, bwdInS)) - where - go (fwdIn, bwdIn) = fmap (\pkt -> pkt{_abort = _abort pkt || not (_ready bwdIn)}) fwdIn +{- | +Packet buffer, a circuit which stores words in a buffer until the packet is complete. +Once a packet is complete it will send the entire packet out at once without stalls. +If a transfer in a packet has `_abort` set to true, the packetBuffer will drop the entire packet. -{- | Packet buffer, a circuit which stores words in a buffer until the packet is complete -once a packet is complete it will send the entire packet out at once without stalls. -If a word in a packet has `_abort` set to true, the packetBuffer will drop the entire packet. -If a packet is equal to or larger than 2^sizeBits-1, the packetBuffer will have a deadlock, this should be avoided! +__UNSAFE__: if `FullMode` is set to @Backpressure@ and a packet containing +@>= 2^contentSizeBits-1@ transfers is loaded into the FIFO, it will deadlock. -} packetFifoC :: forall (dom :: Domain) (dataWidth :: Nat) - (metaType :: Type) + (meta :: Type) (contentSizeBits :: Nat) (metaSizeBits :: Nat). (HiddenClockResetEnable dom) => (KnownNat dataWidth) => (1 <= contentSizeBits) => (1 <= metaSizeBits) => - (NFDataX metaType) => - -- | Depth of the content of the packet buffer, this is equal to 2^contentSizeBits + (NFDataX meta) => + -- | The FIFO can store @2^contentSizeBits@ transfers SNat contentSizeBits -> - -- | Depth for the meta of the packet buffer, this is equal to 2^metaSizeBits. - -- This can usually be smaller than contentSizeBits as for every packet we only need a single meta entry, while we usually have many words. + -- | The FIFO can store @2^metaSizeBits@ packets SNat metaSizeBits -> - Circuit (PacketStream dom dataWidth metaType) (PacketStream dom dataWidth metaType) -packetFifoC cSizeBits mSizeBits = forceResetSanity |> fromSignals (packetFifoImpl cSizeBits mSizeBits) - --- | A packet buffer that drops packets when it is full, instead of giving backpressure, see packetBufferC for more detailed explanation -overflowDropPacketFifoC :: - forall - (dom :: Domain) - (dataWidth :: Nat) - (metaType :: Type) - (contentSizeBits :: Nat) - (metaSizeBits :: Nat). - (HiddenClockResetEnable dom) => - (KnownNat dataWidth) => - (1 <= contentSizeBits) => - (1 <= metaSizeBits) => - (NFDataX metaType) => - SNat contentSizeBits -> - SNat metaSizeBits -> - Circuit - (CSignal dom (Maybe (PacketStreamM2S dataWidth metaType))) - (PacketStream dom dataWidth metaType) -overflowDropPacketFifoC cSizeBits mSizeBits = backPressureC |> packetFifoC cSizeBits mSizeBits - where - backPressureC :: - Circuit - (CSignal dom (Maybe (PacketStreamM2S dataWidth metaType))) - (PacketStream dom dataWidth metaType) - backPressureC = fromSignals abortOnBackPressure + -- | Specifies the behaviour of the FIFO when it is full + FullMode -> + Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) +packetFifoC cSizeBits mSizeBits mode = case mode of + Backpressure -> + forceResetSanity + |> fromSignals (packetFifoImpl cSizeBits mSizeBits) + Drop -> + fromPacketStream + |> abortOnBackPressureC + |> forceResetSanity + |> fromSignals (packetFifoImpl cSizeBits mSizeBits) + +-- | Specifies the behaviour of `packetFifoC` when it is full. +data FullMode + = -- | Assert backpressure when the FIFO is full. + Backpressure + | -- | Drop new packets when the FIFO is full. + -- The FIFO never asserts backpressure. + Drop diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index 5f25aad9..716f7e25 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -287,9 +287,11 @@ packetizerT3 toMetaOut _ st@LastForward3{..} (Just inPkt, bwdIn) = nextStOut = if _ready bwdIn then LoadHeader3 else st packetizerT3 _ _ s (Nothing, bwdIn) = (s, (bwdIn, Nothing)) -{- | Puts a portion of the metadata in front of the packet stream, and shifts the stream accordingly. - This portion is defined by the metadata to header transformer function. If this function is `id`, - the entire metadata is put in front of the packet stream. +{- | +Writes a portion of the metadata to the front of the packet stream, and shifts +the stream accordingly. This portion is defined by the @(metaIn -> header)@ +input function. If this function is `id`, the entire metadata is put in front +of the packet stream. -} packetizerC :: forall @@ -299,18 +301,17 @@ packetizerC :: (metaOut :: Type) (header :: Type) (headerBytes :: Nat). - ( HiddenClockResetEnable dom - , NFDataX metaOut - , BitPack header - , BitSize header ~ headerBytes * 8 - , KnownNat headerBytes - , 1 <= dataWidth - , 1 <= headerBytes - , KnownNat dataWidth - ) => - -- | Metadata transformer function + (HiddenClockResetEnable dom) => + (NFDataX metaOut) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + (1 <= headerBytes) => + (KnownNat dataWidth) => + -- | Mapping from input `_meta` to output `_meta` (metaIn -> metaOut) -> - -- | metaData to header that will be packetized transformer function + -- | Mapping from input `_meta` to the header that will be packetized (metaIn -> header) -> Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) packetizerC toMetaOut toHeader = fromSignals outCircuit @@ -351,9 +352,9 @@ packetizeFromDfT :: (1 <= dataWidth) => (1 <= headerBytes `DivRU` dataWidth) => ((dataWidth + 1) <= headerBytes) => - -- | function that transforms the Df input to the output metadata. + -- | Mapping from `Df` input to output `_meta` (a -> metaOut) -> - -- | function that transforms the Df input to the header that will be packetized. + -- | Mapping from `Df` input to the header that will be packetized (a -> header) -> DfPacketizerState metaOut headerBytes dataWidth -> (Df.Data a, PacketStreamS2M) -> @@ -382,9 +383,10 @@ packetizeFromDfT toMetaOut _ st@DfInsert{..} (Df.Data dataIn, bwdIn) = (nextStOu nextStOut = if _ready bwdIn then nextSt else st packetizeFromDfT _ _ s (Df.NoData, bwdIn) = (s, (Ack (_ready bwdIn), Nothing)) -{- | Starts a packet stream upon receiving some data. - The bytes to be packetized and the output metadata - are specified by the input functions. +{- | +Starts a packet stream upon receiving some data over a `Df` channel. +The bytes to be packetized and the output metadata are specified by the +input functions. -} packetizeFromDfC :: forall @@ -402,9 +404,9 @@ packetizeFromDfC :: (KnownNat dataWidth) => (1 <= headerBytes) => (1 <= dataWidth) => - -- | Function that transforms the Df input to the output metadata. + -- | Mapping from `Df` input to output `_meta` (a -> metaOut) -> - -- | Function that transforms the Df input to the header that will be packetized. + -- | Mapping from `Df` input to the header that will be packetized (a -> header) -> Circuit (Df dom a) (PacketStream dom dataWidth metaOut) packetizeFromDfC toMetaOut toHeader = case strictlyPositiveDivRu @headerBytes @dataWidth of diff --git a/clash-protocols/src/Protocols/PacketStream/Routing.hs b/clash-protocols/src/Protocols/PacketStream/Routing.hs index 4bf4b146..389ee0c8 100644 --- a/clash-protocols/src/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/src/Protocols/PacketStream/Routing.hs @@ -16,7 +16,6 @@ import Clash.Prelude import Protocols import Protocols.PacketStream.Base -import Data.Bifunctor (Bifunctor (second)) import qualified Data.Bifunctor as B import Data.Maybe @@ -28,22 +27,17 @@ data ArbiterMode -- biased towards the last source and also slightly more expensive. Parallel --- | Collects packets from all sources, respecting packet boundaries. +-- | Merges multiple packet streams into one, respecting packet boundaries. packetArbiterC :: - forall dom p n a. - ( HiddenClockResetEnable dom - , KnownNat p - , 1 <= p - ) => + forall dom p dataWidth meta. + (HiddenClockResetEnable dom) => + (KnownNat p) => + (1 <= p) => -- | See `ArbiterMode` ArbiterMode -> - Circuit (Vec p (PacketStream dom n a)) (PacketStream dom n a) + Circuit (Vec p (PacketStream dom dataWidth meta)) (PacketStream dom dataWidth meta) packetArbiterC mode = Circuit (B.first unbundle . mealyB go (maxBound, True) . B.first bundle) where - go :: - (Index p, Bool) -> - (Vec p (Maybe (PacketStreamM2S n a)), PacketStreamS2M) -> - ((Index p, Bool), (Vec p PacketStreamS2M, Maybe (PacketStreamM2S n a))) go (i, first) (fwds, bwd@(PacketStreamS2M ack)) = ((i', continue), (bwds, fwd)) where bwds = replace i bwd (repeat (PacketStreamS2M False)) @@ -57,37 +51,39 @@ packetArbiterC mode = Circuit (B.first unbundle . mealyB go (maxBound, True) . B (RoundRobin, _) -> satSucc SatWrap i -- next index (Parallel, _) -> fromMaybe maxBound $ fold @(p - 1) (<|>) (zipWith (<$) indicesI fwds) -- index of last sink with data -{- | Routes packets depending on their metadata, using given routing functions. +{- | +Routes packets depending on their metadata, using given routing functions. -Data is sent to at most one element of the output vector, for which the -dispatch function evaluates to true on the metadata of the input. If none of -the functions evaluate to true, the input is dropped. If more than one of the -predicates are true, the first one is picked. +Data is sent to at most one sink, for which the dispatch function evaluates to +@True@ when applied to the input metadata. If none of the predicates hold, the +input packet is dropped. If more than one of the predicates hold, the sink +that occcurs first in the vector is picked. -Sends out packets in the same clock cycle as they are received. +Sends out packets in the same clock cycle as they are received, this +component has zero latency and runs at full throughput. -} packetDispatcherC :: - forall (dom :: Domain) (p :: Nat) (n :: Nat) (a :: Type). - ( HiddenClockResetEnable dom - , KnownNat p - ) => - -- | Dispatch function. If function at index i returns true for the metaData it - -- dispatches the current packet to that sink. - Vec p (a -> Bool) -> - Circuit (PacketStream dom n a) (Vec p (PacketStream dom n a)) -packetDispatcherC fs = Circuit (second unbundle . unbundle . fmap go . bundle . second bundle) + forall (dom :: Domain) (p :: Nat) (dataWidth :: Nat) (meta :: Type). + (HiddenClockResetEnable dom) => + (KnownNat p) => + -- | Dispatch function + Vec p (meta -> Bool) -> + Circuit (PacketStream dom dataWidth meta) (Vec p (PacketStream dom dataWidth meta)) +packetDispatcherC predicates = Circuit (B.second unbundle . unbundle . fmap go . bundle . B.second bundle) where - go (Just x, bwds) = case findIndex id $ zipWith ($) fs (pure $ _meta x) of - Just i -> (bwds !! i, replace i (Just x) (repeat Nothing)) - _ -> (PacketStreamS2M True, repeat Nothing) - go _ = (PacketStreamS2M False, repeat Nothing) + idleOtp = repeat Nothing + go (Nothing, _) = (PacketStreamS2M False, idleOtp) + go (Just x, bwds) = case findIndex id (zipWith ($) predicates (pure $ _meta x)) of + Just i -> (bwds !! i, replace i (Just x) idleOtp) + Nothing -> (PacketStreamS2M True, idleOtp) -{- | Routing function for `packetDispatcherC` that matches against values with +{- | +Routing function for `packetDispatcherC` that matches against values with an `Eq` instance. Useful to route according to a record field. -} routeBy :: - (Eq b) => - (a -> b) -> - Vec p b -> - Vec p (a -> Bool) -routeBy f = fmap $ \x -> (== x) . f + (Eq a) => + (meta -> a) -> + Vec p a -> + Vec p (meta -> Bool) +routeBy f = map $ \x -> (== x) . f diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index a9008347..53e8022b 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -26,7 +26,7 @@ import Test.Tasty.TH (testGroupGenerator) import Protocols import Protocols.Hedgehog import Protocols.PacketStream.Base -import Protocols.PacketStream.PacketFifo (overflowDropPacketFifoC, packetFifoC) +import Protocols.PacketStream.PacketFifo -- tests import Tests.Protocols.PacketStream.Base as U @@ -51,7 +51,7 @@ prop_packetFifo_id = ckt :: (HiddenClockResetEnable System) => Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = packetFifoC d12 d12 + ckt = packetFifoC d12 d12 Backpressure -- | test for id with a small buffer to ensure backpressure is tested prop_packetFifo_small_buffer_id :: Property @@ -66,7 +66,7 @@ prop_packetFifo_small_buffer_id = ckt :: (HiddenClockResetEnable System) => Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = packetFifoC d5 d5 + ckt = packetFifoC d5 d5 Backpressure -- | test to check if there are no gaps inside of packets prop_packetFifo_no_gaps :: Property @@ -75,7 +75,7 @@ prop_packetFifo_no_gaps = property $ do maxInputSize = 50 ckt = exposeClockResetEnable - (packetFifoC packetFifoSize packetFifoSize) + (packetFifoC packetFifoSize packetFifoSize Backpressure) systemClockGen resetGen enableGen @@ -107,7 +107,7 @@ prop_overFlowDrop_packetFifo_id = ckt :: (HiddenClockResetEnable System) => Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = fromPacketStream |> overflowDropPacketFifoC d12 d12 + ckt = packetFifoC d12 d12 Drop -- | test for proper dropping when full prop_overFlowDrop_packetFifo_drop :: Property @@ -123,7 +123,7 @@ prop_overFlowDrop_packetFifo_drop = ckt :: (HiddenClockResetEnable System) => Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = fromPacketStream |> overflowDropPacketFifoC d5 d5 + ckt = packetFifoC d5 d5 Drop model :: [PacketStreamM2S 4 Int16] -> [PacketStreamM2S 4 Int16] model packets = Prelude.concat $ take 1 packetChunk ++ drop 2 packetChunk @@ -146,7 +146,7 @@ prop_packetFifo_small_metaBuffer = ckt :: (HiddenClockResetEnable System) => Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = packetFifoC d12 d2 + ckt = packetFifoC d12 d2 Backpressure tests :: TestTree tests = From 7d9df313495e9c072113014cbd16d1fb623e6007 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Tue, 27 Aug 2024 17:43:43 +0200 Subject: [PATCH 33/63] Export useful testing functions and generators via Protocols.PacketStream.Hedgehog --- clash-protocols/clash-protocols.cabal | 2 +- .../src/Protocols/PacketStream/Hedgehog.hs | 418 ++++++++++++++++++ .../Tests/Protocols/PacketStream/AsyncFifo.hs | 25 +- .../Tests/Protocols/PacketStream/Base.hs | 242 ---------- .../Protocols/PacketStream/Converters.hs | 74 ++-- .../Tests/Protocols/PacketStream/Delay.hs | 29 +- .../Protocols/PacketStream/Depacketizers.hs | 89 +--- .../Protocols/PacketStream/PacketFifo.hs | 64 ++- .../Protocols/PacketStream/Packetizers.hs | 80 +--- .../Tests/Protocols/PacketStream/Routing.hs | 85 ++-- 10 files changed, 538 insertions(+), 570 deletions(-) create mode 100644 clash-protocols/src/Protocols/PacketStream/Hedgehog.hs delete mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs diff --git a/clash-protocols/clash-protocols.cabal b/clash-protocols/clash-protocols.cabal index 6271f0d1..271b4129 100644 --- a/clash-protocols/clash-protocols.cabal +++ b/clash-protocols/clash-protocols.cabal @@ -152,6 +152,7 @@ library Protocols.PacketStream.Converters Protocols.PacketStream.Delay Protocols.PacketStream.Depacketizers + Protocols.PacketStream.Hedgehog Protocols.PacketStream.PacketFifo Protocols.PacketStream.Packetizers Protocols.PacketStream.Routing @@ -204,7 +205,6 @@ test-suite unittests Tests.Protocols.Wishbone Tests.Protocols.PacketStream Tests.Protocols.PacketStream.AsyncFifo - Tests.Protocols.PacketStream.Base Tests.Protocols.PacketStream.Converters Tests.Protocols.PacketStream.Delay Tests.Protocols.PacketStream.Depacketizers diff --git a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs new file mode 100644 index 00000000..3a5e2e68 --- /dev/null +++ b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs @@ -0,0 +1,418 @@ +{-# LANGUAGE RecordWildCards #-} + +{- | +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. + +Provides Hedgehog generators, models and utility functions for testing +`PacketStream` circuits. +-} +module Protocols.PacketStream.Hedgehog ( + -- * Utility functions + chopPacket, + chunkByPacket, + chunkToPacket, + fullPackets, + smearAbort, + + -- * Models + dropAbortedPackets, + downConvert, + upConvert, + depacketizerModel, + depacketizeToDfModel, + packetize, + packetizeFromDf, + + -- * Hedgehog generators + AbortMode (..), + genValidPacket, + genValidPackets, +) where + +import Prelude + +import Clash.Hedgehog.Sized.Vector (genVec) +import qualified Clash.Prelude as C +import qualified Clash.Sized.Vector as Vec + +import Hedgehog (Gen, Range) +import qualified Hedgehog.Gen as Gen + +import Protocols.PacketStream.Base + +import qualified Data.List as L +import Data.Maybe (fromJust, isJust) + +-- | Partition a list based on given function. +chunkBy :: (a -> Bool) -> [a] -> [[a]] +chunkBy _ [] = [] +chunkBy predicate list = L.filter (not . null) (chunkByHelper predicate list []) + +-- Helper function to accumulate chunks. +chunkByHelper :: (a -> Bool) -> [a] -> [a] -> [[a]] +chunkByHelper _ [] acc = [L.reverse acc] +chunkByHelper predicate (x : xs) acc + | predicate x = L.reverse (x : acc) : chunkByHelper predicate xs [] + | otherwise = chunkByHelper predicate xs (x : acc) + +-- | Partition a list of `PacketStream` transfers into complete packets. +chunkByPacket :: + [PacketStreamM2S dataWidth meta] -> + [[PacketStreamM2S dataWidth meta]] +chunkByPacket = chunkBy (isJust . _last) + +{- | +If a packet contains a transfer with `_abort` set, set the `_abort` of +all following transfers in the same packet. +-} +smearAbort :: + [PacketStreamM2S dataWidth meta] -> + [PacketStreamM2S dataWidth meta] +smearAbort [] = [] +smearAbort (x : xs) = L.reverse $ L.foldl' go [x] xs + where + go [] _ = [] + go l@(a : _) (PacketStreamM2S dat last' meta abort) = + PacketStreamM2S dat last' meta (_abort a || abort) : l + +-- | Partition a list into groups of given size +chopBy :: Int -> [a] -> [[a]] +chopBy _ [] = [] +chopBy n xs = as : chopBy n bs where (as, bs) = splitAt n xs + +{- | +Merge a list of `PacketStream` transfers with data width @1@ to +a single `PacketStream` transfer with data width @dataWidth@. +-} +chunkToPacket :: + (C.KnownNat dataWidth) => + [PacketStreamM2S 1 meta] -> + PacketStreamM2S dataWidth meta +chunkToPacket l = + PacketStreamM2S + { _last = + if isJust (_last lastTransfer) + then Just (fromIntegral $ L.length l - 1) + else Nothing + , _abort = any _abort l + , _meta = _meta lastTransfer + , _data = foldr ((C.+>>) . C.head . _data) (C.repeat 0) l + } + where + lastTransfer = L.last l + +{- | +Split a single `PacketStream` transfer with data width @dataWidth@ to +a list of `PacketStream` transfers with data width @1@. +-} +chopPacket :: + forall dataWidth meta. + (1 C.<= dataWidth) => + (C.KnownNat dataWidth) => + PacketStreamM2S dataWidth meta -> + [PacketStreamM2S 1 meta] +chopPacket PacketStreamM2S{..} = packets + where + lasts = case _last of + Nothing -> repeat Nothing + Just in' -> replicate (fromIntegral in') Nothing ++ [Just (0 :: C.Index 1)] + + datas = case _last of + Nothing -> C.toList _data + Just in' -> take (fromIntegral in' + 1) $ C.toList _data + + packets = + ( \(idx, dat) -> + PacketStreamM2S (pure dat) idx _meta _abort + ) + <$> zip lasts datas + +-- | Set `_last` of the last transfer in the list to @Just 0@ +fullPackets :: + (C.KnownNat dataWidth) => + [PacketStreamM2S dataWidth meta] -> + [PacketStreamM2S dataWidth meta] +fullPackets [] = [] +fullPackets fragments = + let lastFragment = (last fragments){_last = Just 0} + in init fragments ++ [lastFragment] + +-- | Drops packets if one of the transfers in the packet has `_abort` set. +dropAbortedPackets :: + [PacketStreamM2S dataWidth meta] -> + [PacketStreamM2S dataWidth meta] +dropAbortedPackets packets = concat $ filter (not . any _abort) (chunkByPacket packets) + +{- | +Splits a list of `PacketStream` transfers with data width @1@ into +a list of `PacketStream` transfers with data width @dataWidth@ +-} +downConvert :: + forall dataWidth meta. + (1 C.<= dataWidth) => + (C.KnownNat dataWidth) => + [PacketStreamM2S dataWidth meta] -> + [PacketStreamM2S 1 meta] +downConvert = concatMap chopPacket + +{- | +Merges a list of `PacketStream` transfers with data width @dataWidth into +a list of `PacketStream` transfers with data width @1@ +-} +upConvert :: + forall dataWidth meta. + (1 C.<= dataWidth) => + (C.KnownNat dataWidth) => + [PacketStreamM2S 1 meta] -> + [PacketStreamM2S dataWidth meta] +upConvert packets = + map + chunkToPacket + (chunkByPacket packets >>= chopBy (C.natToNum @dataWidth)) + +-- | Model of the generic `Protocols.PacketStream.depacketizerC`. +depacketizerModel :: + forall + (dataWidth :: C.Nat) + (headerBytes :: C.Nat) + (metaIn :: C.Type) + (header :: C.Type) + (metaOut :: C.Type). + (C.KnownNat dataWidth) => + (C.KnownNat headerBytes) => + (1 C.<= dataWidth) => + (1 C.<= headerBytes) => + (C.BitPack header) => + (C.BitSize header ~ headerBytes C.* 8) => + (header -> metaIn -> metaOut) -> + [PacketStreamM2S dataWidth metaIn] -> + [PacketStreamM2S dataWidth metaOut] +depacketizerModel toMetaOut ps = L.concat dataWidthPackets + where + hdrbytes = C.natToNum @headerBytes + + parseHdr :: + ([PacketStreamM2S 1 metaIn], [PacketStreamM2S 1 metaIn]) -> + [PacketStreamM2S 1 metaOut] + parseHdr (hdrF, fwdF) = fmap (\f -> f{_meta = metaOut}) fwdF + where + hdr = C.bitCoerce $ Vec.unsafeFromList @headerBytes $ _data <$> hdrF + metaOut = toMetaOut hdr (_meta $ L.head fwdF) + + bytePackets :: [[PacketStreamM2S 1 metaIn]] + bytePackets = + L.filter (\fs -> L.length fs > hdrbytes) $ + L.concatMap chopPacket . smearAbort <$> chunkByPacket ps + + parsedPackets :: [[PacketStreamM2S 1 metaOut]] + parsedPackets = parseHdr . L.splitAt hdrbytes <$> bytePackets + + dataWidthPackets :: [[PacketStreamM2S dataWidth metaOut]] + dataWidthPackets = L.map upConvert parsedPackets + +-- | Model of the generic `Protocols.PacketStream.depacketizeToDfC`. +depacketizeToDfModel :: + forall + (dataWidth :: C.Nat) + (headerBytes :: C.Nat) + (a :: C.Type) + (header :: C.Type) + (metaIn :: C.Type). + (C.KnownNat dataWidth) => + (C.KnownNat headerBytes) => + (1 C.<= dataWidth) => + (1 C.<= headerBytes) => + (C.BitPack header) => + (C.BitSize header ~ headerBytes C.* 8) => + (header -> metaIn -> a) -> + [PacketStreamM2S dataWidth metaIn] -> + [a] +depacketizeToDfModel toOut ps = L.map parseHdr bytePackets + where + parseHdr :: [PacketStreamM2S 1 metaIn] -> a + parseHdr hdrF = + toOut + (C.bitCoerce $ Vec.unsafeFromList $ _data <$> hdrF) + (_meta $ L.head hdrF) + + bytePackets :: [[PacketStreamM2S 1 metaIn]] + bytePackets = + L.filter + (\pkt -> L.length pkt >= C.natToNum @headerBytes) + (chunkByPacket $ downConvert (dropAbortedPackets ps)) + +-- | Model of the generic `Protocols.PacketStream.packetizerC`. +packetize :: + forall + (dataWidth :: C.Nat) + (headerBytes :: C.Nat) + (metaIn :: C.Type) + (header :: C.Type) + (metaOut :: C.Type). + (C.KnownNat dataWidth) => + (C.KnownNat headerBytes) => + (1 C.<= dataWidth) => + (1 C.<= headerBytes) => + (C.BitPack header) => + (C.BitSize header ~ headerBytes C.* 8) => + (metaIn -> metaOut) -> + (metaIn -> header) -> + [PacketStreamM2S dataWidth metaIn] -> + [PacketStreamM2S dataWidth metaOut] +packetize toMetaOut toHeader ps = L.concatMap (upConvert . prependHdr) bytePackets + where + prependHdr :: [PacketStreamM2S 1 metaIn] -> [PacketStreamM2S 1 metaOut] + prependHdr fragments = hdr L.++ L.map (\f -> f{_meta = metaOut}) fragments + where + h = L.head fragments + metaOut = toMetaOut (_meta h) + hdr = L.map go (C.toList $ C.bitCoerce (toHeader (_meta h))) + go byte = PacketStreamM2S (C.singleton byte) Nothing metaOut (_abort h) + + bytePackets :: [[PacketStreamM2S 1 metaIn]] + bytePackets = downConvert . smearAbort <$> chunkByPacket ps + +-- | Model of the generic `Protocols.PacketStream.packetizeFromDfC`. +packetizeFromDf :: + forall + (dataWidth :: C.Nat) + (headerBytes :: C.Nat) + (a :: C.Type) + (header :: C.Type) + (metaOut :: C.Type). + (C.KnownNat dataWidth) => + (C.KnownNat headerBytes) => + (1 C.<= dataWidth) => + (1 C.<= headerBytes) => + (C.BitPack header) => + (C.BitSize header ~ headerBytes C.* 8) => + (a -> metaOut) -> + (a -> header) -> + [a] -> + [PacketStreamM2S dataWidth metaOut] +packetizeFromDf toMetaOut toHeader = L.concatMap (upConvert . dfToPacket) + where + dfToPacket :: a -> [PacketStreamM2S 1 metaOut] + dfToPacket d = + fullPackets $ + L.map + (\byte -> PacketStreamM2S (C.singleton byte) Nothing (toMetaOut d) False) + (C.toList $ C.bitCoerce (toHeader d)) + +{- | +If set to @NoAbort@, packets will never contain a transfer with _abort set. +Otherwise, transfers of roughly 50% of the packets will randomly have _abort set. +-} +data AbortMode = Abort | NoAbort + +{- | +Generate valid packets, i.e. packets of which all transfers carry the same +`_meta` and with all unenabled bytes in `_data` set to 0x00. +-} +genValidPackets :: + forall (dataWidth :: C.Nat) (metaType :: C.Type). + (1 C.<= dataWidth) => + (C.KnownNat dataWidth) => + (C.BitPack metaType) => + -- | The amount of packets to generate. + Range Int -> + -- | The amount of transfers with @_last = Nothing@ to generate per packet. + -- This function will always generate an extra transfer per packet + -- with @_last = Just i@. + Range Int -> + -- | If set to @NoAbort@, no generated packets will have `_abort` set in + -- any of their transfers. Else, roughly 50% of packets will contain + -- fragments with their `_abort` randomly set. + AbortMode -> + Gen [PacketStreamM2S dataWidth metaType] +genValidPackets pkts size Abort = concat <$> Gen.list pkts gen + where + gen = do + abortPacket <- Gen.bool + genValidPacket size (if abortPacket then Abort else NoAbort) +genValidPackets pkts size NoAbort = + concat <$> Gen.list pkts (genValidPacket size NoAbort) + +{- | +Generate a valid packet, i.e. a packet of which all transfers carry the same +`_meta` and with all unenabled bytes in `_data` set to 0x00. +-} +genValidPacket :: + forall (dataWidth :: C.Nat) (metaType :: C.Type). + (1 C.<= dataWidth) => + (C.KnownNat dataWidth) => + (C.BitPack metaType) => + -- | The amount of transfers with @_last = Nothing@ to generate. + -- This function will always generate an extra transfer with @_last = Just i@. + Range Int -> + -- | If set to @NoAbort@, no transfers in the packet will have @_abort@ set. + -- Else, they will randomly have @_abort@ set. + AbortMode -> + Gen [PacketStreamM2S dataWidth metaType] +genValidPacket size abortMode = do + meta <- C.unpack <$> Gen.enumBounded + transfers <- Gen.list size (genTransfer @dataWidth meta abortMode) + lastTransfer <- genLastTransfer @dataWidth meta abortMode + pure (transfers ++ [lastTransfer]) + +-- | Generate a single transfer which is not yet the end of a packet. +genTransfer :: + forall (dataWidth :: C.Nat) (metaType :: C.Type). + (1 C.<= dataWidth) => + (C.KnownNat dataWidth) => + -- | We need to use the same metadata + -- for every transfer in a packet to satisfy the protocol + -- invariant that metadata is constant for an entire packet. + metaType -> + -- | If set to @NoAbort@, hardcode @_abort@ to @False@. Else, + -- randomly generate it. + AbortMode -> + Gen (PacketStreamM2S dataWidth metaType) +genTransfer meta abortMode = + PacketStreamM2S + <$> genVec Gen.enumBounded + <*> Gen.constant Nothing + <*> Gen.constant meta + <*> case abortMode of + Abort -> Gen.enumBounded + NoAbort -> Gen.constant False + +{- | +Generate the last transfer of a packet, i.e. a transfer with @_last@ set as @Just@. +All bytes which are not enabled are set to 0x00. +-} +genLastTransfer :: + forall (dataWidth :: C.Nat) (metaType :: C.Type). + (1 C.<= dataWidth) => + (C.KnownNat dataWidth) => + -- | We need to use the same metadata + -- for every transfer in a packet to satisfy the protocol + -- invariant that metadata is constant for an entire packet. + metaType -> + -- | If set to @NoAbort@, hardcode @_abort@ to @False@. Else, + -- randomly generate it. + AbortMode -> + Gen (PacketStreamM2S dataWidth metaType) +genLastTransfer meta abortMode = + setNull + <$> ( PacketStreamM2S + <$> genVec Gen.enumBounded + <*> (Just <$> Gen.enumBounded) + <*> Gen.constant meta + <*> case abortMode of + Abort -> Gen.enumBounded + NoAbort -> Gen.constant False + ) + where + setNull transfer = + let i = fromJust (_last transfer) + in transfer + { _data = + fromJust + ( Vec.fromList $ + take (1 + fromIntegral i) (C.toList (_data transfer)) + ++ replicate ((C.natToNum @dataWidth) - 1 - fromIntegral i) 0x00 + ) + } diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs index b3573868..8ece86c5 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs @@ -1,32 +1,21 @@ -{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE RecordWildCards #-} {-# OPTIONS_GHC -Wno-orphans #-} module Tests.Protocols.PacketStream.AsyncFifo where --- base -import Prelude +import Clash.Prelude --- clash-prelude -import Clash.Prelude as C - --- hedgehog -import Hedgehog +import Hedgehog (Property) import qualified Hedgehog.Range as Range --- tasty -import Test.Tasty +import Test.Tasty (TestTree, localOption, mkTimeout) import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) --- clash-protocols import Protocols.Hedgehog -import Protocols.PacketStream.AsyncFifo - --- tests -import Tests.Protocols.PacketStream.Base +import Protocols.PacketStream.AsyncFifo (asyncFifoC) +import Protocols.PacketStream.Hedgehog createDomain vSystem @@ -57,10 +46,10 @@ clk125 = clockGen -- Assert the reset for a different amount of cycles in each domain -- to properly test the async fifo. rst50 :: Reset TestDom50 -rst50 = resetGenN d50 +rst50 = resetGenN d30 rst125 :: Reset TestDom125 -rst125 = resetGenN d60 +rst125 = resetGenN d40 en50 :: Enable TestDom50 en50 = enableGen diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs deleted file mode 100644 index ff7f8af6..00000000 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs +++ /dev/null @@ -1,242 +0,0 @@ -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE RecordWildCards #-} - -module Tests.Protocols.PacketStream.Base ( - chunkBy, - chunkByPacket, - smearAbort, - chopBy, - chunkToPacket, - chopPacket, - fullPackets, - dropAbortedPackets, - downConvert, - upConvert, - AbortMode (..), - genValidPacket, - genValidPackets, - genVec, -) where - --- base -import qualified Data.List as L -import qualified Data.Maybe as M -import Prelude - --- clash -import qualified Clash.Prelude as C -import qualified Clash.Sized.Vector as Vec - --- hedgehog -import Hedgehog -import qualified Hedgehog.Gen as Gen - --- clash-protocols -import Protocols.PacketStream.Base - --- | Partition a list based on given function -chunkBy :: (a -> Bool) -> [a] -> [[a]] -chunkBy _ [] = [] -chunkBy predicate list = L.filter (not . null) $ chunkByHelper predicate list [] - --- Helper function to accumulate chunks -chunkByHelper :: (a -> Bool) -> [a] -> [a] -> [[a]] -chunkByHelper _ [] acc = [L.reverse acc] -chunkByHelper predicate (x : xs) acc - | predicate x = L.reverse (x : acc) : chunkByHelper predicate xs [] - | otherwise = chunkByHelper predicate xs (x : acc) - --- | Partition a list of PacketStreams into complete packets -chunkByPacket :: [PacketStreamM2S n meta] -> [[PacketStreamM2S n meta]] -chunkByPacket = chunkBy (M.isJust . _last) - --- | Smear abort over the rest of a list of packets -smearAbort :: [PacketStreamM2S n meta] -> [PacketStreamM2S n meta] -smearAbort [] = [] -smearAbort (x : xs) = L.reverse $ L.foldl' go [x] xs - where - go [] _ = [] - go l@(a : _) (PacketStreamM2S dat last' meta abort) = - PacketStreamM2S dat last' meta (_abort a || abort) : l - --- | Partition a list into groups of given size -chopBy :: Int -> [a] -> [[a]] -chopBy _ [] = [] -chopBy n xs = as : chopBy n bs where (as, bs) = splitAt n xs - --- | Merge a list of PacketStream 1 into a PacketStream n -chunkToPacket :: (C.KnownNat n) => [PacketStreamM2S 1 meta] -> PacketStreamM2S n meta -chunkToPacket l = - PacketStreamM2S - { _last = - if M.isJust (_last lastTransfer) then M.Just (fromIntegral $ L.length l - 1) else Nothing - , _abort = any _abort l - , _meta = _meta lastTransfer - , _data = foldr ((C.+>>) . C.head . _data) (C.repeat 0) l - } - where - lastTransfer = L.last l - --- | Split a PacketStream n into a list of PacketStream 1 -chopPacket :: - forall n meta. - (1 C.<= n) => - (C.KnownNat n) => - PacketStreamM2S n meta -> - [PacketStreamM2S 1 meta] -chopPacket PacketStreamM2S{..} = packets - where - lasts = case _last of - Nothing -> repeat Nothing - Just in' -> replicate (fromIntegral in') Nothing ++ [Just (0 :: C.Index 1)] - - datas = case _last of - Nothing -> C.toList _data - Just in' -> take (fromIntegral in' + 1) $ C.toList _data - - packets = (\(idx, dat) -> PacketStreamM2S (pure dat) idx _meta _abort) <$> zip lasts datas - --- | Set the _last of the last element of a list of PacketStreams to 0 -fullPackets :: (C.KnownNat n) => [PacketStreamM2S n meta] -> [PacketStreamM2S n meta] -fullPackets [] = [] -fullPackets fragments = - let lastFragment = (last fragments){_last = Just 0} - in init fragments ++ [lastFragment] - --- | Drops packets if one of the transfers in the packet has the abort flag set -dropAbortedPackets :: [PacketStreamM2S n meta] -> [PacketStreamM2S n meta] -dropAbortedPackets packets = concat $ filter (not . any _abort) (chunkByPacket packets) - --- | Split a list of PacketStream n into a list of PacketStream 1 -downConvert :: - forall n meta. - (1 C.<= n) => - (C.KnownNat n) => - [PacketStreamM2S n meta] -> - [PacketStreamM2S 1 meta] -downConvert = concatMap chopPacket - --- | Merge a list of PacketStream 1 into a list of PacketStream n -upConvert :: - forall n meta. - (1 C.<= n) => - (C.KnownNat n) => - [PacketStreamM2S 1 meta] -> - [PacketStreamM2S n meta] -upConvert packets = map chunkToPacket (chunkByPacket packets >>= chopBy (C.natToNum @n)) - -{- | If set to @NoAbort@, packets will never contain a transfer with _abort set. - Otherwise, transfers of roughly 50% of the packets will randomly have _abort set. --} -data AbortMode = Abort | NoAbort - -{- | Generate valid packets, i.e. packets of which all transfers carry the same - @_meta@ and with all unenabled bytes in @_data@ set to 0x00. --} -genValidPackets :: - forall (dataWidth :: C.Nat) (metaType :: C.Type). - (1 C.<= dataWidth) => - (C.KnownNat dataWidth) => - (C.BitPack metaType) => - -- | The amount of packets to generate. - Range Int -> - -- | The amount of transfers to generate. - Range Int -> - -- | If set to @NoAbort@, no generated packets will have @_abort@ set in - -- any of their transfers. Else, roughly 50% of packets will contain - -- fragments with their @_abort@ randomly set. - AbortMode -> - Gen [PacketStreamM2S dataWidth metaType] -genValidPackets r1 r2 Abort = concat <$> Gen.list r1 x - where - x = do - abortPacket <- Gen.bool - genValidPacket r2 (if abortPacket then Abort else NoAbort) -genValidPackets r1 r2 NoAbort = concat <$> Gen.list r1 (genValidPacket r2 NoAbort) - -{- | Generate a valid packet, i.e. a packet of which all transfers carry the same - @_meta@ and with all unenabled bytes in @_data@ set to 0x00. --} -genValidPacket :: - forall (dataWidth :: C.Nat) (metaType :: C.Type). - (1 C.<= dataWidth) => - (C.KnownNat dataWidth) => - (C.BitPack metaType) => - -- | The amount of transfers to generate. - Range Int -> - -- | If set to @NoAbort@, no transfers in the packet will have @_abort@ set. - -- Else, they will randomly have @_abort@ set. - AbortMode -> - Gen [PacketStreamM2S dataWidth metaType] -genValidPacket r abortMode = do - meta <- C.unpack <$> Gen.enumBounded - transfers <- Gen.list r (genTransfer @dataWidth meta abortMode) - lastTransfer <- genLastTransfer @dataWidth meta abortMode - pure (transfers ++ [lastTransfer]) - --- | Generate a single transfer which is not yet the end of a packet. -genTransfer :: - forall (dataWidth :: C.Nat) (metaType :: C.Type). - (1 C.<= dataWidth) => - (C.KnownNat dataWidth) => - -- | We need to use the same metadata - -- for every transfer in a packet to satisfy the protocol - -- invariant that metadata is constant for an entire packet. - metaType -> - -- | If set to @NoAbort@, hardcode @_abort@ to @False@. Else, - -- randomly generate it. - AbortMode -> - Gen (PacketStreamM2S dataWidth metaType) -genTransfer meta abortMode = - PacketStreamM2S - <$> genVec Gen.enumBounded - <*> Gen.constant Nothing - <*> Gen.constant meta - <*> case abortMode of - Abort -> Gen.enumBounded - NoAbort -> Gen.constant False - -{- | Generate the last transfer of a packet, i.e. a transfer with @_last@ set as @Just@. - All bytes which are not enabled are set to 0x00. --} -genLastTransfer :: - forall (dataWidth :: C.Nat) (metaType :: C.Type). - (1 C.<= dataWidth) => - (C.KnownNat dataWidth) => - -- | We need to use the same metadata - -- for every transfer in a packet to satisfy the protocol - -- invariant that metadata is constant for an entire packet. - metaType -> - -- | If set to @NoAbort@, hardcode @_abort@ to @False@. Else, - -- randomly generate it. - AbortMode -> - Gen (PacketStreamM2S dataWidth metaType) -genLastTransfer meta abortMode = - setNull - <$> ( PacketStreamM2S - <$> genVec Gen.enumBounded - <*> (Just <$> Gen.enumBounded) - <*> Gen.constant meta - <*> case abortMode of - Abort -> Gen.enumBounded - NoAbort -> Gen.constant False - ) - where - setNull transfer = - let i = M.fromJust (_last transfer) - in transfer - { _data = - M.fromJust - ( Vec.fromList $ - take (1 + fromIntegral i) (C.toList (_data transfer)) - ++ replicate ((C.natToNum @dataWidth) - 1 - fromIntegral i) 0x00 - ) - } - --- | Randomly generate a vector of length @n@. -genVec :: - forall (n :: C.Nat) (a :: C.Type). - (C.KnownNat n, 1 C.<= n) => - Gen a -> - Gen (C.Vec n a) -genVec gen = sequence (C.repeat gen) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs index c87563a8..3cebebde 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs @@ -1,102 +1,90 @@ -{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE RecordWildCards #-} module Tests.Protocols.PacketStream.Converters where --- base -import Prelude +import Clash.Prelude --- clash-prelude -import Clash.Prelude (type (<=)) -import qualified Clash.Prelude as C - --- hedgehog -import Hedgehog +import Hedgehog (Property) import qualified Hedgehog.Range as Range --- tasty import Test.Tasty import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) --- clash-protocols import Protocols.Hedgehog import Protocols.PacketStream.Converters - --- tests -import Tests.Protocols.PacketStream.Base +import Protocols.PacketStream.Hedgehog generateUpConverterProperty :: - forall (dwIn :: C.Nat) (dwOut :: C.Nat) (n :: C.Nat). + forall (dwIn :: Nat) (dwOut :: Nat) (n :: Nat). (1 <= dwIn) => (1 <= dwOut) => (1 <= n) => - (C.KnownNat n) => - (dwOut ~ n C.* dwIn) => - C.SNat dwIn -> - C.SNat dwOut -> + (KnownNat n) => + (dwOut ~ n * dwIn) => + SNat dwIn -> + SNat dwOut -> Property -generateUpConverterProperty C.SNat C.SNat = +generateUpConverterProperty SNat SNat = idWithModelSingleDomain defExpectOptions (genValidPackets (Range.linear 1 10) (Range.linear 1 20) Abort) - (C.exposeClockResetEnable (upConvert . downConvert)) - (C.exposeClockResetEnable @C.System (upConverterC @dwIn @dwOut @Int)) + (exposeClockResetEnable (upConvert . downConvert)) + (exposeClockResetEnable @System (upConverterC @dwIn @dwOut @Int)) prop_upConverter4to8 :: Property -prop_upConverter4to8 = generateUpConverterProperty C.d4 C.d8 +prop_upConverter4to8 = generateUpConverterProperty d4 d8 prop_upConverter3to6 :: Property -prop_upConverter3to6 = generateUpConverterProperty C.d3 C.d6 +prop_upConverter3to6 = generateUpConverterProperty d3 d6 prop_upConverter2to4 :: Property -prop_upConverter2to4 = generateUpConverterProperty C.d2 C.d4 +prop_upConverter2to4 = generateUpConverterProperty d2 d4 prop_upConverter1to4 :: Property -prop_upConverter1to4 = generateUpConverterProperty C.d1 C.d4 +prop_upConverter1to4 = generateUpConverterProperty d1 d4 prop_upConverter1to2 :: Property -prop_upConverter1to2 = generateUpConverterProperty C.d1 C.d2 +prop_upConverter1to2 = generateUpConverterProperty d1 d2 prop_upConverter1to1 :: Property -prop_upConverter1to1 = generateUpConverterProperty C.d1 C.d1 +prop_upConverter1to1 = generateUpConverterProperty d1 d1 generateDownConverterProperty :: - forall (dwIn :: C.Nat) (dwOut :: C.Nat) (n :: C.Nat). + forall (dwIn :: Nat) (dwOut :: Nat) (n :: Nat). (1 <= dwIn) => (1 <= dwOut) => (1 <= n) => - (C.KnownNat n) => - (dwIn ~ n C.* dwOut) => - C.SNat dwIn -> - C.SNat dwOut -> + (KnownNat n) => + (dwIn ~ n * dwOut) => + SNat dwIn -> + SNat dwOut -> Property -generateDownConverterProperty C.SNat C.SNat = +generateDownConverterProperty SNat SNat = idWithModelSingleDomain defExpectOptions{eoSampleMax = 1000} (genValidPackets (Range.linear 1 8) (Range.linear 1 10) Abort) - (C.exposeClockResetEnable (upConvert . downConvert)) - (C.exposeClockResetEnable @C.System (downConverterC @dwIn @dwOut @Int)) + (exposeClockResetEnable (upConvert . downConvert)) + (exposeClockResetEnable @System (downConverterC @dwIn @dwOut @Int)) prop_downConverter8to4 :: Property -prop_downConverter8to4 = generateDownConverterProperty C.d8 C.d4 +prop_downConverter8to4 = generateDownConverterProperty d8 d4 prop_downConverter6to3 :: Property -prop_downConverter6to3 = generateDownConverterProperty C.d6 C.d3 +prop_downConverter6to3 = generateDownConverterProperty d6 d3 prop_downConverter4to2 :: Property -prop_downConverter4to2 = generateDownConverterProperty C.d4 C.d2 +prop_downConverter4to2 = generateDownConverterProperty d4 d2 prop_downConverter4to1 :: Property -prop_downConverter4to1 = generateDownConverterProperty C.d4 C.d1 +prop_downConverter4to1 = generateDownConverterProperty d4 d1 prop_downConverter2to1 :: Property -prop_downConverter2to1 = generateDownConverterProperty C.d2 C.d1 +prop_downConverter2to1 = generateDownConverterProperty d2 d1 prop_downConverter1to1 :: Property -prop_downConverter1to1 = generateDownConverterProperty C.d1 C.d1 +prop_downConverter1to1 = generateDownConverterProperty d1 d1 tests :: TestTree tests = diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs index 8b1b3f35..65d19a5e 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs @@ -1,45 +1,38 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NumericUnderscores #-} -module Tests.Protocols.PacketStream.Delay where +module Tests.Protocols.PacketStream.Delay ( + tests, +) where --- base -import Prelude +import Clash.Prelude --- clash-prelude -import qualified Clash.Prelude as C - --- hedgehog import Hedgehog import qualified Hedgehog.Range as Range --- tasty import Test.Tasty import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) --- clash-protocols import Protocols import Protocols.Hedgehog import Protocols.PacketStream - --- tests -import Tests.Protocols.PacketStream.Base +import Protocols.PacketStream.Hedgehog prop_delaystream_id :: Property prop_delaystream_id = idWithModelSingleDomain - @C.System + @System defExpectOptions (genValidPackets (Range.linear 1 10) (Range.linear 1 6) Abort) - (C.exposeClockResetEnable id) - (C.exposeClockResetEnable ckt) + (exposeClockResetEnable id) + (exposeClockResetEnable ckt) where ckt :: - (C.HiddenClockResetEnable C.System) => - Circuit (PacketStream C.System 2 ()) (PacketStream C.System 2 ()) - ckt = delayStream @C.System @2 @() @4 C.d4 + (HiddenClockResetEnable System) => + Circuit (PacketStream System 2 ()) (PacketStream System 2 ()) + ckt = delayStream @System @2 @() @4 d4 tests :: TestTree tests = diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index be9572de..7988cc0e 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -2,108 +2,25 @@ {-# LANGUAGE NumericUnderscores #-} module Tests.Protocols.PacketStream.Depacketizers ( - depacketizerModel, - depacketizeToDfModel, tests, ) where --- base -import qualified Data.List as L -import Prelude - --- clash import Clash.Prelude -import Clash.Sized.Vector (unsafeFromList) --- hedgehog import Hedgehog import qualified Hedgehog.Range as Range --- tasty import Test.Tasty import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) --- clash-protocols -import Protocols.Hedgehog -import Protocols.PacketStream.Base - --- tests import Protocols +import Protocols.Hedgehog import Protocols.PacketStream (depacketizeToDfC, depacketizerC) -import Tests.Protocols.PacketStream.Base - --- | Model of the generic `depacketizerC`. -depacketizerModel :: - forall - (dataWidth :: Nat) - (headerBytes :: Nat) - (metaIn :: Type) - (metaOut :: Type) - (header :: Type). - ( KnownNat dataWidth - , KnownNat headerBytes - , 1 <= dataWidth - , 1 <= headerBytes - , BitPack header - , BitSize header ~ headerBytes * 8 - ) => - (header -> metaIn -> metaOut) -> - [PacketStreamM2S dataWidth metaIn] -> - [PacketStreamM2S dataWidth metaOut] -depacketizerModel toMetaOut ps = L.concat dataWidthPackets - where - hdrbytes = natToNum @headerBytes - - parseHdr :: - ([PacketStreamM2S 1 metaIn], [PacketStreamM2S 1 metaIn]) -> [PacketStreamM2S 1 metaOut] - parseHdr (hdrF, fwdF) = fmap (\f -> f{_meta = metaOut}) fwdF - where - hdr = bitCoerce $ unsafeFromList @headerBytes $ _data <$> hdrF - metaOut = toMetaOut hdr (_meta $ L.head fwdF) - - bytePackets :: [[PacketStreamM2S 1 metaIn]] - bytePackets = - L.filter (\fs -> L.length fs > hdrbytes) $ - L.concatMap chopPacket . smearAbort <$> chunkByPacket ps - - parsedPackets :: [[PacketStreamM2S 1 metaOut]] - parsedPackets = parseHdr . L.splitAt hdrbytes <$> bytePackets - - dataWidthPackets :: [[PacketStreamM2S dataWidth metaOut]] - dataWidthPackets = fmap chunkToPacket . chopBy (natToNum @dataWidth) <$> parsedPackets - --- | Model of the generic `depacketizeToDfC`. -depacketizeToDfModel :: - forall - (dataWidth :: Nat) - (headerBytes :: Nat) - (meta :: Type) - (a :: Type) - (header :: Type). - ( KnownNat dataWidth - , KnownNat headerBytes - , 1 <= dataWidth - , 1 <= headerBytes - , BitPack header - , BitSize header ~ headerBytes * 8 - ) => - (header -> meta -> a) -> - [PacketStreamM2S dataWidth meta] -> - [a] -depacketizeToDfModel toOut ps = parseHdr <$> bytePackets - where - hdrbytes = natToNum @headerBytes - - parseHdr :: [PacketStreamM2S 1 meta] -> a - parseHdr hdrF = toOut (bitCoerce $ unsafeFromList @headerBytes $ _data <$> hdrF) (_meta $ L.head hdrF) - - bytePackets :: [[PacketStreamM2S 1 meta]] - bytePackets = - L.filter (\fs -> L.length fs >= hdrbytes) $ - L.concatMap chopPacket <$> chunkByPacket (dropAbortedPackets ps) +import Protocols.PacketStream.Base +import Protocols.PacketStream.Hedgehog {- | Test the depacketizer with varying datawidth and number of bytes in the header, with metaIn = () and toMetaOut = const. diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index 53e8022b..175693e7 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -1,52 +1,38 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE RecordWildCards #-} -module Tests.Protocols.PacketStream.PacketFifo where +module Tests.Protocols.PacketStream.PacketFifo ( + tests, +) where --- base -import Data.Int (Int16) -import Prelude +import Clash.Prelude --- clash-prelude -import Clash.Prelude hiding (drop, take, undefined, (++)) -import qualified Clash.Prelude as C +import Data.Int (Int16) +import qualified Data.List as L --- hedgehog import Hedgehog as H import qualified Hedgehog.Range as Range --- tasty import Test.Tasty import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) --- clash-protocols import Protocols import Protocols.Hedgehog import Protocols.PacketStream.Base +import Protocols.PacketStream.Hedgehog import Protocols.PacketStream.PacketFifo --- tests -import Tests.Protocols.PacketStream.Base as U - -isSubsequenceOf :: (Eq a) => [a] -> [a] -> Bool -isSubsequenceOf [] _ = True -isSubsequenceOf _ [] = False -isSubsequenceOf (x : xs) (y : ys) - | x == y = isSubsequenceOf xs ys - | otherwise = isSubsequenceOf xs (y : ys) - -- | test for id and proper dropping of aborted packets prop_packetFifo_id :: Property prop_packetFifo_id = idWithModelSingleDomain - @C.System + @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} (genValidPackets (Range.linear 1 30) (Range.linear 1 10) Abort) - (C.exposeClockResetEnable dropAbortedPackets) - (C.exposeClockResetEnable ckt) + (exposeClockResetEnable dropAbortedPackets) + (exposeClockResetEnable ckt) where ckt :: (HiddenClockResetEnable System) => @@ -57,11 +43,11 @@ prop_packetFifo_id = prop_packetFifo_small_buffer_id :: Property prop_packetFifo_small_buffer_id = idWithModelSingleDomain - @C.System + @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} (genValidPackets (Range.linear 1 10) (Range.linear 1 30) NoAbort) - (C.exposeClockResetEnable dropAbortedPackets) - (C.exposeClockResetEnable ckt) + (exposeClockResetEnable dropAbortedPackets) + (exposeClockResetEnable ckt) where ckt :: (HiddenClockResetEnable System) => @@ -87,7 +73,7 @@ prop_packetFifo_no_gaps = property $ do cfg = SimulationConfig 1 (2 * packetSize) False cktResult = simulateC ckt cfg (Just <$> packets) - assert $ noGaps $ take (5 * maxInputSize) cktResult + assert $ noGaps $ L.take (5 * maxInputSize) cktResult where noGaps :: [Maybe (PacketStreamM2S 4 Int16)] -> Bool noGaps (Just (PacketStreamM2S{_last = Nothing}) : Nothing : _) = False @@ -98,11 +84,11 @@ prop_packetFifo_no_gaps = property $ do prop_overFlowDrop_packetFifo_id :: Property prop_overFlowDrop_packetFifo_id = idWithModelSingleDomain - @C.System + @System defExpectOptions{eoSampleMax = 2000, eoStopAfterEmpty = 1000} (genValidPackets (Range.linear 1 30) (Range.linear 1 10) Abort) - (C.exposeClockResetEnable dropAbortedPackets) - (C.exposeClockResetEnable ckt) + (exposeClockResetEnable dropAbortedPackets) + (exposeClockResetEnable ckt) where ckt :: (HiddenClockResetEnable System) => @@ -113,12 +99,12 @@ prop_overFlowDrop_packetFifo_id = prop_overFlowDrop_packetFifo_drop :: Property prop_overFlowDrop_packetFifo_drop = idWithModelSingleDomain - @C.System + @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} -- make sure the timeout is long as the packetFifo can be quiet for a while while dropping - (liftA3 (\a b c -> a ++ b ++ c) genSmall genBig genSmall) - (C.exposeClockResetEnable model) - (C.exposeClockResetEnable ckt) + (liftA3 (\a b c -> a L.++ b L.++ c) genSmall genBig genSmall) + (exposeClockResetEnable model) + (exposeClockResetEnable ckt) where ckt :: (HiddenClockResetEnable System) => @@ -126,7 +112,7 @@ prop_overFlowDrop_packetFifo_drop = ckt = packetFifoC d5 d5 Drop model :: [PacketStreamM2S 4 Int16] -> [PacketStreamM2S 4 Int16] - model packets = Prelude.concat $ take 1 packetChunk ++ drop 2 packetChunk + model packets = Prelude.concat $ L.take 1 packetChunk L.++ L.drop 2 packetChunk where packetChunk = chunkByPacket packets @@ -137,11 +123,11 @@ prop_overFlowDrop_packetFifo_drop = prop_packetFifo_small_metaBuffer :: Property prop_packetFifo_small_metaBuffer = idWithModelSingleDomain - @C.System + @System defExpectOptions (genValidPackets (Range.linear 1 30) (Range.linear 1 4) Abort) - (C.exposeClockResetEnable dropAbortedPackets) - (C.exposeClockResetEnable ckt) + (exposeClockResetEnable dropAbortedPackets) + (exposeClockResetEnable ckt) where ckt :: (HiddenClockResetEnable System) => diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs index e85e0dec..9104ac1c 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs @@ -2,97 +2,27 @@ {-# LANGUAGE NumericUnderscores #-} module Tests.Protocols.PacketStream.Packetizers ( - packetizerModel, - packetizeFromDfModel, tests, ) where --- base -import qualified Data.List as L -import Prelude - --- clash +import Clash.Hedgehog.Sized.Vector (genVec) import Clash.Prelude --- hedgehog import Hedgehog import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range --- tasty import Test.Tasty import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) - import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) --- clash-protocols import Protocols import qualified Protocols.Df as Df import Protocols.Hedgehog -import Protocols.PacketStream.Base - --- tests import Protocols.PacketStream (packetizeFromDfC, packetizerC) -import Tests.Protocols.PacketStream.Base - --- | Model of the generic `packetizerC`. -packetizerModel :: - forall - (dataWidth :: Nat) - (headerBytes :: Nat) - (metaIn :: Type) - (header :: Type) - (metaOut :: Type). - (KnownNat dataWidth) => - (KnownNat headerBytes) => - (1 <= dataWidth) => - (1 <= headerBytes) => - (BitPack header) => - (BitSize header ~ headerBytes * 8) => - (metaIn -> metaOut) -> - (metaIn -> header) -> - [PacketStreamM2S dataWidth metaIn] -> - [PacketStreamM2S dataWidth metaOut] -packetizerModel toMetaOut toHeader ps = L.concatMap (upConvert . prependHdr) bytePackets - where - prependHdr :: [PacketStreamM2S 1 metaIn] -> [PacketStreamM2S 1 metaOut] - prependHdr fragments = hdr L.++ L.map (\f -> f{_meta = metaOut}) fragments - where - h = L.head fragments - metaOut = toMetaOut (_meta h) - hdr = L.map go (toList $ bitCoerce (toHeader (_meta h))) - go byte = PacketStreamM2S (singleton byte) Nothing metaOut (_abort h) - - bytePackets :: [[PacketStreamM2S 1 metaIn]] - bytePackets = downConvert . smearAbort <$> chunkByPacket ps - --- | Model of the generic `packetizeFromDfC`. -packetizeFromDfModel :: - forall - (dataWidth :: Nat) - (headerBytes :: Nat) - (a :: Type) - (header :: Type) - (metaOut :: Type). - (KnownNat dataWidth) => - (KnownNat headerBytes) => - (1 <= dataWidth) => - (1 <= headerBytes) => - (BitPack header) => - (BitSize header ~ headerBytes * 8) => - (a -> metaOut) -> - (a -> header) -> - [a] -> - [PacketStreamM2S dataWidth metaOut] -packetizeFromDfModel toMetaOut toHeader = L.concatMap (upConvert . packetize) - where - packetize :: a -> [PacketStreamM2S 1 metaOut] - packetize d = - fullPackets $ - L.map - (\byte -> PacketStreamM2S (byte :> Nil) Nothing (toMetaOut d) False) - (toList $ bitCoerce (toHeader d)) +import Protocols.PacketStream.Base +import Protocols.PacketStream.Hedgehog {- | Test the packetizer with varying datawidth and number of bytes in the header, with metaOut = (). @@ -114,7 +44,7 @@ packetizerPropertyGenerator SNat SNat = (exposeClockResetEnable model) (exposeClockResetEnable ckt) where - model = packetizerModel (const ()) id + model = packetize (const ()) id ckt :: (HiddenClockResetEnable System) => Circuit @@ -158,7 +88,7 @@ packetizeFromDfPropertyGenerator SNat SNat = (exposeClockResetEnable model) (exposeClockResetEnable ckt) where - model = packetizeFromDfModel (const ()) id + model = packetizeFromDf (const ()) id ckt :: (HiddenClockResetEnable System) => Circuit (Df.Df System (Vec headerBytes (BitVector 8))) (PacketStream System dataWidth ()) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs index 26bb410e..96e9f110 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs @@ -1,53 +1,42 @@ -{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE RecordWildCards #-} -module Tests.Protocols.PacketStream.Routing where +module Tests.Protocols.PacketStream.Routing ( + tests, +) where --- base -import Data.List (groupBy, sortOn) -import Prelude - --- clash-prelude -import Clash.Prelude (type (<=)) +import Clash.Prelude import qualified Clash.Prelude as C --- hedgehog import Hedgehog hiding (Parallel) import qualified Hedgehog.Range as Range --- tasty import Test.Tasty import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) --- clash-protocols - import Protocols.Hedgehog import Protocols.PacketStream.Base +import Protocols.PacketStream.Hedgehog import Protocols.PacketStream.Routing --- tests -import Tests.Protocols.PacketStream.Base - import qualified Data.List as L -- | Tests the round-robin packet arbiter with one source; essentially an id test prop_packetarbiter_roundrobin_id :: Property -prop_packetarbiter_roundrobin_id = makePropPacketArbiter C.d1 C.d2 RoundRobin +prop_packetarbiter_roundrobin_id = makePropPacketArbiter d1 d2 RoundRobin -- | Tests the parallel packet arbiter with one source; essentially an id test prop_packetarbiter_parallel_id :: Property -prop_packetarbiter_parallel_id = makePropPacketArbiter C.d1 C.d2 Parallel +prop_packetarbiter_parallel_id = makePropPacketArbiter d1 d2 Parallel -- Tests the round-robin arbiter with five sources prop_packetarbiter_roundrobin :: Property -prop_packetarbiter_roundrobin = makePropPacketArbiter C.d5 C.d2 RoundRobin +prop_packetarbiter_roundrobin = makePropPacketArbiter d5 d2 RoundRobin -- Tests the parallel arbiter with five sources prop_packetarbiter_parallel :: Property -prop_packetarbiter_parallel = makePropPacketArbiter C.d5 C.d2 Parallel +prop_packetarbiter_parallel = makePropPacketArbiter d5 d2 Parallel {- | Tests a packet arbiter for any data width and number of sources. In particular, tests that packets from all sources are sent out unmodified in the same order @@ -55,32 +44,32 @@ they were in in the source streams. -} makePropPacketArbiter :: forall p n. - ( C.KnownNat p + ( KnownNat p , 1 <= p - , C.KnownNat n + , KnownNat n , 1 <= n ) => - C.SNat p -> - C.SNat n -> + SNat p -> + SNat n -> ArbiterMode -> Property makePropPacketArbiter _ _ mode = propWithModelSingleDomain - @C.System + @System defExpectOptions{eoSampleMax = 1000} genSources - (C.exposeClockResetEnable concat) - (C.exposeClockResetEnable (packetArbiterC mode)) + (exposeClockResetEnable L.concat) + (exposeClockResetEnable (packetArbiterC mode)) (\xs ys -> partitionPackets xs === partitionPackets ys) where - genSources = mapM setMeta (C.indicesI @p) + genSources = mapM setMeta (indicesI @p) setMeta j = do pkts <- genValidPackets @n @() (Range.linear 1 10) (Range.linear 1 10) Abort pure $ L.map (\pkt -> pkt{_meta = j}) pkts partitionPackets packets = - sortOn (_meta . head . head) $ - groupBy (\a b -> _meta a == _meta b) <$> chunkByPacket packets + L.sortOn (_meta . L.head . L.head) $ + L.groupBy (\a b -> _meta a == _meta b) <$> chunkByPacket packets {- | Tests that the packet dispatcher works correctly with one sink that accepts all packets; essentially an id test. @@ -88,8 +77,8 @@ all packets; essentially an id test. prop_packetdispatcher_id :: Property prop_packetdispatcher_id = makePropPacketDispatcher - C.d4 - ((const True :: Int -> Bool) C.:> C.Nil) + d4 + ((const True :: Int -> Bool) :> Nil) {- | Tests the packet dispatcher for a data width of four bytes and three overlapping but incomplete dispatch functions, effectively testing whether @@ -97,43 +86,43 @@ the circuit sends input to the first allowed output channel and drops input if there are none. -} prop_packetdispatcher :: Property -prop_packetdispatcher = makePropPacketDispatcher C.d4 fs +prop_packetdispatcher = makePropPacketDispatcher d4 fs where - fs :: C.Vec 3 (C.Index 4 -> Bool) + fs :: Vec 3 (Index 4 -> Bool) fs = (>= 3) - C.:> (>= 2) - C.:> (>= 1) - C.:> C.Nil + :> (>= 2) + :> (>= 1) + :> Nil {- | Generic test function for the packet dispatcher, testing for all data widths, dispatch functions, and some meta types -} makePropPacketDispatcher :: - forall (p :: C.Nat) (dataWidth :: C.Nat) (a :: C.Type). - ( C.KnownNat p + forall (p :: Nat) (dataWidth :: Nat) (a :: Type). + ( KnownNat p , 1 <= p - , C.KnownNat dataWidth + , KnownNat dataWidth , 1 <= dataWidth , TestType a , Bounded a - , C.BitPack a + , BitPack a ) => - C.SNat dataWidth -> - C.Vec p (a -> Bool) -> + SNat dataWidth -> + Vec p (a -> Bool) -> Property makePropPacketDispatcher _ fs = - idWithModelSingleDomain @C.System + idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 2000, eoStopAfterEmpty = 1000} (genValidPackets (Range.linear 1 10) (Range.linear 1 6) Abort) - (C.exposeClockResetEnable (model 0)) - (C.exposeClockResetEnable (packetDispatcherC fs)) + (exposeClockResetEnable (model 0)) + (exposeClockResetEnable (packetDispatcherC fs)) where model :: - C.Index p -> [PacketStreamM2S dataWidth a] -> C.Vec p [PacketStreamM2S dataWidth a] + Index p -> [PacketStreamM2S dataWidth a] -> Vec p [PacketStreamM2S dataWidth a] model _ [] = pure [] model i (y : ys) - | (fs C.!! i) (_meta y) = let next = model 0 ys in C.replace i (y : (next C.!! i)) next + | (fs C.!! i) (_meta y) = let next = model 0 ys in replace i (y : (next C.!! i)) next | i < maxBound = model (i + 1) (y : ys) | otherwise = model 0 ys From b9376b7359dd8a52ac601e8b8b65446b94530dd4 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Tue, 27 Aug 2024 17:47:45 +0200 Subject: [PATCH 34/63] Rename packetizer model functions --- .../src/Protocols/PacketStream/Hedgehog.hs | 12 ++++++------ .../Tests/Protocols/PacketStream/Packetizers.hs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs index 3a5e2e68..62b1662a 100644 --- a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs +++ b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs @@ -22,8 +22,8 @@ module Protocols.PacketStream.Hedgehog ( upConvert, depacketizerModel, depacketizeToDfModel, - packetize, - packetizeFromDf, + packetizerModel, + packetizeFromDfModel, -- * Hedgehog generators AbortMode (..), @@ -244,7 +244,7 @@ depacketizeToDfModel toOut ps = L.map parseHdr bytePackets (chunkByPacket $ downConvert (dropAbortedPackets ps)) -- | Model of the generic `Protocols.PacketStream.packetizerC`. -packetize :: +packetizerModel :: forall (dataWidth :: C.Nat) (headerBytes :: C.Nat) @@ -261,7 +261,7 @@ packetize :: (metaIn -> header) -> [PacketStreamM2S dataWidth metaIn] -> [PacketStreamM2S dataWidth metaOut] -packetize toMetaOut toHeader ps = L.concatMap (upConvert . prependHdr) bytePackets +packetizerModel toMetaOut toHeader ps = L.concatMap (upConvert . prependHdr) bytePackets where prependHdr :: [PacketStreamM2S 1 metaIn] -> [PacketStreamM2S 1 metaOut] prependHdr fragments = hdr L.++ L.map (\f -> f{_meta = metaOut}) fragments @@ -275,7 +275,7 @@ packetize toMetaOut toHeader ps = L.concatMap (upConvert . prependHdr) bytePacke bytePackets = downConvert . smearAbort <$> chunkByPacket ps -- | Model of the generic `Protocols.PacketStream.packetizeFromDfC`. -packetizeFromDf :: +packetizeFromDfModel :: forall (dataWidth :: C.Nat) (headerBytes :: C.Nat) @@ -292,7 +292,7 @@ packetizeFromDf :: (a -> header) -> [a] -> [PacketStreamM2S dataWidth metaOut] -packetizeFromDf toMetaOut toHeader = L.concatMap (upConvert . dfToPacket) +packetizeFromDfModel toMetaOut toHeader = L.concatMap (upConvert . dfToPacket) where dfToPacket :: a -> [PacketStreamM2S 1 metaOut] dfToPacket d = diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs index 9104ac1c..36617f6b 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs @@ -44,7 +44,7 @@ packetizerPropertyGenerator SNat SNat = (exposeClockResetEnable model) (exposeClockResetEnable ckt) where - model = packetize (const ()) id + model = packetizerModel (const ()) id ckt :: (HiddenClockResetEnable System) => Circuit @@ -88,7 +88,7 @@ packetizeFromDfPropertyGenerator SNat SNat = (exposeClockResetEnable model) (exposeClockResetEnable ckt) where - model = packetizeFromDf (const ()) id + model = packetizeFromDfModel (const ()) id ckt :: (HiddenClockResetEnable System) => Circuit (Df.Df System (Vec headerBytes (BitVector 8))) (PacketStream System dataWidth ()) From 9138b2ddf72bcc5b786e441b28e2bb3dc1aa8a1e Mon Sep 17 00:00:00 2001 From: t-wallet Date: Tue, 27 Aug 2024 18:23:00 +0200 Subject: [PATCH 35/63] Add sensible DfConv functions --- clash-protocols/src/Protocols/DfConv.hs | 2 +- clash-protocols/src/Protocols/PacketStream.hs | 12 ++++ .../src/Protocols/PacketStream/Base.hs | 71 ++++++++++++++++++- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/clash-protocols/src/Protocols/DfConv.hs b/clash-protocols/src/Protocols/DfConv.hs index 02652a6c..cec2087d 100644 --- a/clash-protocols/src/Protocols/DfConv.hs +++ b/clash-protocols/src/Protocols/DfConv.hs @@ -1212,7 +1212,7 @@ fanout :: , FwdPayload dfA ~ FwdPayload dfB , NFDataX (FwdPayload dfA) , KnownNat numB - , numB ~ (decNumB + 1) + , 1 <= numB ) => Proxy dfA -> Proxy dfB -> diff --git a/clash-protocols/src/Protocols/PacketStream.hs b/clash-protocols/src/Protocols/PacketStream.hs index 8ee43ffe..41ea82ba 100644 --- a/clash-protocols/src/Protocols/PacketStream.hs +++ b/clash-protocols/src/Protocols/PacketStream.hs @@ -16,12 +16,24 @@ Apart from the protocol definition, some components, all of which are generic in -} module Protocols.PacketStream ( module Protocols.PacketStream.Base, + + -- * Fifos module Protocols.PacketStream.PacketFifo, module Protocols.PacketStream.AsyncFifo, + + -- * Converters module Protocols.PacketStream.Converters, + + -- * Delay components module Protocols.PacketStream.Delay, + + -- * Depacketizers module Protocols.PacketStream.Depacketizers, + + -- * Packetizers module Protocols.PacketStream.Packetizers, + + -- * Routing components module Protocols.PacketStream.Routing, ) where diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index f78d7eaa..4bd6bbb9 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -1,4 +1,5 @@ {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_HADDOCK hide #-} @@ -6,18 +7,29 @@ Definitions and instances of the PacketStream protocol -} module Protocols.PacketStream.Base ( + -- * Protocol definition PacketStreamM2S (..), PacketStreamS2M (..), PacketStream, - abortOnBackPressureC, + + -- * CSignal conversion unsafeToPacketStream, fromPacketStream, + + -- * Basic operations on the PacketStream protocol + abortOnBackPressureC, + fanout, forceResetSanity, + registerBwd, + registerFwd, + void, + zeroOutInvalidBytesC, + + -- * Operations on metadata filterMetaS, filterMeta, mapMetaS, mapMeta, - zeroOutInvalidBytesC, ) where import Clash.Prelude hiding (sample) @@ -64,6 +76,10 @@ data PacketStreamM2S (dataWidth :: Nat) (meta :: Type) = PacketStreamM2S } deriving (Eq, Generic, ShowX, Show, NFData, Bundle, Functor) +-- | Used by circuit-notation to create an empty stream +instance Default (Maybe (PacketStreamM2S dataWidth meta)) where + def = Nothing + {- | Data sent from the subordinate to manager. @@ -75,6 +91,10 @@ newtype PacketStreamS2M = PacketStreamS2M } deriving (Eq, Generic, ShowX, Show, NFData, Bundle, NFDataX) +-- | Used by circuit-notation to create a sink that always acknowledges +instance Default PacketStreamS2M where + def = PacketStreamS2M True + {- | Simple valid-ready streaming protocol for transferring packets between components. @@ -278,3 +298,50 @@ zeroOutInvalidBytesC = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, fmap (go <$>) fwdIn) -- The first byte is always valid, so we only map over the rest. (a, b') = splitAt d1 (_data transferIn) b = imap (\(j :: Index (dataWidth - 1)) byte -> if resize j < i then byte else 0x00) b' + +{- | +Copy data of a single `PacketStream` to multiple. LHS will only receive +an acknowledgement when all RHS receivers have acknowledged data. +-} +fanout :: + forall n dataWidth meta dom. + (HiddenClockResetEnable dom) => + (KnownNat n) => + (KnownNat dataWidth) => + (1 <= n) => + (NFDataX meta) => + Circuit + (PacketStream dom dataWidth meta) + (Vec n (PacketStream dom dataWidth meta)) +fanout = DfConv.fanout Proxy Proxy + +{- | +Place register on /forward/ part of a circuit. +This adds combinational delay on the /backward/ path. +-} +registerFwd :: + forall dataWidth meta dom. + (HiddenClockResetEnable dom) => + (KnownNat dataWidth) => + (NFDataX meta) => + Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) +registerFwd = DfConv.registerFwd Proxy Proxy + +{- | +Place register on /backward/ part of a circuit. +This adds combinational delay on the /forward/ path. +-} +registerBwd :: + forall dataWidth meta dom. + (HiddenClockResetEnable dom) => + (KnownNat dataWidth) => + (NFDataX meta) => + Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) +registerBwd = DfConv.registerBwd Proxy Proxy + +-- | Ignore incoming data. +void :: + forall dataWidth meta dom. + (HiddenClockResetEnable dom) => + Circuit (PacketStream dom dataWidth meta) () +void = DfConv.void Proxy From 0d40b6f3eb17b183d784ae53cb92d337a80663b6 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Wed, 28 Aug 2024 14:44:10 +0200 Subject: [PATCH 36/63] Export chopBy --- clash-protocols/src/Protocols/PacketStream/Hedgehog.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs index 62b1662a..0345a45f 100644 --- a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs +++ b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs @@ -10,6 +10,7 @@ Provides Hedgehog generators, models and utility functions for testing -} module Protocols.PacketStream.Hedgehog ( -- * Utility functions + chopBy, chopPacket, chunkByPacket, chunkToPacket, From 250428abadda145f37c7546746a98914526f2778 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 30 Aug 2024 18:21:33 +0200 Subject: [PATCH 37/63] Allow user to pass custom packet generator to genPackets --- .../src/Protocols/PacketStream/Hedgehog.hs | 75 ++++++++++--------- .../Tests/Protocols/PacketStream/AsyncFifo.hs | 4 +- .../Protocols/PacketStream/Converters.hs | 6 +- .../Tests/Protocols/PacketStream/Delay.hs | 7 +- .../Protocols/PacketStream/Depacketizers.hs | 4 +- .../Protocols/PacketStream/PacketFifo.hs | 26 ++++--- .../Protocols/PacketStream/Packetizers.hs | 6 +- .../Tests/Protocols/PacketStream/Routing.hs | 7 +- 8 files changed, 79 insertions(+), 56 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs index 0345a45f..63196337 100644 --- a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs +++ b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs @@ -29,7 +29,7 @@ module Protocols.PacketStream.Hedgehog ( -- * Hedgehog generators AbortMode (..), genValidPacket, - genValidPackets, + genPackets, ) where import Prelude @@ -159,7 +159,7 @@ downConvert :: downConvert = concatMap chopPacket {- | -Merges a list of `PacketStream` transfers with data width @dataWidth into +Merges a list of `PacketStream` transfers with data width @dataWidth@ into a list of `PacketStream` transfers with data width @1@ -} upConvert :: @@ -309,75 +309,78 @@ Otherwise, transfers of roughly 50% of the packets will randomly have _abort set data AbortMode = Abort | NoAbort {- | -Generate valid packets, i.e. packets of which all transfers carry the same -`_meta` and with all unenabled bytes in `_data` set to 0x00. +Generate packets with a user-supplied generator. -} -genValidPackets :: - forall (dataWidth :: C.Nat) (metaType :: C.Type). +genPackets :: + forall (dataWidth :: C.Nat) (meta :: C.Type). (1 C.<= dataWidth) => (C.KnownNat dataWidth) => - (C.BitPack metaType) => -- | The amount of packets to generate. Range Int -> - -- | The amount of transfers with @_last = Nothing@ to generate per packet. - -- This function will always generate an extra transfer per packet - -- with @_last = Just i@. - Range Int -> - -- | If set to @NoAbort@, no generated packets will have `_abort` set in - -- any of their transfers. Else, roughly 50% of packets will contain - -- fragments with their `_abort` randomly set. + -- | If set to @NoAbort@, always pass @NoAbort@ to the packet generator. + -- Else, pass @Abort@ to roughly 50% of the packet generators. AbortMode -> - Gen [PacketStreamM2S dataWidth metaType] -genValidPackets pkts size Abort = concat <$> Gen.list pkts gen - where - gen = do - abortPacket <- Gen.bool - genValidPacket size (if abortPacket then Abort else NoAbort) -genValidPackets pkts size NoAbort = - concat <$> Gen.list pkts (genValidPacket size NoAbort) + -- | Packet generator. + (AbortMode -> Gen [PacketStreamM2S dataWidth meta]) -> + Gen [PacketStreamM2S dataWidth meta] +genPackets pkts Abort pktGen = + concat + <$> Gen.list + pkts + (Gen.choice [pktGen Abort, pktGen NoAbort]) +genPackets pkts NoAbort pktGen = + concat + <$> Gen.list + pkts + (pktGen NoAbort) {- | Generate a valid packet, i.e. a packet of which all transfers carry the same `_meta` and with all unenabled bytes in `_data` set to 0x00. -} genValidPacket :: - forall (dataWidth :: C.Nat) (metaType :: C.Type). + forall (dataWidth :: C.Nat) (meta :: C.Type). (1 C.<= dataWidth) => (C.KnownNat dataWidth) => - (C.BitPack metaType) => + -- | Generator for the metadata. + Gen meta -> -- | The amount of transfers with @_last = Nothing@ to generate. -- This function will always generate an extra transfer with @_last = Just i@. Range Int -> -- | If set to @NoAbort@, no transfers in the packet will have @_abort@ set. - -- Else, they will randomly have @_abort@ set. + -- Else, each transfer has a 10% chance to have @_abort@ set. AbortMode -> - Gen [PacketStreamM2S dataWidth metaType] -genValidPacket size abortMode = do - meta <- C.unpack <$> Gen.enumBounded + Gen [PacketStreamM2S dataWidth meta] +genValidPacket metaGen size abortMode = do + meta <- metaGen transfers <- Gen.list size (genTransfer @dataWidth meta abortMode) lastTransfer <- genLastTransfer @dataWidth meta abortMode pure (transfers ++ [lastTransfer]) -- | Generate a single transfer which is not yet the end of a packet. genTransfer :: - forall (dataWidth :: C.Nat) (metaType :: C.Type). + forall (dataWidth :: C.Nat) (meta :: C.Type). (1 C.<= dataWidth) => (C.KnownNat dataWidth) => -- | We need to use the same metadata -- for every transfer in a packet to satisfy the protocol -- invariant that metadata is constant for an entire packet. - metaType -> + meta -> -- | If set to @NoAbort@, hardcode @_abort@ to @False@. Else, - -- randomly generate it. + -- there is a 10% chance for it to be set. AbortMode -> - Gen (PacketStreamM2S dataWidth metaType) + Gen (PacketStreamM2S dataWidth meta) genTransfer meta abortMode = PacketStreamM2S <$> genVec Gen.enumBounded <*> Gen.constant Nothing <*> Gen.constant meta <*> case abortMode of - Abort -> Gen.enumBounded + Abort -> + Gen.frequency + [ (90, Gen.constant False) + , (10, Gen.constant True) + ] NoAbort -> Gen.constant False {- | @@ -385,17 +388,17 @@ Generate the last transfer of a packet, i.e. a transfer with @_last@ set as @Jus All bytes which are not enabled are set to 0x00. -} genLastTransfer :: - forall (dataWidth :: C.Nat) (metaType :: C.Type). + forall (dataWidth :: C.Nat) (meta :: C.Type). (1 C.<= dataWidth) => (C.KnownNat dataWidth) => -- | We need to use the same metadata -- for every transfer in a packet to satisfy the protocol -- invariant that metadata is constant for an entire packet. - metaType -> + meta -> -- | If set to @NoAbort@, hardcode @_abort@ to @False@. Else, -- randomly generate it. AbortMode -> - Gen (PacketStreamM2S dataWidth metaType) + Gen (PacketStreamM2S dataWidth meta) genLastTransfer meta abortMode = setNull <$> ( PacketStreamM2S diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs index 8ece86c5..393152d8 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs @@ -6,6 +6,7 @@ module Tests.Protocols.PacketStream.AsyncFifo where import Clash.Prelude import Hedgehog (Property) +import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import Test.Tasty (TestTree, localOption, mkTimeout) @@ -70,7 +71,8 @@ generateAsyncFifoIdProp :: generateAsyncFifoIdProp wClk wRst wEn rClk rRst rEn = idWithModel defExpectOptions - (genValidPackets (Range.linear 1 10) (Range.linear 1 30) Abort) + ( genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 1 30)) + ) id (asyncFifoC @wDom @rDom @4 @1 @Int d4 wClk wRst wEn rClk rRst rEn) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs index 3cebebde..ac48662f 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs @@ -5,6 +5,7 @@ module Tests.Protocols.PacketStream.Converters where import Clash.Prelude import Hedgehog (Property) +import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import Test.Tasty @@ -29,7 +30,8 @@ generateUpConverterProperty :: generateUpConverterProperty SNat SNat = idWithModelSingleDomain defExpectOptions - (genValidPackets (Range.linear 1 10) (Range.linear 1 20) Abort) + ( genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 1 20)) + ) (exposeClockResetEnable (upConvert . downConvert)) (exposeClockResetEnable @System (upConverterC @dwIn @dwOut @Int)) @@ -64,7 +66,7 @@ generateDownConverterProperty :: generateDownConverterProperty SNat SNat = idWithModelSingleDomain defExpectOptions{eoSampleMax = 1000} - (genValidPackets (Range.linear 1 8) (Range.linear 1 10) Abort) + (genPackets (Range.linear 1 8) Abort (genValidPacket Gen.enumBounded (Range.linear 1 10))) (exposeClockResetEnable (upConvert . downConvert)) (exposeClockResetEnable @System (downConverterC @dwIn @dwOut @Int)) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs index 65d19a5e..1660b78c 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs @@ -8,6 +8,7 @@ module Tests.Protocols.PacketStream.Delay ( import Clash.Prelude import Hedgehog +import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import Test.Tasty @@ -25,14 +26,14 @@ prop_delaystream_id = idWithModelSingleDomain @System defExpectOptions - (genValidPackets (Range.linear 1 10) (Range.linear 1 6) Abort) + (genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 1 6))) (exposeClockResetEnable id) (exposeClockResetEnable ckt) where ckt :: (HiddenClockResetEnable System) => - Circuit (PacketStream System 2 ()) (PacketStream System 2 ()) - ckt = delayStream @System @2 @() @4 d4 + Circuit (PacketStream System 2 Int) (PacketStream System 2 Int) + ckt = delayStream @System @2 @Int @4 d4 tests :: TestTree tests = diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index 7988cc0e..d62ba34d 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -38,7 +38,7 @@ depacketizerPropertyGenerator SNat SNat = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - (genValidPackets (Range.linear 1 4) (Range.linear 1 30) Abort) + (genPackets (Range.linear 1 4) Abort (genValidPacket (pure ()) (Range.linear 1 30))) (exposeClockResetEnable model) (exposeClockResetEnable ckt) where @@ -82,7 +82,7 @@ depacketizeToDfPropertyGenerator SNat SNat = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - (genValidPackets (Range.linear 1 4) (Range.linear 1 30) NoAbort) + (genPackets (Range.linear 1 10) Abort (genValidPacket (pure ()) (Range.linear 1 20))) (exposeClockResetEnable model) (exposeClockResetEnable ckt) where diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index 175693e7..9095da0e 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -10,7 +10,8 @@ import Clash.Prelude import Data.Int (Int16) import qualified Data.List as L -import Hedgehog as H +import Hedgehog +import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import Test.Tasty @@ -30,7 +31,8 @@ prop_packetFifo_id = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - (genValidPackets (Range.linear 1 30) (Range.linear 1 10) Abort) + ( genPackets (Range.linear 1 30) Abort (genValidPacket Gen.enumBounded (Range.linear 1 10)) + ) (exposeClockResetEnable dropAbortedPackets) (exposeClockResetEnable ckt) where @@ -45,7 +47,8 @@ prop_packetFifo_small_buffer_id = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - (genValidPackets (Range.linear 1 10) (Range.linear 1 30) NoAbort) + ( genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 1 30)) + ) (exposeClockResetEnable dropAbortedPackets) (exposeClockResetEnable ckt) where @@ -65,9 +68,13 @@ prop_packetFifo_no_gaps = property $ do systemClockGen resetGen enableGen - gen = genValidPackets (Range.linear 1 10) (Range.linear 1 10) NoAbort + gen = + genPackets + (Range.linear 1 10) + NoAbort + (genValidPacket Gen.enumBounded (Range.linear 1 10)) - packets :: [PacketStreamM2S 4 Int16] <- H.forAll gen + packets :: [PacketStreamM2S 4 Int16] <- forAll gen let packetSize = 2 Prelude.^ snatToInteger packetFifoSize cfg = SimulationConfig 1 (2 * packetSize) False @@ -86,7 +93,8 @@ prop_overFlowDrop_packetFifo_id = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 2000, eoStopAfterEmpty = 1000} - (genValidPackets (Range.linear 1 30) (Range.linear 1 10) Abort) + ( genPackets (Range.linear 1 30) Abort (genValidPacket Gen.enumBounded (Range.linear 1 10)) + ) (exposeClockResetEnable dropAbortedPackets) (exposeClockResetEnable ckt) where @@ -116,8 +124,8 @@ prop_overFlowDrop_packetFifo_drop = where packetChunk = chunkByPacket packets - genSmall = genValidPacket (Range.linear 1 5) NoAbort - genBig = genValidPacket (Range.linear 33 33) NoAbort + genSmall = genValidPacket Gen.enumBounded (Range.linear 1 5) NoAbort + genBig = genValidPacket Gen.enumBounded (Range.linear 33 33) NoAbort -- | test for id using a small metabuffer to ensure backpressure using the metabuffer is tested prop_packetFifo_small_metaBuffer :: Property @@ -125,7 +133,7 @@ prop_packetFifo_small_metaBuffer = idWithModelSingleDomain @System defExpectOptions - (genValidPackets (Range.linear 1 30) (Range.linear 1 4) Abort) + (genPackets (Range.linear 1 30) Abort (genValidPacket Gen.enumBounded (Range.linear 1 4))) (exposeClockResetEnable dropAbortedPackets) (exposeClockResetEnable ckt) where diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs index 36617f6b..b7d1d742 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs @@ -40,7 +40,11 @@ packetizerPropertyGenerator SNat SNat = idWithModelSingleDomain @System defExpectOptions - (genValidPackets (Range.linear 1 10) (Range.linear 1 10) Abort) + ( genPackets + (Range.linear 1 10) + Abort + (genValidPacket (genVec Gen.enumBounded) (Range.linear 1 10)) + ) (exposeClockResetEnable model) (exposeClockResetEnable ckt) where diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs index 96e9f110..e008e8a0 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs @@ -8,6 +8,7 @@ import Clash.Prelude import qualified Clash.Prelude as C import Hedgehog hiding (Parallel) +import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import Test.Tasty @@ -64,7 +65,8 @@ makePropPacketArbiter _ _ mode = where genSources = mapM setMeta (indicesI @p) setMeta j = do - pkts <- genValidPackets @n @() (Range.linear 1 10) (Range.linear 1 10) Abort + pkts <- + genPackets @n (Range.linear 1 10) Abort (genValidPacket (pure ()) (Range.linear 1 10)) pure $ L.map (\pkt -> pkt{_meta = j}) pkts partitionPackets packets = @@ -106,6 +108,7 @@ makePropPacketDispatcher :: , 1 <= dataWidth , TestType a , Bounded a + , Enum a , BitPack a ) => SNat dataWidth -> @@ -114,7 +117,7 @@ makePropPacketDispatcher :: makePropPacketDispatcher _ fs = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 2000, eoStopAfterEmpty = 1000} - (genValidPackets (Range.linear 1 10) (Range.linear 1 6) Abort) + (genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 1 6))) (exposeClockResetEnable (model 0)) (exposeClockResetEnable (packetDispatcherC fs)) where From 62365f612d3bfa6372129e4371bf0cae6c1f0bd1 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Tue, 3 Sep 2024 17:03:06 +0200 Subject: [PATCH 38/63] Optimize delay circuit --- .../src/Protocols/PacketStream/Delay.hs | 191 ++++++++++++------ 1 file changed, 130 insertions(+), 61 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs index 06b2b941..eb813ba6 100644 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -6,7 +6,7 @@ Provides a circuit that delays a stream by a configurable amount of transfers. -} module Protocols.PacketStream.Delay ( - delayStream, + delayStreamC, ) where import Clash.Prelude @@ -14,79 +14,148 @@ import Clash.Prelude import Protocols import Protocols.PacketStream.Base +import Data.Constraint.Deferrable ((:~:) (Refl)) import Data.Maybe --- TODO Optimization: _meta only needs to be buffered once because it is constant per packet --- Holds for _abort and _last too -data DelayState n dataWidth meta = DelayState - { _buf :: Vec n (PacketStreamM2S dataWidth meta) - -- ^ Transfer buffer - , _size :: Index (n + 1) - , _flush :: Bool +type M2SNoMeta dataWidth = + (Vec dataWidth (BitVector 8), Maybe (Index dataWidth), Bool) + +toPacketstreamM2S :: M2SNoMeta dataWidth -> meta -> PacketStreamM2S dataWidth meta +toPacketstreamM2S (a, b, c) d = PacketStreamM2S a b d c + +dropMeta :: PacketStreamM2S dataWidth meta -> M2SNoMeta dataWidth +dropMeta PacketStreamM2S{..} = (_data, _last, _abort) + +-- | State of 'delayStreamT'. +data DelayState n = DelayState + { _size :: Index (n + 1) + -- ^ The number of valid transactions in the buffer. , _readPtr :: Index n + -- ^ Current block RAM read address. , _writePtr :: Index n + -- ^ Current block RAM write address. + , _flush :: Bool + -- ^ Iff true, transmit all remaining transactions of the current packet, + -- regardless of whether the buffer is full or not. + , _metaWriteEn :: Bool + -- ^ If true, write to the meta buffer. We need this in case of LHS stalls. } deriving (Generic, NFDataX, Show, ShowX) --- | Forwards incoming packets with @n@ fragments latency. -delayStream :: - forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type) (n :: Nat). +-- | State transition function of 'delayStream'. +delayStreamT :: + forall + (n :: Nat) + (dataWidth :: Nat) + (meta :: Type). + (KnownNat n) => + (KnownNat dataWidth) => + (1 <= n) => + (1 <= dataWidth) => + (NFDataX meta) => + DelayState n -> + ( Maybe (PacketStreamM2S dataWidth meta) + , PacketStreamS2M + , M2SNoMeta dataWidth + , meta + ) -> + ( DelayState n + , ( PacketStreamS2M + , Maybe (PacketStreamM2S dataWidth meta) + , Index n + , Maybe (Index n, M2SNoMeta dataWidth) + , Maybe meta + ) + ) +delayStreamT st (fwdIn, bwdIn, buff@(_, b, _), metaBuf) = + (nextStOut, (bwdOut, fwdOut, _readPtr nextStOut, writeCmd, mWriteCmd)) + where + emptyBuf = _size st == 0 + fullBuf = _size st == maxBound + + readEn = case fwdIn of + Nothing -> not emptyBuf && _flush st + Just _ -> fullBuf || _flush st + + bwdOut = PacketStreamS2M (isNothing fwdIn || not readEn || _ready bwdIn) + + (readPtr', fwdOut) = + if readEn + then (satSucc SatWrap (_readPtr st), Just (toPacketstreamM2S buff metaBuf)) + else (_readPtr st, Nothing) + + (writeCmd, mWriteCmd, writePtr') = case fwdIn of + Nothing -> (Nothing, Nothing, _writePtr st) + Just inPkt -> + ( if readEn && not (_ready bwdIn) + then Nothing + else Just (_writePtr st, dropMeta inPkt) + , if _metaWriteEn st || (emptyBuf || readEn && isJust b && _ready bwdIn) + then Just (_meta inPkt) + else Nothing + , satSucc SatWrap (_writePtr st) + ) + + metaWriteEn' = isNothing fwdIn && (_metaWriteEn st || isJust b) + + (size', flush') = case fwdIn of + Nothing -> (_size st - 1, isNothing b && _flush st) + Just inPkt + | _flush st -> + (_size st, not (readEn && isJust b) && _flush st) + | otherwise -> + (satSucc SatBound (_size st), isJust (_last inPkt)) + + nextSt = DelayState size' readPtr' writePtr' flush' metaWriteEn' + nextStOut = + if isJust fwdIn + && (isNothing fwdOut || _ready bwdIn) + || isNothing fwdIn + && (readEn && _ready bwdIn) + then nextSt + else st + +{- | +Forwards incoming packets with @n@ transactions latency. Because of potential +stalls this is not the same as @n@ clock cycles. Assumes that all packets +passing through this component are bigger than @n@ transactions. If not, this +component has __UNDEFINED BEHAVIOUR__ and things will break. +-} +delayStreamC :: + forall + (dataWidth :: Nat) + (meta :: Type) + (dom :: Domain) + (n :: Nat). (HiddenClockResetEnable dom) => (KnownNat dataWidth) => (1 <= n) => (1 <= dataWidth) => (NFDataX meta) => - -- | The number of fragments to delay + -- | The number of transactions to delay SNat n -> Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) --- TODO this component is very unoptimized/ugly. The most important thing is --- that it works now, but it should be improved in the future by removing. --- dynamic indexing and perhaps using blockram. -delayStream SNat = - forceResetSanity - |> fromSignals - (mealyB go (DelayState @n (repeat (errorX "undefined initial contents")) 0 False 0 0)) +delayStreamC SNat = forceResetSanity |> fromSignals ckt where - go st@DelayState{..} (Nothing, bwdIn) = (nextStOut, (bwdOut, fwdOut)) - where - out = _buf !! _readPtr - - readEn = _size > 0 && _flush - - fwdOut = - if readEn - then Just out - else Nothing - - bwdOut = PacketStreamS2M True - - nextSt = - st - { _size = _size - 1 - , _flush = _size - 1 > 0 - , _readPtr = satSucc SatWrap _readPtr - } - nextStOut = if readEn && _ready bwdIn then nextSt else st - go st@DelayState{..} (Just inPkt, bwdIn) = (nextStOut, (bwdOut, fwdOut)) + ckt (fwdInS, bwdInS) = (bwdOutS, fwdOutS) where - readEn = _flush || _size == maxBound - - out = _buf !! _readPtr - newBuf = replace _writePtr inPkt _buf - - fwdOut = - if readEn - then Just out - else Nothing - - bwdOut = PacketStreamS2M $ not _flush && (not readEn || _ready bwdIn) - - nextReadPtr = if readEn then satSucc SatWrap _readPtr else _readPtr - - (nextBuf, nextSize, nextFlush, nextWritePtr) - | _flush = (_buf, satPred SatBound _size, satPred SatBound _size > 0, _writePtr) - | otherwise = - (newBuf, satSucc SatBound _size, isJust (_last inPkt), satSucc SatWrap _writePtr) - - nextSt = DelayState nextBuf nextSize nextFlush nextReadPtr nextWritePtr - nextStOut = if isNothing fwdOut || _ready bwdIn then nextSt else st + noRstFunc = deepErrorX "delayStream: undefined reset function" + + -- Store the contents of transactions without metadata. + -- We only need write before read semantics in case n ~ 1. + bram :: Signal dom (M2SNoMeta dataWidth) + bram = case sameNat d1 (SNat @n) of + Nothing -> blockRamU NoClearOnReset (SNat @n) noRstFunc readAddr writeCmd + Just Refl -> readNew (blockRamU NoClearOnReset (SNat @n) noRstFunc) readAddr writeCmd + + -- There are at most two packets in the blockram, but they are required + -- to be bigger than @n@ transactions. Thus, we only need to store the + -- metadata once. + metaBuffer :: Signal dom meta + metaBuffer = regMaybe (deepErrorX "delayStream: undefined initial meta") mWriteCmd + + (bwdOutS, fwdOutS, readAddr, writeCmd, mWriteCmd) = + unbundle + $ mealy delayStreamT (DelayState @n 0 0 0 False True) input + + input = bundle (fwdInS, bwdInS, bram, metaBuffer) From 3e8636dcf5d527063a53f10510fba86084b8043e Mon Sep 17 00:00:00 2001 From: t-wallet Date: Tue, 3 Sep 2024 17:16:55 +0200 Subject: [PATCH 39/63] Add delayStream id test --- clash-protocols/src/Protocols/PacketStream/Delay.hs | 2 +- .../tests/Tests/Protocols/PacketStream.hs | 2 ++ .../tests/Tests/Protocols/PacketStream/Delay.hs | 12 +++--------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs index eb813ba6..db47d39e 100644 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -42,7 +42,7 @@ data DelayState n = DelayState } deriving (Generic, NFDataX, Show, ShowX) --- | State transition function of 'delayStream'. +-- | State transition function of 'delayStreamC'. delayStreamT :: forall (n :: Nat) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream.hs b/clash-protocols/tests/Tests/Protocols/PacketStream.hs index b7b7392a..7e7ffcb1 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream.hs @@ -4,6 +4,7 @@ import Test.Tasty import qualified Tests.Protocols.PacketStream.AsyncFifo import qualified Tests.Protocols.PacketStream.Converters +import qualified Tests.Protocols.PacketStream.Delay import qualified Tests.Protocols.PacketStream.Depacketizers import qualified Tests.Protocols.PacketStream.PacketFifo import qualified Tests.Protocols.PacketStream.Packetizers @@ -15,6 +16,7 @@ tests = "PacketStream" [ Tests.Protocols.PacketStream.AsyncFifo.tests , Tests.Protocols.PacketStream.Converters.tests + , Tests.Protocols.PacketStream.Delay.tests , Tests.Protocols.PacketStream.Depacketizers.tests , Tests.Protocols.PacketStream.PacketFifo.tests , Tests.Protocols.PacketStream.Packetizers.tests diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs index 1660b78c..eff1181e 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NumericUnderscores #-} module Tests.Protocols.PacketStream.Delay ( @@ -16,7 +15,6 @@ import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) -import Protocols import Protocols.Hedgehog import Protocols.PacketStream import Protocols.PacketStream.Hedgehog @@ -26,14 +24,10 @@ prop_delaystream_id = idWithModelSingleDomain @System defExpectOptions - (genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 1 6))) + ( genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 4 20)) + ) (exposeClockResetEnable id) - (exposeClockResetEnable ckt) - where - ckt :: - (HiddenClockResetEnable System) => - Circuit (PacketStream System 2 Int) (PacketStream System 2 Int) - ckt = delayStream @System @2 @Int @4 d4 + (exposeClockResetEnable (delayStreamC @2 @Int d4)) tests :: TestTree tests = From 8ddf374d562017ca7f8a1bb22bfb5e52fd85d2ea Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 9 Sep 2024 18:31:16 +0200 Subject: [PATCH 40/63] Add dropTailC --- .../Protocols/PacketStream/Depacketizers.hs | 139 +++++++++++++++++- .../src/Protocols/PacketStream/Hedgehog.hs | 21 +++ .../Protocols/PacketStream/Depacketizers.hs | 102 +++++++++---- 3 files changed, 236 insertions(+), 26 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index d36bab18..5f4c4446 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -2,14 +2,16 @@ {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-} +{-# OPTIONS_GHC -fplugin Protocols.Plugin #-} {-# OPTIONS_HADDOCK hide #-} {- | -Utility circuits for stripping headers from the beginning of packets. +Utility circuits for reading from a packet stream. -} module Protocols.PacketStream.Depacketizers ( depacketizerC, depacketizeToDfC, + dropTailC, ) where import Clash.Prelude @@ -18,7 +20,9 @@ import Clash.Sized.Vector.Extra import Protocols import qualified Protocols.Df as Df import Protocols.PacketStream.Base +import Protocols.PacketStream.Delay +import qualified Data.Bifunctor as B import Data.Constraint (Dict (Dict)) import Data.Constraint.Nat.Extra import Data.Data ((:~:) (Refl)) @@ -352,3 +356,136 @@ depacketizeToDfC toOut = forceResetSanity |> fromSignals ckt where ckt = case leTimesDivRu @dataWidth @headerBytes of Dict -> mealyB (depacketizeToDfT toOut) def + +-- | Information about the tail of a packet, for dropping purposes. +data DropTailInfo dataWidth delay = DropTailInfo + { _dtAborted :: Bool + -- ^ Whether any fragment of the packet was aborted + , _newIdx :: Index dataWidth + -- ^ The adjusted byte enable + , _drops :: Index (delay + 1) + -- ^ The amount of transfers to drop from the tail + , _wait :: Bool + -- ^ Iff true, apply changes to transfer the next clock cycle instead + } + +{- | +Transmits information about a single packet upon seeing its last transfer. +-} +transmitDropInfoC :: + forall (dataWidth :: Nat) (delay :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). + (KnownNat dataWidth) => + (KnownNat delay) => + (1 <= dataWidth) => + (1 <= n) => + (HiddenClockResetEnable dom) => + SNat n -> + Circuit + (PacketStream dom dataWidth meta) + (Df dom (DropTailInfo dataWidth delay)) +transmitDropInfoC SNat = forceResetSanity |> fromSignals (mealyB go False) + where + go aborted (Nothing, _) = (aborted, (PacketStreamS2M True, Df.NoData)) + go aborted (Just PacketStreamM2S{..}, Ack readyIn) = (nextAborted, (PacketStreamS2M readyIn, fwdOut)) + where + (nextAborted, fwdOut) = case _last of + Nothing -> (aborted || _abort, Df.NoData) + Just i -> (not readyIn && (aborted || _abort), Df.Data (toDropTailInfo i)) + + toDropTailInfo i = + DropTailInfo + { _dtAborted = aborted || _abort + , _newIdx = satSub SatWrap i (natToNum @(n `Mod` dataWidth)) + , _drops = drops + , _wait = wait + } + where + (drops, wait) = case ( compareSNat (SNat @dataWidth) (SNat @n) + , sameNat d0 (SNat @(n `Mod` dataWidth)) + ) of + (SNatLE, Nothing) -> + let smaller = (resize i :: Index n) < natToNum @(n - dataWidth) + in ( if smaller + then natToNum @(n `DivRU` dataWidth) + else natToNum @(n `Div` dataWidth) + , not smaller + ) + (SNatLE, Just Refl) -> + (natToNum @(n `Div` dataWidth), False) + (SNatGT, _) -> + if i >= natToNum @n + then (0, True) + else (1, False) + +{- | +Gets a delayed packet stream as input together with non-delayed +'DropTailInfo', so that dropping can be done while correctly preserving +'_abort' and adjusting '_last'. +-} +dropTailC' :: + forall (dataWidth :: Nat) (delay :: Nat) (meta :: Type) (dom :: Domain). + (KnownDomain dom) => + (KnownNat dataWidth) => + (KnownNat delay) => + (HiddenClockResetEnable dom) => + Circuit + (PacketStream dom dataWidth meta, Df dom (DropTailInfo dataWidth delay)) + (PacketStream dom dataWidth meta) +dropTailC' = fromSignals (B.first unbundle . mealyB go (0, Nothing) . B.first bundle) + where + go (0, cache) ((fwdIn, infoIn), bwdIn) = (nextStOut, ((bwdOut1, bwdOut2), fwdOut)) + where + (bwdOut1, bwdOut2) + | isNothing fwdOut = (PacketStreamS2M True, Ack True) + | otherwise = (bwdIn, Ack (_ready bwdIn)) + + (nextSt, fwdOut) = case (fwdIn, infoIn) of + (Nothing, _) -> + ((0, cache), Nothing) + (Just pkt, Df.NoData) -> + case cache of + Nothing -> + ((0, Nothing), Just pkt) + Just (aborted, newIdx, delayy) -> + ((delayy, Nothing), Just pkt{_abort = aborted, _last = Just newIdx}) + (Just pkt, Df.Data inf) -> + if _wait inf + then ((0, Just (_dtAborted inf, _newIdx inf, _drops inf)), Just pkt) + else + ( (_drops inf, Nothing) + , Just pkt{_last = Just (_newIdx inf), _abort = _dtAborted inf} + ) + + nextStOut = if isNothing fwdOut || _ready bwdIn then nextSt else (0, cache) + go (cycles, cache) _ = ((cycles - 1, cache), ((PacketStreamS2M True, Ack True), Nothing)) + +{- | +Removes the last @n@ bytes from each packet in the packet stream. +If any dropped transfers had '_abort' set, this will be preserved by +setting the '_abort' of an earlier transfer that is not dropped. + +__NB__: assumes that packets are longer than @ceil(n / dataWidth)@ transfers. +If this is not the case, the behaviour of this component is undefined. +-} +dropTailC :: + forall (dataWidth :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). + (HiddenClockResetEnable dom) => + (KnownNat dataWidth) => + (1 <= dataWidth) => + (1 <= n) => + (NFDataX meta) => + SNat n -> + Circuit + (PacketStream dom dataWidth meta) + (PacketStream dom dataWidth meta) +dropTailC SNat = case strictlyPositiveDivRu @n @dataWidth of + Dict -> + forceResetSanity + |> circuit + ( \stream -> do + [s1, s2] <- fanout -< stream + delayed <- delayStreamC (SNat @(n `DivRU` dataWidth)) -< s1 + info <- transmitDropInfoC @dataWidth @(n `DivRU` dataWidth) (SNat @n) -< s2 + dropped <- dropTailC' @dataWidth @(n `DivRU` dataWidth) -< (delayed, info) + zeroOutInvalidBytesC -< dropped + ) diff --git a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs index 63196337..f69dfcaa 100644 --- a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs +++ b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs @@ -23,6 +23,7 @@ module Protocols.PacketStream.Hedgehog ( upConvert, depacketizerModel, depacketizeToDfModel, + dropTailModel, packetizerModel, packetizeFromDfModel, @@ -244,6 +245,26 @@ depacketizeToDfModel toOut ps = L.map parseHdr bytePackets (\pkt -> L.length pkt >= C.natToNum @headerBytes) (chunkByPacket $ downConvert (dropAbortedPackets ps)) +-- | Model of 'Protocols.PacketStream.dropTailC'. +dropTailModel :: + forall dataWidth meta n. + (C.KnownNat dataWidth) => + (1 C.<= dataWidth) => + (1 C.<= n) => + C.SNat n -> + [PacketStreamM2S dataWidth meta] -> + [PacketStreamM2S dataWidth meta] +dropTailModel C.SNat packets = L.concatMap go (chunkByPacket packets) + where + go :: [PacketStreamM2S dataWidth meta] -> [PacketStreamM2S dataWidth meta] + go packet = + upConvert $ + L.init trimmed L.++ [(L.last trimmed){_last = Just 0, _abort = aborted}] + where + aborted = L.any _abort packet + bytePkts = downConvert packet + trimmed = L.take (L.length bytePkts - C.natToNum @n) bytePkts + -- | Model of the generic `Protocols.PacketStream.packetizerC`. packetizerModel :: forall diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index d62ba34d..2cb74995 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -7,7 +7,8 @@ module Tests.Protocols.PacketStream.Depacketizers ( import Clash.Prelude -import Hedgehog +import Hedgehog (Property) +import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import Test.Tasty @@ -18,8 +19,8 @@ import Test.Tasty.TH (testGroupGenerator) import Protocols import Protocols.Hedgehog -import Protocols.PacketStream (depacketizeToDfC, depacketizerC) import Protocols.PacketStream.Base +import Protocols.PacketStream.Depacketizers import Protocols.PacketStream.Hedgehog {- | Test the depacketizer with varying datawidth and number of bytes in the header, @@ -39,10 +40,9 @@ depacketizerPropertyGenerator SNat SNat = @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} (genPackets (Range.linear 1 4) Abort (genValidPacket (pure ()) (Range.linear 1 30))) - (exposeClockResetEnable model) + (exposeClockResetEnable (depacketizerModel const)) (exposeClockResetEnable ckt) where - model = depacketizerModel const ckt :: (HiddenClockResetEnable System) => Circuit @@ -50,24 +50,9 @@ depacketizerPropertyGenerator SNat SNat = (PacketStream System dataWidth (Vec headerBytes (BitVector 8))) ckt = depacketizerC const --- | headerBytes % dataWidth ~ 0 -prop_const_depacketizer_d1_d14 :: Property -prop_const_depacketizer_d1_d14 = depacketizerPropertyGenerator d1 d14 - --- | dataWidth < headerBytes -prop_const_depacketizer_d3_d11 :: Property -prop_const_depacketizer_d3_d11 = depacketizerPropertyGenerator d3 d11 - --- | dataWidth ~ header byte size -prop_const_depacketizer_d7_d7 :: Property -prop_const_depacketizer_d7_d7 = depacketizerPropertyGenerator d7 d7 - --- | dataWidth > header byte size -prop_const_depacketizer_d5_d4 :: Property -prop_const_depacketizer_d5_d4 = depacketizerPropertyGenerator d5 d4 - -{- | Test depacketizeToDf with varying datawidth and number of bytes in the header, - with metaIn = () and toMetaOut = const. +{- | +Test depacketizeToDf with varying datawidth and number of bytes in the header, +with metaIn = () and toMetaOut = const. -} depacketizeToDfPropertyGenerator :: forall @@ -83,15 +68,54 @@ depacketizeToDfPropertyGenerator SNat SNat = @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} (genPackets (Range.linear 1 10) Abort (genValidPacket (pure ()) (Range.linear 1 20))) - (exposeClockResetEnable model) + (exposeClockResetEnable (depacketizeToDfModel const)) (exposeClockResetEnable ckt) where - model = depacketizeToDfModel const ckt :: (HiddenClockResetEnable System) => Circuit (PacketStream System dataWidth ()) (Df System (Vec headerBytes (BitVector 8))) ckt = depacketizeToDfC const +-- | Test 'dropTailC' with varying data width and bytes to drop. +dropTailTest :: + forall dataWidth n. + (KnownNat n) => + (1 <= dataWidth) => + (1 <= n) => + SNat dataWidth -> + SNat n -> + Property +dropTailTest SNat n = + idWithModelSingleDomain + @System + defExpectOptions + ( genPackets + (Range.linear 1 4) + Abort + ( genValidPacket + (Gen.int8 Range.linearBounded) + (Range.linear (natToNum @(n `DivRU` dataWidth)) 20) + ) + ) + (exposeClockResetEnable (dropTailModel n)) + (exposeClockResetEnable (dropTailC @dataWidth n)) + +-- | headerBytes % dataWidth ~ 0 +prop_const_depacketizer_d1_d14 :: Property +prop_const_depacketizer_d1_d14 = depacketizerPropertyGenerator d1 d14 + +-- | dataWidth < headerBytes +prop_const_depacketizer_d3_d11 :: Property +prop_const_depacketizer_d3_d11 = depacketizerPropertyGenerator d3 d11 + +-- | dataWidth ~ header byte size +prop_const_depacketizer_d7_d7 :: Property +prop_const_depacketizer_d7_d7 = depacketizerPropertyGenerator d7 d7 + +-- | dataWidth > header byte size +prop_const_depacketizer_d5_d4 :: Property +prop_const_depacketizer_d5_d4 = depacketizerPropertyGenerator d5 d4 + -- | headerBytes % dataWidth ~ 0 prop_const_depacketize_to_df_d1_d14 :: Property prop_const_depacketize_to_df_d1_d14 = depacketizeToDfPropertyGenerator d1 d14 @@ -108,9 +132,37 @@ prop_const_depacketize_to_df_d7_d7 = depacketizeToDfPropertyGenerator d7 d7 prop_const_depacketize_to_df_d5_d4 :: Property prop_const_depacketize_to_df_d5_d4 = depacketizeToDfPropertyGenerator d5 d4 +-- | dataWidth < n && dataWidth % n ~ 0 +prop_droptail_4_bytes_d1 :: Property +prop_droptail_4_bytes_d1 = dropTailTest d4 d1 + +prop_droptail_7_bytes_d1 :: Property +prop_droptail_7_bytes_d1 = dropTailTest d7 d1 + +-- | dataWidth < n && dataWidth % n > 0 +prop_droptail_4_bytes_d3 :: Property +prop_droptail_4_bytes_d3 = dropTailTest d4 d3 + +prop_droptail_7_bytes_d3 :: Property +prop_droptail_7_bytes_d3 = dropTailTest d7 d4 + +-- | dataWidth ~ n +prop_droptail_4_bytes_d4 :: Property +prop_droptail_4_bytes_d4 = dropTailTest d4 d4 + +prop_droptail_7_bytes_d4 :: Property +prop_droptail_7_bytes_d4 = dropTailTest d7 d7 + +-- | dataWidth > n +prop_droptail_4_bytes_d7 :: Property +prop_droptail_4_bytes_d7 = dropTailTest d4 d7 + +prop_droptail_7_bytes_d7 :: Property +prop_droptail_7_bytes_d7 = dropTailTest d7 d12 + tests :: TestTree tests = localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ localOption - (HedgehogTestLimit (Just 100)) + (HedgehogTestLimit (Just 500)) $(testGroupGenerator) From cc7131490490366e9a35220786bf4de3bd22656b Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 9 Sep 2024 18:39:10 +0200 Subject: [PATCH 41/63] Fix typos in dropTailC tests --- .../Tests/Protocols/PacketStream/Depacketizers.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index 2cb74995..59d58ab8 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -143,22 +143,22 @@ prop_droptail_7_bytes_d1 = dropTailTest d7 d1 prop_droptail_4_bytes_d3 :: Property prop_droptail_4_bytes_d3 = dropTailTest d4 d3 -prop_droptail_7_bytes_d3 :: Property -prop_droptail_7_bytes_d3 = dropTailTest d7 d4 +prop_droptail_7_bytes_d4 :: Property +prop_droptail_7_bytes_d4 = dropTailTest d7 d4 -- | dataWidth ~ n prop_droptail_4_bytes_d4 :: Property prop_droptail_4_bytes_d4 = dropTailTest d4 d4 -prop_droptail_7_bytes_d4 :: Property -prop_droptail_7_bytes_d4 = dropTailTest d7 d7 +prop_droptail_7_bytes_d7 :: Property +prop_droptail_7_bytes_d7 = dropTailTest d7 d7 -- | dataWidth > n prop_droptail_4_bytes_d7 :: Property prop_droptail_4_bytes_d7 = dropTailTest d4 d7 -prop_droptail_7_bytes_d7 :: Property -prop_droptail_7_bytes_d7 = dropTailTest d7 d12 +prop_droptail_7_bytes_d12 :: Property +prop_droptail_7_bytes_d12 = dropTailTest d7 d12 tests :: TestTree tests = From f2bdeafcde87fba7b34e2def2889f0b6032d4aab Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 13 Sep 2024 14:23:04 +0200 Subject: [PATCH 42/63] Implement more basic operations --- .../src/Protocols/PacketStream/Base.hs | 263 +++++++++++++----- .../src/Protocols/PacketStream/PacketFifo.hs | 2 +- 2 files changed, 200 insertions(+), 65 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 4bd6bbb9..6615730d 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -13,37 +13,57 @@ module Protocols.PacketStream.Base ( PacketStream, -- * CSignal conversion - unsafeToPacketStream, - fromPacketStream, + toCSignal, + unsafeFromCSignal, + unsafeDropBackpressure, -- * Basic operations on the PacketStream protocol - abortOnBackPressureC, + empty, + consume, + void, fanout, forceResetSanity, + zeroOutInvalidBytesC, + abortOnBackPressureC, + + -- * Skid buffers + registerBoth, registerBwd, registerFwd, - void, - zeroOutInvalidBytesC, -- * Operations on metadata - filterMetaS, + fstMeta, + sndMeta, + mapMeta, filterMeta, + firstMeta, + secondMeta, + bimapMeta, + eitherMeta, + + -- * Operations on metadata (Signal versions) mapMetaS, - mapMeta, + filterMetaS, + firstMetaS, + secondMetaS, + bimapMetaS, + eitherMetaS, ) where -import Clash.Prelude hiding (sample) +import Clash.Prelude hiding (empty, sample) import qualified Prelude as P +import qualified Data.Bifunctor as B +import Data.Coerce (coerce) +import qualified Data.Maybe as Maybe +import Data.Proxy + import qualified Protocols.Df as Df import qualified Protocols.DfConv as DfConv import Protocols.Hedgehog.Internal import Protocols.Internal import Control.DeepSeq (NFData) -import Data.Coerce (coerce) -import qualified Data.Maybe as Maybe -import Data.Proxy {- | Data sent from manager to subordinate. @@ -197,22 +217,36 @@ instance $ Df.maybeToData <$> sampled --- | Circuit to convert a CSignal into a PacketStream. This is unsafe, because it drops backpressure. -unsafeToPacketStream :: +{- | +Circuit to convert a 'CSignal' into a 'PacketStream'. +This is unsafe, because it ignores all incoming backpressure. +-} +unsafeFromCSignal :: forall dom dataWidth meta. Circuit (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) (PacketStream dom dataWidth meta) -unsafeToPacketStream = Circuit (\(fwdInS, _) -> (pure (), fwdInS)) +unsafeFromCSignal = Circuit (\(fwdInS, _) -> (pure (), fwdInS)) --- | Converts a PacketStream into a CSignal. -fromPacketStream :: +-- | Converts a 'PacketStream' into a 'CSignal': always acknowledges. +toCSignal :: forall dom dataWidth meta. (HiddenClockResetEnable dom) => Circuit (PacketStream dom dataWidth meta) (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) -fromPacketStream = forceResetSanity |> Circuit (\(fwdIn, _) -> (pure (PacketStreamS2M True), fwdIn)) +toCSignal = forceResetSanity |> Circuit (\(fwdIn, _) -> (pure (PacketStreamS2M True), fwdIn)) + +-- | Drop all backpressure signals. +unsafeDropBackpressure :: + (HiddenClockResetEnable dom) => + Circuit + (PacketStream dom dataWidth meta) + (PacketStream dom dataWidth meta) -> + Circuit + (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) + (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) +unsafeDropBackpressure ckt = unsafeFromCSignal |> ckt |> toCSignal -- | A circuit that sets `_abort` upon backpressure from the forward circuit. abortOnBackPressureC :: @@ -237,48 +271,6 @@ forceResetSanity :: Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) forceResetSanity = forceResetSanityGeneric -{- | -Filter a packet stream based on its metadata, -with the predicate wrapped in a @Signal@. --} -filterMetaS :: - -- | Predicate which specifies whether to keep a fragment based on its metadata, - -- wrapped in a @Signal@ - Signal dom (meta -> Bool) -> - Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) -filterMetaS pS = Circuit $ \(fwdIn, bwdIn) -> unbundle (go <$> bundle (fwdIn, bwdIn, pS)) - where - go (Nothing, bwdIn, _) = (bwdIn, Nothing) - go (Just inPkt, bwdIn, predicate) - | predicate (_meta inPkt) = (bwdIn, Just inPkt) - | otherwise = (PacketStreamS2M True, Nothing) - --- | Filter a packet stream based on its metadata. -filterMeta :: - -- | Predicate which specifies whether to keep a fragment based on its metadata - (meta -> Bool) -> - Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) -filterMeta p = filterMetaS (pure p) - -{- | -Map a function on the metadata of a packet stream, -with the function wrapped in a @Signal@. --} -mapMetaS :: - -- | Function to apply on the metadata, wrapped in a @Signal@ - Signal dom (metaIn -> metaOut) -> - Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) -mapMetaS fS = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, go <$> bundle (fwdIn, fS)) - where - go (inp, f) = (\inPkt -> inPkt{_meta = f (_meta inPkt)}) <$> inp - --- | Map a function on the metadata of a packet stream. -mapMeta :: - -- | Function to apply on the metadata - (metaIn -> metaOut) -> - Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) -mapMeta f = mapMetaS (pure f) - -- | Sets data bytes that are not enabled in a @PacketStream@ to @0x00@. zeroOutInvalidBytesC :: forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type). @@ -316,7 +308,7 @@ fanout :: fanout = DfConv.fanout Proxy Proxy {- | -Place register on /forward/ part of a circuit. +Place a register on the /forward/ part of a circuit. This adds combinational delay on the /backward/ path. -} registerFwd :: @@ -328,7 +320,7 @@ registerFwd :: registerFwd = DfConv.registerFwd Proxy Proxy {- | -Place register on /backward/ part of a circuit. +Place a register on the /backward/ part of a circuit. This adds combinational delay on the /forward/ path. -} registerBwd :: @@ -339,9 +331,152 @@ registerBwd :: Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) registerBwd = DfConv.registerBwd Proxy Proxy --- | Ignore incoming data. -void :: +{- | +A pipeline skid buffer: places registers on both the /backward/ and /forward/ +part of a circuit. This completely breaks up the combinatorial path between +the left and right side of this component. In order to achieve this, it has to +buffer @Fwd@ twice. + +Another benefit of this component is that the circuit on the left hand side +may now use @Bwd@ in order to compute its @Fwd@, because this cannot +introduce combinatorial loops anymore. + +Runs at full throughput, but causes 2 clock cycles of latency. +-} +registerBoth :: forall dataWidth meta dom. (HiddenClockResetEnable dom) => - Circuit (PacketStream dom dataWidth meta) () + (KnownNat dataWidth) => + (NFDataX meta) => + Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) +registerBoth = registerBwd |> registerFwd + +-- | Never produces a value. +empty :: Circuit () (PacketStream dom dataWidth meta) +empty = Circuit (const ((), pure Nothing)) + +-- | Always acknowledges incoming data. +consume :: (HiddenReset dom) => Circuit (PacketStream dom dataWidth meta) () +consume = Circuit (const (pure (PacketStreamS2M True), ())) + +-- | Never acknowledges incoming data. +void :: (HiddenClockResetEnable dom) => Circuit (PacketStream dom dataWidth meta) () void = DfConv.void Proxy + +-- | Like 'P.fst', but over the metadata of a 'PacketStream'. +fstMeta :: Circuit (PacketStream dom dataWidth (a, b)) (PacketStream dom dataWidth a) +fstMeta = mapMeta P.fst + +-- | Like 'P.snd', but over the metadata of a 'PacketStream'. +sndMeta :: Circuit (PacketStream dom dataWidth (a, b)) (PacketStream dom dataWidth b) +sndMeta = mapMeta P.snd + +-- | Like 'Data.List.map', but over the metadata of a 'PacketStream'. +mapMeta :: + -- | Function to apply on the metadata + (metaIn -> metaOut) -> + Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) +mapMeta f = mapMetaS (pure f) + +-- | Like 'mapMeta', but can reason over signals. +mapMetaS :: + -- | Function to apply on the metadata, wrapped in a @Signal@ + Signal dom (metaIn -> metaOut) -> + Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) +mapMetaS fS = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, go <$> bundle (fwdIn, fS)) + where + go (inp, f) = (\inPkt -> inPkt{_meta = f (_meta inPkt)}) <$> inp + +-- | Like 'Data.List.filter', but over the metadata of a 'PacketStream'. +filterMeta :: + -- | Predicate which specifies whether to keep a fragment based on its metadata + (meta -> Bool) -> + Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) +filterMeta p = filterMetaS (pure p) + +-- | Like 'filterMeta', but can reason over signals. +filterMetaS :: + -- | Predicate which specifies whether to keep a fragment based on its metadata, + -- wrapped in a @Signal@ + Signal dom (meta -> Bool) -> + Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) +filterMetaS pS = Circuit $ \(fwdIn, bwdIn) -> unbundle (go <$> bundle (fwdIn, bwdIn, pS)) + where + go (Nothing, bwdIn, _) = (bwdIn, Nothing) + go (Just inPkt, bwdIn, predicate) + | predicate (_meta inPkt) = (bwdIn, Just inPkt) + | otherwise = (PacketStreamS2M True, Nothing) + +-- | Like 'Data.Either.either', but over the metadata of a 'PacketStream'. +eitherMeta :: + (a -> c) -> + (b -> c) -> + Circuit + (PacketStream dom dataWidth (Either a b)) + (PacketStream dom dataWidth c) +eitherMeta f g = eitherMetaS (pure f) (pure g) + +-- | Like 'eitherMeta', but can reason over signals. +eitherMetaS :: + Signal dom (a -> c) -> + Signal dom (b -> c) -> + Circuit + (PacketStream dom dataWidth (Either a b)) + (PacketStream dom dataWidth c) +eitherMetaS fS gS = mapMetaS (liftA2 P.either fS gS) + +-- | Like 'Data.Bifunctor.bimap', but over the metadata of a 'PacketStream'. +bimapMeta :: + (B.Bifunctor p) => + (a -> b) -> + (c -> d) -> + Circuit + (PacketStream dom dataWidth (p a c)) + (PacketStream dom dataWidth (p b d)) +bimapMeta f g = bimapMetaS (pure f) (pure g) + +-- | Like 'bimapMeta', but can reason over signals. +bimapMetaS :: + (B.Bifunctor p) => + Signal dom (a -> b) -> + Signal dom (c -> d) -> + Circuit + (PacketStream dom dataWidth (p a c)) + (PacketStream dom dataWidth (p b d)) +bimapMetaS fS gS = mapMetaS (liftA2 B.bimap fS gS) + +-- | Like 'Data.Bifunctor.first', but over the metadata of a 'PacketStream'. +firstMeta :: + (B.Bifunctor p) => + (a -> b) -> + Circuit + (PacketStream dom dataWidth (p a c)) + (PacketStream dom dataWidth (p b c)) +firstMeta f = firstMetaS (pure f) + +-- | Like 'firstMeta', but can reason over signals. +firstMetaS :: + (B.Bifunctor p) => + Signal dom (a -> b) -> + Circuit + (PacketStream dom dataWidth (p a c)) + (PacketStream dom dataWidth (p b c)) +firstMetaS fS = mapMetaS (B.first <$> fS) + +-- | Like 'Data.Bifunctor.second', but over the metadata of a 'PacketStream'. +secondMeta :: + (B.Bifunctor p) => + (b -> c) -> + Circuit + (PacketStream dom dataWidth (p a b)) + (PacketStream dom dataWidth (p a c)) +secondMeta f = secondMetaS (pure f) + +-- | Like 'secondMeta', but can reason over signals. +secondMetaS :: + (B.Bifunctor p) => + Signal dom (b -> c) -> + Circuit + (PacketStream dom dataWidth (p a b)) + (PacketStream dom dataWidth (p a c)) +secondMetaS fS = mapMetaS (B.second <$> fS) diff --git a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs index 29ee4475..e30771f1 100644 --- a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs @@ -150,7 +150,7 @@ packetFifoC cSizeBits mSizeBits mode = case mode of forceResetSanity |> fromSignals (packetFifoImpl cSizeBits mSizeBits) Drop -> - fromPacketStream + toCSignal |> abortOnBackPressureC |> forceResetSanity |> fromSignals (packetFifoImpl cSizeBits mSizeBits) From 7aa0c17e1b0182fc183d65d6eba329936b7c2a4f Mon Sep 17 00:00:00 2001 From: t-wallet Date: Sat, 14 Sep 2024 13:26:25 +0200 Subject: [PATCH 43/63] Add autoreg instances --- .../src/Protocols/PacketStream/Base.hs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 6615730d..93a8a720 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -94,12 +94,18 @@ data PacketStreamM2S (dataWidth :: Nat) (meta :: Type) = PacketStreamM2S -- ^ Iff true, the packet corresponding to this transfer is invalid. The subordinate -- must either drop the packet or forward the `_abort`. } - deriving (Eq, Generic, ShowX, Show, NFData, Bundle, Functor) + deriving (Bundle, Eq, Functor, Generic, NFData, Show, ShowX) + +deriving instance + (KnownNat dataWidth, NFDataX meta) => + NFDataX (PacketStreamM2S dataWidth meta) -- | Used by circuit-notation to create an empty stream instance Default (Maybe (PacketStreamM2S dataWidth meta)) where def = Nothing +deriveAutoReg ''PacketStreamM2S + {- | Data sent from the subordinate to manager. @@ -109,12 +115,14 @@ newtype PacketStreamS2M = PacketStreamS2M { _ready :: Bool -- ^ Iff True, the subordinate is ready to receive data. } - deriving (Eq, Generic, ShowX, Show, NFData, Bundle, NFDataX) + deriving (Bundle, Eq, Generic, NFData, NFDataX, Show, ShowX) -- | Used by circuit-notation to create a sink that always acknowledges instance Default PacketStreamS2M where def = PacketStreamS2M True +deriveAutoReg ''PacketStreamS2M + {- | Simple valid-ready streaming protocol for transferring packets between components. @@ -129,10 +137,6 @@ Invariants: -} data PacketStream (dom :: Domain) (dataWidth :: Nat) (meta :: Type) -deriving instance - (KnownNat dataWidth, NFDataX meta) => - NFDataX (PacketStreamM2S dataWidth meta) - instance Protocol (PacketStream dom dataWidth meta) where type Fwd (PacketStream dom dataWidth meta) = From 1f603258ab38d806ab97ea1db38b6e27f3901cc8 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 16 Sep 2024 11:50:45 +0200 Subject: [PATCH 44/63] Slap unsafe on abortOnBackpressureC --- .../src/Protocols/PacketStream/Base.hs | 17 ++++++++++------- .../src/Protocols/PacketStream/PacketFifo.hs | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 93a8a720..7352a0e6 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -24,7 +24,7 @@ module Protocols.PacketStream.Base ( fanout, forceResetSanity, zeroOutInvalidBytesC, - abortOnBackPressureC, + unsafeAbortOnBackpressureC, -- * Skid buffers registerBoth, @@ -252,16 +252,19 @@ unsafeDropBackpressure :: (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) unsafeDropBackpressure ckt = unsafeFromCSignal |> ckt |> toCSignal --- | A circuit that sets `_abort` upon backpressure from the forward circuit. -abortOnBackPressureC :: - forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type). +{- | +Sets '_abort' upon receiving backpressure from the subordinate. + +__UNSAFE__: because @fwdOut@ depends on @bwdIn@, this may introduce +combinatorial loops. +-} +unsafeAbortOnBackpressureC :: + forall (dataWidth :: Nat) (meta :: Type) (dom :: Domain). (HiddenClockResetEnable dom) => - (KnownNat dataWidth) => - (NFDataX meta) => Circuit (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) (PacketStream dom dataWidth meta) -abortOnBackPressureC = Circuit $ \(fwdInS, bwdInS) -> (pure (), go <$> bundle (fwdInS, bwdInS)) +unsafeAbortOnBackpressureC = Circuit $ \(fwdInS, bwdInS) -> (pure (), go <$> bundle (fwdInS, bwdInS)) where go (fwdIn, bwdIn) = fmap (\pkt -> pkt{_abort = _abort pkt || not (_ready bwdIn)}) fwdIn diff --git a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs index e30771f1..3b361074 100644 --- a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs @@ -151,7 +151,7 @@ packetFifoC cSizeBits mSizeBits mode = case mode of |> fromSignals (packetFifoImpl cSizeBits mSizeBits) Drop -> toCSignal - |> abortOnBackPressureC + |> unsafeAbortOnBackpressureC |> forceResetSanity |> fromSignals (packetFifoImpl cSizeBits mSizeBits) From df5fd6db589cccf577224ccabaf05a890df3bd49 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 23 Sep 2024 10:12:43 +0200 Subject: [PATCH 45/63] Add unsafe version of upConverterC --- .../src/Protocols/PacketStream/Base.hs | 8 +-- .../src/Protocols/PacketStream/Converters.hs | 66 ++++++++++++++++--- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 7352a0e6..9797033c 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -245,11 +245,11 @@ toCSignal = forceResetSanity |> Circuit (\(fwdIn, _) -> (pure (PacketStreamS2M T unsafeDropBackpressure :: (HiddenClockResetEnable dom) => Circuit - (PacketStream dom dataWidth meta) - (PacketStream dom dataWidth meta) -> + (PacketStream dom dwIn meta) + (PacketStream dom dwOut meta) -> Circuit - (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) - (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) + (CSignal dom (Maybe (PacketStreamM2S dwIn meta))) + (CSignal dom (Maybe (PacketStreamM2S dwOut meta))) unsafeDropBackpressure ckt = unsafeFromCSignal |> ckt |> toCSignal {- | diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index ec4291f6..215baf71 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -6,13 +6,14 @@ Provides an upconverter and downconverter for changing the data width of packet streams. -} module Protocols.PacketStream.Converters ( - upConverterC, downConverterC, + upConverterC, + unsafeUpConverterC, ) where import Clash.Prelude -import Protocols (Circuit (..), fromSignals, idC, (|>)) +import Protocols (CSignal, Circuit (..), fromSignals, idC, (|>)) import Protocols.PacketStream.Base import Data.Data ((:~:) (Refl)) @@ -100,6 +101,31 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M } nextSt = if outReady then nextStRaw else st +upConverter :: + forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). + (HiddenClockResetEnable dom) => + (1 <= dwIn) => + (1 <= dwOut) => + (1 <= n) => + (KnownNat dwIn) => + (KnownNat dwOut) => + (KnownNat n) => + (dwOut ~ dwIn * n) => + (NFDataX meta) => + ( Signal dom (Maybe (PacketStreamM2S dwIn meta)) + , Signal dom PacketStreamS2M + ) -> + ( Signal dom PacketStreamS2M + , Signal dom (Maybe (PacketStreamM2S dwOut meta)) + ) +upConverter = mealyB go s0 + where + s0 = UpConverterState (repeat undefined) 0 False True False Nothing undefined + go st@(UpConverterState{..}) (fwdIn, bwdIn) = + (nextState st fwdIn bwdIn, (PacketStreamS2M outReady, toPacketStream st)) + where + outReady = not _ucFlush || _ready bwdIn + {- | Converts packet streams of arbitrary data width @dwIn@ to packet streams of a bigger data width @dwOut@, where @dwIn@ must divide @dwOut@. When @dwIn ~ dwOut@, @@ -122,13 +148,35 @@ upConverterC :: Circuit (PacketStream dom dwIn meta) (PacketStream dom dwOut meta) upConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of Just Refl -> idC - _ -> forceResetSanity |> fromSignals (mealyB go s0) - where - s0 = UpConverterState (repeat undefined) 0 False True False Nothing undefined - go st@(UpConverterState{..}) (fwdIn, bwdIn) = - (nextState st fwdIn bwdIn, (PacketStreamS2M outReady, toPacketStream st)) - where - outReady = not _ucFlush || _ready bwdIn + _ -> forceResetSanity |> fromSignals upConverter + +{- | +Unsafe version of 'upConverterC'. + +Because 'upConverterC' runs at full throughput, i.e. it only asserts backpressure +if the subordinate asserts backpressure, we supply this variant which drops all +backpressure signals. This can be used when the source circuit does not support +backpressure. Using this variant in that case will improve timing and probably +reduce resource usage. +-} +unsafeUpConverterC :: + forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). + (HiddenClockResetEnable dom) => + (1 <= dwIn) => + (1 <= dwOut) => + (1 <= n) => + (KnownNat dwIn) => + (KnownNat dwOut) => + (KnownNat n) => + (dwOut ~ dwIn * n) => + (NFDataX meta) => + -- | Unsafe upconverter circuit + Circuit + (CSignal dom (Maybe (PacketStreamM2S dwIn meta))) + (CSignal dom (Maybe (PacketStreamM2S dwOut meta))) +unsafeUpConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of + Just Refl -> idC + _ -> unsafeDropBackpressure (fromSignals upConverter) data DownConverterState (dwIn :: Nat) = DownConverterState { _dcBuf :: Vec dwIn (BitVector 8) From 5adc39383c1976cc564edc07b1cd36209dfb0016 Mon Sep 17 00:00:00 2001 From: Tim Wallet Date: Thu, 10 Oct 2024 17:20:17 +0200 Subject: [PATCH 46/63] PacketStream: support zero-byte transfers. (#122) * Support zero-byte transfers * Add utility that merges trailing zero-byte transfers * Make PacketStream test framework more flexible Now allows users to: - Set the distribution of abort generation themselves; - Choose whether to generate zero-byte packets; - Choose whether to generate trailing zero-byte transfers. * Converters: remove expensive operations `mod` and `*` are removed. --- clash-protocols/clash-protocols.cabal | 1 + .../src/Protocols/PacketStream/Base.hs | 72 ++- .../src/Protocols/PacketStream/Converters.hs | 53 ++- .../src/Protocols/PacketStream/Delay.hs | 2 +- .../Protocols/PacketStream/Depacketizers.hs | 41 +- .../src/Protocols/PacketStream/Hedgehog.hs | 412 ++++++++++-------- .../src/Protocols/PacketStream/PacketFifo.hs | 2 +- .../src/Protocols/PacketStream/Packetizers.hs | 14 +- .../tests/Tests/Protocols/PacketStream.hs | 2 + .../Tests/Protocols/PacketStream/AsyncFifo.hs | 3 +- .../Tests/Protocols/PacketStream/Base.hs | 53 +++ .../Protocols/PacketStream/Converters.hs | 45 +- .../Tests/Protocols/PacketStream/Delay.hs | 3 +- .../Protocols/PacketStream/Depacketizers.hs | 21 +- .../Protocols/PacketStream/PacketFifo.hs | 27 +- .../Protocols/PacketStream/Packetizers.hs | 48 +- .../Tests/Protocols/PacketStream/Routing.hs | 4 +- 17 files changed, 514 insertions(+), 289 deletions(-) create mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs diff --git a/clash-protocols/clash-protocols.cabal b/clash-protocols/clash-protocols.cabal index 271b4129..fec027bf 100644 --- a/clash-protocols/clash-protocols.cabal +++ b/clash-protocols/clash-protocols.cabal @@ -205,6 +205,7 @@ test-suite unittests Tests.Protocols.Wishbone Tests.Protocols.PacketStream Tests.Protocols.PacketStream.AsyncFifo + Tests.Protocols.PacketStream.Base Tests.Protocols.PacketStream.Converters Tests.Protocols.PacketStream.Delay Tests.Protocols.PacketStream.Depacketizers diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 9797033c..702d882a 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -24,6 +24,7 @@ module Protocols.PacketStream.Base ( fanout, forceResetSanity, zeroOutInvalidBytesC, + stripTrailingEmptyC, unsafeAbortOnBackpressureC, -- * Skid buffers @@ -82,9 +83,11 @@ Heavily inspired by the M2S data of AMBA AXI4-Stream, but simplified: data PacketStreamM2S (dataWidth :: Nat) (meta :: Type) = PacketStreamM2S { _data :: Vec dataWidth (BitVector 8) -- ^ The bytes to be transmitted. - , _last :: Maybe (Index dataWidth) - -- ^ If this is @Just@ then it signals that this transfer - -- is the end of a packet and contains the index of the last valid byte in `_data`. + , _last :: Maybe (Index (dataWidth + 1)) + -- ^ If this is @Just@ then it signals that this transfer is the end of a + -- packet and contains the number of valid bytes in '_data', starting from + -- index @0@. + -- -- If it is @Nothing@ then this transfer is not yet the end of a packet and all -- bytes are valid. This implies that no null bytes are allowed in the middle of -- a packet, only after a packet. @@ -94,7 +97,7 @@ data PacketStreamM2S (dataWidth :: Nat) (meta :: Type) = PacketStreamM2S -- ^ Iff true, the packet corresponding to this transfer is invalid. The subordinate -- must either drop the packet or forward the `_abort`. } - deriving (Bundle, Eq, Functor, Generic, NFData, Show, ShowX) + deriving (Eq, Generic, ShowX, Show, NFData, Bundle, Functor) deriving instance (KnownNat dataWidth, NFDataX meta) => @@ -134,6 +137,11 @@ Invariants: 4. A subordinate which receives a transfer with `_abort` asserted must either forward this `_abort` or drop the packet. 5. A packet may not be interrupted by another packet. 6. All bytes in `_data` which are not enabled must be 0x00. + +This protocol allows the last transfer of a packet to have zero valid bytes in +'_data', so it also allows 0-byte packets. Note that concrete implementations +of the protocol are free to disallow 0-byte packets or packets with a trailing +zero-byte transfer for whatever reason. -} data PacketStream (dom :: Domain) (dataWidth :: Nat) (meta :: Type) @@ -278,6 +286,53 @@ forceResetSanity :: Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) forceResetSanity = forceResetSanityGeneric +{- | +Strips trailing zero-byte transfers from packets in the stream. That is, +if a packet consists of more than one transfer and '_last' of the last +transfer in that packet is @Just 0@, the last transfer of that packet will +be dropped and '_last' of the transfer before that will be set to @maxBound@. +If such a trailing zero-byte transfer had '_abort' asserted, it will be +preserved. + +Has one clock cycle latency, but runs at full throughput. +-} +stripTrailingEmptyC :: + forall (dataWidth :: Nat) (meta :: Type) (dom :: Domain). + (HiddenClockResetEnable dom) => + (KnownNat dataWidth) => + (NFDataX meta) => + Circuit + (PacketStream dom dataWidth meta) + (PacketStream dom dataWidth meta) +stripTrailingEmptyC = forceResetSanity |> fromSignals (mealyB go (False, False, Nothing)) + where + go (notFirst, flush, cache) (Nothing, bwdIn) = + ((notFirst, flush', cache'), (PacketStreamS2M True, fwdOut)) + where + fwdOut = if flush then cache else Nothing + (flush', cache') + | flush && _ready bwdIn = (False, Nothing) + | otherwise = (flush, cache) + go (notFirst, flush, cache) (Just transferIn, bwdIn) = (nextStOut, (bwdOut, fwdOut)) + where + (notFirst', flush', cache', fwdOut) = case _last transferIn of + Nothing -> (True, False, Just transferIn, cache) + Just i -> + let trailing = i == 0 && notFirst + in ( False + , not trailing + , if trailing then Nothing else Just transferIn + , if trailing + then (\x -> x{_last = Just maxBound, _abort = _abort x || _abort transferIn}) <$> cache + else cache + ) + + bwdOut = PacketStreamS2M (Maybe.isNothing cache || _ready bwdIn) + + nextStOut + | Maybe.isNothing cache || _ready bwdIn = (notFirst', flush', cache') + | otherwise = (notFirst, flush, cache) + -- | Sets data bytes that are not enabled in a @PacketStream@ to @0x00@. zeroOutInvalidBytesC :: forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type). @@ -292,11 +347,10 @@ zeroOutInvalidBytesC = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, fmap (go <$>) fwdIn) where dataOut = case _last transferIn of Nothing -> _data transferIn - Just i -> a ++ b - where - -- The first byte is always valid, so we only map over the rest. - (a, b') = splitAt d1 (_data transferIn) - b = imap (\(j :: Index (dataWidth - 1)) byte -> if resize j < i then byte else 0x00) b' + Just i -> + imap + (\(j :: Index dataWidth) byte -> if resize j < i then byte else 0x00) + (_data transferIn) {- | Copy data of a single `PacketStream` to multiple. LHS will only receive diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index 215baf71..ba4f17a1 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -13,12 +13,12 @@ module Protocols.PacketStream.Converters ( import Clash.Prelude -import Protocols (CSignal, Circuit (..), fromSignals, idC, (|>)) -import Protocols.PacketStream.Base - -import Data.Data ((:~:) (Refl)) import Data.Maybe (isJust) import Data.Maybe.Extra +import Data.Type.Equality ((:~:) (Refl)) + +import Protocols (CSignal, Circuit (..), fromSignals, idC, (|>)) +import Protocols.PacketStream.Base -- | Upconverter state, consisting of at most p `BitVector 8`s and a vector indicating which bytes are valid data UpConverterState (dwOut :: Nat) (n :: Nat) (meta :: Type) = UpConverterState @@ -26,17 +26,21 @@ data UpConverterState (dwOut :: Nat) (n :: Nat) (meta :: Type) = UpConverterStat -- ^ The buffer we are filling , _ucIdx :: Index n -- ^ Where in the buffer we need to write the next element + , _ucIdx2 :: Index (dwOut + 1) + -- ^ Used when @dwIn@ is not a power of two to determine the adjusted '_last', + -- to avoid multiplication (infers an expensive DSP slice). + -- If @dwIn@ is a power of two then we can multiply by shifting left. , _ucFlush :: Bool -- ^ If this is true the current state can presented as packetstream word , _ucFreshBuf :: Bool -- ^ If this is true we need to start a fresh buffer , _ucAborted :: Bool -- ^ Current packet is aborted - , _ucLastIdx :: Maybe (Index dwOut) + , _ucLastIdx :: Maybe (Index (dwOut + 1)) -- ^ If true the current buffer contains the last byte of the current packet , _ucMeta :: meta } - deriving (Generic, NFDataX) + deriving (Generic, NFDataX, Show, ShowX) toPacketStream :: UpConverterState dwOut n meta -> Maybe (PacketStreamM2S dwOut meta) toPacketStream UpConverterState{..} = toMaybe _ucFlush (PacketStreamM2S _ucBuf _ucLastIdx _ucMeta _ucAborted) @@ -89,14 +93,28 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M nextFlush = isJust _last || bufFull nextIdx = if nextFlush then 0 else _ucIdx + 1 + -- If @dwIn@ is not a power of two, we need to do some extra bookkeeping to + -- avoid multiplication. If not, _ucIdx2 stays at 0 and is never used, and + -- should therefore be optimized out by synthesis tools. + (nextIdx2, nextLastIdx) = case sameNat (SNat @(FLog 2 dwIn)) (SNat @(CLog 2 dwIn)) of + Just Refl -> + ( 0 + , (\i -> shiftL (resize _ucIdx) (natToNum @(Log 2 dwIn)) + resize i) <$> _last + ) + Nothing -> + ( if nextFlush then 0 else _ucIdx2 + natToNum @dwIn + , (\i -> _ucIdx2 + resize i) <$> _last + ) + nextStRaw = UpConverterState { _ucBuf = nextBuf , _ucIdx = nextIdx + , _ucIdx2 = nextIdx2 , _ucFlush = nextFlush , _ucFreshBuf = nextFlush , _ucAborted = nextAbort - , _ucLastIdx = (\i -> resize _ucIdx * natToNum @dwIn + resize i) <$> _last + , _ucLastIdx = nextLastIdx , _ucMeta = _meta } nextSt = if outReady then nextStRaw else st @@ -120,7 +138,17 @@ upConverter :: ) upConverter = mealyB go s0 where - s0 = UpConverterState (repeat undefined) 0 False True False Nothing undefined + s0 = + UpConverterState + { _ucBuf = deepErrorX "upConverterC: undefined initial buffer" + , _ucIdx = 0 + , _ucIdx2 = 0 + , _ucFlush = False + , _ucFreshBuf = True + , _ucAborted = False + , _ucLastIdx = Nothing + , _ucMeta = deepErrorX "upConverterC: undefined initial metadata" + } go st@(UpConverterState{..}) (fwdIn, bwdIn) = (nextState st fwdIn bwdIn, (PacketStreamS2M outReady, toPacketStream st)) where @@ -206,7 +234,7 @@ downConverterT st@DownConverterState{..} (Just inPkt, bwdIn) = (nextSt, (PacketS -- its corresponding _data. Else, we should use our stored buffer. (nextSize, buf) = case (_dcSize == 0, _last inPkt) of (True, Nothing) -> (maxBound - natToNum @dwOut, _data inPkt) - (True, Just i) -> (satSub SatBound (resize i + 1) (natToNum @dwOut), _data inPkt) + (True, Just i) -> (satSub SatBound i (natToNum @dwOut), _data inPkt) (False, _) -> (satSub SatBound _dcSize (natToNum @dwOut), _dcBuf) (newBuf, dataOut) = leToPlus @dwOut @dwIn shiftOutFrom0 (SNat @dwOut) buf @@ -221,7 +249,10 @@ downConverterT st@DownConverterState{..} (Just inPkt, bwdIn) = (nextSt, (PacketS (outReady, outLast) | nextSize == 0 = - (_ready bwdIn, resize . (\i -> i `mod` natToNum @dwOut) <$> _last inPkt) + ( _ready bwdIn + , (\i -> resize $ if _dcSize == 0 then i else _dcSize) + <$> _last inPkt + ) | otherwise = (False, Nothing) -- Keep the buffer in the state and rotate it once the byte is acknowledged to avoid @@ -256,6 +287,6 @@ downConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of where s0 = DownConverterState - { _dcBuf = errorX "downConverterC: undefined initial value" + { _dcBuf = deepErrorX "downConverterC: undefined initial buffer" , _dcSize = 0 } diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs index db47d39e..9741af8b 100644 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -18,7 +18,7 @@ import Data.Constraint.Deferrable ((:~:) (Refl)) import Data.Maybe type M2SNoMeta dataWidth = - (Vec dataWidth (BitVector 8), Maybe (Index dataWidth), Bool) + (Vec dataWidth (BitVector 8), Maybe (Index (dataWidth + 1)), Bool) toPacketstreamM2S :: M2SNoMeta dataWidth -> meta -> PacketStreamM2S dataWidth meta toPacketstreamM2S (a, b, c) d = PacketStreamM2S a b d c diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 5f4c4446..35ca2222 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -121,7 +121,7 @@ depacketizerT _ Parse{..} (Just PacketStreamM2S{..}, _) = (nextStOut, (PacketStr nextParseBuf = fst (shiftInAtN _buf _data) prematureEnd idx = case sameNat d0 (SNat @(headerBytes `Mod` dataWidth)) of - Just Refl -> True + Just Refl -> idx /= maxBound _ -> idx < natToNum @(headerBytes `Mod` dataWidth) -- Upon seeing _last being set, move back to the initial state if the @@ -150,8 +150,12 @@ depacketizerT toMetaOut st@Forward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = ( (dataOut, nextFwdBytes) = splitAt (SNat @dataWidth) (fwdBytes ++ _data) -- Only use if headerBytes `Mod` dataWidth > 0. - adjustLast :: Index dataWidth -> Either (Index dataWidth) (Index dataWidth) - adjustLast idx = if idx < x then Left (idx + y) else Right (idx - x) + adjustLast :: + Index (dataWidth + 1) -> Either (Index (dataWidth + 1)) (Index (dataWidth + 1)) + adjustLast idx + | _lastFwd = Right (idx - x) + | idx <= x = Left (idx + y) + | otherwise = Right (idx - x) where x = natToNum @(headerBytes `Mod` dataWidth) y = natToNum @(ForwardBytes headerBytes dataWidth) @@ -159,7 +163,9 @@ depacketizerT toMetaOut st@Forward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = ( outPkt = case sameNat d0 (SNat @(headerBytes `Mod` dataWidth)) of Just Refl -> pkt - { _meta = toMetaOut (bitCoerce header) _meta + { _data = if _lastFwd then repeat 0x00 else _data + , _last = if _lastFwd then Just 0 else _last + , _meta = toMetaOut (bitCoerce header) _meta , _abort = nextAborted } Nothing -> @@ -289,8 +295,8 @@ depacketizeToDfT _ DfParse{..} (Just (PacketStreamM2S{..}), _) = (nextStOut, (Pa prematureEnd idx = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of - SNatGT -> idx < (natToNum @(headerBytes `Mod` dataWidth - 1)) - _ -> idx < (natToNum @(dataWidth - 1)) + SNatGT -> idx < (natToNum @(headerBytes `Mod` dataWidth)) + _ -> idx < (natToNum @dataWidth) (nextStOut, readyOut) = case (_dfCounter == 0, _last) of @@ -361,7 +367,7 @@ depacketizeToDfC toOut = forceResetSanity |> fromSignals ckt data DropTailInfo dataWidth delay = DropTailInfo { _dtAborted :: Bool -- ^ Whether any fragment of the packet was aborted - , _newIdx :: Index dataWidth + , _newIdx :: Index (dataWidth + 1) -- ^ The adjusted byte enable , _drops :: Index (delay + 1) -- ^ The amount of transfers to drop from the tail @@ -395,27 +401,28 @@ transmitDropInfoC SNat = forceResetSanity |> fromSignals (mealyB go False) toDropTailInfo i = DropTailInfo { _dtAborted = aborted || _abort - , _newIdx = satSub SatWrap i (natToNum @(n `Mod` dataWidth)) + , _newIdx = newIdx , _drops = drops , _wait = wait } where - (drops, wait) = case ( compareSNat (SNat @dataWidth) (SNat @n) - , sameNat d0 (SNat @(n `Mod` dataWidth)) - ) of + (newIdx, drops, wait) = case ( compareSNat (SNat @dataWidth) (SNat @n) + , sameNat d0 (SNat @(n `Mod` dataWidth)) + ) of (SNatLE, Nothing) -> - let smaller = (resize i :: Index n) < natToNum @(n - dataWidth) - in ( if smaller + let smaller = (resize i :: Index n) <= natToNum @(n - dataWidth) + in ( satSub SatWrap i (natToNum @(n `Mod` dataWidth) + (if smaller then 1 else 0)) + , if smaller then natToNum @(n `DivRU` dataWidth) else natToNum @(n `Div` dataWidth) , not smaller ) (SNatLE, Just Refl) -> - (natToNum @(n `Div` dataWidth), False) + (i, natToNum @(n `Div` dataWidth), False) (SNatGT, _) -> - if i >= natToNum @n - then (0, True) - else (1, False) + if i > natToNum @n + then (i - natToNum @n, 0, True) + else (maxBound - (natToNum @n - i), 1, False) {- | Gets a delayed packet stream as input together with non-delayed diff --git a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs index f69dfcaa..dbfa166b 100644 --- a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs +++ b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs @@ -1,4 +1,5 @@ {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE NoImplicitPrelude #-} {- | Copyright : (C) 2024, QBayLogic B.V. @@ -29,24 +30,25 @@ module Protocols.PacketStream.Hedgehog ( -- * Hedgehog generators AbortMode (..), + PacketOptions (..), + defPacketOptions, genValidPacket, genPackets, ) where -import Prelude - import Clash.Hedgehog.Sized.Vector (genVec) -import qualified Clash.Prelude as C +import Clash.Prelude import qualified Clash.Sized.Vector as Vec +import qualified Data.List as L +import Data.Maybe (fromJust, isJust) + import Hedgehog (Gen, Range) import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range import Protocols.PacketStream.Base -import qualified Data.List as L -import Data.Maybe (fromJust, isJust) - -- | Partition a list based on given function. chunkBy :: (a -> Bool) -> [a] -> [[a]] chunkBy _ [] = [] @@ -82,28 +84,27 @@ smearAbort (x : xs) = L.reverse $ L.foldl' go [x] xs -- | Partition a list into groups of given size chopBy :: Int -> [a] -> [[a]] chopBy _ [] = [] -chopBy n xs = as : chopBy n bs where (as, bs) = splitAt n xs +chopBy n xs = as : chopBy n bs where (as, bs) = L.splitAt n xs {- | Merge a list of `PacketStream` transfers with data width @1@ to a single `PacketStream` transfer with data width @dataWidth@. -} chunkToPacket :: - (C.KnownNat dataWidth) => + (KnownNat dataWidth) => [PacketStreamM2S 1 meta] -> PacketStreamM2S dataWidth meta -chunkToPacket l = +chunkToPacket xs = PacketStreamM2S { _last = - if isJust (_last lastTransfer) - then Just (fromIntegral $ L.length l - 1) - else Nothing - , _abort = any _abort l + (\i -> let l = fromIntegral (L.length xs) in if i == 0 then l - 1 else l) + <$> _last lastTransfer + , _abort = any _abort xs , _meta = _meta lastTransfer - , _data = foldr ((C.+>>) . C.head . _data) (C.repeat 0) l + , _data = L.foldr ((+>>) . head . _data) (repeat 0x00) xs } where - lastTransfer = L.last l + lastTransfer = L.last xs {- | Split a single `PacketStream` transfer with data width @dataWidth@ to @@ -111,41 +112,44 @@ a list of `PacketStream` transfers with data width @1@. -} chopPacket :: forall dataWidth meta. - (1 C.<= dataWidth) => - (C.KnownNat dataWidth) => + (1 <= dataWidth) => + (KnownNat dataWidth) => PacketStreamM2S dataWidth meta -> [PacketStreamM2S 1 meta] chopPacket PacketStreamM2S{..} = packets where lasts = case _last of - Nothing -> repeat Nothing - Just in' -> replicate (fromIntegral in') Nothing ++ [Just (0 :: C.Index 1)] + Nothing -> L.repeat Nothing + Just size -> + if size == 0 + then [Just 0] + else L.replicate (fromIntegral size - 1) Nothing L.++ [Just (1 :: Index 2)] datas = case _last of - Nothing -> C.toList _data - Just in' -> take (fromIntegral in' + 1) $ C.toList _data + Nothing -> toList _data + Just size -> L.take (max 1 (fromIntegral size)) $ toList _data packets = - ( \(idx, dat) -> - PacketStreamM2S (pure dat) idx _meta _abort + ( \(size, dat) -> + PacketStreamM2S (pure dat) size _meta _abort ) - <$> zip lasts datas + <$> L.zip lasts datas --- | Set `_last` of the last transfer in the list to @Just 0@ +-- | Set `_last` of the last transfer in the list to @Just 1@ fullPackets :: - (C.KnownNat dataWidth) => + (KnownNat dataWidth) => [PacketStreamM2S dataWidth meta] -> [PacketStreamM2S dataWidth meta] fullPackets [] = [] fullPackets fragments = - let lastFragment = (last fragments){_last = Just 0} - in init fragments ++ [lastFragment] + let lastFragment = (L.last fragments){_last = Just 1} + in L.init fragments L.++ [lastFragment] -- | Drops packets if one of the transfers in the packet has `_abort` set. dropAbortedPackets :: [PacketStreamM2S dataWidth meta] -> [PacketStreamM2S dataWidth meta] -dropAbortedPackets packets = concat $ filter (not . any _abort) (chunkByPacket packets) +dropAbortedPackets packets = L.concat $ L.filter (not . any _abort) (chunkByPacket packets) {- | Splits a list of `PacketStream` transfers with data width @1@ into @@ -153,11 +157,11 @@ a list of `PacketStream` transfers with data width @dataWidth@ -} downConvert :: forall dataWidth meta. - (1 C.<= dataWidth) => - (C.KnownNat dataWidth) => + (1 <= dataWidth) => + (KnownNat dataWidth) => [PacketStreamM2S dataWidth meta] -> [PacketStreamM2S 1 meta] -downConvert = concatMap chopPacket +downConvert = L.concatMap chopPacket {- | Merges a list of `PacketStream` transfers with data width @dataWidth@ into @@ -165,51 +169,69 @@ a list of `PacketStream` transfers with data width @1@ -} upConvert :: forall dataWidth meta. - (1 C.<= dataWidth) => - (C.KnownNat dataWidth) => + (1 <= dataWidth) => + (KnownNat dataWidth) => [PacketStreamM2S 1 meta] -> [PacketStreamM2S dataWidth meta] upConvert packets = - map + L.map chunkToPacket - (chunkByPacket packets >>= chopBy (C.natToNum @dataWidth)) + (chunkByPacket packets >>= chopBy (natToNum @dataWidth)) -- | Model of the generic `Protocols.PacketStream.depacketizerC`. depacketizerModel :: forall - (dataWidth :: C.Nat) - (headerBytes :: C.Nat) - (metaIn :: C.Type) - (header :: C.Type) - (metaOut :: C.Type). - (C.KnownNat dataWidth) => - (C.KnownNat headerBytes) => - (1 C.<= dataWidth) => - (1 C.<= headerBytes) => - (C.BitPack header) => - (C.BitSize header ~ headerBytes C.* 8) => + (dataWidth :: Nat) + (headerBytes :: Nat) + (metaIn :: Type) + (header :: Type) + (metaOut :: Type). + (KnownNat dataWidth) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + (1 <= headerBytes) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => (header -> metaIn -> metaOut) -> [PacketStreamM2S dataWidth metaIn] -> [PacketStreamM2S dataWidth metaOut] depacketizerModel toMetaOut ps = L.concat dataWidthPackets where - hdrbytes = C.natToNum @headerBytes + hdrbytes = natToNum @headerBytes parseHdr :: ([PacketStreamM2S 1 metaIn], [PacketStreamM2S 1 metaIn]) -> [PacketStreamM2S 1 metaOut] - parseHdr (hdrF, fwdF) = fmap (\f -> f{_meta = metaOut}) fwdF + parseHdr (hdrF, fwdF) = fmap (\f -> f{_meta = metaOut}) fwdF' where - hdr = C.bitCoerce $ Vec.unsafeFromList @headerBytes $ _data <$> hdrF - metaOut = toMetaOut hdr (_meta $ L.head fwdF) + fwdF' = case fwdF of + [] -> + [ PacketStreamM2S + (Vec.singleton 0x00) + (Just 0) + (error "depacketizerModel: should be replaced") + (_abort (L.last hdrF)) + ] + _ -> fwdF + + hdr = bitCoerce $ Vec.unsafeFromList @headerBytes $ _data <$> hdrF + metaOut = toMetaOut hdr (_meta $ L.head hdrF) bytePackets :: [[PacketStreamM2S 1 metaIn]] bytePackets = - L.filter (\fs -> L.length fs > hdrbytes) $ - L.concatMap chopPacket . smearAbort <$> chunkByPacket ps + L.filter + ( \fs -> + let len' = L.length fs + in len' > hdrbytes || len' == hdrbytes && _last (L.last fs) == Just 1 + ) + $ downConvert + . smearAbort + <$> chunkByPacket ps parsedPackets :: [[PacketStreamM2S 1 metaOut]] - parsedPackets = parseHdr . L.splitAt hdrbytes <$> bytePackets + parsedPackets = L.map go bytePackets + + go = parseHdr . L.splitAt hdrbytes dataWidthPackets :: [[PacketStreamM2S dataWidth metaOut]] dataWidthPackets = L.map upConvert parsedPackets @@ -217,17 +239,17 @@ depacketizerModel toMetaOut ps = L.concat dataWidthPackets -- | Model of the generic `Protocols.PacketStream.depacketizeToDfC`. depacketizeToDfModel :: forall - (dataWidth :: C.Nat) - (headerBytes :: C.Nat) - (a :: C.Type) - (header :: C.Type) - (metaIn :: C.Type). - (C.KnownNat dataWidth) => - (C.KnownNat headerBytes) => - (1 C.<= dataWidth) => - (1 C.<= headerBytes) => - (C.BitPack header) => - (C.BitSize header ~ headerBytes C.* 8) => + (dataWidth :: Nat) + (headerBytes :: Nat) + (a :: Type) + (header :: Type) + (metaIn :: Type). + (KnownNat dataWidth) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + (1 <= headerBytes) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => (header -> metaIn -> a) -> [PacketStreamM2S dataWidth metaIn] -> [a] @@ -236,49 +258,53 @@ depacketizeToDfModel toOut ps = L.map parseHdr bytePackets parseHdr :: [PacketStreamM2S 1 metaIn] -> a parseHdr hdrF = toOut - (C.bitCoerce $ Vec.unsafeFromList $ _data <$> hdrF) + (bitCoerce $ Vec.unsafeFromList $ _data <$> hdrF) (_meta $ L.head hdrF) bytePackets :: [[PacketStreamM2S 1 metaIn]] bytePackets = L.filter - (\pkt -> L.length pkt >= C.natToNum @headerBytes) + ( \pkt -> + (L.length pkt > natToNum @headerBytes) + || (L.length pkt == natToNum @headerBytes && _last (L.last pkt) == Just 1) + ) (chunkByPacket $ downConvert (dropAbortedPackets ps)) -- | Model of 'Protocols.PacketStream.dropTailC'. dropTailModel :: forall dataWidth meta n. - (C.KnownNat dataWidth) => - (1 C.<= dataWidth) => - (1 C.<= n) => - C.SNat n -> + (KnownNat dataWidth) => + (1 <= dataWidth) => + (1 <= n) => + SNat n -> [PacketStreamM2S dataWidth meta] -> [PacketStreamM2S dataWidth meta] -dropTailModel C.SNat packets = L.concatMap go (chunkByPacket packets) +dropTailModel SNat packets = L.concatMap go (chunkByPacket packets) where go :: [PacketStreamM2S dataWidth meta] -> [PacketStreamM2S dataWidth meta] go packet = - upConvert $ - L.init trimmed L.++ [(L.last trimmed){_last = Just 0, _abort = aborted}] + upConvert + $ L.init trimmed + L.++ [setNull (L.last trimmed){_last = _last $ L.last bytePkts, _abort = aborted}] where aborted = L.any _abort packet bytePkts = downConvert packet - trimmed = L.take (L.length bytePkts - C.natToNum @n) bytePkts + trimmed = L.take (L.length bytePkts - natToNum @n) bytePkts -- | Model of the generic `Protocols.PacketStream.packetizerC`. packetizerModel :: forall - (dataWidth :: C.Nat) - (headerBytes :: C.Nat) - (metaIn :: C.Type) - (header :: C.Type) - (metaOut :: C.Type). - (C.KnownNat dataWidth) => - (C.KnownNat headerBytes) => - (1 C.<= dataWidth) => - (1 C.<= headerBytes) => - (C.BitPack header) => - (C.BitSize header ~ headerBytes C.* 8) => + (dataWidth :: Nat) + (headerBytes :: Nat) + (metaIn :: Type) + (header :: Type) + (metaOut :: Type). + (KnownNat dataWidth) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + (1 <= headerBytes) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => (metaIn -> metaOut) -> (metaIn -> header) -> [PacketStreamM2S dataWidth metaIn] -> @@ -290,8 +316,8 @@ packetizerModel toMetaOut toHeader ps = L.concatMap (upConvert . prependHdr) byt where h = L.head fragments metaOut = toMetaOut (_meta h) - hdr = L.map go (C.toList $ C.bitCoerce (toHeader (_meta h))) - go byte = PacketStreamM2S (C.singleton byte) Nothing metaOut (_abort h) + hdr = L.map go (toList $ bitCoerce (toHeader (_meta h))) + go byte = PacketStreamM2S (singleton byte) Nothing metaOut (_abort h) bytePackets :: [[PacketStreamM2S 1 metaIn]] bytePackets = downConvert . smearAbort <$> chunkByPacket ps @@ -299,17 +325,17 @@ packetizerModel toMetaOut toHeader ps = L.concatMap (upConvert . prependHdr) byt -- | Model of the generic `Protocols.PacketStream.packetizeFromDfC`. packetizeFromDfModel :: forall - (dataWidth :: C.Nat) - (headerBytes :: C.Nat) - (a :: C.Type) - (header :: C.Type) - (metaOut :: C.Type). - (C.KnownNat dataWidth) => - (C.KnownNat headerBytes) => - (1 C.<= dataWidth) => - (1 C.<= headerBytes) => - (C.BitPack header) => - (C.BitSize header ~ headerBytes C.* 8) => + (dataWidth :: Nat) + (headerBytes :: Nat) + (a :: Type) + (header :: Type) + (metaOut :: Type). + (KnownNat dataWidth) => + (KnownNat headerBytes) => + (1 <= dataWidth) => + (1 <= headerBytes) => + (BitPack header) => + (BitSize header ~ headerBytes * 8) => (a -> metaOut) -> (a -> header) -> [a] -> @@ -318,126 +344,166 @@ packetizeFromDfModel toMetaOut toHeader = L.concatMap (upConvert . dfToPacket) where dfToPacket :: a -> [PacketStreamM2S 1 metaOut] dfToPacket d = - fullPackets $ - L.map - (\byte -> PacketStreamM2S (C.singleton byte) Nothing (toMetaOut d) False) - (C.toList $ C.bitCoerce (toHeader d)) + fullPackets + $ L.map + (\byte -> PacketStreamM2S (singleton byte) Nothing (toMetaOut d) False) + (toList $ bitCoerce (toHeader d)) + +-- | Abort generation options for packet generation. +data AbortMode + = Abort + { amPacketGen :: Gen Bool + -- ^ Determines the chance to generate aborted fragments in a packet. + , amTransferGen :: Gen Bool + -- ^ Determines the frequency of aborted fragments in a packet. + } + | NoAbort + +-- | Various configuration options for packet generation. +data PacketOptions = PacketOptions + { poAllowEmptyPackets :: Bool + -- ^ Whether to allow the generation of zero-byte packets. + , poAllowTrailingEmpty :: Bool + -- ^ Whether to allow the generation of trailing zero-byte transfers. + , poAbortMode :: AbortMode + -- ^ If set to @NoAbort@, no transfers in the packet will have '_abort' set. + -- Else, randomly generate them according to some distribution. See 'AbortMode'. + } {- | -If set to @NoAbort@, packets will never contain a transfer with _abort set. -Otherwise, transfers of roughly 50% of the packets will randomly have _abort set. +Default packet generation options: + +- Allow the generation of a zero-byte packet; +- Allow the generation of a trailing zero-byte transfer; +- 50% chance to generate aborted transfers. If aborts are generated, 10% of + transfers will be aborted. -} -data AbortMode = Abort | NoAbort +defPacketOptions :: PacketOptions +defPacketOptions = + PacketOptions + { poAllowEmptyPackets = True + , poAllowTrailingEmpty = True + , poAbortMode = + Abort + { amPacketGen = Gen.enumBounded + , amTransferGen = + Gen.frequency + [ (90, Gen.constant False) + , (10, Gen.constant True) + ] + } + } {- | -Generate packets with a user-supplied generator. +Generate packets with a user-supplied generator and a linear range. -} genPackets :: - forall (dataWidth :: C.Nat) (meta :: C.Type). - (1 C.<= dataWidth) => - (C.KnownNat dataWidth) => - -- | The amount of packets to generate. - Range Int -> - -- | If set to @NoAbort@, always pass @NoAbort@ to the packet generator. - -- Else, pass @Abort@ to roughly 50% of the packet generators. - AbortMode -> + forall (dataWidth :: Nat) (meta :: Type). + (1 <= dataWidth) => + (KnownNat dataWidth) => + -- | Minimum amount of packets to generate. + Int -> + -- | Maximum amount of packets to generate. + Int -> -- | Packet generator. - (AbortMode -> Gen [PacketStreamM2S dataWidth meta]) -> + Gen [PacketStreamM2S dataWidth meta] -> Gen [PacketStreamM2S dataWidth meta] -genPackets pkts Abort pktGen = - concat - <$> Gen.list - pkts - (Gen.choice [pktGen Abort, pktGen NoAbort]) -genPackets pkts NoAbort pktGen = - concat - <$> Gen.list - pkts - (pktGen NoAbort) +genPackets minB maxB pktGen = L.concat <$> Gen.list (Range.linear minB maxB) pktGen +{-# INLINE genPackets #-} {- | Generate a valid packet, i.e. a packet of which all transfers carry the same `_meta` and with all unenabled bytes in `_data` set to 0x00. -} genValidPacket :: - forall (dataWidth :: C.Nat) (meta :: C.Type). - (1 C.<= dataWidth) => - (C.KnownNat dataWidth) => + forall (dataWidth :: Nat) (meta :: Type). + (1 <= dataWidth) => + (KnownNat dataWidth) => + -- | Configuration options for packet generation. + PacketOptions -> -- | Generator for the metadata. Gen meta -> -- | The amount of transfers with @_last = Nothing@ to generate. -- This function will always generate an extra transfer with @_last = Just i@. Range Int -> - -- | If set to @NoAbort@, no transfers in the packet will have @_abort@ set. - -- Else, each transfer has a 10% chance to have @_abort@ set. - AbortMode -> Gen [PacketStreamM2S dataWidth meta] -genValidPacket metaGen size abortMode = do - meta <- metaGen - transfers <- Gen.list size (genTransfer @dataWidth meta abortMode) - lastTransfer <- genLastTransfer @dataWidth meta abortMode - pure (transfers ++ [lastTransfer]) +genValidPacket PacketOptions{..} metaGen size = + let + abortGen = case poAbortMode of + NoAbort -> Gen.constant False + Abort pktGen transferGen -> do + allowAborts <- pktGen + (if allowAborts then transferGen else Gen.constant False) + in + do + meta <- metaGen + transfers <- Gen.list size (genTransfer meta abortGen) + lastTransfer <- + genLastTransfer + meta + ( (null transfers && poAllowEmptyPackets) + || (not (null transfers) && poAllowTrailingEmpty) + ) + abortGen + pure (transfers L.++ [lastTransfer]) -- | Generate a single transfer which is not yet the end of a packet. genTransfer :: - forall (dataWidth :: C.Nat) (meta :: C.Type). - (1 C.<= dataWidth) => - (C.KnownNat dataWidth) => + forall (dataWidth :: Nat) (meta :: Type). + (1 <= dataWidth) => + (KnownNat dataWidth) => -- | We need to use the same metadata -- for every transfer in a packet to satisfy the protocol -- invariant that metadata is constant for an entire packet. meta -> - -- | If set to @NoAbort@, hardcode @_abort@ to @False@. Else, - -- there is a 10% chance for it to be set. - AbortMode -> + -- | Whether to set '_abort'. + Gen Bool -> Gen (PacketStreamM2S dataWidth meta) -genTransfer meta abortMode = +genTransfer meta abortGen = PacketStreamM2S <$> genVec Gen.enumBounded <*> Gen.constant Nothing <*> Gen.constant meta - <*> case abortMode of - Abort -> - Gen.frequency - [ (90, Gen.constant False) - , (10, Gen.constant True) - ] - NoAbort -> Gen.constant False + <*> abortGen {- | Generate the last transfer of a packet, i.e. a transfer with @_last@ set as @Just@. All bytes which are not enabled are set to 0x00. -} genLastTransfer :: - forall (dataWidth :: C.Nat) (meta :: C.Type). - (1 C.<= dataWidth) => - (C.KnownNat dataWidth) => + forall (dataWidth :: Nat) (meta :: Type). + (1 <= dataWidth) => + (KnownNat dataWidth) => -- | We need to use the same metadata -- for every transfer in a packet to satisfy the protocol -- invariant that metadata is constant for an entire packet. meta -> - -- | If set to @NoAbort@, hardcode @_abort@ to @False@. Else, - -- randomly generate it. - AbortMode -> + -- | Whether we are allowed to generate a 0-byte transfer. + Bool -> + -- | Whether to set '_abort'. + Gen Bool -> Gen (PacketStreamM2S dataWidth meta) -genLastTransfer meta abortMode = +genLastTransfer meta allowEmpty abortGen = setNull <$> ( PacketStreamM2S <$> genVec Gen.enumBounded - <*> (Just <$> Gen.enumBounded) + <*> (Just <$> Gen.enum (if allowEmpty then 0 else 1) maxBound) <*> Gen.constant meta - <*> case abortMode of - Abort -> Gen.enumBounded - NoAbort -> Gen.constant False + <*> abortGen ) - where - setNull transfer = - let i = fromJust (_last transfer) - in transfer - { _data = - fromJust - ( Vec.fromList $ - take (1 + fromIntegral i) (C.toList (_data transfer)) - ++ replicate ((C.natToNum @dataWidth) - 1 - fromIntegral i) 0x00 - ) - } + +setNull :: + forall (dataWidth :: Nat) (meta :: Type). + (KnownNat dataWidth) => + PacketStreamM2S dataWidth meta -> + PacketStreamM2S dataWidth meta +setNull transfer = + let i = fromJust (_last transfer) + in transfer + { _data = + fromJust + ( Vec.fromList + $ L.take (fromIntegral i) (toList (_data transfer)) + L.++ L.replicate ((natToNum @dataWidth) - fromIntegral i) 0x00 + ) + } diff --git a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs index 3b361074..78a55426 100644 --- a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs @@ -19,7 +19,7 @@ import Data.Maybe import Data.Maybe.Extra type PacketStreamContent (dataWidth :: Nat) (meta :: Type) = - (Vec dataWidth (BitVector 8), Maybe (Index dataWidth)) + (Vec dataWidth (BitVector 8), Maybe (Index (dataWidth + 1))) toPacketStreamContent :: PacketStreamM2S dataWidth meta -> PacketStreamContent dataWidth meta diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index 716f7e25..84d0e2f0 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -87,7 +87,7 @@ packetizerT1 toMetaOut toHeader st (Just inPkt, bwdIn) = (nextSt, newLast) = case _last inPkt of Nothing -> (Forward1 nextAborted newBuf, Nothing) Just i - | i < natToNum @(dataWidth - headerBytes) -> + | i <= natToNum @(dataWidth - headerBytes) -> (Insert1 False, Just (i + natToNum @headerBytes)) | otherwise -> (LastForward1 nextAborted newBuf, Nothing) @@ -237,7 +237,7 @@ packetizerT3 toMetaOut _ st@Insert3{..} (Just inPkt, bwdIn) = (False, _) -> (Nothing, Insert3 nextAborted newHdrBuf (succ _counter3)) (True, Nothing) -> (Nothing, Forward3 nextAborted newHdrBuf) (True, Just i) -> - if i < natToNum @(dataWidth - headerBytes `Mod` dataWidth) + if i <= natToNum @(dataWidth - headerBytes `Mod` dataWidth) then (Just (i + natToNum @(headerBytes `Mod` dataWidth)), LoadHeader3) else (Nothing, LastForward3 nextAborted newHdrBuf) nextStOut = if _ready bwdIn then nextSt else st @@ -266,7 +266,7 @@ packetizerT3 toMetaOut _ st@Forward3{..} (Just inPkt, bwdIn) = (lastOut, nextSt) = case _last inPkt of Nothing -> (Nothing, Forward3 nextAborted newBuf) Just i -> - if i < natToNum @(dataWidth - headerBytes `Mod` dataWidth) + if i <= natToNum @(dataWidth - headerBytes `Mod` dataWidth) then (Just (i + natToNum @(headerBytes `Mod` dataWidth)), LoadHeader3) else (Nothing, LastForward3 nextAborted newBuf) nextStOut = if _ready bwdIn then nextSt else st @@ -375,8 +375,8 @@ packetizeFromDfT toMetaOut _ st@DfInsert{..} (Df.Data dataIn, bwdIn) = (nextStOu outPkt = PacketStreamM2S dataOut newLast (toMetaOut dataIn) False newLast = toMaybe (_dfCounter == maxBound) $ case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of - SNatGT -> natToNum @(headerBytes `Mod` dataWidth - 1) - _ -> natToNum @(dataWidth - 1) + SNatGT -> natToNum @(headerBytes `Mod` dataWidth) + _ -> natToNum @dataWidth bwdOut = Ack (_ready bwdIn && _dfCounter == maxBound) nextSt = if _dfCounter == maxBound then DfIdle else DfInsert (succ _dfCounter) newHdrBuf @@ -421,6 +421,6 @@ packetizeFromDfC toMetaOut toHeader = case strictlyPositiveDivRu @headerBytes @d outPkt = PacketStreamM2S dataOut (Just l) (toMetaOut dataIn) False dataOut = bitCoerce (toHeader dataIn) ++ repeat @(dataWidth - headerBytes) defaultByte l = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of - SNatGT -> natToNum @(headerBytes `Mod` dataWidth - 1) - _ -> natToNum @(dataWidth - 1) + SNatGT -> natToNum @(headerBytes `Mod` dataWidth) + _ -> natToNum @dataWidth SNatGT -> fromSignals (mealyB (packetizeFromDfT toMetaOut toHeader) DfIdle) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream.hs b/clash-protocols/tests/Tests/Protocols/PacketStream.hs index 7e7ffcb1..f83077f5 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream.hs @@ -3,6 +3,7 @@ module Tests.Protocols.PacketStream (tests) where import Test.Tasty import qualified Tests.Protocols.PacketStream.AsyncFifo +import qualified Tests.Protocols.PacketStream.Base import qualified Tests.Protocols.PacketStream.Converters import qualified Tests.Protocols.PacketStream.Delay import qualified Tests.Protocols.PacketStream.Depacketizers @@ -15,6 +16,7 @@ tests = testGroup "PacketStream" [ Tests.Protocols.PacketStream.AsyncFifo.tests + , Tests.Protocols.PacketStream.Base.tests , Tests.Protocols.PacketStream.Converters.tests , Tests.Protocols.PacketStream.Delay.tests , Tests.Protocols.PacketStream.Depacketizers.tests diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs index 393152d8..4da9e9b3 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/AsyncFifo.hs @@ -71,8 +71,7 @@ generateAsyncFifoIdProp :: generateAsyncFifoIdProp wClk wRst wEn rClk rRst rEn = idWithModel defExpectOptions - ( genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 1 30)) - ) + (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 30))) id (asyncFifoC @wDom @rDom @4 @1 @Int d4 wClk wRst wEn rClk rRst rEn) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs new file mode 100644 index 00000000..2ef0e311 --- /dev/null +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Base.hs @@ -0,0 +1,53 @@ +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE NoImplicitPrelude #-} + +module Tests.Protocols.PacketStream.Base ( + tests, +) where + +import Clash.Prelude + +import qualified Data.List as L +import Data.List.Extra (unsnoc) + +import Hedgehog (Property) +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range + +import Test.Tasty (TestTree, localOption, mkTimeout) +import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) +import Test.Tasty.Hedgehog.Extra (testProperty) +import Test.Tasty.TH (testGroupGenerator) + +import Protocols.Hedgehog +import Protocols.PacketStream.Base +import Protocols.PacketStream.Hedgehog + +prop_strip_trailing_empty :: Property +prop_strip_trailing_empty = + idWithModelSingleDomain + @System + defExpectOptions + (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 10))) + (exposeClockResetEnable model') + (exposeClockResetEnable (stripTrailingEmptyC @1 @Char)) + where + model' packets = L.concatMap model (chunkByPacket packets) + + model :: [PacketStreamM2S 1 Char] -> [PacketStreamM2S 1 Char] + model packet = case unsnoc packet of + Nothing -> [] + Just (xs, l) -> case unsnoc xs of + -- Preserve packets that consist of a single zero-byte transfer. + Nothing -> [l] + Just (ys, l2) -> + if _last l == Just 0 + then ys L.++ [l2{_last = Just maxBound, _abort = _abort l2 || _abort l}] + else packet + +tests :: TestTree +tests = + localOption (mkTimeout 20_000_000 {- 20 seconds -}) + $ localOption + (HedgehogTestLimit (Just 500)) + $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs index ac48662f..deda660e 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs @@ -1,6 +1,8 @@ {-# LANGUAGE NumericUnderscores #-} -module Tests.Protocols.PacketStream.Converters where +module Tests.Protocols.PacketStream.Converters ( + tests, +) where import Clash.Prelude @@ -30,11 +32,30 @@ generateUpConverterProperty :: generateUpConverterProperty SNat SNat = idWithModelSingleDomain defExpectOptions - ( genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 1 20)) - ) + (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 20))) (exposeClockResetEnable (upConvert . downConvert)) (exposeClockResetEnable @System (upConverterC @dwIn @dwOut @Int)) +generateDownConverterProperty :: + forall (dwIn :: Nat) (dwOut :: Nat) (n :: Nat). + (1 <= dwIn) => + (1 <= dwOut) => + (1 <= n) => + (KnownNat n) => + (dwIn ~ n * dwOut) => + SNat dwIn -> + SNat dwOut -> + Property +generateDownConverterProperty SNat SNat = + idWithModelSingleDomain + defExpectOptions{eoSampleMax = 1000} + (genPackets 1 8 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 10))) + (exposeClockResetEnable (upConvert . downConvert)) + (exposeClockResetEnable @System (downConverterC @dwIn @dwOut @Int)) + +prop_upConverter3to9 :: Property +prop_upConverter3to9 = generateUpConverterProperty d3 d9 + prop_upConverter4to8 :: Property prop_upConverter4to8 = generateUpConverterProperty d4 d8 @@ -53,22 +74,8 @@ prop_upConverter1to2 = generateUpConverterProperty d1 d2 prop_upConverter1to1 :: Property prop_upConverter1to1 = generateUpConverterProperty d1 d1 -generateDownConverterProperty :: - forall (dwIn :: Nat) (dwOut :: Nat) (n :: Nat). - (1 <= dwIn) => - (1 <= dwOut) => - (1 <= n) => - (KnownNat n) => - (dwIn ~ n * dwOut) => - SNat dwIn -> - SNat dwOut -> - Property -generateDownConverterProperty SNat SNat = - idWithModelSingleDomain - defExpectOptions{eoSampleMax = 1000} - (genPackets (Range.linear 1 8) Abort (genValidPacket Gen.enumBounded (Range.linear 1 10))) - (exposeClockResetEnable (upConvert . downConvert)) - (exposeClockResetEnable @System (downConverterC @dwIn @dwOut @Int)) +prop_downConverter9to3 :: Property +prop_downConverter9to3 = generateDownConverterProperty d9 d3 prop_downConverter8to4 :: Property prop_downConverter8to4 = generateDownConverterProperty d8 d4 diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs index eff1181e..4f1f8a60 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs @@ -24,8 +24,7 @@ prop_delaystream_id = idWithModelSingleDomain @System defExpectOptions - ( genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 4 20)) - ) + (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 4 20))) (exposeClockResetEnable id) (exposeClockResetEnable (delayStreamC @2 @Int d4)) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index 59d58ab8..f76595b5 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -39,7 +39,7 @@ depacketizerPropertyGenerator SNat SNat = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - (genPackets (Range.linear 1 4) Abort (genValidPacket (pure ()) (Range.linear 1 30))) + (genPackets 1 4 (genValidPacket defPacketOptions (pure ()) (Range.linear 0 30))) (exposeClockResetEnable (depacketizerModel const)) (exposeClockResetEnable ckt) where @@ -67,7 +67,7 @@ depacketizeToDfPropertyGenerator SNat SNat = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - (genPackets (Range.linear 1 10) Abort (genValidPacket (pure ()) (Range.linear 1 20))) + (genPackets 1 10 (genValidPacket defPacketOptions (pure ()) (Range.linear 0 20))) (exposeClockResetEnable (depacketizeToDfModel const)) (exposeClockResetEnable ckt) where @@ -90,9 +90,10 @@ dropTailTest SNat n = @System defExpectOptions ( genPackets - (Range.linear 1 4) - Abort + 1 + 4 ( genValidPacket + defPacketOptions (Gen.int8 Range.linearBounded) (Range.linear (natToNum @(n `DivRU` dataWidth)) 20) ) @@ -134,17 +135,17 @@ prop_const_depacketize_to_df_d5_d4 = depacketizeToDfPropertyGenerator d5 d4 -- | dataWidth < n && dataWidth % n ~ 0 prop_droptail_4_bytes_d1 :: Property -prop_droptail_4_bytes_d1 = dropTailTest d4 d1 +prop_droptail_4_bytes_d1 = dropTailTest d1 d4 prop_droptail_7_bytes_d1 :: Property -prop_droptail_7_bytes_d1 = dropTailTest d7 d1 +prop_droptail_7_bytes_d1 = dropTailTest d1 d7 -- | dataWidth < n && dataWidth % n > 0 prop_droptail_4_bytes_d3 :: Property -prop_droptail_4_bytes_d3 = dropTailTest d4 d3 +prop_droptail_4_bytes_d3 = dropTailTest d3 d4 prop_droptail_7_bytes_d4 :: Property -prop_droptail_7_bytes_d4 = dropTailTest d7 d4 +prop_droptail_7_bytes_d4 = dropTailTest d4 d7 -- | dataWidth ~ n prop_droptail_4_bytes_d4 :: Property @@ -155,10 +156,10 @@ prop_droptail_7_bytes_d7 = dropTailTest d7 d7 -- | dataWidth > n prop_droptail_4_bytes_d7 :: Property -prop_droptail_4_bytes_d7 = dropTailTest d4 d7 +prop_droptail_4_bytes_d7 = dropTailTest d7 d4 prop_droptail_7_bytes_d12 :: Property -prop_droptail_7_bytes_d12 = dropTailTest d7 d12 +prop_droptail_7_bytes_d12 = dropTailTest d12 d7 tests :: TestTree tests = diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index 9095da0e..75b4ecd0 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -31,8 +31,7 @@ prop_packetFifo_id = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - ( genPackets (Range.linear 1 30) Abort (genValidPacket Gen.enumBounded (Range.linear 1 10)) - ) + (genPackets 1 30 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 10))) (exposeClockResetEnable dropAbortedPackets) (exposeClockResetEnable ckt) where @@ -47,8 +46,7 @@ prop_packetFifo_small_buffer_id = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - ( genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 1 30)) - ) + (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 30))) (exposeClockResetEnable dropAbortedPackets) (exposeClockResetEnable ckt) where @@ -70,9 +68,10 @@ prop_packetFifo_no_gaps = property $ do enableGen gen = genPackets - (Range.linear 1 10) - NoAbort - (genValidPacket Gen.enumBounded (Range.linear 1 10)) + 1 + 10 + ( genValidPacket defPacketOptions{poAbortMode = NoAbort} Gen.enumBounded (Range.linear 0 10) + ) packets :: [PacketStreamM2S 4 Int16] <- forAll gen @@ -93,8 +92,7 @@ prop_overFlowDrop_packetFifo_id = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 2000, eoStopAfterEmpty = 1000} - ( genPackets (Range.linear 1 30) Abort (genValidPacket Gen.enumBounded (Range.linear 1 10)) - ) + (genPackets 1 30 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 10))) (exposeClockResetEnable dropAbortedPackets) (exposeClockResetEnable ckt) where @@ -124,8 +122,13 @@ prop_overFlowDrop_packetFifo_drop = where packetChunk = chunkByPacket packets - genSmall = genValidPacket Gen.enumBounded (Range.linear 1 5) NoAbort - genBig = genValidPacket Gen.enumBounded (Range.linear 33 33) NoAbort + genSmall = + genValidPacket defPacketOptions{poAbortMode = NoAbort} Gen.enumBounded (Range.linear 1 5) + genBig = + genValidPacket + defPacketOptions{poAbortMode = NoAbort} + Gen.enumBounded + (Range.linear 33 33) -- | test for id using a small metabuffer to ensure backpressure using the metabuffer is tested prop_packetFifo_small_metaBuffer :: Property @@ -133,7 +136,7 @@ prop_packetFifo_small_metaBuffer = idWithModelSingleDomain @System defExpectOptions - (genPackets (Range.linear 1 30) Abort (genValidPacket Gen.enumBounded (Range.linear 1 4))) + (genPackets 1 30 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 4))) (exposeClockResetEnable dropAbortedPackets) (exposeClockResetEnable ckt) where diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs index b7d1d742..9edb1c43 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs @@ -24,8 +24,9 @@ import Protocols.PacketStream (packetizeFromDfC, packetizerC) import Protocols.PacketStream.Base import Protocols.PacketStream.Hedgehog -{- | Test the packetizer with varying datawidth and number of bytes in the header, - with metaOut = (). +{- | +Test the packetizer with varying datawidth and number of bytes in the header, +with metaOut = (). -} packetizerPropertyGenerator :: forall @@ -41,9 +42,9 @@ packetizerPropertyGenerator SNat SNat = @System defExpectOptions ( genPackets - (Range.linear 1 10) - Abort - (genValidPacket (genVec Gen.enumBounded) (Range.linear 1 10)) + 1 + 10 + (genValidPacket defPacketOptions (genVec Gen.enumBounded) (Range.linear 0 10)) ) (exposeClockResetEnable model) (exposeClockResetEnable ckt) @@ -56,24 +57,9 @@ packetizerPropertyGenerator SNat SNat = (PacketStream System dataWidth ()) ckt = packetizerC (const ()) id --- | headerBytes % dataWidth ~ 0 -prop_const_packetizer_d1_d14 :: Property -prop_const_packetizer_d1_d14 = packetizerPropertyGenerator d1 d14 - --- | dataWidth < headerBytes -prop_const_packetizer_d3_d11 :: Property -prop_const_packetizer_d3_d11 = packetizerPropertyGenerator d3 d11 - --- | dataWidth ~ header byte size -prop_const_packetizer_d7_d7 :: Property -prop_const_packetizer_d7_d7 = packetizerPropertyGenerator d7 d7 - --- | dataWidth > header byte size -prop_const_packetizer_d5_d4 :: Property -prop_const_packetizer_d5_d4 = packetizerPropertyGenerator d5 d4 - -{- | Test packetizeFromDf with varying datawidth and number of bytes in the header - , with metaOut = (). +{- | +Test packetizeFromDf with varying datawidth and number of bytes in the header, +with metaOut = (). -} packetizeFromDfPropertyGenerator :: forall @@ -98,6 +84,22 @@ packetizeFromDfPropertyGenerator SNat SNat = Circuit (Df.Df System (Vec headerBytes (BitVector 8))) (PacketStream System dataWidth ()) ckt = packetizeFromDfC (const ()) id +-- | headerBytes % dataWidth ~ 0 +prop_const_packetizer_d1_d14 :: Property +prop_const_packetizer_d1_d14 = packetizerPropertyGenerator d1 d14 + +-- | dataWidth < headerBytes +prop_const_packetizer_d3_d11 :: Property +prop_const_packetizer_d3_d11 = packetizerPropertyGenerator d3 d11 + +-- | dataWidth ~ header byte size +prop_const_packetizer_d7_d7 :: Property +prop_const_packetizer_d7_d7 = packetizerPropertyGenerator d7 d7 + +-- | dataWidth > header byte size +prop_const_packetizer_d5_d4 :: Property +prop_const_packetizer_d5_d4 = packetizerPropertyGenerator d5 d4 + -- | headerBytes % dataWidth ~ 0 prop_const_packetizeFromDf_d1_d14 :: Property prop_const_packetizeFromDf_d1_d14 = packetizeFromDfPropertyGenerator d1 d14 diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs index e008e8a0..1e962ef9 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs @@ -66,7 +66,7 @@ makePropPacketArbiter _ _ mode = genSources = mapM setMeta (indicesI @p) setMeta j = do pkts <- - genPackets @n (Range.linear 1 10) Abort (genValidPacket (pure ()) (Range.linear 1 10)) + genPackets @n 1 10 (genValidPacket defPacketOptions (pure ()) (Range.linear 0 10)) pure $ L.map (\pkt -> pkt{_meta = j}) pkts partitionPackets packets = @@ -117,7 +117,7 @@ makePropPacketDispatcher :: makePropPacketDispatcher _ fs = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 2000, eoStopAfterEmpty = 1000} - (genPackets (Range.linear 1 10) Abort (genValidPacket Gen.enumBounded (Range.linear 1 6))) + (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 6))) (exposeClockResetEnable (model 0)) (exposeClockResetEnable (packetDispatcherC fs)) where From 1c39ebbadc2236e982929fb32964524dfa0f0fe4 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Thu, 10 Oct 2024 17:39:28 +0200 Subject: [PATCH 47/63] Adapt to new repo structure --- clash-protocols/src/Protocols/PacketStream/Base.hs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 702d882a..76fde6b3 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -59,10 +59,11 @@ import Data.Coerce (coerce) import qualified Data.Maybe as Maybe import Data.Proxy +import Protocols import qualified Protocols.Df as Df import qualified Protocols.DfConv as DfConv -import Protocols.Hedgehog.Internal -import Protocols.Internal +import Protocols.Hedgehog (Test (..)) +import Protocols.Idle import Control.DeepSeq (NFData) From 9ba7c6935a5f0f5fc377ad04312979b216288d6a Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 18 Oct 2024 14:42:03 +0200 Subject: [PATCH 48/63] downConverterC changes. Reverted back to the old version where all M2S outputs are registered, but now also handles arbitrary metadata and output data widths bigger than 1 that divide the input data width. Zero-byte transfers are always preserved. This new version reduces the LUT resource usage by half compared to the old registered version, because of a mux with 1 selector bit (optimal) being inferred by synthesizers for the next state computation, instead of a mux with 2 selector bits. --- .../src/Protocols/PacketStream/Converters.hs | 119 ++++++++++++------ 1 file changed, 79 insertions(+), 40 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index ba4f17a1..2a8fdea7 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -13,7 +13,7 @@ module Protocols.PacketStream.Converters ( import Clash.Prelude -import Data.Maybe (isJust) +import Data.Maybe (fromMaybe, isJust) import Data.Maybe.Extra import Data.Type.Equality ((:~:) (Refl)) @@ -206,69 +206,102 @@ unsafeUpConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of Just Refl -> idC _ -> unsafeDropBackpressure (fromSignals upConverter) -data DownConverterState (dwIn :: Nat) = DownConverterState +-- | State of 'downConverterT'. +data DownConverterState (dwIn :: Nat) (meta :: Type) = DownConverterState { _dcBuf :: Vec dwIn (BitVector 8) - -- ^ Buffer + -- ^ Registered _data of the last transfer. + , _dcLast :: Bool + -- ^ Is the last transfer the end of a packet? + , _dcMeta :: meta + -- ^ Registered _meta of the last transfer. + , _dcAborted :: Bool + -- ^ Registered _abort of the last transfer. All sub-transfers corresponding + -- to this transfer need to be marked with the same _abort value. , _dcSize :: Index (dwIn + 1) - -- ^ Number of valid bytes in _dcBuf + -- ^ Number of valid bytes in _dcBuf. + , _dcZeroByteTransfer :: Bool + -- ^ Is the current transfer we store a zero-byte transfer? In this case, + -- _dcSize is 0 but we still need to transmit something in order to + -- preserve zero-byte transfers. } deriving (Generic, NFDataX) +-- | State transition function of 'downConverterC', in case @dwIn /= dwOut@. downConverterT :: forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (n :: Nat). (1 <= dwIn) => (1 <= dwOut) => (1 <= n) => + (NFDataX meta) => (KnownNat dwIn) => (KnownNat dwOut) => (dwIn ~ dwOut * n) => - DownConverterState dwIn -> + DownConverterState dwIn meta -> (Maybe (PacketStreamM2S dwIn meta), PacketStreamS2M) -> - ( DownConverterState dwIn + ( DownConverterState dwIn meta , (PacketStreamS2M, Maybe (PacketStreamM2S dwOut meta)) ) -downConverterT st (Nothing, _) = (st, (PacketStreamS2M True, Nothing)) -downConverterT st@DownConverterState{..} (Just inPkt, bwdIn) = (nextSt, (PacketStreamS2M outReady, Just outPkt)) +downConverterT st@(DownConverterState{..}) (fwdIn, bwdIn) = + (nextSt, (PacketStreamS2M readyOut, fwdOut)) where - -- If _dcSize == 0, then we have received a new transfer and should use - -- its corresponding _data. Else, we should use our stored buffer. - (nextSize, buf) = case (_dcSize == 0, _last inPkt) of - (True, Nothing) -> (maxBound - natToNum @dwOut, _data inPkt) - (True, Just i) -> (satSub SatBound i (natToNum @dwOut), _data inPkt) - (False, _) -> (satSub SatBound _dcSize (natToNum @dwOut), _dcBuf) - - (newBuf, dataOut) = leToPlus @dwOut @dwIn shiftOutFrom0 (SNat @dwOut) buf + (shiftedBuf, dataOut) = leToPlus @dwOut @dwIn $ shiftOutFrom0 (SNat @dwOut) _dcBuf - outPkt = - PacketStreamM2S - { _data = dataOut - , _last = outLast - , _meta = _meta inPkt - , _abort = _abort inPkt - } + -- Either we preserve a zero-byte transfer or we have some real data to transmit. + fwdOut = + toMaybe (_dcSize > 0 || _dcZeroByteTransfer) + $ PacketStreamM2S + { _data = dataOut + , _last = + if _dcZeroByteTransfer + then Just 0 + else toMaybe (_dcSize <= natToNum @dwOut && _dcLast) (resize _dcSize) + , _meta = _dcMeta + , _abort = _dcAborted + } - (outReady, outLast) - | nextSize == 0 = - ( _ready bwdIn - , (\i -> resize $ if _dcSize == 0 then i else _dcSize) - <$> _last inPkt - ) - | otherwise = (False, Nothing) + -- If the state buffer is empty, or if the state buffer is not empty and + -- the final sub-transfer is acknowledged this clock cycle, we can acknowledge + -- newly received valid data and load it into our registers. + emptyState = _dcSize == 0 && not _dcZeroByteTransfer + readyOut = isJust fwdIn && (emptyState || (_dcSize <= natToNum @dwOut && _ready bwdIn)) - -- Keep the buffer in the state and rotate it once the byte is acknowledged to avoid - -- dynamic indexing. nextSt - | _ready bwdIn = DownConverterState newBuf nextSize + | readyOut = newState (fromJustX fwdIn) + | not emptyState && _ready bwdIn = + st + { _dcBuf = shiftedBuf + , _dcSize = satSub SatBound _dcSize (natToNum @dwOut) + , _dcZeroByteTransfer = False + } | otherwise = st -{- | Converts packet streams of arbitrary data width @dwIn@ to packet streams of -a smaller data width @dwOut@, where @dwOut@ must divide @dwIn@. When @dwIn ~ dwOut@, -this component is set to be `idC`. + -- Computes a new state from a valid incoming transfer. + newState PacketStreamM2S{..} = + DownConverterState + { _dcBuf = _data + , _dcMeta = _meta + , _dcSize = fromMaybe (natToNum @dwIn) _last + , _dcLast = isJust _last + , _dcAborted = _abort + , _dcZeroByteTransfer = _last == Just 0 + } -If `_abort` is asserted on an input transfer, it will be asserted on all -corresponding output transfers as well. +{- | +Converts packet streams of arbitrary data width @dwIn@ to packet streams of +a smaller (or equal) data width @dwOut@, where @dwOut@ must divide @dwIn@. +When @dwIn ~ dwOut@, this component is just the identity circuit, `idC`. -Provides zero latency and full throughput. +If '_abort' is asserted on an input transfer, it will be asserted on all +corresponding output sub-transfers as well. All zero-byte transfers are +preserved. + +Has one clock cycle of latency, all M2S outputs are registered. +Throughput is optimal, a transfer of @n@ valid bytes is transmitted in @n@ +clock cycles. To be precise, throughput is at least @(dwIn / dwOut)%@, so at +least @50%@ if @dwIn = 4@ and @dwOut = 2@ for example. We specify /at least/, +because the throughput may be on the last transfer of a packet, when not all +bytes have to be valid. If there is only one valid byte in the last transfer, +then the throughput will always be @100%@ for that particular transfer. -} downConverterC :: forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). @@ -276,6 +309,7 @@ downConverterC :: (1 <= dwIn) => (1 <= dwOut) => (1 <= n) => + (NFDataX meta) => (KnownNat dwIn) => (KnownNat dwOut) => (dwIn ~ dwOut * n) => @@ -285,8 +319,13 @@ downConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of Just Refl -> idC _ -> forceResetSanity |> fromSignals (mealyB downConverterT s0) where + errPrefix = "downConverterT: undefined initial " s0 = DownConverterState - { _dcBuf = deepErrorX "downConverterC: undefined initial buffer" + { _dcBuf = deepErrorX (errPrefix <> "_dcBuf") + , _dcLast = deepErrorX (errPrefix <> "_dcLast") + , _dcMeta = deepErrorX (errPrefix <> "_dcMeta") + , _dcAborted = deepErrorX (errPrefix <> "_dcAborted") , _dcSize = 0 + , _dcZeroByteTransfer = False } From ad75ad0c88ba0718da0630430ba43c34031e3c9c Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 18 Oct 2024 16:10:27 +0200 Subject: [PATCH 49/63] Prefer use of deepErrorX over undefined --- clash-protocols/src/Protocols/PacketStream/Base.hs | 10 ++++++++-- .../src/Protocols/PacketStream/Depacketizers.hs | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 76fde6b3..c13bbc9a 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -166,7 +166,10 @@ instance DfConv.DfConv (PacketStream dom dataWidth meta) where toDfCircuit _ = fromSignals go where go (fwdIn, bwdIn) = - ( (fmap coerce bwdIn, pure undefined) + ( + ( fmap coerce bwdIn + , pure (deepErrorX "PacketStream toDfCircuit: undefined") + ) , Df.dataToMaybe <$> P.fst fwdIn ) @@ -174,7 +177,10 @@ instance DfConv.DfConv (PacketStream dom dataWidth meta) where where go (fwdIn, bwdIn) = ( coerce <$> P.fst bwdIn - , (fmap Df.maybeToData fwdIn, pure undefined) + , + ( fmap Df.maybeToData fwdIn + , pure (deepErrorX "PacketStream fromDfCircuit: undefined") + ) ) instance diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 35ca2222..3b5812a1 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -94,7 +94,7 @@ instance Default (DepacketizerState headerBytes dataWidth) where def :: DepacketizerState headerBytes dataWidth - def = Parse False (repeat undefined) maxBound + def = Parse False (deepErrorX "depacketizerT: undefined intial buffer") maxBound -- | Depacketizer state transition function. depacketizerT :: @@ -270,7 +270,7 @@ instance Default (DfDepacketizerState headerBytes dataWidth) where def :: DfDepacketizerState headerBytes dataWidth - def = DfParse False (repeat undefined) maxBound + def = DfParse False (deepErrorX "depacketizeToDfT: undefined intial buffer") maxBound -- | Df depacketizer transition function. depacketizeToDfT :: From c14b940be963bb0d1f93f0b6065a69a3fa939744 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 18 Oct 2024 16:44:14 +0200 Subject: [PATCH 50/63] Converters: patch up documentation --- .../src/Protocols/PacketStream/Converters.hs | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index 2a8fdea7..ca29176c 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -20,31 +20,31 @@ import Data.Type.Equality ((:~:) (Refl)) import Protocols (CSignal, Circuit (..), fromSignals, idC, (|>)) import Protocols.PacketStream.Base --- | Upconverter state, consisting of at most p `BitVector 8`s and a vector indicating which bytes are valid +-- | State of 'upConverter'. data UpConverterState (dwOut :: Nat) (n :: Nat) (meta :: Type) = UpConverterState { _ucBuf :: Vec dwOut (BitVector 8) - -- ^ The buffer we are filling + -- ^ The data buffer we are filling. , _ucIdx :: Index n - -- ^ Where in the buffer we need to write the next element + -- ^ Where in _ucBuf we need to write the next data. , _ucIdx2 :: Index (dwOut + 1) -- ^ Used when @dwIn@ is not a power of two to determine the adjusted '_last', -- to avoid multiplication (infers an expensive DSP slice). - -- If @dwIn@ is a power of two then we can multiply by shifting left. + -- If @dwIn@ is a power of two then we can multiply by shifting left with + -- a constant, which is free in hardware in terms of resource usage. , _ucFlush :: Bool - -- ^ If this is true the current state can presented as packetstream word + -- ^ If true, we should output the current state as a PacketStream transfer. , _ucFreshBuf :: Bool - -- ^ If this is true we need to start a fresh buffer + -- ^ If true, we need to start a fresh buffer (all zeroes). , _ucAborted :: Bool - -- ^ Current packet is aborted + -- ^ Whether the current transfer we are building is aborted. , _ucLastIdx :: Maybe (Index (dwOut + 1)) - -- ^ If true the current buffer contains the last byte of the current packet + -- ^ If true, the current buffer contains the last byte of the current packet. , _ucMeta :: meta + -- ^ Metadata of the current transfer we are a building. } deriving (Generic, NFDataX, Show, ShowX) -toPacketStream :: UpConverterState dwOut n meta -> Maybe (PacketStreamM2S dwOut meta) -toPacketStream UpConverterState{..} = toMaybe _ucFlush (PacketStreamM2S _ucBuf _ucLastIdx _ucMeta _ucAborted) - +-- | Computes the next state for 'upConverter'. nextState :: forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (n :: Nat). (1 <= dwIn) => @@ -94,8 +94,8 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M nextIdx = if nextFlush then 0 else _ucIdx + 1 -- If @dwIn@ is not a power of two, we need to do some extra bookkeeping to - -- avoid multiplication. If not, _ucIdx2 stays at 0 and is never used, and - -- should therefore be optimized out by synthesis tools. + -- avoid multiplication to calculate _last. If not, _ucIdx2 stays at 0 and is + -- never used, and should therefore be optimized out by synthesis tools. (nextIdx2, nextLastIdx) = case sameNat (SNat @(FLog 2 dwIn)) (SNat @(CLog 2 dwIn)) of Just Refl -> ( 0 @@ -138,28 +138,43 @@ upConverter :: ) upConverter = mealyB go s0 where + errPrefix = "upConverterT: undefined initial " s0 = UpConverterState - { _ucBuf = deepErrorX "upConverterC: undefined initial buffer" + { _ucBuf = deepErrorX (errPrefix <> " _ucBuf") , _ucIdx = 0 , _ucIdx2 = 0 , _ucFlush = False , _ucFreshBuf = True , _ucAborted = False - , _ucLastIdx = Nothing - , _ucMeta = deepErrorX "upConverterC: undefined initial metadata" + , _ucLastIdx = deepErrorX (errPrefix <> " _ucLastIdx") + , _ucMeta = deepErrorX (errPrefix <> " _ucMeta") } go st@(UpConverterState{..}) (fwdIn, bwdIn) = - (nextState st fwdIn bwdIn, (PacketStreamS2M outReady, toPacketStream st)) + (nextState st fwdIn bwdIn, (PacketStreamS2M outReady, fwdOut)) where outReady = not _ucFlush || _ready bwdIn + fwdOut = + toMaybe _ucFlush + $ PacketStreamM2S + { _data = _ucBuf + , _last = _ucLastIdx + , _meta = _ucMeta + , _abort = _ucAborted + } + {- | Converts packet streams of arbitrary data width @dwIn@ to packet streams of -a bigger data width @dwOut@, where @dwIn@ must divide @dwOut@. When @dwIn ~ dwOut@, -this component is set to be `idC`. +a bigger (or equal) data width @dwOut@, where @dwOut@ must divide @dwIn@. +When @dwIn ~ dwOut@, this component is just the identity circuit, `idC`. + +If '_abort' is asserted on any of the input sub-transfers, it will be asserted +on the corresponding output transfer as well. All zero-byte transfers are +preserved. -Has one cycle of latency, but full throughput. +Has one cycle of latency, all M2S outputs are registered. +Provides full throughput. -} upConverterC :: forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). From 4d161eb529c7be454a9b977080e3bab93857caf0 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 18 Oct 2024 16:58:34 +0200 Subject: [PATCH 51/63] Add copyright, license and maintainer attributes to all PacketStream source files --- clash-protocols/src/Protocols/PacketStream.hs | 6 +++--- clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs | 4 ++++ clash-protocols/src/Protocols/PacketStream/Base.hs | 6 +++++- clash-protocols/src/Protocols/PacketStream/Converters.hs | 7 ++++++- clash-protocols/src/Protocols/PacketStream/Delay.hs | 4 ++++ .../src/Protocols/PacketStream/Depacketizers.hs | 4 ++++ clash-protocols/src/Protocols/PacketStream/Hedgehog.hs | 6 +++--- clash-protocols/src/Protocols/PacketStream/PacketFifo.hs | 4 ++++ clash-protocols/src/Protocols/PacketStream/Packetizers.hs | 4 ++++ clash-protocols/src/Protocols/PacketStream/Routing.hs | 4 ++++ 10 files changed, 41 insertions(+), 8 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream.hs b/clash-protocols/src/Protocols/PacketStream.hs index 41ea82ba..1799886d 100644 --- a/clash-protocols/src/Protocols/PacketStream.hs +++ b/clash-protocols/src/Protocols/PacketStream.hs @@ -1,7 +1,7 @@ {- | - Copyright : (C) 2024, QBayLogic B.V. - License : BSD2 (see the file LICENSE) - Maintainer : QBayLogic B.V. +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. Provides the PacketStream protocol, a simple streaming protocol for transferring packets of data between components. diff --git a/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs b/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs index b4f80dd2..f017d461 100644 --- a/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/AsyncFifo.hs @@ -2,6 +2,10 @@ {-# OPTIONS_HADDOCK hide #-} {- | +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. + Provides `asyncFifoC` for crossing clock domains in the packet stream protocol. -} module Protocols.PacketStream.AsyncFifo (asyncFifoC) where diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index c13bbc9a..e410caba 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -4,7 +4,11 @@ {-# OPTIONS_HADDOCK hide #-} {- | -Definitions and instances of the PacketStream protocol +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. + +Definitions and instances of the PacketStream protocol. -} module Protocols.PacketStream.Base ( -- * Protocol definition diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index ca29176c..71f945fc 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -3,7 +3,12 @@ {-# OPTIONS_HADDOCK hide #-} {- | -Provides an upconverter and downconverter for changing the data width of packet streams. +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. + +Provides an upconverter and downconverter for changing the data width of +packet streams. -} module Protocols.PacketStream.Converters ( downConverterC, diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs index 9741af8b..2bc9abc8 100644 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -3,6 +3,10 @@ {-# OPTIONS_HADDOCK hide #-} {- | +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. + Provides a circuit that delays a stream by a configurable amount of transfers. -} module Protocols.PacketStream.Delay ( diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 3b5812a1..5681a61b 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -6,6 +6,10 @@ {-# OPTIONS_HADDOCK hide #-} {- | +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. + Utility circuits for reading from a packet stream. -} module Protocols.PacketStream.Depacketizers ( diff --git a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs index dbfa166b..d6d8f91d 100644 --- a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs +++ b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs @@ -2,9 +2,9 @@ {-# LANGUAGE NoImplicitPrelude #-} {- | -Copyright : (C) 2024, QBayLogic B.V. -License : BSD2 (see the file LICENSE) -Maintainer : QBayLogic B.V. +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. Provides Hedgehog generators, models and utility functions for testing `PacketStream` circuits. diff --git a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs index 78a55426..0afb1b05 100644 --- a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs @@ -3,6 +3,10 @@ {-# OPTIONS_HADDOCK hide #-} {- | +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. + Optimized Store and forward FIFO circuit for packet streams. -} module Protocols.PacketStream.PacketFifo ( diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index 84d0e2f0..64881e36 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -5,6 +5,10 @@ {-# OPTIONS_HADDOCK hide #-} {- | +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. + Utility circuits for appending headers to the beginning of packets. -} module Protocols.PacketStream.Packetizers ( diff --git a/clash-protocols/src/Protocols/PacketStream/Routing.hs b/clash-protocols/src/Protocols/PacketStream/Routing.hs index 389ee0c8..c0c597fa 100644 --- a/clash-protocols/src/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/src/Protocols/PacketStream/Routing.hs @@ -2,6 +2,10 @@ {-# OPTIONS_HADDOCK hide #-} {- | +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. + Provides a packet arbiter and dispatcher, for merging and splitting packet streams. -} module Protocols.PacketStream.Routing ( From 399ae170be7149ca37c3dea77082b9065954cf16 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 25 Oct 2024 14:04:13 +0200 Subject: [PATCH 52/63] Fix PacketFifo bug Now correctly drops packets that are too big to fit in the content FIFO in 'Backpressure' mode. --- .../src/Protocols/PacketStream/PacketFifo.hs | 231 +++++++++++++++--- .../Protocols/PacketStream/PacketFifo.hs | 161 ++++++------ 2 files changed, 270 insertions(+), 122 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs index 0afb1b05..834ab475 100644 --- a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs @@ -1,4 +1,5 @@ {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_HADDOCK hide #-} @@ -7,7 +8,7 @@ Copyright : (C) 2024, QBayLogic B.V. License : BSD2 (see the file LICENSE) Maintainer : QBayLogic B.V. -Optimized Store and forward FIFO circuit for packet streams. +Optimized store and forward FIFO circuit for packet streams. -} module Protocols.PacketStream.PacketFifo ( packetFifoC, @@ -16,23 +17,154 @@ module Protocols.PacketStream.PacketFifo ( import Clash.Prelude +import Data.Maybe +import Data.Maybe.Extra (toMaybe) + import Protocols import Protocols.PacketStream.Base -import Data.Maybe -import Data.Maybe.Extra - type PacketStreamContent (dataWidth :: Nat) (meta :: Type) = (Vec dataWidth (BitVector 8), Maybe (Index (dataWidth + 1))) +-- | Specifies the behaviour of `packetFifoC` when it is full. +data FullMode + = -- | Assert backpressure when the FIFO is full. + Backpressure + | -- | Drop new packets when the FIFO is full. + -- The FIFO never asserts backpressure. + Drop + toPacketStreamContent :: PacketStreamM2S dataWidth meta -> PacketStreamContent dataWidth meta -toPacketStreamContent PacketStreamM2S{_data = d, _last = l, _meta = _, _abort = _} = (d, l) +toPacketStreamContent PacketStreamM2S{..} = (_data, _last) toPacketStreamM2S :: PacketStreamContent dataWidth meta -> meta -> PacketStreamM2S dataWidth meta toPacketStreamM2S (d, l) m = PacketStreamM2S d l m False +data PacketFifoState contentDepth metaDepth = PacketFifoState + { _canRead :: Bool + -- ^ We need this to avoid read-write conflicts. + , _dropping :: Bool + -- ^ Whether we are dropping the current packet. + , _basePtr :: Unsigned contentDepth + -- ^ Points to the base address of the current packet, i.e. the address of + -- the first transfer. + , _cReadPtr :: Unsigned contentDepth + -- ^ Read pointer in the content block ram. + , _cWritePtr :: Unsigned contentDepth + -- ^ Write pointer in the content block ram. + , _mReadPtr :: Unsigned metaDepth + -- ^ Read pointer in the metadata block ram. + , _mWritePtr :: Unsigned metaDepth + -- ^ Write pointer in the metadata block ram. + } + deriving (Generic, NFDataX, Show, ShowX) + +-- | State transition function of 'packetFifoC', mode @Backpressure@. +packetFifoT :: + forall + (dataWidth :: Nat) + (meta :: Type) + (contentDepth :: Nat) + (metaDepth :: Nat). + (KnownNat dataWidth) => + (KnownNat contentDepth) => + (KnownNat metaDepth) => + (1 <= contentDepth) => + (1 <= metaDepth) => + (NFDataX meta) => + PacketFifoState contentDepth metaDepth -> + ( Maybe (PacketStreamM2S dataWidth meta) + , PacketStreamS2M + , PacketStreamContent dataWidth meta + , meta + ) -> + ( PacketFifoState contentDepth metaDepth + , ( Unsigned contentDepth + , Unsigned metaDepth + , Maybe (Unsigned contentDepth, PacketStreamContent dataWidth meta) + , Maybe (Unsigned metaDepth, meta) + , PacketStreamS2M + , Maybe (PacketStreamM2S dataWidth meta) + ) + ) +packetFifoT st@PacketFifoState{..} (fwdIn, bwdIn, cRam, mRam) = + (nextSt, (cReadPtr', mReadPtr', cWriteCmd, mWriteCmd, bwdOut, fwdOut)) + where + -- Status signals + pktTooBig = _cWritePtr + 1 == _cReadPtr && fifoEmpty + (lastPkt, dropping) = case fwdIn of + Nothing -> (False, _dropping || pktTooBig) + Just PacketStreamM2S{..} -> (isJust _last, _dropping || pktTooBig || _abort) + + fifoEmpty = _mReadPtr == _mWritePtr + fifoSinglePacket = _mReadPtr + 1 == _mWritePtr + fifoFull = + (_cWritePtr + 1 == _cReadPtr) + || (_mWritePtr + 1 == _mReadPtr && lastPkt) + + -- Enables + readEn = _canRead && not fifoEmpty + cReadEn = readEn && _ready bwdIn + mReadEn = readEn && _ready bwdIn && isJust (snd cRam) + + -- Output + bwdOut = PacketStreamS2M (not fifoFull || dropping) + fwdOut = + if readEn + then Just (toPacketStreamM2S cRam mRam) + else Nothing + + -- New state + + -- Our block RAM is read-before-write, so we cannot use the read value next + -- clock cycle if there is a read-write conflict. Such a conflict might happen + -- when we finish writing a packet into the FIFO while: + -- 1. The FIFO is empty. + -- 2. The FIFO has one packet inside and we finish outputting it this clock cycle. + canRead' = not (lastPkt && (fifoEmpty || (mReadEn && fifoSinglePacket))) + dropping' = dropping && not lastPkt + + basePtr' = if lastPkt && _ready bwdOut then cWritePtr' else _basePtr + cReadPtr' = if cReadEn then _cReadPtr + 1 else _cReadPtr + mReadPtr' = if mReadEn then _mReadPtr + 1 else _mReadPtr + + (cWriteCmd, cWritePtr') = + if not dropping && not fifoFull + then + ( (\t -> (_cWritePtr, toPacketStreamContent t)) <$> fwdIn + , _cWritePtr + 1 + ) + else + ( Nothing + , if dropping then _basePtr else _cWritePtr + ) + + -- Write the metadata into RAM upon the last transfer of a packet, and + -- advance the write pointer. This allows us to write the data of a packet + -- into RAM even if the metadata RAM is currently full (hoping that it will + -- free up before we read the end of the packet). It also prevents unnecessary + -- writes in case a packet is aborted or too big. + (mWriteCmd, mWritePtr') = + if not dropping && not fifoFull && lastPkt + then ((\t -> (_mWritePtr, _meta t)) <$> fwdIn, _mWritePtr + 1) + else (Nothing, _mWritePtr) + + nextSt = case fwdIn of + Nothing -> st{_canRead = True, _cReadPtr = cReadPtr', _mReadPtr = mReadPtr'} + Just _ -> + PacketFifoState + { _canRead = canRead' + , _dropping = dropping' + , _basePtr = basePtr' + , _cReadPtr = cReadPtr' + , _cWritePtr = cWritePtr' + , _mReadPtr = mReadPtr' + , _mWritePtr = mWritePtr' + } + +-- | Implementation of 'packetFifoC', mode @Drop@. packetFifoImpl :: forall (dom :: Domain) @@ -123,46 +255,75 @@ packetFifoImpl SNat SNat (fwdIn, bwdIn) = (PacketStreamS2M . not <$> fullBuffer, nextPacketIn = lastWordIn .&&. writeEnable {- | -Packet buffer, a circuit which stores words in a buffer until the packet is complete. -Once a packet is complete it will send the entire packet out at once without stalls. -If a transfer in a packet has `_abort` set to true, the packetBuffer will drop the entire packet. +FIFO circuit optimized for the PacketStream protocol. Contains two FIFOs, one +for packet data ('_data', '_last') and one for packet metadata ('_meta'). +Because metadata is constant per packet, the metadata FIFO can be signficantly +shallower, saving resources. + +Moreover, the output of the FIFO has some other properties: + +- All packets which contain a transfer with '_abort' set are dropped. +- All packets that are bigger than or equal to @2^contentDepth - 1@ transfers are dropped. +- There are no gaps in output packets, i.e. @Nothing@ in between valid transfers of a packet. -__UNSAFE__: if `FullMode` is set to @Backpressure@ and a packet containing -@>= 2^contentSizeBits-1@ transfers is loaded into the FIFO, it will deadlock. +The circuit is able to satisfy these properties because it first loads an entire +packet before it may transmit it. That is also why packets bigger than the +content FIFO need to be dropped. + +Two modes can be selected: + +- @Backpressure@: assert backpressure like normal when the FIFO is full. +- @Drop@: never give backpressure, instead drop the current packet we are loading. -} packetFifoC :: forall (dom :: Domain) (dataWidth :: Nat) (meta :: Type) - (contentSizeBits :: Nat) - (metaSizeBits :: Nat). + (contentDepth :: Nat) + (metaDepth :: Nat). (HiddenClockResetEnable dom) => (KnownNat dataWidth) => - (1 <= contentSizeBits) => - (1 <= metaSizeBits) => + (1 <= contentDepth) => + (1 <= metaDepth) => (NFDataX meta) => - -- | The FIFO can store @2^contentSizeBits@ transfers - SNat contentSizeBits -> - -- | The FIFO can store @2^metaSizeBits@ packets - SNat metaSizeBits -> - -- | Specifies the behaviour of the FIFO when it is full + -- | The content FIFO will contain @2^contentDepth@ entries. + SNat contentDepth -> + -- | The metadata FIFO will contain @2^metaDepth@ entries. + SNat metaDepth -> + -- | The backpressure behaviour of the FIFO when it is full. FullMode -> Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) -packetFifoC cSizeBits mSizeBits mode = case mode of - Backpressure -> - forceResetSanity - |> fromSignals (packetFifoImpl cSizeBits mSizeBits) - Drop -> - toCSignal - |> unsafeAbortOnBackpressureC - |> forceResetSanity - |> fromSignals (packetFifoImpl cSizeBits mSizeBits) +packetFifoC cSize@SNat mSize@SNat fullMode = + let + ckt (fwdIn, bwdIn) = (bwdOut, fwdOut) + where + ramContent = + blockRam1 + NoClearOnReset + (SNat @(2 ^ contentDepth)) + (deepErrorX "initial block ram content") + cReadPtr + cWriteCommand + ramMeta = + blockRam1 + NoClearOnReset + (SNat @(2 ^ metaDepth)) + (deepErrorX "initial block ram meta content") + mReadPtr + mWriteCommand --- | Specifies the behaviour of `packetFifoC` when it is full. -data FullMode - = -- | Assert backpressure when the FIFO is full. - Backpressure - | -- | Drop new packets when the FIFO is full. - -- The FIFO never asserts backpressure. - Drop + (cReadPtr, mReadPtr, cWriteCommand, mWriteCommand, bwdOut, fwdOut) = + mealyB + (packetFifoT @dataWidth @meta @contentDepth @metaDepth) + (PacketFifoState False False 0 0 0 0 0) + (fwdIn, bwdIn, ramContent, ramMeta) + in + case fullMode of + Backpressure -> + forceResetSanity |> fromSignals ckt + Drop -> + toCSignal + |> unsafeAbortOnBackpressureC + |> forceResetSanity + |> fromSignals (packetFifoImpl cSize mSize) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index 75b4ecd0..4388634f 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -1,5 +1,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE NoImplicitPrelude #-} module Tests.Protocols.PacketStream.PacketFifo ( tests, @@ -14,6 +15,8 @@ import Hedgehog import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range +import qualified Prelude as P + import Test.Tasty import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) import Test.Tasty.Hedgehog.Extra (testProperty) @@ -25,44 +28,87 @@ import Protocols.PacketStream.Base import Protocols.PacketStream.Hedgehog import Protocols.PacketStream.PacketFifo --- | test for id and proper dropping of aborted packets -prop_packetFifo_id :: Property -prop_packetFifo_id = +-- | Drops packets that consist of more than 2^n transfers. +dropBigPackets :: + SNat n -> + [PacketStreamM2S dataWidth meta] -> + [PacketStreamM2S dataWidth meta] +dropBigPackets n packets = + L.concat + $ L.filter + (\p -> L.length p < 2 P.^ snatToInteger n) + (chunkByPacket packets) + +-- | Test for id and proper dropping of aborted packets. +prop_packet_fifo_id :: Property +prop_packet_fifo_id = idWithModelSingleDomain @System - defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - (genPackets 1 30 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 10))) + defExpectOptions + (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 10))) (exposeClockResetEnable dropAbortedPackets) - (exposeClockResetEnable ckt) - where - ckt :: - (HiddenClockResetEnable System) => - Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = packetFifoC d12 d12 Backpressure - --- | test for id with a small buffer to ensure backpressure is tested -prop_packetFifo_small_buffer_id :: Property -prop_packetFifo_small_buffer_id = + (exposeClockResetEnable (packetFifoC @_ @1 @Int16 d10 d10 Backpressure)) + +{- | +Ensure that backpressure becayse of a full content RAM and dropping of packets +that are too big to fit in the FIFO is tested. +-} +prop_packet_fifo_small_buffer_id :: Property +prop_packet_fifo_small_buffer_id = idWithModelSingleDomain @System - defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} + defExpectOptions{eoStopAfterEmpty = 1000} (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 30))) + (exposeClockResetEnable (dropBigPackets d3 . dropAbortedPackets)) + (exposeClockResetEnable (packetFifoC @_ @1 @Int16 d3 d5 Backpressure)) + +-- | test for id using a small metabuffer to ensure backpressure using the metabuffer is tested +prop_packet_fifo_small_meta_buffer_id :: Property +prop_packet_fifo_small_meta_buffer_id = + idWithModelSingleDomain + @System + defExpectOptions + (genPackets 1 30 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 4))) + (exposeClockResetEnable dropAbortedPackets) + (exposeClockResetEnable (packetFifoC @_ @1 @Int16 d10 d2 Backpressure)) + +-- | test for id and proper dropping of aborted packets +prop_overFlowDrop_packetFifo_id :: Property +prop_overFlowDrop_packetFifo_id = + idWithModelSingleDomain + @System + defExpectOptions + (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 10))) (exposeClockResetEnable dropAbortedPackets) - (exposeClockResetEnable ckt) + (exposeClockResetEnable (packetFifoC @_ @1 @Int16 d10 d10 Drop)) + +-- | test for proper dropping when full +prop_overFlowDrop_packetFifo_drop :: Property +prop_overFlowDrop_packetFifo_drop = + propWithModelSingleDomain + @System + defExpectOptions + -- make sure the timeout is long as the packetFifo can be quiet for a while while dropping + (liftA3 (\a b c -> a L.++ b L.++ c) genSmall genBig genSmall) + (exposeClockResetEnable id) + (exposeClockResetEnable (packetFifoC @_ @4 @Int16 d3 d5 Drop)) + (\xs ys -> diff ys L.isSubsequenceOf xs) where - ckt :: - (HiddenClockResetEnable System) => - Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = packetFifoC d5 d5 Backpressure + genSmall = + genValidPacket defPacketOptions{poAbortMode = NoAbort} Gen.enumBounded (Range.linear 0 3) + genBig = + genValidPacket + defPacketOptions{poAbortMode = NoAbort} + Gen.enumBounded + (Range.linear 9 9) -- | test to check if there are no gaps inside of packets prop_packetFifo_no_gaps :: Property prop_packetFifo_no_gaps = property $ do - let packetFifoSize = d12 - maxInputSize = 50 + let maxInputSize = 50 ckt = exposeClockResetEnable - (packetFifoC packetFifoSize packetFifoSize Backpressure) + (packetFifoC d12 d12 Backpressure) systemClockGen resetGen enableGen @@ -75,7 +121,7 @@ prop_packetFifo_no_gaps = property $ do packets :: [PacketStreamM2S 4 Int16] <- forAll gen - let packetSize = 2 Prelude.^ snatToInteger packetFifoSize + let packetSize = 2 P.^ snatToInteger d12 cfg = SimulationConfig 1 (2 * packetSize) False cktResult = simulateC ckt cfg (Just <$> packets) @@ -86,68 +132,9 @@ prop_packetFifo_no_gaps = property $ do noGaps (_ : xs) = noGaps xs noGaps _ = True --- | test for id and proper dropping of aborted packets -prop_overFlowDrop_packetFifo_id :: Property -prop_overFlowDrop_packetFifo_id = - idWithModelSingleDomain - @System - defExpectOptions{eoSampleMax = 2000, eoStopAfterEmpty = 1000} - (genPackets 1 30 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 10))) - (exposeClockResetEnable dropAbortedPackets) - (exposeClockResetEnable ckt) - where - ckt :: - (HiddenClockResetEnable System) => - Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = packetFifoC d12 d12 Drop - --- | test for proper dropping when full -prop_overFlowDrop_packetFifo_drop :: Property -prop_overFlowDrop_packetFifo_drop = - idWithModelSingleDomain - @System - defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - -- make sure the timeout is long as the packetFifo can be quiet for a while while dropping - (liftA3 (\a b c -> a L.++ b L.++ c) genSmall genBig genSmall) - (exposeClockResetEnable model) - (exposeClockResetEnable ckt) - where - ckt :: - (HiddenClockResetEnable System) => - Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = packetFifoC d5 d5 Drop - - model :: [PacketStreamM2S 4 Int16] -> [PacketStreamM2S 4 Int16] - model packets = Prelude.concat $ L.take 1 packetChunk L.++ L.drop 2 packetChunk - where - packetChunk = chunkByPacket packets - - genSmall = - genValidPacket defPacketOptions{poAbortMode = NoAbort} Gen.enumBounded (Range.linear 1 5) - genBig = - genValidPacket - defPacketOptions{poAbortMode = NoAbort} - Gen.enumBounded - (Range.linear 33 33) - --- | test for id using a small metabuffer to ensure backpressure using the metabuffer is tested -prop_packetFifo_small_metaBuffer :: Property -prop_packetFifo_small_metaBuffer = - idWithModelSingleDomain - @System - defExpectOptions - (genPackets 1 30 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 4))) - (exposeClockResetEnable dropAbortedPackets) - (exposeClockResetEnable ckt) - where - ckt :: - (HiddenClockResetEnable System) => - Circuit (PacketStream System 4 Int16) (PacketStream System 4 Int16) - ckt = packetFifoC d12 d2 Backpressure - tests :: TestTree tests = - localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ - localOption - (HedgehogTestLimit (Just 100)) + localOption (mkTimeout 20_000_000 {- 20 seconds -}) + $ localOption + (HedgehogTestLimit (Just 500)) $(testGroupGenerator) From f77599e102fbd9a4db507ebbb7c743e5df1516c5 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 25 Oct 2024 14:20:30 +0200 Subject: [PATCH 53/63] Fix CSignal simulation functions --- clash-protocols/src/Protocols/Internal.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clash-protocols/src/Protocols/Internal.hs b/clash-protocols/src/Protocols/Internal.hs index aa36ef07..34ef214e 100644 --- a/clash-protocols/src/Protocols/Internal.hs +++ b/clash-protocols/src/Protocols/Internal.hs @@ -306,7 +306,7 @@ instance (C.KnownDomain dom) => Simulate (CSignal dom a) where type SimulateChannels (CSignal dom a) = 1 simToSigFwd Proxy list = C.fromList_lazy list - simToSigBwd Proxy () = def + simToSigBwd Proxy () = pure () sigToSimFwd Proxy sig = C.sample_lazy sig sigToSimBwd Proxy _ = () @@ -324,7 +324,7 @@ instance (C.NFDataX a, C.ShowX a, Show a, C.KnownDomain dom) => Drivable (CSigna in Circuit (\_ -> ((), fwd1)) sampleC SimulationConfig{resetCycles, ignoreReset} (Circuit f) = - let sampled = CE.sample_lazy (snd (f ((), def))) + let sampled = CE.sample_lazy (snd (f ((), pure ()))) in if ignoreReset then drop resetCycles sampled else sampled {- | Simulate a circuit. Includes samples while reset is asserted. From c897b8327fb6e5e09de1cea869051adac540e451 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 28 Oct 2024 09:21:35 +0100 Subject: [PATCH 54/63] Remove global constraint solver iterations and unused code --- clash-protocols/clash-protocols.cabal | 1 - .../src/Clash/Sized/Vector/Extra.hs | 23 ------------------- .../Protocols/PacketStream/Depacketizers.hs | 2 +- .../src/Protocols/PacketStream/Packetizers.hs | 2 +- 4 files changed, 2 insertions(+), 26 deletions(-) diff --git a/clash-protocols/clash-protocols.cabal b/clash-protocols/clash-protocols.cabal index fec027bf..6bf555ab 100644 --- a/clash-protocols/clash-protocols.cabal +++ b/clash-protocols/clash-protocols.cabal @@ -77,7 +77,6 @@ common common-options -fplugin GHC.TypeLits.Extra.Solver -fplugin GHC.TypeLits.Normalise -fplugin GHC.TypeLits.KnownNat.Solver - -fconstraint-solver-iterations=6 -- Clash needs access to the source code in compiled modules -fexpose-all-unfoldings diff --git a/clash-protocols/src/Clash/Sized/Vector/Extra.hs b/clash-protocols/src/Clash/Sized/Vector/Extra.hs index a0fcc40d..7ba97c79 100644 --- a/clash-protocols/src/Clash/Sized/Vector/Extra.hs +++ b/clash-protocols/src/Clash/Sized/Vector/Extra.hs @@ -3,7 +3,6 @@ module Clash.Sized.Vector.Extra ( dropLe, takeLe, - appendVec, ) where import Clash.Prelude @@ -35,25 +34,3 @@ takeLe :: Vec m a -> Vec n a takeLe SNat vs = leToPlus @n @m $ takeI vs - --- | Take the first @valid@ elements of @xs@, append @ys@, then pad with 0s -appendVec :: - forall n m a. - (KnownNat n) => - (Num a) => - Index n -> - Vec n a -> - Vec m a -> - Vec (n + m) a -appendVec valid xs ys = results !! valid - where - go :: forall l. SNat l -> Vec (n + m) a - go l@SNat = - let f = addSNat l d1 - in case compareSNat f (SNat @n) of - SNatLE -> takeLe (addSNat l d1) xs ++ ys ++ extra - where - extra :: Vec (n - (l + 1)) a - extra = repeat 0 - _ -> error "appendVec: Absurd" - results = smap (\s _ -> go s) xs diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 5681a61b..98541e3b 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -1,7 +1,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoImplicitPrelude #-} -{-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-} +{-# OPTIONS_GHC -fconstraint-solver-iterations=5 #-} {-# OPTIONS_GHC -fplugin Protocols.Plugin #-} {-# OPTIONS_HADDOCK hide #-} diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index 64881e36..cf5ac7ae 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -1,7 +1,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoImplicitPrelude #-} -{-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-} +{-# OPTIONS_GHC -fconstraint-solver-iterations=5 #-} {-# OPTIONS_HADDOCK hide #-} {- | From 1309de184d8963f253133858d2b3a64a74b58fc6 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 8 Nov 2024 14:00:51 +0100 Subject: [PATCH 55/63] Remove null-byte restriction Forcing bytes in PacketStream _data to be 0x00 turned out to be too restrictive. Allowing them to be undefined makes a lot of components more efficient: depacketizers, packetizers, dropTail, and upConverter. It also strengthens the test framework as we can now force un-enabled bytes to throw an error when evaluated. --- .../src/Protocols/PacketStream/Base.hs | 63 ++++++++++++++++++- .../src/Protocols/PacketStream/Converters.hs | 11 +--- .../Protocols/PacketStream/Depacketizers.hs | 13 +--- .../src/Protocols/PacketStream/Hedgehog.hs | 10 +-- .../src/Protocols/PacketStream/Packetizers.hs | 11 ++-- .../Protocols/PacketStream/PacketFifo.hs | 2 +- 6 files changed, 77 insertions(+), 33 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index e410caba..4bb9c306 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -16,6 +16,9 @@ module Protocols.PacketStream.Base ( PacketStreamS2M (..), PacketStream, + -- * Constants + nullByte, + -- * CSignal conversion toCSignal, unsafeFromCSignal, @@ -102,12 +105,60 @@ data PacketStreamM2S (dataWidth :: Nat) (meta :: Type) = PacketStreamM2S -- ^ Iff true, the packet corresponding to this transfer is invalid. The subordinate -- must either drop the packet or forward the `_abort`. } - deriving (Eq, Generic, ShowX, Show, NFData, Bundle, Functor) + deriving (Generic, ShowX, Show, NFData, Bundle, Functor) deriving instance (KnownNat dataWidth, NFDataX meta) => NFDataX (PacketStreamM2S dataWidth meta) +{- | +Two PacketStream transfers are equal if and only if: + +1. They have the same `_last` +2. They have the same `_meta`. +3. They have the same `_abort`. +4. All bytes in `_data` which are enabled by `_last` are equal. + +Data bytes that are not enabled are not considered in the equality check, +because the protocol allows them to be /undefined/. + +=== Examples + +>>> t1 = PacketStreamM2S (0x11 :> 0x22 :> 0x33 :> Nil) Nothing () False +>>> t2 = PacketStreamM2S (0x11 :> 0x22 :> 0x33 :> Nil) (Just 2) () False +>>> t3 = PacketStreamM2S (0x11 :> 0x22 :> 0xFF :> Nil) (Just 2) () False +>>> t4 = PacketStreamM2S (0x11 :> 0x22 :> undefined :> Nil) (Just 2) () False + +>>> t1 == t1 +True +>>> t2 == t3 +True +>>> t1 /= t2 +True +>>> t3 == t4 +True +-} +instance (KnownNat dataWidth, Eq meta) => Eq (PacketStreamM2S dataWidth meta) where + t1 == t2 = lastEq && metaEq && abortEq && dataEq + where + lastEq = _last t1 == _last t2 + metaEq = _meta t1 == _meta t2 + abortEq = _abort t1 == _abort t2 + + -- Bitmask used for data equality. If the index of a data byte is larger + -- than or equal to the size of `_data`, it is a null byte and must be + -- disregarded in the equality check. + mask = case _last t1 of + Nothing -> repeat False + Just size -> imap (\i _ -> resize i >= size) (_data t1) + + dataEq = case compareSNat (SNat @dataWidth) d0 of + SNatLE -> True + SNatGT -> + leToPlus @1 @dataWidth + $ fold (&&) + $ zipWith3 (\b1 b2 isNull -> isNull || b1 == b2) (_data t1) (_data t2) mask + -- | Used by circuit-notation to create an empty stream instance Default (Maybe (PacketStreamM2S dataWidth meta)) where def = Nothing @@ -141,12 +192,13 @@ Invariants: 3. A manager must keep the metadata (`_meta`) of an entire packet it sends constant. 4. A subordinate which receives a transfer with `_abort` asserted must either forward this `_abort` or drop the packet. 5. A packet may not be interrupted by another packet. -6. All bytes in `_data` which are not enabled must be 0x00. This protocol allows the last transfer of a packet to have zero valid bytes in '_data', so it also allows 0-byte packets. Note that concrete implementations of the protocol are free to disallow 0-byte packets or packets with a trailing zero-byte transfer for whatever reason. + +The value of data bytes which are not enabled is /undefined/. -} data PacketStream (dom :: Domain) (dataWidth :: Nat) (meta :: Type) @@ -240,6 +292,13 @@ instance $ Df.maybeToData <$> sampled +-- | Undefined PacketStream null byte. Will throw an error if evaluated. +nullByte :: BitVector 8 +nullByte = + deepErrorX + $ "value of PacketStream null byte is undefined. " + <> "Data bytes that are not enabled must not be evaluated." + {- | Circuit to convert a 'CSignal' into a 'PacketStream'. This is unsafe, because it ignores all incoming backpressure. diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index 71f945fc..264aae28 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -38,12 +38,10 @@ data UpConverterState (dwOut :: Nat) (n :: Nat) (meta :: Type) = UpConverterStat -- a constant, which is free in hardware in terms of resource usage. , _ucFlush :: Bool -- ^ If true, we should output the current state as a PacketStream transfer. - , _ucFreshBuf :: Bool - -- ^ If true, we need to start a fresh buffer (all zeroes). , _ucAborted :: Bool -- ^ Whether the current transfer we are building is aborted. , _ucLastIdx :: Maybe (Index (dwOut + 1)) - -- ^ If true, the current buffer contains the last byte of the current packet. + -- ^ If Just, the current buffer contains the last byte of the current packet. , _ucMeta :: meta -- ^ Metadata of the current transfer we are a building. } @@ -86,14 +84,13 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M -- output fragment is accepted by the sink outReady = not _ucFlush || inReady bufFull = _ucIdx == maxBound - currBuf = if _ucFreshBuf then repeat 0 else _ucBuf nextBuf = bitCoerce $ replace _ucIdx (bitCoerce _data :: BitVector (8 * dwIn)) - (bitCoerce currBuf :: Vec n (BitVector (8 * dwIn))) + (bitCoerce _ucBuf :: Vec n (BitVector (8 * dwIn))) nextFlush = isJust _last || bufFull nextIdx = if nextFlush then 0 else _ucIdx + 1 @@ -117,7 +114,6 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M , _ucIdx = nextIdx , _ucIdx2 = nextIdx2 , _ucFlush = nextFlush - , _ucFreshBuf = nextFlush , _ucAborted = nextAbort , _ucLastIdx = nextLastIdx , _ucMeta = _meta @@ -146,11 +142,10 @@ upConverter = mealyB go s0 errPrefix = "upConverterT: undefined initial " s0 = UpConverterState - { _ucBuf = deepErrorX (errPrefix <> " _ucBuf") + { _ucBuf = repeat nullByte , _ucIdx = 0 , _ucIdx2 = 0 , _ucFlush = False - , _ucFreshBuf = True , _ucAborted = False , _ucLastIdx = deepErrorX (errPrefix <> " _ucLastIdx") , _ucMeta = deepErrorX (errPrefix <> " _ucMeta") diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 98541e3b..72d0c468 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -32,9 +32,6 @@ import Data.Constraint.Nat.Extra import Data.Data ((:~:) (Refl)) import Data.Maybe -defaultByte :: BitVector 8 -defaultByte = 0x00 - {- | Vectors of this size are able to hold @headerBytes `DivRU` dataWidth@ transfers of size @dataWidth@, which is bigger than or equal to @headerBytes@. -} @@ -167,17 +164,14 @@ depacketizerT toMetaOut st@Forward{..} (Just pkt@PacketStreamM2S{..}, bwdIn) = ( outPkt = case sameNat d0 (SNat @(headerBytes `Mod` dataWidth)) of Just Refl -> pkt - { _data = if _lastFwd then repeat 0x00 else _data + { _data = _data , _last = if _lastFwd then Just 0 else _last , _meta = toMetaOut (bitCoerce header) _meta , _abort = nextAborted } Nothing -> pkt - { _data = - if _lastFwd - then fwdBytes ++ repeat @(dataWidth - ForwardBytes headerBytes dataWidth) defaultByte - else dataOut + { _data = dataOut , _last = if _lastFwd then either Just Just =<< newLast @@ -497,6 +491,5 @@ dropTailC SNat = case strictlyPositiveDivRu @n @dataWidth of [s1, s2] <- fanout -< stream delayed <- delayStreamC (SNat @(n `DivRU` dataWidth)) -< s1 info <- transmitDropInfoC @dataWidth @(n `DivRU` dataWidth) (SNat @n) -< s2 - dropped <- dropTailC' @dataWidth @(n `DivRU` dataWidth) -< (delayed, info) - zeroOutInvalidBytesC -< dropped + dropTailC' @dataWidth @(n `DivRU` dataWidth) -< (delayed, info) ) diff --git a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs index d6d8f91d..7a87ea07 100644 --- a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs +++ b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs @@ -101,7 +101,7 @@ chunkToPacket xs = <$> _last lastTransfer , _abort = any _abort xs , _meta = _meta lastTransfer - , _data = L.foldr ((+>>) . head . _data) (repeat 0x00) xs + , _data = L.foldr ((+>>) . head . _data) (repeat nullByte) xs } where lastTransfer = L.last xs @@ -207,7 +207,7 @@ depacketizerModel toMetaOut ps = L.concat dataWidthPackets fwdF' = case fwdF of [] -> [ PacketStreamM2S - (Vec.singleton 0x00) + (Vec.singleton nullByte) (Just 0) (error "depacketizerModel: should be replaced") (_abort (L.last hdrF)) @@ -285,7 +285,7 @@ dropTailModel SNat packets = L.concatMap go (chunkByPacket packets) go packet = upConvert $ L.init trimmed - L.++ [setNull (L.last trimmed){_last = _last $ L.last bytePkts, _abort = aborted}] + L.++ [(L.last trimmed){_last = _last $ L.last bytePkts, _abort = aborted}] where aborted = L.any _abort packet bytePkts = downConvert packet @@ -468,7 +468,7 @@ genTransfer meta abortGen = {- | Generate the last transfer of a packet, i.e. a transfer with @_last@ set as @Just@. -All bytes which are not enabled are set to 0x00. +All bytes which are not enabled are forced /undefined/. -} genLastTransfer :: forall (dataWidth :: Nat) (meta :: Type). @@ -504,6 +504,6 @@ setNull transfer = fromJust ( Vec.fromList $ L.take (fromIntegral i) (toList (_data transfer)) - L.++ L.replicate ((natToNum @dataWidth) - fromIntegral i) 0x00 + L.++ L.replicate ((natToNum @dataWidth) - fromIntegral i) nullByte ) } diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index cf5ac7ae..3dfcac5b 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -28,9 +28,6 @@ import Data.Constraint.Nat.Extra (leModulusDivisor, leZeroIsZero, strictlyPositi import Data.Maybe import Data.Maybe.Extra -defaultByte :: BitVector 8 -defaultByte = 0x00 - type PacketizerCt (header :: Type) (headerBytes :: Nat) (dataWidth :: Nat) = ( BitPack header , BitSize header ~ headerBytes * 8 @@ -107,7 +104,7 @@ packetizerT1 toMetaOut toHeader st (Just inPkt, bwdIn) = where outPkt = inPkt - { _data = _hdrBuf1 ++ repeat defaultByte + { _data = _hdrBuf1 ++ repeat nullByte , _last = (\i -> i - natToNum @(dataWidth - headerBytes)) <$> _last inPkt , _meta = toMetaOut (_meta inPkt) , _abort = _aborted1 || _abort inPkt @@ -256,7 +253,7 @@ packetizerT3 toMetaOut _ st@Forward3{..} (Just inPkt, bwdIn) = buf :: Vec (headerBytes `Mod` dataWidth) (BitVector 8) (bytesOut, buf) = splitAt (SNat @(dataWidth - headerBytes `Mod` dataWidth)) (_data inPkt) newBuf :: Vec (headerBytes + dataWidth) (BitVector 8) - newBuf = buf ++ repeat @(headerBytes + dataWidth - headerBytes `Mod` dataWidth) defaultByte + newBuf = buf ++ repeat @(headerBytes + dataWidth - headerBytes `Mod` dataWidth) nullByte nextAborted = _aborted3 || _abort inPkt outPkt = @@ -375,7 +372,7 @@ packetizeFromDfT toMetaOut toHeader DfIdle (Df.Data dataIn, bwdIn) = (nextStOut, -- Thus, we don't need to store the metadata in the state. packetizeFromDfT toMetaOut _ st@DfInsert{..} (Df.Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) where - (dataOut, newHdrBuf) = splitAt (SNat @dataWidth) (_dfHdrBuf ++ repeat @dataWidth defaultByte) + (dataOut, newHdrBuf) = splitAt (SNat @dataWidth) (_dfHdrBuf ++ repeat @dataWidth nullByte) outPkt = PacketStreamM2S dataOut newLast (toMetaOut dataIn) False newLast = toMaybe (_dfCounter == maxBound) $ case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of @@ -423,7 +420,7 @@ packetizeFromDfC toMetaOut toHeader = case strictlyPositiveDivRu @headerBytes @d go (Df.Data dataIn, bwdIn) = (Ack (_ready bwdIn), Just outPkt) where outPkt = PacketStreamM2S dataOut (Just l) (toMetaOut dataIn) False - dataOut = bitCoerce (toHeader dataIn) ++ repeat @(dataWidth - headerBytes) defaultByte + dataOut = bitCoerce (toHeader dataIn) ++ repeat @(dataWidth - headerBytes) nullByte l = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of SNatGT -> natToNum @(headerBytes `Mod` dataWidth) _ -> natToNum @dataWidth diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index 4388634f..2df527f2 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -77,7 +77,7 @@ prop_overFlowDrop_packetFifo_id :: Property prop_overFlowDrop_packetFifo_id = idWithModelSingleDomain @System - defExpectOptions + defExpectOptions{eoStopAfterEmpty = 1000} (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 10))) (exposeClockResetEnable dropAbortedPackets) (exposeClockResetEnable (packetFifoC @_ @1 @Int16 d10 d10 Drop)) From d855b6b18f0b18adb911047e657dd1a17d0724cd Mon Sep 17 00:00:00 2001 From: t-wallet Date: Sat, 30 Nov 2024 15:43:31 +0100 Subject: [PATCH 56/63] Add generic padding stripper Adds a generic component which enforces packets to be a certain length, which is extracted from the metadata. Packets that are too small are aborted. This is useful for higher-level network protocols such as IP and UDP. --- clash-protocols/clash-protocols.cabal | 2 + clash-protocols/src/Protocols/PacketStream.hs | 4 + .../src/Protocols/PacketStream/Padding.hs | 166 ++++++++++++++++++ .../tests/Tests/Protocols/PacketStream.hs | 2 + .../Tests/Protocols/PacketStream/Padding.hs | 87 +++++++++ 5 files changed, 261 insertions(+) create mode 100644 clash-protocols/src/Protocols/PacketStream/Padding.hs create mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/Padding.hs diff --git a/clash-protocols/clash-protocols.cabal b/clash-protocols/clash-protocols.cabal index 6bf555ab..8d83070e 100644 --- a/clash-protocols/clash-protocols.cabal +++ b/clash-protocols/clash-protocols.cabal @@ -154,6 +154,7 @@ library Protocols.PacketStream.Hedgehog Protocols.PacketStream.PacketFifo Protocols.PacketStream.Packetizers + Protocols.PacketStream.Padding Protocols.PacketStream.Routing Protocols.Df Protocols.DfConv @@ -210,6 +211,7 @@ test-suite unittests Tests.Protocols.PacketStream.Depacketizers Tests.Protocols.PacketStream.Packetizers Tests.Protocols.PacketStream.PacketFifo + Tests.Protocols.PacketStream.Padding Tests.Protocols.PacketStream.Routing Util diff --git a/clash-protocols/src/Protocols/PacketStream.hs b/clash-protocols/src/Protocols/PacketStream.hs index 1799886d..a14ccf6f 100644 --- a/clash-protocols/src/Protocols/PacketStream.hs +++ b/clash-protocols/src/Protocols/PacketStream.hs @@ -33,6 +33,9 @@ module Protocols.PacketStream ( -- * Packetizers module Protocols.PacketStream.Packetizers, + -- * Padding removal + module Protocols.PacketStream.Padding, + -- * Routing components module Protocols.PacketStream.Routing, ) @@ -45,4 +48,5 @@ import Protocols.PacketStream.Delay import Protocols.PacketStream.Depacketizers import Protocols.PacketStream.PacketFifo import Protocols.PacketStream.Packetizers +import Protocols.PacketStream.Padding import Protocols.PacketStream.Routing diff --git a/clash-protocols/src/Protocols/PacketStream/Padding.hs b/clash-protocols/src/Protocols/PacketStream/Padding.hs new file mode 100644 index 00000000..e6f7ad9a --- /dev/null +++ b/clash-protocols/src/Protocols/PacketStream/Padding.hs @@ -0,0 +1,166 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# OPTIONS_HADDOCK hide #-} + +{- | +Copyright : (C) 2024, QBayLogic B.V. +License : BSD2 (see the file LICENSE) +Maintainer : QBayLogic B.V. + +Provides a generic component which enforces some expected packet length field +in the metadata. +-} +module Protocols.PacketStream.Padding ( + stripPaddingC, +) where + +import Clash.Prelude + +import qualified Data.Bifunctor as B +import Data.Maybe +import Data.Type.Equality ((:~:) (Refl)) + +import Protocols +import Protocols.PacketStream.Base + +-- | State of `stripPaddingT`. +data StripPaddingState p dataWidth meta + = Counting + { _buffer :: PacketStreamM2S dataWidth meta + -- ^ Contains the last transfer, with `_abort` set if a premature end + -- was detected. If the packet contained padding, `_last` is already + -- correctly adjusted. + , _valid :: Bool + -- ^ Qualifier for _buffer. If false, its value is undefined. + , _counter :: Unsigned p + -- ^ Counts the actual length of the current packet. + } + | Strip + { _buffer :: PacketStreamM2S dataWidth meta + -- ^ We need to wait until forwarding the last transfer, as the padding + -- may be aborted. In this state we do not need _valid, as the buffered + -- transfer is always valid. + } + deriving (Generic, NFDataX) + +-- | State transition function of `stripPaddingC`. +stripPaddingT :: + forall dataWidth meta p. + (KnownNat dataWidth) => + (KnownNat p) => + (meta -> Unsigned p) -> + StripPaddingState p dataWidth meta -> + ( Maybe (PacketStreamM2S dataWidth meta) + , PacketStreamS2M + ) -> + ( StripPaddingState p dataWidth meta + , ( PacketStreamS2M + , Maybe (PacketStreamM2S dataWidth meta) + ) + ) +stripPaddingT _ st@Counting{} (Nothing, bwdIn) = (nextSt, (PacketStreamS2M True, fwdOut)) + where + fwdOut = + if _valid st + then Just (_buffer st) + else Nothing + + nextSt + | isJust fwdOut && _ready bwdIn = st{_valid = False} + | otherwise = st +stripPaddingT toLength st@Counting{} (Just inPkt, bwdIn) = (nextSt, (bwdOut, fwdOut)) + where + expectedLen = toLength (_meta inPkt) + + toAdd :: Unsigned p + toAdd = case _last inPkt of + Nothing -> natToNum @dataWidth + -- Here we do a slightly dangerous resize. Because @dataWidth@ should + -- never be bigger than @2^p@, this is not an issue in practice, so I + -- don't believe it requires a constraint as long as it is well-documented. + Just size -> bitCoerce (resize size :: Index (2 ^ p)) + + carry :: Bool + nextCount :: Unsigned p + (carry, nextCount) = + B.bimap unpack unpack + $ split + $ add (_counter st) toAdd + + -- True if the payload size is smaller than expected. + -- We have to take the carry into account as well, otherwise if the + -- calculation overflows then we will wrongly signal a premature end. + prematureEnd = + isJust (_last inPkt) + && (nextCount < expectedLen) + && not carry + + tooBig = nextCount > expectedLen || carry + + fwdOut = + if _valid st + then Just (_buffer st) + else Nothing + + bwdOut = PacketStreamS2M (isNothing fwdOut || _ready bwdIn) + + nextLast + -- If @dataWidth is 1, the adjusted `_last` is always @Just 0@. + -- Otherwise, we need to do some arithmetic. + | tooBig = case sameNat d1 (SNat @dataWidth) of + Just Refl -> Just 0 + Nothing -> Just $ bitCoerce $ resize $ expectedLen - _counter st + | otherwise = _last inPkt + + nextBuf = inPkt{_last = nextLast, _abort = _abort inPkt || prematureEnd} + nextValid = isJust (_last inPkt) || not tooBig + + nextCounter = + if prematureEnd || isJust (_last inPkt) + then 0 + else nextCount + + nextSt + | isJust fwdOut && not (_ready bwdIn) = st + | isNothing (_last inPkt) && tooBig = Strip nextBuf + | otherwise = Counting nextBuf nextValid nextCounter +stripPaddingT _ st@Strip{} (Nothing, _) = (st, (PacketStreamS2M True, Nothing)) +stripPaddingT _ Strip{_buffer = f} (Just inPkt, _) = + (nextSt, (PacketStreamS2M True, Nothing)) + where + nextAborted = _abort f || _abort inPkt + + nextSt = + if isJust (_last inPkt) + then Counting f{_abort = nextAborted} True 0 + else Strip (f{_abort = nextAborted}) + +{- | +Removes padding from packets according to some expected packet length field +in the metadata. If the actual length of a packet is smaller than expected, +the packet is aborted. + +Has one clock cycle of latency, because all M2S outputs are registered. +Runs at full throughput. + +__NB__: @dataWidth@ /must/ be smaller than @2^p@. Because this should never +occur in practice, this constraint is not enforced on the type-level. +-} +stripPaddingC :: + forall dataWidth meta p dom. + (HiddenClockResetEnable dom) => + (KnownNat dataWidth) => + (KnownNat p) => + (NFDataX meta) => + -- | Function that extracts the expected packet length from the metadata + (meta -> Unsigned p) -> + Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) +stripPaddingC toLength = + forceResetSanity + |> fromSignals (mealyB (stripPaddingT toLength) s0) + where + s0 = + Counting + { _buffer = deepErrorX "stripPaddingT: undefined initial buffer." + , _valid = False + , _counter = 0 + } diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream.hs b/clash-protocols/tests/Tests/Protocols/PacketStream.hs index f83077f5..57848d33 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream.hs @@ -9,6 +9,7 @@ import qualified Tests.Protocols.PacketStream.Delay import qualified Tests.Protocols.PacketStream.Depacketizers import qualified Tests.Protocols.PacketStream.PacketFifo import qualified Tests.Protocols.PacketStream.Packetizers +import qualified Tests.Protocols.PacketStream.Padding import qualified Tests.Protocols.PacketStream.Routing tests :: TestTree @@ -22,5 +23,6 @@ tests = , Tests.Protocols.PacketStream.Depacketizers.tests , Tests.Protocols.PacketStream.PacketFifo.tests , Tests.Protocols.PacketStream.Packetizers.tests + , Tests.Protocols.PacketStream.Padding.tests , Tests.Protocols.PacketStream.Routing.tests ] diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Padding.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Padding.hs new file mode 100644 index 00000000..43ade268 --- /dev/null +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Padding.hs @@ -0,0 +1,87 @@ +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE NoImplicitPrelude #-} + +module Tests.Protocols.PacketStream.Padding ( + tests, +) where + +import Clash.Prelude + +import qualified Data.List.Extra as L + +import Hedgehog +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range + +import Test.Tasty +import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) +import Test.Tasty.Hedgehog.Extra (testProperty) +import Test.Tasty.TH (testGroupGenerator) + +import Protocols.Hedgehog +import Protocols.PacketStream +import Protocols.PacketStream.Hedgehog + +-- | Pure model of `stripPaddingC`. +stripPaddingModel :: + forall p. + (KnownNat p) => + [PacketStreamM2S 1 (Unsigned p)] -> + [PacketStreamM2S 1 (Unsigned p)] +stripPaddingModel packets = L.concatMap go (chunkByPacket packets) + where + go packet + | packetBytes == expectedSize = packet + | packetBytes > expectedSize = + x L.++ [(L.head padding){_last = Just 0, _abort = any _abort padding}] + | otherwise = a L.++ [b{_abort = True}] + where + (a, b) = case L.unsnoc packet of + Nothing -> error "stripPaddingModel: list should not be empty." + Just (xs, l) -> (xs, l) + + packetBytes = L.length packet - (if _last b == Just 0 then 1 else 0) + expectedSize = fromIntegral (_meta b) + + (x, padding) = L.splitAt expectedSize packet + +{- | +Test `stripPaddingC` with a given @dataWidth@ against a pure model. + +We make sure to test integer overflow by making the data type which holds +the expected packet length extra small: @Unsigned 6@. +-} +stripPaddingProperty :: + forall dataWidth. + (1 <= dataWidth) => + SNat dataWidth -> + Property +stripPaddingProperty SNat = + idWithModelSingleDomain + @System + defExpectOptions + (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 20))) + (exposeClockResetEnable (upConvert . stripPaddingModel @6 . downConvert)) + (exposeClockResetEnable (stripPaddingC @dataWidth id)) + +prop_strip_padding_d1 :: Property +prop_strip_padding_d1 = stripPaddingProperty d1 + +prop_strip_padding_d2 :: Property +prop_strip_padding_d2 = stripPaddingProperty d2 + +prop_strip_padding_d4 :: Property +prop_strip_padding_d4 = stripPaddingProperty d4 + +prop_strip_padding_d5 :: Property +prop_strip_padding_d5 = stripPaddingProperty d5 + +prop_strip_padding_d8 :: Property +prop_strip_padding_d8 = stripPaddingProperty d8 + +tests :: TestTree +tests = + localOption (mkTimeout 20_000_000 {- 20 seconds -}) + $ localOption + (HedgehogTestLimit (Just 1_000)) + $(testGroupGenerator) From 8038001a901b9fef2784876a00e1818e4bd5eec3 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Mon, 2 Dec 2024 17:31:43 +0100 Subject: [PATCH 57/63] Handle review comments 1 - Removed dangerous leZeroIsZero (could cause type checker to break) - Added source argument to nullByte - Improved documentation of unsafeAbortOnBackpressureC - Remove use of bitCoerce if only pack or unpack is required - Removed M2SNoMeta in favour of PacketStreamM2S n () --- .../src/Data/Constraint/Nat/Extra.hs | 4 --- .../src/Protocols/PacketStream/Base.hs | 31 ++++++++++++++----- .../src/Protocols/PacketStream/Converters.hs | 4 +-- .../src/Protocols/PacketStream/Delay.hs | 30 ++++++------------ .../src/Protocols/PacketStream/Hedgehog.hs | 9 +++--- .../src/Protocols/PacketStream/Packetizers.hs | 24 ++++++++------ 6 files changed, 56 insertions(+), 46 deletions(-) diff --git a/clash-protocols/src/Data/Constraint/Nat/Extra.hs b/clash-protocols/src/Data/Constraint/Nat/Extra.hs index 2344569e..d80a5e80 100644 --- a/clash-protocols/src/Data/Constraint/Nat/Extra.hs +++ b/clash-protocols/src/Data/Constraint/Nat/Extra.hs @@ -22,10 +22,6 @@ cancelMulDiv = unsafeCoerce (Dict :: Dict (0 ~ 0)) leModulusDivisor :: forall a b. (1 <= b) => Dict (Mod a b + 1 <= b) leModulusDivisor = unsafeCoerce (Dict :: Dict (0 <= 0)) --- | if (a <= 0) then (a ~ 0) -leZeroIsZero :: forall (a :: Nat). (a <= 0) => Dict (a ~ 0) -leZeroIsZero = unsafeCoerce (Dict :: Dict (0 ~ 0)) - -- | if (1 <= a) and (1 <= b) then (1 <= DivRU a b) strictlyPositiveDivRu :: forall a b. (1 <= a, 1 <= b) => Dict (1 <= DivRU a b) strictlyPositiveDivRu = unsafeCoerce (Dict :: Dict (0 <= 0)) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 4bb9c306..89fd7d7d 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -292,11 +292,19 @@ instance $ Df.maybeToData <$> sampled --- | Undefined PacketStream null byte. Will throw an error if evaluated. -nullByte :: BitVector 8 -nullByte = +{- | +Undefined PacketStream null byte. Will throw an error if evaluated. The source +of the error should be supplied for a more informative error message; otherwise +it is unclear which component threw the error. +-} +nullByte :: + -- | Component which caused the error + String -> + BitVector 8 +nullByte src = deepErrorX - $ "value of PacketStream null byte is undefined. " + $ src + <> ": value of PacketStream null byte is undefined. " <> "Data bytes that are not enabled must not be evaluated." {- | @@ -334,7 +342,14 @@ unsafeDropBackpressure ckt = unsafeFromCSignal |> ckt |> toCSignal Sets '_abort' upon receiving backpressure from the subordinate. __UNSAFE__: because @fwdOut@ depends on @bwdIn@, this may introduce -combinatorial loops. +combinatorial loops. You need to make sure that a sequential element is +inserted along this path. It is possible to use one of the skid buffers to +ensure this. For example: + +>>> safeAbortOnBackpressureC1 = unsafeAbortOnBackpressureC |> registerFwd +>>> safeAbortOnBackpressureC2 = unsafeAbortOnBackpressureC |> registerBwd + +Note that `registerFwd` utilizes less resources than `registerBwd`. -} unsafeAbortOnBackpressureC :: forall (dataWidth :: Nat) (meta :: Type) (dom :: Domain). @@ -342,9 +357,11 @@ unsafeAbortOnBackpressureC :: Circuit (CSignal dom (Maybe (PacketStreamM2S dataWidth meta))) (PacketStream dom dataWidth meta) -unsafeAbortOnBackpressureC = Circuit $ \(fwdInS, bwdInS) -> (pure (), go <$> bundle (fwdInS, bwdInS)) +unsafeAbortOnBackpressureC = + Circuit $ \(fwdInS, bwdInS) -> (pure (), go <$> bundle (fwdInS, bwdInS)) where - go (fwdIn, bwdIn) = fmap (\pkt -> pkt{_abort = _abort pkt || not (_ready bwdIn)}) fwdIn + go (fwdIn, bwdIn) = + fmap (\pkt -> pkt{_abort = _abort pkt || not (_ready bwdIn)}) fwdIn {- | Force a /nack/ on the backward channel and /Nothing/ on the forward diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index 264aae28..b34f1825 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -89,7 +89,7 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M bitCoerce $ replace _ucIdx - (bitCoerce _data :: BitVector (8 * dwIn)) + (pack _data :: BitVector (8 * dwIn)) (bitCoerce _ucBuf :: Vec n (BitVector (8 * dwIn))) nextFlush = isJust _last || bufFull @@ -142,7 +142,7 @@ upConverter = mealyB go s0 errPrefix = "upConverterT: undefined initial " s0 = UpConverterState - { _ucBuf = repeat nullByte + { _ucBuf = repeat (nullByte "upConverter") , _ucIdx = 0 , _ucIdx2 = 0 , _ucFlush = False diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs index 2bc9abc8..0bbc491a 100644 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ b/clash-protocols/src/Protocols/PacketStream/Delay.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_HADDOCK hide #-} @@ -21,15 +20,6 @@ import Protocols.PacketStream.Base import Data.Constraint.Deferrable ((:~:) (Refl)) import Data.Maybe -type M2SNoMeta dataWidth = - (Vec dataWidth (BitVector 8), Maybe (Index (dataWidth + 1)), Bool) - -toPacketstreamM2S :: M2SNoMeta dataWidth -> meta -> PacketStreamM2S dataWidth meta -toPacketstreamM2S (a, b, c) d = PacketStreamM2S a b d c - -dropMeta :: PacketStreamM2S dataWidth meta -> M2SNoMeta dataWidth -dropMeta PacketStreamM2S{..} = (_data, _last, _abort) - -- | State of 'delayStreamT'. data DelayState n = DelayState { _size :: Index (n + 1) @@ -60,18 +50,18 @@ delayStreamT :: DelayState n -> ( Maybe (PacketStreamM2S dataWidth meta) , PacketStreamS2M - , M2SNoMeta dataWidth + , PacketStreamM2S dataWidth () , meta ) -> ( DelayState n , ( PacketStreamS2M , Maybe (PacketStreamM2S dataWidth meta) , Index n - , Maybe (Index n, M2SNoMeta dataWidth) + , Maybe (Index n, PacketStreamM2S dataWidth ()) , Maybe meta ) ) -delayStreamT st (fwdIn, bwdIn, buff@(_, b, _), metaBuf) = +delayStreamT st (fwdIn, bwdIn, buff, metaBuf) = (nextStOut, (bwdOut, fwdOut, _readPtr nextStOut, writeCmd, mWriteCmd)) where emptyBuf = _size st == 0 @@ -85,7 +75,7 @@ delayStreamT st (fwdIn, bwdIn, buff@(_, b, _), metaBuf) = (readPtr', fwdOut) = if readEn - then (satSucc SatWrap (_readPtr st), Just (toPacketstreamM2S buff metaBuf)) + then (satSucc SatWrap (_readPtr st), Just (buff{_meta = metaBuf})) else (_readPtr st, Nothing) (writeCmd, mWriteCmd, writePtr') = case fwdIn of @@ -93,20 +83,20 @@ delayStreamT st (fwdIn, bwdIn, buff@(_, b, _), metaBuf) = Just inPkt -> ( if readEn && not (_ready bwdIn) then Nothing - else Just (_writePtr st, dropMeta inPkt) - , if _metaWriteEn st || (emptyBuf || readEn && isJust b && _ready bwdIn) + else Just (_writePtr st, inPkt{_meta = ()}) + , if _metaWriteEn st || (emptyBuf || readEn && isJust (_last buff) && _ready bwdIn) then Just (_meta inPkt) else Nothing , satSucc SatWrap (_writePtr st) ) - metaWriteEn' = isNothing fwdIn && (_metaWriteEn st || isJust b) + metaWriteEn' = isNothing fwdIn && (_metaWriteEn st || isJust (_last buff)) (size', flush') = case fwdIn of - Nothing -> (_size st - 1, isNothing b && _flush st) + Nothing -> (_size st - 1, isNothing (_last buff) && _flush st) Just inPkt | _flush st -> - (_size st, not (readEn && isJust b) && _flush st) + (_size st, not (readEn && isJust (_last buff)) && _flush st) | otherwise -> (satSucc SatBound (_size st), isJust (_last inPkt)) @@ -147,7 +137,7 @@ delayStreamC SNat = forceResetSanity |> fromSignals ckt -- Store the contents of transactions without metadata. -- We only need write before read semantics in case n ~ 1. - bram :: Signal dom (M2SNoMeta dataWidth) + bram :: Signal dom (PacketStreamM2S dataWidth ()) bram = case sameNat d1 (SNat @n) of Nothing -> blockRamU NoClearOnReset (SNat @n) noRstFunc readAddr writeCmd Just Refl -> readNew (blockRamU NoClearOnReset (SNat @n) noRstFunc) readAddr writeCmd diff --git a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs index 7a87ea07..837ef76a 100644 --- a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs +++ b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs @@ -101,7 +101,7 @@ chunkToPacket xs = <$> _last lastTransfer , _abort = any _abort xs , _meta = _meta lastTransfer - , _data = L.foldr ((+>>) . head . _data) (repeat nullByte) xs + , _data = L.foldr ((+>>) . head . _data) (repeat (nullByte "chunkToPacket")) xs } where lastTransfer = L.last xs @@ -190,6 +190,7 @@ depacketizerModel :: (KnownNat headerBytes) => (1 <= dataWidth) => (1 <= headerBytes) => + (NFDataX metaIn) => (BitPack header) => (BitSize header ~ headerBytes * 8) => (header -> metaIn -> metaOut) -> @@ -207,9 +208,9 @@ depacketizerModel toMetaOut ps = L.concat dataWidthPackets fwdF' = case fwdF of [] -> [ PacketStreamM2S - (Vec.singleton nullByte) + (Vec.singleton (nullByte "depacketizerModel")) (Just 0) - (error "depacketizerModel: should be replaced") + (deepErrorX "depacketizerModel: should be replaced") (_abort (L.last hdrF)) ] _ -> fwdF @@ -504,6 +505,6 @@ setNull transfer = fromJust ( Vec.fromList $ L.take (fromIntegral i) (toList (_data transfer)) - L.++ L.replicate ((natToNum @dataWidth) - fromIntegral i) nullByte + L.++ L.replicate ((natToNum @dataWidth) - fromIntegral i) (nullByte "setNull") ) } diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index 3dfcac5b..368c3b21 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -24,9 +24,10 @@ import Protocols.PacketStream.Base import Clash.Sized.Vector.Extra (takeLe) import Data.Constraint (Dict (Dict)) -import Data.Constraint.Nat.Extra (leModulusDivisor, leZeroIsZero, strictlyPositiveDivRu) +import Data.Constraint.Nat.Extra (leModulusDivisor, strictlyPositiveDivRu) import Data.Maybe import Data.Maybe.Extra +import Data.Type.Equality ((:~:) (Refl)) type PacketizerCt (header :: Type) (headerBytes :: Nat) (dataWidth :: Nat) = ( BitPack header @@ -104,7 +105,7 @@ packetizerT1 toMetaOut toHeader st (Just inPkt, bwdIn) = where outPkt = inPkt - { _data = _hdrBuf1 ++ repeat nullByte + { _data = _hdrBuf1 ++ repeat (nullByte "packetizerT1") , _last = (\i -> i - natToNum @(dataWidth - headerBytes)) <$> _last inPkt , _meta = toMetaOut (_meta inPkt) , _abort = _aborted1 || _abort inPkt @@ -253,7 +254,7 @@ packetizerT3 toMetaOut _ st@Forward3{..} (Just inPkt, bwdIn) = buf :: Vec (headerBytes `Mod` dataWidth) (BitVector 8) (bytesOut, buf) = splitAt (SNat @(dataWidth - headerBytes `Mod` dataWidth)) (_data inPkt) newBuf :: Vec (headerBytes + dataWidth) (BitVector 8) - newBuf = buf ++ repeat @(headerBytes + dataWidth - headerBytes `Mod` dataWidth) nullByte + newBuf = buf ++ repeat @(headerBytes + dataWidth - headerBytes `Mod` dataWidth) (nullByte "packetizerT3") nextAborted = _aborted3 || _abort inPkt outPkt = @@ -320,10 +321,15 @@ packetizerC toMetaOut toHeader = fromSignals outCircuit outCircuit = case leModulusDivisor @headerBytes @dataWidth of Dict -> case compareSNat (SNat @(headerBytes + 1)) (SNat @dataWidth) of SNatLE -> mealyB (packetizerT1 @headerBytes toMetaOut toHeader) (Insert1 False) - SNatGT -> case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of - SNatLE -> case leZeroIsZero @(headerBytes `Mod` dataWidth) of - Dict -> mealyB (packetizerT2 @headerBytes toMetaOut toHeader) LoadHeader2 - SNatGT -> mealyB (packetizerT3 @headerBytes toMetaOut toHeader) LoadHeader3 + SNatGT -> + case ( sameNat (SNat @(headerBytes `Mod` dataWidth)) d0 + , compareSNat d1 (SNat @(headerBytes `Mod` dataWidth)) + ) of + (Just Refl, _) -> mealyB (packetizerT2 @headerBytes toMetaOut toHeader) LoadHeader2 + (Nothing, SNatLE) -> mealyB (packetizerT3 @headerBytes toMetaOut toHeader) LoadHeader3 + (_, _) -> + clashCompileError + "packetizerC: unreachable. Report this at https://github.com/clash-lang/clash-protocols/issues" data DfPacketizerState (metaOut :: Type) (headerBytes :: Nat) (dataWidth :: Nat) = DfIdle @@ -372,7 +378,7 @@ packetizeFromDfT toMetaOut toHeader DfIdle (Df.Data dataIn, bwdIn) = (nextStOut, -- Thus, we don't need to store the metadata in the state. packetizeFromDfT toMetaOut _ st@DfInsert{..} (Df.Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) where - (dataOut, newHdrBuf) = splitAt (SNat @dataWidth) (_dfHdrBuf ++ repeat @dataWidth nullByte) + (dataOut, newHdrBuf) = splitAt (SNat @dataWidth) (_dfHdrBuf ++ repeat @dataWidth (nullByte "packetizeFromDfT")) outPkt = PacketStreamM2S dataOut newLast (toMetaOut dataIn) False newLast = toMaybe (_dfCounter == maxBound) $ case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of @@ -420,7 +426,7 @@ packetizeFromDfC toMetaOut toHeader = case strictlyPositiveDivRu @headerBytes @d go (Df.Data dataIn, bwdIn) = (Ack (_ready bwdIn), Just outPkt) where outPkt = PacketStreamM2S dataOut (Just l) (toMetaOut dataIn) False - dataOut = bitCoerce (toHeader dataIn) ++ repeat @(dataWidth - headerBytes) nullByte + dataOut = bitCoerce (toHeader dataIn) ++ repeat @(dataWidth - headerBytes) (nullByte "packetizeFromDfC") l = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of SNatGT -> natToNum @(headerBytes `Mod` dataWidth) _ -> natToNum @dataWidth From f44da8b3f6bc424e372ddcc842993faa9dd563ee Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 13 Dec 2024 13:44:54 +0100 Subject: [PATCH 58/63] Improve depacketizer/packetizer testing Now tests the behaviour of the metadata transformer functions. --- .../src/Protocols/PacketStream/Hedgehog.hs | 2 +- .../src/Protocols/PacketStream/Packetizers.hs | 11 +- .../Protocols/PacketStream/Depacketizers.hs | 141 +++++++++++++---- .../Protocols/PacketStream/Packetizers.hs | 146 ++++++++++++++---- 4 files changed, 241 insertions(+), 59 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs index 837ef76a..d9236113 100644 --- a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs +++ b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs @@ -259,7 +259,7 @@ depacketizeToDfModel toOut ps = L.map parseHdr bytePackets parseHdr :: [PacketStreamM2S 1 metaIn] -> a parseHdr hdrF = toOut - (bitCoerce $ Vec.unsafeFromList $ _data <$> hdrF) + (bitCoerce $ Vec.unsafeFromList $ L.map _data hdrF) (_meta $ L.head hdrF) bytePackets :: [[PacketStreamM2S 1 metaIn]] diff --git a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs index 368c3b21..d35fe927 100644 --- a/clash-protocols/src/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Packetizers.hs @@ -254,7 +254,9 @@ packetizerT3 toMetaOut _ st@Forward3{..} (Just inPkt, bwdIn) = buf :: Vec (headerBytes `Mod` dataWidth) (BitVector 8) (bytesOut, buf) = splitAt (SNat @(dataWidth - headerBytes `Mod` dataWidth)) (_data inPkt) newBuf :: Vec (headerBytes + dataWidth) (BitVector 8) - newBuf = buf ++ repeat @(headerBytes + dataWidth - headerBytes `Mod` dataWidth) (nullByte "packetizerT3") + newBuf = + buf + ++ repeat @(headerBytes + dataWidth - headerBytes `Mod` dataWidth) (nullByte "packetizerT3") nextAborted = _aborted3 || _abort inPkt outPkt = @@ -378,7 +380,8 @@ packetizeFromDfT toMetaOut toHeader DfIdle (Df.Data dataIn, bwdIn) = (nextStOut, -- Thus, we don't need to store the metadata in the state. packetizeFromDfT toMetaOut _ st@DfInsert{..} (Df.Data dataIn, bwdIn) = (nextStOut, (bwdOut, Just outPkt)) where - (dataOut, newHdrBuf) = splitAt (SNat @dataWidth) (_dfHdrBuf ++ repeat @dataWidth (nullByte "packetizeFromDfT")) + (dataOut, newHdrBuf) = + splitAt (SNat @dataWidth) (_dfHdrBuf ++ repeat @dataWidth (nullByte "packetizeFromDfT")) outPkt = PacketStreamM2S dataOut newLast (toMetaOut dataIn) False newLast = toMaybe (_dfCounter == maxBound) $ case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of @@ -426,7 +429,9 @@ packetizeFromDfC toMetaOut toHeader = case strictlyPositiveDivRu @headerBytes @d go (Df.Data dataIn, bwdIn) = (Ack (_ready bwdIn), Just outPkt) where outPkt = PacketStreamM2S dataOut (Just l) (toMetaOut dataIn) False - dataOut = bitCoerce (toHeader dataIn) ++ repeat @(dataWidth - headerBytes) (nullByte "packetizeFromDfC") + dataOut = + bitCoerce (toHeader dataIn) + ++ repeat @(dataWidth - headerBytes) (nullByte "packetizeFromDfC") l = case compareSNat (SNat @(headerBytes `Mod` dataWidth)) d0 of SNatGT -> natToNum @(headerBytes `Mod` dataWidth) _ -> natToNum @dataWidth diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index f76595b5..7487860d 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -1,5 +1,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE NoImplicitPrelude #-} module Tests.Protocols.PacketStream.Depacketizers ( tests, @@ -7,7 +8,9 @@ module Tests.Protocols.PacketStream.Depacketizers ( import Clash.Prelude -import Hedgehog (Property) +import Control.DeepSeq (NFData) + +import Hedgehog (Gen, Property) import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range @@ -23,58 +26,89 @@ import Protocols.PacketStream.Base import Protocols.PacketStream.Depacketizers import Protocols.PacketStream.Hedgehog -{- | Test the depacketizer with varying datawidth and number of bytes in the header, - with metaIn = () and toMetaOut = const. +{- | +Test @depacketizerC@ with varying data width, number of bytes in the +header, input metadata, and output metadata. -} -depacketizerPropertyGenerator :: +depacketizerPropGen :: forall + (metaIn :: Type) + (metaOut :: Type) (dataWidth :: Nat) (headerBytes :: Nat). (1 <= dataWidth) => (1 <= headerBytes) => + (NFData metaIn) => + (NFDataX metaIn) => + (NFData metaOut) => + (NFDataX metaOut) => + (Eq metaIn) => + (Eq metaOut) => + (Show metaIn) => + (Show metaOut) => + (ShowX metaIn) => + (ShowX metaOut) => SNat dataWidth -> SNat headerBytes -> + Gen metaIn -> + (Vec headerBytes (BitVector 8) -> metaIn -> metaOut) -> Property -depacketizerPropertyGenerator SNat SNat = +depacketizerPropGen SNat SNat metaGen toMetaOut = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - (genPackets 1 4 (genValidPacket defPacketOptions (pure ()) (Range.linear 0 30))) - (exposeClockResetEnable (depacketizerModel const)) + (genPackets 1 4 (genValidPacket defPacketOptions metaGen (Range.linear 0 30))) + (exposeClockResetEnable (depacketizerModel toMetaOut)) (exposeClockResetEnable ckt) where ckt :: (HiddenClockResetEnable System) => Circuit - (PacketStream System dataWidth ()) - (PacketStream System dataWidth (Vec headerBytes (BitVector 8))) - ckt = depacketizerC const + (PacketStream System dataWidth metaIn) + (PacketStream System dataWidth metaOut) + ckt = depacketizerC toMetaOut {- | -Test depacketizeToDf with varying datawidth and number of bytes in the header, -with metaIn = () and toMetaOut = const. +Test @depacketizeToDfC@ with varying data width, number of bytes in the +header, input metadata, and output type @a@. -} -depacketizeToDfPropertyGenerator :: +depacketizeToDfPropGen :: forall + (metaIn :: Type) + (a :: Type) (dataWidth :: Nat) (headerBytes :: Nat). (1 <= dataWidth) => (1 <= headerBytes) => + (BitPack a) => + (BitSize a ~ headerBytes * 8) => + (NFData metaIn) => + (NFDataX metaIn) => + (NFData a) => + (NFDataX a) => + (Eq metaIn) => + (Eq a) => + (Show metaIn) => + (Show a) => + (ShowX metaIn) => + (ShowX a) => SNat dataWidth -> SNat headerBytes -> + Gen metaIn -> + (Vec headerBytes (BitVector 8) -> metaIn -> a) -> Property -depacketizeToDfPropertyGenerator SNat SNat = +depacketizeToDfPropGen SNat SNat metaGen toOut = idWithModelSingleDomain @System defExpectOptions{eoSampleMax = 1000, eoStopAfterEmpty = 1000} - (genPackets 1 10 (genValidPacket defPacketOptions (pure ()) (Range.linear 0 20))) - (exposeClockResetEnable (depacketizeToDfModel const)) + (genPackets 1 10 (genValidPacket defPacketOptions metaGen (Range.linear 0 20))) + (exposeClockResetEnable (depacketizeToDfModel toOut)) (exposeClockResetEnable ckt) where ckt :: (HiddenClockResetEnable System) => - Circuit (PacketStream System dataWidth ()) (Df System (Vec headerBytes (BitVector 8))) - ckt = depacketizeToDfC const + Circuit (PacketStream System dataWidth metaIn) (Df System a) + ckt = depacketizeToDfC toOut -- | Test 'dropTailC' with varying data width and bytes to drop. dropTailTest :: @@ -101,37 +135,88 @@ dropTailTest SNat n = (exposeClockResetEnable (dropTailModel n)) (exposeClockResetEnable (dropTailC @dataWidth n)) +{- | +Do something interesting with both the parsed header and the input +metadata for testing purposes. We just xor every byte in the parsed +header with the byte in the input metadata. +-} +exampleToMetaOut :: + Vec headerBytes (BitVector 8) -> + BitVector 8 -> + Vec headerBytes (BitVector 8) +exampleToMetaOut hdr metaIn = map (`xor` metaIn) hdr + -- | headerBytes % dataWidth ~ 0 prop_const_depacketizer_d1_d14 :: Property -prop_const_depacketizer_d1_d14 = depacketizerPropertyGenerator d1 d14 +prop_const_depacketizer_d1_d14 = + depacketizerPropGen d1 d14 (pure ()) const + +prop_xor_depacketizer_d1_d14 :: Property +prop_xor_depacketizer_d1_d14 = + depacketizerPropGen d1 d14 Gen.enumBounded exampleToMetaOut -- | dataWidth < headerBytes prop_const_depacketizer_d3_d11 :: Property -prop_const_depacketizer_d3_d11 = depacketizerPropertyGenerator d3 d11 +prop_const_depacketizer_d3_d11 = + depacketizerPropGen d3 d11 (pure ()) const + +prop_xor_depacketizer_d3_d11 :: Property +prop_xor_depacketizer_d3_d11 = + depacketizerPropGen d3 d11 Gen.enumBounded exampleToMetaOut -- | dataWidth ~ header byte size prop_const_depacketizer_d7_d7 :: Property -prop_const_depacketizer_d7_d7 = depacketizerPropertyGenerator d7 d7 +prop_const_depacketizer_d7_d7 = + depacketizerPropGen d7 d7 (pure ()) const + +prop_xor_depacketizer_d7_d7 :: Property +prop_xor_depacketizer_d7_d7 = + depacketizerPropGen d7 d7 Gen.enumBounded exampleToMetaOut -- | dataWidth > header byte size prop_const_depacketizer_d5_d4 :: Property -prop_const_depacketizer_d5_d4 = depacketizerPropertyGenerator d5 d4 +prop_const_depacketizer_d5_d4 = + depacketizerPropGen d5 d4 (pure ()) const + +prop_xor_depacketizer_d5_d4 :: Property +prop_xor_depacketizer_d5_d4 = + depacketizerPropGen d5 d4 Gen.enumBounded exampleToMetaOut -- | headerBytes % dataWidth ~ 0 prop_const_depacketize_to_df_d1_d14 :: Property -prop_const_depacketize_to_df_d1_d14 = depacketizeToDfPropertyGenerator d1 d14 +prop_const_depacketize_to_df_d1_d14 = + depacketizeToDfPropGen d1 d14 (pure ()) const + +prop_xor_depacketize_to_df_d1_d14 :: Property +prop_xor_depacketize_to_df_d1_d14 = + depacketizeToDfPropGen d1 d14 Gen.enumBounded exampleToMetaOut -- | dataWidth < headerBytes prop_const_depacketize_to_df_d3_d11 :: Property -prop_const_depacketize_to_df_d3_d11 = depacketizeToDfPropertyGenerator d3 d11 +prop_const_depacketize_to_df_d3_d11 = + depacketizeToDfPropGen d3 d11 (pure ()) const + +prop_xor_depacketize_to_df_d3_d11 :: Property +prop_xor_depacketize_to_df_d3_d11 = + depacketizeToDfPropGen d3 d11 Gen.enumBounded exampleToMetaOut -- | dataWidth ~ header byte size prop_const_depacketize_to_df_d7_d7 :: Property -prop_const_depacketize_to_df_d7_d7 = depacketizeToDfPropertyGenerator d7 d7 +prop_const_depacketize_to_df_d7_d7 = + depacketizeToDfPropGen d7 d7 (pure ()) const + +prop_xor_depacketize_to_df_d7_d7 :: Property +prop_xor_depacketize_to_df_d7_d7 = + depacketizeToDfPropGen d7 d7 Gen.enumBounded exampleToMetaOut -- | dataWidth > header byte size prop_const_depacketize_to_df_d5_d4 :: Property -prop_const_depacketize_to_df_d5_d4 = depacketizeToDfPropertyGenerator d5 d4 +prop_const_depacketize_to_df_d5_d4 = + depacketizeToDfPropGen d5 d4 (pure ()) const + +prop_xor_depacketize_to_df_d5_d4 :: Property +prop_xor_depacketize_to_df_d5_d4 = + depacketizeToDfPropGen d5 d4 Gen.enumBounded exampleToMetaOut -- | dataWidth < n && dataWidth % n ~ 0 prop_droptail_4_bytes_d1 :: Property @@ -163,7 +248,7 @@ prop_droptail_7_bytes_d12 = dropTailTest d12 d7 tests :: TestTree tests = - localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ - localOption + localOption (mkTimeout 20_000_000 {- 20 seconds -}) + $ localOption (HedgehogTestLimit (Just 500)) $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs index 9edb1c43..8eeada06 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs @@ -1,5 +1,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE NoImplicitPrelude #-} module Tests.Protocols.PacketStream.Packetizers ( tests, @@ -8,6 +9,8 @@ module Tests.Protocols.PacketStream.Packetizers ( import Clash.Hedgehog.Sized.Vector (genVec) import Clash.Prelude +import Control.DeepSeq (NFData) + import Hedgehog import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range @@ -25,19 +28,33 @@ import Protocols.PacketStream.Base import Protocols.PacketStream.Hedgehog {- | -Test the packetizer with varying datawidth and number of bytes in the header, -with metaOut = (). +Test @packetizerC@ with varying data width, number of bytes in the +header, input metadata, and output metadata. + +We consider the input metadata to be @Vec metaInBytes (BitVector 8)@ to +avoid unnecessary conversions, because @packetizerC@ requires that the +input metadata is convertible to this type anyway. -} -packetizerPropertyGenerator :: +packetizerPropGen :: forall (dataWidth :: Nat) - (headerBytes :: Nat). + (headerBytes :: Nat) + (metaInBytes :: Nat) + (metaOut :: Type). + (KnownNat metaInBytes) => (1 <= dataWidth) => (1 <= headerBytes) => + (NFData metaOut) => + (NFDataX metaOut) => + (Eq metaOut) => + (Show metaOut) => + (ShowX metaOut) => SNat dataWidth -> SNat headerBytes -> + (Vec metaInBytes (BitVector 8) -> metaOut) -> + (Vec metaInBytes (BitVector 8) -> Vec headerBytes (BitVector 8)) -> Property -packetizerPropertyGenerator SNat SNat = +packetizerPropGen SNat SNat toMetaOut toHeader = idWithModelSingleDomain @System defExpectOptions @@ -49,28 +66,42 @@ packetizerPropertyGenerator SNat SNat = (exposeClockResetEnable model) (exposeClockResetEnable ckt) where - model = packetizerModel (const ()) id + model = packetizerModel toMetaOut toHeader ckt :: (HiddenClockResetEnable System) => Circuit - (PacketStream System dataWidth (Vec headerBytes (BitVector 8))) - (PacketStream System dataWidth ()) - ckt = packetizerC (const ()) id + (PacketStream System dataWidth (Vec metaInBytes (BitVector 8))) + (PacketStream System dataWidth metaOut) + ckt = packetizerC toMetaOut toHeader {- | -Test packetizeFromDf with varying datawidth and number of bytes in the header, -with metaOut = (). +Test @packetizeFromDfC@ with varying data width, number of bytes in the +header, input type, and output metadata. + +We consider the input type to be @Vec aBytes (BitVector 8)@ to +avoid unnecessary conversions, because @packetizerC@ requires that the +input type is convertible to this type anyway. -} -packetizeFromDfPropertyGenerator :: +packetizeFromDfPropGen :: forall (dataWidth :: Nat) - (headerBytes :: Nat). + (headerBytes :: Nat) + (aBytes :: Nat) + (metaOut :: Type). + (KnownNat aBytes) => (1 <= dataWidth) => (1 <= headerBytes) => + (NFData metaOut) => + (NFDataX metaOut) => + (Eq metaOut) => + (Show metaOut) => + (ShowX metaOut) => SNat dataWidth -> SNat headerBytes -> + (Vec aBytes (BitVector 8) -> metaOut) -> + (Vec aBytes (BitVector 8) -> Vec headerBytes (BitVector 8)) -> Property -packetizeFromDfPropertyGenerator SNat SNat = +packetizeFromDfPropGen SNat SNat toMetaOut toHeader = idWithModelSingleDomain @System defExpectOptions @@ -78,47 +109,108 @@ packetizeFromDfPropertyGenerator SNat SNat = (exposeClockResetEnable model) (exposeClockResetEnable ckt) where - model = packetizeFromDfModel (const ()) id + model = packetizeFromDfModel toMetaOut toHeader ckt :: (HiddenClockResetEnable System) => - Circuit (Df.Df System (Vec headerBytes (BitVector 8))) (PacketStream System dataWidth ()) - ckt = packetizeFromDfC (const ()) id + Circuit + (Df.Df System (Vec aBytes (BitVector 8))) + (PacketStream System dataWidth metaOut) + ckt = packetizeFromDfC toMetaOut toHeader + +{- | +Do something interesting with the input metadata to derive the output +metadata for testing purposes. We just xor-reduce the input metadata. +-} +myToMetaOut :: Vec n (BitVector 8) -> BitVector 8 +myToMetaOut = foldr xor 0 + +{- | +Do something interesting with the input metadata to derive the header +for testing purposes. We just xor every byte in the input metadata with +an arbitrary constant and add some bytes. +-} +myToHeader :: + forall metaInBytes headerBytes. + (2 + metaInBytes ~ headerBytes) => + Vec metaInBytes (BitVector 8) -> + Vec headerBytes (BitVector 8) +myToHeader metaIn = map (`xor` 0xAB) metaIn ++ (0x01 :> 0x02 :> Nil) -- | headerBytes % dataWidth ~ 0 prop_const_packetizer_d1_d14 :: Property -prop_const_packetizer_d1_d14 = packetizerPropertyGenerator d1 d14 +prop_const_packetizer_d1_d14 = + packetizerPropGen d1 d14 (const ()) id + +prop_xor_packetizer_d1_d14 :: Property +prop_xor_packetizer_d1_d14 = + packetizerPropGen d1 d14 myToMetaOut myToHeader -- | dataWidth < headerBytes prop_const_packetizer_d3_d11 :: Property -prop_const_packetizer_d3_d11 = packetizerPropertyGenerator d3 d11 +prop_const_packetizer_d3_d11 = + packetizerPropGen d3 d11 (const ()) id + +prop_xor_packetizer_d3_d11 :: Property +prop_xor_packetizer_d3_d11 = + packetizerPropGen d3 d11 myToMetaOut myToHeader -- | dataWidth ~ header byte size prop_const_packetizer_d7_d7 :: Property -prop_const_packetizer_d7_d7 = packetizerPropertyGenerator d7 d7 +prop_const_packetizer_d7_d7 = + packetizerPropGen d7 d7 (const ()) id + +prop_xor_packetizer_d7_d7 :: Property +prop_xor_packetizer_d7_d7 = + packetizerPropGen d7 d7 myToMetaOut myToHeader -- | dataWidth > header byte size prop_const_packetizer_d5_d4 :: Property -prop_const_packetizer_d5_d4 = packetizerPropertyGenerator d5 d4 +prop_const_packetizer_d5_d4 = + packetizerPropGen d5 d4 (const ()) id + +prop_xor_packetizer_d5_d4 :: Property +prop_xor_packetizer_d5_d4 = + packetizerPropGen d5 d4 myToMetaOut myToHeader -- | headerBytes % dataWidth ~ 0 prop_const_packetizeFromDf_d1_d14 :: Property -prop_const_packetizeFromDf_d1_d14 = packetizeFromDfPropertyGenerator d1 d14 +prop_const_packetizeFromDf_d1_d14 = + packetizeFromDfPropGen d1 d14 (const ()) id + +prop_xor_packetizeFromDf_d1_d14 :: Property +prop_xor_packetizeFromDf_d1_d14 = + packetizeFromDfPropGen d1 d14 myToMetaOut myToHeader -- | dataWidth < headerBytes prop_const_packetizeFromDf_d3_d11 :: Property -prop_const_packetizeFromDf_d3_d11 = packetizeFromDfPropertyGenerator d3 d11 +prop_const_packetizeFromDf_d3_d11 = + packetizeFromDfPropGen d3 d11 (const ()) id + +prop_xor_packetizeFromDf_d3_d11 :: Property +prop_xor_packetizeFromDf_d3_d11 = + packetizeFromDfPropGen d3 d11 myToMetaOut myToHeader -- | dataWidth ~ header byte size prop_const_packetizeFromDf_d7_d7 :: Property -prop_const_packetizeFromDf_d7_d7 = packetizeFromDfPropertyGenerator d7 d7 +prop_const_packetizeFromDf_d7_d7 = + packetizeFromDfPropGen d7 d7 (const ()) id + +prop_xor_packetizeFromDf_d7_d7 :: Property +prop_xor_packetizeFromDf_d7_d7 = + packetizeFromDfPropGen d7 d7 myToMetaOut myToHeader -- | dataWidth > header byte size prop_const_packetizeFromDf_d5_d4 :: Property -prop_const_packetizeFromDf_d5_d4 = packetizeFromDfPropertyGenerator d5 d4 +prop_const_packetizeFromDf_d5_d4 = + packetizeFromDfPropGen d5 d4 (const ()) id + +prop_xor_packetizeFromDf_d5_d4 :: Property +prop_xor_packetizeFromDf_d5_d4 = + packetizeFromDfPropGen d5 d4 myToMetaOut myToHeader tests :: TestTree tests = - localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ - localOption + localOption (mkTimeout 20_000_000 {- 20 seconds -}) + $ localOption (HedgehogTestLimit (Just 1_000)) $(testGroupGenerator) From d673a9d93cef0cfaddf52ded12e5c7b1ee6ea055 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 13 Dec 2024 13:47:31 +0100 Subject: [PATCH 59/63] Converters: remove unneeded constraints --- .../src/Protocols/PacketStream/Converters.hs | 103 ++++++++---------- .../Protocols/PacketStream/Converters.hs | 40 +++---- 2 files changed, 64 insertions(+), 79 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Converters.hs b/clash-protocols/src/Protocols/PacketStream/Converters.hs index b34f1825..ebbf932b 100644 --- a/clash-protocols/src/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/src/Protocols/PacketStream/Converters.hs @@ -26,12 +26,12 @@ import Protocols (CSignal, Circuit (..), fromSignals, idC, (|>)) import Protocols.PacketStream.Base -- | State of 'upConverter'. -data UpConverterState (dwOut :: Nat) (n :: Nat) (meta :: Type) = UpConverterState - { _ucBuf :: Vec dwOut (BitVector 8) +data UpConverterState (dwIn :: Nat) (n :: Nat) (meta :: Type) = UpConverterState + { _ucBuf :: Vec (dwIn * n) (BitVector 8) -- ^ The data buffer we are filling. , _ucIdx :: Index n -- ^ Where in _ucBuf we need to write the next data. - , _ucIdx2 :: Index (dwOut + 1) + , _ucIdx2 :: Index (dwIn * n + 1) -- ^ Used when @dwIn@ is not a power of two to determine the adjusted '_last', -- to avoid multiplication (infers an expensive DSP slice). -- If @dwIn@ is a power of two then we can multiply by shifting left with @@ -40,7 +40,7 @@ data UpConverterState (dwOut :: Nat) (n :: Nat) (meta :: Type) = UpConverterStat -- ^ If true, we should output the current state as a PacketStream transfer. , _ucAborted :: Bool -- ^ Whether the current transfer we are building is aborted. - , _ucLastIdx :: Maybe (Index (dwOut + 1)) + , _ucLastIdx :: Maybe (Index (dwIn * n + 1)) -- ^ If Just, the current buffer contains the last byte of the current packet. , _ucMeta :: meta -- ^ Metadata of the current transfer we are a building. @@ -49,19 +49,16 @@ data UpConverterState (dwOut :: Nat) (n :: Nat) (meta :: Type) = UpConverterStat -- | Computes the next state for 'upConverter'. nextState :: - forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (n :: Nat). + forall (dwIn :: Nat) (meta :: Type) (n :: Nat). (1 <= dwIn) => - (1 <= dwOut) => (1 <= n) => (KnownNat dwIn) => - (KnownNat dwOut) => (KnownNat n) => (NFDataX meta) => - (dwOut ~ dwIn * n) => - UpConverterState dwOut n meta -> + UpConverterState dwIn n meta -> Maybe (PacketStreamM2S dwIn meta) -> PacketStreamS2M -> - UpConverterState dwOut n meta + UpConverterState dwIn n meta nextState st@(UpConverterState{..}) Nothing (PacketStreamS2M inReady) = nextSt where @@ -121,21 +118,18 @@ nextState st@(UpConverterState{..}) (Just PacketStreamM2S{..}) (PacketStreamS2M nextSt = if outReady then nextStRaw else st upConverter :: - forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). + forall (dwIn :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). (HiddenClockResetEnable dom) => (1 <= dwIn) => - (1 <= dwOut) => (1 <= n) => (KnownNat dwIn) => - (KnownNat dwOut) => (KnownNat n) => - (dwOut ~ dwIn * n) => (NFDataX meta) => ( Signal dom (Maybe (PacketStreamM2S dwIn meta)) , Signal dom PacketStreamS2M ) -> ( Signal dom PacketStreamS2M - , Signal dom (Maybe (PacketStreamM2S dwOut meta)) + , Signal dom (Maybe (PacketStreamM2S (dwIn * n) meta)) ) upConverter = mealyB go s0 where @@ -166,8 +160,8 @@ upConverter = mealyB go s0 {- | Converts packet streams of arbitrary data width @dwIn@ to packet streams of -a bigger (or equal) data width @dwOut@, where @dwOut@ must divide @dwIn@. -When @dwIn ~ dwOut@, this component is just the identity circuit, `idC`. +a bigger (or equal) data width @dwIn * n@, where @n > 0@. +When @n ~ 1@, this component is just the identity circuit, `idC`. If '_abort' is asserted on any of the input sub-transfers, it will be asserted on the corresponding output transfer as well. All zero-byte transfers are @@ -177,19 +171,16 @@ Has one cycle of latency, all M2S outputs are registered. Provides full throughput. -} upConverterC :: - forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). + forall (dwIn :: Nat) (n :: Nat) (meta :: Type) (dom :: Domain). (HiddenClockResetEnable dom) => (1 <= dwIn) => - (1 <= dwOut) => (1 <= n) => (KnownNat dwIn) => - (KnownNat dwOut) => (KnownNat n) => - (dwOut ~ dwIn * n) => (NFDataX meta) => -- | Upconverter circuit - Circuit (PacketStream dom dwIn meta) (PacketStream dom dwOut meta) -upConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of + Circuit (PacketStream dom dwIn meta) (PacketStream dom (dwIn * n) meta) +upConverterC = case sameNat d1 (SNat @n) of Just Refl -> idC _ -> forceResetSanity |> fromSignals upConverter @@ -203,27 +194,24 @@ backpressure. Using this variant in that case will improve timing and probably reduce resource usage. -} unsafeUpConverterC :: - forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). + forall (dwIn :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). (HiddenClockResetEnable dom) => (1 <= dwIn) => - (1 <= dwOut) => (1 <= n) => (KnownNat dwIn) => - (KnownNat dwOut) => (KnownNat n) => - (dwOut ~ dwIn * n) => (NFDataX meta) => -- | Unsafe upconverter circuit Circuit (CSignal dom (Maybe (PacketStreamM2S dwIn meta))) - (CSignal dom (Maybe (PacketStreamM2S dwOut meta))) -unsafeUpConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of + (CSignal dom (Maybe (PacketStreamM2S (dwIn * n) meta))) +unsafeUpConverterC = case sameNat d1 (SNat @n) of Just Refl -> idC _ -> unsafeDropBackpressure (fromSignals upConverter) -- | State of 'downConverterT'. -data DownConverterState (dwIn :: Nat) (meta :: Type) = DownConverterState - { _dcBuf :: Vec dwIn (BitVector 8) +data DownConverterState (dwOut :: Nat) (n :: Nat) (meta :: Type) = DownConverterState + { _dcBuf :: Vec (dwOut * n) (BitVector 8) -- ^ Registered _data of the last transfer. , _dcLast :: Bool -- ^ Is the last transfer the end of a packet? @@ -232,7 +220,7 @@ data DownConverterState (dwIn :: Nat) (meta :: Type) = DownConverterState , _dcAborted :: Bool -- ^ Registered _abort of the last transfer. All sub-transfers corresponding -- to this transfer need to be marked with the same _abort value. - , _dcSize :: Index (dwIn + 1) + , _dcSize :: Index (dwOut * n + 1) -- ^ Number of valid bytes in _dcBuf. , _dcZeroByteTransfer :: Bool -- ^ Is the current transfer we store a zero-byte transfer? In this case, @@ -241,25 +229,25 @@ data DownConverterState (dwIn :: Nat) (meta :: Type) = DownConverterState } deriving (Generic, NFDataX) --- | State transition function of 'downConverterC', in case @dwIn /= dwOut@. +-- | State transition function of 'downConverterC', in case @n > 1@. downConverterT :: - forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (n :: Nat). - (1 <= dwIn) => + forall (dwOut :: Nat) (n :: Nat) (meta :: Type). + (KnownNat dwOut) => + (KnownNat n) => (1 <= dwOut) => (1 <= n) => (NFDataX meta) => - (KnownNat dwIn) => - (KnownNat dwOut) => - (dwIn ~ dwOut * n) => - DownConverterState dwIn meta -> - (Maybe (PacketStreamM2S dwIn meta), PacketStreamS2M) -> - ( DownConverterState dwIn meta + DownConverterState dwOut n meta -> + (Maybe (PacketStreamM2S (dwOut * n) meta), PacketStreamS2M) -> + ( DownConverterState dwOut n meta , (PacketStreamS2M, Maybe (PacketStreamM2S dwOut meta)) ) downConverterT st@(DownConverterState{..}) (fwdIn, bwdIn) = (nextSt, (PacketStreamS2M readyOut, fwdOut)) where - (shiftedBuf, dataOut) = leToPlus @dwOut @dwIn $ shiftOutFrom0 (SNat @dwOut) _dcBuf + (shiftedBuf, dataOut) = + leToPlus @dwOut @(dwOut * n) + $ shiftOutFrom0 (SNat @dwOut) _dcBuf -- Either we preserve a zero-byte transfer or we have some real data to transmit. fwdOut = @@ -278,7 +266,9 @@ downConverterT st@(DownConverterState{..}) (fwdIn, bwdIn) = -- the final sub-transfer is acknowledged this clock cycle, we can acknowledge -- newly received valid data and load it into our registers. emptyState = _dcSize == 0 && not _dcZeroByteTransfer - readyOut = isJust fwdIn && (emptyState || (_dcSize <= natToNum @dwOut && _ready bwdIn)) + readyOut = + isJust fwdIn + && (emptyState || (_dcSize <= natToNum @dwOut && _ready bwdIn)) nextSt | readyOut = newState (fromJustX fwdIn) @@ -295,42 +285,41 @@ downConverterT st@(DownConverterState{..}) (fwdIn, bwdIn) = DownConverterState { _dcBuf = _data , _dcMeta = _meta - , _dcSize = fromMaybe (natToNum @dwIn) _last + , _dcSize = fromMaybe (natToNum @(dwOut * n)) _last , _dcLast = isJust _last , _dcAborted = _abort , _dcZeroByteTransfer = _last == Just 0 } {- | -Converts packet streams of arbitrary data width @dwIn@ to packet streams of -a smaller (or equal) data width @dwOut@, where @dwOut@ must divide @dwIn@. -When @dwIn ~ dwOut@, this component is just the identity circuit, `idC`. +Converts packet streams of a data width which is a multiple of @n@, i.e. +@dwOut * n@, to packet streams of a smaller (or equal) data width @dwOut@, +where @n > 0@. +When @n ~ 1@, this component is just the identity circuit, `idC`. If '_abort' is asserted on an input transfer, it will be asserted on all corresponding output sub-transfers as well. All zero-byte transfers are preserved. Has one clock cycle of latency, all M2S outputs are registered. -Throughput is optimal, a transfer of @n@ valid bytes is transmitted in @n@ -clock cycles. To be precise, throughput is at least @(dwIn / dwOut)%@, so at -least @50%@ if @dwIn = 4@ and @dwOut = 2@ for example. We specify /at least/, +Throughput is optimal, a transfer of @k@ valid bytes is transmitted in @k@ +clock cycles. To be precise, throughput is at least @(1 / n)%@, so at +least @50%@ if @n = 2@ for example. We specify /at least/, because the throughput may be on the last transfer of a packet, when not all bytes have to be valid. If there is only one valid byte in the last transfer, then the throughput will always be @100%@ for that particular transfer. -} downConverterC :: - forall (dwIn :: Nat) (dwOut :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). + forall (dwOut :: Nat) (n :: Nat) (meta :: Type) (dom :: Domain). (HiddenClockResetEnable dom) => - (1 <= dwIn) => + (KnownNat dwOut) => + (KnownNat n) => (1 <= dwOut) => (1 <= n) => (NFDataX meta) => - (KnownNat dwIn) => - (KnownNat dwOut) => - (dwIn ~ dwOut * n) => -- | Downconverter circuit - Circuit (PacketStream dom dwIn meta) (PacketStream dom dwOut meta) -downConverterC = case sameNat (SNat @dwIn) (SNat @dwOut) of + Circuit (PacketStream dom (dwOut * n) meta) (PacketStream dom dwOut meta) +downConverterC = case sameNat d1 (SNat @n) of Just Refl -> idC _ -> forceResetSanity |> fromSignals (mealyB downConverterT s0) where diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs index deda660e..88e127c9 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Converters.hs @@ -20,50 +20,46 @@ import Protocols.PacketStream.Converters import Protocols.PacketStream.Hedgehog generateUpConverterProperty :: - forall (dwIn :: Nat) (dwOut :: Nat) (n :: Nat). + forall (dwIn :: Nat) (n :: Nat). (1 <= dwIn) => - (1 <= dwOut) => (1 <= n) => - (KnownNat n) => - (dwOut ~ n * dwIn) => + (1 <= dwIn * n) => SNat dwIn -> - SNat dwOut -> + SNat n -> Property generateUpConverterProperty SNat SNat = idWithModelSingleDomain defExpectOptions (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 20))) (exposeClockResetEnable (upConvert . downConvert)) - (exposeClockResetEnable @System (upConverterC @dwIn @dwOut @Int)) + (exposeClockResetEnable @System (upConverterC @dwIn @n @Int)) generateDownConverterProperty :: - forall (dwIn :: Nat) (dwOut :: Nat) (n :: Nat). - (1 <= dwIn) => + forall (dwOut :: Nat) (n :: Nat). (1 <= dwOut) => (1 <= n) => - (KnownNat n) => - (dwIn ~ n * dwOut) => - SNat dwIn -> + (1 <= dwOut * n) => SNat dwOut -> + SNat n -> Property generateDownConverterProperty SNat SNat = idWithModelSingleDomain defExpectOptions{eoSampleMax = 1000} (genPackets 1 8 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 10))) (exposeClockResetEnable (upConvert . downConvert)) - (exposeClockResetEnable @System (downConverterC @dwIn @dwOut @Int)) + (exposeClockResetEnable @System (downConverterC @dwOut @n @Int)) prop_upConverter3to9 :: Property -prop_upConverter3to9 = generateUpConverterProperty d3 d9 +prop_upConverter3to9 = generateUpConverterProperty d3 d3 prop_upConverter4to8 :: Property -prop_upConverter4to8 = generateUpConverterProperty d4 d8 +prop_upConverter4to8 = generateUpConverterProperty d4 d2 prop_upConverter3to6 :: Property -prop_upConverter3to6 = generateUpConverterProperty d3 d6 +prop_upConverter3to6 = generateUpConverterProperty d3 d2 prop_upConverter2to4 :: Property -prop_upConverter2to4 = generateUpConverterProperty d2 d4 +prop_upConverter2to4 = generateUpConverterProperty d2 d2 prop_upConverter1to4 :: Property prop_upConverter1to4 = generateUpConverterProperty d1 d4 @@ -75,22 +71,22 @@ prop_upConverter1to1 :: Property prop_upConverter1to1 = generateUpConverterProperty d1 d1 prop_downConverter9to3 :: Property -prop_downConverter9to3 = generateDownConverterProperty d9 d3 +prop_downConverter9to3 = generateDownConverterProperty d3 d3 prop_downConverter8to4 :: Property -prop_downConverter8to4 = generateDownConverterProperty d8 d4 +prop_downConverter8to4 = generateDownConverterProperty d4 d2 prop_downConverter6to3 :: Property -prop_downConverter6to3 = generateDownConverterProperty d6 d3 +prop_downConverter6to3 = generateDownConverterProperty d3 d2 prop_downConverter4to2 :: Property -prop_downConverter4to2 = generateDownConverterProperty d4 d2 +prop_downConverter4to2 = generateDownConverterProperty d2 d2 prop_downConverter4to1 :: Property -prop_downConverter4to1 = generateDownConverterProperty d4 d1 +prop_downConverter4to1 = generateDownConverterProperty d1 d4 prop_downConverter2to1 :: Property -prop_downConverter2to1 = generateDownConverterProperty d2 d1 +prop_downConverter2to1 = generateDownConverterProperty d1 d2 prop_downConverter1to1 :: Property prop_downConverter1to1 = generateDownConverterProperty d1 d1 From c70f77d33b0be9a658df60b0ee1f38a297051ab8 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 13 Dec 2024 13:58:21 +0100 Subject: [PATCH 60/63] Base: small documentation improvements --- .../src/Protocols/PacketStream/Base.hs | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/clash-protocols/src/Protocols/PacketStream/Base.hs b/clash-protocols/src/Protocols/PacketStream/Base.hs index 89fd7d7d..c5c31016 100644 --- a/clash-protocols/src/Protocols/PacketStream/Base.hs +++ b/clash-protocols/src/Protocols/PacketStream/Base.hs @@ -27,17 +27,17 @@ module Protocols.PacketStream.Base ( -- * Basic operations on the PacketStream protocol empty, consume, - void, - fanout, forceResetSanity, zeroOutInvalidBytesC, stripTrailingEmptyC, unsafeAbortOnBackpressureC, - -- * Skid buffers - registerBoth, + -- * Components imported from DfConv + void, + fanout, registerBwd, registerFwd, + registerBoth, -- * Operations on metadata fstMeta, @@ -58,9 +58,12 @@ module Protocols.PacketStream.Base ( eitherMetaS, ) where -import Clash.Prelude hiding (empty, sample) import qualified Prelude as P +import Control.DeepSeq (NFData) + +import Clash.Prelude hiding (empty, sample) + import qualified Data.Bifunctor as B import Data.Coerce (coerce) import qualified Data.Maybe as Maybe @@ -72,8 +75,6 @@ import qualified Protocols.DfConv as DfConv import Protocols.Hedgehog (Test (..)) import Protocols.Idle -import Control.DeepSeq (NFData) - {- | Data sent from manager to subordinate. @@ -526,7 +527,10 @@ mapMeta :: Circuit (PacketStream dom dataWidth metaIn) (PacketStream dom dataWidth metaOut) mapMeta f = mapMetaS (pure f) --- | Like 'mapMeta', but can reason over signals. +{- | +Like 'mapMeta' but can reason over signals, +this circuit combinator is akin to `Clash.HaskellPrelude.<*>`. +-} mapMetaS :: -- | Function to apply on the metadata, wrapped in a @Signal@ Signal dom (metaIn -> metaOut) -> @@ -542,7 +546,10 @@ filterMeta :: Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) filterMeta p = filterMetaS (pure p) --- | Like 'filterMeta', but can reason over signals. +{- | +Like 'filterMeta' but can reason over signals, +this circuit combinator is akin to `Clash.HaskellPrelude.<*>`. +-} filterMetaS :: -- | Predicate which specifies whether to keep a fragment based on its metadata, -- wrapped in a @Signal@ @@ -564,7 +571,10 @@ eitherMeta :: (PacketStream dom dataWidth c) eitherMeta f g = eitherMetaS (pure f) (pure g) --- | Like 'eitherMeta', but can reason over signals. +{- | +Like 'eitherMeta' but can reason over signals, +this circuit combinator is akin to `Clash.HaskellPrelude.<*>`. +-} eitherMetaS :: Signal dom (a -> c) -> Signal dom (b -> c) -> @@ -583,7 +593,10 @@ bimapMeta :: (PacketStream dom dataWidth (p b d)) bimapMeta f g = bimapMetaS (pure f) (pure g) --- | Like 'bimapMeta', but can reason over signals. +{- | +Like 'bimapMeta' but can reason over signals, +this circuit combinator is akin to `Clash.HaskellPrelude.<*>`. +-} bimapMetaS :: (B.Bifunctor p) => Signal dom (a -> b) -> @@ -602,7 +615,10 @@ firstMeta :: (PacketStream dom dataWidth (p b c)) firstMeta f = firstMetaS (pure f) --- | Like 'firstMeta', but can reason over signals. +{- | +Like 'firstMeta' but can reason over signals, +this circuit combinator is akin to `Clash.HaskellPrelude.<*>`. +-} firstMetaS :: (B.Bifunctor p) => Signal dom (a -> b) -> @@ -620,7 +636,10 @@ secondMeta :: (PacketStream dom dataWidth (p a c)) secondMeta f = secondMetaS (pure f) --- | Like 'secondMeta', but can reason over signals. +{- | +Like 'secondMeta' but can reason over signals, +this circuit combinator is akin to `Clash.HaskellPrelude.<*>`. +-} secondMetaS :: (B.Bifunctor p) => Signal dom (b -> c) -> From c94fc47e9ed270d480f0132a0093acac886e319b Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 13 Dec 2024 15:26:31 +0100 Subject: [PATCH 61/63] Packet arbiter improvements packetArbiterC now reuses Df.CollectMode. A NoSkip mode is added. --- clash-protocols/src/Protocols/Df.hs | 16 +- .../src/Protocols/PacketStream/Routing.hs | 66 +++---- .../Tests/Protocols/PacketStream/Routing.hs | 169 ++++++++++-------- 3 files changed, 141 insertions(+), 110 deletions(-) diff --git a/clash-protocols/src/Protocols/Df.hs b/clash-protocols/src/Protocols/Df.hs index 29eddc56..73c14079 100644 --- a/clash-protocols/src/Protocols/Df.hs +++ b/clash-protocols/src/Protocols/Df.hs @@ -804,16 +804,18 @@ roundrobin = in (i1, (Ack ack, datOut1)) --- | Collect mode in 'roundrobinCollect' +-- | Collect modes for dataflow arbiters. data CollectMode - = -- | Collect in a /roundrobin/ fashion. If a component does not produce - -- data, wait until it does. + = -- | Collect in a /round-robin/ fashion. If a source does not produce + -- data, wait until it does. Use with care, as there is a risk of + -- starvation if a selected source is idle for a long time. NoSkip - | -- | Collect in a /roundrobin/ fashion. If a component does not produce - -- data, skip it and check the next component on the next cycle. + | -- | Collect in a /round-robin/ fashion. If a source does not produce + -- data, skip it and check the next source on the next cycle. Skip - | -- | Check all components in parallel. Biased towards the /last/ Df - -- channel. + | -- | Check all sources in parallel. Biased towards the /last/ source. + -- If the number of sources is high, this is more expensive than other + -- modes. Parallel {- | Opposite of 'roundrobin'. Useful to collect data from workers that only diff --git a/clash-protocols/src/Protocols/PacketStream/Routing.hs b/clash-protocols/src/Protocols/PacketStream/Routing.hs index c0c597fa..1b6f7280 100644 --- a/clash-protocols/src/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/src/Protocols/PacketStream/Routing.hs @@ -10,7 +10,6 @@ Provides a packet arbiter and dispatcher, for merging and splitting packet strea -} module Protocols.PacketStream.Routing ( packetArbiterC, - ArbiterMode (..), packetDispatcherC, routeBy, ) where @@ -18,42 +17,46 @@ module Protocols.PacketStream.Routing ( import Clash.Prelude import Protocols +import qualified Protocols.Df as Df import Protocols.PacketStream.Base import qualified Data.Bifunctor as B import Data.Maybe --- | Collect mode for `packetArbiterC` -data ArbiterMode - = -- | Collect in a round-robin fashion. Fair and cheaper than `Parallel`. - RoundRobin - | -- | Check components in parallel. This mode has a higher throughput, but is - -- biased towards the last source and also slightly more expensive. - Parallel - -- | Merges multiple packet streams into one, respecting packet boundaries. packetArbiterC :: - forall dom p dataWidth meta. + forall dataWidth sources meta dom. (HiddenClockResetEnable dom) => - (KnownNat p) => - (1 <= p) => - -- | See `ArbiterMode` - ArbiterMode -> - Circuit (Vec p (PacketStream dom dataWidth meta)) (PacketStream dom dataWidth meta) -packetArbiterC mode = Circuit (B.first unbundle . mealyB go (maxBound, True) . B.first bundle) + (KnownNat sources) => + (1 <= sources) => + -- | Determines the mode of arbitration. See `Df.CollectMode` + Df.CollectMode -> + Circuit + (Vec sources (PacketStream dom dataWidth meta)) + (PacketStream dom dataWidth meta) +packetArbiterC mode = + Circuit (B.first unbundle . mealyB go (maxBound, True) . B.first bundle) where go (i, first) (fwds, bwd@(PacketStreamS2M ack)) = ((i', continue), (bwds, fwd)) where bwds = replace i bwd (repeat (PacketStreamS2M False)) fwd = fwds !! i - continue = case fwd of - Nothing -> first -- only switch sources if this is not somewhere inside a packet - Just (PacketStreamM2S _ (Just _) _ _) -> ack -- switch source once last packet is acknowledged - _ -> False + + -- We may only switch sources if we are not currently in the middle + -- of forwarding a packet. + continue = case (fwd, mode) of + (Nothing, Df.NoSkip) -> False + (Nothing, _) -> first + (Just transferIn, _) -> isJust (_last transferIn) && ack + i' = case (mode, continue) of (_, False) -> i - (RoundRobin, _) -> satSucc SatWrap i -- next index - (Parallel, _) -> fromMaybe maxBound $ fold @(p - 1) (<|>) (zipWith (<$) indicesI fwds) -- index of last sink with data + (Df.NoSkip, _) -> satSucc SatWrap i + (Df.Skip, _) -> satSucc SatWrap i + (Df.Parallel, _) -> + -- Index of last sink with data + fromMaybe maxBound + $ fold @(sources - 1) (<|>) (zipWith (<$) indicesI fwds) {- | Routes packets depending on their metadata, using given routing functions. @@ -61,19 +64,22 @@ Routes packets depending on their metadata, using given routing functions. Data is sent to at most one sink, for which the dispatch function evaluates to @True@ when applied to the input metadata. If none of the predicates hold, the input packet is dropped. If more than one of the predicates hold, the sink -that occcurs first in the vector is picked. +that occurs first in the vector is picked. Sends out packets in the same clock cycle as they are received, this component has zero latency and runs at full throughput. -} packetDispatcherC :: - forall (dom :: Domain) (p :: Nat) (dataWidth :: Nat) (meta :: Type). + forall dataWidth sinks meta dom. (HiddenClockResetEnable dom) => - (KnownNat p) => + (KnownNat sinks) => -- | Dispatch function - Vec p (meta -> Bool) -> - Circuit (PacketStream dom dataWidth meta) (Vec p (PacketStream dom dataWidth meta)) -packetDispatcherC predicates = Circuit (B.second unbundle . unbundle . fmap go . bundle . B.second bundle) + Vec sinks (meta -> Bool) -> + Circuit + (PacketStream dom dataWidth meta) + (Vec sinks (PacketStream dom dataWidth meta)) +packetDispatcherC predicates = + Circuit (B.second unbundle . unbundle . fmap go . bundle . B.second bundle) where idleOtp = repeat Nothing go (Nothing, _) = (PacketStreamS2M False, idleOtp) @@ -88,6 +94,6 @@ an `Eq` instance. Useful to route according to a record field. routeBy :: (Eq a) => (meta -> a) -> - Vec p a -> - Vec p (meta -> Bool) + Vec sinks a -> + Vec sinks (meta -> Bool) routeBy f = map $ \x -> (== x) . f diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs index 1e962ef9..1ddb7521 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Routing.hs @@ -1,11 +1,11 @@ {-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE NoImplicitPrelude #-} module Tests.Protocols.PacketStream.Routing ( tests, ) where import Clash.Prelude -import qualified Clash.Prelude as C import Hedgehog hiding (Parallel) import qualified Hedgehog.Gen as Gen @@ -16,6 +16,7 @@ import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) import Test.Tasty.Hedgehog.Extra (testProperty) import Test.Tasty.TH (testGroupGenerator) +import qualified Protocols.Df as Df import Protocols.Hedgehog import Protocols.PacketStream.Base import Protocols.PacketStream.Hedgehog @@ -23,38 +24,20 @@ import Protocols.PacketStream.Routing import qualified Data.List as L --- | Tests the round-robin packet arbiter with one source; essentially an id test -prop_packetarbiter_roundrobin_id :: Property -prop_packetarbiter_roundrobin_id = makePropPacketArbiter d1 d2 RoundRobin - --- | Tests the parallel packet arbiter with one source; essentially an id test -prop_packetarbiter_parallel_id :: Property -prop_packetarbiter_parallel_id = makePropPacketArbiter d1 d2 Parallel - --- Tests the round-robin arbiter with five sources -prop_packetarbiter_roundrobin :: Property -prop_packetarbiter_roundrobin = makePropPacketArbiter d5 d2 RoundRobin - --- Tests the parallel arbiter with five sources -prop_packetarbiter_parallel :: Property -prop_packetarbiter_parallel = makePropPacketArbiter d5 d2 Parallel - -{- | Tests a packet arbiter for any data width and number of sources. In particular, +{- | +Tests a packet arbiter for any data width and number of sources. In particular, tests that packets from all sources are sent out unmodified in the same order they were in in the source streams. -} makePropPacketArbiter :: - forall p n. - ( KnownNat p - , 1 <= p - , KnownNat n - , 1 <= n - ) => - SNat p -> - SNat n -> - ArbiterMode -> + forall sources dataWidth. + (1 <= sources) => + (1 <= dataWidth) => + SNat sources -> + SNat dataWidth -> + Df.CollectMode -> Property -makePropPacketArbiter _ _ mode = +makePropPacketArbiter SNat SNat mode = propWithModelSingleDomain @System defExpectOptions{eoSampleMax = 1000} @@ -63,32 +46,104 @@ makePropPacketArbiter _ _ mode = (exposeClockResetEnable (packetArbiterC mode)) (\xs ys -> partitionPackets xs === partitionPackets ys) where - genSources = mapM setMeta (indicesI @p) + (minPackets, maxPackets) = case mode of + -- NoSkip mode needs the same amount of packets generated for each + -- source. Otherwise, starvation happens and the test won't end. + Df.NoSkip -> (5, 5) + _ -> (1, 10) + genSources = mapM setMeta (indicesI @sources) setMeta j = do pkts <- - genPackets @n 1 10 (genValidPacket defPacketOptions (pure ()) (Range.linear 0 10)) + genPackets + @dataWidth + minPackets + maxPackets + (genValidPacket defPacketOptions (pure ()) (Range.linear 0 10)) pure $ L.map (\pkt -> pkt{_meta = j}) pkts partitionPackets packets = - L.sortOn (_meta . L.head . L.head) $ - L.groupBy (\a b -> _meta a == _meta b) <$> chunkByPacket packets + L.sortOn (_meta . L.head . L.head) + $ L.groupBy (\a b -> _meta a == _meta b) + <$> chunkByPacket packets + +{- | +Generic test function for the packet dispatcher, testing for all data widths, +dispatch functions, and some meta types. +-} +makePropPacketDispatcher :: + forall sinks dataWidth meta. + (KnownNat sinks) => + (1 <= sinks) => + (1 <= dataWidth) => + (TestType meta) => + (Bounded meta) => + (Enum meta) => + (BitPack meta) => + SNat dataWidth -> + -- | Dispatch function + Vec sinks (meta -> Bool) -> + Property +makePropPacketDispatcher SNat fs = + idWithModelSingleDomain @System + defExpectOptions{eoSampleMax = 2000, eoStopAfterEmpty = 1000} + (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 6))) + (exposeClockResetEnable (model 0)) + (exposeClockResetEnable (packetDispatcherC fs)) + where + model :: + Index sinks -> + [PacketStreamM2S dataWidth meta] -> + Vec sinks [PacketStreamM2S dataWidth meta] + model _ [] = pure [] + model i (y : ys) + | (fs !! i) (_meta y) = + let next = model 0 ys + in replace i (y : (next !! i)) next + | i < maxBound = model (i + 1) (y : ys) + | otherwise = model 0 ys + +-- | Tests the @NoSkip@ packet arbiter with one source; essentially an id test. +prop_packet_arbiter_noskip_id :: Property +prop_packet_arbiter_noskip_id = makePropPacketArbiter d1 d2 Df.NoSkip -{- | Tests that the packet dispatcher works correctly with one sink that accepts +-- | Tests the @Skip@ packet arbiter with one source; essentially an id test. +prop_packet_arbiter_skip_id :: Property +prop_packet_arbiter_skip_id = makePropPacketArbiter d1 d2 Df.Skip + +-- | Tests the @Parallel@ packet arbiter with one source; essentially an id test. +prop_packet_arbiter_parallel_id :: Property +prop_packet_arbiter_parallel_id = makePropPacketArbiter d1 d2 Df.Parallel + +-- | Tests the @NoSkip@ arbiter with five sources. +prop_packet_arbiter_noskip :: Property +prop_packet_arbiter_noskip = makePropPacketArbiter d5 d2 Df.NoSkip + +-- | Tests the @Skip@ arbiter with five sources. +prop_packet_arbiter_skip :: Property +prop_packet_arbiter_skip = makePropPacketArbiter d5 d2 Df.Skip + +-- | Tests the @Parallel@ arbiter with five sources. +prop_packet_arbiter_parallel :: Property +prop_packet_arbiter_parallel = makePropPacketArbiter d5 d2 Df.Parallel + +{- | +Tests that the packet dispatcher works correctly with one sink that accepts all packets; essentially an id test. -} -prop_packetdispatcher_id :: Property -prop_packetdispatcher_id = +prop_packet_dispatcher_id :: Property +prop_packet_dispatcher_id = makePropPacketDispatcher d4 ((const True :: Int -> Bool) :> Nil) -{- | Tests the packet dispatcher for a data width of four bytes and three +{- | +Tests the packet dispatcher for a data width of four bytes and three overlapping but incomplete dispatch functions, effectively testing whether the circuit sends input to the first allowed output channel and drops input if there are none. -} -prop_packetdispatcher :: Property -prop_packetdispatcher = makePropPacketDispatcher d4 fs +prop_packet_dispatcher :: Property +prop_packet_dispatcher = makePropPacketDispatcher d4 fs where fs :: Vec 3 (Index 4 -> Bool) fs = @@ -97,41 +152,9 @@ prop_packetdispatcher = makePropPacketDispatcher d4 fs :> (>= 1) :> Nil -{- | Generic test function for the packet dispatcher, testing for all data widths, -dispatch functions, and some meta types --} -makePropPacketDispatcher :: - forall (p :: Nat) (dataWidth :: Nat) (a :: Type). - ( KnownNat p - , 1 <= p - , KnownNat dataWidth - , 1 <= dataWidth - , TestType a - , Bounded a - , Enum a - , BitPack a - ) => - SNat dataWidth -> - Vec p (a -> Bool) -> - Property -makePropPacketDispatcher _ fs = - idWithModelSingleDomain @System - defExpectOptions{eoSampleMax = 2000, eoStopAfterEmpty = 1000} - (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 0 6))) - (exposeClockResetEnable (model 0)) - (exposeClockResetEnable (packetDispatcherC fs)) - where - model :: - Index p -> [PacketStreamM2S dataWidth a] -> Vec p [PacketStreamM2S dataWidth a] - model _ [] = pure [] - model i (y : ys) - | (fs C.!! i) (_meta y) = let next = model 0 ys in replace i (y : (next C.!! i)) next - | i < maxBound = model (i + 1) (y : ys) - | otherwise = model 0 ys - tests :: TestTree tests = - localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ - localOption - (HedgehogTestLimit (Just 100)) + localOption (mkTimeout 20_000_000 {- 20 seconds -}) + $ localOption + (HedgehogTestLimit (Just 500)) $(testGroupGenerator) From f09558d3ebff9486562334d0490c8791c73ce63b Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 13 Dec 2024 15:35:09 +0100 Subject: [PATCH 62/63] Fix spelling mistakes --- clash-protocols/src/Data/Maybe/Extra.hs | 2 +- .../src/Protocols/PacketStream/Depacketizers.hs | 8 ++++---- clash-protocols/src/Protocols/PacketStream/Hedgehog.hs | 8 ++++---- clash-protocols/src/Protocols/PacketStream/PacketFifo.hs | 7 ++++--- .../tests/Tests/Protocols/PacketStream/PacketFifo.hs | 7 +++++-- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/clash-protocols/src/Data/Maybe/Extra.hs b/clash-protocols/src/Data/Maybe/Extra.hs index 84847c51..dd22dc40 100644 --- a/clash-protocols/src/Data/Maybe/Extra.hs +++ b/clash-protocols/src/Data/Maybe/Extra.hs @@ -2,7 +2,7 @@ module Data.Maybe.Extra ( toMaybe, ) where --- | Wrap a value in a Just if True +-- | Wrap a value in a @Just@ if @True@ toMaybe :: Bool -> a -> Maybe a toMaybe True x = Just x toMaybe False _ = Nothing diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 72d0c468..3f04b6a7 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -38,12 +38,12 @@ import Data.Maybe type BufSize (headerBytes :: Nat) (dataWidth :: Nat) = dataWidth * headerBytes `DivRU` dataWidth -{- | Since the header might be unaligned compared to the datawidth +{- | Since the header might be unaligned compared to the data width we need to store a partial fragment when forwarding. The number of bytes we need to store depends on our "unalignedness". Ex. We parse a header of 17 bytes and our @dataWidth@ is 4 bytes. - That means at the end of the header we can have upto 3 bytes left + That means at the end of the header we can have up to 3 bytes left in the fragment which we may need to forward. -} type ForwardBytes (headerBytes :: Nat) (dataWidth :: Nat) = @@ -95,7 +95,7 @@ instance Default (DepacketizerState headerBytes dataWidth) where def :: DepacketizerState headerBytes dataWidth - def = Parse False (deepErrorX "depacketizerT: undefined intial buffer") maxBound + def = Parse False (deepErrorX "depacketizerT: undefined initial buffer") maxBound -- | Depacketizer state transition function. depacketizerT :: @@ -268,7 +268,7 @@ instance Default (DfDepacketizerState headerBytes dataWidth) where def :: DfDepacketizerState headerBytes dataWidth - def = DfParse False (deepErrorX "depacketizeToDfT: undefined intial buffer") maxBound + def = DfParse False (deepErrorX "depacketizeToDfT: undefined initial buffer") maxBound -- | Df depacketizer transition function. depacketizeToDfT :: diff --git a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs index d9236113..4e0549b7 100644 --- a/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs +++ b/clash-protocols/src/Protocols/PacketStream/Hedgehog.hs @@ -198,7 +198,7 @@ depacketizerModel :: [PacketStreamM2S dataWidth metaOut] depacketizerModel toMetaOut ps = L.concat dataWidthPackets where - hdrbytes = natToNum @headerBytes + hdrBytes = natToNum @headerBytes parseHdr :: ([PacketStreamM2S 1 metaIn], [PacketStreamM2S 1 metaIn]) -> @@ -223,7 +223,7 @@ depacketizerModel toMetaOut ps = L.concat dataWidthPackets L.filter ( \fs -> let len' = L.length fs - in len' > hdrbytes || len' == hdrbytes && _last (L.last fs) == Just 1 + in len' > hdrBytes || len' == hdrBytes && _last (L.last fs) == Just 1 ) $ downConvert . smearAbort @@ -232,7 +232,7 @@ depacketizerModel toMetaOut ps = L.concat dataWidthPackets parsedPackets :: [[PacketStreamM2S 1 metaOut]] parsedPackets = L.map go bytePackets - go = parseHdr . L.splitAt hdrbytes + go = parseHdr . L.splitAt hdrBytes dataWidthPackets :: [[PacketStreamM2S dataWidth metaOut]] dataWidthPackets = L.map upConvert parsedPackets @@ -414,7 +414,7 @@ genPackets minB maxB pktGen = L.concat <$> Gen.list (Range.linear minB maxB) pkt {- | Generate a valid packet, i.e. a packet of which all transfers carry the same -`_meta` and with all unenabled bytes in `_data` set to 0x00. +`_meta` and with all bytes in `_data` that are not enabled set to 0x00. -} genValidPacket :: forall (dataWidth :: Nat) (meta :: Type). diff --git a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs index 834ab475..bb6ba99a 100644 --- a/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/src/Protocols/PacketStream/PacketFifo.hs @@ -230,7 +230,7 @@ packetFifoImpl SNat SNat (fwdIn, bwdIn) = (PacketStreamS2M . not <$> fullBuffer, mReadAddr' = mux mReadEnable (mReadAddr + 1) mReadAddr mReadAddr = register 0 mReadAddr' - -- only read the next value if we've outpustted the last word of a packet + -- only read the next value if we've outputted the last word of a packet mReadEnable = lastWordOut .&&. readEnable -- Registers : status @@ -238,7 +238,8 @@ packetFifoImpl SNat SNat (fwdIn, bwdIn) = (PacketStreamS2M . not <$> fullBuffer, -- start dropping packet on abort dropping' = abortIn .||. dropping dropping = register False $ dropping' .&&. (not <$> lastWordIn) - -- the buffer is empty if the metaBuffer is empty as the metabuffer only updates when a packet is complete + -- the buffer is empty if the metaBuffer is empty as the meta buffer + -- only updates when a packet is complete emptyBuffer = register 0 mWriteAddr .==. mReadAddr -- Only write if there is space and we're not dropping @@ -257,7 +258,7 @@ packetFifoImpl SNat SNat (fwdIn, bwdIn) = (PacketStreamS2M . not <$> fullBuffer, {- | FIFO circuit optimized for the PacketStream protocol. Contains two FIFOs, one for packet data ('_data', '_last') and one for packet metadata ('_meta'). -Because metadata is constant per packet, the metadata FIFO can be signficantly +Because metadata is constant per packet, the metadata FIFO can be significantly shallower, saving resources. Moreover, the output of the FIFO has some other properties: diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs index 2df527f2..fbd0438f 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/PacketFifo.hs @@ -50,7 +50,7 @@ prop_packet_fifo_id = (exposeClockResetEnable (packetFifoC @_ @1 @Int16 d10 d10 Backpressure)) {- | -Ensure that backpressure becayse of a full content RAM and dropping of packets +Ensure that backpressure because of a full content RAM and dropping of packets that are too big to fit in the FIFO is tested. -} prop_packet_fifo_small_buffer_id :: Property @@ -62,7 +62,10 @@ prop_packet_fifo_small_buffer_id = (exposeClockResetEnable (dropBigPackets d3 . dropAbortedPackets)) (exposeClockResetEnable (packetFifoC @_ @1 @Int16 d3 d5 Backpressure)) --- | test for id using a small metabuffer to ensure backpressure using the metabuffer is tested +{- | +Test for id using a small meta buffer to ensure backpressure using +the meta buffer is tested. +-} prop_packet_fifo_small_meta_buffer_id :: Property prop_packet_fifo_small_meta_buffer_id = idWithModelSingleDomain From b3bd8f3e2212e70e864ec862a858c8a91ac17235 Mon Sep 17 00:00:00 2001 From: t-wallet Date: Fri, 13 Dec 2024 16:38:39 +0100 Subject: [PATCH 63/63] Temporarily remove dropTailC and delayStreamC A new PR will be opened for these. --- clash-protocols/clash-protocols.cabal | 2 - clash-protocols/src/Protocols/PacketStream.hs | 4 - .../src/Protocols/PacketStream/Delay.hs | 155 ------------------ .../Protocols/PacketStream/Depacketizers.hs | 136 --------------- .../tests/Tests/Protocols/PacketStream.hs | 2 - .../Tests/Protocols/PacketStream/Delay.hs | 36 ---- .../Protocols/PacketStream/Depacketizers.hs | 79 +-------- .../Protocols/PacketStream/Packetizers.hs | 16 +- 8 files changed, 7 insertions(+), 423 deletions(-) delete mode 100644 clash-protocols/src/Protocols/PacketStream/Delay.hs delete mode 100644 clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs diff --git a/clash-protocols/clash-protocols.cabal b/clash-protocols/clash-protocols.cabal index 8d83070e..6143620d 100644 --- a/clash-protocols/clash-protocols.cabal +++ b/clash-protocols/clash-protocols.cabal @@ -149,7 +149,6 @@ library Protocols.PacketStream.Base Protocols.PacketStream.AsyncFifo Protocols.PacketStream.Converters - Protocols.PacketStream.Delay Protocols.PacketStream.Depacketizers Protocols.PacketStream.Hedgehog Protocols.PacketStream.PacketFifo @@ -207,7 +206,6 @@ test-suite unittests Tests.Protocols.PacketStream.AsyncFifo Tests.Protocols.PacketStream.Base Tests.Protocols.PacketStream.Converters - Tests.Protocols.PacketStream.Delay Tests.Protocols.PacketStream.Depacketizers Tests.Protocols.PacketStream.Packetizers Tests.Protocols.PacketStream.PacketFifo diff --git a/clash-protocols/src/Protocols/PacketStream.hs b/clash-protocols/src/Protocols/PacketStream.hs index a14ccf6f..45e63999 100644 --- a/clash-protocols/src/Protocols/PacketStream.hs +++ b/clash-protocols/src/Protocols/PacketStream.hs @@ -24,9 +24,6 @@ module Protocols.PacketStream ( -- * Converters module Protocols.PacketStream.Converters, - -- * Delay components - module Protocols.PacketStream.Delay, - -- * Depacketizers module Protocols.PacketStream.Depacketizers, @@ -44,7 +41,6 @@ where import Protocols.PacketStream.AsyncFifo import Protocols.PacketStream.Base import Protocols.PacketStream.Converters -import Protocols.PacketStream.Delay import Protocols.PacketStream.Depacketizers import Protocols.PacketStream.PacketFifo import Protocols.PacketStream.Packetizers diff --git a/clash-protocols/src/Protocols/PacketStream/Delay.hs b/clash-protocols/src/Protocols/PacketStream/Delay.hs deleted file mode 100644 index 0bbc491a..00000000 --- a/clash-protocols/src/Protocols/PacketStream/Delay.hs +++ /dev/null @@ -1,155 +0,0 @@ -{-# LANGUAGE NoImplicitPrelude #-} -{-# OPTIONS_HADDOCK hide #-} - -{- | -Copyright : (C) 2024, QBayLogic B.V. -License : BSD2 (see the file LICENSE) -Maintainer : QBayLogic B.V. - -Provides a circuit that delays a stream by a configurable amount of transfers. --} -module Protocols.PacketStream.Delay ( - delayStreamC, -) where - -import Clash.Prelude - -import Protocols -import Protocols.PacketStream.Base - -import Data.Constraint.Deferrable ((:~:) (Refl)) -import Data.Maybe - --- | State of 'delayStreamT'. -data DelayState n = DelayState - { _size :: Index (n + 1) - -- ^ The number of valid transactions in the buffer. - , _readPtr :: Index n - -- ^ Current block RAM read address. - , _writePtr :: Index n - -- ^ Current block RAM write address. - , _flush :: Bool - -- ^ Iff true, transmit all remaining transactions of the current packet, - -- regardless of whether the buffer is full or not. - , _metaWriteEn :: Bool - -- ^ If true, write to the meta buffer. We need this in case of LHS stalls. - } - deriving (Generic, NFDataX, Show, ShowX) - --- | State transition function of 'delayStreamC'. -delayStreamT :: - forall - (n :: Nat) - (dataWidth :: Nat) - (meta :: Type). - (KnownNat n) => - (KnownNat dataWidth) => - (1 <= n) => - (1 <= dataWidth) => - (NFDataX meta) => - DelayState n -> - ( Maybe (PacketStreamM2S dataWidth meta) - , PacketStreamS2M - , PacketStreamM2S dataWidth () - , meta - ) -> - ( DelayState n - , ( PacketStreamS2M - , Maybe (PacketStreamM2S dataWidth meta) - , Index n - , Maybe (Index n, PacketStreamM2S dataWidth ()) - , Maybe meta - ) - ) -delayStreamT st (fwdIn, bwdIn, buff, metaBuf) = - (nextStOut, (bwdOut, fwdOut, _readPtr nextStOut, writeCmd, mWriteCmd)) - where - emptyBuf = _size st == 0 - fullBuf = _size st == maxBound - - readEn = case fwdIn of - Nothing -> not emptyBuf && _flush st - Just _ -> fullBuf || _flush st - - bwdOut = PacketStreamS2M (isNothing fwdIn || not readEn || _ready bwdIn) - - (readPtr', fwdOut) = - if readEn - then (satSucc SatWrap (_readPtr st), Just (buff{_meta = metaBuf})) - else (_readPtr st, Nothing) - - (writeCmd, mWriteCmd, writePtr') = case fwdIn of - Nothing -> (Nothing, Nothing, _writePtr st) - Just inPkt -> - ( if readEn && not (_ready bwdIn) - then Nothing - else Just (_writePtr st, inPkt{_meta = ()}) - , if _metaWriteEn st || (emptyBuf || readEn && isJust (_last buff) && _ready bwdIn) - then Just (_meta inPkt) - else Nothing - , satSucc SatWrap (_writePtr st) - ) - - metaWriteEn' = isNothing fwdIn && (_metaWriteEn st || isJust (_last buff)) - - (size', flush') = case fwdIn of - Nothing -> (_size st - 1, isNothing (_last buff) && _flush st) - Just inPkt - | _flush st -> - (_size st, not (readEn && isJust (_last buff)) && _flush st) - | otherwise -> - (satSucc SatBound (_size st), isJust (_last inPkt)) - - nextSt = DelayState size' readPtr' writePtr' flush' metaWriteEn' - nextStOut = - if isJust fwdIn - && (isNothing fwdOut || _ready bwdIn) - || isNothing fwdIn - && (readEn && _ready bwdIn) - then nextSt - else st - -{- | -Forwards incoming packets with @n@ transactions latency. Because of potential -stalls this is not the same as @n@ clock cycles. Assumes that all packets -passing through this component are bigger than @n@ transactions. If not, this -component has __UNDEFINED BEHAVIOUR__ and things will break. --} -delayStreamC :: - forall - (dataWidth :: Nat) - (meta :: Type) - (dom :: Domain) - (n :: Nat). - (HiddenClockResetEnable dom) => - (KnownNat dataWidth) => - (1 <= n) => - (1 <= dataWidth) => - (NFDataX meta) => - -- | The number of transactions to delay - SNat n -> - Circuit (PacketStream dom dataWidth meta) (PacketStream dom dataWidth meta) -delayStreamC SNat = forceResetSanity |> fromSignals ckt - where - ckt (fwdInS, bwdInS) = (bwdOutS, fwdOutS) - where - noRstFunc = deepErrorX "delayStream: undefined reset function" - - -- Store the contents of transactions without metadata. - -- We only need write before read semantics in case n ~ 1. - bram :: Signal dom (PacketStreamM2S dataWidth ()) - bram = case sameNat d1 (SNat @n) of - Nothing -> blockRamU NoClearOnReset (SNat @n) noRstFunc readAddr writeCmd - Just Refl -> readNew (blockRamU NoClearOnReset (SNat @n) noRstFunc) readAddr writeCmd - - -- There are at most two packets in the blockram, but they are required - -- to be bigger than @n@ transactions. Thus, we only need to store the - -- metadata once. - metaBuffer :: Signal dom meta - metaBuffer = regMaybe (deepErrorX "delayStream: undefined initial meta") mWriteCmd - - (bwdOutS, fwdOutS, readAddr, writeCmd, mWriteCmd) = - unbundle - $ mealy delayStreamT (DelayState @n 0 0 0 False True) input - - input = bundle (fwdInS, bwdInS, bram, metaBuffer) diff --git a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs index 3f04b6a7..735a6920 100644 --- a/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/src/Protocols/PacketStream/Depacketizers.hs @@ -15,7 +15,6 @@ Utility circuits for reading from a packet stream. module Protocols.PacketStream.Depacketizers ( depacketizerC, depacketizeToDfC, - dropTailC, ) where import Clash.Prelude @@ -24,9 +23,7 @@ import Clash.Sized.Vector.Extra import Protocols import qualified Protocols.Df as Df import Protocols.PacketStream.Base -import Protocols.PacketStream.Delay -import qualified Data.Bifunctor as B import Data.Constraint (Dict (Dict)) import Data.Constraint.Nat.Extra import Data.Data ((:~:) (Refl)) @@ -360,136 +357,3 @@ depacketizeToDfC toOut = forceResetSanity |> fromSignals ckt where ckt = case leTimesDivRu @dataWidth @headerBytes of Dict -> mealyB (depacketizeToDfT toOut) def - --- | Information about the tail of a packet, for dropping purposes. -data DropTailInfo dataWidth delay = DropTailInfo - { _dtAborted :: Bool - -- ^ Whether any fragment of the packet was aborted - , _newIdx :: Index (dataWidth + 1) - -- ^ The adjusted byte enable - , _drops :: Index (delay + 1) - -- ^ The amount of transfers to drop from the tail - , _wait :: Bool - -- ^ Iff true, apply changes to transfer the next clock cycle instead - } - -{- | -Transmits information about a single packet upon seeing its last transfer. --} -transmitDropInfoC :: - forall (dataWidth :: Nat) (delay :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). - (KnownNat dataWidth) => - (KnownNat delay) => - (1 <= dataWidth) => - (1 <= n) => - (HiddenClockResetEnable dom) => - SNat n -> - Circuit - (PacketStream dom dataWidth meta) - (Df dom (DropTailInfo dataWidth delay)) -transmitDropInfoC SNat = forceResetSanity |> fromSignals (mealyB go False) - where - go aborted (Nothing, _) = (aborted, (PacketStreamS2M True, Df.NoData)) - go aborted (Just PacketStreamM2S{..}, Ack readyIn) = (nextAborted, (PacketStreamS2M readyIn, fwdOut)) - where - (nextAborted, fwdOut) = case _last of - Nothing -> (aborted || _abort, Df.NoData) - Just i -> (not readyIn && (aborted || _abort), Df.Data (toDropTailInfo i)) - - toDropTailInfo i = - DropTailInfo - { _dtAborted = aborted || _abort - , _newIdx = newIdx - , _drops = drops - , _wait = wait - } - where - (newIdx, drops, wait) = case ( compareSNat (SNat @dataWidth) (SNat @n) - , sameNat d0 (SNat @(n `Mod` dataWidth)) - ) of - (SNatLE, Nothing) -> - let smaller = (resize i :: Index n) <= natToNum @(n - dataWidth) - in ( satSub SatWrap i (natToNum @(n `Mod` dataWidth) + (if smaller then 1 else 0)) - , if smaller - then natToNum @(n `DivRU` dataWidth) - else natToNum @(n `Div` dataWidth) - , not smaller - ) - (SNatLE, Just Refl) -> - (i, natToNum @(n `Div` dataWidth), False) - (SNatGT, _) -> - if i > natToNum @n - then (i - natToNum @n, 0, True) - else (maxBound - (natToNum @n - i), 1, False) - -{- | -Gets a delayed packet stream as input together with non-delayed -'DropTailInfo', so that dropping can be done while correctly preserving -'_abort' and adjusting '_last'. --} -dropTailC' :: - forall (dataWidth :: Nat) (delay :: Nat) (meta :: Type) (dom :: Domain). - (KnownDomain dom) => - (KnownNat dataWidth) => - (KnownNat delay) => - (HiddenClockResetEnable dom) => - Circuit - (PacketStream dom dataWidth meta, Df dom (DropTailInfo dataWidth delay)) - (PacketStream dom dataWidth meta) -dropTailC' = fromSignals (B.first unbundle . mealyB go (0, Nothing) . B.first bundle) - where - go (0, cache) ((fwdIn, infoIn), bwdIn) = (nextStOut, ((bwdOut1, bwdOut2), fwdOut)) - where - (bwdOut1, bwdOut2) - | isNothing fwdOut = (PacketStreamS2M True, Ack True) - | otherwise = (bwdIn, Ack (_ready bwdIn)) - - (nextSt, fwdOut) = case (fwdIn, infoIn) of - (Nothing, _) -> - ((0, cache), Nothing) - (Just pkt, Df.NoData) -> - case cache of - Nothing -> - ((0, Nothing), Just pkt) - Just (aborted, newIdx, delayy) -> - ((delayy, Nothing), Just pkt{_abort = aborted, _last = Just newIdx}) - (Just pkt, Df.Data inf) -> - if _wait inf - then ((0, Just (_dtAborted inf, _newIdx inf, _drops inf)), Just pkt) - else - ( (_drops inf, Nothing) - , Just pkt{_last = Just (_newIdx inf), _abort = _dtAborted inf} - ) - - nextStOut = if isNothing fwdOut || _ready bwdIn then nextSt else (0, cache) - go (cycles, cache) _ = ((cycles - 1, cache), ((PacketStreamS2M True, Ack True), Nothing)) - -{- | -Removes the last @n@ bytes from each packet in the packet stream. -If any dropped transfers had '_abort' set, this will be preserved by -setting the '_abort' of an earlier transfer that is not dropped. - -__NB__: assumes that packets are longer than @ceil(n / dataWidth)@ transfers. -If this is not the case, the behaviour of this component is undefined. --} -dropTailC :: - forall (dataWidth :: Nat) (meta :: Type) (dom :: Domain) (n :: Nat). - (HiddenClockResetEnable dom) => - (KnownNat dataWidth) => - (1 <= dataWidth) => - (1 <= n) => - (NFDataX meta) => - SNat n -> - Circuit - (PacketStream dom dataWidth meta) - (PacketStream dom dataWidth meta) -dropTailC SNat = case strictlyPositiveDivRu @n @dataWidth of - Dict -> - forceResetSanity - |> circuit - ( \stream -> do - [s1, s2] <- fanout -< stream - delayed <- delayStreamC (SNat @(n `DivRU` dataWidth)) -< s1 - info <- transmitDropInfoC @dataWidth @(n `DivRU` dataWidth) (SNat @n) -< s2 - dropTailC' @dataWidth @(n `DivRU` dataWidth) -< (delayed, info) - ) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream.hs b/clash-protocols/tests/Tests/Protocols/PacketStream.hs index 57848d33..91ba833d 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream.hs @@ -5,7 +5,6 @@ import Test.Tasty import qualified Tests.Protocols.PacketStream.AsyncFifo import qualified Tests.Protocols.PacketStream.Base import qualified Tests.Protocols.PacketStream.Converters -import qualified Tests.Protocols.PacketStream.Delay import qualified Tests.Protocols.PacketStream.Depacketizers import qualified Tests.Protocols.PacketStream.PacketFifo import qualified Tests.Protocols.PacketStream.Packetizers @@ -19,7 +18,6 @@ tests = [ Tests.Protocols.PacketStream.AsyncFifo.tests , Tests.Protocols.PacketStream.Base.tests , Tests.Protocols.PacketStream.Converters.tests - , Tests.Protocols.PacketStream.Delay.tests , Tests.Protocols.PacketStream.Depacketizers.tests , Tests.Protocols.PacketStream.PacketFifo.tests , Tests.Protocols.PacketStream.Packetizers.tests diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs deleted file mode 100644 index 4f1f8a60..00000000 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Delay.hs +++ /dev/null @@ -1,36 +0,0 @@ -{-# LANGUAGE NumericUnderscores #-} - -module Tests.Protocols.PacketStream.Delay ( - tests, -) where - -import Clash.Prelude - -import Hedgehog -import qualified Hedgehog.Gen as Gen -import qualified Hedgehog.Range as Range - -import Test.Tasty -import Test.Tasty.Hedgehog (HedgehogTestLimit (HedgehogTestLimit)) -import Test.Tasty.Hedgehog.Extra (testProperty) -import Test.Tasty.TH (testGroupGenerator) - -import Protocols.Hedgehog -import Protocols.PacketStream -import Protocols.PacketStream.Hedgehog - -prop_delaystream_id :: Property -prop_delaystream_id = - idWithModelSingleDomain - @System - defExpectOptions - (genPackets 1 10 (genValidPacket defPacketOptions Gen.enumBounded (Range.linear 4 20))) - (exposeClockResetEnable id) - (exposeClockResetEnable (delayStreamC @2 @Int d4)) - -tests :: TestTree -tests = - localOption (mkTimeout 20_000_000 {- 20 seconds -}) $ - localOption - (HedgehogTestLimit (Just 1_000)) - $(testGroupGenerator) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs index 7487860d..12e57b32 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Depacketizers.hs @@ -8,8 +8,6 @@ module Tests.Protocols.PacketStream.Depacketizers ( import Clash.Prelude -import Control.DeepSeq (NFData) - import Hedgehog (Gen, Property) import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range @@ -38,16 +36,8 @@ depacketizerPropGen :: (headerBytes :: Nat). (1 <= dataWidth) => (1 <= headerBytes) => - (NFData metaIn) => - (NFDataX metaIn) => - (NFData metaOut) => - (NFDataX metaOut) => - (Eq metaIn) => - (Eq metaOut) => - (Show metaIn) => - (Show metaOut) => - (ShowX metaIn) => - (ShowX metaOut) => + (TestType metaIn) => + (TestType metaOut) => SNat dataWidth -> SNat headerBytes -> Gen metaIn -> @@ -82,16 +72,8 @@ depacketizeToDfPropGen :: (1 <= headerBytes) => (BitPack a) => (BitSize a ~ headerBytes * 8) => - (NFData metaIn) => - (NFDataX metaIn) => - (NFData a) => - (NFDataX a) => - (Eq metaIn) => - (Eq a) => - (Show metaIn) => - (Show a) => - (ShowX metaIn) => - (ShowX a) => + (TestType a) => + (TestType metaIn) => SNat dataWidth -> SNat headerBytes -> Gen metaIn -> @@ -110,31 +92,6 @@ depacketizeToDfPropGen SNat SNat metaGen toOut = Circuit (PacketStream System dataWidth metaIn) (Df System a) ckt = depacketizeToDfC toOut --- | Test 'dropTailC' with varying data width and bytes to drop. -dropTailTest :: - forall dataWidth n. - (KnownNat n) => - (1 <= dataWidth) => - (1 <= n) => - SNat dataWidth -> - SNat n -> - Property -dropTailTest SNat n = - idWithModelSingleDomain - @System - defExpectOptions - ( genPackets - 1 - 4 - ( genValidPacket - defPacketOptions - (Gen.int8 Range.linearBounded) - (Range.linear (natToNum @(n `DivRU` dataWidth)) 20) - ) - ) - (exposeClockResetEnable (dropTailModel n)) - (exposeClockResetEnable (dropTailC @dataWidth n)) - {- | Do something interesting with both the parsed header and the input metadata for testing purposes. We just xor every byte in the parsed @@ -218,34 +175,6 @@ prop_xor_depacketize_to_df_d5_d4 :: Property prop_xor_depacketize_to_df_d5_d4 = depacketizeToDfPropGen d5 d4 Gen.enumBounded exampleToMetaOut --- | dataWidth < n && dataWidth % n ~ 0 -prop_droptail_4_bytes_d1 :: Property -prop_droptail_4_bytes_d1 = dropTailTest d1 d4 - -prop_droptail_7_bytes_d1 :: Property -prop_droptail_7_bytes_d1 = dropTailTest d1 d7 - --- | dataWidth < n && dataWidth % n > 0 -prop_droptail_4_bytes_d3 :: Property -prop_droptail_4_bytes_d3 = dropTailTest d3 d4 - -prop_droptail_7_bytes_d4 :: Property -prop_droptail_7_bytes_d4 = dropTailTest d4 d7 - --- | dataWidth ~ n -prop_droptail_4_bytes_d4 :: Property -prop_droptail_4_bytes_d4 = dropTailTest d4 d4 - -prop_droptail_7_bytes_d7 :: Property -prop_droptail_7_bytes_d7 = dropTailTest d7 d7 - --- | dataWidth > n -prop_droptail_4_bytes_d7 :: Property -prop_droptail_4_bytes_d7 = dropTailTest d7 d4 - -prop_droptail_7_bytes_d12 :: Property -prop_droptail_7_bytes_d12 = dropTailTest d12 d7 - tests :: TestTree tests = localOption (mkTimeout 20_000_000 {- 20 seconds -}) diff --git a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs index 8eeada06..d86677c7 100644 --- a/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs +++ b/clash-protocols/tests/Tests/Protocols/PacketStream/Packetizers.hs @@ -9,9 +9,7 @@ module Tests.Protocols.PacketStream.Packetizers ( import Clash.Hedgehog.Sized.Vector (genVec) import Clash.Prelude -import Control.DeepSeq (NFData) - -import Hedgehog +import Hedgehog (Property) import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range @@ -44,11 +42,7 @@ packetizerPropGen :: (KnownNat metaInBytes) => (1 <= dataWidth) => (1 <= headerBytes) => - (NFData metaOut) => - (NFDataX metaOut) => - (Eq metaOut) => - (Show metaOut) => - (ShowX metaOut) => + (TestType metaOut) => SNat dataWidth -> SNat headerBytes -> (Vec metaInBytes (BitVector 8) -> metaOut) -> @@ -91,11 +85,7 @@ packetizeFromDfPropGen :: (KnownNat aBytes) => (1 <= dataWidth) => (1 <= headerBytes) => - (NFData metaOut) => - (NFDataX metaOut) => - (Eq metaOut) => - (Show metaOut) => - (ShowX metaOut) => + (TestType metaOut) => SNat dataWidth -> SNat headerBytes -> (Vec aBytes (BitVector 8) -> metaOut) ->