From d4f80c19e198c762effdc4b4a5a156dc646229f0 Mon Sep 17 00:00:00 2001 From: lmbollen Date: Wed, 30 Mar 2022 17:31:24 +0200 Subject: [PATCH 01/12] Implemented scatterUnitWB and gatherUnitWB --- bittide/src/Bittide/ScatterGather.hs | 159 ++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 2 deletions(-) diff --git a/bittide/src/Bittide/ScatterGather.hs b/bittide/src/Bittide/ScatterGather.hs index 7b39db346..885fc3784 100644 --- a/bittide/src/Bittide/ScatterGather.hs +++ b/bittide/src/Bittide/ScatterGather.hs @@ -2,15 +2,21 @@ -- -- SPDX-License-Identifier: Apache-2.0 -module Bittide.ScatterGather(scatterEngine, gatherEngine, scatterGatherEngine) where +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE RecordWildCards #-} +module Bittide.ScatterGather(scatterEngine, gatherEngine, scatterGatherEngine, scatterUnitWB, gatherUnitWB) where import Clash.Prelude +import Contranomy.Wishbone +import Data.Type.Equality ((:~:)(Refl)) +import Data.Proxy + import Bittide.Calendar import Bittide.DoubleBufferedRam import Bittide.SharedTypes - -- | Contains a calendar entry that can be used by a scatter or gather engine. type CalendarEntry memDepth = Index memDepth @@ -108,3 +114,152 @@ scatterGatherEngine bootCalS bootCalG scatterConfig gatherConfig writeFramePE = (\f a -> fmap (a,) f) <$> frameInPE' <*> writeAddrPE gatherOut = doubleBufferedRamU newMetaCycleG readAddrSwitch writeFramePE toSwitch = mux (register True $ (==0) <$> readAddrSwitch) (pure Nothing) $ Just <$> gatherOut + + +-- | Doublebuffered memory component that can be written to by a Bittide link, write address +-- of the incoming frame is determined by the scatterUnit's calendar. The buffers are swapped +-- at the beginning of each metacycle. Reading the buffer is done by supplying a read address. +-- Furthermore this component offers ports to control the incorporated calendar. +scatterUnit :: + ( HiddenClockResetEnable dom + , KnownNat memDepth + , KnownNat frameWidth) => + -- | Initial contents of both memory buffers. + Vec memDepth (BitVector frameWidth) -> + -- | Configuration for the calendar. + CalendarConfig bytes addressWidth (CalendarEntry memDepth) -> + -- | Wishbone master-slave port for the calendar. + Signal dom (WishboneM2S bytes addressWidth) -> + -- | Swap active calendar and shadow calendar. + Signal dom Bool -> + -- | Incoming frame from Bittide link. + Signal dom (DataLink frameWidth) -> + -- | Read address. + Signal dom (Index memDepth) -> + -- | (Data at read address delayed 1 cycle, Wishbone slave-master from calendar) + (Signal dom (BitVector frameWidth), Signal dom (WishboneS2M bytes)) +scatterUnit initMem calConfig wbIn calSwitch linkIn readAddr = (readOut, wbOut) + where + (writeAddr, metaCycle, wbOut) = mkCalendar calConfig calSwitch wbIn + writeOp = (\a b -> (a,) <$> b) <$> writeAddr <*> linkIn + readOut = doubleBufferedRam initMem metaCycle readAddr writeOp + +-- | Doublebuffered memory component that can be written to by a generic write operation, write address +-- of the incoming frame is determined by the scatterUnit's calendar. The buffers are swapped +-- at the beginning of each metacycle. Reading the buffer is done by supplying a read address. +-- Furthermore this component offers ports to control the incorporated calendar. +gatherUnit :: + ( HiddenClockResetEnable dom + , KnownNat memDepth + , KnownNat frameWidth + , 1 <= frameWidth + , KnownNat (DivRU frameWidth 8) + , 1 <= (DivRU frameWidth 8)) => + -- | Initial contents for both memory buffers + Vec memDepth (BitVector frameWidth) -> + -- | Configuration for the calendar. + CalendarConfig bytes addressWidth (CalendarEntry memDepth) -> + -- | Wishbone master-slave port for the calendar. + Signal dom (WishboneM2S bytes addressWidth) -> + -- | Swap active calendar and shadow calendar. + Signal dom Bool -> + -- | Generic write operation writing a frame. + Signal dom (Maybe (LocatedBits memDepth frameWidth)) -> + -- | Byte enable for write operation. + Signal dom (ByteEnable (DivRU frameWidth 8)) -> + -- | (Transmitted frame to Bittide Link, Wishbone slave-master from calendar) + (Signal dom (DataLink frameWidth), Signal dom (WishboneS2M bytes)) +gatherUnit initMem calConfig wbIn calSwitch writeOp byteEnables= (linkOut, wbOut) + where + (readAddr, metaCycle, wbOut) = mkCalendar calConfig calSwitch wbIn + linkOut = mux ((==0) <$> readAddr) (pure Nothing) $ Just <$> bramOut + bramOut = doubleBufferedRamByteAddressable initMem metaCycle readAddr writeOp byteEnables + +-- | Wishbone interface for the scatterUnit and gatherUnit. It makes the scatter and gather +-- unit, which operate on 64 bit frames, addressable via a 32 bit wishbone bus. +wbInterface :: + forall bytes addressWidth addresses . + (KnownNat addresses, 1 <= addresses, KnownNat addressWidth, 2 <= addressWidth) => + -- | Maximum address of the respective memory element as seen from the wishbone side. + Index addresses -> + -- | Wishbone master - slave data. + WishboneM2S bytes addressWidth -> + -- | Read data to be send to over the slave-master port. + Bytes bytes -> + -- | (slave - master data, read address memory element, write data memory element) + (WishboneS2M bytes, Index addresses, Maybe (Bytes bytes)) +wbInterface addressRange WishboneM2S{..} readData = (WishboneS2M{readData, acknowledge, err}, memAddr, writeOp) + where + (alignedAddress, alignment) = split @_ @(addressWidth - 2) @2 addr + wordAligned = alignment == (0 :: BitVector 2) + err = (alignedAddress > resize (pack addressRange)) || not wordAligned + acknowledge = not err && strobe + wbAddr = unpack . resize $ pack alignedAddress + memAddr = wbAddr + writeOp | strobe && writeEnable && not err = Just writeData + | otherwise = Nothing + +-- | Wishbone addressable scatterUnit, the wishbone port can read the data from this +-- memory element as if it has a 32 bit port by selecting the upper 32 or lower 32 bits +-- of the read data. +scatterUnitWB :: + forall dom memDepth awSU bsCal awCal . + (HiddenClockResetEnable dom, KnownNat memDepth, 1 <= memDepth, KnownNat awSU, 2 <= awSU) => + -- | Initial contents of both memory buffers. + Vec memDepth (BitVector 64) -> + -- | Configuration for the calendar. + CalendarConfig bsCal awCal (CalendarEntry memDepth) -> + -- | Wishbone master - slave data calendar. + Signal dom (WishboneM2S bsCal awCal) -> + -- | Swap active calendar and shadow calendar. + Signal dom Bool -> + -- | Incoming frame from Bittide link. + Signal dom (DataLink 64) -> + -- | Wishbone master-slave port scatterUnit. + Signal dom (WishboneM2S 4 awSU) -> + -- | (slave - master data scatterUnit , slave - master data calendar) + (Signal dom (WishboneS2M 4), Signal dom (WishboneS2M bsCal)) +scatterUnitWB initMem calConfig wbInCal calSwitch linkIn wbInSU = (wbOutSU, wbOutCal) + where + (wbOutSU, memAddr, _) = unbundle $ wbInterface maxBound <$> wbInSU <*> scatteredData + (readAddr, upperSelected) = unbundle $ coerceIndexes <$> memAddr + (scatterUnitRead, wbOutCal) = scatterUnit initMem calConfig wbInCal calSwitch linkIn readAddr + (upper, lower) = unbundle $ split <$> scatterUnitRead + scatteredData = mux upperSelected upper lower + +-- | Wishbone addressable gatherUnit, the wishbone port can write data to this +-- memory element as if it has a 32 bit port by controlling the byte enables of the +-- gatherUnit based on the third bit. +gatherUnitWB :: + forall dom memDepth awSU bsCal awCal . + (HiddenClockResetEnable dom, KnownNat memDepth, 1 <= memDepth, KnownNat awSU, 2 <= awSU) => + -- | Initial contents of both memory buffers. + Vec memDepth (BitVector 64) -> + -- | Configuration for the calendar. + CalendarConfig bsCal awCal (CalendarEntry memDepth) -> + -- | Wishbone master - slave data calendar. + Signal dom (WishboneM2S bsCal awCal) -> + -- | Swap active calendar and shadow calendar. + Signal dom Bool -> + -- | Wishbone master-slave port gatherUnit. + Signal dom (WishboneM2S 4 awSU) -> + -- | (slave - master data gatherUnit , slave - master data calendar) + (Signal dom (DataLink 64), Signal dom (WishboneS2M 4), Signal dom (WishboneS2M bsCal)) +gatherUnitWB initMem calConfig wbInCal calSwitch wbInSU = (linkOut, wbOutSU, wbOutCal) + where + (wbOutSU, memAddr, writeOp) = unbundle $ wbInterface maxBound <$> wbInSU <*> pure 0b0 + (writeAddr, upperSelected) = unbundle $ coerceIndexes <$> memAddr + (linkOut, wbOutCal) = gatherUnit initMem calConfig wbInCal calSwitch gatherWrite gatherByteEnables + gatherWrite = mkWrite <$> writeAddr <*> writeOp + gatherByteEnables = mkEnables <$> upperSelected <*> (busSelect <$> wbInSU) + mkWrite address (Just write) = Just (address, write ++# write) + mkWrite _ _ = Nothing + mkEnables selected byteEnables = if selected then byteEnables ++# 0b0 else 0b0 ++# byteEnables + +coerceIndexes :: forall n . (KnownNat n, 1 <= n) => (Index (n*2) -> (Index n, Bool)) +coerceIndexes = case sameNat natA natB of + Just Refl -> bitCoerce + _ -> error "gatherUnitWB: Index coercion failed." + where + natA = Proxy @(CLog 2 (n*2)) + natB = Proxy @(1 + CLog 2 n) From 17646433fe03f9b2ff2bd465a981c4e7743af5ea Mon Sep 17 00:00:00 2001 From: lmbollen Date: Wed, 13 Apr 2022 08:36:03 +0200 Subject: [PATCH 02/12] scatterUnitWB Test works --- bittide/src/Bittide/ScatterGather.hs | 30 ++++-- bittide/tests/Tests/ScatterGather.hs | 136 ++++++++++++++++++++++++--- 2 files changed, 148 insertions(+), 18 deletions(-) diff --git a/bittide/src/Bittide/ScatterGather.hs b/bittide/src/Bittide/ScatterGather.hs index 885fc3784..a6bf42f75 100644 --- a/bittide/src/Bittide/ScatterGather.hs +++ b/bittide/src/Bittide/ScatterGather.hs @@ -10,14 +10,14 @@ module Bittide.ScatterGather(scatterEngine, gatherEngine, scatterGatherEngine, s import Clash.Prelude import Contranomy.Wishbone -import Data.Type.Equality ((:~:)(Refl)) import Data.Proxy +import Data.Type.Equality ((:~:)(Refl)) import Bittide.Calendar import Bittide.DoubleBufferedRam import Bittide.SharedTypes --- | Contains a calendar entry that can be used by a scatter or gather engine. +-- | Calendar entry that can be used by a scatter or gather engine. type CalendarEntry memDepth = Index memDepth -- | The Calendar for the scatter and gather engines contains entries of a single index @@ -179,7 +179,11 @@ gatherUnit initMem calConfig wbIn calSwitch writeOp byteEnables= (linkOut, wbOut -- unit, which operate on 64 bit frames, addressable via a 32 bit wishbone bus. wbInterface :: forall bytes addressWidth addresses . - (KnownNat addresses, 1 <= addresses, KnownNat addressWidth, 2 <= addressWidth) => + ( KnownNat bytes + , KnownNat addresses + , 1 <= addresses + , KnownNat addressWidth + , 2 <= addressWidth) => -- | Maximum address of the respective memory element as seen from the wishbone side. Index addresses -> -- | Wishbone master - slave data. @@ -219,13 +223,15 @@ scatterUnitWB :: Signal dom (WishboneM2S 4 awSU) -> -- | (slave - master data scatterUnit , slave - master data calendar) (Signal dom (WishboneS2M 4), Signal dom (WishboneS2M bsCal)) -scatterUnitWB initMem calConfig wbInCal calSwitch linkIn wbInSU = (wbOutSU, wbOutCal) +scatterUnitWB initMem calConfig wbInCal calSwitch linkIn wbInSU = + (delayControls wbOutSU, wbOutCal) where (wbOutSU, memAddr, _) = unbundle $ wbInterface maxBound <$> wbInSU <*> scatteredData (readAddr, upperSelected) = unbundle $ coerceIndexes <$> memAddr (scatterUnitRead, wbOutCal) = scatterUnit initMem calConfig wbInCal calSwitch linkIn readAddr (upper, lower) = unbundle $ split <$> scatterUnitRead - scatteredData = mux upperSelected upper lower + selected = register (errorX "scatterUnitWB: Initial selection undefined") upperSelected + scatteredData = mux selected upper lower -- | Wishbone addressable gatherUnit, the wishbone port can write data to this -- memory element as if it has a 32 bit port by controlling the byte enables of the @@ -245,7 +251,8 @@ gatherUnitWB :: Signal dom (WishboneM2S 4 awSU) -> -- | (slave - master data gatherUnit , slave - master data calendar) (Signal dom (DataLink 64), Signal dom (WishboneS2M 4), Signal dom (WishboneS2M bsCal)) -gatherUnitWB initMem calConfig wbInCal calSwitch wbInSU = (linkOut, wbOutSU, wbOutCal) +gatherUnitWB initMem calConfig wbInCal calSwitch wbInSU = + (linkOut, delayControls wbOutSU, wbOutCal) where (wbOutSU, memAddr, writeOp) = unbundle $ wbInterface maxBound <$> wbInSU <*> pure 0b0 (writeAddr, upperSelected) = unbundle $ coerceIndexes <$> memAddr @@ -256,10 +263,19 @@ gatherUnitWB initMem calConfig wbInCal calSwitch wbInSU = (linkOut, wbOutSU, wbO mkWrite _ _ = Nothing mkEnables selected byteEnables = if selected then byteEnables ++# 0b0 else 0b0 ++# byteEnables +-- | Coerces an index of size (n*2) to index n with the lower bit as seperate boolean. coerceIndexes :: forall n . (KnownNat n, 1 <= n) => (Index (n*2) -> (Index n, Bool)) coerceIndexes = case sameNat natA natB of Just Refl -> bitCoerce _ -> error "gatherUnitWB: Index coercion failed." where natA = Proxy @(CLog 2 (n*2)) - natB = Proxy @(1 + CLog 2 n) + natB = Proxy @(CLog 2 n + 1) + +delayControls :: HiddenClockResetEnable dom => + Signal dom (WishboneS2M bytes) -> Signal dom (WishboneS2M bytes) +delayControls wbIn = wbOut + where + delayedAck = register False (acknowledge <$> wbIn) + delayedErr = register False (err <$> wbIn) + wbOut = (\wb newAck newErr-> wb{acknowledge = newAck, err = newErr}) <$> wbIn <*> delayedAck <*> delayedErr diff --git a/bittide/tests/Tests/ScatterGather.hs b/bittide/tests/Tests/ScatterGather.hs index acd932d7f..9d62757fa 100644 --- a/bittide/tests/Tests/ScatterGather.hs +++ b/bittide/tests/Tests/ScatterGather.hs @@ -2,19 +2,23 @@ -- -- SPDX-License-Identifier: Apache-2.0 +{-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-} + {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE FlexibleContexts #-} module Tests.ScatterGather(sgGroup) where -import Clash.Prelude +import Clash.Prelude hiding (fromList) import qualified Prelude as P import Clash.Sized.Internal.BitVector -import Clash.Sized.Vector (unsafeFromList) +import Clash.Sized.Vector ( unsafeFromList, fromList) +import Contranomy.Wishbone import Data.Bifunctor import Data.String import Hedgehog @@ -27,6 +31,9 @@ import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import Bittide.ScatterGather +import Bittide.Calendar +import Data.Maybe +import Bittide.SharedTypes isUndefined :: forall n . KnownNat n => BitVector n -> Bool isUndefined (BV mask _) = mask == full @@ -61,14 +68,15 @@ genData = Gen.integral Range.linearBounded genFrame :: Gen (Maybe (BitVector 64)) genFrame = Gen.maybe genData -genFrameList :: Gen [Maybe (BitVector 64)] -genFrameList = Gen.list (Range.constant 1 100) genFrame +genFrameList :: Range Int -> Gen [Maybe (BitVector 64)] +genFrameList range = Gen.list range genFrame sgGroup :: TestTree sgGroup = testGroup "Scatter Gather group" [ testPropertyNamed "GatherSequential - No overwriting implies no lost frames." "engineNoFrameLoss gatherEngine" (engineNoFrameLoss gatherEngine) , testPropertyNamed "ScatterSequential - No overwriting implies no lost frames." "engineNoFrameLoss scatterEngine" (engineNoFrameLoss scatterEngine) - , testPropertyNamed "ScatterGather - No overwriting implies no lost frames." "scatterGatherNoFrameLoss" scatterGatherNoFrameLoss] + , testPropertyNamed "ScatterGather - No overwriting implies no lost frames." "scatterGatherNoFrameLoss" scatterGatherNoFrameLoss + , testPropertyNamed "scatterUnit - No overwriting implies no lost frames." "scatterUnitNoFrameLoss" scatterUnitNoFrameLoss] -- |The type of a sequential engine that is tested by engineNoFrameLoss. type MemoryEngine = @@ -91,8 +99,8 @@ engineNoFrameLoss :: MemoryEngine -> Property engineNoFrameLoss engine = property $ do someCalendar <- forAll genSomeCalendar case someCalendar of - SomeCalendar size@SNat (toList -> calendar) -> do - inputFrames <- forAll genFrameList + SomeCalendar size@SNat (toList -> calendar0) -> do + inputFrames <- forAll $ genFrameList (Range.constant 1 100) let topEntity (unbundle -> (frameIn, calIn, newMeta)) = maybeIsUndefined <$> withClockResetEnable clockGen resetGen enableGen @@ -101,8 +109,8 @@ engineNoFrameLoss engine = property $ do -- length of the inputdata + 2 to compensate for the reset cycle and delay cycle. simLength = 2 * snatToNum size + P.length inputFrames + 2 inputFrames' = P.take simLength $ inputFrames <> P.repeat Nothing - newMetaSignal = cycle $ (==0) <$> [0..P.length calendar - 1] - topEntityInput = P.zip3 inputFrames' (cycle calendar) newMetaSignal + newMetaSignal = cycle $ (==0) <$> [0..P.length calendar0 - 1] + topEntityInput = P.zip3 inputFrames' (cycle calendar0) newMetaSignal simOut = simulateN @System simLength topEntity topEntityInput footnote . fromString $ showX simOut footnote . fromString $ showX topEntityInput @@ -123,8 +131,8 @@ scatterGatherNoFrameLoss :: Property scatterGatherNoFrameLoss = property $ do someCalendarS <- forAll genSomeCalendar someCalendarG <- forAll genSomeCalendar - inputFramesSwitch <- forAll genFrameList - inputFramesPE <- forAll genFrameList + inputFramesSwitch <- forAll $ genFrameList (Range.constant 1 100) + inputFramesPE <- forAll $ genFrameList (Range.constant 1 100) case (someCalendarS, someCalendarG) of (SomeCalendar depthScat@SNat calScat, SomeCalendar depthGath@SNat calGath) -> do let @@ -157,3 +165,109 @@ scatterGatherNoFrameLoss = property $ do footnote . fromString $ "Frames from Switch: " <> show (P.zip addressesScat $ P.take simLength inputFramesSwitch') expected === result + + +deriving instance Show (SNatLE a b) + +data IsInBounds a b c where + InBounds :: (a <= b, b <= c) => IsInBounds a b c + NotInBounds :: IsInBounds a b c + +deriving instance Show (IsInBounds a b c) + +isInBounds :: SNat a -> SNat b -> SNat c -> IsInBounds a b c +isInBounds a b c = case (compareSNat a b, compareSNat b c) of + (SNatLE, SNatLE) -> InBounds + _ -> NotInBounds + +genCalendarConfig :: + forall bytes addressWidth calEntry maxDepth . + ( KnownNat bytes + , 1 <= bytes + , KnownNat maxDepth + , 1 <= maxDepth + , calEntry ~ Index maxDepth + , KnownNat addressWidth) => + SNat maxDepth -> + Gen (CalendarConfig bytes addressWidth calEntry) +genCalendarConfig sizeNat@(snatToNum -> dMax) = do + dA <- Gen.enum 2 dMax + dB <- Gen.enum 2 dMax + case (TN.someNatVal dA, TN.someNatVal dB) of + ( SomeNat (snatProxy -> depthA) + ,SomeNat (snatProxy -> depthB)) -> do + let + regAddrBits = SNat @(NatRequiredBits (Regs calEntry (bytes * 8))) + bsCalEntry = SNat @(BitSize calEntry) + case + ( isInBounds d1 depthA sizeNat + , isInBounds d1 depthB sizeNat + , compareSNat regAddrBits (SNat @addressWidth) + , compareSNat d1 bsCalEntry) of + (InBounds, InBounds, SNatLE, SNatLE)-> go depthA depthB + (a,b,c,d) -> error $ "genCalendarConfig: calEntry constraints not satisfied: (" + <> show a <> ", " <> show b <> ", " <> show c <> ", " <> show d <> "), \n(depthA, depthB, maxDepth, calEntry bitsize) = (" + <> show depthA <> ", " <> show depthB <> ", " <> show sizeNat <> ", " <> show bsCalEntry <> ")" + where + go :: forall depthA depthB . (LessThan depthA maxDepth, LessThan depthB maxDepth, NatFitsInBits (Regs calEntry (bytes * 8)) addressWidth) => SNat depthA -> SNat depthB -> Gen (CalendarConfig bytes addressWidth (Index maxDepth)) + go SNat SNat = do + calActive <- fromMaybe errmsg . fromList @depthA . P.take (natToNum @depthA) <$> Gen.shuffle @_ @(Index maxDepth) [0.. natToNum @(maxDepth-1)] + calShadow <- fromMaybe errmsg . fromList @depthB . P.take (natToNum @depthB) <$> Gen.shuffle @_ @(Index maxDepth) [0.. natToNum @(maxDepth-1)] + return $ CalendarConfig sizeNat calActive calShadow + errmsg = errorX "genCalendarConfig: list to vector conversion failed" + +scatterUnitNoFrameLoss :: Property +scatterUnitNoFrameLoss = property $ do + maxCalSize <- forAll $ Gen.enum 2 32 + case TN.someNatVal (maxCalSize - 1) of + SomeNat (succSNat . snatProxy -> p) -> do + calConfig <- forAll $ genCal p + case calConfig of + CalendarConfig _ calA@(length -> depth) _ -> do + metaCycles <- forAll $ Gen.enum 1 10 + let simLength = 2 + (1+metaCycles) * depth + inputFrames <- forAll $ (\g -> P.take (simLength `div` depth + 1) $ g P.++ P.repeat (P.replicate depth Nothing)) <$> + Gen.list (Range.singleton metaCycles) ( + (\gen -> P.take depth (gen P.++ P.repeat Nothing)) <$> + genFrameList (Range.singleton $ depth `div` 2)) + let + topEntity (unbundle -> (wbIn, linkIn)) = + fst $ withClockResetEnable clockGen resetGen enableGen (scatterUnitWB @System @_ @32) + (deepErrorX "scatterUnit initial elements undefined") calConfig + (pure $ wishboneM2S SNat SNat) (pure False) linkIn wbIn + wbReadOps = P.take simLength $ P.replicate depth idleM2S P.++ P.concat (P.take depth . (P.++ P.repeat idleM2S) . P.concat . P.zipWith wbRead (toList calA) <$> inputFrames) + topEntityInput = P.zip wbReadOps (P.concat inputFrames) + simOut = simulateN simLength topEntity topEntityInput + footnote . fromString $ "simOut: " <> showX simOut + footnote . fromString $ "simIn: " <> showX wbReadOps + footnote . fromString $ "cal: " <> showX calA + wbDirectedDecoding simOut === P.take simLength (fromMaybe 1 <$> P.filter isJust (P.concat inputFrames)) + where + genCal :: forall maxSize . 1 <= maxSize => SNat maxSize -> Gen (CalendarConfig 4 32 (Index maxSize)) + genCal SNat = genCalendarConfig @4 @32 (SNat @maxSize) + +wbDirectedDecoding :: KnownNat bytes => + [WishboneS2M bytes] + -> [BitVector ((8 * bytes) + (8 * bytes))] +wbDirectedDecoding (s2m0 : s2m1 : s2ms) + | acknowledge s2m0 && acknowledge s2m1 = out : wbDirectedDecoding s2ms + | otherwise = wbDirectedDecoding (s2m1 : s2ms) + where + out = readData s2m1 ++# readData s2m0 +wbDirectedDecoding _ = [] + +wbRead :: + forall bytes addressWidth maxIndex a . + (KnownNat bytes, KnownNat addressWidth, KnownNat maxIndex, 1 <= maxIndex) => + Index maxIndex -> Maybe a -> [WishboneM2S bytes addressWidth] +wbRead readAddr (Just _) = + [(wishboneM2S SNat (SNat @addressWidth)){addr = (`shiftL` 3) . resize $ pack readAddr, busCycle = maxBound, strobe = True} + , + (wishboneM2S SNat (SNat @addressWidth)){addr = 4 .|. ((`shiftL` 3) . resize $ pack readAddr), busCycle = maxBound, strobe = True} + ] +wbRead _ Nothing = [] + +idleM2S :: forall bytes aw . (KnownNat bytes, KnownNat aw) => WishboneM2S bytes aw +idleM2S = (wishboneM2S SNat (SNat @aw)){ + addr = 0 +} From 9e939ecdd4bec29c72e0e38baaa1c6cf660b4155 Mon Sep 17 00:00:00 2001 From: lmbollen Date: Wed, 13 Apr 2022 13:48:52 +0200 Subject: [PATCH 03/12] gatherUnitWB test works --- bittide/src/Bittide/ScatterGather.hs | 2 +- bittide/tests/Tests/ScatterGather.hs | 58 ++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/bittide/src/Bittide/ScatterGather.hs b/bittide/src/Bittide/ScatterGather.hs index a6bf42f75..49d52d558 100644 --- a/bittide/src/Bittide/ScatterGather.hs +++ b/bittide/src/Bittide/ScatterGather.hs @@ -172,7 +172,7 @@ gatherUnit :: gatherUnit initMem calConfig wbIn calSwitch writeOp byteEnables= (linkOut, wbOut) where (readAddr, metaCycle, wbOut) = mkCalendar calConfig calSwitch wbIn - linkOut = mux ((==0) <$> readAddr) (pure Nothing) $ Just <$> bramOut + linkOut = mux (register True $ (==0) <$> readAddr) (pure Nothing) $ Just <$> bramOut bramOut = doubleBufferedRamByteAddressable initMem metaCycle readAddr writeOp byteEnables -- | Wishbone interface for the scatterUnit and gatherUnit. It makes the scatter and gather diff --git a/bittide/tests/Tests/ScatterGather.hs b/bittide/tests/Tests/ScatterGather.hs index 9d62757fa..9eca39f29 100644 --- a/bittide/tests/Tests/ScatterGather.hs +++ b/bittide/tests/Tests/ScatterGather.hs @@ -76,7 +76,9 @@ sgGroup = testGroup "Scatter Gather group" [ testPropertyNamed "GatherSequential - No overwriting implies no lost frames." "engineNoFrameLoss gatherEngine" (engineNoFrameLoss gatherEngine) , testPropertyNamed "ScatterSequential - No overwriting implies no lost frames." "engineNoFrameLoss scatterEngine" (engineNoFrameLoss scatterEngine) , testPropertyNamed "ScatterGather - No overwriting implies no lost frames." "scatterGatherNoFrameLoss" scatterGatherNoFrameLoss - , testPropertyNamed "scatterUnit - No overwriting implies no lost frames." "scatterUnitNoFrameLoss" scatterUnitNoFrameLoss] + , testPropertyNamed "scatterUnitWb - No overwriting implies no lost frames." "scatterUnitNoFrameLoss" scatterUnitNoFrameLoss + , testPropertyNamed "gatherUnitWb - No overwriting implies no lost frames." "gatherUnitNoFrameLoss" gatherUnitNoFrameLoss + ] -- |The type of a sequential engine that is tested by engineNoFrameLoss. type MemoryEngine = @@ -246,6 +248,43 @@ scatterUnitNoFrameLoss = property $ do genCal :: forall maxSize . 1 <= maxSize => SNat maxSize -> Gen (CalendarConfig 4 32 (Index maxSize)) genCal SNat = genCalendarConfig @4 @32 (SNat @maxSize) +gatherUnitNoFrameLoss :: Property +gatherUnitNoFrameLoss = property $ do + maxCalSize <- forAll $ Gen.enum 2 32 + case TN.someNatVal (maxCalSize - 1) of + SomeNat (succSNat . snatProxy -> p) -> do + calConfig <- forAll $ genCal p + case calConfig of + CalendarConfig _ calA@(length -> depth) _ -> do + metaCycles <- forAll $ Gen.enum 1 10 + let simLength = 2 + (1+metaCycles) * depth + inputFrames <- forAll $ (\g -> P.take (simLength `div` depth + 1) $ g P.++ P.repeat (P.replicate depth Nothing)) <$> + Gen.list (Range.singleton metaCycles) ( + (\gen -> P.take depth (gen P.++ P.repeat Nothing)) <$> + genFrameList (Range.singleton $ depth `div` 2)) + let + topEntity wbIn = + (\ (a, _ ,_) -> a) $ withClockResetEnable clockGen resetGen enableGen (gatherUnitWB @System @_ @32) + (deepErrorX "scatterUnit initial elements undefined") calConfig + (pure $ wishboneM2S SNat SNat) (pure False) wbIn + wbWriteOps = P.take simLength $ P.concat (P.take depth . (P.++ P.repeat idleM2S) . P.concat . P.zipWith wbWrite (toList calA) <$> inputFrames) + simOut = simulateN simLength topEntity wbWriteOps + writtenFrames = [if snd e /= 0 then fst e else Nothing | e <- P.zip (P.concat inputFrames) (cycle $ toList calA)] + footnote . fromString $ "simOut: " <> showX simOut + footnote . fromString $ "simIn: " <> showX wbWriteOps + footnote . fromString $ "cal: " <> showX calA + footnote . fromString $ "writtenFrames: " <> showX writtenFrames + directedDecode (P.replicate (1+depth) Nothing P.++ writtenFrames) simOut === P.take simLength (fromMaybe 1 <$> P.filter isJust writtenFrames) + where + genCal :: forall maxSize . 1 <= maxSize => SNat maxSize -> Gen (CalendarConfig 4 32 (Index maxSize)) + genCal SNat = genCalendarConfig @4 @32 (SNat @maxSize) + +directedDecode :: [Maybe a] -> [Maybe b] -> [b] +directedDecode ((Just _) : as) ((Just b) : bs) = b : directedDecode as bs +directedDecode (Nothing : as) (_ : bs) = directedDecode as bs +directedDecode _ _ = [] + + wbDirectedDecoding :: KnownNat bytes => [WishboneS2M bytes] -> [BitVector ((8 * bytes) + (8 * bytes))] @@ -261,12 +300,25 @@ wbRead :: (KnownNat bytes, KnownNat addressWidth, KnownNat maxIndex, 1 <= maxIndex) => Index maxIndex -> Maybe a -> [WishboneM2S bytes addressWidth] wbRead readAddr (Just _) = - [(wishboneM2S SNat (SNat @addressWidth)){addr = (`shiftL` 3) . resize $ pack readAddr, busCycle = maxBound, strobe = True} + [(wishboneM2S SNat (SNat @addressWidth)){addr = (`shiftL` 3) . resize $ pack readAddr, busCycle = True, strobe = True} , - (wishboneM2S SNat (SNat @addressWidth)){addr = 4 .|. ((`shiftL` 3) . resize $ pack readAddr), busCycle = maxBound, strobe = True} + (wishboneM2S SNat (SNat @addressWidth)){addr = 4 .|. ((`shiftL` 3) . resize $ pack readAddr), busCycle = True, strobe = True} ] wbRead _ Nothing = [] +wbWrite :: + forall bytes addressWidth maxIndex . + (KnownNat bytes, KnownNat addressWidth, KnownNat maxIndex, 1 <= maxIndex) => + Index maxIndex -> Maybe (BitVector (bytes*2*8)) -> [WishboneM2S bytes addressWidth] +wbWrite writeAddr (Just frame) = + [(wishboneM2S @bytes @addressWidth SNat SNat){addr = (`shiftL` 3) . resize $ pack writeAddr, busSelect = maxBound, busCycle = True, strobe = True, writeEnable = True, writeData = lower} + , + (wishboneM2S @bytes @addressWidth SNat SNat){addr = 4 .|. ((`shiftL` 3) . resize $ pack writeAddr), busSelect = maxBound, busCycle = True, strobe = True, writeEnable = True, writeData = upper} + ] + where + (upper, lower) = split frame +wbWrite _ Nothing = [] + idleM2S :: forall bytes aw . (KnownNat bytes, KnownNat aw) => WishboneM2S bytes aw idleM2S = (wishboneM2S SNat (SNat @aw)){ addr = 0 From dc1f0491d0ce5178694b1a0969305d05462895c7 Mon Sep 17 00:00:00 2001 From: lmbollen Date: Wed, 13 Apr 2022 14:27:03 +0200 Subject: [PATCH 04/12] cleanup --- bittide/tests/Tests/ScatterGather.hs | 130 +++++++++++++++++++-------- 1 file changed, 94 insertions(+), 36 deletions(-) diff --git a/bittide/tests/Tests/ScatterGather.hs b/bittide/tests/Tests/ScatterGather.hs index 9eca39f29..6e40a6d49 100644 --- a/bittide/tests/Tests/ScatterGather.hs +++ b/bittide/tests/Tests/ScatterGather.hs @@ -193,8 +193,8 @@ genCalendarConfig :: SNat maxDepth -> Gen (CalendarConfig bytes addressWidth calEntry) genCalendarConfig sizeNat@(snatToNum -> dMax) = do - dA <- Gen.enum 2 dMax - dB <- Gen.enum 2 dMax + dA <- Gen.enum 1 dMax + dB <- Gen.enum 1 dMax case (TN.someNatVal dA, TN.someNatVal dB) of ( SomeNat (snatProxy -> depthA) ,SomeNat (snatProxy -> depthB)) -> do @@ -211,9 +211,16 @@ genCalendarConfig sizeNat@(snatToNum -> dMax) = do <> show a <> ", " <> show b <> ", " <> show c <> ", " <> show d <> "), \n(depthA, depthB, maxDepth, calEntry bitsize) = (" <> show depthA <> ", " <> show depthB <> ", " <> show sizeNat <> ", " <> show bsCalEntry <> ")" where - go :: forall depthA depthB . (LessThan depthA maxDepth, LessThan depthB maxDepth, NatFitsInBits (Regs calEntry (bytes * 8)) addressWidth) => SNat depthA -> SNat depthB -> Gen (CalendarConfig bytes addressWidth (Index maxDepth)) + go :: forall depthA depthB . + ( LessThan depthA maxDepth + , LessThan depthB maxDepth + , NatFitsInBits (Regs calEntry (bytes * 8)) addressWidth) => + SNat depthA -> + SNat depthB -> + Gen (CalendarConfig bytes addressWidth (Index maxDepth)) go SNat SNat = do - calActive <- fromMaybe errmsg . fromList @depthA . P.take (natToNum @depthA) <$> Gen.shuffle @_ @(Index maxDepth) [0.. natToNum @(maxDepth-1)] + calActive <- fromMaybe errmsg . fromList @depthA . P.take (natToNum @depthA) + <$> Gen.shuffle @_ @(Index maxDepth) [0.. natToNum @(maxDepth-1)] calShadow <- fromMaybe errmsg . fromList @depthB . P.take (natToNum @depthB) <$> Gen.shuffle @_ @(Index maxDepth) [0.. natToNum @(maxDepth-1)] return $ CalendarConfig sizeNat calActive calShadow errmsg = errorX "genCalendarConfig: list to vector conversion failed" @@ -226,27 +233,38 @@ scatterUnitNoFrameLoss = property $ do calConfig <- forAll $ genCal p case calConfig of CalendarConfig _ calA@(length -> depth) _ -> do + -- Amount of metacycles of input to generate metaCycles <- forAll $ Gen.enum 1 10 - let simLength = 2 + (1+metaCycles) * depth - inputFrames <- forAll $ (\g -> P.take (simLength `div` depth + 1) $ g P.++ P.repeat (P.replicate depth Nothing)) <$> - Gen.list (Range.singleton metaCycles) ( - (\gen -> P.take depth (gen P.++ P.repeat Nothing)) <$> - genFrameList (Range.singleton $ depth `div` 2)) let - topEntity (unbundle -> (wbIn, linkIn)) = - fst $ withClockResetEnable clockGen resetGen enableGen (scatterUnitWB @System @_ @32) + -- reset cycle + cycle delay, last metacycle's writes can be read in (metacycles + 1) + simLength = 2 + (1+metaCycles) * depth + inputGen = Gen.list (Range.singleton metaCycles) + metaCycleNothing = P.replicate depth Nothing + -- Generate at most depth `div` 2 elements to be written each metacycle since + -- we need two cycles to read a written element. + metaCycleGen = genFrameList (Range.singleton $ depth `div` 2) + + inputFrames <- forAll $ padToLength (simLength `div` depth + 1) metaCycleNothing + <$> inputGen (padToLength depth Nothing <$> metaCycleGen) + let + topEntity (unbundle -> (wbIn, linkIn)) = fst $ + withClockResetEnable clockGen resetGen enableGen (scatterUnitWB @System @_ @32) (deepErrorX "scatterUnit initial elements undefined") calConfig (pure $ wishboneM2S SNat SNat) (pure False) linkIn wbIn - wbReadOps = P.take simLength $ P.replicate depth idleM2S P.++ P.concat (P.take depth . (P.++ P.repeat idleM2S) . P.concat . P.zipWith wbRead (toList calA) <$> inputFrames) + + wbReadOps = P.take simLength $ P.replicate depth idleM2S P.++ P.concat + (padToLength depth idleM2S . P.concat . P.zipWith wbRead (toList calA) <$> inputFrames) + topEntityInput = P.zip wbReadOps (P.concat inputFrames) simOut = simulateN simLength topEntity topEntityInput footnote . fromString $ "simOut: " <> showX simOut footnote . fromString $ "simIn: " <> showX wbReadOps footnote . fromString $ "cal: " <> showX calA - wbDirectedDecoding simOut === P.take simLength (fromMaybe 1 <$> P.filter isJust (P.concat inputFrames)) + wbDirectedDecoding simOut === P.take simLength (catMaybes (P.concat inputFrames)) where genCal :: forall maxSize . 1 <= maxSize => SNat maxSize -> Gen (CalendarConfig 4 32 (Index maxSize)) genCal SNat = genCalendarConfig @4 @32 (SNat @maxSize) + padToLength l padElement g = P.take l (g P.++ P.repeat padElement) gatherUnitNoFrameLoss :: Property gatherUnitNoFrameLoss = property $ do @@ -257,27 +275,41 @@ gatherUnitNoFrameLoss = property $ do case calConfig of CalendarConfig _ calA@(length -> depth) _ -> do metaCycles <- forAll $ Gen.enum 1 10 - let simLength = 2 + (1+metaCycles) * depth - inputFrames <- forAll $ (\g -> P.take (simLength `div` depth + 1) $ g P.++ P.repeat (P.replicate depth Nothing)) <$> - Gen.list (Range.singleton metaCycles) ( - (\gen -> P.take depth (gen P.++ P.repeat Nothing)) <$> - genFrameList (Range.singleton $ depth `div` 2)) let - topEntity wbIn = - (\ (a, _ ,_) -> a) $ withClockResetEnable clockGen resetGen enableGen (gatherUnitWB @System @_ @32) + simLength = 2 + (1+metaCycles) * depth + inputGen = Gen.list (Range.singleton metaCycles) + metaCycleNothing = P.replicate depth Nothing + metaCycleGen = genFrameList (Range.singleton $ depth `div` 2) + inputFrames <- forAll $ padToLength (simLength `div` depth + 1) metaCycleNothing + <$> inputGen (padToLength depth Nothing <$> metaCycleGen) + let + topEntity wbIn = (\ (a, _ ,_) -> a) $ + withClockResetEnable clockGen resetGen enableGen (gatherUnitWB @System @_ @32) (deepErrorX "scatterUnit initial elements undefined") calConfig (pure $ wishboneM2S SNat SNat) (pure False) wbIn - wbWriteOps = P.take simLength $ P.concat (P.take depth . (P.++ P.repeat idleM2S) . P.concat . P.zipWith wbWrite (toList calA) <$> inputFrames) + + wbWriteOps = P.take simLength . P.concat $ + padToLength depth idleM2S . P.concat . P.zipWith wbWrite (toList calA) <$> inputFrames + simOut = simulateN simLength topEntity wbWriteOps - writtenFrames = [if snd e /= 0 then fst e else Nothing | e <- P.zip (P.concat inputFrames) (cycle $ toList calA)] + addressedFrames = P.zip (P.concat inputFrames) (cycle $ toList calA) + writtenFrames = [if snd e /= 0 then fst e else Nothing | e <- addressedFrames] + prePad = (P.replicate (1+depth) Nothing P.++) + expectedOutput = P.take simLength (fromMaybe 1 <$> P.filter isJust writtenFrames) + footnote . fromString $ "simOut: " <> showX simOut footnote . fromString $ "simIn: " <> showX wbWriteOps footnote . fromString $ "cal: " <> showX calA footnote . fromString $ "writtenFrames: " <> showX writtenFrames - directedDecode (P.replicate (1+depth) Nothing P.++ writtenFrames) simOut === P.take simLength (fromMaybe 1 <$> P.filter isJust writtenFrames) + + directedDecode (prePad writtenFrames) simOut === expectedOutput where - genCal :: forall maxSize . 1 <= maxSize => SNat maxSize -> Gen (CalendarConfig 4 32 (Index maxSize)) + genCal :: forall maxSize . + 1 <= maxSize => + SNat maxSize -> + Gen (CalendarConfig 4 32 (Index maxSize)) genCal SNat = genCalendarConfig @4 @32 (SNat @maxSize) + padToLength l padElement g = P.take l (g P.++ P.repeat padElement) directedDecode :: [Maybe a] -> [Maybe b] -> [b] directedDecode ((Just _) : as) ((Just b) : bs) = b : directedDecode as bs @@ -297,29 +329,55 @@ wbDirectedDecoding _ = [] wbRead :: forall bytes addressWidth maxIndex a . - (KnownNat bytes, KnownNat addressWidth, KnownNat maxIndex, 1 <= maxIndex) => - Index maxIndex -> Maybe a -> [WishboneM2S bytes addressWidth] + ( KnownNat bytes + , KnownNat addressWidth + , KnownNat maxIndex + , 1 <= maxIndex) => + Index maxIndex -> + Maybe a -> + [WishboneM2S bytes addressWidth] wbRead readAddr (Just _) = - [(wishboneM2S SNat (SNat @addressWidth)){addr = (`shiftL` 3) . resize $ pack readAddr, busCycle = True, strobe = True} + [(wishboneM2S SNat (SNat @addressWidth)) + { addr = (`shiftL` 3) . resize $ pack readAddr + , busCycle = True + , strobe = True} , - (wishboneM2S SNat (SNat @addressWidth)){addr = 4 .|. ((`shiftL` 3) . resize $ pack readAddr), busCycle = True, strobe = True} + (wishboneM2S SNat (SNat @addressWidth)) + { addr = 4 .|. ((`shiftL` 3) . resize $ pack readAddr) + , busCycle = True + , strobe = True} ] wbRead _ Nothing = [] wbWrite :: forall bytes addressWidth maxIndex . - (KnownNat bytes, KnownNat addressWidth, KnownNat maxIndex, 1 <= maxIndex) => - Index maxIndex -> Maybe (BitVector (bytes*2*8)) -> [WishboneM2S bytes addressWidth] + ( KnownNat bytes + , KnownNat addressWidth + , KnownNat maxIndex + , 1 <= maxIndex) => + Index maxIndex -> + Maybe (BitVector (bytes*2*8)) -> + [WishboneM2S bytes addressWidth] wbWrite writeAddr (Just frame) = - [(wishboneM2S @bytes @addressWidth SNat SNat){addr = (`shiftL` 3) . resize $ pack writeAddr, busSelect = maxBound, busCycle = True, strobe = True, writeEnable = True, writeData = lower} - , - (wishboneM2S @bytes @addressWidth SNat SNat){addr = 4 .|. ((`shiftL` 3) . resize $ pack writeAddr), busSelect = maxBound, busCycle = True, strobe = True, writeEnable = True, writeData = upper} + [(wishboneM2S @bytes @addressWidth SNat SNat) + { addr = (`shiftL` 3) . resize $ pack writeAddr + , busSelect = maxBound + , busCycle = True + , strobe = True + , writeEnable = True + , writeData = lower} + , + (wishboneM2S @bytes @addressWidth SNat SNat) + { addr = 4 .|. ((`shiftL` 3) . resize $ pack writeAddr) + , busSelect = maxBound + , busCycle = True + , strobe = True + , writeEnable = True + , writeData = upper} ] where (upper, lower) = split frame wbWrite _ Nothing = [] idleM2S :: forall bytes aw . (KnownNat bytes, KnownNat aw) => WishboneM2S bytes aw -idleM2S = (wishboneM2S SNat (SNat @aw)){ - addr = 0 -} +idleM2S = (wishboneM2S SNat (SNat @aw)){addr = 0} From fde29f24dace2915503382abbe59ae36b6215711 Mon Sep 17 00:00:00 2001 From: lmbollen Date: Mon, 25 Apr 2022 11:29:06 +0200 Subject: [PATCH 05/12] Add bits of documentation --- bittide/src/Bittide/ScatterGather.hs | 1 + bittide/tests/Tests/ScatterGather.hs | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/bittide/src/Bittide/ScatterGather.hs b/bittide/src/Bittide/ScatterGather.hs index 49d52d558..295e531d3 100644 --- a/bittide/src/Bittide/ScatterGather.hs +++ b/bittide/src/Bittide/ScatterGather.hs @@ -272,6 +272,7 @@ coerceIndexes = case sameNat natA natB of natA = Proxy @(CLog 2 (n*2)) natB = Proxy @(CLog 2 n + 1) +-- | Delays the output controls to align them with the actual read / write timing. delayControls :: HiddenClockResetEnable dom => Signal dom (WishboneS2M bytes) -> Signal dom (WishboneS2M bytes) delayControls wbIn = wbOut diff --git a/bittide/tests/Tests/ScatterGather.hs b/bittide/tests/Tests/ScatterGather.hs index 6e40a6d49..04a7442c3 100644 --- a/bittide/tests/Tests/ScatterGather.hs +++ b/bittide/tests/Tests/ScatterGather.hs @@ -177,11 +177,13 @@ data IsInBounds a b c where deriving instance Show (IsInBounds a b c) +-- | Returns 'InBounds' when a <= b <= c, otherwise returns 'NotInBounds'. isInBounds :: SNat a -> SNat b -> SNat c -> IsInBounds a b c isInBounds a b c = case (compareSNat a b, compareSNat b c) of (SNatLE, SNatLE) -> InBounds _ -> NotInBounds +-- | Generates a 'CalendarConfig' for the 'gatherUnitWB' or 'scatterUnitWB' genCalendarConfig :: forall bytes addressWidth calEntry maxDepth . ( KnownNat bytes @@ -225,6 +227,7 @@ genCalendarConfig sizeNat@(snatToNum -> dMax) = do return $ CalendarConfig sizeNat calActive calShadow errmsg = errorX "genCalendarConfig: list to vector conversion failed" +-- | Check if the scatter unit with wishbone interface loses no frames. scatterUnitNoFrameLoss :: Property scatterUnitNoFrameLoss = property $ do maxCalSize <- forAll $ Gen.enum 2 32 @@ -260,12 +263,13 @@ scatterUnitNoFrameLoss = property $ do footnote . fromString $ "simOut: " <> showX simOut footnote . fromString $ "simIn: " <> showX wbReadOps footnote . fromString $ "cal: " <> showX calA - wbDirectedDecoding simOut === P.take simLength (catMaybes (P.concat inputFrames)) + wbDecoding simOut === P.take simLength (catMaybes (P.concat inputFrames)) where genCal :: forall maxSize . 1 <= maxSize => SNat maxSize -> Gen (CalendarConfig 4 32 (Index maxSize)) genCal SNat = genCalendarConfig @4 @32 (SNat @maxSize) padToLength l padElement g = P.take l (g P.++ P.repeat padElement) +-- | Check if the gather unit with wishbone interface loses no frames. gatherUnitNoFrameLoss :: Property gatherUnitNoFrameLoss = property $ do maxCalSize <- forAll $ Gen.enum 2 32 @@ -316,17 +320,21 @@ directedDecode ((Just _) : as) ((Just b) : bs) = b : directedDecode as bs directedDecode (Nothing : as) (_ : bs) = directedDecode as bs directedDecode _ _ = [] - -wbDirectedDecoding :: KnownNat bytes => +-- | Decode an incoming slave bus by consuming two acknowledged signals and concatenating +-- their readData's. +wbDecoding :: KnownNat bytes => [WishboneS2M bytes] -> [BitVector ((8 * bytes) + (8 * bytes))] -wbDirectedDecoding (s2m0 : s2m1 : s2ms) - | acknowledge s2m0 && acknowledge s2m1 = out : wbDirectedDecoding s2ms - | otherwise = wbDirectedDecoding (s2m1 : s2ms) +wbDecoding (s2m0 : s2m1 : s2ms) + | acknowledge s2m0 && acknowledge s2m1 = out : wbDecoding s2ms + | otherwise = wbDecoding (s2m1 : s2ms) where out = readData s2m1 ++# readData s2m0 -wbDirectedDecoding _ = [] +wbDecoding _ = [] +-- | Tranform a read address with expected frame into a wishbone read operation for testing +-- the 'scatterUnitWB'. The second argument indicate wether or not a frame can be read from +-- that read address. The read operation reads data over 2 read cycles. wbRead :: forall bytes addressWidth maxIndex a . ( KnownNat bytes @@ -349,6 +357,8 @@ wbRead readAddr (Just _) = ] wbRead _ Nothing = [] +-- | Transform a write address with frame to a wishbone write operation for testing the +-- 'gatherUnitWB'. The write operation writes the incoming bitvector over 2 write cycles. wbWrite :: forall bytes addressWidth maxIndex . ( KnownNat bytes @@ -379,5 +389,6 @@ wbWrite writeAddr (Just frame) = (upper, lower) = split frame wbWrite _ Nothing = [] +-- | Idle 'WishboneM2S' bus. idleM2S :: forall bytes aw . (KnownNat bytes, KnownNat aw) => WishboneM2S bytes aw idleM2S = (wishboneM2S SNat (SNat @aw)){addr = 0} From f6a028a28167fce8a093ca0a27e034339fa04f66 Mon Sep 17 00:00:00 2001 From: lmbollen Date: Wed, 4 May 2022 16:09:06 +0200 Subject: [PATCH 06/12] Made wbInterface address eval more lazy --- bittide/src/Bittide/ScatterGather.hs | 5 +++-- bittide/tests/Tests/ScatterGather.hs | 2 +- contranomy/src/Contranomy/Wishbone.hs | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bittide/src/Bittide/ScatterGather.hs b/bittide/src/Bittide/ScatterGather.hs index 295e531d3..f66eed0f1 100644 --- a/bittide/src/Bittide/ScatterGather.hs +++ b/bittide/src/Bittide/ScatterGather.hs @@ -194,10 +194,11 @@ wbInterface :: (WishboneS2M bytes, Index addresses, Maybe (Bytes bytes)) wbInterface addressRange WishboneM2S{..} readData = (WishboneS2M{readData, acknowledge, err}, memAddr, writeOp) where + masterActive = strobe && busCycle (alignedAddress, alignment) = split @_ @(addressWidth - 2) @2 addr wordAligned = alignment == (0 :: BitVector 2) - err = (alignedAddress > resize (pack addressRange)) || not wordAligned - acknowledge = not err && strobe + err = masterActive && ((alignedAddress > resize (pack addressRange)) || not wordAligned) + acknowledge = masterActive && not err wbAddr = unpack . resize $ pack alignedAddress memAddr = wbAddr writeOp | strobe && writeEnable && not err = Just writeData diff --git a/bittide/tests/Tests/ScatterGather.hs b/bittide/tests/Tests/ScatterGather.hs index 04a7442c3..681428984 100644 --- a/bittide/tests/Tests/ScatterGather.hs +++ b/bittide/tests/Tests/ScatterGather.hs @@ -391,4 +391,4 @@ wbWrite _ Nothing = [] -- | Idle 'WishboneM2S' bus. idleM2S :: forall bytes aw . (KnownNat bytes, KnownNat aw) => WishboneM2S bytes aw -idleM2S = (wishboneM2S SNat (SNat @aw)){addr = 0} +idleM2S = (wishboneM2S SNat (SNat @aw)) diff --git a/contranomy/src/Contranomy/Wishbone.hs b/contranomy/src/Contranomy/Wishbone.hs index 7a867d553..5eec275fc 100644 --- a/contranomy/src/Contranomy/Wishbone.hs +++ b/contranomy/src/Contranomy/Wishbone.hs @@ -79,7 +79,7 @@ wishboneM2S SNat SNat wishboneS2M :: SNat bytes -> WishboneS2M bytes wishboneS2M SNat = WishboneS2M - { readData = 0 + { readData = undefined , acknowledge = False , err = False } From 44c2adb1500d9fce881a5c04b717ebd129eed2ec Mon Sep 17 00:00:00 2001 From: lmbollen Date: Fri, 6 May 2022 15:13:53 +0200 Subject: [PATCH 07/12] Processed reviewer comments --- bittide/bittide.cabal | 1 + bittide/src/Bittide/ScatterGather.hs | 57 +++++++++++++++++++++------- bittide/tests/Tests/Calendar.hs | 14 +------ bittide/tests/Tests/ScatterGather.hs | 25 ++++-------- bittide/tests/Tests/Shared.hs | 20 ++++++++++ 5 files changed, 75 insertions(+), 42 deletions(-) create mode 100644 bittide/tests/Tests/Shared.hs diff --git a/bittide/bittide.cabal b/bittide/bittide.cabal index 403a7d510..4523514ec 100644 --- a/bittide/bittide.cabal +++ b/bittide/bittide.cabal @@ -97,6 +97,7 @@ test-suite unittests Tests.DoubleBufferedRam Tests.ScatterGather Tests.Switch + Tests.Shared default-extensions: ImplicitPrelude build-depends: diff --git a/bittide/src/Bittide/ScatterGather.hs b/bittide/src/Bittide/ScatterGather.hs index f66eed0f1..5edda8e22 100644 --- a/bittide/src/Bittide/ScatterGather.hs +++ b/bittide/src/Bittide/ScatterGather.hs @@ -5,7 +5,12 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} -module Bittide.ScatterGather(scatterEngine, gatherEngine, scatterGatherEngine, scatterUnitWB, gatherUnitWB) where +module Bittide.ScatterGather + ( scatterEngine + , gatherEngine + , scatterGatherEngine + , scatterUnitWB + , gatherUnitWB) where import Clash.Prelude @@ -66,7 +71,10 @@ gatherEngine newMetaCycle frameIn readAddr = writeAddr = register 0 $ satSucc SatWrap <$> writeAddr writeFrame = combineFrameWithAddr frameIn writeAddr -combineFrameWithAddr :: Signal dom (Maybe dat) -> Signal dom addr -> Signal dom (Maybe (addr,dat)) +combineFrameWithAddr :: + Signal dom (Maybe dat) -> + Signal dom addr -> + Signal dom (Maybe (addr,dat)) combineFrameWithAddr frameIn writeAddr = combine <$> frameIn <*> writeAddr where combine :: Maybe dat -> addr -> Maybe (addr,dat) @@ -78,8 +86,16 @@ combineFrameWithAddr frameIn writeAddr = combine <$> frameIn <*> writeAddr -- If the read address for the scatter engine is 0, a null frame (Nothing) will be sent to the switch. scatterGatherEngine :: forall dom calDepthG calDepthS memDepthG memDepthS frameWidth . - (HiddenClockResetEnable dom, KnownNat calDepthG, KnownNat calDepthS, KnownNat memDepthG, KnownNat memDepthS, KnownNat frameWidth, - 1 <= calDepthG , 1 <= calDepthS, 1 <= memDepthG, 1 <= memDepthS) => + ( HiddenClockResetEnable dom + , KnownNat calDepthG + , KnownNat calDepthS + , KnownNat memDepthG + , KnownNat memDepthS + , KnownNat frameWidth + , 1 <= calDepthG + , 1 <= calDepthS + , 1 <= memDepthG + , 1 <= memDepthS) => -- | Bootstrap calendar gather memory. Calendar calDepthS memDepthS -> -- | Bootstrap calendar scatter memory. @@ -113,7 +129,8 @@ scatterGatherEngine bootCalS bootCalG scatterConfig gatherConfig (readAddrSwitch, newMetaCycleG) = calendar bootCalG (pure False) gatherConfig writeFramePE = (\f a -> fmap (a,) f) <$> frameInPE' <*> writeAddrPE gatherOut = doubleBufferedRamU newMetaCycleG readAddrSwitch writeFramePE - toSwitch = mux (register True $ (==0) <$> readAddrSwitch) (pure Nothing) $ Just <$> gatherOut + toSwitch = mux (register True $ (==0) <$> readAddrSwitch) (pure Nothing) + $ Just <$> gatherOut -- | Doublebuffered memory component that can be written to by a Bittide link, write address @@ -192,7 +209,8 @@ wbInterface :: Bytes bytes -> -- | (slave - master data, read address memory element, write data memory element) (WishboneS2M bytes, Index addresses, Maybe (Bytes bytes)) -wbInterface addressRange WishboneM2S{..} readData = (WishboneS2M{readData, acknowledge, err}, memAddr, writeOp) +wbInterface addressRange WishboneM2S{..} readData = + (WishboneS2M{readData, acknowledge, err}, memAddr, writeOp) where masterActive = strobe && busCycle (alignedAddress, alignment) = split @_ @(addressWidth - 2) @2 addr @@ -209,7 +227,11 @@ wbInterface addressRange WishboneM2S{..} readData = (WishboneS2M{readData, ackno -- of the read data. scatterUnitWB :: forall dom memDepth awSU bsCal awCal . - (HiddenClockResetEnable dom, KnownNat memDepth, 1 <= memDepth, KnownNat awSU, 2 <= awSU) => + ( HiddenClockResetEnable dom + , KnownNat memDepth + , 1 <= memDepth + , KnownNat awSU + , 2 <= awSU) => -- | Initial contents of both memory buffers. Vec memDepth (BitVector 64) -> -- | Configuration for the calendar. @@ -231,7 +253,7 @@ scatterUnitWB initMem calConfig wbInCal calSwitch linkIn wbInSU = (readAddr, upperSelected) = unbundle $ coerceIndexes <$> memAddr (scatterUnitRead, wbOutCal) = scatterUnit initMem calConfig wbInCal calSwitch linkIn readAddr (upper, lower) = unbundle $ split <$> scatterUnitRead - selected = register (errorX "scatterUnitWB: Initial selection undefined") upperSelected + selected = register (errorX "scatterUnitWb: Initial selection undefined") upperSelected scatteredData = mux selected upper lower -- | Wishbone addressable gatherUnit, the wishbone port can write data to this @@ -239,7 +261,11 @@ scatterUnitWB initMem calConfig wbInCal calSwitch linkIn wbInSU = -- gatherUnit based on the third bit. gatherUnitWB :: forall dom memDepth awSU bsCal awCal . - (HiddenClockResetEnable dom, KnownNat memDepth, 1 <= memDepth, KnownNat awSU, 2 <= awSU) => + ( HiddenClockResetEnable dom + , KnownNat memDepth + , 1 <= memDepth + , KnownNat awSU + , 2 <= awSU) => -- | Initial contents of both memory buffers. Vec memDepth (BitVector 64) -> -- | Configuration for the calendar. @@ -257,12 +283,16 @@ gatherUnitWB initMem calConfig wbInCal calSwitch wbInSU = where (wbOutSU, memAddr, writeOp) = unbundle $ wbInterface maxBound <$> wbInSU <*> pure 0b0 (writeAddr, upperSelected) = unbundle $ coerceIndexes <$> memAddr - (linkOut, wbOutCal) = gatherUnit initMem calConfig wbInCal calSwitch gatherWrite gatherByteEnables + (linkOut, wbOutCal) = + gatherUnit initMem calConfig wbInCal calSwitch gatherWrite gatherByteEnables gatherWrite = mkWrite <$> writeAddr <*> writeOp gatherByteEnables = mkEnables <$> upperSelected <*> (busSelect <$> wbInSU) + mkWrite address (Just write) = Just (address, write ++# write) mkWrite _ _ = Nothing - mkEnables selected byteEnables = if selected then byteEnables ++# 0b0 else 0b0 ++# byteEnables + mkEnables selected byteEnables + | selected = byteEnables ++# 0b0 + | otherwise = 0b0 ++# byteEnables -- | Coerces an index of size (n*2) to index n with the lower bit as seperate boolean. coerceIndexes :: forall n . (KnownNat n, 1 <= n) => (Index (n*2) -> (Index n, Bool)) @@ -271,7 +301,7 @@ coerceIndexes = case sameNat natA natB of _ -> error "gatherUnitWB: Index coercion failed." where natA = Proxy @(CLog 2 (n*2)) - natB = Proxy @(CLog 2 n + 1) + natB = Proxy @(1 + (CLog 2 n)) -- | Delays the output controls to align them with the actual read / write timing. delayControls :: HiddenClockResetEnable dom => @@ -280,4 +310,5 @@ delayControls wbIn = wbOut where delayedAck = register False (acknowledge <$> wbIn) delayedErr = register False (err <$> wbIn) - wbOut = (\wb newAck newErr-> wb{acknowledge = newAck, err = newErr}) <$> wbIn <*> delayedAck <*> delayedErr + wbOut = (\wb newAck newErr-> wb{acknowledge = newAck, err = newErr}) + <$> wbIn <*> delayedAck <*> delayedErr diff --git a/bittide/tests/Tests/Calendar.hs b/bittide/tests/Tests/Calendar.hs index c20f93ac2..fdcba912b 100644 --- a/bittide/tests/Tests/Calendar.hs +++ b/bittide/tests/Tests/Calendar.hs @@ -20,6 +20,8 @@ import Clash.Hedgehog.Sized.Vector import Bittide.Calendar import Bittide.SharedTypes +import Tests.Shared + import Clash.Sized.Vector (unsafeFromList) import Contranomy.Wishbone import Data.Constraint @@ -71,18 +73,6 @@ instance Show (BVCalendar addressWidth) where -- TODO: Remove this show instance after issue (https://github.com/clash-lang/clash-compiler/issues/2190) has been fixed. deriving instance Show (SNatLE a b) -data IsInBounds a b c where - InBounds :: (a <= b, b <= c) => IsInBounds a b c - NotInBounds :: IsInBounds a b c - -deriving instance Show (IsInBounds a b c) - --- | Returns 'InBounds' if a <= b <= c, otherwise returns 'NotInBounds'. -isInBounds :: SNat a -> SNat b -> SNat c -> IsInBounds a b c -isInBounds a b c = case (compareSNat a b, compareSNat b c) of - (SNatLE, SNatLE) -> InBounds - _ -> NotInBounds - -- | Generates a configuration for 'Bittide.Calendar.calendarWb', with as first argument -- the maximum depth of the stored calendar and as second argument a generator for the -- calendar entries. diff --git a/bittide/tests/Tests/ScatterGather.hs b/bittide/tests/Tests/ScatterGather.hs index 681428984..f2bbd448f 100644 --- a/bittide/tests/Tests/ScatterGather.hs +++ b/bittide/tests/Tests/ScatterGather.hs @@ -34,6 +34,7 @@ import Bittide.ScatterGather import Bittide.Calendar import Data.Maybe import Bittide.SharedTypes +import Tests.Shared isUndefined :: forall n . KnownNat n => BitVector n -> Bool isUndefined (BV mask _) = mask == full @@ -168,21 +169,9 @@ scatterGatherNoFrameLoss = property $ do expected === result - +-- TODO: Remove instance once Clash.Prelude has it. deriving instance Show (SNatLE a b) -data IsInBounds a b c where - InBounds :: (a <= b, b <= c) => IsInBounds a b c - NotInBounds :: IsInBounds a b c - -deriving instance Show (IsInBounds a b c) - --- | Returns 'InBounds' when a <= b <= c, otherwise returns 'NotInBounds'. -isInBounds :: SNat a -> SNat b -> SNat c -> IsInBounds a b c -isInBounds a b c = case (compareSNat a b, compareSNat b c) of - (SNatLE, SNatLE) -> InBounds - _ -> NotInBounds - -- | Generates a 'CalendarConfig' for the 'gatherUnitWB' or 'scatterUnitWB' genCalendarConfig :: forall bytes addressWidth calEntry maxDepth . @@ -210,8 +199,9 @@ genCalendarConfig sizeNat@(snatToNum -> dMax) = do , compareSNat d1 bsCalEntry) of (InBounds, InBounds, SNatLE, SNatLE)-> go depthA depthB (a,b,c,d) -> error $ "genCalendarConfig: calEntry constraints not satisfied: (" - <> show a <> ", " <> show b <> ", " <> show c <> ", " <> show d <> "), \n(depthA, depthB, maxDepth, calEntry bitsize) = (" - <> show depthA <> ", " <> show depthB <> ", " <> show sizeNat <> ", " <> show bsCalEntry <> ")" + <> show a <> ", " <> show b <> ", " <> show c <> ", " <> show d <> + "), \n(depthA, depthB, maxDepth, calEntry bitsize) = (" <> show depthA <> ", " + <> show depthB <> ", " <> show sizeNat <> ", " <> show bsCalEntry <> ")" where go :: forall depthA depthB . ( LessThan depthA maxDepth @@ -222,8 +212,9 @@ genCalendarConfig sizeNat@(snatToNum -> dMax) = do Gen (CalendarConfig bytes addressWidth (Index maxDepth)) go SNat SNat = do calActive <- fromMaybe errmsg . fromList @depthA . P.take (natToNum @depthA) - <$> Gen.shuffle @_ @(Index maxDepth) [0.. natToNum @(maxDepth-1)] - calShadow <- fromMaybe errmsg . fromList @depthB . P.take (natToNum @depthB) <$> Gen.shuffle @_ @(Index maxDepth) [0.. natToNum @(maxDepth-1)] + <$> Gen.shuffle @_ @(Index maxDepth) [0.. natToNum @(maxDepth-1)] + calShadow <- fromMaybe errmsg . fromList @depthB . P.take (natToNum @depthB) + <$> Gen.shuffle @_ @(Index maxDepth) [0.. natToNum @(maxDepth-1)] return $ CalendarConfig sizeNat calActive calShadow errmsg = errorX "genCalendarConfig: list to vector conversion failed" diff --git a/bittide/tests/Tests/Shared.hs b/bittide/tests/Tests/Shared.hs new file mode 100644 index 000000000..86654f22c --- /dev/null +++ b/bittide/tests/Tests/Shared.hs @@ -0,0 +1,20 @@ +-- SPDX-FileCopyrightText: 2022 Google LLC +-- +-- SPDX-License-Identifier: Apache-2.0 + +{-# LANGUAGE GADTs #-} +module Tests.Shared where + +import Clash.Prelude + +data IsInBounds a b c where + InBounds :: (a <= b, b <= c) => IsInBounds a b c + NotInBounds :: IsInBounds a b c + +deriving instance Show (IsInBounds a b c) + +-- | Returns 'InBounds' when a <= b <= c, otherwise returns 'NotInBounds'. +isInBounds :: SNat a -> SNat b -> SNat c -> IsInBounds a b c +isInBounds a b c = case (compareSNat a b, compareSNat b c) of + (SNatLE, SNatLE) -> InBounds + _ -> NotInBounds From 81ad567ea48138b7539fd2d3eef340286b61045f Mon Sep 17 00:00:00 2001 From: lmbollen Date: Mon, 27 Jun 2022 11:50:38 +0200 Subject: [PATCH 08/12] processed review comments --- bittide/src/Bittide/ScatterGather.hs | 17 +-- bittide/tests/Tests/ScatterGather.hs | 154 ++++++++++++++------------ contranomy/src/Contranomy/Wishbone.hs | 8 +- 3 files changed, 95 insertions(+), 84 deletions(-) diff --git a/bittide/src/Bittide/ScatterGather.hs b/bittide/src/Bittide/ScatterGather.hs index 5edda8e22..e2ec55dac 100644 --- a/bittide/src/Bittide/ScatterGather.hs +++ b/bittide/src/Bittide/ScatterGather.hs @@ -133,10 +133,10 @@ scatterGatherEngine bootCalS bootCalG scatterConfig gatherConfig $ Just <$> gatherOut --- | Doublebuffered memory component that can be written to by a Bittide link, write address --- of the incoming frame is determined by the scatterUnit's calendar. The buffers are swapped --- at the beginning of each metacycle. Reading the buffer is done by supplying a read address. --- Furthermore this component offers ports to control the incorporated calendar. +-- | Doublebuffered memory component that can be written to by a Bittide link. The write +-- address of the incoming frame is determined by the scatterUnit's calendar. The buffers +-- are swapped at the beginning of each metacycle. Reading the buffer is done by supplying +-- a read address. Furthermore this component offers ports to control the incorporated calendar. scatterUnit :: ( HiddenClockResetEnable dom , KnownNat memDepth @@ -161,10 +161,11 @@ scatterUnit initMem calConfig wbIn calSwitch linkIn readAddr = (readOut, wbOut) writeOp = (\a b -> (a,) <$> b) <$> writeAddr <*> linkIn readOut = doubleBufferedRam initMem metaCycle readAddr writeOp --- | Doublebuffered memory component that can be written to by a generic write operation, write address --- of the incoming frame is determined by the scatterUnit's calendar. The buffers are swapped --- at the beginning of each metacycle. Reading the buffer is done by supplying a read address. --- Furthermore this component offers ports to control the incorporated calendar. +-- | Doublebuffered memory component that can be written to by a generic write operation. The +-- write address of the incoming frame is determined by the scatterUnit's calendar. The +-- buffers are swapped at the beginning of each metacycle. Reading the buffer is done by +-- supplying a read address. Furthermore this component offers ports to control the +-- incorporated calendar. gatherUnit :: ( HiddenClockResetEnable dom , KnownNat memDepth diff --git a/bittide/tests/Tests/ScatterGather.hs b/bittide/tests/Tests/ScatterGather.hs index f2bbd448f..c7a4a1a92 100644 --- a/bittide/tests/Tests/ScatterGather.hs +++ b/bittide/tests/Tests/ScatterGather.hs @@ -224,38 +224,43 @@ scatterUnitNoFrameLoss = property $ do maxCalSize <- forAll $ Gen.enum 2 32 case TN.someNatVal (maxCalSize - 1) of SomeNat (succSNat . snatProxy -> p) -> do - calConfig <- forAll $ genCal p - case calConfig of - CalendarConfig _ calA@(length -> depth) _ -> do - -- Amount of metacycles of input to generate - metaCycles <- forAll $ Gen.enum 1 10 - let - -- reset cycle + cycle delay, last metacycle's writes can be read in (metacycles + 1) - simLength = 2 + (1+metaCycles) * depth - inputGen = Gen.list (Range.singleton metaCycles) - metaCycleNothing = P.replicate depth Nothing - -- Generate at most depth `div` 2 elements to be written each metacycle since - -- we need two cycles to read a written element. - metaCycleGen = genFrameList (Range.singleton $ depth `div` 2) - - inputFrames <- forAll $ padToLength (simLength `div` depth + 1) metaCycleNothing - <$> inputGen (padToLength depth Nothing <$> metaCycleGen) - let - topEntity (unbundle -> (wbIn, linkIn)) = fst $ - withClockResetEnable clockGen resetGen enableGen (scatterUnitWB @System @_ @32) - (deepErrorX "scatterUnit initial elements undefined") calConfig - (pure $ wishboneM2S SNat SNat) (pure False) linkIn wbIn - - wbReadOps = P.take simLength $ P.replicate depth idleM2S P.++ P.concat - (padToLength depth idleM2S . P.concat . P.zipWith wbRead (toList calA) <$> inputFrames) - - topEntityInput = P.zip wbReadOps (P.concat inputFrames) - simOut = simulateN simLength topEntity topEntityInput - footnote . fromString $ "simOut: " <> showX simOut - footnote . fromString $ "simIn: " <> showX wbReadOps - footnote . fromString $ "cal: " <> showX calA - wbDecoding simOut === P.take simLength (catMaybes (P.concat inputFrames)) + cal <- forAll $ genCal p + runTest cal where + runTest :: + (KnownNat maxSize, 1 <= maxSize) => + CalendarConfig 4 32 (Index maxSize) -> + PropertyT IO () + runTest calConfig@(CalendarConfig _ calA@(length -> depth) _) = do + -- Amount of metacycles of input to generate + metaCycles <- forAll $ Gen.enum 1 10 + let + -- reset cycle + cycle delay, last metacycle's writes can be read in (metacycles + 1) + simLength = 2 + (1+metaCycles) * depth + inputGen = Gen.list (Range.singleton metaCycles) + metaCycleNothing = P.replicate depth Nothing + -- Generate at most depth `div` 2 elements to be written each metacycle since + -- we need two cycles to read a written element. + metaCycleGen = genFrameList (Range.singleton $ depth `div` 2) + + inputFrames <- forAll $ padToLength (simLength `div` depth + 1) metaCycleNothing + <$> inputGen (padToLength depth Nothing <$> metaCycleGen) + let + topEntity (unbundle -> (wbIn, linkIn)) = fst $ + withClockResetEnable clockGen resetGen enableGen (scatterUnitWB @System @_ @32) + (deepErrorX "scatterUnit initial elements undefined") calConfig + (pure $ wishboneM2S SNat SNat) (pure False) linkIn wbIn + + wbReadOps = P.take simLength $ P.replicate depth idleM2S P.++ P.concat + (padToLength depth idleM2S . P.concat . P.zipWith wbRead (toList calA) <$> inputFrames) + + topEntityInput = P.zip wbReadOps (P.concat inputFrames) + simOut = simulateN simLength topEntity topEntityInput + footnote . fromString $ "simOut: " <> showX simOut + footnote . fromString $ "simIn: " <> showX wbReadOps + footnote . fromString $ "cal: " <> showX calA + wbDecoding simOut === P.take simLength (catMaybes (P.concat inputFrames)) + genCal :: forall maxSize . 1 <= maxSize => SNat maxSize -> Gen (CalendarConfig 4 32 (Index maxSize)) genCal SNat = genCalendarConfig @4 @32 (SNat @maxSize) padToLength l padElement g = P.take l (g P.++ P.repeat padElement) @@ -267,38 +272,43 @@ gatherUnitNoFrameLoss = property $ do case TN.someNatVal (maxCalSize - 1) of SomeNat (succSNat . snatProxy -> p) -> do calConfig <- forAll $ genCal p - case calConfig of - CalendarConfig _ calA@(length -> depth) _ -> do - metaCycles <- forAll $ Gen.enum 1 10 - let - simLength = 2 + (1+metaCycles) * depth - inputGen = Gen.list (Range.singleton metaCycles) - metaCycleNothing = P.replicate depth Nothing - metaCycleGen = genFrameList (Range.singleton $ depth `div` 2) - inputFrames <- forAll $ padToLength (simLength `div` depth + 1) metaCycleNothing - <$> inputGen (padToLength depth Nothing <$> metaCycleGen) - let - topEntity wbIn = (\ (a, _ ,_) -> a) $ - withClockResetEnable clockGen resetGen enableGen (gatherUnitWB @System @_ @32) - (deepErrorX "scatterUnit initial elements undefined") calConfig - (pure $ wishboneM2S SNat SNat) (pure False) wbIn - - wbWriteOps = P.take simLength . P.concat $ - padToLength depth idleM2S . P.concat . P.zipWith wbWrite (toList calA) <$> inputFrames - - simOut = simulateN simLength topEntity wbWriteOps - addressedFrames = P.zip (P.concat inputFrames) (cycle $ toList calA) - writtenFrames = [if snd e /= 0 then fst e else Nothing | e <- addressedFrames] - prePad = (P.replicate (1+depth) Nothing P.++) - expectedOutput = P.take simLength (fromMaybe 1 <$> P.filter isJust writtenFrames) - - footnote . fromString $ "simOut: " <> showX simOut - footnote . fromString $ "simIn: " <> showX wbWriteOps - footnote . fromString $ "cal: " <> showX calA - footnote . fromString $ "writtenFrames: " <> showX writtenFrames - - directedDecode (prePad writtenFrames) simOut === expectedOutput + runTest calConfig where + runTest :: + (KnownNat maxSize, 1 <= maxSize) => + CalendarConfig 4 32 (Index maxSize) -> + PropertyT IO () + runTest calConfig@(CalendarConfig _ calA@(length -> depth) _) = do + metaCycles <- forAll $ Gen.enum 1 10 + let + simLength = 2 + (1+metaCycles) * depth + inputGen = Gen.list (Range.singleton metaCycles) + metaCycleNothing = P.replicate depth Nothing + metaCycleGen = genFrameList (Range.singleton $ depth `div` 2) + inputFrames <- forAll $ padToLength (simLength `div` depth + 1) metaCycleNothing + <$> inputGen (padToLength depth Nothing <$> metaCycleGen) + let + topEntity wbIn = (\ (a, _ ,_) -> a) $ + withClockResetEnable clockGen resetGen enableGen (gatherUnitWB @System @_ @32) + (deepErrorX "scatterUnit initial elements undefined") calConfig + (pure $ wishboneM2S SNat SNat) (pure False) wbIn + + wbWriteOps = P.take simLength . P.concat $ + padToLength depth idleM2S . P.concat . P.zipWith wbWrite (toList calA) <$> inputFrames + + simOut = simulateN simLength topEntity wbWriteOps + addressedFrames = P.zip (P.concat inputFrames) (cycle $ toList calA) + writtenFrames = [if snd e /= 0 then fst e else Nothing | e <- addressedFrames] + prePad = (P.replicate (1+depth) Nothing P.++) + expectedOutput = P.take simLength (fromMaybe 1 <$> P.filter isJust writtenFrames) + + footnote . fromString $ "simOut: " <> showX simOut + footnote . fromString $ "simIn: " <> showX wbWriteOps + footnote . fromString $ "cal: " <> showX calA + footnote . fromString $ "writtenFrames: " <> showX writtenFrames + + directedDecode (prePad writtenFrames) simOut === expectedOutput + genCal :: forall maxSize . 1 <= maxSize => SNat maxSize -> @@ -336,15 +346,15 @@ wbRead :: Maybe a -> [WishboneM2S bytes addressWidth] wbRead readAddr (Just _) = - [(wishboneM2S SNat (SNat @addressWidth)) + [ (wishboneM2S SNat (SNat @addressWidth)) { addr = (`shiftL` 3) . resize $ pack readAddr , busCycle = True - , strobe = True} - , - (wishboneM2S SNat (SNat @addressWidth)) + , strobe = True } + + , (wishboneM2S SNat (SNat @addressWidth)) { addr = 4 .|. ((`shiftL` 3) . resize $ pack readAddr) , busCycle = True - , strobe = True} + , strobe = True } ] wbRead _ Nothing = [] @@ -360,21 +370,21 @@ wbWrite :: Maybe (BitVector (bytes*2*8)) -> [WishboneM2S bytes addressWidth] wbWrite writeAddr (Just frame) = - [(wishboneM2S @bytes @addressWidth SNat SNat) + [ (wishboneM2S @bytes @addressWidth SNat SNat) { addr = (`shiftL` 3) . resize $ pack writeAddr , busSelect = maxBound , busCycle = True , strobe = True , writeEnable = True - , writeData = lower} - , - (wishboneM2S @bytes @addressWidth SNat SNat) + , writeData = lower } + + , (wishboneM2S @bytes @addressWidth SNat SNat) { addr = 4 .|. ((`shiftL` 3) . resize $ pack writeAddr) , busSelect = maxBound , busCycle = True , strobe = True , writeEnable = True - , writeData = upper} + , writeData = upper } ] where (upper, lower) = split frame diff --git a/contranomy/src/Contranomy/Wishbone.hs b/contranomy/src/Contranomy/Wishbone.hs index 5eec275fc..dc609eab1 100644 --- a/contranomy/src/Contranomy/Wishbone.hs +++ b/contranomy/src/Contranomy/Wishbone.hs @@ -66,9 +66,9 @@ data BurstTypeExtension wishboneM2S :: SNat bytes -> SNat addressWidth -> WishboneM2S bytes addressWidth wishboneM2S SNat SNat = WishboneM2S - { addr = undefined - , writeData = undefined - , busSelect = undefined + { addr = deepErrorX "wishboneM2S: addr undefined." + , writeData = deepErrorX "wishboneM2S: writeData undefined." + , busSelect = deepErrorX "wishboneM2S: busSelect undefined." , busCycle = False , strobe = False , writeEnable = False @@ -79,7 +79,7 @@ wishboneM2S SNat SNat wishboneS2M :: SNat bytes -> WishboneS2M bytes wishboneS2M SNat = WishboneS2M - { readData = undefined + { readData = deepErrorX "wishboneM2S: readData undefined." , acknowledge = False , err = False } From da0eb2cf7ad807a5832db37ff6baa0e19ed12af7 Mon Sep 17 00:00:00 2001 From: lmbollen Date: Tue, 28 Jun 2022 14:20:53 +0200 Subject: [PATCH 09/12] Remove initial content from S/G units --- bittide/src/Bittide/ScatterGather.hs | 52 ++++++++++------------------ bittide/tests/Tests/ScatterGather.hs | 6 ++-- 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/bittide/src/Bittide/ScatterGather.hs b/bittide/src/Bittide/ScatterGather.hs index e2ec55dac..a322d9b2b 100644 --- a/bittide/src/Bittide/ScatterGather.hs +++ b/bittide/src/Bittide/ScatterGather.hs @@ -139,10 +139,8 @@ scatterGatherEngine bootCalS bootCalG scatterConfig gatherConfig -- a read address. Furthermore this component offers ports to control the incorporated calendar. scatterUnit :: ( HiddenClockResetEnable dom - , KnownNat memDepth + , KnownNat memDepth, 1 <= memDepth , KnownNat frameWidth) => - -- | Initial contents of both memory buffers. - Vec memDepth (BitVector frameWidth) -> -- | Configuration for the calendar. CalendarConfig bytes addressWidth (CalendarEntry memDepth) -> -- | Wishbone master-slave port for the calendar. @@ -155,11 +153,11 @@ scatterUnit :: Signal dom (Index memDepth) -> -- | (Data at read address delayed 1 cycle, Wishbone slave-master from calendar) (Signal dom (BitVector frameWidth), Signal dom (WishboneS2M bytes)) -scatterUnit initMem calConfig wbIn calSwitch linkIn readAddr = (readOut, wbOut) +scatterUnit calConfig wbIn calSwitch linkIn readAddr = (readOut, wbOut) where (writeAddr, metaCycle, wbOut) = mkCalendar calConfig calSwitch wbIn writeOp = (\a b -> (a,) <$> b) <$> writeAddr <*> linkIn - readOut = doubleBufferedRam initMem metaCycle readAddr writeOp + readOut = doubleBufferedRamU metaCycle readAddr writeOp -- | Doublebuffered memory component that can be written to by a generic write operation. The -- write address of the incoming frame is determined by the scatterUnit's calendar. The @@ -168,13 +166,9 @@ scatterUnit initMem calConfig wbIn calSwitch linkIn readAddr = (readOut, wbOut) -- incorporated calendar. gatherUnit :: ( HiddenClockResetEnable dom - , KnownNat memDepth - , KnownNat frameWidth - , 1 <= frameWidth - , KnownNat (DivRU frameWidth 8) - , 1 <= (DivRU frameWidth 8)) => - -- | Initial contents for both memory buffers - Vec memDepth (BitVector frameWidth) -> + , KnownNat memDepth, 1 <= memDepth + , KnownNat frameWidth, 1 <= frameWidth + , KnownNat (DivRU frameWidth 8), 1 <= (DivRU frameWidth 8)) => -- | Configuration for the calendar. CalendarConfig bytes addressWidth (CalendarEntry memDepth) -> -- | Wishbone master-slave port for the calendar. @@ -187,21 +181,19 @@ gatherUnit :: Signal dom (ByteEnable (DivRU frameWidth 8)) -> -- | (Transmitted frame to Bittide Link, Wishbone slave-master from calendar) (Signal dom (DataLink frameWidth), Signal dom (WishboneS2M bytes)) -gatherUnit initMem calConfig wbIn calSwitch writeOp byteEnables= (linkOut, wbOut) +gatherUnit calConfig wbIn calSwitch writeOp byteEnables= (linkOut, wbOut) where (readAddr, metaCycle, wbOut) = mkCalendar calConfig calSwitch wbIn linkOut = mux (register True $ (==0) <$> readAddr) (pure Nothing) $ Just <$> bramOut - bramOut = doubleBufferedRamByteAddressable initMem metaCycle readAddr writeOp byteEnables + bramOut = doubleBufferedRamByteAddressableU metaCycle readAddr writeOp byteEnables -- | Wishbone interface for the scatterUnit and gatherUnit. It makes the scatter and gather -- unit, which operate on 64 bit frames, addressable via a 32 bit wishbone bus. wbInterface :: forall bytes addressWidth addresses . ( KnownNat bytes - , KnownNat addresses - , 1 <= addresses - , KnownNat addressWidth - , 2 <= addressWidth) => + , KnownNat addresses, 1 <= addresses + , KnownNat addressWidth, 2 <= addressWidth) => -- | Maximum address of the respective memory element as seen from the wishbone side. Index addresses -> -- | Wishbone master - slave data. @@ -229,12 +221,8 @@ wbInterface addressRange WishboneM2S{..} readData = scatterUnitWB :: forall dom memDepth awSU bsCal awCal . ( HiddenClockResetEnable dom - , KnownNat memDepth - , 1 <= memDepth - , KnownNat awSU - , 2 <= awSU) => - -- | Initial contents of both memory buffers. - Vec memDepth (BitVector 64) -> + , KnownNat memDepth, 1 <= memDepth + , KnownNat awSU, 2 <= awSU) => -- | Configuration for the calendar. CalendarConfig bsCal awCal (CalendarEntry memDepth) -> -- | Wishbone master - slave data calendar. @@ -247,12 +235,12 @@ scatterUnitWB :: Signal dom (WishboneM2S 4 awSU) -> -- | (slave - master data scatterUnit , slave - master data calendar) (Signal dom (WishboneS2M 4), Signal dom (WishboneS2M bsCal)) -scatterUnitWB initMem calConfig wbInCal calSwitch linkIn wbInSU = +scatterUnitWB calConfig wbInCal calSwitch linkIn wbInSU = (delayControls wbOutSU, wbOutCal) where (wbOutSU, memAddr, _) = unbundle $ wbInterface maxBound <$> wbInSU <*> scatteredData (readAddr, upperSelected) = unbundle $ coerceIndexes <$> memAddr - (scatterUnitRead, wbOutCal) = scatterUnit initMem calConfig wbInCal calSwitch linkIn readAddr + (scatterUnitRead, wbOutCal) = scatterUnit calConfig wbInCal calSwitch linkIn readAddr (upper, lower) = unbundle $ split <$> scatterUnitRead selected = register (errorX "scatterUnitWb: Initial selection undefined") upperSelected scatteredData = mux selected upper lower @@ -263,12 +251,8 @@ scatterUnitWB initMem calConfig wbInCal calSwitch linkIn wbInSU = gatherUnitWB :: forall dom memDepth awSU bsCal awCal . ( HiddenClockResetEnable dom - , KnownNat memDepth - , 1 <= memDepth - , KnownNat awSU - , 2 <= awSU) => - -- | Initial contents of both memory buffers. - Vec memDepth (BitVector 64) -> + , KnownNat memDepth, 1 <= memDepth + , KnownNat awSU, 2 <= awSU) => -- | Configuration for the calendar. CalendarConfig bsCal awCal (CalendarEntry memDepth) -> -- | Wishbone master - slave data calendar. @@ -279,13 +263,13 @@ gatherUnitWB :: Signal dom (WishboneM2S 4 awSU) -> -- | (slave - master data gatherUnit , slave - master data calendar) (Signal dom (DataLink 64), Signal dom (WishboneS2M 4), Signal dom (WishboneS2M bsCal)) -gatherUnitWB initMem calConfig wbInCal calSwitch wbInSU = +gatherUnitWB calConfig wbInCal calSwitch wbInSU = (linkOut, delayControls wbOutSU, wbOutCal) where (wbOutSU, memAddr, writeOp) = unbundle $ wbInterface maxBound <$> wbInSU <*> pure 0b0 (writeAddr, upperSelected) = unbundle $ coerceIndexes <$> memAddr (linkOut, wbOutCal) = - gatherUnit initMem calConfig wbInCal calSwitch gatherWrite gatherByteEnables + gatherUnit calConfig wbInCal calSwitch gatherWrite gatherByteEnables gatherWrite = mkWrite <$> writeAddr <*> writeOp gatherByteEnables = mkEnables <$> upperSelected <*> (busSelect <$> wbInSU) diff --git a/bittide/tests/Tests/ScatterGather.hs b/bittide/tests/Tests/ScatterGather.hs index c7a4a1a92..3552c1447 100644 --- a/bittide/tests/Tests/ScatterGather.hs +++ b/bittide/tests/Tests/ScatterGather.hs @@ -248,8 +248,7 @@ scatterUnitNoFrameLoss = property $ do let topEntity (unbundle -> (wbIn, linkIn)) = fst $ withClockResetEnable clockGen resetGen enableGen (scatterUnitWB @System @_ @32) - (deepErrorX "scatterUnit initial elements undefined") calConfig - (pure $ wishboneM2S SNat SNat) (pure False) linkIn wbIn + calConfig (pure $ wishboneM2S SNat SNat) (pure False) linkIn wbIn wbReadOps = P.take simLength $ P.replicate depth idleM2S P.++ P.concat (padToLength depth idleM2S . P.concat . P.zipWith wbRead (toList calA) <$> inputFrames) @@ -290,8 +289,7 @@ gatherUnitNoFrameLoss = property $ do let topEntity wbIn = (\ (a, _ ,_) -> a) $ withClockResetEnable clockGen resetGen enableGen (gatherUnitWB @System @_ @32) - (deepErrorX "scatterUnit initial elements undefined") calConfig - (pure $ wishboneM2S SNat SNat) (pure False) wbIn + calConfig (pure $ wishboneM2S SNat SNat) (pure False) wbIn wbWriteOps = P.take simLength . P.concat $ padToLength depth idleM2S . P.concat . P.zipWith wbWrite (toList calA) <$> inputFrames From 1ddeea56fe9cc362a8e39cbb0180c014ef7c10fa Mon Sep 17 00:00:00 2001 From: lmbollen Date: Tue, 28 Jun 2022 14:24:04 +0200 Subject: [PATCH 10/12] Spelling fixes --- bittide/src/Bittide/ScatterGather.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bittide/src/Bittide/ScatterGather.hs b/bittide/src/Bittide/ScatterGather.hs index a322d9b2b..24c297a14 100644 --- a/bittide/src/Bittide/ScatterGather.hs +++ b/bittide/src/Bittide/ScatterGather.hs @@ -81,7 +81,7 @@ combineFrameWithAddr frameIn writeAddr = combine <$> frameIn <*> writeAddr combine frame addr = fmap (addr,) frame -- | scatterGatherEngine is a 4 port memory component that enables gathering and scattering for the processing element. --- Scattering and gathering data is done using two seperate memory banks with each their own calendar, +-- Scattering and gathering data is done using two separate memory banks with each their own calendar, -- the writeAddrSwitch dictate the read and write address on the switch side for the gather and scatter memory respectively. -- If the read address for the scatter engine is 0, a null frame (Nothing) will be sent to the switch. scatterGatherEngine :: @@ -133,7 +133,7 @@ scatterGatherEngine bootCalS bootCalG scatterConfig gatherConfig $ Just <$> gatherOut --- | Doublebuffered memory component that can be written to by a Bittide link. The write +-- | Double buffered memory component that can be written to by a Bittide link. The write -- address of the incoming frame is determined by the scatterUnit's calendar. The buffers -- are swapped at the beginning of each metacycle. Reading the buffer is done by supplying -- a read address. Furthermore this component offers ports to control the incorporated calendar. @@ -159,7 +159,7 @@ scatterUnit calConfig wbIn calSwitch linkIn readAddr = (readOut, wbOut) writeOp = (\a b -> (a,) <$> b) <$> writeAddr <*> linkIn readOut = doubleBufferedRamU metaCycle readAddr writeOp --- | Doublebuffered memory component that can be written to by a generic write operation. The +-- | Double buffered memory component that can be written to by a generic write operation. The -- write address of the incoming frame is determined by the scatterUnit's calendar. The -- buffers are swapped at the beginning of each metacycle. Reading the buffer is done by -- supplying a read address. Furthermore this component offers ports to control the @@ -279,7 +279,7 @@ gatherUnitWB calConfig wbInCal calSwitch wbInSU = | selected = byteEnables ++# 0b0 | otherwise = 0b0 ++# byteEnables --- | Coerces an index of size (n*2) to index n with the lower bit as seperate boolean. +-- | Coerces an index of size (n*2) to index n with the lower bit as separate boolean. coerceIndexes :: forall n . (KnownNat n, 1 <= n) => (Index (n*2) -> (Index n, Bool)) coerceIndexes = case sameNat natA natB of Just Refl -> bitCoerce From 8595c53dea3fe1e8796fb06584a56da3a8dfaddf Mon Sep 17 00:00:00 2001 From: lmbollen Date: Tue, 5 Jul 2022 13:27:55 +0200 Subject: [PATCH 11/12] Applied acronym naming convention --- bittide/src/Bittide/ScatterGather.hs | 14 +++++++------- bittide/tests/Tests/ScatterGather.hs | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bittide/src/Bittide/ScatterGather.hs b/bittide/src/Bittide/ScatterGather.hs index 24c297a14..9074765b9 100644 --- a/bittide/src/Bittide/ScatterGather.hs +++ b/bittide/src/Bittide/ScatterGather.hs @@ -9,8 +9,8 @@ module Bittide.ScatterGather ( scatterEngine , gatherEngine , scatterGatherEngine - , scatterUnitWB - , gatherUnitWB) where + , scatterUnitWb + , gatherUnitWb) where import Clash.Prelude @@ -218,7 +218,7 @@ wbInterface addressRange WishboneM2S{..} readData = -- | Wishbone addressable scatterUnit, the wishbone port can read the data from this -- memory element as if it has a 32 bit port by selecting the upper 32 or lower 32 bits -- of the read data. -scatterUnitWB :: +scatterUnitWb :: forall dom memDepth awSU bsCal awCal . ( HiddenClockResetEnable dom , KnownNat memDepth, 1 <= memDepth @@ -235,7 +235,7 @@ scatterUnitWB :: Signal dom (WishboneM2S 4 awSU) -> -- | (slave - master data scatterUnit , slave - master data calendar) (Signal dom (WishboneS2M 4), Signal dom (WishboneS2M bsCal)) -scatterUnitWB calConfig wbInCal calSwitch linkIn wbInSU = +scatterUnitWb calConfig wbInCal calSwitch linkIn wbInSU = (delayControls wbOutSU, wbOutCal) where (wbOutSU, memAddr, _) = unbundle $ wbInterface maxBound <$> wbInSU <*> scatteredData @@ -248,7 +248,7 @@ scatterUnitWB calConfig wbInCal calSwitch linkIn wbInSU = -- | Wishbone addressable gatherUnit, the wishbone port can write data to this -- memory element as if it has a 32 bit port by controlling the byte enables of the -- gatherUnit based on the third bit. -gatherUnitWB :: +gatherUnitWb :: forall dom memDepth awSU bsCal awCal . ( HiddenClockResetEnable dom , KnownNat memDepth, 1 <= memDepth @@ -263,7 +263,7 @@ gatherUnitWB :: Signal dom (WishboneM2S 4 awSU) -> -- | (slave - master data gatherUnit , slave - master data calendar) (Signal dom (DataLink 64), Signal dom (WishboneS2M 4), Signal dom (WishboneS2M bsCal)) -gatherUnitWB calConfig wbInCal calSwitch wbInSU = +gatherUnitWb calConfig wbInCal calSwitch wbInSU = (linkOut, delayControls wbOutSU, wbOutCal) where (wbOutSU, memAddr, writeOp) = unbundle $ wbInterface maxBound <$> wbInSU <*> pure 0b0 @@ -283,7 +283,7 @@ gatherUnitWB calConfig wbInCal calSwitch wbInSU = coerceIndexes :: forall n . (KnownNat n, 1 <= n) => (Index (n*2) -> (Index n, Bool)) coerceIndexes = case sameNat natA natB of Just Refl -> bitCoerce - _ -> error "gatherUnitWB: Index coercion failed." + _ -> error "gatherUnitWb: Index coercion failed." where natA = Proxy @(CLog 2 (n*2)) natB = Proxy @(1 + (CLog 2 n)) diff --git a/bittide/tests/Tests/ScatterGather.hs b/bittide/tests/Tests/ScatterGather.hs index 3552c1447..3be24a3bc 100644 --- a/bittide/tests/Tests/ScatterGather.hs +++ b/bittide/tests/Tests/ScatterGather.hs @@ -172,7 +172,7 @@ scatterGatherNoFrameLoss = property $ do -- TODO: Remove instance once Clash.Prelude has it. deriving instance Show (SNatLE a b) --- | Generates a 'CalendarConfig' for the 'gatherUnitWB' or 'scatterUnitWB' +-- | Generates a 'CalendarConfig' for the 'gatherUnitWb' or 'scatterUnitWb' genCalendarConfig :: forall bytes addressWidth calEntry maxDepth . ( KnownNat bytes @@ -247,7 +247,7 @@ scatterUnitNoFrameLoss = property $ do <$> inputGen (padToLength depth Nothing <$> metaCycleGen) let topEntity (unbundle -> (wbIn, linkIn)) = fst $ - withClockResetEnable clockGen resetGen enableGen (scatterUnitWB @System @_ @32) + withClockResetEnable clockGen resetGen enableGen (scatterUnitWb @System @_ @32) calConfig (pure $ wishboneM2S SNat SNat) (pure False) linkIn wbIn wbReadOps = P.take simLength $ P.replicate depth idleM2S P.++ P.concat @@ -288,7 +288,7 @@ gatherUnitNoFrameLoss = property $ do <$> inputGen (padToLength depth Nothing <$> metaCycleGen) let topEntity wbIn = (\ (a, _ ,_) -> a) $ - withClockResetEnable clockGen resetGen enableGen (gatherUnitWB @System @_ @32) + withClockResetEnable clockGen resetGen enableGen (gatherUnitWb @System @_ @32) calConfig (pure $ wishboneM2S SNat SNat) (pure False) wbIn wbWriteOps = P.take simLength . P.concat $ @@ -332,7 +332,7 @@ wbDecoding (s2m0 : s2m1 : s2ms) wbDecoding _ = [] -- | Tranform a read address with expected frame into a wishbone read operation for testing --- the 'scatterUnitWB'. The second argument indicate wether or not a frame can be read from +-- the 'scatterUnitWb'. The second argument indicate wether or not a frame can be read from -- that read address. The read operation reads data over 2 read cycles. wbRead :: forall bytes addressWidth maxIndex a . @@ -357,7 +357,7 @@ wbRead readAddr (Just _) = wbRead _ Nothing = [] -- | Transform a write address with frame to a wishbone write operation for testing the --- 'gatherUnitWB'. The write operation writes the incoming bitvector over 2 write cycles. +-- 'gatherUnitWb'. The write operation writes the incoming bitvector over 2 write cycles. wbWrite :: forall bytes addressWidth maxIndex . ( KnownNat bytes From 719abf27ddb56ae9635a6f3554cdc358b3589d94 Mon Sep 17 00:00:00 2001 From: lmbollen Date: Mon, 11 Jul 2022 09:54:00 +0200 Subject: [PATCH 12/12] Processed review feedback --- bittide/bittide.cabal | 9 +- bittide/src/Bittide/ScatterGather.hs | 160 ++++++----------------- bittide/src/Bittide/Switch.hs | 2 + bittide/src/Data/Constraint/Nat/Extra.hs | 5 + bittide/tests/Tests/ScatterGather.hs | 158 ++++------------------ 5 files changed, 77 insertions(+), 257 deletions(-) diff --git a/bittide/bittide.cabal b/bittide/bittide.cabal index 4523514ec..467943e54 100644 --- a/bittide/bittide.cabal +++ b/bittide/bittide.cabal @@ -63,13 +63,14 @@ common common-options Cabal, array, -- clash-prelude will set suitable version bounds for the plugins + clash-lib >= 1.6.3 && < 1.7, clash-prelude >= 1.6.3 && < 1.7, - ghc-typelits-natnormalise, - ghc-typelits-extra, - ghc-typelits-knownnat, + constraints >= 0.13.3 && < 0.15, containers >= 0.4.0 && < 0.7, contranomy, - constraints >= 0.13.3 && < 0.15 + ghc-typelits-extra, + ghc-typelits-knownnat, + ghc-typelits-natnormalise, library import: common-options hs-source-dirs: src diff --git a/bittide/src/Bittide/ScatterGather.hs b/bittide/src/Bittide/ScatterGather.hs index 9074765b9..acb6324fe 100644 --- a/bittide/src/Bittide/ScatterGather.hs +++ b/bittide/src/Bittide/ScatterGather.hs @@ -5,33 +5,27 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} + module Bittide.ScatterGather ( scatterEngine - , gatherEngine - , scatterGatherEngine , scatterUnitWb - , gatherUnitWb) where + , gatherUnitWb + ) where import Clash.Prelude import Contranomy.Wishbone -import Data.Proxy import Data.Type.Equality ((:~:)(Refl)) import Bittide.Calendar import Bittide.DoubleBufferedRam import Bittide.SharedTypes +import Data.Constraint.Nat.Extra -- | Calendar entry that can be used by a scatter or gather engine. type CalendarEntry memDepth = Index memDepth --- | The Calendar for the scatter and gather engines contains entries of a single index --- to the engine. For more information regarding calendar datatypes see --- see NOTE [component calendar types] -type Calendar calDepth memDepth = Vec calDepth (CalendarEntry memDepth) - --- | Write operation for a scatter or gather calendar. -type ConfigurationPort calDepth memDepth = Maybe (Index calDepth, CalendarEntry memDepth) +-- TODO: scatterEngine should be removed after merging #71, which removes scatterEngine -- | scatterEngine is a memory bank that allows for random writes and sequentially -- reads data based on an internal counter that runs up to the maximum index, then wraps around. @@ -52,25 +46,6 @@ scatterEngine newMetaCycle frameIn writeAddr = readAddr = register 0 $ satSucc SatWrap <$> readAddr writeFrame = combineFrameWithAddr frameIn writeAddr --- | gatherEngine is a memory bank that allows for random reads and sequentially --- writes data based on an internal counter that runs up to the maximum index and wraps around. --- The initial contents are undefined. -gatherEngine :: - (HiddenClockResetEnable dom, KnownNat memDepth, 1 <= memDepth, NFDataX a) => - -- | Indicates when a new metacycle has started. - Signal dom Bool -> - -- | Incoming frame from link, if it contains Just a, a will be written to the memory. - Signal dom (Maybe a) -> - -- | Read address. - Signal dom (Index memDepth) -> - -- | Data at the read address, delayed by one clock cycle. - Signal dom a -gatherEngine newMetaCycle frameIn readAddr = - doubleBufferedRamU newMetaCycle readAddr writeFrame - where - writeAddr = register 0 $ satSucc SatWrap <$> writeAddr - writeFrame = combineFrameWithAddr frameIn writeAddr - combineFrameWithAddr :: Signal dom (Maybe dat) -> Signal dom addr -> @@ -80,70 +55,17 @@ combineFrameWithAddr frameIn writeAddr = combine <$> frameIn <*> writeAddr combine :: Maybe dat -> addr -> Maybe (addr,dat) combine frame addr = fmap (addr,) frame --- | scatterGatherEngine is a 4 port memory component that enables gathering and scattering for the processing element. --- Scattering and gathering data is done using two separate memory banks with each their own calendar, --- the writeAddrSwitch dictate the read and write address on the switch side for the gather and scatter memory respectively. --- If the read address for the scatter engine is 0, a null frame (Nothing) will be sent to the switch. -scatterGatherEngine :: - forall dom calDepthG calDepthS memDepthG memDepthS frameWidth . - ( HiddenClockResetEnable dom - , KnownNat calDepthG - , KnownNat calDepthS - , KnownNat memDepthG - , KnownNat memDepthS - , KnownNat frameWidth - , 1 <= calDepthG - , 1 <= calDepthS - , 1 <= memDepthG - , 1 <= memDepthS) => - -- | Bootstrap calendar gather memory. - Calendar calDepthS memDepthS -> - -- | Bootstrap calendar scatter memory. - Calendar calDepthG memDepthG -> - -- | Configuration port for the scatter engine. - Signal dom (ConfigurationPort calDepthS memDepthS) -> - -- | Configuration port for the gather engine. - Signal dom (ConfigurationPort calDepthG memDepthG) -> - -- | Incoming frame from the switch - Signal dom (DataLink frameWidth) -> - -- | Incoming data from the PE. - Signal dom (DataLink frameWidth) -> - -- | Read address of the PE frame. - Signal dom (Index memDepthS) -> - -- | Write address for the PE link. - Signal dom (Index memDepthG) -> - -- | Frame read by the switch and the data read by the PE. - (Signal dom (BitVector frameWidth),Signal dom (DataLink frameWidth)) -scatterGatherEngine bootCalS bootCalG scatterConfig gatherConfig - frameInSwitch frameInPE readAddrPE writeAddrPE = (toPE, toSwitch) - where - -- Scatter engine - frameInSwitch' = mux ((==0) <$> writeAddrSwitch) (pure Nothing) frameInSwitch - (writeAddrSwitch, newMetaCycleS) = calendar bootCalS (pure False) scatterConfig - writeFrameSwitch = (\f a -> fmap (a,) f) <$> frameInSwitch' <*> writeAddrSwitch - scatterOut = doubleBufferedRamU newMetaCycleS readAddrPE writeFrameSwitch - toPE = scatterOut - - -- Gather engine - frameInPE' = mux ((==0) <$> writeAddrPE) (pure Nothing) frameInPE - (readAddrSwitch, newMetaCycleG) = calendar bootCalG (pure False) gatherConfig - writeFramePE = (\f a -> fmap (a,) f) <$> frameInPE' <*> writeAddrPE - gatherOut = doubleBufferedRamU newMetaCycleG readAddrSwitch writeFramePE - toSwitch = mux (register True $ (==0) <$> readAddrSwitch) (pure Nothing) - $ Just <$> gatherOut - - -- | Double buffered memory component that can be written to by a Bittide link. The write --- address of the incoming frame is determined by the scatterUnit's calendar. The buffers +-- address of the incoming frame is determined by the incorporated 'calendarWB'. The buffers -- are swapped at the beginning of each metacycle. Reading the buffer is done by supplying --- a read address. Furthermore this component offers ports to control the incorporated calendar. +-- a read address. Furthermore this component offers ports to control the incorporated 'calendarWB'. scatterUnit :: ( HiddenClockResetEnable dom , KnownNat memDepth, 1 <= memDepth , KnownNat frameWidth) => - -- | Configuration for the calendar. + -- | Configuration for the 'calendarWB'. CalendarConfig bytes addressWidth (CalendarEntry memDepth) -> - -- | Wishbone master-slave port for the calendar. + -- | Wishbone (master -> slave) port for the 'calendarWB'. Signal dom (WishboneM2S bytes addressWidth) -> -- | Swap active calendar and shadow calendar. Signal dom Bool -> @@ -151,7 +73,7 @@ scatterUnit :: Signal dom (DataLink frameWidth) -> -- | Read address. Signal dom (Index memDepth) -> - -- | (Data at read address delayed 1 cycle, Wishbone slave-master from calendar) + -- | (Data at read address delayed 1 cycle, Wishbone (slave -> master) from 'calendarWB') (Signal dom (BitVector frameWidth), Signal dom (WishboneS2M bytes)) scatterUnit calConfig wbIn calSwitch linkIn readAddr = (readOut, wbOut) where @@ -160,34 +82,34 @@ scatterUnit calConfig wbIn calSwitch linkIn readAddr = (readOut, wbOut) readOut = doubleBufferedRamU metaCycle readAddr writeOp -- | Double buffered memory component that can be written to by a generic write operation. The --- write address of the incoming frame is determined by the scatterUnit's calendar. The +-- write address of the incoming frame is determined by the incorporated 'calendarWB'. The -- buffers are swapped at the beginning of each metacycle. Reading the buffer is done by -- supplying a read address. Furthermore this component offers ports to control the --- incorporated calendar. +-- incorporated 'calendarWB'. gatherUnit :: ( HiddenClockResetEnable dom , KnownNat memDepth, 1 <= memDepth , KnownNat frameWidth, 1 <= frameWidth , KnownNat (DivRU frameWidth 8), 1 <= (DivRU frameWidth 8)) => - -- | Configuration for the calendar. + -- | Configuration for the 'calendarWB'. CalendarConfig bytes addressWidth (CalendarEntry memDepth) -> - -- | Wishbone master-slave port for the calendar. + -- | Wishbone (master -> slave) port for the 'calendarWB'. Signal dom (WishboneM2S bytes addressWidth) -> -- | Swap active calendar and shadow calendar. Signal dom Bool -> - -- | Generic write operation writing a frame. + -- | Write operation writing a frame. Signal dom (Maybe (LocatedBits memDepth frameWidth)) -> -- | Byte enable for write operation. Signal dom (ByteEnable (DivRU frameWidth 8)) -> - -- | (Transmitted frame to Bittide Link, Wishbone slave-master from calendar) + -- | (Transmitted frame to Bittide Link, Wishbone (slave -> master) from 'calendarWB') (Signal dom (DataLink frameWidth), Signal dom (WishboneS2M bytes)) gatherUnit calConfig wbIn calSwitch writeOp byteEnables= (linkOut, wbOut) where (readAddr, metaCycle, wbOut) = mkCalendar calConfig calSwitch wbIn - linkOut = mux (register True $ (==0) <$> readAddr) (pure Nothing) $ Just <$> bramOut + linkOut = mux (register True ((==0) <$> readAddr)) (pure Nothing) (Just <$> bramOut) bramOut = doubleBufferedRamByteAddressableU metaCycle readAddr writeOp byteEnables --- | Wishbone interface for the scatterUnit and gatherUnit. It makes the scatter and gather +-- | Wishbone interface for the 'scatterUnit' and 'gatherUnit'. It makes the scatter and gather -- unit, which operate on 64 bit frames, addressable via a 32 bit wishbone bus. wbInterface :: forall bytes addressWidth addresses . @@ -196,9 +118,9 @@ wbInterface :: , KnownNat addressWidth, 2 <= addressWidth) => -- | Maximum address of the respective memory element as seen from the wishbone side. Index addresses -> - -- | Wishbone master - slave data. + -- | Wishbone (master -> slave) data. WishboneM2S bytes addressWidth -> - -- | Read data to be send to over the slave-master port. + -- | Read data to be send to over the (slave -> master) port. Bytes bytes -> -- | (slave - master data, read address memory element, write data memory element) (WishboneS2M bytes, Index addresses, Maybe (Bytes bytes)) @@ -207,7 +129,7 @@ wbInterface addressRange WishboneM2S{..} readData = where masterActive = strobe && busCycle (alignedAddress, alignment) = split @_ @(addressWidth - 2) @2 addr - wordAligned = alignment == (0 :: BitVector 2) + wordAligned = alignment == 0 err = masterActive && ((alignedAddress > resize (pack addressRange)) || not wordAligned) acknowledge = masterActive && not err wbAddr = unpack . resize $ pack alignedAddress @@ -215,7 +137,7 @@ wbInterface addressRange WishboneM2S{..} readData = writeOp | strobe && writeEnable && not err = Just writeData | otherwise = Nothing --- | Wishbone addressable scatterUnit, the wishbone port can read the data from this +-- | Wishbone addressable 'scatterUnit', the wishbone port can read the data from this -- memory element as if it has a 32 bit port by selecting the upper 32 or lower 32 bits -- of the read data. scatterUnitWb :: @@ -223,70 +145,68 @@ scatterUnitWb :: ( HiddenClockResetEnable dom , KnownNat memDepth, 1 <= memDepth , KnownNat awSU, 2 <= awSU) => - -- | Configuration for the calendar. + -- | Configuration for the 'calendarWB'. CalendarConfig bsCal awCal (CalendarEntry memDepth) -> - -- | Wishbone master - slave data calendar. + -- | Wishbone (master -> slave) port 'calendarWB'. Signal dom (WishboneM2S bsCal awCal) -> -- | Swap active calendar and shadow calendar. Signal dom Bool -> -- | Incoming frame from Bittide link. Signal dom (DataLink 64) -> - -- | Wishbone master-slave port scatterUnit. + -- | Wishbone (master -> slave) port scatter memory. Signal dom (WishboneM2S 4 awSU) -> - -- | (slave - master data scatterUnit , slave - master data calendar) + -- | (Wishbone (slave -> master) port scatter memory, Wishbone (slave -> master) port 'calendarWB') (Signal dom (WishboneS2M 4), Signal dom (WishboneS2M bsCal)) scatterUnitWb calConfig wbInCal calSwitch linkIn wbInSU = (delayControls wbOutSU, wbOutCal) where (wbOutSU, memAddr, _) = unbundle $ wbInterface maxBound <$> wbInSU <*> scatteredData - (readAddr, upperSelected) = unbundle $ coerceIndexes <$> memAddr + (readAddr, upperSelected) = unbundle $ coerceIndices <$> memAddr (scatterUnitRead, wbOutCal) = scatterUnit calConfig wbInCal calSwitch linkIn readAddr (upper, lower) = unbundle $ split <$> scatterUnitRead selected = register (errorX "scatterUnitWb: Initial selection undefined") upperSelected scatteredData = mux selected upper lower --- | Wishbone addressable gatherUnit, the wishbone port can write data to this +-- | Wishbone addressable 'gatherUnit', the wishbone port can write data to this -- memory element as if it has a 32 bit port by controlling the byte enables of the --- gatherUnit based on the third bit. +-- 'gatherUnit' based on the third bit. gatherUnitWb :: forall dom memDepth awSU bsCal awCal . ( HiddenClockResetEnable dom , KnownNat memDepth, 1 <= memDepth , KnownNat awSU, 2 <= awSU) => - -- | Configuration for the calendar. + -- | Configuration for the 'calendarWB'. CalendarConfig bsCal awCal (CalendarEntry memDepth) -> - -- | Wishbone master - slave data calendar. + -- | Wishbone (master -> slave) data 'calendarWB'. Signal dom (WishboneM2S bsCal awCal) -> -- | Swap active calendar and shadow calendar. Signal dom Bool -> - -- | Wishbone master-slave port gatherUnit. + -- | Wishbone (master -> slave) port gather memory. Signal dom (WishboneM2S 4 awSU) -> - -- | (slave - master data gatherUnit , slave - master data calendar) + -- | (Wishbone (slave -> master) port gather memory, Wishbone (slave -> master) port 'calendarWB') (Signal dom (DataLink 64), Signal dom (WishboneS2M 4), Signal dom (WishboneS2M bsCal)) gatherUnitWb calConfig wbInCal calSwitch wbInSU = (linkOut, delayControls wbOutSU, wbOutCal) where (wbOutSU, memAddr, writeOp) = unbundle $ wbInterface maxBound <$> wbInSU <*> pure 0b0 - (writeAddr, upperSelected) = unbundle $ coerceIndexes <$> memAddr + (writeAddr, upperSelected) = unbundle $ coerceIndices <$> memAddr (linkOut, wbOutCal) = gatherUnit calConfig wbInCal calSwitch gatherWrite gatherByteEnables gatherWrite = mkWrite <$> writeAddr <*> writeOp gatherByteEnables = mkEnables <$> upperSelected <*> (busSelect <$> wbInSU) + -- We update the 64 bit entry of the 'gatherUnit' in chunks of 32 bits. Thus we repeat + -- the writeData of the wishbone bus twice in the write operation to the 'gatherUnit' and + -- use the byte enables to either update the upper or lower mkWrite address (Just write) = Just (address, write ++# write) mkWrite _ _ = Nothing mkEnables selected byteEnables | selected = byteEnables ++# 0b0 | otherwise = 0b0 ++# byteEnables --- | Coerces an index of size (n*2) to index n with the lower bit as separate boolean. -coerceIndexes :: forall n . (KnownNat n, 1 <= n) => (Index (n*2) -> (Index n, Bool)) -coerceIndexes = case sameNat natA natB of - Just Refl -> bitCoerce - _ -> error "gatherUnitWb: Index coercion failed." - where - natA = Proxy @(CLog 2 (n*2)) - natB = Proxy @(1 + (CLog 2 n)) +-- | Coerces an index of size (n*2) to index n with the LSB as separate boolean. +coerceIndices :: forall n. (KnownNat n, 1 <= n) => Index (n*2) -> (Index n, Bool) +coerceIndices = case clog2axiom @n of Refl -> bitCoerce -- | Delays the output controls to align them with the actual read / write timing. delayControls :: HiddenClockResetEnable dom => diff --git a/bittide/src/Bittide/Switch.hs b/bittide/src/Bittide/Switch.hs index 64307cd2d..4463484a1 100644 --- a/bittide/src/Bittide/Switch.hs +++ b/bittide/src/Bittide/Switch.hs @@ -18,6 +18,8 @@ type CrossbarIndex links = Index (links+1) -- memory and a crossbar index to select the outgoing frame. type CalendarEntry links memDepth = Vec links (Index memDepth, CrossbarIndex links) +-- TODO: Remove Bittide.ScatterEngine and its tests before merging #71 + -- | The Bittide Switch routes data from incoming to outgoing links based on a calendar. -- The switch consists of a crossbar, a calendar and a scatter engine for all incoming links. -- The crossbar selects one of the scatter engine outputs for every outgoing link, index 0 diff --git a/bittide/src/Data/Constraint/Nat/Extra.hs b/bittide/src/Data/Constraint/Nat/Extra.hs index b095c9dc5..64661d4ef 100644 --- a/bittide/src/Data/Constraint/Nat/Extra.hs +++ b/bittide/src/Data/Constraint/Nat/Extra.hs @@ -16,9 +16,14 @@ solved by the constraint solver. module Data.Constraint.Nat.Extra where import Data.Constraint +import Data.Type.Equality +import GHC.TypeLits.Extra import GHC.TypeNats import Unsafe.Coerce -- | b <= ceiling(b/a)*a timesDivRU :: forall a b . Dict (b <= (Div (b + (a - 1)) a * a)) timesDivRU = unsafeCoerce (Dict :: Dict ()) + +clog2axiom :: CLog 2 (n * 2) :~: (CLog 2 n + 1) +clog2axiom = unsafeCoerce Refl diff --git a/bittide/tests/Tests/ScatterGather.hs b/bittide/tests/Tests/ScatterGather.hs index 3be24a3bc..1111fbe7d 100644 --- a/bittide/tests/Tests/ScatterGather.hs +++ b/bittide/tests/Tests/ScatterGather.hs @@ -16,16 +16,13 @@ module Tests.ScatterGather(sgGroup) where import Clash.Prelude hiding (fromList) import qualified Prelude as P -import Clash.Sized.Internal.BitVector -import Clash.Sized.Vector ( unsafeFromList, fromList) +import Clash.Sized.Vector (fromList) import Contranomy.Wishbone -import Data.Bifunctor import Data.String import Hedgehog import Test.Tasty import Test.Tasty.Hedgehog -import qualified Data.List as L -import qualified Data.Set as Set +import qualified Clash.Util.Interpolate as I import qualified GHC.TypeNats as TN import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range @@ -36,14 +33,6 @@ import Data.Maybe import Bittide.SharedTypes import Tests.Shared -isUndefined :: forall n . KnownNat n => BitVector n -> Bool -isUndefined (BV mask _) = mask == full - where full = 1 `shiftL` (natToNum @n @Int) - 1 - -maybeIsUndefined :: (KnownNat n, 1 <= n) => BitVector n -> Maybe (BitVector n) -maybeIsUndefined v | isUndefined v = Nothing - | otherwise = Just v - -- | The extra in SomeCalendar extra defines the minimum amount of elements in the vector -- and the minimum addressable indexes in the vector elements. I.e, vectors of 0 elements -- and Index 0 as element are not allowed. @@ -53,16 +42,6 @@ data SomeCalendar extra where instance Show (SomeCalendar extra) where show (SomeCalendar SNat list) = show list --- | Returns a calendar for a engine with a memory depth equal to the calendar depth, --- the addresses in the calendar are all unique. -genSomeCalendar :: Gen (SomeCalendar 1) -genSomeCalendar = do - calendarSize <- Gen.enum 1 255 - case TN.someNatVal calendarSize of - (SomeNat size) -> do - cal <- Gen.shuffle [0.. fromIntegral calendarSize] - return (SomeCalendar (snatProxy size) $ unsafeFromList cal) - genData :: Gen (BitVector 64) genData = Gen.integral Range.linearBounded @@ -74,102 +53,11 @@ genFrameList range = Gen.list range genFrame sgGroup :: TestTree sgGroup = testGroup "Scatter Gather group" - [ testPropertyNamed "GatherSequential - No overwriting implies no lost frames." "engineNoFrameLoss gatherEngine" (engineNoFrameLoss gatherEngine) - , testPropertyNamed "ScatterSequential - No overwriting implies no lost frames." "engineNoFrameLoss scatterEngine" (engineNoFrameLoss scatterEngine) - , testPropertyNamed "ScatterGather - No overwriting implies no lost frames." "scatterGatherNoFrameLoss" scatterGatherNoFrameLoss - , testPropertyNamed "scatterUnitWb - No overwriting implies no lost frames." "scatterUnitNoFrameLoss" scatterUnitNoFrameLoss + [ testPropertyNamed "scatterUnitWb - No overwriting implies no lost frames." "scatterUnitNoFrameLoss" scatterUnitNoFrameLoss , testPropertyNamed "gatherUnitWb - No overwriting implies no lost frames." "gatherUnitNoFrameLoss" gatherUnitNoFrameLoss ] --- |The type of a sequential engine that is tested by engineNoFrameLoss. -type MemoryEngine = - forall dom memDepth a . - (NFDataX a, KnownNat memDepth, 1 <= memDepth, HiddenClockResetEnable dom) => - -- | Indicates when a new metacycle has started. - Signal dom Bool -> - -- | Incoming frame from link, if it contains Just a, a will be written to the memory. - Signal dom (Maybe a) -> - -- | Write address, when the incoming frame contains Just a, a will be written to this address. - Signal dom (Index memDepth) -> - -- | Outgoing data - Signal dom a - --- | Tests that for a calendar that contains only unique entries, --- all frames send to the gather engine will appear at its output. --- The amount of frames we test for is equal to twice the size of the buffer, since --- it is double buffered and +2 to account for reset cycle and the one cycle delay. -engineNoFrameLoss :: MemoryEngine -> Property -engineNoFrameLoss engine = property $ do - someCalendar <- forAll genSomeCalendar - case someCalendar of - SomeCalendar size@SNat (toList -> calendar0) -> do - inputFrames <- forAll $ genFrameList (Range.constant 1 100) - let - topEntity (unbundle -> (frameIn, calIn, newMeta)) = - maybeIsUndefined <$> withClockResetEnable clockGen resetGen enableGen - engine newMeta frameIn calIn - -- Simulate for at least twice the size of the doublebuffered memory + the - -- length of the inputdata + 2 to compensate for the reset cycle and delay cycle. - simLength = 2 * snatToNum size + P.length inputFrames + 2 - inputFrames' = P.take simLength $ inputFrames <> P.repeat Nothing - newMetaSignal = cycle $ (==0) <$> [0..P.length calendar0 - 1] - topEntityInput = P.zip3 inputFrames' (cycle calendar0) newMetaSignal - simOut = simulateN @System simLength topEntity topEntityInput - footnote . fromString $ showX simOut - footnote . fromString $ showX topEntityInput - Set.fromList inputFrames' === Set.fromList simOut - -filterSgOut :: - (KnownDomain dom, KnownNat n, 1 <= n) => - (Signal dom (BitVector n), Signal dom (Maybe (BitVector n))) -> - (Signal dom (Maybe (BitVector n)), Signal dom (Maybe (BitVector n))) -filterSgOut (toP, toS) = (maybeIsUndefined <$> toP, (maybeIsUndefined =<<) <$> toS) - -filterZeroes :: (Num a, Eq a) => [Maybe f] -> [a] -> [Maybe f] -filterZeroes fs as = [ if a == 0 then Nothing else f | (f, a) <- P.zip fs as] - --- | Tests that for a calendar that contains only unique entries, --- all frames send from the PE side to a non zero address, appear at the PE side output. -scatterGatherNoFrameLoss :: Property -scatterGatherNoFrameLoss = property $ do - someCalendarS <- forAll genSomeCalendar - someCalendarG <- forAll genSomeCalendar - inputFramesSwitch <- forAll $ genFrameList (Range.constant 1 100) - inputFramesPE <- forAll $ genFrameList (Range.constant 1 100) - case (someCalendarS, someCalendarG) of - (SomeCalendar depthScat@SNat calScat, SomeCalendar depthGath@SNat calGath) -> do - let - addressesScat = cycle $ toList calScat - addressesGath = cycle $ toList calGath - inputFramesSwitch' = inputFramesSwitch <> P.repeat Nothing - inputFramesPE' = inputFramesPE <> P.repeat Nothing - - topEntity (unbundle -> (frameInS, frameInP, readAddrPE, writeAddrPE)) = bundle $ - filterSgOut @System (withClockResetEnable clockGen resetGen enableGen - scatterGatherEngine calScat calGath (pure Nothing) (pure Nothing) frameInS frameInP - readAddrPE writeAddrPE) - - maxCalDepth = max (snatToNum depthScat) (snatToNum depthGath) - maxInputLength = max (P.length inputFramesSwitch) (P.length inputFramesPE) - -- Simulate for at least the largest calendar + twice the length of the longest input. - -- This ensures all frames will appear at the output. - simLength = maxCalDepth + 2 * maxInputLength + 1 - topEntityInput = L.zip4 inputFramesSwitch' inputFramesPE' addressesScat addressesGath - simOut = simulateN simLength topEntity topEntityInput - - expectedScat = filterZeroes (P.take simLength inputFramesSwitch') addressesScat - expectedGath = filterZeroes (P.take simLength inputFramesPE') addressesGath - expected = bimap Set.fromList Set.fromList (expectedScat, expectedGath) - result = bimap Set.fromList Set.fromList $ P.unzip simOut - - footnote . fromString $ "Frames to PE: " <> showX (fmap fst simOut) - footnote . fromString $ "Frames to Switch: " <> showX (fmap snd simOut) - footnote . fromString $ "Frames from PE: " <> show (P.zip addressesGath $ P.take simLength inputFramesPE') - footnote . fromString $ "Frames from Switch: " <> show (P.zip addressesScat $ P.take simLength inputFramesSwitch') - - expected === result - --- TODO: Remove instance once Clash.Prelude has it. +-- TODO: Remove instance once https://github.com/clash-lang/clash-compiler/pull/2197 is released deriving instance Show (SNatLE a b) -- | Generates a 'CalendarConfig' for the 'gatherUnitWb' or 'scatterUnitWb' @@ -197,11 +85,17 @@ genCalendarConfig sizeNat@(snatToNum -> dMax) = do , isInBounds d1 depthB sizeNat , compareSNat regAddrBits (SNat @addressWidth) , compareSNat d1 bsCalEntry) of - (InBounds, InBounds, SNatLE, SNatLE)-> go depthA depthB - (a,b,c,d) -> error $ "genCalendarConfig: calEntry constraints not satisfied: (" - <> show a <> ", " <> show b <> ", " <> show c <> ", " <> show d <> - "), \n(depthA, depthB, maxDepth, calEntry bitsize) = (" <> show depthA <> ", " - <> show depthB <> ", " <> show sizeNat <> ", " <> show bsCalEntry <> ")" + (InBounds, InBounds, SNatLE, SNatLE) -> go depthA depthB + (a, b, c, d) -> error [I.i| + genCalendarConfig: calEntry constraints not satisfied: + + a: #{a} + b: #{b} + b: #{c} + c: #{d} + + ... + |] where go :: forall depthA depthB . ( LessThan depthA maxDepth @@ -224,15 +118,14 @@ scatterUnitNoFrameLoss = property $ do maxCalSize <- forAll $ Gen.enum 2 32 case TN.someNatVal (maxCalSize - 1) of SomeNat (succSNat . snatProxy -> p) -> do - cal <- forAll $ genCal p - runTest cal + runTest =<< forAll (genCal p) where runTest :: (KnownNat maxSize, 1 <= maxSize) => CalendarConfig 4 32 (Index maxSize) -> PropertyT IO () runTest calConfig@(CalendarConfig _ calA@(length -> depth) _) = do - -- Amount of metacycles of input to generate + -- Number of metacycles of input to generate metaCycles <- forAll $ Gen.enum 1 10 let -- reset cycle + cycle delay, last metacycle's writes can be read in (metacycles + 1) @@ -270,13 +163,11 @@ gatherUnitNoFrameLoss = property $ do maxCalSize <- forAll $ Gen.enum 2 32 case TN.someNatVal (maxCalSize - 1) of SomeNat (succSNat . snatProxy -> p) -> do - calConfig <- forAll $ genCal p - runTest calConfig + runTest =<< forAll (genCal p) where runTest :: (KnownNat maxSize, 1 <= maxSize) => - CalendarConfig 4 32 (Index maxSize) -> - PropertyT IO () + CalendarConfig 4 32 (Index maxSize) -> PropertyT IO () runTest calConfig@(CalendarConfig _ calA@(length -> depth) _) = do metaCycles <- forAll $ Gen.enum 1 10 let @@ -297,7 +188,7 @@ gatherUnitNoFrameLoss = property $ do simOut = simulateN simLength topEntity wbWriteOps addressedFrames = P.zip (P.concat inputFrames) (cycle $ toList calA) writtenFrames = [if snd e /= 0 then fst e else Nothing | e <- addressedFrames] - prePad = (P.replicate (1+depth) Nothing P.++) + prePad items = P.replicate (1+depth) Nothing P.++ items expectedOutput = P.take simLength (fromMaybe 1 <$> P.filter isJust writtenFrames) footnote . fromString $ "simOut: " <> showX simOut @@ -321,9 +212,10 @@ directedDecode _ _ = [] -- | Decode an incoming slave bus by consuming two acknowledged signals and concatenating -- their readData's. -wbDecoding :: KnownNat bytes => - [WishboneS2M bytes] - -> [BitVector ((8 * bytes) + (8 * bytes))] +wbDecoding :: + KnownNat bytes => + [WishboneS2M bytes] -> + [BitVector ((8 * bytes) + (8 * bytes))] wbDecoding (s2m0 : s2m1 : s2ms) | acknowledge s2m0 && acknowledge s2m1 = out : wbDecoding s2ms | otherwise = wbDecoding (s2m1 : s2ms) @@ -390,4 +282,4 @@ wbWrite _ Nothing = [] -- | Idle 'WishboneM2S' bus. idleM2S :: forall bytes aw . (KnownNat bytes, KnownNat aw) => WishboneM2S bytes aw -idleM2S = (wishboneM2S SNat (SNat @aw)) +idleM2S = wishboneM2S SNat (SNat @aw)