[otp_ctrl] Add KDI stub, define errors for aux FSMs

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 93777d0..d3d8fbb 100644
--- a/hw/ip/otp_ctrl/data/otp_ctrl.hjson
+++ b/hw/ip/otp_ctrl/data/otp_ctrl.hjson
@@ -256,13 +256,37 @@
         }
         { 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."
+          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: "LFSR_FSM_ERROR"
+          desc: '''
+                Set to 1 if the LFSR timer FSM has reached an invalid state.
+                This raises an otp_check_failed alert and is an unrecoverable error condition.
+                '''
+        }
+        { bits: "11"
+          name: "SCRAMBLING_FSM_ERROR"
+          desc: '''
+                Set to 1 if the scrambling datapath FSM has reached an invalid state.
+                This raises an otp_check_failed alert and is an unrecoverable error condition.
+                '''
+        }
+        { bits: "12"
+          name: "KEY_DERIV_FSM_ERROR"
+          desc: '''
+                Set to 1 if the key derivation FSM has reached an invalid state.
+                This raises an otp_check_failed alert and is an unrecoverable error condition.
+                '''
+        }
+        { bits: "13"
           name: "DAI_IDLE"
           desc: "Set to 1 if the DAI is idle and ready to accept commands."
         }
-        { bits: "11"
+        { bits: "14"
           name: "CHECK_PENDING"
           desc: "Set to 1 if an integrity or consistency check triggered by the LFSR timer or via !!CHECK_TRIGGER is pending."
         }
@@ -530,7 +554,7 @@
             Register write enable for !!CHECK_TRIGGER.
             ''',
       swaccess: "rw1c",
-      hwaccess: "hro",
+      hwaccess: "none",
       fields: [
         { bits:   "0",
           desc: '''
@@ -571,7 +595,7 @@
             Register write enable for !!INTEGRITY_CHECK_PERIOD and !!CONSISTENCY_CHECK_PERIOD.
             ''',
       swaccess: "rw1c",
-      hwaccess: "hro",
+      hwaccess: "none",
       fields: [
         { bits:   "0",
           desc: '''
diff --git a/hw/ip/otp_ctrl/otp_ctrl.core b/hw/ip/otp_ctrl/otp_ctrl.core
index 716ac7e..ab26115 100644
--- a/hw/ip/otp_ctrl/otp_ctrl.core
+++ b/hw/ip/otp_ctrl/otp_ctrl.core
@@ -18,11 +18,12 @@
       - rtl/otp_ctrl_reg_top.sv
       - rtl/otp_ctrl_parity_reg.sv
       - rtl/otp_ctrl_scrmbl.sv
-      - rtl/otp_ctrl_dai.sv
-      - rtl/otp_ctrl_lci.sv
+      - rtl/otp_ctrl_lfsr_timer.sv
       - rtl/otp_ctrl_part_unbuf.sv
       - rtl/otp_ctrl_part_buf.sv
-      - rtl/otp_ctrl_lfsr_timer.sv
+      - rtl/otp_ctrl_dai.sv
+      - rtl/otp_ctrl_kdi.sv
+      - rtl/otp_ctrl_lci.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 3a198b4..4d86609 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl.sv
@@ -11,7 +11,12 @@
   import otp_ctrl_pkg::*;
   import otp_ctrl_reg_pkg::*;
 #(
-  parameter logic [NumAlerts-1:0]  AlertAsyncOn = {NumAlerts{1'b1}},
+  // TODO: set this when integrating the module into the top-level.
+  // There is no limit on the number of SRAM key request generation slots,
+  // since each requested key is ephemeral.
+  parameter int                          NumSramKeyReqSlots = 2,
+  // Enable asynchronous transitions on alerts.
+  parameter logic [NumAlerts-1:0]        AlertAsyncOn = {NumAlerts{1'b1}},
   // TODO: These constants have to be replaced by the silicon creator before taping out.
   parameter logic [TimerWidth-1:0]       LfsrSeed     = TimerWidth'(1'b1),
   parameter logic [TimerWidth-1:0][31:0] LfsrPerm     = {
@@ -22,40 +27,49 @@
     32'd16, 32'd14, 32'd23, 32'd07, 32'd30, 32'd09, 32'd18, 32'd36
   }
 ) (
-  input                             clk_i,
-  input                             rst_ni,
+  input                                              clk_i,
+  input                                              rst_ni,
   // TODO: signals to AST
   // Bus Interface (device)
-  input  tlul_pkg::tl_h2d_t         tl_i,
-  output tlul_pkg::tl_d2h_t         tl_o,
+  input  tlul_pkg::tl_h2d_t                          tl_i,
+  output tlul_pkg::tl_d2h_t                          tl_o,
   // Interrupt Requests
-  output logic                      intr_otp_operation_done_o,
-  output logic                      intr_otp_error_o,
+  output logic                                       intr_otp_operation_done_o,
+  output logic                                       intr_otp_error_o,
   // 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,
+  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: EDN interface for entropy updates
+  input  edn_otp_up_t                                edn_otp_up_i,
+  // TODO: EDN interface for requesting entropy
+  output otp_edn_req_t                               otp_edn_req_o,
+  input  otp_edn_rsp_t                               otp_edn_rsp_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,
-  output otp_pwr_state_t            otp_pwr_state_o,
+  input  pwr_otp_init_req_t                          pwr_otp_init_req_i,
+  output pwr_otp_init_rsp_t                          pwr_otp_init_rsp_o,
+  output otp_pwr_state_t                             otp_pwr_state_o,
   // Lifecycle transition command interface
-  input  lc_otp_program_req_t       lc_otp_program_req_i,
-  output lc_otp_program_rsp_t       lc_otp_program_rsp_o,
+  input  lc_otp_program_req_t                        lc_otp_program_req_i,
+  output lc_otp_program_rsp_t                        lc_otp_program_rsp_o,
   // Lifecycle hashing interface for raw unlock
-  input  lc_otp_token_req_t         lc_otp_token_req_i,
-  output lc_otp_token_rsp_t         lc_otp_token_rsp_o,
+  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_tx_t                                     lc_escalate_en_i,
+  input  lc_tx_t                                     lc_provision_en_i,
+  input  lc_tx_t                                     lc_test_en_i,
   // OTP broadcast outputs
-  output otp_lc_data_t              otp_lc_data_o,
-  output keymgr_key_t               otp_keymgr_key_o,
-  output flash_key_t                otp_flash_key_o,
+  output otp_lc_data_t                               otp_lc_data_o,
+  output otp_keymgr_key_t                            otp_keymgr_key_o,
+  // Scrambling key requests
+  input  flash_otp_key_req_t                         flash_otp_key_req_i,
+  output flash_otp_key_rsp_t                         flash_otp_key_rsp_o,
+  input  sram_otp_key_req_t [NumSramKeyReqSlots-1:0] sram_otp_key_req_i,
+  output sram_otp_key_rsp_t [NumSramKeyReqSlots-1:0] sram_otp_key_rsp_o,
+  input  otbn_otp_key_req_t                          otbn_otp_key_req_i,
+  output otbn_otp_key_rsp_t                          otbn_otp_key_rsp_o,
   // Hardware config bits
-  output logic [NumHwCfgBits-1:0]   hw_cfg_o
+  output logic [NumHwCfgBits-1:0]                    hw_cfg_o
 );
 
   import prim_util_pkg::vbits;
@@ -169,6 +183,7 @@
   logic otp_operation_done, otp_error;
   logic otp_fatal_error, otp_check_failed;
   logic chk_pending, chk_timeout;
+  logic lfsr_fsm_err, key_deriv_fsm_err, scrmbl_fsm_err;
   always_comb begin : p_errors_alerts
     hw2reg.err_code = part_error;
     otp_fatal_error = 1'b0;
@@ -190,13 +205,20 @@
       otp_check_failed |= part_error[k] inside {ParityErr,
                                                 IntegErr,
                                                 CnstyErr,
-                                                FsmErr} | chk_timeout;
+                                                FsmErr} |
+                          chk_timeout       |
+                          lfsr_fsm_err      |
+                          scrmbl_fsm_err    |
+                          key_deriv_fsm_err;
     end
   end
 
   // Assign these to the status register.
   assign hw2reg.status = {chk_pending,
                           dai_idle,
+                          key_deriv_fsm_err,
+                          scrmbl_fsm_err,
+                          lfsr_fsm_err,
                           chk_timeout,
                           part_errors_reduced};
   // If we got an error, we trigger an interrupt.
@@ -276,9 +298,9 @@
   ) u_otp_ctrl_lfsr_timer (
     .clk_i,
     .rst_ni,
-    .entropy_en_i       ( entropy_i.en            ),
+    .entropy_en_i       ( edn_otp_up_i.en          ),
     // Lower entropy bits are used for reseeding secure erase LFSRs
-    .entropy_i          ( entropy_i.data[31:28]   ),
+    .entropy_i          ( edn_otp_up_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          ),
@@ -291,7 +313,9 @@
     .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             )
+    .escalate_en_i      ( lc_escalate_en_i        ),
+    .chk_timeout_o      ( chk_timeout             ),
+    .fsm_err_o          ( lfsr_fsm_err            )
   );
 
   ///////////////////////////////
