[otbn] Detect glitches on stack write pointer control signal

This hardening applies to the call stack and the loop info stack, as
`otbn_stack` is used in both of them.

Signed-off-by: Andreas Kurth <adk@lowrisc.org>
diff --git a/hw/ip/otbn/data/otbn.hjson b/hw/ip/otbn/data/otbn.hjson
index a579cc1..c46b531 100644
--- a/hw/ip/otbn/data/otbn.hjson
+++ b/hw/ip/otbn/data/otbn.hjson
@@ -184,6 +184,9 @@
         instances of the counter mismatch, an error is emitted.
       '''
     }
+    { name: "STACK_WR_PTR.CTR.GLITCH_DETECT"
+      desc: "Glitches are detected on the stack counter increment / decrement control signal."
+    }
     { name: "RF_BIGNUM.DATA_REG_SW.GLITCH_DETECT"
       desc: '''
         This countermeasure checks for spurious write-enable signals on the register
diff --git a/hw/ip/otbn/data/otbn_sec_cm_testplan.hjson b/hw/ip/otbn/data/otbn_sec_cm_testplan.hjson
index 9031fb3..addb9f4 100644
--- a/hw/ip/otbn/data/otbn_sec_cm_testplan.hjson
+++ b/hw/ip/otbn/data/otbn_sec_cm_testplan.hjson
@@ -114,6 +114,12 @@
       tests: []
     }
     {
+      name: sec_cm_stack_wr_ptr_ctr_glitch_detect
+      desc: "Verify the countermeasure(s) STACK_WR_PTR.CTR.GLITCH_DETECT."
+      milestone: V2S
+      tests: []
+    }
+    {
       name: sec_cm_rf_bignum_data_reg_sw_glitch_detect
       desc: "Verify the countermeasure(s) RF_BIGNUM.DATA_REG_SW.GLITCH_DETECT."
       milestone: V2S
diff --git a/hw/ip/otbn/rtl/otbn_stack.sv b/hw/ip/otbn/rtl/otbn_stack.sv
index c513752..894cd0e 100644
--- a/hw/ip/otbn/rtl/otbn_stack.sv
+++ b/hw/ip/otbn/rtl/otbn_stack.sv
@@ -67,10 +67,43 @@
     endcase
   end
 
+  // SEC_CM: STACK_WR_PTR.CTR.GLITCH_DETECT
+  // Detect glitches on the `step_i` input of the stack write pointer.  If a glitch is detected,
+  // latch it until the stack gets cleared (or the module is reset).  Detecting glitches on the
+  // clock edge instead of combinationally is required because the error output drives the control
+  // path, thus feeding the glitch detector output back combinationally would result in
+  // combinational loops.
+  logic stack_wr_ptr_step_err_d, stack_wr_ptr_step_err_q;
+  always_comb begin
+    stack_wr_ptr_step_err_d = stack_wr_ptr_step_err_q;
+    if (clear_i) stack_wr_ptr_step_err_d = 1'b0;
+    if (stack_wr_ptr_step > 1 || stack_wr_ptr_step < -1) stack_wr_ptr_step_err_d = 1'b1;
+    if (stack_wr_ptr_step == 1 && !stack_write) stack_wr_ptr_step_err_d = 1'b1;
+    if (stack_wr_ptr_step == -1 && !stack_read) stack_wr_ptr_step_err_d = 1'b1;
+    if (stack_wr_ptr_step == 0 && (stack_write ^ stack_read)) stack_wr_ptr_step_err_d = 1'b1;
+  end
+
+  logic stack_wr_ptr_step_err_buf;
+  prim_buf #(
+    .Width (1)
+  ) u_stack_wr_ptr_step_err_buf (
+    .in_i   (stack_wr_ptr_step_err_d),
+    .out_o  (stack_wr_ptr_step_err_buf)
+  );
+
+  always_ff @(posedge clk_i, negedge rst_ni) begin
+    if (!rst_ni) begin
+      stack_wr_ptr_step_err_q <= 1'b0;
+    end else begin
+      stack_wr_ptr_step_err_q <= stack_wr_ptr_step_err_buf;
+    end
+  end
+
   logic stack_wr_ptr_en;
   assign stack_wr_ptr_en = stack_wr_ptr_step != '0;
 
   // SEC_CM: STACK_WR_PTR.CTR.REDUN
+  logic stack_wr_ptr_err;
   prim_count #(
     .Width        (StackDepthW+1),
     .OutSelDnCnt  (0),
@@ -84,7 +117,7 @@
     .en_i       (stack_wr_ptr_en),
     .step_i     (stack_wr_ptr_step),
     .cnt_o      (stack_wr_ptr),
-    .err_o      (cnt_err_o)
+    .err_o      (stack_wr_ptr_err)
   );
 
   always_ff @(posedge clk_i) begin
@@ -96,4 +129,5 @@
   assign full_o      = stack_full;
   assign top_data_o  = stack_storage[stack_rd_idx];
   assign top_valid_o = ~stack_empty;
+  assign cnt_err_o   = stack_wr_ptr_err | stack_wr_ptr_step_err_q;
 endmodule