blob: 310c60950c4fe9a7dbeae15b699909ce1c50be72 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Debounce and detector module.
//
// The module is able to detect either low/high levels, or transitions to low/high levels (edges).
// The event type to trigger on can be configured via the EventType parameter.
//
// Whenever a trigger event is seen at the input (for instance a transition to low) the module first
// backs off for the amount of debounce cycles configured through cfg_debounce_timer_i. Then the
// module samples the input value again and if it still matches the active level (low in this
// example), the module goes into detection mode. In detection mode, the module checks whether the
// trigger signal remains stable for the number of cycles configured via cfg_detect_timer_i. If it
// does, event_detected_pulse_o will be pulsed high for one cycle and event_detected_o will be held
// high until the input trigger value changes.
//
// Note that if the module is configured as "Sticky", the module needs to be explicitly disabled
// and enabled again in order to reset the internal FSM into its idle state.
//
module sysrst_ctrl_detect
import sysrst_ctrl_pkg::*;
#(
// The module only contains one counter to implement both timers.
// The width of that counter is set to the maximum of the two values below.
parameter int unsigned DebounceTimerWidth = 16,
parameter int unsigned DetectTimerWidth = 32,
// Determines the event type that the detector should be sensitive to.
parameter event_t EventType = LowLevel,
// If this parameter is set to 1, the l2h_detected_o and h2l_detected_o conditions can only be
// reset by disabling the corresponding detector (cfg_l2h_en_i or cfg_h2l_en_i).
parameter bit Sticky = 0
) (
input clk_i,
input rst_ni,
// Trigger input signal.
input trigger_i,
// Debounce and detection timer thresholds.
input [DebounceTimerWidth-1:0] cfg_debounce_timer_i,
input [DetectTimerWidth-1:0] cfg_detect_timer_i,
// Enables the detector module.
// Note that the internal state machine can be reset to its idle state by disabling the module.
input cfg_enable_i,
// Held high after detection of the event as long as the trigger level is correct.
output logic event_detected_o,
// Pulsed high for one cycle when the event is detected.
output logic event_detected_pulse_o
);
//////////////////
// Detect Event //
//////////////////
// This just decodes the active level needed for detection.
logic trigger_active;
if (EventType inside {LowLevel, EdgeToLow}) begin : gen_trigger_active_low
assign trigger_active = (trigger_i == 1'b0);
end else begin : gen_trigger_active_high
assign trigger_active = (trigger_i == 1'b1);
end
// In case of edge events, we also need to detect the transition.
logic trigger_event;
if (EventType inside {EdgeToLow, EdgeToHigh}) begin : gen_trigger_event_edge
// This flop is always active, no matter the enable state.
logic trigger_active_q;
always_ff @(posedge clk_i or negedge rst_ni) begin : p_trigger_reg
if (!rst_ni) begin
trigger_active_q <= 1'b0;
end else begin
trigger_active_q <= trigger_active;
end
end
assign trigger_event = trigger_active & ~trigger_active_q;
// In case of level events, the event is equal to the level being active.
end else begin : gen_trigger_event_level
assign trigger_event = trigger_active;
end
/////////////////
// Timer Logic //
/////////////////
// Take the maximum width of both timer values.
localparam int unsigned TimerWidth =
(DetectTimerWidth > DebounceTimerWidth) ? DetectTimerWidth : DebounceTimerWidth;
logic cnt_en, cnt_clr;
logic [TimerWidth-1:0] cnt_d, cnt_q;
assign cnt_d = (cnt_clr) ? '0 :
(cnt_en) ? cnt_q + 1'b1 :
cnt_q;
logic cnt_done, thresh_sel;
logic [TimerWidth-1:0] thresh;
assign thresh = (thresh_sel) ? TimerWidth'(cfg_detect_timer_i) :
TimerWidth'(cfg_debounce_timer_i);
assign cnt_done = (cnt_q >= thresh);
always_ff @(posedge clk_i or negedge rst_ni) begin : p_cnt_reg
if (!rst_ni) begin
cnt_q <= '0;
end else begin
cnt_q <= cnt_d;
end
end
/////////
// FSM //
/////////
typedef enum logic [1:0] {
IdleSt,
DebounceSt,
DetectSt,
StableSt
} state_t;
state_t state_d, state_q;
always_comb begin : p_fsm
state_d = state_q;
// Counter controls (clear has priority).
cnt_clr = 1'b0;
cnt_en = 1'b0;
// Detected outputs
event_detected_o = 1'b0;
event_detected_pulse_o = 1'b0;
// Threshold select
// 0: debounce
// 1: detect
thresh_sel = 1'b0;
unique case (state_q)
////////////////////////////////////////////
// We are waiting for the event to occur.
// This can be either a specific level or edge,
// depending on the configuration.
IdleSt: begin
// Stay here if the detector is disabled.
if (trigger_event && cfg_enable_i) begin
state_d = DebounceSt;
cnt_en = 1'b1;
end
end
////////////////////////////////////////////
// If an event has occurred, we back off for
// the amount of debounce cycles configured.
// Once the timer has expired, we sample the
// signal again and check whether it has the
// correct level. If so, we move on to the
// detection stage, otherwise we fall back.
DebounceSt: begin
cnt_en = 1'b1;
// Unconditionally go back to idle if the detector is disabled.
if (!cfg_enable_i) begin
state_d = IdleSt;
cnt_clr = 1'b1;
end else if (cnt_done) begin
cnt_clr = 1'b1;
if (trigger_active) begin
state_d = DetectSt;
end else begin
state_d = IdleSt;
end
end
end
////////////////////////////////////////////
// Once the debounce period has passed, we
// check whether the signal remains stable
// throughout the entire detection period.
// If it is not stable at any cycle, we fall
// back to idle.
DetectSt: begin
thresh_sel = 1'b1;
cnt_en = 1'b1;
// Go back to idle if either the trigger level is not active anymore, or if the
// detector is disabled.
if (!cfg_enable_i || !trigger_active) begin
state_d = IdleSt;
cnt_clr = 1'b1;
// If the trigger is active, count up.
end else begin
if (cnt_done) begin
state_d = StableSt;
cnt_clr = 1'b1;
event_detected_o = 1'b1;
event_detected_pulse_o = 1'b1;
end
end
end
////////////////////////////////////////////
// At this point we have detected the event
// and monitor whether the signal remains stable.
StableSt: begin
// Go back to idle if either the trigger level is not active anymore, or if the detector is
// disabled. Note that if the detector is sticky, it has to be explicitly disabled in order
// to go back to the idle state.
if (!cfg_enable_i || (!trigger_active && !Sticky)) begin
state_d = IdleSt;
// Otherwise keep the event detected output signal high.
end else begin
event_detected_o = 1'b1;
end
end
////////////////////////////////////////////
// This is a full case statement
default: ;
endcase // state_q
end
always_ff @(posedge clk_i or negedge rst_ni) begin : p_fsm_reg
if (!rst_ni) begin
state_q <= IdleSt;
end else begin
state_q <= state_d;
end
end
////////////////
// Assertions //
////////////////
`ASSERT(DisabledIdleSt_A, !cfg_enable_i |=> state_q == IdleSt)
`ASSERT(DisabledNoDetection_A, !cfg_enable_i |-> !event_detected_o && !event_detected_pulse_o)
if (EventType inside {LowLevel, EdgeToLow}) begin : gen_low_level_sva
`ASSERT(LowLevelEvent_A, !trigger_i === trigger_active)
end else begin: gen_high_level_sva
`ASSERT(HighLevelEvent_A, trigger_i === trigger_active)
end
if (EventType == LowLevel) begin : gen_low_event_sva
`ASSERT(LowLevelEvent_A, !trigger_i === trigger_event)
end else if (EventType == HighLevel) begin : gen_high_event_sva
`ASSERT(HighLevelEvent_A, trigger_i === trigger_event)
end else if (EventType == EdgeToLow) begin : gen_edge_to_low_event_sva
`ASSERT(EdgeToLowEvent_A, !trigger_i && $past(trigger_i) |-> trigger_event)
end else if (EventType == EdgeToLow) begin : gen_edge_to_high_event_sva
`ASSERT(EdgeToHighEvent_A, trigger_i && !$past(trigger_i) |-> trigger_event)
end
`ASSERT(EnterDebounceSt_A,
state_q == IdleSt && cfg_enable_i && trigger_event
|=>
state_q == DebounceSt)
`ASSERT(EnterDetectSt_A,
state_q == DebounceSt && cnt_q >= cfg_debounce_timer_i && trigger_active && cfg_enable_i
|=>
state_q == DetectSt)
`ASSERT(DetectStDropOut_A,
state_q == DetectSt && !trigger_active && cfg_enable_i
|=>
state_q == IdleSt)
`ASSERT(EnterStableSt_A,
state_q == DetectSt && cnt_q >= cfg_detect_timer_i && trigger_active && cfg_enable_i
|=>
state_q == StableSt)
`ASSERT(StayInStableSt,
state_q == StableSt && trigger_active && cfg_enable_i
|=>
state_q == StableSt)
if (Sticky) begin : gen_sticky_sva
`ASSERT(StableStDropOut_A,
state_q == StableSt && cfg_enable_i && !trigger_active
|=>
state_q == StableSt)
end else begin : gen_not_sticky_sva
`ASSERT(StableStDropOut_A,
state_q == StableSt && cfg_enable_i && !trigger_active
|=>
state_q == IdleSt)
end
`ASSERT(DetectedOut_A,
state_q == StableSt && trigger_active && cfg_enable_i ||
state_q == DetectSt && cnt_q >= cfg_detect_timer_i && trigger_active && cfg_enable_i
|->
event_detected_o)
`ASSERT(DetectedPulseOut_A,
state_q == DetectSt && cnt_q >= cfg_detect_timer_i && trigger_active && cfg_enable_i
|->
event_detected_pulse_o)
`ASSERT(PulseIsPulse_A,
event_detected_pulse_o |=> !event_detected_pulse_o)
// Counter does not wrap around unless it is explicitly cleared
`ASSERT(CntNoWrap_A,
!cnt_clr
|=>
cnt_q >= $past(cnt_q))
`ASSERT(CntClr_A,
cnt_clr
|=>
cnt_q == '0)
`ASSERT(CntIncr_A,
cnt_en && !cnt_clr
|=>
cnt_q == $past(cnt_q) + 1)
endmodule : sysrst_ctrl_detect