[lc_ctrl] Add initial markdown shell and hjson

This allows us to reference and link to life-cycle collateral.

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/Makefile b/hw/Makefile
index 95d9208..51f6bbe9 100644
--- a/hw/Makefile
+++ b/hw/Makefile
@@ -19,6 +19,7 @@
        kmac          \
        i2c           \
        keymgr        \
+       lc_ctrl       \
        nmi_gen       \
        otp_ctrl      \
        padctrl       \
diff --git a/hw/ip/lc_ctrl/data/lc_ctrl.hjson b/hw/ip/lc_ctrl/data/lc_ctrl.hjson
new file mode 100644
index 0000000..11c94d1
--- /dev/null
+++ b/hw/ip/lc_ctrl/data/lc_ctrl.hjson
@@ -0,0 +1,63 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+{ name: "lc_ctrl",
+  clock_primary: "clk_i",
+  bus_device: "tlul",
+  bus_host: "none",
+
+//  ///////////////////////////
+//  // Interrupts and Alerts //
+//  ///////////////////////////
+//
+//  interrupt_list: [
+//  ],
+//
+//  alert_list: [
+//  ],
+//
+//  ////////////////
+//  // Parameters //
+//  ////////////////
+//  param_list: [
+//  ]
+//
+//  /////////////////////////////
+//  // Intermodule Connections //
+//  /////////////////////////////
+//  // TODO: these need to be refined during implementation and integration
+//  inter_signal_list: [
+//  ] // inter_signal_list
+
+  ///////////////
+  // Registers //
+  ///////////////
+
+  regwidth: "32",
+  registers: [
+    { name: "STATUS",
+      desc: "LC status register.",
+      swaccess: "ro",
+      hwaccess: "hwo",
+      fields: [
+        { bits: "1:0"
+          name: "DUMMY"
+          desc: '''foo
+                '''
+        }
+      ]
+    }
+    { name: "CTRL",
+      desc: "LC control register.",
+      swaccess: "ro",
+      hwaccess: "hwo",
+      fields: [
+        { bits: "1:0"
+          name: "DUMMY"
+          desc: '''foo
+                '''
+        }
+      ]
+    }
+  ]
+}
diff --git a/hw/ip/lc_ctrl/data/lc_ctrl.prj.hjson b/hw/ip/lc_ctrl/data/lc_ctrl.prj.hjson
new file mode 100644
index 0000000..89cafe2
--- /dev/null
+++ b/hw/ip/lc_ctrl/data/lc_ctrl.prj.hjson
@@ -0,0 +1,15 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+{
+    name:               "lc_ctrl",
+    design_spec:        "hw/ip/lc_ctrl/doc",
+    dv_plan:            "",
+    hw_checklist:       "",
+    version:            "0.1",
+    life_stage:         "L0",
+    design_stage:       "D0",
+    verification_stage: "V0",
+    notes:              "",
+}
diff --git a/hw/ip/lc_ctrl/doc/_index.md b/hw/ip/lc_ctrl/doc/_index.md
new file mode 100644
index 0000000..0edd27c
--- /dev/null
+++ b/hw/ip/lc_ctrl/doc/_index.md
@@ -0,0 +1,34 @@
+---
+title: "Life Cycle Controller Technical Specification"
+---
+
+
+# Overview
+
+**TODO: populate with spec text. this is an empty shell to be able to link to from other docs.**
+
+## Features
+
+## Life Cycle Controller Overview
+
+# Theory of Operations
+
+## Hardware Interfaces
+
+### Parameters
+
+Parameter                   | Default (Max)         | Top Earlgrey | Description
+----------------------------|-----------------------|--------------|---------------
+
+### Signals
+
+{{< hwcfg "hw/ip/lc_ctrl/data/lc_ctrl.hjson" >}}
+
+Signal                   | Direction        | Type                        | Description
+-------------------------|------------------|-----------------------------|---------------
+
+
+## Register Table
+
+{{< registers "hw/ip/lc_ctrl/data/lc_ctrl.hjson" >}}
+
diff --git a/hw/ip/lc_ctrl/lc_ctrl_pkg.core b/hw/ip/lc_ctrl/lc_ctrl_pkg.core
new file mode 100644
index 0000000..09d879f
--- /dev/null
+++ b/hw/ip/lc_ctrl/lc_ctrl_pkg.core
@@ -0,0 +1,21 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:ip:lc_ctrl_pkg:0.1"
+description: "LC Controller Package"
+filesets:
+  files_rtl:
+    depend:
+      - lowrisc:tlul:headers
+
+    files:
+      - rtl/lc_ctrl_reg_pkg.sv
+      - rtl/lc_ctrl_pkg.sv
+    file_type: systemVerilogSource
+
+
+targets:
+  default: &default_target
+    filesets:
+      - files_rtl
diff --git a/hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv b/hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv
new file mode 100644
index 0000000..36a78ef
--- /dev/null
+++ b/hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv
@@ -0,0 +1,56 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+
+package lc_ctrl_pkg;
+
+  /////////////////////////////////
+  // General Typedefs and Params //
+  /////////////////////////////////
+
+  parameter int LcValueWidth = 16;
+  parameter int LcTokenWidth = 128;
+  parameter int NumLcStateValues = 12;
+  parameter int LcStateWidth = NumLcStateValues * LcValueWidth;
+  parameter int NumLcCountValues = 32;
+
+  typedef enum logic [LcValueWidth-1:0] {
+    Blk = 16'h0000, // blank
+    Set = 16'hF5FA  // programmed
+  } lc_value_e;
+
+  typedef enum logic [LcStateWidth-1:0] {
+    // Halfword idx   :  11 | 10 |  9 |  8 |  7 |  6 |  5 |  4 |  3 |  2 |  1 |  0
+    LcStRaw           = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk},
+    LcStTestUnlocked0 = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set},
+    LcStTestLocked0   = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set},
+    LcStTestUnlocked1 = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set},
+    LcStTestLocked1   = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set},
+    LcStTestUnlocked2 = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set},
+    LcStTestLocked2   = {Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set, Set},
+    LcStTestUnlocked3 = {Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set, Set, Set},
+    LcStDev           = {Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set, Set, Set, Set},
+    LcStProd          = {Blk, Blk, Blk, Set, Blk, Set, Set, Set, Set, Set, Set, Set},
+    LcStProdEnd       = {Blk, Blk, Set, Blk, Blk, Set, Set, Set, Set, Set, Set, Set},
+    LcStRma           = {Set, Set, Blk, Set, Set, Set, Set, Set, Set, Set, Set, Set},
+    LcStScrap         = {Set, Set, Set, Set, Set, Set, Set, Set, Set, Set, Set, Set}
+  } lc_state_e;
+
+  typedef lc_value_e [NumLcCountValues-1:0] lc_cnt_t;
+
+  ////////////////////////////////
+  // Typedefs for LC Interfaces //
+  ////////////////////////////////
+
+  typedef enum logic [2:0] {
+    On  = 3'b101,
+    Off = 3'b000
+  } lc_tx_e;
+
+  typedef struct packed {
+    lc_tx_e state;
+  } lc_tx_t;
+
+
+endpackage : lc_ctrl_pkg
diff --git a/hw/ip/lc_ctrl/rtl/lc_ctrl_reg_pkg.sv b/hw/ip/lc_ctrl/rtl/lc_ctrl_reg_pkg.sv
new file mode 100644
index 0000000..696b4bd
--- /dev/null
+++ b/hw/ip/lc_ctrl/rtl/lc_ctrl_reg_pkg.sv
@@ -0,0 +1,53 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Register Package auto-generated by `reggen` containing data structure
+
+package lc_ctrl_reg_pkg;
+
+  ////////////////////////////
+  // Typedefs for registers //
+  ////////////////////////////
+
+  typedef struct packed {
+    logic [1:0]  d;
+    logic        de;
+  } lc_ctrl_hw2reg_status_reg_t;
+
+  typedef struct packed {
+    logic [1:0]  d;
+    logic        de;
+  } lc_ctrl_hw2reg_ctrl_reg_t;
+
+
+  ///////////////////////////////////////
+  // Register to internal design logic //
+  ///////////////////////////////////////
+
+  ///////////////////////////////////////
+  // Internal design logic to register //
+  ///////////////////////////////////////
+  typedef struct packed {
+    lc_ctrl_hw2reg_status_reg_t status; // [5:6]
+    lc_ctrl_hw2reg_ctrl_reg_t ctrl; // [5:6]
+  } lc_ctrl_hw2reg_t;
+
+  // Register Address
+  parameter logic [2:0] LC_CTRL_STATUS_OFFSET = 3'h 0;
+  parameter logic [2:0] LC_CTRL_CTRL_OFFSET = 3'h 4;
+
+
+  // Register Index
+  typedef enum int {
+    LC_CTRL_STATUS,
+    LC_CTRL_CTRL
+  } lc_ctrl_id_e;
+
+  // Register width information to check illegal writes
+  parameter logic [3:0] LC_CTRL_PERMIT [2] = '{
+    4'b 0001, // index[0] LC_CTRL_STATUS
+    4'b 0001  // index[1] LC_CTRL_CTRL
+  };
+endpackage
+
diff --git a/hw/ip/lc_ctrl/rtl/lc_ctrl_reg_top.sv b/hw/ip/lc_ctrl/rtl/lc_ctrl_reg_top.sv
new file mode 100644
index 0000000..40dcded
--- /dev/null
+++ b/hw/ip/lc_ctrl/rtl/lc_ctrl_reg_top.sv
@@ -0,0 +1,179 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Register Top module auto-generated by `reggen`
+
+`include "prim_assert.sv"
+
+module lc_ctrl_reg_top (
+  input clk_i,
+  input rst_ni,
+
+  // Below Regster interface can be changed
+  input  tlul_pkg::tl_h2d_t tl_i,
+  output tlul_pkg::tl_d2h_t tl_o,
+  // To HW
+  input  lc_ctrl_reg_pkg::lc_ctrl_hw2reg_t hw2reg, // Read
+
+  // Config
+  input devmode_i // If 1, explicit error return for unmapped register access
+);
+
+  import lc_ctrl_reg_pkg::* ;
+
+  localparam int AW = 3;
+  localparam int DW = 32;
+  localparam int DBW = DW/8;                    // Byte Width
+
+  // register signals
+  logic           reg_we;
+  logic           reg_re;
+  logic [AW-1:0]  reg_addr;
+  logic [DW-1:0]  reg_wdata;
+  logic [DBW-1:0] reg_be;
+  logic [DW-1:0]  reg_rdata;
+  logic           reg_error;
+
+  logic          addrmiss, wr_err;
+
+  logic [DW-1:0] reg_rdata_next;
+
+  tlul_pkg::tl_h2d_t tl_reg_h2d;
+  tlul_pkg::tl_d2h_t tl_reg_d2h;
+
+  assign tl_reg_h2d = tl_i;
+  assign tl_o       = tl_reg_d2h;
+
+  tlul_adapter_reg #(
+    .RegAw(AW),
+    .RegDw(DW)
+  ) u_reg_if (
+    .clk_i,
+    .rst_ni,
+
+    .tl_i (tl_reg_h2d),
+    .tl_o (tl_reg_d2h),
+
+    .we_o    (reg_we),
+    .re_o    (reg_re),
+    .addr_o  (reg_addr),
+    .wdata_o (reg_wdata),
+    .be_o    (reg_be),
+    .rdata_i (reg_rdata),
+    .error_i (reg_error)
+  );
+
+  assign reg_rdata = reg_rdata_next ;
+  assign reg_error = (devmode_i & addrmiss) | wr_err ;
+
+  // Define SW related signals
+  // Format: <reg>_<field>_{wd|we|qs}
+  //        or <reg>_{wd|we|qs} if field == 1 or 0
+  logic [1:0] status_qs;
+  logic [1:0] ctrl_qs;
+
+  // Register instances
+  // R[status]: V(False)
+
+  prim_subreg #(
+    .DW      (2),
+    .SWACCESS("RO"),
+    .RESVAL  (2'h0)
+  ) u_status (
+    .clk_i   (clk_i    ),
+    .rst_ni  (rst_ni  ),
+
+    .we     (1'b0),
+    .wd     ('0  ),
+
+    // from internal hardware
+    .de     (hw2reg.status.de),
+    .d      (hw2reg.status.d ),
+
+    // to internal hardware
+    .qe     (),
+    .q      (),
+
+    // to register interface (read)
+    .qs     (status_qs)
+  );
+
+
+  // R[ctrl]: V(False)
+
+  prim_subreg #(
+    .DW      (2),
+    .SWACCESS("RO"),
+    .RESVAL  (2'h0)
+  ) u_ctrl (
+    .clk_i   (clk_i    ),
+    .rst_ni  (rst_ni  ),
+
+    .we     (1'b0),
+    .wd     ('0  ),
+
+    // from internal hardware
+    .de     (hw2reg.ctrl.de),
+    .d      (hw2reg.ctrl.d ),
+
+    // to internal hardware
+    .qe     (),
+    .q      (),
+
+    // to register interface (read)
+    .qs     (ctrl_qs)
+  );
+
+
+
+
+  logic [1:0] addr_hit;
+  always_comb begin
+    addr_hit = '0;
+    addr_hit[0] = (reg_addr == LC_CTRL_STATUS_OFFSET);
+    addr_hit[1] = (reg_addr == LC_CTRL_CTRL_OFFSET);
+  end
+
+  assign addrmiss = (reg_re || reg_we) ? ~|addr_hit : 1'b0 ;
+
+  // Check sub-word write is permitted
+  always_comb begin
+    wr_err = 1'b0;
+    if (addr_hit[0] && reg_we && (LC_CTRL_PERMIT[0] != (LC_CTRL_PERMIT[0] & reg_be))) wr_err = 1'b1 ;
+    if (addr_hit[1] && reg_we && (LC_CTRL_PERMIT[1] != (LC_CTRL_PERMIT[1] & reg_be))) wr_err = 1'b1 ;
+  end
+
+
+
+  // Read data return
+  always_comb begin
+    reg_rdata_next = '0;
+    unique case (1'b1)
+      addr_hit[0]: begin
+        reg_rdata_next[1:0] = status_qs;
+      end
+
+      addr_hit[1]: begin
+        reg_rdata_next[1:0] = ctrl_qs;
+      end
+
+      default: begin
+        reg_rdata_next = '1;
+      end
+    endcase
+  end
+
+  // Assertions for Register Interface
+  `ASSERT_PULSE(wePulse, reg_we)
+  `ASSERT_PULSE(rePulse, reg_re)
+
+  `ASSERT(reAfterRv, $rose(reg_re || reg_we) |=> tl_o.d_valid)
+
+  `ASSERT(en2addrHit, (reg_we || reg_re) |-> $onehot0(addr_hit))
+
+  // this is formulated as an assumption such that the FPV testbenches do disprove this
+  // property by mistake
+  `ASSUME(reqParity, tl_reg_h2d.a_valid |-> tl_reg_h2d.a_user.parity_en == 1'b0)
+
+endmodule
diff --git a/hw/ip/otp_ctrl/otp_ctrl_pkg.core b/hw/ip/otp_ctrl/otp_ctrl_pkg.core
index 02da53b..c799c9e 100644
--- a/hw/ip/otp_ctrl/otp_ctrl_pkg.core
+++ b/hw/ip/otp_ctrl/otp_ctrl_pkg.core
@@ -8,6 +8,7 @@
   files_rtl:
     depend:
       - lowrisc:tlul:headers
+      - lowrisc:ip:lc_ctrl_pkg
 
     files:
       - rtl/otp_ctrl_reg_pkg.sv
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
index c085117..ec8f431 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
@@ -33,7 +33,7 @@
   input                                              rst_ni,
   // Macro-specific power sequencing signals to/from AST.
   output otp_ast_req_t                               otp_ast_pwr_seq_o,
-  input  otp_ast_rsp_t                               otp_ast_pwr_seq_i,
+  input  otp_ast_rsp_t                               otp_ast_pwr_seq_h_i,
   // Bus Interface (device)
   input  tlul_pkg::tl_h2d_t                          tl_i,
   output tlul_pkg::tl_d2h_t                          tl_o,
@@ -56,9 +56,9 @@
   input  lc_otp_token_req_t                          lc_otp_token_req_i,
   output lc_otp_token_rsp_t                          lc_otp_token_rsp_o,
   // Lifecycle broadcast inputs
-  input  lc_tx_t                                     lc_escalate_en_i,
-  input  lc_tx_t                                     lc_provision_en_i,
-  input  lc_tx_t                                     lc_test_en_i,
+  input  lc_ctrl_pkg::lc_tx_t                        lc_escalate_en_i,
+  input  lc_ctrl_pkg::lc_tx_t                        lc_provision_en_i,
+  input  lc_ctrl_pkg::lc_tx_t                        lc_dft_en_i,
   // OTP broadcast outputs
   output otp_lc_data_t                               otp_lc_data_o,
   output otp_keymgr_key_t                            otp_keymgr_key_o,
@@ -134,8 +134,8 @@
     // Propagate CSR read enables down to the SW_CFG partitions.
     if (!reg2hw.creator_sw_cfg_read_lock) part_access_csrs[CreatorSwCfgIdx].read_lock = Locked;
     if (!reg2hw.owner_sw_cfg_read_lock) part_access_csrs[OwnerSwCfgIdx].read_lock = Locked;
-    // The SECRET2 partition can only be accessed when provisioning is enabled.
-    if (lc_provision_en_i != On) part_access_csrs[Secret2Idx].read_lock = Locked;
+    // The SECRET2 partition can only be accessed (write&read) when provisioning is enabled.
+    if (lc_provision_en_i != lc_ctrl_pkg::On) part_access_csrs[Secret2Idx] = {2{Locked}};
     // Permanently lock DAI write access to the life cycle partition
     part_access_csrs[LifeCycleIdx].write_lock = Locked;
   end
@@ -385,8 +385,10 @@
   tlul_pkg::tl_d2h_t     tl_win_d2h_gated;
 
   // Life cycle qualification of TL-UL test interface.
-  assign tl_win_h2d_gated              = (lc_test_en_i == On) ? tl_win_h2d[$high(tl_win_h2d)] : '0;
-  assign tl_win_d2h[$high(tl_win_h2d)] = (lc_test_en_i == On) ? tl_win_d2h_gated : '0;
+  assign tl_win_h2d_gated              = (lc_dft_en_i == lc_ctrl_pkg::On) ?
+                                         tl_win_h2d[$high(tl_win_h2d)] : '0;
+  assign tl_win_d2h[$high(tl_win_h2d)] = (lc_dft_en_i == lc_ctrl_pkg::On) ?
+                                         tl_win_d2h_gated : '0;
 
   prim_otp #(
     .Width(OtpWidth),
@@ -395,8 +397,8 @@
     .clk_i,
     .rst_ni,
     // Power sequencing signals to/from AST
-    .pwr_seq_o   ( otp_ast_pwr_seq_o.pwr_seq   ),
-    .pwr_seq_h_i ( otp_ast_pwr_seq_i.pwr_seq_h ),
+    .pwr_seq_o   ( otp_ast_pwr_seq_o.pwr_seq     ),
+    .pwr_seq_h_i ( otp_ast_pwr_seq_h_i.pwr_seq_h ),
     // Test interface
     .test_tl_i   ( tl_win_h2d_gated     ),
     .test_tl_o   ( tl_win_d2h_gated     ),
@@ -455,6 +457,8 @@
   // partition must yield its lock by deasserting the request signal for the arbiter to proceed.
   // Since this scheme does not have built-in preemtion, it must be ensured that the agents
   // eventually release their locks for this to be fair.
+  //
+  // See also https://docs.opentitan.org/hw/ip/otp_ctrl/doc/index.html#block-diagram for details.
   typedef struct packed {
     otp_scrmbl_cmd_e             cmd;
     logic [ConstSelWidth-1:0]    sel;
@@ -575,24 +579,24 @@
   ) u_otp_ctrl_lci (
     .clk_i,
     .rst_ni,
-    .lci_en_i        ( pwr_otp_rsp_o.otp_done            ),
-    .escalate_en_i   ( lc_escalate_en_i                  ),
-    .error_o         ( part_error[LciIdx]                ),
-    .lci_idle_o      ( lci_idle                          ),
-    .lc_req_i        ( lc_otp_program_req_i.req          ),
-    .lc_state_diff_i ( lc_otp_program_req_i.state_diff   ),
-    .lc_count_diff_i ( lc_otp_program_req_i.count_diff   ),
-    .lc_ack_o        ( lc_otp_program_rsp_o.ack          ),
-    .lc_err_o        ( lc_otp_program_rsp_o.err          ),
-    .otp_req_o       ( part_otp_arb_req[LciIdx]          ),
-    .otp_cmd_o       ( part_otp_arb_bundle[LciIdx].cmd   ),
-    .otp_size_o      ( part_otp_arb_bundle[LciIdx].size  ),
-    .otp_wdata_o     ( part_otp_arb_bundle[LciIdx].wdata ),
-    .otp_addr_o      ( part_otp_arb_bundle[LciIdx].addr  ),
-    .otp_gnt_i       ( part_otp_arb_gnt[LciIdx]          ),
-    .otp_rvalid_i    ( part_otp_rvalid[LciIdx]           ),
-    .otp_rdata_i     ( part_otp_rdata                    ),
-    .otp_err_i       ( part_otp_err                      )
+    .lci_en_i         ( pwr_otp_rsp_o.otp_done            ),
+    .escalate_en_i    ( lc_escalate_en_i                  ),
+    .error_o          ( part_error[LciIdx]                ),
+    .lci_idle_o       ( lci_idle                          ),
+    .lc_req_i         ( lc_otp_program_req_i.req          ),
+    .lc_state_delta_i ( lc_otp_program_req_i.state_delta  ),
+    .lc_count_delta_i ( lc_otp_program_req_i.count_delta  ),
+    .lc_ack_o         ( lc_otp_program_rsp_o.ack          ),
+    .lc_err_o         ( lc_otp_program_rsp_o.err          ),
+    .otp_req_o        ( part_otp_arb_req[LciIdx]          ),
+    .otp_cmd_o        ( part_otp_arb_bundle[LciIdx].cmd   ),
+    .otp_size_o       ( part_otp_arb_bundle[LciIdx].size  ),
+    .otp_wdata_o      ( part_otp_arb_bundle[LciIdx].wdata ),
+    .otp_addr_o       ( part_otp_arb_bundle[LciIdx].addr  ),
+    .otp_gnt_i        ( part_otp_arb_gnt[LciIdx]          ),
+    .otp_rvalid_i     ( part_otp_rvalid[LciIdx]           ),
+    .otp_rdata_i      ( part_otp_rdata                    ),
+    .otp_err_i        ( part_otp_err                      )
   );
 
   // Tie off unused connections.
@@ -780,24 +784,24 @@
                                                2*FlashKeySeedWidth/8 +
                                                SramKeySeedWidth/8];
   // Test unlock and exit tokens
