[otp_ctrl] Switch to standardized prim_count

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/otp_ctrl/otp_ctrl.core b/hw/ip/otp_ctrl/otp_ctrl.core
index 159c101..7a604ca 100644
--- a/hw/ip/otp_ctrl/otp_ctrl.core
+++ b/hw/ip/otp_ctrl/otp_ctrl.core
@@ -14,6 +14,7 @@
       - lowrisc:prim:ram_1p
       - lowrisc:prim:otp
       - lowrisc:prim:double_lfsr
+      - lowrisc:prim:count
       - lowrisc:prim:lc_sync
       - lowrisc:prim:buf
       - lowrisc:prim:flop
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
index 85a0a9e..627dbf7 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
@@ -1071,7 +1071,7 @@
                                          cnsty_chk_req[k]};
 
       // Alert assertion for sparse FSM.
-      `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlPartBufFsmCheck_A,
+      `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlPartUnbufFsmCheck_A,
           u_part_unbuf.u_state_regs, alert_tx_o[1])
     ////////////////////////////////////////////////////////////////////////////////////////////////
     end else if (PartInfo[k].variant == Buffered) begin : gen_buffered
@@ -1127,6 +1127,8 @@
       // Alert assertion for sparse FSM.
       `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlPartBufFsmCheck_A,
           u_part_buf.u_state_regs, alert_tx_o[1])
+      `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntPartBufCheck_A,
+          u_part_buf.u_prim_count, alert_tx_o[1])
     ////////////////////////////////////////////////////////////////////////////////////////////////
     end else if (PartInfo[k].variant == LifeCycle) begin : gen_lifecycle
       otp_ctrl_part_buf #(
@@ -1193,6 +1195,8 @@
       // Alert assertion for sparse FSM.
       `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlPartLcFsmCheck_A,
           u_part_buf.u_state_regs, alert_tx_o[1])
+      `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntPartLcCheck_A,
+          u_part_buf.u_prim_count, alert_tx_o[1])
     ////////////////////////////////////////////////////////////////////////////////////////////////
     end else begin : gen_invalid
       // This is invalid and should break elaboration