@@ -305,16 +329,16 @@
     logic [OtpAddrWidth-1:0]     addr; // Halfword address.
   } otp_bundle_t;
 
-  logic [NumPart+1:0]          part_otp_arb_req, part_otp_arb_gnt;
-  otp_bundle_t                 part_otp_arb_bundle [NumPart+2];
+  logic [NumAgents-1:0]        part_otp_arb_req, part_otp_arb_gnt;
+  otp_bundle_t                 part_otp_arb_bundle [NumAgents];
   logic                        otp_arb_valid, otp_arb_ready;
-  logic [vbits(NumPart+2)-1:0] otp_arb_idx;
+  logic [vbits(NumAgents)-1:0] otp_arb_idx;
   otp_bundle_t                 otp_arb_bundle;
 
   // The OTP interface is arbitrated on a per-cycle basis, meaning that back-to-back
   // transactions can be completely independent.
   prim_arbiter_tree #(
-    .N(NumPart+2),
+    .N(NumAgents),
     .DW($bits(otp_bundle_t))
   ) u_otp_arb (
     .clk_i,
@@ -363,13 +387,13 @@
   );
 
   logic otp_fifo_valid;
-  logic [NumPartWidth-1:0] otp_part_idx;
-  logic [NumPart+1:0] part_otp_rvalid;
+  logic [vbits(NumAgents)-1:0] otp_part_idx;
+  logic [NumAgents-1:0] part_otp_rvalid;
 
   // We can have up to two OTP commands in flight, hence we size this to be 2 deep.
   // The partitions can unconditionally sink requested data.
   prim_fifo_sync #(
