[otp_ctrl] Update LC types within OTP

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/otp_ctrl/doc/_index.md b/hw/ip/otp_ctrl/doc/_index.md
index 82d8eb9..cafcefa 100644
--- a/hw/ip/otp_ctrl/doc/_index.md
+++ b/hw/ip/otp_ctrl/doc/_index.md
@@ -594,8 +594,8 @@
 ![Life Cycle Interface FSM](otp_ctrl_lci_fsm.svg)
 
 Upon reset release the LCI FSM waits until the OTP controller has initialized and the LCI gets enabled.
-Once it is in the idle state, incremental life cycle state updates can be initiated via the life cycle interface as [described here]({{< relref "#state-transitions" >}}).
-The LCI controller takes the life cycle state delta to be programmed and writes all non-blank 16bit words to OTP.
+Once it is in the idle state, life cycle state updates can be initiated via the life cycle interface as [described here]({{< relref "#state-transitions" >}}).
+The LCI controller takes the life cycle state to be programmed and writes all 16bit words to OTP.
 In case of unrecoverable OTP errors, the FSM signals an error to the life cycle controller and moves into a terminal error state.
 
 ### Key Derivation Interface
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
index 8d9a348..ee73824 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
@@ -758,8 +758,8 @@
     .error_o          ( part_error[LciIdx]                ),
     .lci_idle_o       ( lci_idle                          ),
     .lc_req_i         ( lc_otp_program_i.req              ),
-    .lc_state_delta_i ( lc_otp_program_i.state_delta      ),
-    .lc_count_delta_i ( lc_otp_program_i.count_delta      ),
+    .lc_state_i       ( lc_otp_program_i.state            ),
+    .lc_count_i       ( lc_otp_program_i.count            ),
     .lc_ack_o         ( lc_otp_program_o.ack              ),
     .lc_err_o         ( lc_otp_program_o.err              ),
     .otp_req_o        ( part_otp_arb_req[LciIdx]          ),
@@ -940,7 +940,7 @@
       otp_ctrl_part_buf #(
         .Info(PartInfo[k]),
         // By default all counter words are set and the life cycle state is scrapped.
-        .DataDefault({{lc_ctrl_pkg::NumLcCountValues{lc_ctrl_pkg::Set}}, lc_ctrl_pkg::LcStScrap})
+        .DataDefault({lc_ctrl_pkg::LcCnt16, lc_ctrl_pkg::LcStScrap})
       ) u_part_buf (
         .clk_i,
         .rst_ni,
@@ -1024,13 +1024,14 @@
                                                          RmaTokenSize];
 
   // The device is personalized if the root key has been provisioned and locked
-  assign otp_lc_data_o.id_state       = (part_digest[Secret2Idx] != '0) ? lc_ctrl_pkg::Set :
-                                                                          lc_ctrl_pkg::Blk;
+  assign otp_lc_data_o.id_state       = (part_digest[Secret2Idx] != '0) ?
+                                        lc_ctrl_pkg::LcIdPersonalized :
+                                        lc_ctrl_pkg::LcIdBlank;
 
   // Lifecycle state
   assign otp_lc_data_o.state = lc_ctrl_pkg::lc_state_e'(part_buf_data[LcStateOffset +:
                                                                       LcStateSize]);
