Skip to content

Commit 3dd15c1

Browse files
committed
clash-prelude: Fix holdReset glitch behaviour
for async resets add `registerSyncReset`
1 parent d0f65c4 commit 3dd15c1

File tree

7 files changed

+244
-25
lines changed

7 files changed

+244
-25
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ADDED: `registerSyncReset` [#3115](https://github.com/clash-lang/clash-compiler/issues/3115)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FIXED: Fix `holdReset` glitch behaviour for asynchronous resets [#3115](https://github.com/clash-lang/clash-compiler/issues/3115)

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

Lines changed: 109 additions & 9 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>
@@ -23,6 +23,7 @@ module Clash.Explicit.Reset
2323
, resetGlitchFilter
2424
, resetGlitchFilterWithReset
2525
, unsafeResetGlitchFilter
26+
, registerSyncReset
2627
, holdReset
2728
, convertReset
2829
, noReset
@@ -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,62 @@ 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+
-- | Register a synchronous reset signal.
406+
--
407+
-- `registerSyncReset` delays an incoming reset by one clock cycle using a
408+
-- register. This can be useful to break combinational paths involving reset
409+
-- logic.
410+
--
411+
-- __NB__: This is not a synchronizer. Use `resetSynchronizer` to synchronize
412+
-- a reset.
413+
--
414+
-- Example:
415+
--
416+
-- >>> let sampleReset = sampleN 7 . unsafeToActiveHigh
417+
-- >>> let rst = unsafeFromActiveHigh (fromList [False, True, False, False, True, False])
418+
-- >>> sampleReset (registerSyncReset @XilinxSystem clockGen rst enableGen True)
419+
-- [True,False,True,False,False,True,False]
420+
--
421+
registerSyncReset
422+
:: forall dom
423+
. KnownDomain dom
424+
=> DomainResetKind dom ~ 'Synchronous
425+
=> Clock dom
426+
-> Reset dom
427+
-> Enable dom
428+
-> Bool
429+
-- ^ Initial assert value of the register if supported by the domain.
430+
-- If True the initial reset value is asserted.
431+
-- If False the initial reset value is de-asserted.
432+
-> Reset dom
433+
registerSyncReset clk (unsafeFromReset -> rst) en initialValue = unsafeToReset outRst
434+
where
435+
intialRst :: Bool
436+
intialRst =
437+
case resetPolarity @dom of
438+
SActiveHigh -> initialValue
439+
SActiveLow -> not initialValue
440+
outRst = delay clk en intialRst rst
441+
442+
-- | Hold/stretch reset for a number of cycles relative to an incoming reset
443+
-- signal.
444+
--
445+
-- __NB__: The output of this function is combinational for @n > 1@ on domains
446+
-- with a synchronous reset. Use `registerSyncReset` to add an output register if
447+
-- desired.
403448
--
404449
-- Example:
405450
--
406-
-- >>> let sampleWithReset = sampleN 8 . unsafeToActiveHigh
407-
-- >>> sampleWithReset (holdReset @System clockGen enableGen (SNat @2) (resetGenN (SNat @3)))
451+
-- >>> let sampleReset = sampleN 8 . unsafeToActiveHigh
452+
-- >>> sampleReset (holdReset @System clockGen enableGen (SNat @2) (resetGenN (SNat @3)))
408453
-- [True,True,True,True,True,False,False,False]
409454
--
410455
-- 'holdReset' holds the reset for an additional 2 clock cycles for a total
411456
-- of 5 clock cycles where the reset is asserted. 'holdReset' also works on
412457
-- intermediate assertions of the reset signal:
413458
--
414459
-- >>> let rst = fromList [True, False, False, False, True, False, False, False]
415-
-- >>> sampleWithReset (holdReset @System clockGen enableGen (SNat @2) (unsafeFromActiveHigh rst))
460+
-- >>> sampleReset (holdReset @System clockGen enableGen (SNat @2) (unsafeFromActiveHigh rst))
416461
-- [True,True,True,False,True,True,True,False]
417462
--
418463
holdReset
@@ -427,11 +472,66 @@ holdReset
427472
-> Reset dom
428473
-- ^ Reset to extend
429474
-> Reset dom
430-
holdReset clk en SNat rst =
431-
unsafeFromActiveHigh ((/=maxBound) <$> counter)
475+
holdReset clk en n rst = case resetKind @dom of
476+
SSynchronous -> holdResetSync clk en n rst
477+
SAsynchronous -> holdResetAsync clk en n rst
478+
479+
holdResetSync
480+
:: forall dom n
481+
. KnownDomain dom
482+
=> DomainResetKind dom ~ 'Synchronous
483+
=> Clock dom
484+
-> Enable dom
485+
-- ^ Global enable
486+
-> SNat n
487+
-- ^ Hold for /n/ cycles
488+
-> Reset dom
489+
-- ^ Reset to extend
490+
-> Reset dom
491+
holdResetSync clk en sn@SNat rst = rstOut
432492
where
433-
counter :: Signal dom (Index (n+1))
434-
counter = register clk rst en 0 (satSucc SatBound <$> counter)
493+
isActiveHigh = case resetPolarity @dom of { SActiveHigh -> True; _ -> False }
494+
495+
rstOut = case snatToInteger sn of
496+
0 -> rst
497+
1 -> unsafeToReset (register clk rst en isActiveHigh (unsafeFromReset rst))
498+
_ -> unsafeToReset rawRst
499+
where
500+
counter :: Signal dom (Index (n + 1))
501+
counter = register clk rst en 0 (satSucc SatBound <$> counter)
502+
rawRst :: Signal dom Bool
503+
rawRst = case resetPolarity @dom of
504+
SActiveHigh -> (/=maxBound) <$> counter
505+
SActiveLow -> (==maxBound) <$> counter
506+
507+
holdResetAsync
508+
:: forall dom n
509+
. KnownDomain dom
510+
=> DomainResetKind dom ~ 'Asynchronous
511+
=> Clock dom
512+
-> Enable dom
513+
-- ^ Global enable
514+
-> SNat n
515+
-- ^ Hold for /n/ cycles
516+
-> Reset dom
517+
-- ^ Reset to extend
518+
-> Reset dom
519+
holdResetAsync clk en sn@SNat rst = rstOut
520+
where
521+
isActiveHigh = case resetPolarity @dom of { SActiveHigh -> True; _ -> False }
522+
rstOut = case (snatToInteger sn, compareSNat d2 sn) of
523+
(0, _) -> rst
524+
(1, _) -> unsafeToReset (register clk rst en isActiveHigh (pure (not isActiveHigh)))
525+
(_, SNatLE) -> unsafeToReset (register clk rst en isActiveHigh rawRst)
526+
where
527+
counter :: Signal dom (Index n)
528+
counter = register clk rst en 0 (satSucc SatBound <$> counter)
529+
rawRst :: Signal dom Bool
530+
rawRst = case resetPolarity @dom of
531+
SActiveHigh -> (/=maxBound) <$> counter
532+
SActiveLow -> (==maxBound) <$> counter
533+
(_, SNatGT) -> clashCompileError "holdResetAsync: Impossbile! n =! 0 && n != 1 && 2 > n, Please report this at https://github.com/clash-lang/clash-compiler/issues."
534+
435535

436536
-- | Convert between different types of reset, adding a synchronizer when
437537
-- the domains are not the same. See 'resetSynchronizer' for further details

clash-prelude/src/Clash/Signal.hs

Lines changed: 47 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
@@ -156,6 +156,7 @@ module Clash.Signal
156156
, unsafeFromActiveLow
157157
, resetSynchronizer
158158
, resetGlitchFilter
159+
, registerSyncReset
159160
, holdReset
160161
-- * Enabling
161162
, Enable
@@ -1635,22 +1636,61 @@ 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+
1640+
-- | Register a synchronous reset signal.
1641+
--
1642+
-- `registerSyncReset` delays an incoming reset by one clock cycle using a
1643+
-- register. This can be useful to break combinational paths involving reset
1644+
-- logic.
1645+
--
1646+
-- __NB__: This is not a synchronizer. Use `resetSynchronizer` to synchronize
1647+
-- a reset.
1648+
--
1649+
-- Example:
1650+
--
1651+
-- >>> registerSyncResetBool = unsafeToActiveHigh . (registerSyncReset @XilinxSystem)
1652+
-- >>> let rst = unsafeFromActiveHigh (fromList [False, True, False, False, True, False])
1653+
-- >>> sampleN 8 (exposeReset (registerSyncResetBool True) rst)
1654+
-- [True,False,True,False,False,True,False]
1655+
--
1656+
registerSyncReset
1657+
:: forall dom
1658+
. HiddenClockResetEnable dom
1659+
=> KnownDomain dom
1660+
=> DomainResetKind dom ~ 'Synchronous
1661+
=> Bool
1662+
-- ^ Initial assert value of the register if supported by the domain.
1663+
-- If True the initial reset value is asserted.
1664+
-- If False the initial reset value is de-asserted.
1665+
-> Reset dom
1666+
registerSyncReset initialValue = hideClockResetEnable E.registerSyncReset initialValue
1667+
1668+
-- | Hold/stretch reset for a number of cycles relative to an incoming reset
1669+
-- signal.
1670+
--
1671+
-- __NB__: The output of this function is combinational for @n > 1@ on domains
1672+
-- with a synchronous reset. Use `registerSyncReset` to add an output register if
1673+
-- desired.
16391674
--
16401675
-- Example:
16411676
--
1642-
-- >>> sampleN @System 8 (unsafeToActiveHigh (holdReset (SNat @2)))
1643-
-- [True,True,True,False,False,False,False,False]
1677+
-- >>> holdResetBool = unsafeToActiveHigh . (holdReset @System)
1678+
-- >>> sampleN 8 (exposeReset (holdResetBool (SNat @2)) (resetGenN (SNat @3)))
1679+
-- [True,True,True,True,True,False,False,False]
16441680
--
16451681
-- 'holdReset' holds the reset for an additional 2 clock cycles for a total
1646-
-- of 3 clock cycles where the reset is asserted.
1682+
-- of 5 clock cycles where the reset is asserted. 'holdReset' also works on
1683+
-- intermediate assertions of the reset signal:
1684+
--
1685+
-- >>> let rst = fromList [True, False, False, False, True, False, False, False]
1686+
-- >>> sampleN 8 (exposeReset (holdResetBool (SNat @2)) (unsafeFromActiveHigh rst))
1687+
-- [True,True,True,False,True,True,True,False]
16471688
--
16481689
holdReset
16491690
:: forall dom m
16501691
. HiddenClockResetEnable dom
16511692
=> SNat m
1652-
-- ^ Hold for /m/ cycles, counting from the moment the incoming reset
1653-
-- signal becomes deasserted.
1693+
-- ^ Hold for /m/ cycles
16541694
-> Reset dom
16551695
holdReset m =
16561696
hideClockResetEnable (\clk rst en -> E.holdReset clk en m rst)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{-# LANGUAGE CPP #-}
2+
3+
module ResetGen where
4+
5+
import Clash.Explicit.Prelude
6+
import Clash.Explicit.Testbench
7+
8+
topEntity
9+
:: Clock System
10+
-> Signal System (Bool, Bool, Bool)
11+
topEntity clk = bundle (r0, r1, r2)
12+
where
13+
r = fromList [True, False, False, False, True, False, False, False]
14+
r0 = unsafeToActiveHigh (holdReset clk enableGen (SNat @0) r)
15+
r1 = unsafeToActiveHigh (holdReset clk enableGen (SNat @1) r)
16+
r2 = unsafeToActiveHigh (holdReset clk enableGen (SNat @2) r)
17+
{-# OPAQUE topEntity #-}
18+
19+
testBench :: Signal System Bool
20+
testBench = done
21+
where
22+
expectedOutput =
23+
outputVerifier'
24+
clk
25+
rst
26+
-- Note that outputVerifier' skips first sample
27+
( (True, True, True)
28+
:> (False, True, True)
29+
:> (False, False, True)
30+
:> (False, False, False)
31+
:> (True, True, True)
32+
:> (False, True, True)
33+
:> (False, False, True)
34+
:> (False, False, False)
35+
:> Nil )
36+
37+
done = expectedOutput (topEntity clk)
38+
clk = tbClockGen (not <$> done)
39+
rst = resetGen
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{-# LANGUAGE CPP #-}
2+
3+
module ResetGen where
4+
5+
import Clash.Explicit.Prelude
6+
import Clash.Explicit.Testbench
7+
8+
topEntity
9+
:: Clock XilinxSystem
10+
-> Signal XilinxSystem (Bool, Bool, Bool)
11+
topEntity clk = bundle (r0, r1, r2)
12+
where
13+
r = fromList [True, False, False, False, True, False, False, False]
14+
r0 = unsafeToActiveHigh (holdReset clk enableGen (SNat @0) r)
15+
r1 = unsafeToActiveHigh (holdReset clk enableGen (SNat @1) r)
16+
r2 = unsafeToActiveHigh (holdReset clk enableGen (SNat @2) r)
17+
{-# OPAQUE topEntity #-}
18+
19+
testBench :: Signal XilinxSystem Bool
20+
testBench = done
21+
where
22+
expectedOutput =
23+
outputVerifier'
24+
clk
25+
rst
26+
-- Note that outputVerifier' skips first sample
27+
( (True, True, True)
28+
:> (False, True, True)
29+
:> (False, False, True)
30+
:> (False, False, False)
31+
:> (True, True, True)
32+
:> (False, True, True)
33+
:> (False, False, True)
34+
:> (False, False, False)
35+
:> Nil )
36+
37+
done = expectedOutput (topEntity clk)
38+
clk = tbClockGen (not <$> done)
39+
rst = resetGen

tests/shouldwork/Signal/ResetGen.hs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@ import Clash.Explicit.Testbench
77

88
topEntity
99
:: Clock System
10-
-> Signal System (Bool, Bool)
11-
topEntity clk = bundle (unsafeToActiveHigh r, unsafeToActiveHigh r')
10+
-> Signal System Bool
11+
topEntity clk = bundle (unsafeToActiveHigh r)
1212
where
1313
r = resetGenN (SNat @3)
14-
r' = holdReset clk enableGen (SNat @2) r
1514
{-# OPAQUE topEntity #-}
1615

1716
testBench :: Signal System Bool
@@ -22,12 +21,12 @@ testBench = done
2221
clk
2322
rst
2423
-- Note that outputVerifier' skips first sample
25-
( (True, True)
26-
:> (True, True)
27-
:> (False, True)
28-
:> (False, True)
29-
:> (False, False)
30-
:> (False, False)
24+
( True
25+
:> True
26+
:> False
27+
:> False
28+
:> False
29+
:> False
3130
:> Nil )
3231

3332
done = expectedOutput (topEntity clk)

0 commit comments

Comments
 (0)