Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/2026-01-08T16_58_32+01_00_add_registerSyncReset
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ADDED: `registerSyncReset` [#3115](https://github.com/clash-lang/clash-compiler/issues/3115)
1 change: 1 addition & 0 deletions changelog/2026-01-08T16_58_32+01_00_fix_holdreset
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FIXED: Fix `holdReset` glitch behaviour for asynchronous resets and wrong hold cycles for sync resets. [#3115](https://github.com/clash-lang/clash-compiler/issues/3115)
116 changes: 107 additions & 9 deletions clash-prelude/src/Clash/Explicit/Reset.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{-|
Copyright : (C) 2020-2023, QBayLogic B.V.,
Copyright : (C) 2020-2026, QBayLogic B.V.,
2022-2023, Google LLC
License : BSD2 (see the file LICENSE)
Maintainer : QBayLogic B.V. <devops@qbaylogic.com>
Expand All @@ -23,6 +23,7 @@ module Clash.Explicit.Reset
, resetGlitchFilter
, resetGlitchFilterWithReset
, unsafeResetGlitchFilter
, registerSyncReset
, holdReset
, convertReset
, noReset
Expand Down Expand Up @@ -399,20 +400,62 @@ resetGlitchFilter# SNat reg dffSync rstIn0 =
SActiveHigh -> True
SActiveLow -> False

-- | Hold reset for a number of cycles relative to an incoming reset signal.
-- | Register a synchronous reset signal.
--
-- `registerSyncReset` delays an incoming reset by one clock cycle using a
-- register. This can be useful to break combinational paths involving reset
-- logic.
--
-- __NB__: This is not a synchronizer. Use `resetSynchronizer` to synchronize
-- a reset.
--
-- Example:
--
-- >>> let sampleReset = sampleN 7 . unsafeToActiveHigh
-- >>> let rst = unsafeFromActiveHigh (fromList [False, True, False, False, True, False])
-- >>> sampleReset (registerSyncReset @XilinxSystem clockGen rst enableGen True)
-- [True,False,True,False,False,True,False]
--
registerSyncReset
:: forall dom
. KnownDomain dom
=> HasSynchronousReset dom
=> Clock dom
-> Reset dom
-> Enable dom
-> Bool
-- ^ Initial assert value of the register if supported by the domain.
-- If True the initial reset value is asserted.
-- If False the initial reset value is de-asserted.
-> Reset dom
registerSyncReset clk (unsafeFromReset -> rst) en initialValue = unsafeToReset outRst
where
intialRst :: Bool
intialRst =
case resetPolarity @dom of
SActiveHigh -> initialValue
SActiveLow -> not initialValue
outRst = delay clk en intialRst rst

-- | Hold reset for a number of cycles relative to an incoming reset
-- signal.
--
-- __NB__: The output of this function is combinational for @n > 0@ on domains
-- with a synchronous reset. Use `registerSyncReset` to add an output register if
-- desired.
--
-- Example:
--
-- >>> let sampleWithReset = sampleN 8 . unsafeToActiveHigh
-- >>> sampleWithReset (holdReset @System clockGen enableGen (SNat @2) (resetGenN (SNat @3)))
-- >>> let sampleReset = sampleN 8 . unsafeToActiveHigh
-- >>> sampleReset (holdReset @System clockGen enableGen (SNat @2) (resetGenN (SNat @3)))
-- [True,True,True,True,True,False,False,False]
--
-- 'holdReset' holds the reset for an additional 2 clock cycles for a total
-- of 5 clock cycles where the reset is asserted. 'holdReset' also works on
-- intermediate assertions of the reset signal:
--
-- >>> let rst = fromList [True, False, False, False, True, False, False, False]
-- >>> sampleWithReset (holdReset @System clockGen enableGen (SNat @2) (unsafeFromActiveHigh rst))
-- >>> sampleReset (holdReset @System clockGen enableGen (SNat @2) (unsafeFromActiveHigh rst))
-- [True,True,True,False,True,True,True,False]
--
holdReset
Expand All @@ -427,11 +470,66 @@ holdReset
-> Reset dom
-- ^ Reset to extend
-> Reset dom
holdReset clk en SNat rst =
unsafeFromActiveHigh ((/=maxBound) <$> counter)
holdReset clk en n rst = case resetKind @dom of
SSynchronous -> holdResetSync clk en n rst
SAsynchronous -> holdResetAsync clk en n rst

holdResetSync
:: forall dom n
. KnownDomain dom
=> DomainResetKind dom ~ 'Synchronous
=> Clock dom
-> Enable dom
-- ^ Global enable
-> SNat n
-- ^ Hold for /n/ cycles
-> Reset dom
-- ^ Reset to extend
-> Reset dom
holdResetSync clk en sn@SNat rst = rstOut
where
rstOut = case snatToInteger sn of
0 -> rst
1 -> orReset rst (unsafeToReset regReset)
where
isActiveHigh = case resetPolarity @dom of { SActiveHigh -> True; _ -> False }
regReset = register clk rst en isActiveHigh (pure (not isActiveHigh))
_ -> orReset rst (unsafeToReset rawRst)
where
counter :: Signal dom (Index (n + 1))
counter = register clk rst en 0 (satSucc SatBound <$> counter)
rawRst :: Signal dom Bool
rawRst = case resetPolarity @dom of
SActiveHigh -> (/=maxBound) <$> counter
SActiveLow -> (==maxBound) <$> counter

holdResetAsync
:: forall dom n
. KnownDomain dom
=> DomainResetKind dom ~ 'Asynchronous
=> Clock dom
-> Enable dom
-- ^ Global enable
-> SNat n
-- ^ Hold for /n/ cycles
-> Reset dom
-- ^ Reset to extend
-> Reset dom
holdResetAsync clk en sn@SNat rst = rstOut
where
counter :: Signal dom (Index (n+1))
counter = register clk rst en 0 (satSucc SatBound <$> counter)
isActiveHigh = case resetPolarity @dom of { SActiveHigh -> True; _ -> False }
rstOut = case toUNat sn of
UZero -> rst
USucc UZero -> unsafeToReset (register clk rst en isActiveHigh (pure (not isActiveHigh)))
USucc (USucc _) -> unsafeToReset (register clk rst en isActiveHigh rawRst)
where
counter :: Signal dom (Index n)
counter = register clk rst en 0 (satSucc SatBound <$> counter)
rawRst :: Signal dom Bool
rawRst = case resetPolarity @dom of
SActiveHigh -> (/=maxBound) <$> counter
SActiveLow -> (==maxBound) <$> counter


-- | Convert between different types of reset, adding a synchronizer when
-- the domains are not the same. See 'resetSynchronizer' for further details
Expand Down
54 changes: 47 additions & 7 deletions clash-prelude/src/Clash/Signal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Copyright : (C) 2013-2016, University of Twente,
2016-2019, Myrtle Software Ltd,
2017 , Google Inc.,
2021-2024, QBayLogic B.V.
2021-2026, QBayLogic B.V.
License : BSD2 (see the file LICENSE)
Maintainer : QBayLogic B.V. <devops@qbaylogic.com>

Expand Down Expand Up @@ -156,6 +156,7 @@ module Clash.Signal
, unsafeFromActiveLow
, resetSynchronizer
, resetGlitchFilter
, registerSyncReset
, holdReset
-- * Enabling
, Enable
Expand Down Expand Up @@ -1635,22 +1636,61 @@ testFor
-> Property
testFor n s = property (and (Clash.Signal.sampleN n s))

-- | Hold reset for a number of cycles relative to an implicit reset signal.

-- | Register a synchronous reset signal.
--
-- `registerSyncReset` delays an incoming reset by one clock cycle using a
-- register. This can be useful to break combinational paths involving reset
-- logic.
--
-- __NB__: This is not a synchronizer. Use `resetSynchronizer` to synchronize
-- a reset.
--
-- Example:
--
-- >>> registerSyncResetBool = unsafeToActiveHigh . (registerSyncReset @XilinxSystem)
-- >>> let rst = unsafeFromActiveHigh (fromList [False, True, False, False, True, False])
-- >>> sampleN 7 (exposeReset (registerSyncResetBool True) rst)
-- [True,False,True,False,False,True,False]
--
registerSyncReset
:: forall dom
. HiddenClockResetEnable dom
=> KnownDomain dom
=> DomainResetKind dom ~ 'Synchronous
=> Bool
-- ^ Initial assert value of the register if supported by the domain.
-- If True the initial reset value is asserted.
-- If False the initial reset value is de-asserted.
-> Reset dom
registerSyncReset initialValue = hideClockResetEnable E.registerSyncReset initialValue

-- | Hold reset for a number of cycles relative to an incoming reset
-- signal.
--
-- __NB__: The output of this function is combinational for @n > 1@ on domains
-- with a synchronous reset. Use `registerSyncReset` to add an output register if
-- desired.
--
-- Example:
--
-- >>> sampleN @System 8 (unsafeToActiveHigh (holdReset (SNat @2)))
-- [True,True,True,False,False,False,False,False]
-- >>> holdResetBool = unsafeToActiveHigh . (holdReset @System)
-- >>> sampleN 8 (exposeReset (holdResetBool (SNat @2)) (resetGenN (SNat @3)))
-- [True,True,True,True,True,False,False,False]
--
-- 'holdReset' holds the reset for an additional 2 clock cycles for a total
-- of 3 clock cycles where the reset is asserted.
-- of 5 clock cycles where the reset is asserted. 'holdReset' also works on
-- intermediate assertions of the reset signal:
--
-- >>> let rst = fromList [True, False, False, False, True, False, False, False]
-- >>> sampleN 8 (exposeReset (holdResetBool (SNat @2)) (unsafeFromActiveHigh rst))
-- [True,True,True,False,True,True,True,False]
--
holdReset
:: forall dom m
. HiddenClockResetEnable dom
=> SNat m
-- ^ Hold for /m/ cycles, counting from the moment the incoming reset
-- signal becomes deasserted.
-- ^ Hold for /m/ cycles
-> Reset dom
holdReset m =
hideClockResetEnable (\clk rst en -> E.holdReset clk en m rst)
Expand Down
2 changes: 2 additions & 0 deletions tests/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,8 @@ runClashTest = defaultMain
, "testBench53"]}
in runTest "RWMultiTop" _opts
]
, runTest "HoldResetAsync" def
, runTest "HoldResetSync" def
, runTest "ResetGen" def
,
-- TODO: we do not support memory files in Vivado
Expand Down
37 changes: 37 additions & 0 deletions tests/shouldwork/Signal/HoldResetAsync.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{-# LANGUAGE CPP #-}

module HoldResetAsync where

import Clash.Explicit.Prelude
import Clash.Explicit.Testbench

topEntity
:: Clock XilinxSystem
-> Signal XilinxSystem (Bool, Bool, Bool, Bool)
topEntity clk = bundle (rBool, r0, r1, r2)
where
r = resetGenN (SNat @3)
rBool = unsafeToActiveHigh r
r0 = unsafeToActiveHigh (holdReset clk enableGen (SNat @0) r)
r1 = unsafeToActiveHigh (holdReset clk enableGen (SNat @1) r)
r2 = unsafeToActiveHigh (holdReset clk enableGen (SNat @2) r)
{-# OPAQUE topEntity #-}

testBench :: Signal XilinxSystem Bool
testBench = done
where
expectedOutput =
outputVerifier'
clk
rst
-- Note that outputVerifier' skips first sample
( (True, True, True, True)
:> (True, True, True, True)
:> (False, False, True, True)
:> (False, False, False, True)
:> (False, False, False, False)
:> Nil )

done = expectedOutput (topEntity clk)
clk = tbClockGen (not <$> done)
rst = resetGen
37 changes: 37 additions & 0 deletions tests/shouldwork/Signal/HoldResetSync.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{-# LANGUAGE CPP #-}

module HoldResetSync where

import Clash.Explicit.Prelude
import Clash.Explicit.Testbench

topEntity
:: Clock System
-> Signal System (Bool, Bool, Bool, Bool)
topEntity clk = bundle (rBool, r0, r1, r2)
where
r = resetGenN (SNat @3)
rBool = unsafeToActiveHigh r
r0 = unsafeToActiveHigh (holdReset clk enableGen (SNat @0) r)
r1 = unsafeToActiveHigh (holdReset clk enableGen (SNat @1) r)
r2 = unsafeToActiveHigh (holdReset clk enableGen (SNat @2) r)
{-# OPAQUE topEntity #-}

testBench :: Signal System Bool
testBench = done
where
expectedOutput =
outputVerifier'
clk
rst
-- Note that outputVerifier' skips first sample
( (True, True, True, True)
:> (True, True, True, True)
:> (False, False, True, True)
:> (False, False, False, True)
:> (False, False, False, False)
:> Nil )

done = expectedOutput (topEntity clk)
clk = tbClockGen (not <$> done)
rst = resetGen
17 changes: 8 additions & 9 deletions tests/shouldwork/Signal/ResetGen.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import Clash.Explicit.Testbench

topEntity
:: Clock System
-> Signal System (Bool, Bool)
topEntity clk = bundle (unsafeToActiveHigh r, unsafeToActiveHigh r')
-> Signal System Bool
topEntity clk = bundle (unsafeToActiveHigh r)
where
r = resetGenN (SNat @3)
r' = holdReset clk enableGen (SNat @2) r
{-# OPAQUE topEntity #-}

testBench :: Signal System Bool
Expand All @@ -22,12 +21,12 @@ testBench = done
clk
rst
-- Note that outputVerifier' skips first sample
( (True, True)
:> (True, True)
:> (False, True)
:> (False, True)
:> (False, False)
:> (False, False)
( True
:> True
:> False
:> False
:> False
:> False
:> Nil )

done = expectedOutput (topEntity clk)
Expand Down