| // 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 |