{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Protocols.PacketStream.Hedgehog (
chopBy,
chopPacket,
chunkByPacket,
chunkToPacket,
fullPackets,
smearAbort,
dropAbortedPackets,
downConvert,
upConvert,
depacketizerModel,
depacketizeToDfModel,
dropTailModel,
packetizerModel,
packetizeFromDfModel,
AbortMode (..),
PacketOptions (..),
defPacketOptions,
genValidPacket,
genPackets,
) where
import Clash.Hedgehog.Sized.Vector (genVec)
import Clash.Prelude
import Clash.Sized.Vector qualified as Vec
import Data.List qualified as L
import Data.Maybe (fromJust, isJust)
import Hedgehog (Gen, Range)
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Protocols.PacketStream.Base
chunkBy :: (a -> Bool) -> [a] -> [[a]]
chunkBy _ [] = []
chunkBy predicate list = L.filter (not . null) (chunkByHelper predicate list [])
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)
chunkByPacket ::
[PacketStreamM2S dataWidth meta] ->
[[PacketStreamM2S dataWidth meta]]
chunkByPacket = chunkBy (isJust . _last)
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
chopBy :: Int -> [a] -> [[a]]
chopBy _ [] = []
chopBy n xs = as : chopBy n bs where (as, bs) = L.splitAt n xs
chunkToPacket ::
(KnownNat dataWidth) =>
[PacketStreamM2S 1 meta] ->
PacketStreamM2S dataWidth meta
chunkToPacket xs =
PacketStreamM2S
{ _last =
(\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 = L.foldr ((+>>) . head . _data) (repeat (nullByte "chunkToPacket")) xs
}
where
lastTransfer = L.last xs
chopPacket ::
forall dataWidth meta.
(1 <= dataWidth) =>
(KnownNat dataWidth) =>
PacketStreamM2S dataWidth meta ->
[PacketStreamM2S 1 meta]
chopPacket PacketStreamM2S{..} = packets
where
lasts = case _last of
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 -> toList _data
Just size -> L.take (max 1 (fromIntegral size)) $ toList _data
packets =
( \(size, dat) ->
PacketStreamM2S (pure dat) size _meta _abort
)
<$> L.zip lasts datas
fullPackets ::
(KnownNat dataWidth) =>
[PacketStreamM2S dataWidth meta] ->
[PacketStreamM2S dataWidth meta]
fullPackets [] = []
fullPackets fragments =
let lastFragment = (L.last fragments){_last = Just 1}
in L.init fragments L.++ [lastFragment]
dropAbortedPackets ::
[PacketStreamM2S dataWidth meta] ->
[PacketStreamM2S dataWidth meta]
dropAbortedPackets packets = L.concat $ L.filter (not . any _abort) (chunkByPacket packets)
downConvert ::
forall dataWidth meta.
(1 <= dataWidth) =>
(KnownNat dataWidth) =>
[PacketStreamM2S dataWidth meta] ->
[PacketStreamM2S 1 meta]
downConvert = L.concatMap chopPacket
upConvert ::
forall dataWidth meta.
(1 <= dataWidth) =>
(KnownNat dataWidth) =>
[PacketStreamM2S 1 meta] ->
[PacketStreamM2S dataWidth meta]
upConvert packets =
L.map
chunkToPacket
(chunkByPacket packets >>= chopBy (natToNum @dataWidth))
depacketizerModel ::
forall
(dataWidth :: Nat)
(headerBytes :: Nat)
(metaIn :: Type)
(header :: Type)
(metaOut :: Type).
(KnownNat dataWidth) =>
(KnownNat headerBytes) =>
(1 <= dataWidth) =>
(1 <= headerBytes) =>
(NFDataX metaIn) =>
(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
fwdF' = case fwdF of
[] ->
[ PacketStreamM2S
(Vec.singleton (nullByte "depacketizerModel"))
(Just 0)
(deepErrorX "depacketizerModel: should be replaced")
(_abort (L.last hdrF))
]
_ -> fwdF
hdr = bitCoerce $ Vec.unsafeFromList @headerBytes $ _data <$> hdrF
metaIn = case hdrF of
[] ->
error "depacketizerModel: absurd"
(hdrF0 : _) -> _meta hdrF0
metaOut = toMetaOut hdr metaIn
bytePackets :: [[PacketStreamM2S 1 metaIn]]
bytePackets =
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 = L.map go bytePackets
go = parseHdr . L.splitAt hdrBytes
dataWidthPackets :: [[PacketStreamM2S dataWidth metaOut]]
dataWidthPackets = L.map upConvert parsedPackets
depacketizeToDfModel ::
forall
(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]
depacketizeToDfModel toOut ps = L.map parseHdr bytePackets
where
parseHdr :: [PacketStreamM2S 1 metaIn] -> a
parseHdr [] =
error "depacketizeToDfModel: absurd"
parseHdr hdrF@(hdrF0 : _) =
toOut
(bitCoerce $ Vec.unsafeFromList $ L.map _data hdrF)
(_meta hdrF0)
bytePackets :: [[PacketStreamM2S 1 metaIn]]
bytePackets =
L.filter
( \pkt ->
(L.length pkt > natToNum @headerBytes)
|| (L.length pkt == natToNum @headerBytes && _last (L.last pkt) == Just 1)
)
(chunkByPacket $ downConvert (dropAbortedPackets ps))
dropTailModel ::
forall dataWidth meta n.
(KnownNat dataWidth) =>
(1 <= dataWidth) =>
(1 <= n) =>
SNat n ->
[PacketStreamM2S dataWidth meta] ->
[PacketStreamM2S dataWidth meta]
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 = _last $ L.last bytePkts, _abort = aborted}]
where
aborted = L.any _abort packet
bytePkts = downConvert packet
trimmed = L.take (L.length bytePkts - natToNum @n) bytePkts
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 [] =
error "packetizerModel: Unreachable code"
prependHdr fragments@(h : _) =
hdr L.++ L.map (\f -> f{_meta = metaOut}) fragments
where
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
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 . dfToPacket)
where
dfToPacket :: a -> [PacketStreamM2S 1 metaOut]
dfToPacket d =
fullPackets
$ L.map
(\byte -> PacketStreamM2S (singleton byte) Nothing (toMetaOut d) False)
(toList $ bitCoerce (toHeader d))
data AbortMode
= Abort
{ amPacketGen :: Gen Bool
, amTransferGen :: Gen Bool
}
| NoAbort
data PacketOptions = PacketOptions
{ poAllowEmptyPackets :: Bool
, poAllowTrailingEmpty :: Bool
, poAbortMode :: AbortMode
}
defPacketOptions :: PacketOptions
defPacketOptions =
PacketOptions
{ poAllowEmptyPackets = True
, poAllowTrailingEmpty = True
, poAbortMode =
Abort
{ amPacketGen = Gen.enumBounded
, amTransferGen =
Gen.frequency
[ (90, Gen.constant False)
, (10, Gen.constant True)
]
}
}
genPackets ::
forall (dataWidth :: Nat) (meta :: Type).
(1 <= dataWidth) =>
(KnownNat dataWidth) =>
Int ->
Int ->
Gen [PacketStreamM2S dataWidth meta] ->
Gen [PacketStreamM2S dataWidth meta]
genPackets minB maxB pktGen = L.concat <$> Gen.list (Range.linear minB maxB) pktGen
{-# INLINE genPackets #-}
genValidPacket ::
forall (dataWidth :: Nat) (meta :: Type).
(1 <= dataWidth) =>
(KnownNat dataWidth) =>
PacketOptions ->
Gen meta ->
Range Int ->
Gen [PacketStreamM2S dataWidth meta]
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])
genTransfer ::
forall (dataWidth :: Nat) (meta :: Type).
(1 <= dataWidth) =>
(KnownNat dataWidth) =>
meta ->
Gen Bool ->
Gen (PacketStreamM2S dataWidth meta)
genTransfer meta abortGen =
PacketStreamM2S
<$> genVec Gen.enumBounded
<*> Gen.constant Nothing
<*> Gen.constant meta
<*> abortGen
genLastTransfer ::
forall (dataWidth :: Nat) (meta :: Type).
(1 <= dataWidth) =>
(KnownNat dataWidth) =>
meta ->
Bool ->
Gen Bool ->
Gen (PacketStreamM2S dataWidth meta)
genLastTransfer meta allowEmpty abortGen =
setNull
<$> ( PacketStreamM2S
<$> genVec Gen.enumBounded
<*> (Just <$> Gen.enum (if allowEmpty then 0 else 1) maxBound)
<*> Gen.constant meta
<*> abortGen
)
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) (nullByte "setNull")
)
}