Skip to content

Commit 46bccdb

Browse files
committed
clash-prelude: Add warning to holdReset and implement correct stretchReset
1 parent d0f65c4 commit 46bccdb

File tree

3 files changed

+232
-8
lines changed

3 files changed

+232
-8
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FIXED: Added warnings to `holdReset` [#3115](https://github.com/clash-lang/clash-compiler/issues/3115)
2+
FIXED: Added `stretchReset` as safe replacement for `holdReset` [#3115](https://github.com/clash-lang/clash-compiler/issues/3115)

clash-prelude/src/Clash/Explicit/Reset.hs

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Utilities to deal with resets.
1616

1717
{-# OPTIONS_GHC -fplugin=GHC.TypeLits.Normalise #-}
1818
{-# OPTIONS_GHC -fplugin=GHC.TypeLits.KnownNat.Solver #-}
19+
{-# OPTIONS_GHC -fplugin=GHC.TypeLits.Extra.Solver #-}
1920

2021
module Clash.Explicit.Reset
2122
( -- Defined in this module
@@ -24,6 +25,7 @@ module Clash.Explicit.Reset
2425
, resetGlitchFilterWithReset
2526
, unsafeResetGlitchFilter
2627
, holdReset
28+
, stretchReset
2729
, convertReset
2830
, noReset
2931
, andReset, unsafeAndReset
@@ -55,11 +57,15 @@ import Clash.Class.Num (satSucc, SaturationMode(SatBound))
5557
import Clash.Explicit.Signal
5658
import Clash.Explicit.Synchronizer (dualFlipFlopSynchronizer)
5759
import Clash.Promoted.Nat
60+
import Clash.Promoted.Nat.Literals
5861
import Clash.Signal.Internal
5962
import Clash.Sized.Index (Index)
63+
import Clash.Magic (clashCompileError)
6064

6165
import GHC.Stack (HasCallStack)
62-
import GHC.TypeLits (type (+), type (<=))
66+
import GHC.TypeLits (type (+), type (-), type (<=))
67+
68+
import GHC.TypeLits.Extra (Max)
6369

6470
{- $setup
6571
>>> import Clash.Explicit.Prelude
@@ -399,7 +405,15 @@ resetGlitchFilter# SNat reg dffSync rstIn0 =
399405
SActiveHigh -> True
400406
SActiveLow -> False
401407

402-
-- | Hold reset for a number of cycles relative to an incoming reset signal.
408+
-- | Hold/stretch reset for a number of cycles relative to an incoming reset
409+
-- signal.
410+
--
411+
-- __NB__: 'holdReset' is unsafe for asynchronous resets. It can cause
412+
-- spurious resets.
413+
-- For synchronous the output is pessimistic since it is driven by combinational
414+
-- logic.
415+
-- You probably want to use 'stretchReset' which is safe but has slightly
416+
-- different timing behaviour.
403417
--
404418
-- Example:
405419
--
@@ -433,6 +447,135 @@ holdReset clk en SNat rst =
433447
counter :: Signal dom (Index (n+1))
434448
counter = register clk rst en 0 (satSucc SatBound <$> counter)
435449

450+
-- | Hold/stretch reset for a number of cycles relative to an incoming reset
451+
-- signal. This is a safe replacement for `holdReset`. There are some
452+
-- differences in behaviour for synchronous resets and asynchronous resets.
453+
--
454+
-- Synchronous resets
455+
--
456+
-- If used with a synchronous reset the assertion of the reset is delayed by 1
457+
-- cycle. If your domain can support initial values the first clock cycle
458+
-- after boot the reset will be asserted. Afterwards the reset keeps being
459+
-- asserted for (n + 1) cycles.
460+
--
461+
-- Examples on a domain with synchronous reset and supporting initial values:
462+
--
463+
-- >>> let sampleWithReset = sampleN 8 . unsafeToActiveHigh
464+
-- >>> let stretchResetBy2 = stretchReset @XilinxSystem clockGen enableGen (SNat @2)
465+
-- >>> sampleWithReset (stretchResetBy2 (resetGenN (SNat @3)))
466+
-- [True,True,True,True,True,True,False,False]
467+
--
468+
-- The first 3 cycles the input reset is asserted and `stretchReset` should
469+
-- stretch it by 2 cycles. That results in a reset that is asserted for 6
470+
-- cycles. This additional cycle is due to the first cycle always being reset if
471+
-- initial values are supported. 'stretchReset' also works on intermediate
472+
-- assertions of the reset signal:
473+
--
474+
-- >>> let rst = fromList [False, False, False, True, False, False, False, False]
475+
-- >>> sampleWithReset (stretchResetBy2 (unsafeFromActiveHigh rst))
476+
-- [True,True,True,False,False,True,True,False]
477+
--
478+
-- Here it again shows that on boot the reset is asserted for (n + 1) cycles
479+
-- And then after 4 cycles the input reset is asserted. Notice the delay on
480+
-- the output reset.
481+
--
482+
-- Asynchronous resets
483+
--
484+
-- If used with an asynchronous reset the minimum stretch period is always three
485+
-- cycles. This is due to an additional reset synchronizer that is inserted.
486+
-- The synchronizer itself consumes two cycles and the stretch adds at least
487+
-- one cycle, hence the minimum of three. There is no delay between assertion
488+
-- of the input and the assertion of the output.
489+
--
490+
-- Example with asynchronous reset:
491+
--
492+
-- >>> let sampleWithReset = sampleN 9 . unsafeToActiveHigh
493+
-- >>> let stretchResetBy3 = stretchReset @System clockGen enableGen (SNat @3)
494+
-- >>> sampleWithReset (stretchResetBy3 (resetGenN (SNat @3)))
495+
-- [True,True,True,True,True,True,False,False,False]
496+
--
497+
-- 'stretchReset' holds the reset for an additional 3 clock cycles for a total
498+
-- of 6 clock cycles where the reset is asserted. 'stretchReset' also works on
499+
-- intermediate assertions of the reset signal:
500+
--
501+
-- >>> let rst = fromList [False, False, False, False, True, False, False, False, False]
502+
-- >>> sampleWithReset (stretchResetBy3 (unsafeFromActiveHigh rst))
503+
-- [True,True,True,False,True,True,True,True, False]
504+
--
505+
-- Notice here that on startup it always resets for exactly the specified amount
506+
-- of cycles. In addition it appears to reset for (n + 1) cycles on a trigger.
507+
-- However the first asserted cycle is the asynchronous assertion so we don't
508+
-- count it as a full cycle.
509+
--
510+
stretchReset
511+
:: forall dom n
512+
. KnownDomain dom
513+
=> Clock dom
514+
-> Enable dom
515+
-- ^ Global enable
516+
-> SNat n
517+
-- ^ Hold for /n/ cycles
518+
-> Reset dom
519+
-- ^ Reset to extend
520+
-> Reset dom
521+
stretchReset clk en n@SNat rst = case (resetKind @dom, compareSNat d1 (SNat @(Max 2 (Max 1 n - 1)))) of
522+
(SSynchronous, _) -> stretchResetSync clk en n rst
523+
(SAsynchronous, SNatGT) -> clashCompileError "stretchReset: Impossible 1 <= Max 2 (Max 1 n - 1) does not hold. Contact clash developers at: https://github.com/clash-lang/clash-compiler/issues"
524+
(SAsynchronous, SNatLE) -> stretchResetAsync clk en n rst
525+
526+
stretchResetSync
527+
:: forall dom n
528+
. KnownDomain dom
529+
=> DomainResetKind dom ~ 'Synchronous
530+
=> Clock dom
531+
-> Enable dom
532+
-- ^ Global enable
533+
-> SNat n
534+
-- ^ Hold for /n/ cycles
535+
-> Reset dom
536+
-- ^ Reset to extend
537+
-> Reset dom
538+
stretchResetSync clk en SNat rst = unsafeToReset (delay clk en isActiveHigh rawRst)
539+
where
540+
counter :: Signal dom (Index (n + 1))
541+
counter = register clk rst en 0 (satSucc SatBound <$> counter)
542+
rawRst :: Signal dom Bool
543+
rawRst = case resetPolarity @dom of
544+
SActiveHigh -> (/=maxBound) <$> counter
545+
SActiveLow -> (==maxBound) <$> counter
546+
547+
isActiveHigh = case resetPolarity @dom of { SActiveHigh -> True; _ -> False }
548+
549+
stretchResetAsync
550+
:: forall dom n
551+
. KnownDomain dom
552+
=> DomainResetKind dom ~ 'Asynchronous
553+
=> 1 <= Max 2 (Max 1 n - 1)
554+
=> Clock dom
555+
-> Enable dom
556+
-- ^ Global enable
557+
-> SNat n
558+
-- ^ Hold for /n/ cycles, counting from the moment the incoming reset
559+
-- signal becomes deasserted.
560+
-> Reset dom
561+
-- ^ Reset to extend
562+
-> Reset dom
563+
stretchResetAsync clk en SNat rst = rstOut
564+
where
565+
-- This ensures the index at least has one cycle for counting.
566+
counter :: Signal dom (Index ((Max 2 ((Max 1 n) - 1))))
567+
counter = register clk rst en 0 (satSucc SatBound <$> counter)
568+
rawRst :: Signal dom Bool
569+
rawRst = case resetPolarity @dom of
570+
SActiveHigh -> (/=maxBound) <$> counter
571+
SActiveLow -> (==maxBound) <$> counter
572+
573+
isActiveHigh = case resetPolarity @dom of { SActiveHigh -> True; _ -> False }
574+
rstOut = unsafeToReset
575+
$ register clk (unsafeToReset rawRst) en isActiveHigh
576+
$ register clk (unsafeToReset rawRst) en isActiveHigh
577+
$ pure (not isActiveHigh)
578+
436579
-- | Convert between different types of reset, adding a synchronizer when
437580
-- the domains are not the same. See 'resetSynchronizer' for further details
438581
-- about reset synchronization.

clash-prelude/src/Clash/Signal.hs

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ module Clash.Signal
157157
, resetSynchronizer
158158
, resetGlitchFilter
159159
, holdReset
160+
, stretchReset
160161
-- * Enabling
161162
, Enable
162163
, toEnable
@@ -1635,26 +1636,104 @@ testFor
16351636
-> Property
16361637
testFor n s = property (and (Clash.Signal.sampleN n s))
16371638

1638-
-- | Hold reset for a number of cycles relative to an implicit reset signal.
1639+
-- __NB__: 'holdReset' is unsafe for asynchronous resets. It can cause
1640+
-- spurious resets.
1641+
-- For synchronous the output is pessimistic since it is driven by combinational
1642+
-- logic.
1643+
-- You probably want to use 'stretchReset' which is safe but has slightly
1644+
-- different timing behaviour.
16391645
--
16401646
-- Example:
16411647
--
1642-
-- >>> sampleN @System 8 (unsafeToActiveHigh (holdReset (SNat @2)))
1643-
-- [True,True,True,False,False,False,False,False]
1648+
-- >>> holdResetBool = unsafeToActiveHigh . (holdReset @System)
1649+
-- >>> sampleN 8 (exposeReset (holdResetBool (SNat @2)) (resetGenN (SNat @3)))
1650+
-- [True,True,True,True,True,False,False,False]
16441651
--
16451652
-- 'holdReset' holds the reset for an additional 2 clock cycles for a total
1646-
-- of 3 clock cycles where the reset is asserted.
1653+
-- of 5 clock cycles where the reset is asserted. 'holdReset' also works on
1654+
-- intermediate assertions of the reset signal:
1655+
--
1656+
-- >>> let rst = fromList [True, False, False, False, True, False, False, False]
1657+
-- >>> sampleN 8 (exposeReset (holdResetBool (SNat @2)) (unsafeFromActiveHigh rst))
1658+
-- [True,True,True,False,True,True,True,False]
16471659
--
16481660
holdReset
16491661
:: forall dom m
16501662
. HiddenClockResetEnable dom
16511663
=> SNat m
1652-
-- ^ Hold for /m/ cycles, counting from the moment the incoming reset
1653-
-- signal becomes deasserted.
1664+
-- ^ Hold for /m/ cycles
16541665
-> Reset dom
16551666
holdReset m =
16561667
hideClockResetEnable (\clk rst en -> E.holdReset clk en m rst)
16571668

1669+
1670+
-- | Hold/stretch reset for a number of cycles relative to an incoming reset
1671+
-- signal. This is a safe replacement for `holdReset`. There are some
1672+
-- differences in behaviour for synchronous resets and asynchronous resets.
1673+
--
1674+
-- Synchronous resets
1675+
--
1676+
-- If used with a synchronous reset the assertion of the reset is delayed by 1
1677+
-- cycle. If your domain can support initial values the first clock cycle
1678+
-- after boot the reset will be asserted. Afterwards the reset keeps being
1679+
-- asserted for (n + 1) cycles.
1680+
--
1681+
-- Examples on a domain with synchronous reset and supporting initial values:
1682+
--
1683+
-- >>> stretchResetBool = unsafeToActiveHigh . (stretchReset @System)
1684+
-- >>> sampleN 8 (exposeReset (stretchResetBool (SNat @2)) (resetGenN (SNat @3)))
1685+
-- [True,True,True,True,True,True,False,False]
1686+
--
1687+
-- The first 3 cycles the input reset is asserted and `stretchReset` should
1688+
-- stretch it by 2 cycles. That results in a reset that is asserted for 6
1689+
-- cycles. This additional cycle is due to the first cycle always being reset if
1690+
-- initial values are supported. 'stretchReset' also works on intermediate
1691+
-- assertions of the reset signal:
1692+
--
1693+
-- >>> let rst = fromList [False, False, False, True, False, False, False, False]
1694+
-- >>> sampleN 8 (exposeReset (stretchResetBool (SNat @2)) (unsafeFromActiveHigh rst)))
1695+
-- [True,True,True,False,False,True,True,False]
1696+
--
1697+
-- Here it again shows that on boot the reset is asserted for (n + 1) cycles
1698+
-- And then after 4 cycles the input reset is asserted. Notice the delay on
1699+
-- the output reset.
1700+
--
1701+
-- Asynchronous resets
1702+
--
1703+
-- If used with an asynchronous reset the minimum stretch period is always three
1704+
-- cycles. This is due to an additional reset synchronizer that is inserted.
1705+
-- The synchronizer itself consumes two cycles and the stretch adds at least
1706+
-- one cycle, hence the minimum of three. There is no delay between assertion
1707+
-- of the input and the assertion of the output.
1708+
--
1709+
-- Example with asynchronous reset:
1710+
--
1711+
-- >>> stretchResetBool = unsafeToActiveHigh . (stretchReset @XilinxSystem)
1712+
-- >>> sampleN 8 (exposeReset (stretchResetBool (SNat @2)) (resetGenN (SNat @3)))
1713+
-- [True,True,True,True,True,True,False,False,False]
1714+
--
1715+
-- 'stretchReset' holds the reset for an additional 3 clock cycles for a total
1716+
-- of 6 clock cycles where the reset is asserted. 'stretchReset' also works on
1717+
-- intermediate assertions of the reset signal:
1718+
--
1719+
-- >>> let rst = fromList [False, False, False, False, True, False, False, False, False]
1720+
-- >>> sampleN 8 (exposeReset (stretchResetBool (SNat @2)) (unsafeFromActiveHigh rst)))
1721+
-- [True,True,True,False,True,True,True,True, False]
1722+
--
1723+
-- Notice here that on startup it always resets for exactly the specified amount
1724+
-- of cycles. In addition it appears to reset for (n + 1) cycles on a trigger.
1725+
-- However the first asserted cycle is the asynchronous assertion so we don't
1726+
-- count it as a full cycle.
1727+
--
1728+
stretchReset
1729+
:: forall dom m
1730+
. HiddenClockResetEnable dom
1731+
=> SNat m
1732+
-- ^ Hold for /m/ cycles
1733+
-> Reset dom
1734+
stretchReset m =
1735+
hideClockResetEnable (\clk rst en -> E.stretchReset clk en m rst)
1736+
16581737
-- | Like 'fromList', but resets on reset and has a defined reset value.
16591738
--
16601739
-- >>> let rst = unsafeFromActiveHigh (fromList [True, True, False, False, True, False])

0 commit comments

Comments
 (0)