Skip to content

Commit 7400069

Browse files
committed
clash-prelude: Add holdReset warning documentation
and implement safe replacement `stretchReset`
1 parent d0f65c4 commit 7400069

File tree

3 files changed

+205
-12
lines changed

3 files changed

+205
-12
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FIXED: Added warning documentation 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: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{-|
2-
Copyright : (C) 2020-2023, QBayLogic B.V.,
2+
Copyright : (C) 2020-2026, QBayLogic B.V.,
33
2022-2023, Google LLC
44
License : BSD2 (see the file LICENSE)
55
Maintainer : QBayLogic B.V. <devops@qbaylogic.com>
@@ -24,6 +24,7 @@ module Clash.Explicit.Reset
2424
, resetGlitchFilterWithReset
2525
, unsafeResetGlitchFilter
2626
, holdReset
27+
, stretchReset
2728
, convertReset
2829
, noReset
2930
, andReset, unsafeAndReset
@@ -55,8 +56,10 @@ import Clash.Class.Num (satSucc, SaturationMode(SatBound))
5556
import Clash.Explicit.Signal
5657
import Clash.Explicit.Synchronizer (dualFlipFlopSynchronizer)
5758
import Clash.Promoted.Nat
59+
import Clash.Promoted.Nat.Literals
5860
import Clash.Signal.Internal
5961
import Clash.Sized.Index (Index)
62+
import Clash.Magic (clashCompileError)
6063

6164
import GHC.Stack (HasCallStack)
6265
import GHC.TypeLits (type (+), type (<=))
@@ -399,20 +402,28 @@ resetGlitchFilter# SNat reg dffSync rstIn0 =
399402
SActiveHigh -> True
400403
SActiveLow -> False
401404

402-
-- | Hold reset for a number of cycles relative to an incoming reset signal.
405+
-- | Hold/stretch reset for a number of cycles relative to an incoming reset
406+
-- signal.
407+
--
408+
-- __NB__: 'holdReset' is unsafe for asynchronous resets. It can cause
409+
-- spurious resets.
410+
-- For synchronous resets the output is pessimistic since it is driven by
411+
-- combinational logic.
412+
-- You probably want to use 'stretchReset' which is safe but has slightly
413+
-- different timing behaviour.
403414
--
404415
-- Example:
405416
--
406-
-- >>> let sampleWithReset = sampleN 8 . unsafeToActiveHigh
407-
-- >>> sampleWithReset (holdReset @System clockGen enableGen (SNat @2) (resetGenN (SNat @3)))
417+
-- >>> let sampleReset = sampleN 8 . unsafeToActiveHigh
418+
-- >>> sampleReset (holdReset @System clockGen enableGen (SNat @2) (resetGenN (SNat @3)))
408419
-- [True,True,True,True,True,False,False,False]
409420
--
410421
-- 'holdReset' holds the reset for an additional 2 clock cycles for a total
411422
-- of 5 clock cycles where the reset is asserted. 'holdReset' also works on
412423
-- intermediate assertions of the reset signal:
413424
--
414425
-- >>> let rst = fromList [True, False, False, False, True, False, False, False]
415-
-- >>> sampleWithReset (holdReset @System clockGen enableGen (SNat @2) (unsafeFromActiveHigh rst))
426+
-- >>> sampleReset (holdReset @System clockGen enableGen (SNat @2) (unsafeFromActiveHigh rst))
416427
-- [True,True,True,False,True,True,True,False]
417428
--
418429
holdReset
@@ -433,6 +444,120 @@ holdReset clk en SNat rst =
433444
counter :: Signal dom (Index (n+1))
434445
counter = register clk rst en 0 (satSucc SatBound <$> counter)
435446

447+
-- | Hold/stretch reset for a number of cycles relative to an incoming reset
448+
-- signal. This is a safe replacement for `holdReset`. There are some
449+
-- differences in behaviour for synchronous resets and asynchronous resets.
450+
-- If used with a stretch of 0 the function is equivalent to doing nothing to
451+
-- the reset.
452+
--
453+
-- === Synchronous resets
454+
--
455+
-- If used with a domain on a synchronous reset the assertion of the reset is
456+
-- delayed by one cycle relative to the incoming reset. On startup the reset is
457+
-- asserted for @(n + 1)@ cycles.
458+
--
459+
-- >>> let sampleReset = sampleN 9 . unsafeToActiveHigh
460+
-- >>> let stretchResetBy2 = stretchReset @XilinxSystem clockGen enableGen (SNat @2)
461+
-- >>> sampleReset (stretchResetBy2 (resetGenN (SNat @3)))
462+
-- [True,True,True,True,True,True,False,False,False]
463+
--
464+
-- The reset assertion sequence is:
465+
--
466+
-- - The first cycle the output of the internal register is given.s
467+
-- - The next three cycles the reset is asserted due to the incoming reset
468+
-- being asserted.
469+
-- - The final two assertions are due to the reset being stretched by two cycles.
470+
--
471+
-- >>> let rst = fromList [False, False, False, True, False, False, False, False, False]
472+
-- >>> sampleReset (stretchResetBy2 (unsafeFromActiveHigh rst))
473+
-- [True,True,True,False,True,True,True,False,False]
474+
--
475+
-- Here it shows that on startup the reset is asserted for @(n + 1) = 3@ cycles
476+
-- On the fourth cycle the input reset is asserted. Notice the delay on
477+
-- the output reset assertion and that is stretch by two cycles.
478+
--
479+
-- === Asynchronous reset
480+
--
481+
-- In contrast to the synchronous version there is no single cycle delay from
482+
-- input reset assertion to output reset assertion. On startup it also asserts
483+
-- the reset for exactly the stretch period @n@.
484+
--
485+
-- >>> let rst = fromList [False, False, False, False, True, False, False, False, False]
486+
-- >>> let stretchResetBy2 = stretchReset @System clockGen enableGen (SNat @2)
487+
-- >>> sampleReset (stretchResetBy2 (unsafeFromActiveHigh rst))
488+
-- [True,True,False,False,True,True,True,False,False]
489+
--
490+
stretchReset
491+
:: forall dom n
492+
. KnownDomain dom
493+
=> Clock dom
494+
-> Enable dom
495+
-- ^ Global enable
496+
-> SNat n
497+
-- ^ Hold for /n/ cycles
498+
-> Reset dom
499+
-- ^ Reset to extend
500+
-> Reset dom
501+
stretchReset clk en n@SNat rst = case resetKind @dom of
502+
SSynchronous -> stretchResetSync clk en n rst
503+
SAsynchronous -> stretchResetAsync clk en n rst
504+
505+
stretchResetSync
506+
:: forall dom n
507+
. KnownDomain dom
508+
=> DomainResetKind dom ~ 'Synchronous
509+
=> Clock dom
510+
-> Enable dom
511+
-- ^ Global enable
512+
-> SNat n
513+
-- ^ Hold for /n/ cycles
514+
-> Reset dom
515+
-- ^ Reset to extend
516+
-> Reset dom
517+
stretchResetSync clk en sn@SNat rst = rstOut
518+
where
519+
isActiveHigh = case resetPolarity @dom of { SActiveHigh -> True; _ -> False }
520+
521+
rstOut = case compareSNat d1 sn of
522+
SNatGT -> rst
523+
SNatLE -> unsafeToReset (register clk rst en isActiveHigh rawRst)
524+
where
525+
counter :: Signal dom (Index (n + 1))
526+
counter = register clk rst en 0 (satSucc SatBound <$> counter)
527+
rawRst :: Signal dom Bool
528+
rawRst = case resetPolarity @dom of
529+
SActiveHigh -> (/=maxBound) <$> counter
530+
SActiveLow -> (==maxBound) <$> counter
531+
532+
stretchResetAsync
533+
:: forall dom n
534+
. KnownDomain dom
535+
=> DomainResetKind dom ~ 'Asynchronous
536+
=> Clock dom
537+
-> Enable dom
538+
-- ^ Global enable
539+
-> SNat n
540+
-- ^ Hold for /n/ cycles
541+
-> Reset dom
542+
-- ^ Reset to extend
543+
-> Reset dom
544+
stretchResetAsync clk en sn@SNat rst = rstOut
545+
where
546+
isActiveHigh = case resetPolarity @dom of { SActiveHigh -> True; _ -> False }
547+
rstOut = case (snatToInteger sn, compareSNat d2 sn) of
548+
(0, _) -> rst
549+
(1, _) -> unsafeToReset (register clk rst en isActiveHigh (pure (not isActiveHigh)))
550+
(_, SNatLE) -> unsafeToReset (register clk rst en isActiveHigh rawRst)
551+
where
552+
counter :: Signal dom (Index n)
553+
counter = register clk rst en 0 (satSucc SatBound <$> counter)
554+
rawRst :: Signal dom Bool
555+
rawRst = case resetPolarity @dom of
556+
SActiveHigh -> (/=maxBound) <$> counter
557+
SActiveLow -> (==maxBound) <$> counter
558+
(_, SNatGT) -> clashCompileError "stretchResetAsync: Impossbile! n =! 0 && n != 1 && 2 > n, Please report this at https://github.com/clash-lang/clash-compiler/issues."
559+
560+
436561
-- | Convert between different types of reset, adding a synchronizer when
437562
-- the domains are not the same. See 'resetSynchronizer' for further details
438563
-- about reset synchronization.

clash-prelude/src/Clash/Signal.hs

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Copyright : (C) 2013-2016, University of Twente,
33
2016-2019, Myrtle Software Ltd,
44
2017 , Google Inc.,
5-
2021-2024, QBayLogic B.V.
5+
2021-2026, QBayLogic B.V.
66
License : BSD2 (see the file LICENSE)
77
Maintainer : QBayLogic B.V. <devops@qbaylogic.com>
88
@@ -157,6 +157,7 @@ module Clash.Signal
157157
, resetSynchronizer
158158
, resetGlitchFilter
159159
, holdReset
160+
, stretchReset
160161
-- * Enabling
161162
, Enable
162163
, toEnable
@@ -1635,26 +1636,91 @@ 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+
-- | Hold/stretch reset for a number of cycles relative to an incoming reset
1640+
-- signal.
1641+
--
1642+
-- __NB__: 'holdReset' is unsafe for asynchronous resets. It can cause
1643+
-- spurious resets.
1644+
-- For synchronous resets the output is pessimistic since it is driven by
1645+
-- combinational logic.
1646+
-- You probably want to use 'stretchReset' which is safe but has slightly
1647+
-- different timing behaviour.
16391648
--
16401649
-- Example:
16411650
--
1642-
-- >>> sampleN @System 8 (unsafeToActiveHigh (holdReset (SNat @2)))
1643-
-- [True,True,True,False,False,False,False,False]
1651+
-- >>> holdResetBool = unsafeToActiveHigh . (holdReset @System)
1652+
-- >>> sampleN 8 (exposeReset (holdResetBool (SNat @2)) (resetGenN (SNat @3)))
1653+
-- [True,True,True,True,True,False,False,False]
16441654
--
16451655
-- 'holdReset' holds the reset for an additional 2 clock cycles for a total
1646-
-- of 3 clock cycles where the reset is asserted.
1656+
-- of 5 clock cycles where the reset is asserted. 'holdReset' also works on
1657+
-- intermediate assertions of the reset signal:
1658+
--
1659+
-- >>> let rst = fromList [True, False, False, False, True, False, False, False]
1660+
-- >>> sampleN 8 (exposeReset (holdResetBool (SNat @2)) (unsafeFromActiveHigh rst))
1661+
-- [True,True,True,False,True,True,True,False]
16471662
--
16481663
holdReset
16491664
:: forall dom m
16501665
. HiddenClockResetEnable dom
16511666
=> SNat m
1652-
-- ^ Hold for /m/ cycles, counting from the moment the incoming reset
1653-
-- signal becomes deasserted.
1667+
-- ^ Hold for /m/ cycles
16541668
-> Reset dom
16551669
holdReset m =
16561670
hideClockResetEnable (\clk rst en -> E.holdReset clk en m rst)
16571671

1672+
1673+
-- | Hold/stretch reset for a number of cycles relative to an incoming reset
1674+
-- signal. This is a safe replacement for `holdReset`. There are some
1675+
-- differences in behaviour for synchronous resets and asynchronous resets.
1676+
-- If used with a stretch of 0 the function is equivalent to doing nothing to
1677+
-- the reset.
1678+
--
1679+
-- === Synchronous resets
1680+
--
1681+
-- If used with a domain on a synchronous reset the assertion of the reset is
1682+
-- delayed by one cycle relative to the incoming reset. On startup the reset is
1683+
-- asserted for @(n + 1)@ cycles.
1684+
--
1685+
-- >>> stretchResetToBool = unsafeToActiveHigh . (stretchReset @XilinxSystem)
1686+
-- >>> sampleN 9 (exposeReset (stretchResetToBool (SNat @2)) (resetGenN (SNat @3)))
1687+
-- [True,True,True,True,True,True,False,False,False]
1688+
--
1689+
-- The reset assertion sequence is:
1690+
--
1691+
-- - The first cycle the output of the internal register is given.
1692+
-- - The next three cycles the reset is asserted due to the incoming reset
1693+
-- being asserted.
1694+
-- - The final two assertions are due to the reset being stretched by two cycles.
1695+
--
1696+
-- >>> let rst = fromList [False, False, False, True, False, False, False, False]
1697+
-- >>> sampleN 9 (exposeReset (stretchResetToBool (SNat @2)) (unsafeFromActiveHigh rst))
1698+
-- [True,True,True,False,True,True,True,False,False]
1699+
--
1700+
-- Here it shows that on startup the reset is asserted for @(n + 1) = 3@ cycles
1701+
-- On the fourth cycle the input reset is asserted. Notice the delay on
1702+
-- the output reset assertion and that is stretch by two cycles.
1703+
--
1704+
-- === Asynchronous reset
1705+
--
1706+
-- In contrast to the synchronous version there is no single cycle delay from
1707+
-- input reset assertion to output reset assertion. On startup it also asserts
1708+
-- the reset for exactly the stretch period @n@.
1709+
--
1710+
-- >>> stretchResetToBool = unsafeToActiveHigh . (stretchReset @System)
1711+
-- >>> let rst = fromList [False, False, False, False, True, False, False, False, False]
1712+
-- >>> sampleN 9 (exposeReset (stretchResetToBool (SNat @2)) (unsafeFromActiveHigh rst))
1713+
-- [True,True,False,False,True,True,True,False,False]
1714+
--
1715+
stretchReset
1716+
:: forall dom m
1717+
. HiddenClockResetEnable dom
1718+
=> SNat m
1719+
-- ^ Hold for /m/ cycles
1720+
-> Reset dom
1721+
stretchReset m =
1722+
hideClockResetEnable (\clk rst en -> E.stretchReset clk en m rst)
1723+
16581724
-- | Like 'fromList', but resets on reset and has a defined reset value.
16591725
--
16601726
-- >>> let rst = unsafeFromActiveHigh (fromList [True, True, False, False, True, False])

0 commit comments

Comments
 (0)