-    .Width(NumPartWidth),
+    .Width(vbits(NumAgents)),
     .Depth(2)
   ) u_otp_rsp_fifo (
     .clk_i,
@@ -406,21 +430,21 @@
   // eventually release their locks for this to be fair.
   typedef struct packed {
     otp_scrmbl_cmd_e             cmd;
-    logic  [ConstSelWidth-1:0]   sel;
+    logic [ConstSelWidth-1:0]    sel;
     logic [ScrmblBlockWidth-1:0] data;
     logic                        valid;
   } scrmbl_bundle_t;
 
-  logic [NumPart:0]            part_scrmbl_mtx_req, part_scrmbl_mtx_gnt;
-  scrmbl_bundle_t              part_scrmbl_req_bundle [NumPart+1];
+  logic [NumAgents-1:0]        part_scrmbl_mtx_req, part_scrmbl_mtx_gnt;
+  scrmbl_bundle_t              part_scrmbl_req_bundle [NumAgents];
   scrmbl_bundle_t              scrmbl_req_bundle;
-  logic [vbits(NumPart+1)-1:0] scrmbl_mtx_idx;
+  logic [vbits(NumAgents)-1:0] scrmbl_mtx_idx;
   logic                        scrmbl_mtx_valid;
 
   // Note that arbiter decisions do not change when backpressured.
   // Hence, the idx_o signal is guaranteed to remain stable until ack'ed.
   prim_arbiter_tree #(
-    .N(NumPart+1),
+    .N(NumAgents),
     .DW($bits(scrmbl_bundle_t))
   ) u_scrmbl_mtx (
     .clk_i,
@@ -445,18 +469,20 @@
 
   logic [ScrmblBlockWidth-1:0] part_scrmbl_rsp_data;
   logic scrmbl_arb_req_ready, scrmbl_arb_rsp_valid;
-  logic [NumPart:0] part_scrmbl_req_ready, part_scrmbl_rsp_valid;
+  logic [NumAgents-1:0] part_scrmbl_req_ready, part_scrmbl_rsp_valid;
 
   otp_ctrl_scrmbl u_scrmbl (
     .clk_i,
     .rst_ni,
-    .cmd_i   ( scrmbl_req_bundle.cmd   ),
-    .sel_i   ( scrmbl_req_bundle.sel   ),
-    .data_i  ( scrmbl_req_bundle.data  ),
-    .valid_i ( scrmbl_req_bundle.valid ),
-    .ready_o ( scrmbl_arb_req_ready    ),
-    .data_o  ( part_scrmbl_rsp_data    ),
-    .valid_o ( scrmbl_arb_rsp_valid    )
+    .cmd_i         ( scrmbl_req_bundle.cmd   ),
+    .sel_i         ( scrmbl_req_bundle.sel   ),
+    .data_i        ( scrmbl_req_bundle.data  ),
+    .valid_i       ( scrmbl_req_bundle.valid ),
+    .ready_o       ( scrmbl_arb_req_ready    ),
+    .data_o        ( part_scrmbl_rsp_data    ),
+    .valid_o       ( scrmbl_arb_rsp_valid    ),
+    .escalate_en_i ( lc_escalate_en_i        ),
+    .fsm_err_o     ( scrmbl_fsm_err          )
   );
 
   // steer back responses
@@ -541,19 +567,67 @@
     .otp_err_i       ( part_otp_err                      )
   );
 
