[otp_ctrl] Implement consistency/integrity check timer

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/otp_ctrl/data/otp_ctrl.hjson b/hw/ip/otp_ctrl/data/otp_ctrl.hjson
index cf76384..93777d0 100644
--- a/hw/ip/otp_ctrl/data/otp_ctrl.hjson
+++ b/hw/ip/otp_ctrl/data/otp_ctrl.hjson
@@ -194,70 +194,78 @@
         { bits: "0"
           name: "CREATOR_SW_CFG_ERROR"
           desc: '''
-                Set to 1 if an error occurred in this partition. If set to 1, SW should
-                check the !!ERR_CODE register at the corresponding index to root-cause the error.
+                Set to 1 if an error occurred in this partition.
+                If set to 1, SW should check the !!ERR_CODE register at the corresponding index.
                 '''
         }
         { bits: "1"
           name: "OWNER_SW_CFG_ERROR"
           desc: '''
-                Set to 1 if an error occurred in this partition. If set to 1, SW should
-                check the !!ERR_CODE register at the corresponding index to root-cause the error.
+                Set to 1 if an error occurred in this partition.
+                If set to 1, SW should check the !!ERR_CODE register at the corresponding index.
                 '''
         }
         { bits: "2"
           name: "HW_CFG_ERROR"
           desc: '''
-                Set to 1 if an error occurred in this partition. If set to 1, SW should
-                check the !!ERR_CODE register at the corresponding index to root-cause the error.
+                Set to 1 if an error occurred in this partition.
+                If set to 1, SW should check the !!ERR_CODE register at the corresponding index.
                 '''
         }
         { bits: "3"
-          name: "SECRET0"
+          name: "SECRET0_ERROR"
           desc: '''
-                Set to 1 if an error occurred in this partition. If set to 1, SW should
-                check the !!ERR_CODE register at the corresponding index to root-cause the error.
+                Set to 1 if an error occurred in this partition.
+                If set to 1, SW should check the !!ERR_CODE register at the corresponding index.
                 '''
         }
         { bits: "4"
-          name: "SECRET1"
+          name: "SECRET1_ERROR"
           desc: '''
-                Set to 1 if an error occurred in this partition. If set to 1, SW should
-                check the !!ERR_CODE register at the corresponding index to root-cause the error.
+                Set to 1 if an error occurred in this partition.
+                If set to 1, SW should check the !!ERR_CODE register at the corresponding index.
                 '''
         }
         { bits: "5"
-          name: "SECRET2"
+          name: "SECRET2_ERROR"
           desc: '''
-                Set to 1 if an error occurred in this partition. If set to 1, SW should
-                check the !!ERR_CODE register at the corresponding index to root-cause the error.
+                Set to 1 if an error occurred in this partition.
+                If set to 1, SW should check the !!ERR_CODE register at the corresponding index.
                 '''
         }
         { bits: "6"
-          name: "LIFE_CYCLE"
+          name: "LIFE_CYCLE_ERROR"
           desc: '''
-                Set to 1 if an error occurred in this partition. If set to 1, SW should
-                check the !!ERR_CODE register at the corresponding index to root-cause the error.
+                Set to 1 if an error occurred in this partition.
+                If set to 1, SW should check the !!ERR_CODE register at the corresponding index.
                 '''
         }
         { bits: "7"
           name: "DAI_ERROR"
           desc: '''
-                Set to 1 if an error occurred in the DAI. If set to 1, SW should
-                check the !!ERR_CODE register at the corresponding index to root-cause the error.
+                Set to 1 if an error occurred in the DAI.
+                If set to 1, SW should check the !!ERR_CODE register at the corresponding index.
                 '''
         }
         { bits: "8"
           name: "LCI_ERROR"
           desc: '''
-                Set to 1 if an error occurred in the LCI. If set to 1, SW should
-                check the !!ERR_CODE register at the corresponding index to root-cause the error.
+                Set to 1 if an error occurred in the LCI.
+                If set to 1, SW should check the !!ERR_CODE register at the corresponding index.
                 '''
         }
         { bits: "9"
+          name: "TIMEOUT_ERROR"
+          desc: "Set to 1 if an integrity or consistency check times out. This raises an otp_check_failed alert and is an unrecoverable error condition."
+        }
+        { bits: "10"
           name: "DAI_IDLE"
           desc: "Set to 1 if the DAI is idle and ready to accept commands."
         }