@@ -1333,4 +1337,20 @@
   `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlScrambleFsmCheck_A,
       u_otp_ctrl_scrmbl.u_state_regs, alert_tx_o[1])
 
+  // Alert assertions for redundant counters.
+  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntIntegCheck_A,
+      u_otp_ctrl_lfsr_timer.u_prim_count_integ, alert_tx_o[1])
+  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntCnstyCheck_A,
+      u_otp_ctrl_lfsr_timer.u_prim_count_cnsty, alert_tx_o[1])
+  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntDaiCheck_A,
+      u_otp_ctrl_dai.u_prim_count, alert_tx_o[1])
+  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntKdiSeedCheck_A,
+      u_otp_ctrl_kdi.u_prim_count_seed, alert_tx_o[1])
+  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntKdiEntropyCheck_A,
+      u_otp_ctrl_kdi.u_prim_count_entropy, alert_tx_o[1])
+  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntLciCheck_A,
+      u_otp_ctrl_lci.u_prim_count, alert_tx_o[1])
+  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntScrmblCheck_A,
+      u_otp_ctrl_scrmbl.u_prim_count, alert_tx_o[1])
+
 endmodule : otp_ctrl
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_dai.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_dai.sv
index c2704fc..a0c420a 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_dai.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_dai.sv
@@ -141,8 +141,8 @@
   } addr_sel_e;
 
   state_e state_d, state_q;
-  logic [CntWidth-1:0] cnt_d, cnt_q;
-  logic cnt_en, cnt_clr;
+  logic [CntWidth-1:0] cnt;
+  logic cnt_en, cnt_clr, cnt_err;
   otp_err_e error_d, error_q;
   logic data_en, data_clr;
   data_sel_e data_sel;
@@ -550,7 +550,7 @@
         // No need to digest the digest value itself
         if (otp_addr_o == digest_addr_lut[part_idx]) begin
           // Trigger digest round in case this is the second block in a row.
-          if (!cnt_q[0]) begin
+          if (!cnt[0]) begin
             scrmbl_cmd_o = Digest;
             if (scrmbl_ready_i) begin
               state_d = DigFinSt;
@@ -561,7 +561,7 @@
           end
         end else begin
           // Trigger digest round in case this is the second block in a row.
-          if (!cnt_q[0]) begin
+          if (!cnt[0]) begin
             scrmbl_cmd_o = Digest;
           end
           // Go back and fetch more data blocks.
@@ -624,7 +624,7 @@
     endcase // state_q
 
     // Unconditionally jump into the terminal error state in case of escalation.
-    if (escalate_en_i != lc_ctrl_pkg::Off) begin
+    if (escalate_en_i != lc_ctrl_pkg::Off || cnt_err) begin
       state_d = ErrorSt;
       if (state_q != ErrorSt) begin
         error_d = FsmStateError;
@@ -708,13 +708,26 @@
 
   // Address counter - this is only used for computing a digest, hence the increment is
   // fixed to 8 byte.
-  assign cnt_d = (cnt_clr) ? '0           :
-                 (cnt_en)  ? cnt_q + 1'b1 : cnt_q;
+  prim_count #(
+    .Width(CntWidth),
+    .OutSelDnCnt(0), // count up
+    .CntStyle(prim_count_pkg::CrossCnt)
+  ) u_prim_count (
+    .clk_i,
+    .rst_ni,
+    .clr_i(cnt_clr),
+    .set_i(1'b0),
+    .set_cnt_i('0),
+    .en_i(cnt_en),
+    .step_i(CntWidth'(1)),
+    .cnt_o(cnt),
+    .err_o(cnt_err)
+  );
 
   // Note that OTP works on halfword (16bit) addresses, hence need to
   // shift the addresses appropriately.
   logic [OtpByteAddrWidth-1:0] addr_calc;
-  assign addr_calc = {cnt_q, {$clog2(ScrmblBlockWidth/8){1'b0}}} + addr_base;
+  assign addr_calc = {cnt, {$clog2(ScrmblBlockWidth/8){1'b0}}} + addr_base;
   assign otp_addr_o = OtpAddrWidth'(addr_calc >> OtpAddrShift);
 
   ///////////////
@@ -739,12 +752,10 @@
   always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
     if (!rst_ni) begin
       error_q        <= NoError;
-      cnt_q          <= '0;
       data_q         <= '0;
       base_sel_q     <= DaiOffset;
     end else begin
       error_q        <= error_d;
-      cnt_q          <= cnt_d;
       base_sel_q     <= base_sel_d;
 
       // Working register
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_kdi.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_kdi.sv
index 8339c62..4e7b61d 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_kdi.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_kdi.sv
@@ -188,13 +188,41 @@
   // Temporary Regs and Muxes //
   //////////////////////////////
 
-  logic seed_cnt_clr, seed_cnt_en, entropy_cnt_clr, entropy_cnt_en;
-  logic [1:0] seed_cnt_d, seed_cnt_q, entropy_cnt_d, entropy_cnt_q;
+  localparam int CntWidth = 2;
+  logic seed_cnt_clr, seed_cnt_en, entropy_cnt_clr, entropy_cnt_en, seed_cnt_err, entropy_cnt_err;
+  logic [CntWidth-1:0] seed_cnt, entropy_cnt;
 
-  assign seed_cnt_d    = (seed_cnt_clr)    ? '0 :
-                         (seed_cnt_en)     ? seed_cnt_q + 1'b1 : seed_cnt_q;
-  assign entropy_cnt_d = (entropy_cnt_clr) ? '0 :
-                         (entropy_cnt_en)  ? entropy_cnt_q + 1'b1 : entropy_cnt_q;
+  prim_count #(
+    .Width(CntWidth),
+    .OutSelDnCnt(0), // count up
+    .CntStyle(prim_count_pkg::CrossCnt)
+  ) u_prim_count_seed (
+    .clk_i,
+    .rst_ni,
+    .clr_i(seed_cnt_clr),
+    .set_i(1'b0),
+    .set_cnt_i('0),
+    .en_i(seed_cnt_en),
+    .step_i(CntWidth'(1)),
+    .cnt_o(seed_cnt),
+    .err_o(seed_cnt_err)
+  );
+
+  prim_count #(
+    .Width(CntWidth),
+    .OutSelDnCnt(0), // count up
+    .CntStyle(prim_count_pkg::CrossCnt)
+  ) u_prim_count_entropy (
+    .clk_i,
+    .rst_ni,
+    .clr_i(entropy_cnt_clr),
+    .set_i(1'b0),
+    .set_cnt_i('0),
+    .en_i(entropy_cnt_en),
+    .step_i(CntWidth'(1)),
+    .cnt_o(entropy_cnt),
+    .err_o(entropy_cnt_err)
+  );
 
   logic seed_valid_reg_en;
   logic key_reg_en, nonce_reg_en;
@@ -207,10 +235,10 @@
     nonce_out_d  = nonce_out_q;
     seed_valid_d = seed_valid_q;
     if (key_reg_en) begin
-      key_out_d[seed_cnt_q[1]] = scrmbl_data_i;
+      key_out_d[seed_cnt[1]] = scrmbl_data_i;
     end
     if (nonce_reg_en) begin
-      nonce_out_d[entropy_cnt_q] = edn_data_i;
+      nonce_out_d[entropy_cnt] = edn_data_i;
     end
     if (seed_valid_reg_en) begin
       seed_valid_d = req_bundle.seed_valid;
@@ -239,9 +267,9 @@
 
   // Select correct 64bit block.
   data_sel_e data_sel;
-  assign scrmbl_data_o = (data_sel == EntropyData) ? nonce_out_q[entropy_cnt_q[0]] :
+  assign scrmbl_data_o = (data_sel == EntropyData) ? nonce_out_q[entropy_cnt[0]] :
                         // Gate seed value to '0 if invalid.
-                         (req_bundle.seed_valid)   ? req_bundle.seed[seed_cnt_q]   : '0;
+                         (req_bundle.seed_valid)   ? req_bundle.seed[seed_cnt]   : '0;
 
   /////////////////
   // Control FSM //
@@ -360,7 +388,7 @@
         scrmbl_mtx_req_o = 1'b1;
         scrmbl_valid_o = 1'b1;
         // Trigger digest round in case this is the second block in a row.
-        if (seed_cnt_q[0]) begin
+        if (seed_cnt[0]) begin
           scrmbl_cmd_o = Digest;
           if (scrmbl_ready_i) begin
             // Go and ingest a block of entropy if required.
@@ -384,7 +412,7 @@
         if (edn_ack_i) begin
           nonce_reg_en = 1'b1;
           // Finished, go and acknowledge this request.
-          if (entropy_cnt_q == 2'h1) begin
+          if (entropy_cnt == 2'h1) begin
             state_d = DigEntropySt;
             entropy_cnt_clr = 1'b1;
           // Keep on requesting entropy.
@@ -401,7 +429,7 @@
         scrmbl_valid_o = 1'b1;
         // Trigger digest round in case this is the second block in a row,
         // and go to digest finalization.
-        if (entropy_cnt_q[0]) begin
+        if (entropy_cnt[0]) begin
           scrmbl_cmd_o = Digest;
           if (scrmbl_ready_i) begin
             state_d = DigFinSt;
@@ -431,7 +459,7 @@
         if (scrmbl_valid_i) begin
           key_reg_en = 1'b1;
           // Not finished yet, need to go back and produce second 64bit block.
-          if (seed_cnt_q == 2'h1) begin
+          if (seed_cnt == 2'h1) begin
             seed_cnt_en  = 1'b1;
             // In this case the previous digest state is kept,
             // which leads to a chained digest.
@@ -464,7 +492,7 @@
         if (edn_ack_i) begin
           nonce_reg_en = 1'b1;
           // Finished, go and acknowledge this request.
-          if (entropy_cnt_q == req_bundle.nonce_size) begin
+          if (entropy_cnt == req_bundle.nonce_size) begin
             state_d = FinishSt;
             entropy_cnt_clr = 1'b1;
           // Keep on requesting entropy.
@@ -494,7 +522,7 @@
     endcase // state_q
 
     // Unconditionally jump into the terminal error state in case of escalation.
-    if (escalate_en_i != lc_ctrl_pkg::Off) begin
+    if (escalate_en_i != lc_ctrl_pkg::Off || seed_cnt_err || entropy_cnt_err) begin
       state_d = ErrorSt;
     end
   end
@@ -520,15 +548,11 @@
 
   always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
     if (!rst_ni) begin
-      seed_cnt_q    <= '0;
-      entropy_cnt_q <= '0;
       key_out_q     <= '0;
       nonce_out_q   <= '0;
       seed_valid_q  <= 1'b0;
       edn_req_q     <= 1'b0;
     end else begin
-      seed_cnt_q    <= seed_cnt_d;
-      entropy_cnt_q <= entropy_cnt_d;
       key_out_q     <= key_out_d;
       nonce_out_q   <= nonce_out_d;
       seed_valid_q  <= seed_valid_d;
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
index 1dc605d..f8116a0 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
@@ -97,8 +97,8 @@
     ErrorSt     = 9'b011111101
   } state_e;
 
-  logic cnt_clr, cnt_en;
-  logic [CntWidth-1:0] cnt_d, cnt_q;
+  logic cnt_clr, cnt_en, cnt_err;
+  logic [CntWidth-1:0] cnt;
   otp_err_e error_d, error_q;
   state_e state_d, state_q;
 
@@ -173,7 +173,7 @@
 
           // Check whether we programmed all OTP words.
           // If yes, we are done and can go back to idle.
-          if (cnt_q == LastLcOtpWord) begin
+          if (cnt == LastLcOtpWord) begin
             state_d = IdleSt;
             lc_ack_o = 1'b1;
             // If in any of the words a programming error has occurred,
@@ -208,7 +208,7 @@
     endcase // state_q
 
     // Unconditionally jump into the terminal error state in case of escalation.
-    if (escalate_en_i != lc_ctrl_pkg::Off) begin
+    if (escalate_en_i != lc_ctrl_pkg::Off || cnt_err) begin
       state_d = ErrorSt;
       if (error_q == NoError) begin
         error_d = FsmStateError;
@@ -222,20 +222,33 @@
   //////////////////////////////
 
   // Native OTP word counter
-  assign cnt_d = (cnt_clr) ? '0           :
-                 (cnt_en)  ? cnt_q + 1'b1 : cnt_q;
+  prim_count #(
+    .Width(CntWidth),
+    .OutSelDnCnt(0), // count up
+    .CntStyle(prim_count_pkg::CrossCnt)
+  ) u_prim_count (
+    .clk_i,
+    .rst_ni,
+    .clr_i(cnt_clr),
+    .set_i(1'b0),
+    .set_cnt_i('0),
+    .en_i(cnt_en),
+    .step_i(CntWidth'(1)),
+    .cnt_o(cnt),
+    .err_o(cnt_err)
+  );
 
   // The output address is "offset + count", but we have to convert Info.offset from a byte address
   // to a halfword (16-bit) address by discarding the bottom OtpAddrShift bits. We also make the
-  // zero-extension of cnt_q explicit (to avoid width mismatch warnings).
-  assign otp_addr_o = Info.offset[OtpByteAddrWidth-1:OtpAddrShift] + OtpAddrWidth'(cnt_q);
+  // zero-extension of cnt explicit (to avoid width mismatch warnings).
+  assign otp_addr_o = Info.offset[OtpByteAddrWidth-1:OtpAddrShift] + OtpAddrWidth'(cnt);
 
   // Always transfer 16bit blocks.
   assign otp_size_o = '0;
 
   logic [NumLcOtpWords-1:0][OtpWidth-1:0] data;
   assign data        = lc_data_i;
-  assign otp_wdata_o = (otp_req_o) ? OtpIfWidth'(data[cnt_q]) : '0;
+  assign otp_wdata_o = (otp_req_o) ? OtpIfWidth'(data[cnt]) : '0;
 
   logic unused_rdata;
   assign unused_rdata = ^otp_rdata_i;
@@ -262,10 +275,8 @@
   always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
     if (!rst_ni) begin
       error_q <= NoError;
-      cnt_q   <= '0;
     end else begin
       error_q <= error_d;
-      cnt_q   <= cnt_d;
     end
   end
 
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 7ec2d67..5d8abed 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv
@@ -118,78 +118,61 @@
   // Tandem Counter Instances //
   //////////////////////////////
 
-  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 [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}}};
-
-  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] == '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;
+  logic [LfsrWidth-1:0] integ_cnt, cnsty_cnt, integ_cnt_set_val, cnsty_cnt_set_val;
+  logic [LfsrWidth-1:0] integ_mask, cnsty_mask;
+  logic integ_set_period, integ_set_timeout, integ_cnt_zero;
+  logic cnsty_set_period, cnsty_set_timeout, cnsty_cnt_zero;
+  logic integ_cnt_set, cnsty_cnt_set, integ_cnt_err, cnsty_cnt_err;
+  logic timeout_zero, integ_msk_zero, cnsty_msk_zero, cnsty_cnt_pause;
 
-    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})
-    );
+  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 == '0);
+  assign cnsty_cnt_zero = (cnsty_cnt == '0);
 
-    logic [LfsrWidth-1:0] integ_cnt_d;
-    logic [LfsrWidth-1:0] cnsty_cnt_d;
-    assign integ_cnt_d = (integ_load_period_buf)  ? lfsr_state & integ_mask :
-                         (integ_load_timeout_buf) ? LfsrWidth'(timeout_i)   :
-                         (integ_cnt_q[k] == '0)   ? '0                      :
-                                                    integ_cnt_q[k] - 1'b1;
+  assign integ_cnt_set = integ_set_period || integ_set_timeout;
+  assign cnsty_cnt_set = cnsty_set_period || cnsty_set_timeout;
 
+  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_set_val = (integ_set_period) ? (lfsr_state & integ_mask) : LfsrWidth'(timeout_i);
+  assign cnsty_cnt_set_val = (cnsty_set_period) ? (lfsr_state & cnsty_mask) : LfsrWidth'(timeout_i);
 
-    assign cnsty_cnt_d = (cnsty_load_period_buf)  ? lfsr_state & 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_count #(
+    .Width(LfsrWidth),
+    .OutSelDnCnt(1), // count down
+    .CntStyle(prim_count_pkg::CrossCnt)
+  ) u_prim_count_integ (
+    .clk_i,
+    .rst_ni,
+    .clr_i(1'b0),
+    .set_i(integ_cnt_set),
+    .set_cnt_i(integ_cnt_set_val),
+    .en_i(!integ_cnt_zero),
+    .step_i(LfsrWidth'(1)),
+    .cnt_o(integ_cnt),
+    .err_o(integ_cnt_err)
+  );
 
-    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
+  prim_count #(
+    .Width(LfsrWidth),
+    .OutSelDnCnt(1), // count down
+    .CntStyle(prim_count_pkg::CrossCnt)
+  ) u_prim_count_cnsty (
+    .clk_i,
+    .rst_ni,
+    .clr_i(1'b0),
+    .set_i(cnsty_cnt_set),
+    .set_cnt_i(cnsty_cnt_set_val),
+    .en_i(!cnsty_cnt_zero && !cnsty_cnt_pause),
+    .step_i(LfsrWidth'(1)),
+    .cnt_o(cnsty_cnt),
+    .err_o(cnsty_cnt_err)
+  );
 
   /////////////////////
   // Request signals //
@@ -258,10 +241,10 @@
 
     // 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;
+    integ_set_period  = 1'b0;
+    cnsty_set_period  = 1'b0;
+    integ_set_timeout = 1'b0;
+    cnsty_set_timeout = 1'b0;
     cnsty_cnt_pause    = 1'b0;
 
     // Requests going to partitions.
@@ -293,12 +276,12 @@
       IdleSt: begin
         if ((!integ_msk_zero && integ_cnt_zero) || integ_chk_trig_q) begin
           state_d = IntegWaitSt;
-          integ_load_timeout = 1'b1;
+          integ_set_timeout = 1'b1;
           set_all_integ_reqs = 1'b1;
           clr_integ_chk_trig = integ_chk_trig_q;
         end else if ((!cnsty_msk_zero && cnsty_cnt_zero) || cnsty_chk_trig_q) begin
           state_d = CnstyWaitSt;
-          cnsty_load_timeout = 1'b1;
+          cnsty_set_timeout = 1'b1;
           set_all_cnsty_reqs = 1'b1;
           clr_cnsty_chk_trig = cnsty_chk_trig_q;
         end
@@ -315,7 +298,7 @@
         end else if (integ_chk_req_q == '0) begin
           state_d = IdleSt;
           // This draws the next wait period.
-          integ_load_period = 1'b1;
+          integ_set_period = 1'b1;
           lfsr_en = 1'b1;
         end
       end
@@ -337,7 +320,7 @@
         end else if (cnsty_chk_req_q == '0) begin
           state_d = IdleSt;
           // This draws the next wait period.
-          cnsty_load_period = 1'b1;
+          cnsty_set_period = 1'b1;
           lfsr_en = 1'b1;
         end
       end
@@ -362,10 +345,7 @@
 
     // Unconditionally jump into the terminal error state in case of escalation,
     // or if the two LFSR or counter states do not agree.
-    if (lfsr_err ||
-        integ_cnt_q[0] != integ_cnt_q[1] ||
-        cnsty_cnt_q[0] != cnsty_cnt_q[1] ||
-        escalate_en_i != lc_ctrl_pkg::Off) begin
+    if (lfsr_err || integ_cnt_err || cnsty_cnt_err || escalate_en_i != lc_ctrl_pkg::Off) begin
        state_d = ErrorSt;
     end
   end
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv
index 1367f2f..a30b43b 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv
@@ -163,8 +163,8 @@
   data_sel_e data_sel;
   base_sel_e base_sel;
   mubi8_t dout_locked_d, dout_locked_q;
-  logic [CntWidth-1:0] cnt_d, cnt_q;
-  logic cnt_en, cnt_clr;
+  logic [CntWidth-1:0] cnt;
+  logic cnt_en, cnt_clr, cnt_err;
   logic ecc_err;
   logic buffer_reg_en;
   logic [ScrmblBlockWidth-1:0] data_mux;
@@ -245,7 +245,7 @@
             // Once we've read and descrambled the whole partition, we can go to integrity
             // verification. Note that the last block is the digest value, which does not
             // have to be descrambled.
-            if (cnt_q == LastScrmblBlock) begin
+            if (cnt == LastScrmblBlock) begin
               state_d = IntegDigClrSt;
             // Only need to descramble if this is a scrambled partition.
             // Otherwise, we can just go back to InitSt and read the next block.
@@ -357,7 +357,7 @@
               if (scrmbl_data_o == data_mux || check_byp_en_i == lc_ctrl_pkg::On) begin
                 // Can go back to idle and acknowledge the
                 // request if this is the last block.
-                if (cnt_q == LastScrmblBlock) begin
+                if (cnt == LastScrmblBlock) begin
                   state_d = IdleSt;
                   cnsty_chk_ack_o = 1'b1;
                 // Need to go back and read out more blocks.
@@ -452,11 +452,11 @@
         if (scrmbl_ready_i) begin
           cnt_en = 1'b1;
           // No need to digest the digest value itself
-          if (cnt_q == PenultimateScrmblBlock) begin
+          if (cnt == PenultimateScrmblBlock) begin
             // Note that the digest operates on 128bit blocks since the data is fed in via the
             // PRESENT key input. Therefore, we only trigger a digest update on every second
             // 64bit block that is pushed into the scrambling datapath.
-            if (cnt_q[0]) begin
+            if (cnt[0]) begin
               scrmbl_cmd_o = Digest;
               state_d = IntegDigFinSt;
             end else begin
@@ -465,7 +465,7 @@
             end
           end else begin
             // Trigger digest round in case this is the second block in a row.
-            if (cnt_q[0]) begin
+            if (cnt[0]) begin
               scrmbl_cmd_o = Digest;
             end
             // Go back and scramble the next data block if this is
@@ -560,7 +560,7 @@
         error_d = CheckFailError;
       end
     end
-    if (escalate_en_i != lc_ctrl_pkg::Off) begin
+    if (escalate_en_i != lc_ctrl_pkg::Off || cnt_err) begin
       state_d = ErrorSt;
       if (state_q != ErrorSt) begin
         error_d = FsmStateError;
@@ -574,8 +574,21 @@
 
   // Address counter - this is only used for computing a digest, hence the increment is
   // fixed to 8 byte.
-  assign cnt_d = (cnt_clr) ? '0           :
-                 (cnt_en)  ? cnt_q + 1'b1 : cnt_q;
+  prim_count #(
+    .Width(CntWidth),
+    .OutSelDnCnt(0), // count up
+    .CntStyle(prim_count_pkg::CrossCnt)
+  ) u_prim_count (
+    .clk_i,
+    .rst_ni,
+    .clr_i(cnt_clr),
+    .set_i(1'b0),
+    .set_cnt_i('0),
+    .en_i(cnt_en),
+    .step_i(CntWidth'(1)),
+    .cnt_o(cnt),
+    .err_o(cnt_err)
+  );
 
   logic [OtpByteAddrWidth-1:0] addr_base;
   assign addr_base = (base_sel == DigOffset) ? DigestOffset : Info.offset;
@@ -583,7 +596,7 @@
   // Note that OTP works on halfword (16bit) addresses, hence need to
   // shift the addresses appropriately.
   logic [OtpByteAddrWidth-1:0] addr_calc;
-  assign addr_calc = OtpByteAddrWidth'({cnt_q, {$clog2(ScrmblBlockWidth/8){1'b0}}}) + addr_base;
+  assign addr_calc = OtpByteAddrWidth'({cnt, {$clog2(ScrmblBlockWidth/8){1'b0}}}) + addr_base;
   assign otp_addr_o = addr_calc[OtpByteAddrWidth-1:OtpAddrShift];
 
   if (OtpAddrShift > 0) begin : gen_unused
@@ -595,7 +608,7 @@
   assign otp_size_o = OtpSizeWidth'(unsigned'(ScrmblBlockWidth / OtpWidth) - 1);
 
   logic [Info.size*8-1:0] data;
-  assign scrmbl_data_o = data[{cnt_q, {$clog2(ScrmblBlockWidth){1'b0}}} +: ScrmblBlockWidth];
+  assign scrmbl_data_o = data[{cnt, {$clog2(ScrmblBlockWidth){1'b0}}} +: ScrmblBlockWidth];
 
   assign data_mux = (data_sel == ScrmblData) ? scrmbl_data_i : otp_rdata_i;
 
@@ -610,7 +623,7 @@
     .clk_i,
     .rst_ni,
     .wren_i    ( buffer_reg_en ),
-    .addr_i    ( cnt_q         ),
+    .addr_i    ( cnt         ),
     .wdata_i   ( data_mux      ),
     .data_o    ( data          ),
     .ecc_err_o ( ecc_err       )
@@ -686,12 +699,10 @@
   always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
     if (!rst_ni) begin
       error_q       <= NoError;
-      cnt_q         <= '0;
       // data output is locked by default
       dout_locked_q <= MuBi8True;
     end else begin
       error_q       <= error_d;
-      cnt_q         <= cnt_d;
       dout_locked_q <= dout_locked_d;
     end
   end
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv
index 0682ebc..6758f00 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv
@@ -238,15 +238,27 @@
   localparam bit [CntWidth-1:0] LastPresentRound = LastPresentRoundInt[CntWidth-1:0];
 
   state_e state_d, state_q;
-  logic [CntWidth-1:0] cnt_d, cnt_q;
-  logic cnt_clr, cnt_en;
+  logic [CntWidth-1:0] cnt;
+  logic cnt_clr, cnt_en, cnt_err;
   logic valid_d, valid_q;
 
   assign valid_o = valid_q;
 
-  assign cnt_d = (cnt_clr) ? '0        :
-                 (cnt_en)  ? cnt_q + 1 :
-                             cnt_q;
+  prim_count #(
+    .Width(CntWidth),
+    .OutSelDnCnt(0), // count up
+    .CntStyle(prim_count_pkg::CrossCnt)
+  ) u_prim_count (
+    .clk_i,
+    .rst_ni,
+    .clr_i(cnt_clr),
+    .set_i(1'b0),
+    .set_cnt_i('0),
+    .en_i(cnt_en),
+    .step_i(CntWidth'(1)),
+    .cnt_o(cnt),
+    .err_o(cnt_err)
+  );
 
   always_comb begin : p_fsm
     state_d          = state_q;
@@ -326,7 +338,7 @@
         data_state_en  = 1'b1;
         key_state_en   = 1'b1;
         cnt_en         = 1'b1;
-        if (cnt_q == LastPresentRound) begin
+        if (cnt == LastPresentRound) begin
           state_d = IdleSt;
           valid_d = 1'b1;
         end
@@ -339,7 +351,7 @@
         data_state_en  = 1'b1;
         key_state_en   = 1'b1;
         cnt_en         = 1'b1;
-        if (cnt_q == LastPresentRound) begin
+        if (cnt == LastPresentRound) begin
           state_d = IdleSt;
           valid_d = 1'b1;
         end
@@ -353,7 +365,7 @@
         data_state_en  = 1'b1;
         key_state_en   = 1'b1;
         cnt_en         = 1'b1;
-        if (cnt_q == LastPresentRound) begin
+        if (cnt == LastPresentRound) begin
           state_d = IdleSt;
           valid_d = 1'b1;
           // Apply XOR for Davies-Meyer construction.
@@ -379,7 +391,7 @@
     endcase // state_q
 
     // Unconditionally jump into the terminal error state in case of escalation.
-    if (escalate_en_i != lc_ctrl_pkg::Off) begin
+    if (escalate_en_i != lc_ctrl_pkg::Off || cnt_err) begin
       state_d = ErrorSt;
     end
   end
@@ -437,7 +449,6 @@
 
   always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
     if (!rst_ni) begin
-      cnt_q          <= '0;
       key_state_q    <= '0;
       idx_state_q    <= '0;
       data_state_q   <= '0;
@@ -446,7 +457,6 @@
       valid_q        <= 1'b0;
       digest_mode_q  <= StandardMode;
     end else begin
-      cnt_q         <= cnt_d;
       valid_q       <= valid_d;
       digest_mode_q <= digest_mode_d;