-  ///////////////////////////////
-  // Scrambling Key Derivation //
-  ///////////////////////////////
+  // Tie off unused connections.
+  assign part_scrmbl_mtx_req[LciIdx]    = '0;
+  assign part_scrmbl_req_bundle[LciIdx] = '0;
 
-  // TODO: scrambling key derivation datapath
-  logic scrambling_keys_valid;
-  logic [SramKeySeedWidth-1:0] sram_data_key;
-  logic [FlashKeySeedWidth-1:0] flash_data_key, flash_addr_key;
+  // This stops lint from complaining about unused signals.
+  logic unused_part_scrmbl_req_ready, unused_part_scrmbl_rsp_valid, unused_part_scrmbl_mtx_gnt;
+  assign unused_part_scrmbl_mtx_gnt   = part_scrmbl_mtx_gnt[LciIdx];
+  assign unused_part_scrmbl_req_ready = part_scrmbl_req_ready[LciIdx];
+  assign unused_part_scrmbl_rsp_valid = part_scrmbl_rsp_valid[LciIdx];
 
-  // TODO: add all key outputs
-  assign otp_flash_key_o = '0;
-  // TODO: hashed raw unlock token
-  assign lc_otp_token_rsp_o = '0;
+  ////////////////////////////////////
+  // Key Derivation Interface (KDI) //
+  ////////////////////////////////////
+
+  logic scrmbl_key_seed_valid;
+  logic [SramKeySeedWidth-1:0] sram_data_key_seed;
+  logic [FlashKeySeedWidth-1:0] flash_data_key_seed, flash_addr_key_seed;
+
+  otp_ctrl_kdi i_otp_ctrl_kdi (
+    .clk_i,
+    .rst_ni,
+    .key_deriv_en_i          ( pwr_otp_init_rsp_o.done ),
+    .escalate_en_i           ( lc_escalate_en_i        ),
+    .fsm_err_o               ( key_deriv_fsm_err       ),
+    .scrmbl_key_seed_valid_i ( scrmbl_key_seed_valid   ),
+    .flash_data_key_seed_i   ( flash_data_key_seed     ),
+    .flash_addr_key_seed_i   ( flash_addr_key_seed     ),
+    .sram_data_key_seed_i    ( sram_data_key_seed      ),
+    .otp_edn_req_o,
+    .otp_edn_rsp_i,
+    .lc_otp_token_req_i ,
+    .lc_otp_token_rsp_o ,
+    .flash_otp_key_req_i,
+    .flash_otp_key_rsp_o,
+    .sram_otp_key_req_i,
+    .sram_otp_key_rsp_o,
+    .otbn_otp_key_req_i,
+    .otbn_otp_key_rsp_o,
+    .scrmbl_mtx_req_o        ( part_scrmbl_mtx_req[KdiIdx]          ),
+    .scrmbl_mtx_gnt_i        ( part_scrmbl_mtx_gnt[KdiIdx]          ),
+    .scrmbl_cmd_o            ( part_scrmbl_req_bundle[KdiIdx].cmd   ),
+    .scrmbl_sel_o            ( part_scrmbl_req_bundle[KdiIdx].sel   ),
+    .scrmbl_data_o           ( part_scrmbl_req_bundle[KdiIdx].data  ),
+    .scrmbl_valid_o          ( part_scrmbl_req_bundle[KdiIdx].valid ),
+    .scrmbl_ready_i          ( part_scrmbl_req_ready[KdiIdx]        ),
+    .scrmbl_valid_i          ( part_scrmbl_rsp_valid[KdiIdx]        ),
+    .scrmbl_data_i           ( part_scrmbl_rsp_data                 )
+  );
+
+  // Tie off OTP bus access, since this is not needed.
+  assign part_otp_arb_req[KdiIdx] = 1'b0;
+  assign part_otp_arb_bundle[KdiIdx] = '0;
+
+  // This stops lint from complaining about unused signals.
+  logic unused_part_otp_arb_gnt, unused_part_otp_rvalid;
+  logic [OtpIfWidth-1:0] unused_part_otp_rdata;
+  logic [OtpErrWidth-1:0] unused_part_otp_err;
+  assign unused_part_otp_arb_gnt = part_otp_arb_gnt[KdiIdx];
+  assign unused_part_otp_rvalid  = part_otp_rvalid[KdiIdx];
+  assign unused_part_otp_rdata   = part_otp_rdata[KdiIdx];
+  assign unused_part_otp_err   = part_otp_err[KdiIdx];
 
   /////////////////////////
   // Partition Instances //
