blob: 3147fd2602465d4a10968e3fd2a81f549f494e3c [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// This module implements the escalation timer, which times the four escalation
// phases. There are two mechanisms that can trigger the escalation protocol:
//
// 1) via accum_trigger_i, which will be asserted once the accumulator value
// exceeds a programmable amount of alert occurences.
//
// 2) via an interrupt timeout, if this is enabled. If this functionality is
// enabled, the internal escalation counter is reused to check whether the
// interrupt times out. If it does time out, the outcome is the same as if
// accum_trigger_i where asserted.
//
// Note that escalation always takes precedence over the interrupt timeout.
//
`include "prim_assert.sv"
module alert_handler_esc_timer import alert_pkg::*; (
input clk_i,
input rst_ni,
input en_i, // enables timeout/escalation
input clr_i, // aborts escalation
input accu_trig_i, // this triggers escalation
input accu_fail_i, // this moves the FSM into a terminal error state
input timeout_en_i, // enables timeout
input [EscCntDw-1:0] timeout_cyc_i, // interrupt timeout. 0 = disabled
input [N_ESC_SEV-1:0] esc_en_i, // escalation signal enables
input [N_ESC_SEV-1:0]
[PHASE_DW-1:0] esc_map_i, // escalation signal / phase map
input [N_PHASES-1:0]
[EscCntDw-1:0] phase_cyc_i, // cycle counts of individual phases
input [PHASE_DW-1:0] crashdump_phase_i, // determines when to assert latch_crashdump_o
output logic latch_crashdump_o, // asserted when entering escalation
output logic esc_trig_o, // asserted if escalation triggers
output logic [EscCntDw-1:0] esc_cnt_o, // current timeout / escalation count
output logic [N_ESC_SEV-1:0] esc_sig_req_o, // escalation signal outputs
// current state output
// 000: idle, 001: irq timeout counting 100: phase0, 101: phase1, 110: phase2, 111: phase3
output cstate_e esc_state_o
);
////////////////////
// Tandem Counter //
////////////////////
// We employ two redundant counters to guard against FI attacks.
// If any of the two is glitched and the two counter states do not agree,
// the FSM below is moved into a terminal error state and escalation actions
// are permanently asserted.
logic cnt_en, cnt_clr, cnt_error;
// SEC_CM: ESC_TIMER.CTR.REDUN
prim_count #(
.Width(EscCntDw),
// The alert handler behaves differently than other comportable IP. I.e., instead of sending out
// an alert signal, this condition is handled internally in the alert handler.
.EnableAlertTriggerSVA(0)
) u_prim_count (
.clk_i,
.rst_ni,
.clr_i(cnt_clr && !cnt_en),
.set_i(cnt_clr && cnt_en),
.set_cnt_i(EscCntDw'(1)),
.incr_en_i(cnt_en),
.decr_en_i(1'b0),
.step_i(EscCntDw'(1)),
.cnt_o(esc_cnt_o),
.cnt_next_o(),
.err_o(cnt_error)
);
// threshold test, the thresholds are muxed further below
// depending on the current state
logic cnt_ge;
logic [EscCntDw-1:0] thresh;
assign cnt_ge = (esc_cnt_o >= thresh);
//////////////
// Main FSM //
//////////////
logic [N_PHASES-1:0] phase_oh;
// SEC_CM: ESC_TIMER.FSM.SPARSE
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 5 -m 8 -n 10 \
// -s 784905746 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: --
// 4: --
// 5: |||||||||||||||||||| (46.43%)
// 6: |||||||||||||||||||| (46.43%)
// 7: ||| (7.14%)
// 8: --
// 9: --
// 10: --
//
// Minimum Hamming distance: 5
// Maximum Hamming distance: 7
// Minimum Hamming weight: 3
// Maximum Hamming weight: 9
//
localparam int StateWidth = 10;
typedef enum logic [StateWidth-1:0] {
IdleSt = 10'b1011011010,
TimeoutSt = 10'b0000100110,
Phase0St = 10'b1110000101,
Phase1St = 10'b0101010100,
Phase2St = 10'b0000011001,
Phase3St = 10'b1001100001,
TerminalSt = 10'b1101111111,
FsmErrorSt = 10'b0111101000
} state_e;
logic fsm_error;
state_e state_d, state_q;
always_comb begin : p_fsm
// default
state_d = state_q;
esc_state_o = Idle;
cnt_en = 1'b0;
cnt_clr = 1'b0;
esc_trig_o = 1'b0;
phase_oh = '0;
thresh = timeout_cyc_i;
fsm_error = 1'b0;
latch_crashdump_o = 1'b0;
unique case (state_q)
// wait for an escalation trigger or an alert trigger
// the latter will trigger an interrupt timeout
IdleSt: begin
cnt_clr = 1'b1;
esc_state_o = Idle;
if (accu_trig_i && en_i && !clr_i) begin
state_d = Phase0St;
cnt_en = 1'b1;
esc_trig_o = 1'b1;
// the counter is zero in this state. so if the
// timeout count is zero (==disabled), cnt_ge will be true.
end else if (timeout_en_i && !cnt_ge && en_i) begin
cnt_en = 1'b1;
state_d = TimeoutSt;
end
end
// we are in interrupt timeout state
// in case an escalation comes in, we immediately have to
// switch over to the first escalation phase.
// in case the interrupt timeout hits it's cycle count, we
// also enter escalation phase0.
// ongoing timeouts can always be cleared.
TimeoutSt: begin
esc_state_o = Timeout;
if ((accu_trig_i && en_i && !clr_i) || (cnt_ge && timeout_en_i)) begin
state_d = Phase0St;
cnt_en = 1'b1;
cnt_clr = 1'b1;
esc_trig_o = 1'b1;
// the timeout enable is connected to the irq state
// if that is cleared, stop the timeout counter
end else if (timeout_en_i) begin
cnt_en = 1'b1;
end else begin
state_d = IdleSt;
cnt_clr = 1'b1;
end
end
// note: autolocking the clear signal is done in the regfile
Phase0St: begin
cnt_en = 1'b1;
phase_oh[0] = 1'b1;
thresh = phase_cyc_i[0];
esc_state_o = Phase0;
latch_crashdump_o = (crashdump_phase_i == 2'b00);
if (clr_i) begin
state_d = IdleSt;
cnt_clr = 1'b1;
cnt_en = 1'b0;
end else if (cnt_ge) begin
state_d = Phase1St;
cnt_clr = 1'b1;
cnt_en = 1'b1;
end
end
Phase1St: begin
cnt_en = 1'b1;
phase_oh[1] = 1'b1;
thresh = phase_cyc_i[1];
esc_state_o = Phase1;
latch_crashdump_o = (crashdump_phase_i == 2'b01);
if (clr_i) begin
state_d = IdleSt;
cnt_clr = 1'b1;
cnt_en = 1'b0;
end else if (cnt_ge) begin
state_d = Phase2St;
cnt_clr = 1'b1;
cnt_en = 1'b1;
end
end
Phase2St: begin
cnt_en = 1'b1;
phase_oh[2] = 1'b1;
thresh = phase_cyc_i[2];
esc_state_o = Phase2;
latch_crashdump_o = (crashdump_phase_i == 2'b10);
if (clr_i) begin
state_d = IdleSt;
cnt_clr = 1'b1;
cnt_en = 1'b0;
end else if (cnt_ge) begin
state_d = Phase3St;
cnt_clr = 1'b1;
end
end
Phase3St: begin
cnt_en = 1'b1;
phase_oh[3] = 1'b1;
thresh = phase_cyc_i[3];
esc_state_o = Phase3;
latch_crashdump_o = (crashdump_phase_i == 2'b11);
if (clr_i) begin
state_d = IdleSt;
cnt_clr = 1'b1;
cnt_en = 1'b0;
end else if (cnt_ge) begin
state_d = TerminalSt;
cnt_clr = 1'b1;
cnt_en = 1'b0;
end
end
// final, terminal state after escalation.
// if clr is locked down, only a system reset
// will get us out of this state
TerminalSt: begin
cnt_clr = 1'b1;
esc_state_o = Terminal;
if (clr_i) begin
state_d = IdleSt;
end
end
// error state, only reached if the FSM has been
// glitched. in this state, we trigger all escalation
// actions at once.
FsmErrorSt: begin
esc_state_o = FsmError;
fsm_error = 1'b1;
end
// SEC_CM: ESC_TIMER.FSM.LOCAL_ESC
// catch glitches.
default: begin
state_d = FsmErrorSt;
esc_state_o = FsmError;
fsm_error = 1'b1;
end
endcase
// SEC_CM: ESC_TIMER.FSM.LOCAL_ESC
// if any of the duplicate counter pairs has an inconsistent state
// we move into the terminal FSM error state.
if (accu_fail_i || cnt_error) begin
state_d = FsmErrorSt;
fsm_error = 1'b1;
end
end
logic [N_ESC_SEV-1:0][N_PHASES-1:0] esc_map_oh;
for (genvar k = 0; k < N_ESC_SEV; k++) begin : gen_phase_map
// generate configuration mask for escalation enable signals
assign esc_map_oh[k] = N_ESC_SEV'(esc_en_i[k]) << esc_map_i[k];
// mask reduce current phase state vector
// SEC_CM: ESC_TIMER.FSM.GLOBAL_ESC
assign esc_sig_req_o[k] = |(esc_map_oh[k] & phase_oh) | fsm_error;
end
///////////////////
// FSM Registers //
///////////////////
// The alert handler behaves differently than other comportable IP. I.e., instead of sending out
// an alert signal, this condition is handled internally in the alert handler. The
// EnableAlertTriggerSVA parameter is therefore set to 0.
`PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, state_e, IdleSt, clk_i, rst_ni, 0)
////////////////
// Assertions //
////////////////
// a clear should always bring us back to idle
`ASSERT(CheckClr_A,
!accu_fail_i &&
clr_i &&
!(state_q inside {IdleSt, TimeoutSt, FsmErrorSt})
|=>
state_q == IdleSt)
// if currently in idle and not enabled, must remain here
`ASSERT(CheckEn_A,
!accu_fail_i &&
state_q == IdleSt &&
!en_i
|=>
state_q == IdleSt)
// Check if accumulation trigger correctly captured
`ASSERT(CheckAccumTrig0_A,
!accu_fail_i &&
accu_trig_i &&
state_q == IdleSt &&
en_i &&
!clr_i
|=>
state_q == Phase0St)
`ASSERT(CheckAccumTrig1_A,
!accu_fail_i &&
accu_trig_i &&
state_q == TimeoutSt &&
en_i &&
!clr_i
|=>
state_q == Phase0St)
// Check if timeout correctly captured
`ASSERT(CheckTimeout0_A,
!accu_fail_i &&
state_q == IdleSt &&
timeout_en_i &&
en_i &&
timeout_cyc_i != 0 &&
!accu_trig_i
|=>
state_q == TimeoutSt)
`ASSERT(CheckTimeoutSt1_A,
!accu_fail_i &&
state_q == TimeoutSt &&
timeout_en_i &&
esc_cnt_o < timeout_cyc_i &&
!accu_trig_i
|=>
state_q == TimeoutSt)
`ASSERT(CheckTimeoutSt2_A,
!accu_fail_i &&
state_q == TimeoutSt &&
!timeout_en_i &&
!accu_trig_i
|=>
state_q == IdleSt)
// Check if timeout correctly triggers escalation
`ASSERT(CheckTimeoutStTrig_A,
!accu_fail_i &&
state_q == TimeoutSt &&
timeout_en_i &&
esc_cnt_o == timeout_cyc_i
|=>
state_q == Phase0St)
// Check whether escalation phases are correctly switched
`ASSERT(CheckPhase0_A,
!accu_fail_i &&
state_q == Phase0St &&
!clr_i &&
esc_cnt_o >= phase_cyc_i[0]
|=>
state_q == Phase1St)
`ASSERT(CheckPhase1_A,
!accu_fail_i &&
state_q == Phase1St &&
!clr_i &&
esc_cnt_o >= phase_cyc_i[1]
|=>
state_q == Phase2St)
`ASSERT(CheckPhase2_A,
!accu_fail_i &&
state_q == Phase2St &&
!clr_i &&
esc_cnt_o >= phase_cyc_i[2]
|=>
state_q == Phase3St)
`ASSERT(CheckPhase3_A,
!accu_fail_i &&
state_q == Phase3St &&
!clr_i &&
esc_cnt_o >= phase_cyc_i[3]
|=>
state_q == TerminalSt)
`ASSERT(AccuFailToFsmError_A,
accu_fail_i
|=>
state_q == FsmErrorSt)
`ASSERT(ErrorStIsTerminal_A,
state_q == FsmErrorSt
|=>
state_q == FsmErrorSt)
`ASSERT(ErrorStAllEscAsserted_A,
state_q == FsmErrorSt
|->
esc_sig_req_o == '1)
endmodule : alert_handler_esc_timer