[otp_ctrl] Duplicate counters in LFSR timer to harden against FI

Signed-off-by: Michael Schaffner <msf@google.com>
diff --git a/hw/ip/otp_ctrl/otp_ctrl.core b/hw/ip/otp_ctrl/otp_ctrl.core
index f8682a2..40411b6 100644
--- a/hw/ip/otp_ctrl/otp_ctrl.core
+++ b/hw/ip/otp_ctrl/otp_ctrl.core
@@ -16,6 +16,7 @@
       - lowrisc:prim:lfsr
       - lowrisc:prim:lc_sync
       - lowrisc:prim:buf
+      - lowrisc:prim:flop
       - lowrisc:prim:secded
       - lowrisc:prim:edn_req
       - lowrisc:ip:pwrmgr_pkg
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv
index 1b57725..4b02060 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv
@@ -72,30 +72,62 @@
   assign edn_req_o = (reseed_timer_q == '0);
   assign reseed_en = edn_req_o & edn_ack_i;
 
-  //////////
-  // PRNG //
-  //////////
+  ///////////////////////////
+  // Tandem LFSR Instances //
+  ///////////////////////////
 
-  logic lfsr_en;
-  logic [LfsrWidth-1:0] lfsr_state;
+  logic lfsr_en, lfsr_en_unbuf;
+  logic [LfsrWidth-1:0] entropy_unbuf;
+  logic [1:0][LfsrWidth-1:0] lfsr_state;
 
-  prim_lfsr #(
-    .LfsrDw      ( LfsrWidth      ),
-    .EntropyDw   ( LfsrWidth      ),
-    .StateOutDw  ( LfsrWidth      ),
-    .DefaultSeed ( RndCnstLfsrSeed ),
-    .StatePermEn ( 1'b1            ),
-    .StatePerm   ( RndCnstLfsrPerm ),
-    .ExtSeedSVA  ( 1'b0            ) // ext seed is unused
-  ) i_prim_lfsr (
-    .clk_i,
-    .rst_ni,
-    .seed_en_i  ( 1'b0       ),
-    .seed_i     ( '0         ),
-    .lfsr_en_i  ( lfsr_en | reseed_en                                ),
-    .entropy_i  ( edn_data_i[LfsrWidth-1:0] & {LfsrWidth{reseed_en}} ),
-    .state_o    ( lfsr_state )
-  );
+  assign lfsr_en_unbuf = reseed_en || lfsr_en;
+  assign entropy_unbuf = (reseed_en) ? edn_data_i[LfsrWidth-1:0] : '0;
+
+  // We employ two redundant LFSRs to guard against FI attacks.
+  // If any of the two is glitched and the two LFSR states do not agree,
+  // the FSM below is moved into a terminal error state.
+  for (genvar k = 0; k < 2; k++) begin : gen_double_lfsr
+    // Instantiate size_only buffers to prevent
+    // optimization / merging of redundant logic.
+    logic lfsr_en_buf;
+    logic [LfsrWidth-1:0] entropy_buf, lfsr_state_unbuf;
+    prim_buf #(
+      .Width(LfsrWidth)
+    ) u_prim_buf_entropy (
+      .in_i(entropy_unbuf),
+      .out_o(entropy_buf)
+    );
+
+    prim_buf u_prim_buf_lfsr_en (
+      .in_i(lfsr_en_unbuf),
+      .out_o(lfsr_en_buf)
+    );
+
+    prim_buf #(
+      .Width(LfsrWidth)
+    ) u_prim_buf_state (
+      .in_i(lfsr_state_unbuf),
+      .out_o(lfsr_state[k])
+    );
+
+    prim_lfsr #(
+      .LfsrDw      ( LfsrWidth      ),
+      .EntropyDw   ( LfsrWidth      ),
+      .StateOutDw  ( LfsrWidth      ),
+      .DefaultSeed ( RndCnstLfsrSeed ),
+      .StatePermEn ( 1'b1            ),
+      .StatePerm   ( RndCnstLfsrPerm ),
+      .ExtSeedSVA  ( 1'b0            ) // ext seed is unused
+    ) u_prim_lfsr (
+      .clk_i,
+      .rst_ni,
+      .seed_en_i  ( 1'b0             ),
+      .seed_i     ( '0               ),
+      .lfsr_en_i  ( lfsr_en_buf      ),
+      .entropy_i  ( entropy_buf      ),
+      .state_o    ( lfsr_state_unbuf )
+    );
+  end
 
   // Not all entropy bits are used.
   logic unused_seed;
@@ -103,37 +135,82 @@
 
   `ASSERT_INIT(EdnIsWideEnough_A, EdnDataWidth >= LfsrWidth)
 
-  //////////////
-  // Counters //
-  //////////////
+  //////////////////////////////
+  // Tandem Counter Instances //
+  //////////////////////////////
 
-  logic [LfsrWidth-1:0] integ_cnt_d, integ_cnt_q;
-  logic [LfsrWidth-1:0] cnsty_cnt_d, cnsty_cnt_q;
-  logic [LfsrWidth-1:0] integ_mask, cnsty_mask;
+  logic [1:0][LfsrWidth-1:0] integ_cnt_q;
+  logic [1:0][LfsrWidth-1:0] cnsty_cnt_q;
   logic integ_load_period, integ_load_timeout, integ_cnt_zero;
   logic cnsty_load_period, cnsty_load_timeout, cnsty_cnt_zero;
-  logic timeout_zero, integ_msk_zero, cnsty_msk_zero, cnsty_cnt_pause;
 
+  logic [LfsrWidth-1:0] integ_mask, cnsty_mask;
   assign integ_mask  = {integ_period_msk_i, {LfsrWidth-32{1'b1}}};
   assign cnsty_mask  = {cnsty_period_msk_i, {LfsrWidth-32{1'b1}}};
 
-  assign integ_cnt_d = (integ_load_period)  ? lfsr_state & integ_mask :
-                       (integ_load_timeout) ? LfsrWidth'(timeout_i)   :
-                       (integ_cnt_zero)     ? '0                      :
-                                              integ_cnt_q - 1'b1;
-
-
-  assign cnsty_cnt_d = (cnsty_load_period)  ? lfsr_state & cnsty_mask :
-                       (cnsty_load_timeout) ? LfsrWidth'(timeout_i)   :
-                       (cnsty_cnt_zero)     ? '0                      :
-                       (cnsty_cnt_pause)    ? cnsty_cnt_q             :
-                                              cnsty_cnt_q - 1'b1;
-
+  logic timeout_zero, integ_msk_zero, cnsty_msk_zero, cnsty_cnt_pause;
   assign timeout_zero   = (timeout_i == '0);
   assign integ_msk_zero = (integ_period_msk_i == '0);
   assign cnsty_msk_zero = (cnsty_period_msk_i == '0);
-  assign integ_cnt_zero = (integ_cnt_q == '0);
-  assign cnsty_cnt_zero = (cnsty_cnt_q == '0);
+  assign integ_cnt_zero = (integ_cnt_q[0] == '0);
+  assign cnsty_cnt_zero = (cnsty_cnt_q[0] == '0);
+
+  // We employ redundant counters to guard against FI attacks.
+  // If any of them is glitched and the redundant counter states do not agree,
+  // the FSM below is moved into a terminal error state.
+  for (genvar k = 0; k < 2; k++) begin : gen_double_cnt
+    // Instantiate size_only buffers to prevent
+    // optimization / merging of redundant logic.
+    logic integ_load_period_buf, integ_load_timeout_buf;
+    logic cnsty_load_period_buf, cnsty_load_timeout_buf, cnsty_cnt_pause_buf;
+
+    prim_buf #(
+      .Width(5)
+    ) u_prim_buf (
+      .in_i({integ_load_period,
+             integ_load_timeout,
+             cnsty_load_period,
+             cnsty_load_timeout,
+             cnsty_cnt_pause}),
+      .out_o({integ_load_period_buf,
+              integ_load_timeout_buf,
+              cnsty_load_period_buf,
+              cnsty_load_timeout_buf,
+              cnsty_cnt_pause_buf})
+    );
+
+    logic [LfsrWidth-1:0] integ_cnt_d;
+    logic [LfsrWidth-1:0] cnsty_cnt_d;
+    assign integ_cnt_d = (integ_load_period_buf)  ? lfsr_state[k] & integ_mask :
+                         (integ_load_timeout_buf) ? LfsrWidth'(timeout_i)      :
+                         (integ_cnt_q[k] == '0)   ? '0                         :
+                                                    integ_cnt_q[k] - 1'b1;
+
+
+    assign cnsty_cnt_d = (cnsty_load_period_buf)  ? lfsr_state[k] & cnsty_mask :
+                         (cnsty_load_timeout_buf) ? LfsrWidth'(timeout_i)      :
+                         (cnsty_cnt_q[k] == '0)   ? '0                         :
+                         (cnsty_cnt_pause_buf)    ? cnsty_cnt_q[k]             :
+                                                    cnsty_cnt_q[k] - 1'b1;
+
+    prim_flop #(
+      .Width(LfsrWidth)
+    ) u_prim_flop_integ_cnt (
+      .clk_i,
+      .rst_ni,
+      .d_i   ( integ_cnt_d    ),
+      .q_o   ( integ_cnt_q[k] )
+    );
+
+    prim_flop #(
+      .Width(LfsrWidth)
+    ) u_prim_flop_cnsty_cnt (
+      .clk_i,
+      .rst_ni,
+      .d_i   ( cnsty_cnt_d    ),
+      .q_o   ( cnsty_cnt_q[k] )
+    );
+  end
 
   /////////////////////
   // Request signals //
@@ -299,9 +376,13 @@
       ///////////////////////////////////////////////////////////////////
     endcase // state_q
 
-    // Unconditionally jump into the terminal error state in case of escalation.
-    if (escalate_en_i != lc_ctrl_pkg::Off) begin
-      state_d = ErrorSt;
+    // Unconditionally jump into the terminal error state in case of escalation,
+    // or if the two LFSR or counter states do not agree.
+    if (lfsr_state[0] != lfsr_state[1] ||
+        integ_cnt_q[0] != integ_cnt_q[1] ||
+        cnsty_cnt_q[0] != cnsty_cnt_q[1] ||
+        escalate_en_i != lc_ctrl_pkg::Off) begin
+       state_d = ErrorSt;
     end
   end
 
@@ -325,8 +406,6 @@
 
   always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
     if (!rst_ni) begin
-      integ_cnt_q <= '0;
-      cnsty_cnt_q <= '0;
       integ_chk_req_q <= '0;
       cnsty_chk_req_q <= '0;
       chk_timeout_q   <= 1'b0;
@@ -334,8 +413,6 @@
       integ_chk_trig_q <= 1'b0;
       cnsty_chk_trig_q <= 1'b0;
     end else begin
-      integ_cnt_q <= integ_cnt_d;
-      cnsty_cnt_q <= cnsty_cnt_d;
       integ_chk_req_q <= integ_chk_req_d;
       cnsty_chk_req_q <= cnsty_chk_req_d;
       chk_timeout_q   <= chk_timeout_d;