diff --git a/clash-lib/prims/systemverilog/Clash_Explicit_BlockRam.json b/clash-lib/prims/systemverilog/Clash_Explicit_BlockRam.json index dc5656a3f1..744d63084b 100644 --- a/clash-lib/prims/systemverilog/Clash_Explicit_BlockRam.json +++ b/clash-lib/prims/systemverilog/Clash_Explicit_BlockRam.json @@ -45,4 +45,52 @@ assign ~RESULT = ~FROMBV[~SYM[2]][~TYP[9]]; // blockRam end" } } +, { "BlackBox" : + { "name" : "Clash.Explicit.BlockRam.blockRam1#" + , "kind" : "Declaration" + , "type" : +"blockRam1# + :: ( KnownDomain dom conf ARG[0] + , HasCallStack -- ARG[1] + , Undefined a ) -- ARG[2] + => Clock dom -- clk, ARG[3] + -> Enable dom -- en, ARG[4] + -> SNat n -- len, ARG[5] + -> a -- init, ARG[6] + -> Signal dom Int -- rd, ARG[7] + -> Signal dom Bool -- wren, ARG[8] + -> Signal dom Int -- wr, ARG[9] + -> Signal dom a -- din, ARG[10] + -> Signal dom a" + , "template" : +"// blockRam1 begin, +~TYPO ~GENSYM[~RESULT_RAM][1] [0:~LIT[5]-1]; +logic [~SIZE[~TYP[10]]-1:0] ~GENSYM[~RESULT_q][2]; +initial begin + ~SYM[1] = '{default: ~CONST[6]}; +end~IF ~ISALWAYSENABLED[4] ~THEN +always @(~IF~ACTIVEEDGE[Rising][0]~THENposedge~ELSEnegedge~FI ~ARG[3]) begin : ~GENSYM[~COMPNAME_blockRam][3]~IF ~VIVADO ~THEN + if (~ARG[4]) begin + if (~ARG[8]) begin + ~SYM[1][~ARG[9]] <= ~TOBV[~ARG[10]][~TYP[10]]; + end + ~SYM[2] <= ~SYM[1][~ARG[7]]; + end~ELSE + if (~ARG[8] & ~ARG[4]) begin + ~SYM[1][~ARG[9]] <= ~TOBV[~ARG[10]][~TYP[10]]; + end + if (~ARG[4]) begin + ~SYM[2] <= ~SYM[1][~ARG[7]]; + end~FI +end~ELSE +always @(~IF~ACTIVEEDGE[Rising][0]~THENposedge~ELSEnegedge~FI ~ARG[3]) begin : ~SYM[3] + if (~ARG[8]) begin + ~SYM[1][~ARG[9]] <= ~TOBV[~ARG[10]][~TYP[10]]; + end + ~SYM[2] <= ~SYM[1][~ARG[7]]; +end~FI +assign ~RESULT = ~FROMBV[~SYM[2]][~TYP[10]]; +// blockRam1 end" + } + } ] diff --git a/clash-lib/prims/verilog/Clash_Explicit_BlockRam.json b/clash-lib/prims/verilog/Clash_Explicit_BlockRam.json index e5021407b7..b02de38f0c 100644 --- a/clash-lib/prims/verilog/Clash_Explicit_BlockRam.json +++ b/clash-lib/prims/verilog/Clash_Explicit_BlockRam.json @@ -51,4 +51,56 @@ end~FI // blockRam end" } } +, { "BlackBox" : + { "name" : "Clash.Explicit.BlockRam.blockRam1#" + , "kind" : "Declaration" + , "type" : +"blockRam1# + :: ( KnownDomain dom conf ARG[0] + , HasCallStack -- ARG[1] + , Undefined a ) -- ARG[2] + => Clock dom -- clk, ARG[3] + -> Enable dom -- en, ARG[4] + -> SNat n -- len, ARG[5] + -> a -- init, ARG[6] + -> Signal dom Int -- rd, ARG[7] + -> Signal dom Bool -- wren, ARG[8] + -> Signal dom Int -- wr, ARG[9] + -> Signal dom a -- din, ARG[10] + -> Signal dom a" + , "outputReg" : true + , "template" : +"// blockRam1 begin +reg ~TYPO ~GENSYM[~RESULT_RAM][0] [0:~LIT[5]-1]; +integer ~GENSYM[i][1]; +initial begin + for (~SYM[1]=0;~SYM[1]<~LIT[5];~SYM[1]=~SYM[1]+1) begin + ~SYM[0][~SYM[1]] = ~CONST[6]; + end +end + +~IF ~ISALWAYSENABLED[4] ~THEN +always @(~IF~ACTIVEEDGE[Rising][0]~THENposedge~ELSEnegedge~FI ~ARG[3]) begin : ~GENSYM[~RESULT_blockRam][5]~IF ~VIVADO ~THEN + if (~ARG[4]) begin + if (~ARG[8]) begin + ~SYM[0][~ARG[9]] <= ~ARG[10]; + end + ~RESULT <= ~SYM[0][~ARG[7]]; + end~ELSE + if (~ARG[8] & ~ARG[4]) begin + ~SYM[0][~ARG[9]] <= ~ARG[10]; + end + if (~ARG[4]) begin + ~RESULT <= ~SYM[0][~ARG[7]]; + end~FI +end~ELSE +always @(~IF~ACTIVEEDGE[Rising][0]~THENposedge~ELSEnegedge~FI ~ARG[3]) begin : ~SYM[5] + if (~ARG[8]) begin + ~SYM[0][~ARG[9]] <= ~ARG[10]; + end + ~RESULT <= ~SYM[0][~ARG[7]]; +end~FI +// blockRam1 end" + } + } ] diff --git a/clash-lib/prims/vhdl/Clash_Explicit_BlockRam.json b/clash-lib/prims/vhdl/Clash_Explicit_BlockRam.json index 54e02f575d..17ca3249bf 100644 --- a/clash-lib/prims/vhdl/Clash_Explicit_BlockRam.json +++ b/clash-lib/prims/vhdl/Clash_Explicit_BlockRam.json @@ -63,4 +63,71 @@ end block; --end blockRam" } } +, { "BlackBox" : + { "name" : "Clash.Explicit.BlockRam.blockRam1#" + , "kind" : "Declaration" + , "type" : +"blockRam1# + :: ( KnownDomain dom conf ARG[0] + , HasCallStack -- ARG[1] + , Undefined a ) -- ARG[2] + => Clock dom -- clk, ARG[3] + -> Enable dom -- en, ARG[4] + -> SNat n -- len, ARG[5] + -> a -- init, ARG[6] + -> Signal dom Int -- rd, ARG[7] + -> Signal dom Bool -- wren, ARG[8] + -> Signal dom Int -- wr, ARG[9] + -> Signal dom a -- din, ARG[10] + -> Signal dom a" + , "template" : +"-- blockRam1 begin +~GENSYM[~RESULT_blockRam][1] : block + type ~GENSYM[ram_t][8] is array (0 to integer'(~LIT[5])-1) of ~TYP[6]; + signal ~GENSYM[~RESULT_RAM][2] : ~SYM[8] := (others => ~CONST[6]); + signal ~GENSYM[rd][4] : integer range 0 to ~LIT[5] - 1; + signal ~GENSYM[wr][5] : integer range 0 to ~LIT[5] - 1; +begin + ~SYM[4] <= to_integer(~ARG[7]) + -- pragma translate_off + mod ~LIT[5] + -- pragma translate_on + ; + + ~SYM[5] <= to_integer(~ARG[9]) + -- pragma translate_off + mod ~LIT[5] + -- pragma translate_on + ; +~IF ~VIVADO ~THEN + ~SYM[6] : process(~ARG[3]) + begin + if ~IF~ACTIVEEDGE[Rising][0]~THENrising_edge~ELSEfalling_edge~FI(~ARG[3]) then + if ~ARG[8] ~IF ~ISALWAYSENABLED[4] ~THEN and ~ARG[4] ~ELSE ~FI then + ~SYM[2](~SYM[5]) <= ~TOBV[~ARG[10]][~TYP[10]]; + end if; + ~RESULT <= fromSLV(~SYM[2](~SYM[4])) + -- pragma translate_off + after 1 ps + -- pragma translate_on + ; + end if; + end process; ~ELSE + ~SYM[6] : process(~ARG[3]) + begin + if ~IF~ACTIVEEDGE[Rising][0]~THENrising_edge~ELSEfalling_edge~FI(~ARG[3]) then + if ~ARG[8] ~IF ~ISALWAYSENABLED[4] ~THEN and ~ARG[4] ~ELSE ~FI then + ~SYM[2](~SYM[5]) <= ~ARG[10]; + end if; + ~RESULT <= ~SYM[2](~SYM[4]) + -- pragma translate_off + after 1 ps + -- pragma translate_on + ; + end if; + end process; ~FI +end block; +--end blockRam1" + } + } ] diff --git a/clash-prelude/src/Clash/Explicit/BlockRam.hs b/clash-prelude/src/Clash/Explicit/BlockRam.hs index f53772a6e3..2a70afa290 100644 --- a/clash-prelude/src/Clash/Explicit/BlockRam.hs +++ b/clash-prelude/src/Clash/Explicit/BlockRam.hs @@ -376,6 +376,7 @@ This concludes the short introduction to using 'blockRam'. {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE KindSignatures #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeOperators #-} @@ -393,6 +394,8 @@ module Clash.Explicit.BlockRam ( -- * BlockRAM synchronized to the system clock blockRam , blockRamPow2 + , blockRam1 + , ResetStrategy(..) -- * Read/Write conflict resolution , readNew -- * Internal @@ -400,18 +403,23 @@ module Clash.Explicit.BlockRam ) where +import Control.Applicative (liftA2) import Data.Maybe (isJust) import qualified Data.Vector as V import GHC.Stack (HasCallStack, withFrozenCallStack) -import GHC.TypeLits (KnownNat, type (^)) -import Prelude hiding (length) +import GHC.TypeLits (KnownNat, type (^), type (<=)) +import Prelude hiding (length, replicate) +import Clash.Annotations.Primitive (hasBlackBox) +import Clash.Class.Num (SaturationMode(SatBound), satAdd) import Clash.Explicit.Signal (KnownDomain, Enable, register, fromEnable) import Clash.Signal.Internal - (Clock(..), Reset, Signal (..), (.&&.), mux) + (Clock(..), Reset, Signal (..), invertReset, (.&&.), mux) +import Clash.Promoted.Nat (SNat(..)) import Clash.Signal.Bundle (unbundle) import Clash.Sized.Unsigned (Unsigned) -import Clash.Sized.Vector (Vec, toList) +import Clash.Sized.Index (Index) +import Clash.Sized.Vector (Vec, replicate, toList) import Clash.XException (maybeIsX, seqX, Undefined, deepErrorX, defaultSeqX, errorX) @@ -681,6 +689,10 @@ prog2 = -- 0 := 4 -} +fromJustX :: HasCallStack => Maybe a -> a +fromJustX Nothing = errorX "fromJustX: Nothing" +fromJustX (Just x) = x + -- | Create a blockRAM with space for @n@ elements -- -- * __NB__: Read value is delayed by 1 cycle @@ -725,9 +737,6 @@ blockRam = \clk gen content rd wrM -> (wr,din) = unbundle (fromJustX <$> wrM) in withFrozenCallStack (blockRam# clk gen content (fromEnum <$> rd) en (fromEnum <$> wr) din) - where - fromJustX Nothing = errorX "fromJustX" - fromJustX (Just x) = x {-# INLINE blockRam #-} -- | Create a blockRAM with space for 2^@n@ elements @@ -776,6 +785,98 @@ blockRamPow2 = \clk en cnt rd wrM -> withFrozenCallStack (blockRam clk en cnt rd wrM) {-# INLINE blockRamPow2 #-} +data ResetStrategy (r :: Bool) where + ClearOnReset :: ResetStrategy 'True + NoClearOnReset :: ResetStrategy 'False + +-- | Version of blockram that is initialized with the same value on all +-- memory positions. +blockRam1 + :: forall n dom a r addr conf + . ( KnownDomain dom conf + , HasCallStack + , Undefined a + , Enum addr + , 1 <= n ) + => Clock dom + -- ^ 'Clock' to synchronize to + -> Reset dom + -- ^ 'Reset' line to listen to. Needs to be held at least /n/ cycles in order + -- for the BRAM to be reset to its initial state. + -> Enable dom + -- ^ Global enable + -> ResetStrategy r + -- ^ Whether to clear BRAM on asserted reset ('ClearOnReset') or + -- not ('NoClearOnReset'). Reset needs to be asserted at least /n/ cycles to + -- clear the BRAM. + -> SNat n + -- ^ Number of elements in BRAM + -> a + -- ^ Initial content of the BRAM (replicated /n/ times) + -> Signal dom addr + -- ^ Read address @r@ + -> Signal dom (Maybe (addr, a)) + -- ^ (write address @w@, value to write) + -> Signal dom a + -- ^ Value of the @blockRAM@ at address @r@ from the previous clock cycle +blockRam1 clk rst0 en rstStrategy n@SNat a rd0 mw0 = + case rstStrategy of + ClearOnReset -> + -- Use reset infrastructure + blockRam1# clk en n a rd1 we1 wa1 w1 + NoClearOnReset -> + -- Ignore reset infrastructure, pass values unchanged + blockRam1# clk en n a + (fromEnum <$> rd0) + we0 + (fromEnum <$> wa0) + w0 + where + rstBool = register clk rst0 en True (pure False) + rstInv = invertReset rst0 + + waCounter :: Signal dom (Index n) + waCounter = register clk rstInv en 0 (liftA2 (satAdd SatBound) (pure 1) waCounter) + + wa0 = fst . fromJustX <$> mw0 + w0 = snd . fromJustX <$> mw0 + we0 = isJust <$> mw0 + + rd1 = mux rstBool 0 (fromEnum <$> rd0) + we1 = mux rstBool (pure True) we0 + wa1 = mux rstBool (fromInteger . toInteger <$> waCounter) (fromEnum <$> wa0) + w1 = mux rstBool (pure a) w0 + +-- | blockRAM1 primitive +blockRam1# + :: forall n dom a conf + . ( KnownDomain dom conf + , HasCallStack + , Undefined a ) + => Clock dom + -- ^ 'Clock' to synchronize to + -> Enable dom + -- ^ Global Enable + -> SNat n + -- ^ Number of elements in BRAM + -> a + -- ^ Initial content of the BRAM (replicated /n/ times) + -> Signal dom Int + -- ^ Read address @r@ + -> Signal dom Bool + -- ^ Write enable + -> Signal dom Int + -- ^ Write address @w@ + -> Signal dom a + -- ^ Value to write (at address @w@) + -> Signal dom a + -- ^ Value of the @blockRAM@ at address @r@ from the previous clock cycle +blockRam1# clk en n a = + -- TODO: Generalize to single BRAM primitive taking an initialization function + blockRam# clk en (replicate n a) +{-# NOINLINE blockRam1# #-} +{-# ANN blockRam1# hasBlackBox #-} + -- | blockRAM primitive blockRam# :: ( KnownDomain dom conf @@ -821,6 +922,7 @@ blockRam# (Clock _) gen content rd wen = Nothing -> V.map (const (seq waddr d)) ram Just wa -> ram V.// [(wa,d)] _ -> ram +{-# ANN blockRam# hasBlackBox #-} {-# NOINLINE blockRam# #-} -- | Create read-after-write blockRAM from a read-before-write one diff --git a/clash-prelude/src/Clash/Explicit/Prelude.hs b/clash-prelude/src/Clash/Explicit/Prelude.hs index c6943ca3f1..339f7248f1 100644 --- a/clash-prelude/src/Clash/Explicit/Prelude.hs +++ b/clash-prelude/src/Clash/Explicit/Prelude.hs @@ -44,6 +44,8 @@ module Clash.Explicit.Prelude -- * BlockRAM primitives , blockRam , blockRamPow2 + , blockRam1 + , ResetStrategy(..) -- ** BlockRAM primitives initialized with a data file , blockRamFile , blockRamFilePow2 diff --git a/clash-prelude/src/Clash/Prelude.hs b/clash-prelude/src/Clash/Prelude.hs index 99665d4323..06442cc598 100644 --- a/clash-prelude/src/Clash/Prelude.hs +++ b/clash-prelude/src/Clash/Prelude.hs @@ -65,6 +65,8 @@ module Clash.Prelude -- * BlockRAM primitives , blockRam , blockRamPow2 + , blockRam1 + , E.ResetStrategy(..) -- ** BlockRAM primitives initialized with a data file , blockRamFile , blockRamFilePow2 @@ -167,6 +169,7 @@ import Clash.Hidden import Clash.NamedTypes import Clash.Prelude.BitIndex import Clash.Prelude.BitReduction +import Clash.Prelude.BlockRam import Clash.Prelude.BlockRam.File import Clash.Prelude.DataFlow import Clash.Prelude.ROM.File diff --git a/clash-prelude/src/Clash/Prelude/BlockRam.hs b/clash-prelude/src/Clash/Prelude/BlockRam.hs index 402c786a07..ee2583a8f5 100644 --- a/clash-prelude/src/Clash/Prelude/BlockRam.hs +++ b/clash-prelude/src/Clash/Prelude/BlockRam.hs @@ -386,15 +386,18 @@ module Clash.Prelude.BlockRam ( -- * BlockRAM synchronized to the system clock blockRam , blockRamPow2 + , blockRam1 + , E.ResetStrategy(..) -- * Read/Write conflict resolution , readNew ) where -import GHC.TypeLits (KnownNat, type (^)) +import GHC.TypeLits (KnownNat, type (^), type (<=)) import GHC.Stack (HasCallStack, withFrozenCallStack) import qualified Clash.Explicit.BlockRam as E +import Clash.Promoted.Nat (SNat) import Clash.Signal import Clash.Sized.Unsigned (Unsigned) import Clash.Sized.Vector (Vec) @@ -700,6 +703,34 @@ blockRam = \cnt rd wrM -> withFrozenCallStack (hideEnable (hideClock E.blockRam) cnt rd wrM) {-# INLINE blockRam #-} +-- | Version of blockram that is initialized with the same value on all +-- memory positions. +blockRam1 + :: forall n dom a r addr conf + . ( HasCallStack + , HiddenClockResetEnable dom conf + , Undefined a + , Enum addr + , 1 <= n ) + => E.ResetStrategy r + -- ^ Whether to clear BRAM on asserted reset ('ClearOnReset') or + -- not ('NoClearOnReset'). Reset needs to be asserted at least /n/ cycles to + -- clear the BRAM. + -> SNat n + -- ^ Number of elements in BRAM + -> a + -- ^ Initial content of the BRAM (replicated /n/ times) + -> Signal dom addr + -- ^ Read address @r@ + -> Signal dom (Maybe (addr, a)) + -- ^ (write address @w@, value to write) + -> Signal dom a + -- ^ Value of the @blockRAM@ at address @r@ from the previous clock cycle +blockRam1 = + \rstStrategy cnt rd wrM -> withFrozenCallStack + (hideClockResetEnable E.blockRam1) rstStrategy cnt rd wrM +{-# INLINE blockRam1 #-} + -- | Create a blockRAM with space for 2^@n@ elements -- -- * __NB__: Read value is delayed by 1 cycle diff --git a/clash-prelude/src/Clash/Signal/Internal.hs b/clash-prelude/src/Clash/Signal/Internal.hs index d6f38a4254..475c72e1db 100644 --- a/clash-prelude/src/Clash/Signal/Internal.hs +++ b/clash-prelude/src/Clash/Signal/Internal.hs @@ -89,6 +89,7 @@ module Clash.Signal.Internal , unsafeToLowPolarity , unsafeFromHighPolarity , unsafeFromLowPolarity + , invertReset -- * Basic circuits , delay# , register# @@ -974,6 +975,10 @@ unsafeFromLowPolarity unsafeFromLowPolarity r = unsafeToReset (if isActiveHigh @dom then not <$> r else r) +-- | Invert reset signal +invertReset :: KnownDomain dom conf => Reset dom -> Reset dom +invertReset = unsafeToReset . fmap not . unsafeFromReset + infixr 2 .||. -- | The above type is a generalization for: diff --git a/tests/shouldwork/Signal/BlockRam1.hs b/tests/shouldwork/Signal/BlockRam1.hs new file mode 100644 index 0000000000..226e297801 --- /dev/null +++ b/tests/shouldwork/Signal/BlockRam1.hs @@ -0,0 +1,110 @@ +module BlockRam1 where + +import Clash.Prelude +import Clash.Explicit.Testbench +import qualified Clash.Explicit.Prelude as Explicit + +zeroAt0 + :: HiddenClockResetEnable dom conf + => Signal dom (Unsigned 8) + -> Signal dom (Unsigned 8) +zeroAt0 a = mux en a 0 + where + en = register False (pure True) + +topEntity + :: Clock System + -> Reset System + -> Enable System + -> Signal System (Index 1024) + -> Signal System (Maybe (Index 1024, Unsigned 8)) + -> Signal System (Unsigned 8) +topEntity = exposeClockResetEnable go where + + go rd wr = zeroAt0 dout where + dout = + blockRam1 + ClearOnReset + (SNat @1024) + (3 :: Unsigned 8) + rd + wr +{-# NOINLINE topEntity #-} + +testBench :: Signal System Bool +testBench = done + where + (rst0, rd, wr) = + unbundle $ stimuliGenerator + clk rst + ( (True, 0, Nothing) + + -- Confirm initial values + :> (False, 0, Nothing) + :> (False, 1, Nothing) + :> (False, 2, Nothing) + :> (False, 3, Nothing) + + -- Write some values + :> (False, 0, Just (0, 8)) + :> (False, 0, Just (1, 9)) + :> (False, 0, Just (2, 10)) + :> (False, 0, Just (3, 11)) + + -- Read written values + :> (False, 0, Nothing) + :> (False, 1, Nothing) + :> (False, 2, Nothing) + :> (False, 3, Nothing) + + -- Reset for two cycles + :> (True, 0, Nothing) + :> (True, 0, Nothing) + + -- Check whether first two values were reset + :> (False, 0, Nothing) + :> (False, 1, Nothing) + :> (False, 2, Nothing) + :> (False, 3, Nothing) + + :> Nil + + ) + + expectedOutput = + outputVerifier clk rst + ( 0 :> 0 + + -- Initial values should be all threes + :> 3 + :> 3 + :> 3 + :> 3 + + -- Read address zero while writing data + :> 3 + :> 8 + :> 8 + :> 8 + + -- Read written values back from BRAM + :> 8 + :> 9 + :> 10 + :> 0 -- < Reset is high, so we won't read '11' + + -- Reset for two cycles + :> 0 + :> 0 + + -- Check whether reset worked + :> 3 + :> 3 + :> 10 + :> 11 + + :> Nil) + + done = expectedOutput (topEntity clk (unsafeFromHighPolarity rst0) enableGen rd wr) + clk = tbSystemClockGen (not <$> done) + rst = systemResetGen diff --git a/testsuite/Main.hs b/testsuite/Main.hs index bd97ed86fb..792d68fb63 100755 --- a/testsuite/Main.hs +++ b/testsuite/Main.hs @@ -238,6 +238,7 @@ runClashTest = [ runTest ("tests" "shouldwork" "Signal") defBuild [] "AlwaysHigh" ([""],"AlwaysHigh_topEntity",False) , outputTest ("tests" "shouldwork" "Signal") defBuild [] [] "BlockRamLazy" "main" , runTest ("tests" "shouldwork" "Signal") defBuild [] "BlockRamFile" (["","BlockRamFile_testBench"],"BlockRamFile_testBench",True) + , runTest ("tests" "shouldwork" "Signal") defBuild [] "BlockRam1" (["","BlockRam1_testBench"],"BlockRam1_testBench",True) , runTest ("tests" "shouldwork" "Signal") defBuild [] "BlockRamTest" ([""],"BlockRamTest_topEntity",False) , runTest ("tests" "shouldwork" "Signal") defBuild [] "DelayedReset" (["","DelayedReset_testBench"],"DelayedReset_testBench",True) , runTest ("tests" "shouldwork" "Signal") defBuild [] "NoCPR" (["example"],"example",False)