-  assign otp_lc_data_o.count = lc_ctrl_pkg::lc_cnt_t'(part_buf_data[LcTransitionCntOffset +:
+  assign otp_lc_data_o.count = lc_ctrl_pkg::lc_cnt_e'(part_buf_data[LcTransitionCntOffset +:
                                                                     LcTransitionCntSize]);
 
   // Assert life cycle state valid signal only when all partitions have initialized.
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
index b7ca7cf..aa01105 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
@@ -14,21 +14,22 @@
   // Lifecycle partition information
   parameter part_info_t Info = part_info_t'(0)
 ) (
-  input                                    clk_i,
-  input                                    rst_ni,
-  input                                    lci_en_i,
+  input                                     clk_i,
+  input                                     rst_ni,
+  input                                     lci_en_i,
   // Escalation input. This moves the FSM into a terminal state and locks down
   // the partition.
-  input  lc_ctrl_pkg::lc_tx_t              escalate_en_i,
+  input  lc_ctrl_pkg::lc_tx_t               escalate_en_i,
   // Life cycle transition request. In order to perform a state transition,
-  // the LC controller signals the incremental value with respect to the
-  // current state. The OTP controller then only programs the non-zero
-  // state delta.
-  input                                    lc_req_i,
-  input  lc_ctrl_pkg::lc_state_e           lc_state_delta_i,
-  input  lc_ctrl_pkg::lc_cnt_t             lc_count_delta_i,
-  output logic                             lc_ack_o,
-  output logic                             lc_err_o,
+  // the LC controller signals the new count and state. The OTP wrapper then
+  // only programs bits that have not been programmed before.
+  // Note that a transition request will fail if the request attempts to
+  // clear already programmed bits within OTP.
+  input                                     lc_req_i,
+  input  lc_ctrl_pkg::lc_state_e            lc_state_i,
+  input  lc_ctrl_pkg::lc_cnt_e              lc_count_i,
+  output logic                              lc_ack_o,
+  output logic                              lc_err_o,
   // Output error state of partition, to be consumed by OTP error/alert logic.
   // Note that most errors are not recoverable and move the partition FSM into
   // a terminal error state.
@@ -91,7 +92,6 @@
   logic cnt_clr, cnt_en;
   logic [CntWidth-1:0] cnt_d, cnt_q;
   otp_err_e error_d, error_q;
-  logic delta_data_is_set;
   state_e state_d, state_q;
 
   // Output LCI errors
@@ -135,23 +135,15 @@
         end
       end
       ///////////////////////////////////////////////////////////////////
-      // Loop through the lifecycle sate and burn in the words that are set.
+      // Loop through the lifecycle sate and burn in all words.
+      // If the write data contains a 0 bit in a position where a bit has already been
+      // programmed to 1 before, the OTP errors out.
       WriteSt: begin
         // Check whether the OTP word is nonzero.
-        if (delta_data_is_set) begin
-          otp_req_o = 1'b1;
-          otp_cmd_o = OtpWrite;
-          if (otp_gnt_i) begin
-            state_d = WriteWaitSt;
-          end
-        // Check whether we examined all OTP words.
-        // If yes, we are done and can go back to idle.
-        end else if (cnt_q == NumLcOtpWords-1) begin
-          state_d = IdleSt;
-          lc_ack_o = 1'b1;
-        // Otherwise we increase the OTP word counter.
-        end else begin
-          cnt_en = 1'b1;
+        otp_req_o = 1'b1;
+        otp_cmd_o = OtpWrite;
+        if (otp_gnt_i) begin
+          state_d = WriteWaitSt;
         end
       end
       ///////////////////////////////////////////////////////////////////
@@ -160,17 +152,13 @@
       // terminal error state.
       WriteWaitSt: begin
         if (otp_rvalid_i) begin
-          // Check OTP return code. We only tolerate a MacroWriteBlankError error here -
-          // all other errors should not occur and are therefore terminal.
+          // Check OTP return code.
+          // No errors are tolerated here.
           if (otp_err_i != NoError) begin
             error_d = otp_err_i;
             lc_ack_o = 1'b1;
             lc_err_o = 1'b1;
-            if (otp_err_i != MacroWriteBlankError) begin
-              state_d = ErrorSt;
-            end else begin
-              state_d = IdleSt;
-            end
+            state_d = ErrorSt;
           end else begin
             // Check whether we examined all OTP words.
             // If yes, we are done and can go back to idle.
@@ -228,10 +216,9 @@
   // Always transfer 16bit blocks.
   assign otp_size_o = '0;
 
-  logic [NumLcOtpWords-1:0][OtpWidth-1:0] delta_data;
-  assign delta_data        = {lc_count_delta_i, lc_state_delta_i};
-  assign otp_wdata_o       = OtpIfWidth'(delta_data[cnt_q]);
-  assign delta_data_is_set = (delta_data[cnt_q] != lc_ctrl_pkg::Blk);
+  logic [NumLcOtpWords-1:0][OtpWidth-1:0] data;
+  assign data        = {lc_count_i, lc_state_i};
+  assign otp_wdata_o = OtpIfWidth'(data[cnt_q]);
 
   logic unused_rdata;
   assign unused_rdata = ^otp_rdata_i;
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
index bffde32..47a7d12 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
@@ -202,19 +202,19 @@
   ///////////////////////////////
 
   typedef struct packed {
-    logic                                 valid;
-    lc_ctrl_pkg::lc_state_e               state;
-    lc_ctrl_pkg::lc_cnt_t                 count;
-    logic [lc_ctrl_pkg::LcTokenWidth-1:0] test_unlock_token;
-    logic [lc_ctrl_pkg::LcTokenWidth-1:0] test_exit_token;
-    logic [lc_ctrl_pkg::LcTokenWidth-1:0] rma_token;
-    lc_ctrl_pkg::lc_value_e               id_state;
+    logic                      valid;
+    lc_ctrl_pkg::lc_state_e    state;
+    lc_ctrl_pkg::lc_cnt_e      count;
+    lc_ctrl_pkg::lc_token_t    test_unlock_token;
+    lc_ctrl_pkg::lc_token_t    test_exit_token;
+    lc_ctrl_pkg::lc_token_t    rma_token;
+    lc_ctrl_pkg::lc_id_state_e id_state;
   } otp_lc_data_t;
 
   typedef struct packed {
     logic req;
-    lc_ctrl_pkg::lc_state_e state_delta;
-    lc_ctrl_pkg::lc_cnt_t   count_delta;
+    lc_ctrl_pkg::lc_state_e state;
+    lc_ctrl_pkg::lc_cnt_e   count;
   } lc_otp_program_req_t;
 
   typedef struct packed {
@@ -225,12 +225,12 @@
   // RAW unlock token hashing request.
   typedef struct packed {
     logic req;
-    logic [lc_ctrl_pkg::LcTokenWidth-1:0] token_input;
+    lc_ctrl_pkg::lc_token_t token_input;
   } lc_otp_token_req_t;
 
   typedef struct packed {
     logic ack;
-    logic [lc_ctrl_pkg::LcTokenWidth-1:0] hashed_token;
+    lc_ctrl_pkg::lc_token_t hashed_token;
   } lc_otp_token_rsp_t;
 
   ////////////////////////////////
diff --git a/hw/ip/prim_generic/rtl/prim_generic_otp.sv b/hw/ip/prim_generic/rtl/prim_generic_otp.sv
index 107bd52..f22b00a 100644
--- a/hw/ip/prim_generic/rtl/prim_generic_otp.sv
+++ b/hw/ip/prim_generic/rtl/prim_generic_otp.sv
@@ -10,7 +10,6 @@
   // This determines the maximum number of native words that
   // can be transferred accross the interface in one cycle.
   parameter  int SizeWidth   = otp_ctrl_pkg::OtpSizeWidth,
-  parameter  int ErrWidth    = otp_ctrl_pkg::OtpErrWidth,
   // Width of the power sequencing signal.
   parameter  int PwrSeqWidth = otp_ctrl_pkg::OtpPwrSeqWidth,
   // Number of Test TL-UL words
@@ -231,12 +230,13 @@
         req     = 1'b1;
       end
       // Wait for readout to complete first.
-      // If the word is not blank, or if we got an uncorrectable ECC error,
-      // the check has failed and we abort the write at this point.
+      // If the write data would clear an already programmed bit, or if we got an uncorrectable
+      // ECC error, the check has failed and we abort the write at this point.
       WriteWaitSt: begin
         if (rvalid) begin
           cnt_en = 1'b1;
-          if (rerror[1] || rdata_d != '0) begin
+          // TODO: this blank check needs to be extended to account for the ECC bits as well.
+          if (rerror[1] || (rdata_d & wdata_q[cnt_q]) != rdata_d) begin
             state_d = IdleSt;
             valid_d = 1'b1;
             err_d = otp_ctrl_pkg::MacroWriteBlankError;
@@ -250,7 +250,7 @@
           end
         end
       end
-      // Now that we are sure that the memory is blank, we can write all native words in one go.
+      // Now that the write check was successful, we can write all native words in one go.
       WriteSt: begin
         req = 1'b1;
         wren = 1'b1;