+        { bits: "11"
+          name: "CHECK_PENDING"
+          desc: "Set to 1 if an integrity or consistency check triggered by the LFSR timer or via !!CHECK_TRIGGER is pending."
+        }
       ]
     }
     { multireg: {
@@ -513,58 +521,130 @@
         ]
       }
     },
-    { name: "CHECK_PERIOD_REGEN",
+
+    //////////////////////////////////////
+    // Integrity and Consistency Checks //
+    //////////////////////////////////////
+    { name: "CHECK_TRIGGER_REGWEN",
       desc: '''
-            Register write enable for !!INTEGRITY_CHECK_PERIOD_MSB and !!CONSISTENCY_CHECK_PERIOD_MSB.
+            Register write enable for !!CHECK_TRIGGER.
             ''',
       swaccess: "rw1c",
       hwaccess: "hro",
       fields: [
         { bits:   "0",
           desc: '''
-          When true, !!INTEGRITY_CHECK_PERIOD_MSB and !!CONSISTENCY_CHECK_PERIOD_MSB registers cannot be written anymore.
+          When cleared to 0, the !!CHECK_TRIGGER register cannot be written anymore.
+          Write 1 to clear this bit.
           '''
           resval: 1,
         },
       ]
     },
-    { name: "INTEGRITY_CHECK_PERIOD_MSB",
+    { name: "CHECK_TRIGGER",
+      desc: "Command register for direct accesses.",
+      swaccess: "r0w1c",
+      hwaccess: "hro",
+      hwqe:     "true",
+      regwen:   "CHECK_TRIGGER_REGWEN",
+      fields: [
+        { bits: "0",
+          name: "INTEGRITY",
+          desc: '''
+          Writing 1 to this bit triggers an integrity check. SW should monitor !!STATUS.CHECK_PENDING
+          and wait until the check has been completed. If there are any errors, those will be flagged
+          in the !!STATUS and !!ERR_CODE registers, and via the interrupts and alerts.
+          '''
+        }
+        { bits: "1",
+          name: "CONSISTENCY",
+          desc: '''
+          Writing 1 to this bit triggers a consistency check. SW should monitor !!STATUS.CHECK_PENDING
+          and wait until the check has been completed. If there are any errors, those will be flagged
+          in the !!STATUS and !!ERR_CODE registers, and via interrupts and alerts.
+          '''
+        }
+      ]
+    },
+    { name: "CHECK_REGWEN",
+      desc: '''
+            Register write enable for !!INTEGRITY_CHECK_PERIOD and !!CONSISTENCY_CHECK_PERIOD.
+            ''',
+      swaccess: "rw1c",
+      hwaccess: "hro",
+      fields: [
+        { bits:   "0",
+          desc: '''
+          When cleared to 0, !!INTEGRITY_CHECK_PERIOD and !!CONSISTENCY_CHECK_PERIOD registers cannot be written anymore.
+          Write 1 to clear this bit.
+          '''
+          resval: 1,
+        },
+      ]
+    },
+    { name: "CHECK_TIMEOUT",
+      desc: '''
+            Timeout value for the integrity and consistency checks.
+            ''',
+      swaccess: "rw",
+      hwaccess: "hro",
+      regwen:   "CHECK_REGWEN",
+      fields: [
+        { bits: "31:0",
+          desc: '''
+          Timeout value in cycles for the for the integrity and consistency checks. If an integrity or consistency
+          check does not complete within the timeout window, an error will be flagged in the !!STATUS register,
+          an otp_error interrupt will be raised, and an otp_check_failed alert will be sent out. The timeout should
+          be set to a large value to stay on the safe side. The maximum check time can be upper bounded by the
+          number of cycles it takes to readout, scramble and digest the entire OTP array. Since this amounts to
+          roughly 25k cycles, it is recommended to set this value to at least 100'000 cycles in order to stay on the
+          safe side. A value of zero disables the timeout mechanism (default).
+          '''
+          resval: 0,
+        },
+      ]
+    },
+    { name: "INTEGRITY_CHECK_PERIOD",
       desc: '''
             This value specifies the maximum period that can be generated pseudo-randomly.
-            Only applies to the HW_CFG and SECRET partitions if they are locked.
+            Only applies to the HW_CFG and SECRET* partitions, once they are locked.
             '''
       swaccess: "rw",
       hwaccess: "hro",
-      regwen:   "CHECK_PERIOD_REGEN",
+      regwen:   "CHECK_REGWEN",
       fields: [
-        { bits: "5:0",
+        { bits: "31:0",
           desc: '''
-          The pseudo-random period is generated using a 40bit LFSR internally, and this value defines
-          the bit mask to be applied to the LFSR output in order to limit its range. A value of N will generate
-          an internal mask of 2^N-1. So for N=16 this would allow the maximum pseudo-random period to be 0xFFFF cycles.
-          The default value has been set to 25, which corresponds to a maximum period of a bit more than 1.3s at 25MHz.
+          The pseudo-random period is generated using a 40bit LFSR internally, and this register defines
+          the bit mask to be applied to the LFSR output in order to limit its range. The value of this
+          register is left shifted by 8bits and the lower bits are set to 8'hFF in order to form the 40bit mask.
+          A recommended value is 0x3_FFFF, corresponding to a maximum period of ~2.8s at 24MHz.
+          A value of zero disables the timer (default). Note that a one-off check can always be triggered via
+          !!CHECK_TRIGGER.INTEGRITY.
           '''
-          resval: "25"
+          resval: "0"
         }
       ]
     }
-    { name: "CONSISTENCY_CHECK_PERIOD_MSB",
+    { name: "CONSISTENCY_CHECK_PERIOD",
       desc: '''
             This value specifies the maximum period that can be generated pseudo-randomly.
-            This applies to the LIFE_CYCLE partition and the HW_CFG and SECRET partitions (but only if they are locked).
+            This applies to the LIFE_CYCLE partition and the HW_CFG and SECRET* partitions, once they are locked.
             '''
       swaccess: "rw",
       hwaccess: "hro",
-      regwen:   "CHECK_PERIOD_REGEN",
+      regwen:   "CHECK_REGWEN",
       fields: [
-        { bits: "5:0",
+        { bits: "31:0",
           desc: '''
-          The pseudo-random period is generated using a 40bit LFSR internally, and this value defines
-          the bit mask to be applied to the LFSR output in order to limit its range. A value of N will generate
-          an internal mask of 2^N-1. So for N=16 this would allow the maximum pseudo-random period to be 0xFFFF cycles.
-          The default value has been set to 34, which corresponds to a maximum period of a bit more than 687s at 25MHz.
+          The pseudo-random period is generated using a 40bit LFSR internally, and this register defines
+          the bit mask to be applied to the LFSR output in order to limit its range. The value of this
+          register is left shifted by 8bits and the lower bits are set to 8'hFF in order to form the 40bit mask.
+          A recommended value is 0x3FF_FFFF, corresponding to a maximum period of ~716s at 24MHz.
+          A value of zero disables the timer (default). Note that a one-off check can always be triggered via
+          !!CHECK_TRIGGER.CONSISTENCY.
           '''
-          resval: "34"
+          resval: "0"
         }
       ]
     }
@@ -581,7 +661,8 @@
       fields: [
         { bits:   "0",
           desc: '''
-          When true, read access to the !!CREATOR_SW_CFG partition is locked.
+          When cleared to 0, read access to the !!CREATOR_SW_CFG partition is locked.
+          Write 1 to clear this bit.
           '''
           resval: 1,
         },
@@ -596,7 +677,8 @@
       fields: [
         { bits:   "0",
           desc: '''
-          When true, read access to the !!OWNER_SW_CFG partition is locked.
+          When cleared to 0, read access to the !!OWNER_SW_CFG partition is locked.
+          Write 1 to clear this bit.
           '''
           resval: 1,
         },
diff --git a/hw/ip/otp_ctrl/otp_ctrl.core b/hw/ip/otp_ctrl/otp_ctrl.core
index 8170acc..716ac7e 100644
--- a/hw/ip/otp_ctrl/otp_ctrl.core
+++ b/hw/ip/otp_ctrl/otp_ctrl.core
@@ -12,6 +12,7 @@
       - lowrisc:prim:all
       - lowrisc:prim:ram_1p
       - lowrisc:prim:otp
+      - lowrisc:prim:lfsr
       - lowrisc:ip:otp_ctrl_pkg
     files:
       - rtl/otp_ctrl_reg_top.sv
@@ -21,6 +22,7 @@
       - rtl/otp_ctrl_lci.sv
       - rtl/otp_ctrl_part_unbuf.sv
       - rtl/otp_ctrl_part_buf.sv
+      - rtl/otp_ctrl_lfsr_timer.sv
       - rtl/otp_ctrl.sv
     file_type: systemVerilogSource
 
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
index 889bb3a..10fe002 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
@@ -11,7 +11,9 @@
   import otp_ctrl_pkg::*;
   import otp_ctrl_reg_pkg::*;
 #(
-  parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}}
+  parameter logic [NumAlerts-1:0]  AlertAsyncOn = {NumAlerts{1'b1}},
+  // TODO: need to override this during build time randomization
+  parameter logic [TimerWidth-1:0] LfsrSeed    = TimerWidth'(546532468)
 ) (
   input                             clk_i,
   input                             rst_ni,
@@ -25,6 +27,8 @@
   // Alerts
   input  prim_alert_pkg::alert_rx_t [NumAlerts-1:0] alert_rx_i,
   output prim_alert_pkg::alert_tx_t [NumAlerts-1:0] alert_tx_o,
+  // TODO: Complete entropy interface
+  input  otp_entropy_t              entropy_i,
   // Power manager interface
   input  pwr_otp_init_req_t         pwr_otp_init_req_i,
   output pwr_otp_init_rsp_t         pwr_otp_init_rsp_o,
@@ -153,6 +157,7 @@
   logic [NumPart+1:0] part_errors_reduced;
   logic otp_operation_done, otp_error;
   logic otp_fatal_error, otp_check_failed;
+  logic chk_pending, chk_timeout;
   always_comb begin : p_errors_alerts
     hw2reg.err_code = part_error;
     otp_fatal_error = 1'b0;
@@ -174,14 +179,17 @@
       otp_check_failed |= part_error[k] inside {ParityErr,
                                                 IntegErr,
                                                 CnstyErr,
-                                                FsmErr};
+                                                FsmErr} | chk_timeout;
     end
   end
 
   // Assign these to the status register.
-  assign hw2reg.status = {part_errors_reduced, dai_idle};
+  assign hw2reg.status = {chk_pending,
+                          dai_idle,
+                          chk_timeout,
+                          part_errors_reduced};
   // If we got an error, we trigger an interrupt.
-  assign otp_error = |part_errors_reduced;
+  assign otp_error = |part_errors_reduced | chk_timeout;
 
   //////////////////////////////////
   // Interrupts and Alert Senders //
@@ -237,18 +245,43 @@
     .alert_tx_o ( alert_tx_o[1] )
   );
 
-  ////////////////
-  // LFSR Timer //
-  ////////////////
+  ////////////////////////////////
+  // LFSR Timer and CSR mapping //
+  ////////////////////////////////
 
-  // TBD: should we incorporate a timeout in the LFSR counter to cover the case where a
-  // partition check never completes due to wedged arbitration (or some other condition
-  // induced due to a tampering attempt)? This will likely be constructed in a similar
-  // way as the ping timer inside the alert handler.
-
+  logic integ_chk_trig, cnsty_chk_trig;
   logic [NumPart-1:0] integ_chk_req, integ_chk_ack;
   logic [NumPart-1:0] cnsty_chk_req, cnsty_chk_ack;
 
+  assign integ_chk_trig   = reg2hw.check_trigger.integrity.q &
+                            reg2hw.check_trigger.integrity.qe;
+  assign cnsty_chk_trig   = reg2hw.check_trigger.consistency.q &
+                            reg2hw.check_trigger.consistency.qe;
+
+  otp_ctrl_lfsr_timer #(
+    .LfsrSeed(LfsrSeed),
+    .EntropyWidth(4)
+  ) u_otp_ctrl_lfsr_timer (
+    .clk_i,
+    .rst_ni,
+    .entropy_en_i       ( entropy_i.en            ),
+    // Lower entropy bits are used for reseeding secure erase LFSRs
+    .entropy_i          ( entropy_i.data[31:28]   ),
+    // We can enable the timer once OTP has initialized.
+    .timer_en_i         ( pwr_otp_init_rsp_o.done ),
+    .integ_chk_trig_i   ( integ_chk_trig          ),
+    .cnsty_chk_trig_i   ( cnsty_chk_trig          ),
+    .chk_pending_o      ( chk_pending             ),
+    .timeout_i          ( reg2hw.check_timeout.q  ),
+    .integ_period_msk_i ( reg2hw.integrity_check_period.q   ),
+    .cnsty_period_msk_i ( reg2hw.consistency_check_period.q ),
+    .integ_chk_req_o    ( integ_chk_req           ),
+    .cnsty_chk_req_o    ( cnsty_chk_req           ),
+    .integ_chk_ack_i    ( integ_chk_ack           ),
+    .cnsty_chk_ack_i    ( cnsty_chk_ack           ),
+    .chk_timeout_o      ( chk_timeout             )
+  );
+
   ///////////////////////////////
   // OTP Macro and Arbitration //
   ///////////////////////////////
@@ -545,9 +578,13 @@
     end else if (PartInfo[k].variant == Buffered) begin : gen_buffered
       otp_ctrl_part_buf #(
         .Info(PartInfo[k])
+        // TODO:  .EntropyWidth(8)
       ) u_part_buf (
         .clk_i,
         .rst_ni,
+        // TODO: Entropy for clearing LFSRs
+        // .entropy_en_i     ( entropy_i.en                    ),
+        // .entropy_i        ( entropy_i.data[(k-NumUnbuffered) * 2 +: 2] ),
         .init_req_i       ( part_init_req                   ),
         .init_done_o      ( part_init_done[k]               ),
         .integ_chk_req_i  ( integ_chk_req[k]                ),
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv
new file mode 100644
index 0000000..23647e8
--- /dev/null
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv
@@ -0,0 +1,278 @@
+// 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 LFSR timer for triggering periodic consistency and integrity checks in
+// OTP. In particular, this module contains two 40bit counters (one for the consistency and one
+// for the integrity checks) and a 40bit LFSR to draw pseudo random wait counts.
+//
+// The integ_period_msk_i and cnsty_period_msk_i mask signals are used to mask off the LFSR outputs
+// and hence determine the maximum wait count that can be drawn. If these values are set to
+// zero, the corresponding timer is disabled.
+//
+// Once a particular check timer has expired, the module will send out a check request to all
+// partitions and wait for an acknowledgment. If a particular partition encounters an integrity or
+// consistency mismatch, this will be directly reported via the error and alert logic.
+//
+// In order to guard against wedged partition controllers or arbitration lock ups due to tampering
+// attempts, this check timer module also supports a 32bit timeout that can optionally be
+// programmed. If a particular check times out, chk_timeout_o will be asserted, which will raise
+// an alert via the error logic.
+//
+// If needed, the LFSR can be reseeded with fresh entropy from the CSRNG via entropy_i.
+//
+// It is also possible to trigger one-off checks via integ_chk_trig_i and cnsty_chk_trig_i.
+// This can be useful if SW chooses to leave the periodic checks disabled.
+//
+
+`include "prim_assert.sv"
+
+module otp_ctrl_lfsr_timer import otp_ctrl_pkg::*; #(
+  parameter logic [TimerWidth-1:0] LfsrSeed     = TimerWidth'(1'b1),
+  parameter int                    EntropyWidth = 8
+) (
+  input                            clk_i,
+  input                            rst_ni,
+  input                            entropy_en_i,       // entropy update pulse from CSRNG
+  input        [EntropyWidth-1:0]  entropy_i,          // from CSRNG
+  input                            timer_en_i,         // enable timer
+  input                            integ_chk_trig_i,   // one-off trigger for integrity check
+  input                            cnsty_chk_trig_i,   // one-off trigger for consistency check
+  output logic                     chk_pending_o,      // indicates whether there are pending checks
+  input        [31:0]              timeout_i,          // check timeout
+  input        [31:0]              integ_period_msk_i, // maximum integrity check mask
+  input        [31:0]              cnsty_period_msk_i, // maximum consistency check mask
+  output logic [NumPart-1:0]       integ_chk_req_o,    // request to all partitions
+  output logic [NumPart-1:0]       cnsty_chk_req_o,    // request to all partitions
+  input        [NumPart-1:0]       integ_chk_ack_i,    // response from partitions
+  input        [NumPart-1:0]       cnsty_chk_ack_i,    // response from partitions
+  output logic                     chk_timeout_o       // a check has timed out
+);
+
+  //////////
+  // PRNG //
+  //////////
+
+  logic lfsr_en;
+  logic [TimerWidth-1:0] lfsr_state, perm_state;
+
+  prim_lfsr #(
+    .LfsrDw      ( TimerWidth   ),
+    .EntropyDw   ( EntropyWidth ),
+    .StateOutDw  ( TimerWidth   ),
+    .DefaultSeed ( LfsrSeed     ),
+    .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 | entropy_en_i ),
+    .entropy_i  ( entropy_i & {EntropyWidth{entropy_en_i}} ),
+    .state_o    ( lfsr_state )
+  );
+
+  // This random permutation is meant to break the linear
+  // shifting pattern of the LFSR.
+  localparam int unsigned Perm [TimerWidth] = '{
+    13, 17, 29, 11, 28, 12, 33, 27,
+     5, 39, 31, 21, 15,  1, 24, 37,
+    32, 38, 26, 34,  8, 10,  4,  2,
+    19,  0, 20,  6, 25, 22,  3, 35,
+    16, 14, 23,  7, 30,  9, 18, 36
+  };
+
+  for (genvar k = 0; k < 32; k++) begin : gen_perm
+    assign perm_state[k] = lfsr_state[Perm[k]];
+  end
+
+
+  //////////////
+  // Counters //
+  //////////////
+
+  logic [TimerWidth-1:0] integ_cnt_d, integ_cnt_q;
+  logic [TimerWidth-1:0] cnsty_cnt_d, cnsty_cnt_q;
+  logic [TimerWidth-1:0] integ_mask, cnsty_mask;
+  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;
+
+  assign integ_mask  = {integ_period_msk_i, {TimerWidth-32{1'b1}}};
+  assign cnsty_mask  = {cnsty_period_msk_i, {TimerWidth-32{1'b1}}};
+
+  assign integ_cnt_d = (integ_load_period)  ? lfsr_state & integ_mask :
+                       (integ_load_timeout) ? 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) ? timeout_i               :
+                       (cnsty_cnt_zero)     ? '0                      :
+                                              cnsty_cnt_q - 1'b1;
+
+  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);
+
+  /////////////////////
+  // Request signals //
+  /////////////////////
+
+  logic set_all_integ_reqs, set_all_cnsty_reqs;
+  logic [NumPart-1:0] integ_chk_req_d, integ_chk_req_q;
+  logic [NumPart-1:0] cnsty_chk_req_d, cnsty_chk_req_q;
+  assign integ_chk_req_o = integ_chk_req_q;
+  assign cnsty_chk_req_o = cnsty_chk_req_q;
+  assign integ_chk_req_d = (set_all_integ_reqs) ? {NumPart{1'b1}} :
+                                                  integ_chk_req_q & ~integ_chk_ack_i;
+  assign cnsty_chk_req_d = (set_all_cnsty_reqs) ? {NumPart{1'b1}} :
+                                                  cnsty_chk_req_q & ~cnsty_chk_ack_i;
+
+
+  ////////////////////////////
+  // Ping and Timeout Logic //
+  ////////////////////////////
+
+  // Encoding generated with ./sparse-fsm-encode -d 5 -m 5 -n 9
+  // Hamming distance histogram:
+  //
+  // 0: --
+  // 1: --
+  // 2: --
+  // 3: --
+  // 4: --
+  // 5: |||||||||||||||||||| (60.00%)
+  // 6: ||||||||||||| (40.00%)
+  // 7: --
+  // 8: --
+  // 9: --
+  //
+  // Minimum Hamming distance: 5
+  // Maximum Hamming distance: 6
+  //
+  typedef enum logic [8:0] {
+    ResetSt     = 9'b110010010,
+    IdleSt      = 9'b011011101,
+    IntegWaitSt = 9'b100111111,
+    CnstyWaitSt = 9'b001000110,
+    ErrSt       = 9'b101101000
+  } state_e;
+  state_e state_d, state_q;
+
+  always_comb begin : p_fsm
+    state_d = state_q;
+
+    // LFSR and counter signals
+    lfsr_en = 1'b0;
+    integ_load_period  = 1'b0;
+    cnsty_load_period  = 1'b0;
+    integ_load_timeout = 1'b0;
+    cnsty_load_timeout = 1'b0;
+
+    // Requests going to partitions.
+    set_all_integ_reqs = '0;
+    set_all_cnsty_reqs = '0;
+
+    // Status signals going to CSRs and error logic.
+    chk_timeout_o = 1'b0;
+    chk_pending_o = 1'b0;
+
+    unique case (state_q)
+      ///////////////////////////////////////////////////////////////////
+      // Wait until enabled. We never return to this state
+      // once enabled!
+      ResetSt: begin
+        if (timer_en_i) begin
+          state_d = IdleSt;
+          lfsr_en = 1'b1;
+        end
+      end
+      ///////////////////////////////////////////////////////////////////
+      // Wait here until one of the two timers expires (if enabled) or if
+      // a check is triggered externally.
+      IdleSt: begin
+        if ((!integ_msk_zero && integ_cnt_zero) || integ_chk_trig_i) begin
+          state_d = IntegWaitSt;
+          integ_load_timeout = 1'b1;
+          set_all_integ_reqs = 1'b1;
+        end else if ((!cnsty_msk_zero && cnsty_cnt_zero) || cnsty_chk_trig_i) begin
+          state_d = CnstyWaitSt;
+          cnsty_load_timeout = 1'b1;
+          set_all_cnsty_reqs = 1'b1;
+        end
+      end
+      ///////////////////////////////////////////////////////////////////
+      // Wait for all the partitions to respond and go back to idle.
+      // If the timeout is enabled, bail out into terminal error state
+      // if the timeout counter expires (this will raise an alert).
+      IntegWaitSt: begin
+        chk_pending_o = 1'b1;
+        if (!timeout_zero && integ_cnt_zero) begin
+          state_d = ErrSt;
+        end else if (integ_chk_req_q == '0) begin
+          state_d = IdleSt;
+          // This draws the next wait period.
+          integ_load_period = 1'b1;
+          lfsr_en = 1'b1;
+        end
+      end
+      ///////////////////////////////////////////////////////////////////
+      // Wait for all the partitions to respond and go back to idle.
+      // If the timeout is enabled, bail out into terminal error state
+      // if the timeout counter expires (this will raise an alert).
+      CnstyWaitSt: begin
+        chk_pending_o = 1'b1;
+        if (!timeout_zero && cnsty_cnt_zero) begin
+          state_d = ErrSt;
+        end else if (cnsty_chk_req_q == '0) begin
+          state_d = IdleSt;
+          // This draws the next wait period.
+          cnsty_load_period = 1'b1;
+          lfsr_en = 1'b1;
+        end
+      end
+      ///////////////////////////////////////////////////////////////////
+      // Terminal error state. This raises an alert.
+      ErrSt: begin
+        chk_timeout_o = 1'b1;
+      end
+      ///////////////////////////////////////////////////////////////////
+      // This should never happen, hence we directly jump into the
+      // error state, where an alert will be triggered.
+      default: begin
+        state_d = ErrSt;
+      end
+      ///////////////////////////////////////////////////////////////////
+    endcase
+  end
+
+  ///////////////
+  // Registers //
+  ///////////////
+
+  always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
+    if (!rst_ni) begin
+      state_q     <= ResetSt;
+      integ_cnt_q <= '0;
+      cnsty_cnt_q <= '0;
+      integ_chk_req_q <= '0;
+      cnsty_chk_req_q <= '0;
+    end else begin
+      state_q     <= state_d;
+      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;
+    end
+  end
+
+  ////////////////
+  // Assertions //
+  ////////////////
+
+
+endmodule : otp_ctrl_lfsr_timer
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
index 8e5c685..1805c8b 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
@@ -14,6 +14,8 @@
 
   parameter int NumPart = 7;
   parameter int NumPartWidth = vbits(NumPart);
+  // This defines the width of the check timers and LFSR
+  parameter int TimerWidth = 40;
 
   // TODO: may need to tune this and make sure that this encoding not optimized away.
   // Redundantly encoded and complementary values are used to for signalling to the partition
@@ -186,6 +188,17 @@
   parameter int DaiIdx = 7;
   parameter int LciIdx = 8;
 
+  parameter int NumUnbuffered = 2;
+
+  ////////////////////////
+  // Typedefs for CSRNG //
+  ////////////////////////
+
+  typedef struct packed {
+    logic        en;
+    logic [31:0] data;
+  } otp_entropy_t;
+
   ///////////////////////////////
   // Typedefs for LC Interface //
   ///////////////////////////////