@@ -619,8 +693,8 @@
         .clk_i,
         .rst_ni,
         // TODO: Entropy for clearing LFSRs
-        // .entropy_en_i     ( entropy_i.en                    ),
-        // .entropy_i        ( entropy_i.data[(k-NumUnbuffered) * 2 +: 2] ),
+        // .entropy_en_i     ( edn_otp_up_i.en                    ),
+        // .entropy_i        ( edn_otp_up_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]                ),
@@ -662,6 +736,8 @@
   // Buffered Data Output Mapping //
   //////////////////////////////////
 
+  // TODO: template these mappings, based on the address map hjson contents.
+
   // Output complete hardware config partition.
   // Actual mapping to other IPs can occur at the top-level.
   assign hw_cfg_o = part_buf_data[PartInfo[HwCfgIdx].offset +:
@@ -673,12 +749,12 @@
           otp_keymgr_key_o.key_share0} = part_buf_data[PartInfo[Secret2Idx].offset +:
                                                        2*KeyMgrKeyWidth/8];
   // Scrambling Keys
-  assign scrambling_keys_valid = part_init_done[Secret1Idx];
-  assign {sram_data_key,
-          flash_data_key,
-          flash_addr_key} = part_buf_data[PartInfo[Secret1Idx].offset +:
-                                          2*FlashKeySeedWidth/8 +
-                                          SramKeySeedWidth/8];
+  assign scrmbl_key_seed_valid = part_init_done[Secret1Idx];
+  assign {sram_data_key_seed,
+          flash_data_key_seed,
+          flash_addr_key_seed} = part_buf_data[PartInfo[Secret1Idx].offset +:
+                                               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,
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_kdi.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_kdi.sv
new file mode 100644
index 0000000..16cb7b3
--- /dev/null
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_kdi.sv
@@ -0,0 +1,59 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Scrambling key derivation module for OTP.
+//
+
+`include "prim_assert.sv"
+
+module otp_ctrl_kdi
+  import otp_ctrl_pkg::*;
+  import otp_ctrl_reg_pkg::*;
+#(
+  // Number of SRAM key requestor slots. All of them will use the same key seed.
+  // However, each request will generate a new key seed.
+  parameter int NumSramKeyReqSlots = 3
+) (
+  input                                              clk_i,
+  input                                              rst_ni,
+  // Pulse to enable this module after OTP partitions have
+  // been initialized.
+  input                                              key_deriv_en_i,
+  // Escalation input. This moves the FSM into a terminal state.
+  input  lc_tx_t                                     escalate_en_i,
+  // FSM is in error state
+  output otp_err_e                                   fsm_err_o,
+  // Key seed inputs from OTP
+  input  logic                                       scrmbl_key_seed_valid_i,
+  input  logic [FlashKeySeedWidth-1:0]               flash_data_key_seed_i,
+  input  logic [FlashKeySeedWidth-1:0]               flash_addr_key_seed_i,
+  input  logic [SramKeySeedWidth-1:0]                sram_data_key_seed_i,
+  // EDN interface for requesting entropy
+  output otp_edn_req_t                               otp_edn_req_o,
+  input  otp_edn_rsp_t                               otp_edn_rsp_i,
+  // Lifecycle hashing request
+  input  lc_otp_token_rsp_t                          lc_otp_token_req_i,
+  output lc_otp_token_rsp_t                          lc_otp_token_rsp_o,
+  // Scrambling key requests
+  input  flash_otp_key_req_t                         flash_otp_key_req_i,
+  output flash_otp_key_rsp_t                         flash_otp_key_rsp_o,
+  input  sram_otp_key_req_t [NumSramKeyReqSlots-1:0] sram_otp_key_req_i,
+  output sram_otp_key_rsp_t [NumSramKeyReqSlots-1:0] sram_otp_key_rsp_o,
+  input  otbn_otp_key_req_t                          otbn_otp_key_req_i,
+  output otbn_otp_key_rsp_t                          otbn_otp_key_rsp_o,
+  // Scrambling mutex request
+  output logic                                       scrmbl_mtx_req_o,
+  input                                              scrmbl_mtx_gnt_i,
+  // Scrambling datapath interface
+  output otp_scrmbl_cmd_e                            scrmbl_cmd_o,
+  output logic [ConstSelWidth-1:0]                   scrmbl_sel_o,
+  output logic [ScrmblBlockWidth-1:0]                scrmbl_data_o,
+  output logic                                       scrmbl_valid_o,
+  input  logic                                       scrmbl_ready_i,
+  input  logic                                       scrmbl_valid_i,
+  input  logic [ScrmblBlockWidth-1:0]                scrmbl_data_i
+);
+
+
+endmodule : otp_ctrl_kdi
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 458ab35..34aabee 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_lfsr_timer.sv
@@ -53,7 +53,9 @@
   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
+  input  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
 );
 
   //////////
@@ -153,9 +155,13 @@
     IdleSt      = 9'b011011101,
     IntegWaitSt = 9'b100111111,
     CnstyWaitSt = 9'b001000110,
-    ErrSt       = 9'b101101000
+    ErrorSt     = 9'b101101000
   } state_e;
+
   state_e state_d, state_q;
+  logic chk_timeout_d, chk_timeout_q;
+
+  assign chk_timeout_o = chk_timeout_q;
 
   always_comb begin : p_fsm
     state_d = state_q;
@@ -174,6 +180,7 @@
     // Status signals going to CSRs and error logic.
     chk_timeout_o = 1'b0;
     chk_pending_o = 1'b0;
+    fsm_err_o = 1'b0;
 
     unique case (state_q)
       ///////////////////////////////////////////////////////////////////
@@ -206,7 +213,8 @@
       IntegWaitSt: begin
         chk_pending_o = 1'b1;
         if (!timeout_zero && integ_cnt_zero) begin
-          state_d = ErrSt;
+          state_d = ErrorSt;
+          chk_timeout_d = 1'b1;
         end else if (integ_chk_req_q == '0) begin
           state_d = IdleSt;
           // This draws the next wait period.
@@ -221,7 +229,8 @@
       CnstyWaitSt: begin
         chk_pending_o = 1'b1;
         if (!timeout_zero && cnsty_cnt_zero) begin
-          state_d = ErrSt;
+          state_d = ErrorSt;
+          chk_timeout_d = 1'b1;
         end else if (cnsty_chk_req_q == '0) begin
           state_d = IdleSt;
           // This draws the next wait period.
@@ -231,17 +240,26 @@
       end
       ///////////////////////////////////////////////////////////////////
       // Terminal error state. This raises an alert.
-      ErrSt: begin
-        chk_timeout_o = 1'b1;
+      ErrorSt: begin
+        if (!chk_timeout_q) begin
+          fsm_err_o = 1'b1;
+        end
       end
       ///////////////////////////////////////////////////////////////////
       // This should never happen, hence we directly jump into the
       // error state, where an alert will be triggered.
       default: begin
-        state_d = ErrSt;
+        state_d = ErrorSt;
       end
       ///////////////////////////////////////////////////////////////////
-    endcase
+    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
+    end
   end
 
   ///////////////
@@ -255,12 +273,14 @@
       cnsty_cnt_q <= '0;
       integ_chk_req_q <= '0;
       cnsty_chk_req_q <= '0;
+      chk_timeout_q   <= 1'b0;
     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;
+      chk_timeout_q   <= chk_timeout_d;
     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 b4b2f67..6312855 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_buf.sv
@@ -611,17 +611,23 @@
   ////////////////
 
   // Known assertions
-  `ASSERT_KNOWN(InitDoneKnown_A,  init_done_o)
-  `ASSERT_KNOWN(ErrorKnown_A,     error_o)
-  `ASSERT_KNOWN(AccessKnown_A,    access_o)
-  `ASSERT_KNOWN(DataKnown_A,      data_o)
-  `ASSERT_KNOWN(DigestKnown_A,    digest_o)
-
-  `ASSERT_KNOWN(OtpReqKnown_A,    otp_req_o)
-  `ASSERT_KNOWN(OtpCmdKnown_A,    otp_cmd_o)
-  `ASSERT_KNOWN(OtpSizeKnown_A,   otp_size_o)
-  `ASSERT_KNOWN(OtpWdataKnown_A,  otp_wdata_o)
-  `ASSERT_KNOWN(OtpAddrKnown_A,   otp_addr_o)
+  `ASSERT_KNOWN(InitDoneKnown_A,     init_done_o)
+  `ASSERT_KNOWN(IntegChkAckKnown_A,  integ_chk_ack_o)
+  `ASSERT_KNOWN(CnstyChkAckKnown_A,  cnsty_chk_ack_o)
+  `ASSERT_KNOWN(ErrorKnown_A,        error_o)
+  `ASSERT_KNOWN(AccessKnown_A,       access_o)
+  `ASSERT_KNOWN(DigestKnown_A,       digest_o)
+  `ASSERT_KNOWN(DataKnown_A,         data_o)
+  `ASSERT_KNOWN(OtpReqKnown_A,       otp_req_o)
+  `ASSERT_KNOWN(OtpCmdKnown_A,       otp_cmd_o)
+  `ASSERT_KNOWN(OtpSizeKnown_A,      otp_size_o)
+  `ASSERT_KNOWN(OtpWdataKnown_A,     otp_wdata_o)
+  `ASSERT_KNOWN(OtpAddrKnown_A,      otp_addr_o)
+  `ASSERT_KNOWN(ScrmblMtxReqKnown_A, scrmbl_mtx_req_o)
+  `ASSERT_KNOWN(ScrmblCmdKnown_A,    scrmbl_cmd_o)
+  `ASSERT_KNOWN(ScrmblSelKnown_A,    scrmbl_sel_o)
+  `ASSERT_KNOWN(ScrmblDataKnown_A,   scrmbl_data_o)
+  `ASSERT_KNOWN(ScrmblValidKnown_A,  scrmbl_valid_o)
 
   // Uninitialized partitions should always be locked, no matter what.
   `ASSERT(InitWriteLocksPartition_A,
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
index 1c5413c..dbebbb8 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
@@ -176,29 +176,44 @@
     '{Buffered,     11'h7A8, 88,       0,       1'b0,      1'b0,      1'b0,       1'b0}
   };
 
-  parameter int CreatorSwCfgIdx = 0;
-  parameter int OwnerSwCfgIdx   = 1;
-  parameter int HwCfgIdx        = 2;
-  parameter int Secret0Idx      = 3;
-  parameter int Secret1Idx      = 4;
-  parameter int Secret2Idx      = 5;
-  parameter int LifeCycleIdx    = 6;
-  // These are not "real partitions", but in terms of implementation it is convenient to
-  // add these at the end of certain arrays.
-  parameter int DaiIdx = 7;
-  parameter int LciIdx = 8;
+  typedef enum {
+    CreatorSwCfgIdx,
+    OwnerSwCfgIdx,
+    HwCfgIdx,
+    Secret0Idx,
+    Secret1Idx,
+    Secret2Idx,
+    LifeCycleIdx,
+    // These are not "real partitions", but in terms of implementation it is convenient to
+    // add these at the end of certain arrays.
+    DaiIdx,
+    LciIdx,
+    KdiIdx,
+    // Number of agents is the last idx+1.
+    NumAgents
+  } part_idx_e;
 
-  parameter int NumUnbuffered = 2;
   parameter int NumHwCfgBits = PartInfo[HwCfgIdx].size*8;
 
   ////////////////////////
   // Typedefs for CSRNG //
   ////////////////////////
 
+  // Unidirectional input type for LFSR reseeding.
   typedef struct packed {
     logic        en;
     logic [31:0] data;
-  } otp_entropy_t;
+  } edn_otp_up_t;
+
+  // Bidirectional entropy requests for scramble key derivation.
+  typedef struct packed {
+    logic        req;
+  } otp_edn_req_t;
+
+  typedef struct packed {
+    logic        ack;
+    logic [31:0] data;
+  } otp_edn_rsp_t;
 
   ///////////////////////////////
   // Typedefs for LC Interface //
@@ -212,8 +227,8 @@
   parameter int NumLcCountValues = 32;
 
   typedef enum logic [LcValueWidth-1:0] {
-    Blk = 16'h0000,
-    Set = 16'hF5FA
+    Blk = 16'h0000, // blank
+    Set = 16'hF5FA  // programmed
   } lc_value_e;
 
   typedef enum logic [LcStateWidth-1:0] {
@@ -284,30 +299,57 @@
   // Typedefs for Key Broadcast //
   ////////////////////////////////
 
-  parameter int KeyMgrKeyWidth = 256;
-  parameter int FlashKeyWidth  = 128;
-  parameter int SramKeyWidth  = 128;
-
   parameter int FlashKeySeedWidth = 256;
   parameter int SramKeySeedWidth  = 128;
 
+  parameter int KeyMgrKeyWidth   = 256;
+
+  parameter int FlashKeyWidth    = 128;
+
+  parameter int SramKeyWidth     = 128;
+  parameter int SramNonceWidth   = 64;
+
+  parameter int OtbnKeyWidth     = 128;
+  parameter int OtbnNonceWidth   = 256;
+
   typedef struct packed {
     logic valid;
     logic [KeyMgrKeyWidth-1:0] key_share0;
     logic [KeyMgrKeyWidth-1:0] key_share1;
-  } keymgr_key_t;
+  } otp_keymgr_key_t;
 
   typedef struct packed {
-    logic valid;
+    logic req;
+  } flash_otp_key_req_t;
+
+  typedef struct packed {
+    logic req;
+  } sram_otp_key_req_t;
+
+  typedef struct packed {
+    logic req;
+  } otbn_otp_key_req_t;
+
+  typedef struct packed {
+    logic ack;
     logic [FlashKeyWidth-1:0] addr_key;
     logic [FlashKeyWidth-1:0] data_key;
-  } flash_key_t;
+  } flash_otp_key_rsp_t;
 
   typedef struct packed {
-    logic valid;
-    logic [SramKeyWidth-1:0] addr_key;
-    logic [SramKeyWidth-1:0] data_key;
-  } sram_key_t;
+    logic ack;
+    logic [SramKeyWidth-1:0]   key;
+    logic [SramNonceWidth-1:0] nonce;
+  } sram_otp_key_rsp_t;
+
+  typedef struct packed {
+    logic ack;
+    logic [OtbnKeyWidth-1:0]   key;
+    logic [OtbnNonceWidth-1:0] nonce;
+  } otbn_otp_key_rsp_t;
+
+
+
 
 
   ////////////////////////////////
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv
index 39f270d..6c9a428 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_scrmbl.sv
@@ -77,7 +77,10 @@
   output logic                        ready_o,
   // output data
   output logic [ScrmblBlockWidth-1:0] data_o,
-  output logic                        valid_o
+  output logic                        valid_o,
+  // escalation input and FSM error indication
+  input  logic                        escalate_en_i,
+  output logic                        fsm_err_o
 );
 
   ////////////////////////
@@ -172,7 +175,7 @@
   // FSM //
   /////////
 
-  // Encoding generated with ./sparse-fsm-encode -d 5 -m 4 -n 8
+  // Encoding generated with ./sparse-fsm-encode -d 5 -m 5 -n 9
   // Hamming distance histogram:
   //
   // 0: --
@@ -180,19 +183,21 @@
   // 2: --
   // 3: --
   // 4: --
-  // 5: |||||||||||||||||||| (66.67%)
-  // 6: |||||||||| (33.33%)
+  // 5: |||||||||||||||||||| (60.00%)
+  // 6: ||||||||||||| (40.00%)
   // 7: --
   // 8: --
+  // 9: --
   //
   // Minimum Hamming distance: 5
   // Maximum Hamming distance: 6
   //
-  typedef enum logic [7:0] {
-    IdleSt    = 8'b11100001,
-    DecryptSt = 8'b01011010,
-    EncryptSt = 8'b10010100,
-    DigestSt  = 8'b00101111
+  typedef enum logic [8:0] {
+    IdleSt    = 9'b011110111,
+    DecryptSt = 9'b000010001,
+    EncryptSt = 9'b011101000,
+    DigestSt  = 9'b100111100,
+    ErrorSt   = 9'b101001111
   } state_e;
 
   localparam int CntWidth = $clog2(NumPresentRounds+1);
@@ -224,6 +229,7 @@
     cnt_clr          = 1'b0;
     valid_d          = 1'b0;
     ready_o          = 1'b0;
+    fsm_err_o        = 1'b0;
 
     unique case (state_q)
       ///////////////////////////////////////////////////////////////////
@@ -329,12 +335,25 @@
         end
       end
       ///////////////////////////////////////////////////////////////////
+      // Terminal error state. This raises an alert.
+      ErrorSt: begin
+        fsm_err_o = 1'b1;
+      end
+      ///////////////////////////////////////////////////////////////////
+      // This should never happen, hence we directly jump into the
+      // error state, where an alert will be triggered.
       default: begin
-        // TODO: add error signal here as well, like in the partition controllers?
-        state_d = IdleSt;
+        state_d = ErrorSt;
       end
       ///////////////////////////////////////////////////////////////////
     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
+    end
   end
 
   /////////////////////////////