[alert_handler] Duplicate escalation counter
These counters are duplicated to harden the design against FI attacks.
In case the counters have an inconsistent state, the escalation FSM
goes into a terminal error state in which all countermeasures are asserted
simultaneously.
Signed-off-by: Michael Schaffner <msf@google.com>
diff --git a/hw/ip/alert_handler/rtl/alert_handler_esc_timer.sv b/hw/ip/alert_handler/rtl/alert_handler_esc_timer.sv
index a744d5d..8020a23 100644
--- a/hw/ip/alert_handler/rtl/alert_handler_esc_timer.sv
+++ b/hw/ip/alert_handler/rtl/alert_handler_esc_timer.sv
@@ -40,23 +40,60 @@
output cstate_e esc_state_o
);
- /////////////
- // Counter //
- /////////////
+ ////////////////////
+ // Tandem Counter //
+ ////////////////////
logic cnt_en, cnt_clr, cnt_ge;
- logic [EscCntDw-1:0] cnt_d, cnt_q;
+ logic [1:0][EscCntDw-1:0] cnt_q;
- // escalation counter, used for all phases and the timeout
- assign cnt_d = cnt_q + 1'b1;
+ // 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.
+ for (genvar k = 0; k < 2; k++) begin : gen_double_cnt
- // current counter output
- assign esc_cnt_o = cnt_q;
+ logic cnt_en_buf, cnt_clr_buf;
+
+ // These size_only buffers are instantiated in order to prevent
+ // optimization / merging of the two counters.
+ prim_buf u_prim_buf_clr (
+ .in_i(cnt_clr),
+ .out_o(cnt_clr_buf)
+ );
+
+ prim_buf u_prim_buf_en (
+ .in_i(cnt_en),
+ .out_o(cnt_en_buf)
+ );
+
+ // escalation counter, used for all phases and the timeout
+ logic [EscCntDw-1:0] cnt_d;
+ assign cnt_d = (cnt_clr_buf && cnt_en_buf) ? EscCntDw'(1'b1) :
+ (cnt_clr_buf) ? '0 :
+ (cnt_en_buf) ? cnt_q[k] + 1'b1 : cnt_q[k];
+
+ prim_flop #(
+ .Width(EscCntDw)
+ ) u_prim_flop (
+ .clk_i,
+ .rst_ni,
+ .d_i(cnt_d),
+ .q_o(cnt_q[k])
+ );
+ end
// threshold test, the thresholds are muxed further below
// depending on the current state
logic [EscCntDw-1:0] thresh;
- assign cnt_ge = (cnt_q >= thresh);
+ assign cnt_ge = (cnt_q[0] >= thresh);
+
+ // current counter output
+ assign esc_cnt_o = cnt_q[0];
+
+ // consistency check
+ logic cnt_check_fail;
+ assign cnt_check_fail = cnt_q[0] != cnt_q[1];
//////////////
// Main FSM //
@@ -242,9 +279,9 @@
end
endcase
- // if the tandem accumulator counters have an inconsistent state
+ // if any of the duplicate counter pairs has an inconsistent state
// we move into the terminal FSM error state.
- if (accu_fail_i) begin
+ if (accu_fail_i || cnt_check_fail) begin
state_d = FsmErrorSt;
end
end
@@ -257,9 +294,9 @@
assign esc_sig_req_o[k] = |(esc_map_oh[k] & phase_oh) | fsm_error;
end
- ///////////////
- // Registers //
- ///////////////
+ ///////////////////
+ // FSM Registers //
+ ///////////////////
// This primitive is used to place a size-only constraint on the
// flops in order to prevent FSM state encoding optimizations.
@@ -275,21 +312,6 @@
.q_o ( state_raw_q )
);
- // switch interrupt / escalation mode
- always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
- if (!rst_ni) begin
- cnt_q <= '0;
- end else begin
- if (cnt_en && cnt_clr) begin
- cnt_q <= EscCntDw'(1'b1);
- end else if (cnt_clr) begin
- cnt_q <= '0;
- end else if (cnt_en) begin
- cnt_q <= cnt_d;
- end
- end
- end
-
////////////////
// Assertions //
////////////////
@@ -339,7 +361,7 @@
!accu_fail_i &&
state_q == TimeoutSt &&
timeout_en_i &&
- cnt_q < timeout_cyc_i &&
+ cnt_q[0] < timeout_cyc_i &&
!accu_trig_i
|=>
state_q == TimeoutSt)
@@ -355,7 +377,7 @@
!accu_fail_i &&
state_q == TimeoutSt &&
timeout_en_i &&
- cnt_q == timeout_cyc_i
+ cnt_q[0] == timeout_cyc_i
|=>
state_q == Phase0St)
// Check whether escalation phases are correctly switched
@@ -363,28 +385,28 @@
!accu_fail_i &&
state_q == Phase0St &&
!clr_i &&
- cnt_q >= phase_cyc_i[0]
+ cnt_q[0] >= phase_cyc_i[0]
|=>
state_q == Phase1St)
`ASSERT(CheckPhase1_A,
!accu_fail_i &&
state_q == Phase1St &&
!clr_i &&
- cnt_q >= phase_cyc_i[1]
+ cnt_q[0] >= phase_cyc_i[1]
|=>
state_q == Phase2St)
`ASSERT(CheckPhase2_A,
!accu_fail_i &&
state_q == Phase2St &&
!clr_i &&
- cnt_q >= phase_cyc_i[2]
+ cnt_q[0] >= phase_cyc_i[2]
|=>
state_q == Phase3St)
`ASSERT(CheckPhase3_A,
!accu_fail_i &&
state_q == Phase3St &&
!clr_i &&
- cnt_q >= phase_cyc_i[3]
+ cnt_q[0] >= phase_cyc_i[3]
|=>
state_q == TerminalSt)
`ASSERT(AccuFailToFsmError_A,