-  assign otp_lc_data_o.test_token_valid = part_init_done[Secret0Idx];
   assign {otp_lc_data_o.test_exit_token,
           otp_lc_data_o.test_unlock_token} = part_buf_data[PartInfo[Secret0Idx].offset +:
-                                                           2*LcTokenWidth/8];
+                                                           2*lc_ctrl_pkg::LcTokenWidth/8];
   // RMA token
-  assign otp_lc_data_o.rma_token_valid  = part_init_done[Secret2Idx];
-  assign otp_lc_data_o.rma_token        = part_buf_data[PartInfo[Secret2Idx].offset +:
-                                                        LcTokenWidth/8];
+  assign otp_lc_data_o.rma_token      = part_buf_data[PartInfo[Secret2Idx].offset +:
+                                                        lc_ctrl_pkg::LcTokenWidth/8];
   // The device is personalized if the root key has been provisioned and locked
-  assign otp_lc_data_o.id_state_valid = part_init_done[Secret2Idx];
-  assign otp_lc_data_o.id_state       = (part_digest[Secret2Idx] != '0) ? Set : Blk;
+  assign otp_lc_data_o.id_state       = (part_digest[Secret2Idx] != '0) ? lc_ctrl_pkg::Set :
+                                                                          lc_ctrl_pkg::Blk;
 
   // Lifecycle state
-  assign otp_lc_data_o.state_valid    = part_init_done[LifeCycleIdx];
   assign {otp_lc_data_o.count,
           otp_lc_data_o.state}        = part_buf_data[PartInfo[LifeCycleIdx].offset +:
                                                       PartInfo[LifeCycleIdx].size];
 
+  // Assert life cycle state valid signal only when all partitions have initialized.
+  assign otp_lc_data_o.valid    = &part_init_done;
+
   // Not all bits of part_buf_data are used here.
   logic unused_buf_data;
   assign unused_buf_data = ^part_buf_data;
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_dai.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_dai.sv
index daf7cd9..96497c7 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_dai.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_dai.sv
@@ -21,7 +21,7 @@
   input  [NumPart-1:0]                   part_init_done_i,
   // Escalation input. This moves the FSM into a terminal state and locks down
   // the DAI.
-  input lc_tx_t                          escalate_en_i,
+  input lc_ctrl_pkg::lc_tx_t             escalate_en_i,
   // Output error state of DAI, to be consumed by OTP error/alert logic.
   // Note that most errors are not recoverable and move the DAI FSM into
   // a terminal error state.
@@ -546,11 +546,10 @@
       ///////////////////////////////////////////////////////////////////
     endcase // state_q
 
-    if (state_q != ErrorSt) begin
-      // Unconditionally jump into the terminal error state in case of
-      // escalation, and lock access to the DAI down.
-      if (escalate_en_i != Off) begin
-        state_d = ErrorSt;
+    // Unconditionally jump into the terminal error state in case of escalation.
+    if (escalate_en_i != lc_ctrl_pkg::Off) begin
+      state_d = ErrorSt;
+      if (state_q != ErrorSt) begin
         error_d = EscErr;
       end
     end
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_kdi.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_kdi.sv
index 8e117fb..bbc05cb 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_kdi.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_kdi.sv
@@ -21,7 +21,7 @@
   // been initialized.
   input                                              kdi_en_i,
   // Escalation input. This moves the FSM into a terminal state.
-  input  lc_tx_t                                     escalate_en_i,
+  input  lc_ctrl_pkg::lc_tx_t                        escalate_en_i,
   // FSM is in error state
   output logic                                       fsm_err_o,
   // Key seed inputs from OTP
@@ -455,7 +455,7 @@
     endcase // state_q
 
     // Unconditionally jump into the terminal error state in case of escalation.
-    if (escalate_en_i != Off) begin
+    if (escalate_en_i != lc_ctrl_pkg::Off) begin
       state_d = ErrorSt;
     end
   end
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
index 5e2c73c..741b81f 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_lci.sv
@@ -19,14 +19,14 @@
   input                                    lci_en_i,
   // Escalation input. This moves the FSM into a terminal state and locks down
   // the partition.
-  input  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 differential value with respect to the
+  // the LC controller signals the incremental value with respect to the
   // current state. The OTP controller then only programs the non-zero
-  // state differential.
+  // state delta.
   input                                    lc_req_i,
-  input  lc_state_e                        lc_state_diff_i,
-  input  lc_value_e [NumLcCountValues-1:0] lc_count_diff_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,
   // Output error state of partition, to be consumed by OTP error/alert logic.
@@ -55,6 +55,9 @@
   localparam int NumLcOtpWords = Info.size >> OtpAddrShift;
   localparam int CntWidth = vbits(NumLcOtpWords);
 
+  // This is required, since each native OTP word can only be programmed once.
+  `ASSERT_INIT(LcValueMustBeWiderThanNativeOtpWidth_A, lc_ctrl_pkg::LcValueWidth >= OtpWidth)
+
   ////////////////////
   // Controller FSM //
   ////////////////////
@@ -88,7 +91,7 @@
   logic cnt_clr, cnt_en;
   logic [CntWidth-1:0] cnt_d, cnt_q;
   otp_err_e error_d, error_q;
-  logic diff_data_is_set;
+  logic delta_data_is_set;
   state_e state_d, state_q;
 
   // Output LCI errors
@@ -135,7 +138,7 @@
       // Loop through the lifecycle sate and burn in the words that are set.
       WriteSt: begin
         // Check whether the OTP word is nonzero.
-        if (diff_data_is_set) begin
+        if (delta_data_is_set) begin
           otp_req_o = 1'b1;
           otp_cmd_o = OtpWrite;
           if (otp_gnt_i) begin
@@ -195,10 +198,10 @@
       ///////////////////////////////////////////////////////////////////
     endcase // state_q
 
-    if (state_q != ErrorSt) begin
-      // Unconditionally jump into the terminal error state in case of escalation.
-      if (escalate_en_i != Off) begin
-        state_d = ErrorSt;
+    // Unconditionally jump into the terminal error state in case of escalation.
+    if (escalate_en_i != lc_ctrl_pkg::Off) begin
+      state_d = ErrorSt;
+      if (state_q != ErrorSt) begin
         error_d = EscErr;
       end
     end
@@ -220,10 +223,10 @@
   // Always transfer 16bit blocks.
   assign otp_size_o = '0;
 
-  logic [NumLcOtpWords-1:0][OtpWidth-1:0] diff_data;
-  assign diff_data        = {lc_count_diff_i, lc_state_diff_i};
-  assign otp_wdata_o      = OtpIfWidth'(diff_data[cnt_q]);
-  assign diff_data_is_set = (diff_data[cnt_q] != Blk);
+  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 unused_rdata;
   assign unused_rdata = ^otp_rdata_i;
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 bef05c3..ab4ec6c 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv
@@ -56,7 +56,7 @@
   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
-  input  lc_tx_t                   escalate_en_i,      // escalation input, moves FSM into ErrorSt
+  input  lc_ctrl_pkg::lc_tx_t      escalate_en_i,      // escalation input, moves FSM into ErrorSt
   output logic                     chk_timeout_o,      // a check has timed out
   output logic                     fsm_err_o           // the FSM has reached an invalid state
 );
@@ -274,11 +274,9 @@
       ///////////////////////////////////////////////////////////////////
     endcase // state_q
 
-    if (state_q != ErrorSt) begin
-      // Unconditionally jump into the terminal error state in case of escalation.
-      if (escalate_en_i != Off) begin
-        state_d = ErrorSt;
-      end
+    // Unconditionally jump into the terminal error state in case of escalation.
+    if (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 c11bc28..b1f77c6 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv
@@ -27,7 +27,7 @@
   output logic                        cnsty_chk_ack_o,
   // Escalation input. This moves the FSM into a terminal state and locks down
   // the partition.
-  input  lc_tx_t                      escalate_en_i,
+  input  lc_ctrl_pkg::lc_tx_t         escalate_en_i,
   // 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.
@@ -504,15 +504,18 @@
       ///////////////////////////////////////////////////////////////////
     endcase // state_q
 
-    if (state_q != ErrorSt) begin
-      // Unconditionally jump into the terminal error state in case of
-      // a parity error or escalation, and lock access to the partition down.
-      if (parity_err) begin
-        state_d = ErrorSt;
+
+    // Unconditionally jump into the terminal error state in case of
+    // a parity error or escalation, and lock access to the partition down.
+    if (parity_err) begin
+      state_d = ErrorSt;
+      if (state_q != ErrorSt) begin
         error_d = ParityErr;
       end
-      if (escalate_en_i != Off) begin
-        state_d = ErrorSt;
+    end
+    if (escalate_en_i != lc_ctrl_pkg::Off) begin
+      state_d = ErrorSt;
+      if (state_q != ErrorSt) begin
         error_d = EscErr;
       end
     end
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_unbuf.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_unbuf.sv
index 3661a1f..57a6088 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_unbuf.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_unbuf.sv
@@ -21,7 +21,7 @@
   output logic                        init_done_o,
   // Escalation input. This moves the FSM into a terminal state and locks down
   // the partition.
-  input  lc_tx_t                      escalate_en_i,
+  input  lc_ctrl_pkg::lc_tx_t         escalate_en_i,
   // 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.
@@ -251,15 +251,17 @@
       ///////////////////////////////////////////////////////////////////
     endcase // state_q
 
-    if (state_q != ErrorSt) begin
-      // Unconditionally jump into the terminal error state in case of
-      // a parity error or escalation, and lock access to the partition down.
-      if (parity_err) begin
-        state_d = ErrorSt;
+    // Unconditionally jump into the terminal error state in case of
+    // a parity error or escalation, and lock access to the partition down.
+    if (parity_err) begin
+      state_d = ErrorSt;
+      if (state_q != ErrorSt) begin
         error_d = ParityErr;
       end
-      if (escalate_en_i != Off) begin
-        state_d = ErrorSt;
+    end
+    if (escalate_en_i != lc_ctrl_pkg::Off) begin
+      state_d = ErrorSt;
+      if (state_q != ErrorSt) begin
         error_d = EscErr;
       end
     end
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
index fd277be..4fe6be9 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
@@ -231,51 +231,20 @@
   // Typedefs for LC Interface //
   ///////////////////////////////
 
-  // TODO: move all these definitions to the lc_ctrl_pkg
-  parameter int LcValueWidth = OtpWidth;
-  parameter int LcTokenWidth = 128;
-  parameter int NumLcStateValues = 12;
-  parameter int LcStateWidth = NumLcStateValues * LcValueWidth;
-  parameter int NumLcCountValues = 32;
-
-  typedef enum logic [LcValueWidth-1:0] {
-    Blk = 16'h0000, // blank
-    Set = 16'hF5FA  // programmed
-  } lc_value_e;
-
-  typedef enum logic [LcStateWidth-1:0] {
-    LcStRaw           = {NumLcStateValues{Blk}},
-    LcStTestUnlocked0 = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set},
-    LcStTestLocked0   = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set},
-    LcStTestUnlocked1 = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set},
-    LcStTestLocked1   = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set},
-    LcStTestUnlocked2 = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set},
-    LcStTestLocked2   = {Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set, Set},
-    LcStTestUnlocked3 = {Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set, Set, Set},
-    LcStDev           = {Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set, Set, Set, Set},
-    LcStProd          = {Blk, Blk, Blk, Set, Blk, Set, Set, Set, Set, Set, Set, Set},
-    LcStProdEnd       = {Blk, Blk, Set, Blk, Blk, Set, Set, Set, Set, Set, Set, Set},
-    LcStRma           = {Set, Set, Blk, Set, Set, Set, Set, Set, Set, Set, Set, Set},
-    LcStScrap         = {NumLcStateValues{Set}}
-  } lc_state_e;
-
   typedef struct packed {
-    logic                             state_valid;
-    logic                             test_token_valid;
-    logic                             rma_token_valid;
-    logic                             id_state_valid;
-    lc_state_e                        state;
-    lc_value_e [NumLcCountValues-1:0] count;
-    logic [LcTokenWidth-1:0]          test_unlock_token;
-    logic [LcTokenWidth-1:0]          test_exit_token;
-    logic [LcTokenWidth-1:0]          rma_token;
-    lc_value_e                        id_state;
+    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;
   } otp_lc_data_t;
 
   typedef struct packed {
-    logic      req;
-    lc_state_e state_diff;
-    lc_value_e [NumLcCountValues-1:0] count_diff;
+    logic req;
+    lc_ctrl_pkg::lc_state_e state_delta;
+    lc_ctrl_pkg::lc_cnt_t   count_delta;
   } lc_otp_program_req_t;
 
   typedef struct packed {
@@ -285,33 +254,15 @@
 
   // RAW unlock token hashing request.
   typedef struct packed {
-    logic                    req;
-    logic [LcTokenWidth-1:0] token_input;
+    logic req;
+    logic [lc_ctrl_pkg::LcTokenWidth-1:0] token_input;
   } lc_otp_token_req_t;
 
   typedef struct packed {
-<<<<<<< HEAD
-    logic  ack;
-    logic [LcTokenWidth-1:0] hashed_token;
-=======
     logic ack;
     logic [lc_ctrl_pkg::LcTokenWidth-1:0] hashed_token;
->>>>>>> cd99a29a (fix)
   } lc_otp_token_rsp_t;
 
-
-  // TODO: move this to the LC ctrl package
-  typedef enum logic [2:0] {
-    On  = 3'b101,
-    Off = 3'b000
-  } lc_tx_e;
-
-  // TODO: move this to the LC ctrl package
-  typedef struct packed {
-    lc_tx_e state;
-  } lc_tx_t;
-
-
   ////////////////////////////////
   // Typedefs for Key Broadcast //
   ////////////////////////////////
@@ -366,7 +317,6 @@
     logic [OtbnNonceWidth-1:0] nonce;
   } otbn_otp_key_rsp_t;
 
-
   ////////////////////////////////
   // Power/Reset Ctrl Interface //
   ////////////////////////////////
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv
index 509bfbf..5b48a5e 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv
@@ -79,7 +79,7 @@
   output logic [ScrmblBlockWidth-1:0] data_o,
   output logic                        valid_o,
   // escalation input and FSM error indication
-  input  lc_tx_t                      escalate_en_i,
+  input  lc_ctrl_pkg::lc_tx_t         escalate_en_i,
   output logic                        fsm_err_o
 );
 
@@ -355,11 +355,9 @@
       ///////////////////////////////////////////////////////////////////
     endcase // state_q
 
-    if (state_q != ErrorSt) begin
-      // Unconditionally jump into the terminal error state in case of escalation.
-      if (escalate_en_i != Off) begin
-        state_d = ErrorSt;
-      end
+    // Unconditionally jump into the terminal error state in case of escalation.
+    if (escalate_en_i != lc_ctrl_pkg::Off) begin
+      state_d = ErrorSt;
     end
   end