[keymgr/dv] Add sideload key check

1. Add sideload key check in interface in order to check the duration as
well
2. create a `get_err_code` function and simpilfy some logic in scb
3. store generated meaningful key in interface to compare with invalid
keys generated by error cases
4. fix some typos in cfgen_vseq and lc_disable_vseq

Signed-off-by: Weicai Yang <weicai@google.com>
diff --git a/hw/ip/keymgr/dv/env/keymgr_env.core b/hw/ip/keymgr/dv/env/keymgr_env.core
index 1dbfc73..00c41f8 100644
--- a/hw/ip/keymgr/dv/env/keymgr_env.core
+++ b/hw/ip/keymgr/dv/env/keymgr_env.core
@@ -11,8 +11,8 @@
       - lowrisc:dv:cip_lib
       - lowrisc:dv:keymgr_kmac_agent
     files:
-      - keymgr_if.sv
       - keymgr_env_pkg.sv
+      - keymgr_if.sv
       - keymgr_env_cfg.sv: {is_include_file: true}
       - keymgr_env_cov.sv: {is_include_file: true}
       - keymgr_virtual_sequencer.sv: {is_include_file: true}
diff --git a/hw/ip/keymgr/dv/env/keymgr_env_pkg.sv b/hw/ip/keymgr/dv/env/keymgr_env_pkg.sv
index 9f1a494..7e3652a 100644
--- a/hw/ip/keymgr/dv/env/keymgr_env_pkg.sv
+++ b/hw/ip/keymgr/dv/env/keymgr_env_pkg.sv
@@ -24,6 +24,7 @@
   parameter uint DIGEST_SHARE_WORD_NUM = keymgr_pkg::KeyWidth / TL_DW;
 
   typedef virtual keymgr_if keymgr_vif;
+  typedef bit [keymgr_pkg::Shares-1:0][keymgr_pkg::KeyWidth-1:0] key_shares_t;
   typedef enum {
     IntrOpDone,
     NumKeyMgrIntr
diff --git a/hw/ip/keymgr/dv/env/keymgr_if.sv b/hw/ip/keymgr/dv/env/keymgr_if.sv
index 35fbc3c..cc511b4 100644
--- a/hw/ip/keymgr/dv/env/keymgr_if.sv
+++ b/hw/ip/keymgr/dv/env/keymgr_if.sv
@@ -17,12 +17,25 @@
   keymgr_pkg::hw_key_req_t hmac_key;
   keymgr_pkg::hw_key_req_t aes_key;
 
-  keymgr_pkg::hw_key_req_t kmac_key_exp;
-  keymgr_pkg::hw_key_req_t hmac_key_exp;
-  keymgr_pkg::hw_key_req_t aes_key_exp;
+  keymgr_pkg::hw_key_req_t kmac_key_exp = '0;
+  keymgr_pkg::hw_key_req_t hmac_key_exp = '0;
+  keymgr_pkg::hw_key_req_t aes_key_exp = '0;
 
-  // indicate keymgr entered disabled directly. kmac_key are wiped, don't check output it
-  bit direct_to_disabled;
+  // connect KDF interface for assertion check
+  wire keymgr_pkg::kmac_data_req_t kmac_data_req;
+  wire keymgr_pkg::kmac_data_rsp_t kmac_data_rsp;
+
+  // indicate if check the key is same as expected or shouldn't match to any meaningful key
+  bit is_kmac_key_good = 0;
+  bit is_hmac_key_good = 0;
+  bit is_aes_key_good = 0;
+
+  // when kmac sideload key is generated, kmac may be used to do other OP, but once the OP is done,
+  // it should automatically switch back to sideload key
+  bit is_kmac_sideload_avail = 0;
+  keymgr_env_pkg::key_shares_t kmac_sideload_key_shares;
+
+  keymgr_env_pkg::key_shares_t keys_a_array[string][string];
 
   string msg_id = "keymgr_if";
 
@@ -31,7 +44,7 @@
     // the key manager comes out of reset.
     // The power/reset manager ensures that
     // this sequencing is correct.
-    keymgr_en = lc_ctrl_pkg::Off;
+    keymgr_en = lc_ctrl_pkg::lc_tx_t'($urandom);
 
     // async delay as these signals are from different clock domain
     #($urandom_range(1000, 0) * 1ns);
@@ -40,7 +53,6 @@
     otp_hw_cfg.data.device_id = 'hF0F0;
     otp_key = otp_ctrl_pkg::OTP_KEYMGR_KEY_DEFAULT;
     flash   = flash_ctrl_pkg::KEYMGR_FLASH_DEFAULT;
-    direct_to_disabled = 0;
   endtask
 
   // randomize otp, lc, flash input data
@@ -74,35 +86,105 @@
     flash   = local_flash;
   endtask
 
-  task automatic update_exp_key(bit [keymgr_pkg::Shares-1:0][keymgr_pkg::KeyWidth-1:0] key_shares,
-                                keymgr_pkg::keymgr_key_dest_e dest = keymgr_pkg::Kmac);
+  // update kmac key for comparison during KDF
+  function automatic void update_kdf_key(keymgr_env_pkg::key_shares_t key_shares,
+                                         keymgr_pkg::keymgr_working_state_e state,
+                                         bit good_key = 1);
+
+    kmac_key_exp <= '{1'b1, key_shares[0], key_shares[1]};
+    is_kmac_key_good = good_key;
+  endfunction
+
+  // store internal key once it's available and use to compare if future OP is invalid
+  function automatic void store_internal_key(keymgr_env_pkg::key_shares_t key_shares,
+                                             keymgr_pkg::keymgr_working_state_e state);
+
+    keys_a_array[state.name]["Internal"] = key_shares;
+  endfunction
+
+  // update sideload key for comparison
+  // if it's good key, store it to compare for future invalid OP
+  function automatic void update_sideload_key(keymgr_env_pkg::key_shares_t key_shares,
+                                              keymgr_pkg::keymgr_working_state_e state,
+                                              keymgr_pkg::keymgr_key_dest_e dest = keymgr_pkg::Kmac,
+                                              bit good_key = 1);
     case (dest)
       keymgr_pkg::Kmac: begin
-        kmac_key_exp = '{1'b1, key_shares[0], key_shares[1]};
+        kmac_key_exp             <= '{1'b1, key_shares[0], key_shares[1]};
+        is_kmac_key_good         <= good_key;
+        is_kmac_sideload_avail   <= 1;
+        kmac_sideload_key_shares <= key_shares;
       end
       keymgr_pkg::Hmac: begin
-        hmac_key_exp = '{1'b1, key_shares[0], key_shares[1]};
+        hmac_key_exp     <= '{1'b1, key_shares[0], key_shares[1]};
+        is_hmac_key_good <= good_key;
       end
       keymgr_pkg::Aes: begin
-        aes_key_exp = '{1'b1, key_shares[0], key_shares[1]};
+        aes_key_exp     <= '{1'b1, key_shares[0], key_shares[1]};
+        is_aes_key_good <= good_key;
       end
       default: `uvm_fatal("keymgr_if", $sformatf("Unexpect dest type %0s", dest.name))
     endcase
-  endtask
 
-  task automatic enter_disabled_directly();
-    direct_to_disabled = 1;
-  endtask
-  // TODO kmac_key only last until done signal is set. Fix this later
-  //`ASSERT(KmacKeyStable, $stable(kmac_key_exp) && $stable(direct_to_disabled) |=> $stable(kmac_key),
-  //    clk, !rst_n)
-  //`ASSERT(KmacKeyUpdate, !$stable(kmac_key_exp) |=> kmac_key == kmac_key_exp,
-  //    clk, !rst_n && !direct_to_disabled)
+    if (good_key) keys_a_array[state.name][dest.name]  = key_shares;
+  endfunction
 
-  `ASSERT(HmacKeyStable, $stable(hmac_key_exp) |=> $stable(hmac_key), clk, !rst_n)
-  `ASSERT(HmacKeyUpdate, !$stable(hmac_key_exp) |=> hmac_key == hmac_key_exp, clk, !rst_n)
+  // if kmac sideload key is available, switch to it after an operation is completed
+  // if not available, de-assert valid after done is asserted
+  initial begin
+    forever begin
+      @(posedge clk);
+      if (kmac_data_rsp.done) begin
+        if (is_kmac_sideload_avail) begin
+          kmac_key_exp <= '{1'b1, kmac_sideload_key_shares[0], kmac_sideload_key_shares[1]};
+          is_kmac_key_good <= 1;
+        end else begin
+          kmac_key_exp.valid <= 0;
+        end
+      end // kmac_data_rsp.done
+    end // forever
+  end
 
-  `ASSERT(AesKeyStable, $stable(aes_key_exp) |=> $stable(aes_key), clk, !rst_n)
-  `ASSERT(AesKeyUpdate, !$stable(aes_key_exp) |=> aes_key == aes_key_exp, clk, !rst_n)
+  // check if key is invalid, it should not match to any of the meaningful keys
+  initial begin
+    fork
+      forever begin
+        @(posedge clk);
+        if (!is_kmac_key_good) check_invalid_key(kmac_key, "KMAC");
+      end
+      forever begin
+        @(posedge clk);
+        if (!is_hmac_key_good) check_invalid_key(hmac_key, "HMAC");
+      end
+      forever begin
+        @(posedge clk);
+        if (!is_aes_key_good) check_invalid_key(aes_key, "AES");
+      end
+    join
+  end
 
+  function automatic void check_invalid_key(keymgr_pkg::hw_key_req_t act_key, string key_name);
+    if (rst_n && act_key.valid) begin
+      foreach (keys_a_array[i, j]) begin
+        `DV_CHECK_NE({act_key.key_share1, act_key.key_share0}, keys_a_array[i][j],
+            $sformatf("%s key at state %s from %s", key_name, i, j), , msg_id)
+      end
+    end
+  endfunction
+
+  `define KM_ASSERT(NAME, SEQ) \
+    `ASSERT(NAME, SEQ, clk, !rst_n || keymgr_en != lc_ctrl_pkg::On)
+
+  `KM_ASSERT(CheckKmacKey, is_kmac_key_good && kmac_key_exp.valid -> kmac_key == kmac_key_exp)
+
+  `KM_ASSERT(CheckKmacKeyValid, kmac_key_exp.valid == kmac_key.valid)
+
+  // TODO update hmac and aes checker later
+  //`KM_ASSERT(HmacKeyStable, $stable(hmac_key_exp) |=> $stable(hmac_key))
+  //`KM_ASSERT(HmacKeyUpdate, !$stable(hmac_key_exp) |=> hmac_key == hmac_key_exp)
+
+  //`KM_ASSERT(AesKeyStable, $stable(aes_key_exp) |=> $stable(aes_key))
+  //`KM_ASSERT(AesKeyUpdate, !$stable(aes_key_exp) |=> aes_key == aes_key_exp)
+
+  `undef KM_ASSERT
 endinterface
diff --git a/hw/ip/keymgr/dv/env/keymgr_scoreboard.sv b/hw/ip/keymgr/dv/env/keymgr_scoreboard.sv
index abd0fb8..21d3119 100644
--- a/hw/ip/keymgr/dv/env/keymgr_scoreboard.sv
+++ b/hw/ip/keymgr/dv/env/keymgr_scoreboard.sv
@@ -44,7 +44,7 @@
   keymgr_pkg::keymgr_op_status_e     current_op_status;
 
   // HW internal key, used for OP in current state
-  bit [keymgr_pkg::Shares-1:0][keymgr_pkg::KeyWidth-1:0] current_internal_key;
+  key_shares_t current_internal_key;
 
   // preserve value at TL read address phase and compare it at read data phase
   keymgr_pkg::keymgr_op_status_e     addr_phase_op_status;
@@ -60,9 +60,6 @@
   bit [keymgr_pkg::AdvDataWidth-1:0] adv_data_a_array[keymgr_pkg::keymgr_working_state_e];
   bit [keymgr_pkg::IdDataWidth-1:0]  id_data_a_array[keymgr_pkg::keymgr_working_state_e];
   bit [keymgr_pkg::GenDataWidth-1:0] sw_data_a_array[keymgr_pkg::keymgr_working_state_e];
-  bit [keymgr_pkg::GenDataWidth-1:0] hw_data_a_array[keymgr_pkg::keymgr_working_state_e];
-  bit [keymgr_pkg::Shares-1:0][keymgr_pkg::KeyWidth-1:0]
-      internal_key_a_array[keymgr_pkg::keymgr_working_state_e];
 
   `uvm_component_new
 
@@ -106,6 +103,8 @@
     // there must be a OP which causes the KMAC data req
     `DV_CHECK_EQ(current_op_status, keymgr_pkg::OpWip)
 
+    if (cfg.keymgr_vif.keymgr_en != lc_ctrl_pkg::On) return;
+
     case (op)
       keymgr_pkg::OpAdvance: begin
         case (current_state)
@@ -142,10 +141,10 @@
 
     case (update_result)
       UpdateInternalKey: begin
-        `uvm_info(`gfn, $sformatf("Keymgr State from %0s to %0s", current_state.name,
-                                  get_next_state(current_state)), UVM_LOW)
         current_internal_key = {item.rsp_digest_share1, item.rsp_digest_share0};
-        internal_key_a_array[current_state] = current_internal_key;
+        cfg.keymgr_vif.store_internal_key(current_internal_key, current_state);
+        `uvm_info(`gfn, $sformatf("Update internal key 0x%0h for state %s", current_internal_key,
+                                  current_state.name), UVM_MEDIUM)
       end
       UpdateSwOut: begin
         bit [keymgr_pkg::Shares-1:0][DIGEST_SHARE_WORD_NUM-1:0][TL_DW-1:0] sw_share_output;
@@ -159,8 +158,17 @@
           `uvm_info(`gfn, $sformatf("Predict %0s = 0x%0h", csr_name, sw_share_output[i][j]),
                     UVM_MEDIUM)
         end
-        // TODO
-        UpdateHwOut: begin
+      end
+      UpdateHwOut: begin
+        key_shares_t key_shares = {item.rsp_digest_share1, item.rsp_digest_share0};
+        bit good_key = (get_err_code() == 0);
+        keymgr_pkg::keymgr_key_dest_e dest = keymgr_pkg::keymgr_key_dest_e'(
+            `gmv(ral.control.dest_sel));
+
+        if (dest != keymgr_pkg::None) begin
+          cfg.keymgr_vif.update_sideload_key(key_shares, current_state, dest, good_key);
+          `uvm_info(`gfn, $sformatf("Update sideload key 0x%0h for %s", key_shares, dest.name),
+                    UVM_MEDIUM)
         end
       end
       default: `uvm_info(`gfn, "KMAC result isn't updated to any output", UVM_MEDIUM)
@@ -170,55 +178,47 @@
     void'(ral.intr_state.predict(.value(1 << int'(IntrOpDone))));
   endfunction
 
+  // update current_state, current_op_status, err_code, alert and return update_result for updating
+  // internal key, HW/SW output
   virtual function update_result_e process_update_after_op_done();
     update_result_e update_result;
-    bit advance_state;
     keymgr_pkg::keymgr_ops_e op = get_operation();
+    bit is_err = (get_err_code() > 0);
+
+    if (is_err) current_op_status <= keymgr_pkg::OpDoneFail;
+    else        current_op_status <= keymgr_pkg::OpDoneSuccess;
+
+    process_error_n_alert();
 
     case (current_state)
-      keymgr_pkg::StInit: begin
-        current_op_status = keymgr_pkg::OpDoneSuccess;
+      keymgr_pkg::StInit, keymgr_pkg::StCreatorRootKey, keymgr_pkg::StOwnerIntKey,
+          keymgr_pkg::StOwnerKey: begin
 
         case (op)
-          keymgr_pkg::OpAdvance, keymgr_pkg::OpDisable: begin
-            advance_state = 1;
+          keymgr_pkg::OpAdvance: begin
+            // if it's StOwnerKey, it advacens to OpDisable. Key is just random value
+            if (current_state == keymgr_pkg::StOwnerKey) update_result = NotUpdate;
+            else                                         update_result = UpdateInternalKey;
+            current_state = get_next_state(current_state);
           end
-          default: begin // gen-id/sw/hw
-            current_op_status = keymgr_pkg::OpDoneFail;
-            trigger_error(keymgr_pkg::ErrInvalidOp);
-            update_result = NotUpdate;
+          keymgr_pkg::OpDisable: begin
+            update_result = UpdateInternalKey;
+            current_state = keymgr_pkg::StDisabled;
           end
-        endcase
-      end
-      keymgr_pkg::StCreatorRootKey, keymgr_pkg::StOwnerIntKey, keymgr_pkg::StOwnerKey: begin
-        current_op_status = keymgr_pkg::OpDoneSuccess;
-
-        case (op)
-          keymgr_pkg::OpAdvance, keymgr_pkg::OpDisable: begin
-            advance_state = 1;
-          end
-          keymgr_pkg::OpGenId: begin
-            update_result = UpdateSwOut;
-          end
-          keymgr_pkg::OpGenSwOut, keymgr_pkg::OpGenHwOut: begin
-            // for gen-sw/hw, key_version is used as kmac data. If it's bigger than max
-            // version, status will be failed
-            if (get_current_max_version() < `gmv(ral.key_version)) begin
-              current_op_status = keymgr_pkg::OpDoneFail;
-              trigger_error(keymgr_pkg::ErrInvalidIn);
+          keymgr_pkg::OpGenId, keymgr_pkg::OpGenSwOut, keymgr_pkg::OpGenHwOut: begin
+            // If any error, no update for output
+            if (is_err) begin
               update_result = NotUpdate;
+            end else if (op == keymgr_pkg::OpGenHwOut) begin
+              update_result = UpdateHwOut;
             end else begin
-              if (op == keymgr_pkg::OpGenSwOut) update_result = UpdateSwOut;
-              else                              update_result = UpdateHwOut;
+              update_result = UpdateSwOut;
             end
           end
           default: `uvm_fatal(`gfn, $sformatf("Unexpected operation: %0d", op.name))
         endcase
       end
       keymgr_pkg::StDisabled: begin
-        cfg.keymgr_vif.update_exp_key(current_internal_key);
-        current_op_status = keymgr_pkg::OpDoneFail;
-        trigger_error(keymgr_pkg::ErrInvalidOp);
         case (op)
           keymgr_pkg::OpAdvance, keymgr_pkg::OpDisable: begin
             update_result = NotUpdate;
@@ -235,13 +235,6 @@
       default: `uvm_fatal(`gfn, $sformatf("Unexpected current_state: %0d", current_state))
     endcase
 
-    if (advance_state) begin
-      cfg.keymgr_vif.update_exp_key(current_internal_key);
-      update_result = UpdateInternalKey;
-      if (op == keymgr_pkg::OpAdvance) current_state = get_next_state(current_state);
-      else                             current_state = keymgr_pkg::StDisabled;
-    end
-
     return update_result;
   endfunction
 
@@ -338,16 +331,45 @@
             keymgr_pkg::keymgr_ops_e op = get_operation();
             current_op_status = keymgr_pkg::OpWip;
 
+            `uvm_info(`gfn, $sformatf("At %s, %s is issued", current_state.name, op.name), UVM_LOW)
+
             // In StReset, OP doesn't trigger KDF, update result here for InvalidOp and update
             // status at `op_status`
             // For other states, update result after KDF is done at process_kmac_data_rsp
-            if (current_state == keymgr_pkg::StReset && op != keymgr_pkg::OpAdvance) begin
-              current_op_status = keymgr_pkg::OpDoneFail;
-              // No KDF issued, done interrupt is triggered immediately
-              void'(ral.intr_state.predict(.value(1 << int'(IntrOpDone))));
-              trigger_error(keymgr_pkg::ErrInvalidOp);
-            end
-            `uvm_info(`gfn, $sformatf("At %s, %s is issued", current_state.name, op.name), UVM_LOW)
+            case (current_state)
+              keymgr_pkg::StReset: begin
+                if (op == keymgr_pkg::OpAdvance) begin
+                  current_internal_key = {cfg.keymgr_vif.otp_key.key_share1,
+                                          cfg.keymgr_vif.otp_key.key_share0};
+                  cfg.keymgr_vif.store_internal_key(current_internal_key, current_state);
+                end else begin // !OpAdvance
+                  current_op_status = keymgr_pkg::OpDoneFail;
+                  // No KDF issued, done interrupt is triggered immediately
+                  void'(ral.intr_state.predict(.value(1 << int'(IntrOpDone))));
+                  process_error_n_alert();
+                end
+                void'(ral.intr_state.predict(.value(1 << int'(IntrOpDone))));
+              end
+              keymgr_pkg::StDisabled: begin
+                cfg.keymgr_vif.update_kdf_key(current_internal_key, current_state, .good_key(0));
+              end
+              default: begin // other than StReset and StDisabled
+                bit good_key;
+
+                // when advance from StOwnerKey, it is to StDisabled and key is random value
+                if (current_state == keymgr_pkg::StOwnerKey && op == keymgr_pkg::OpAdvance ||
+                    op == keymgr_pkg::OpDisable) begin
+                  good_key = 0;
+                end else if (current_state == keymgr_pkg::StInit) begin
+                  good_key = (get_err_code() == 0);
+                end else begin
+                  // TODO should be "good_key = (get_err_code() == 0)", but dut acts as below #4826
+                  good_key = 1;
+                end
+                // update kmac key for check
+                cfg.keymgr_vif.update_kdf_key(current_internal_key, current_state, good_key);
+              end
+            endcase
           end // start
         end // addr_phase_write
       end
@@ -358,10 +380,14 @@
         if (addr_phase_read) begin
           addr_phase_working_state = current_state;
         end else if (data_phase_read) begin
-          keymgr_pkg::keymgr_working_state_e act_state;
+          // scb can't predict when advance from stReset is done, as it's updated internally and no
+          // output to indicate that, skip checking it
+          if (current_state != keymgr_pkg::StReset || current_op_status != keymgr_pkg::OpWip) begin
+            keymgr_pkg::keymgr_working_state_e act_state;
 
-          `downcast(act_state, item.d_data)
-          `DV_CHECK_EQ(act_state, addr_phase_working_state)
+            `downcast(act_state, item.d_data)
+            `DV_CHECK_EQ(act_state, addr_phase_working_state)
+          end
         end
       end
       "op_status": begin
@@ -391,13 +417,12 @@
       default: begin
         if (!uvm_re_match("sw_share*", csr.get_name())) begin // sw_share
           // if keymgr isn't On, SW output should be entropy and not match to predict value
-          //if (data_phase_read && cfg.keymgr_vif.keymgr_en != lc_ctrl_pkg::On) begin
-          //  if (item.d_data != 0) begin
-          //    do_read_check = 1'b0;
-          //    `DV_CHECK_NE(item.d_data, `gmv(csr))
-          //  end
-          //end
-          do_read_check = 1'b0;
+          if (data_phase_read && cfg.keymgr_vif.keymgr_en != lc_ctrl_pkg::On) begin
+            if (item.d_data != 0) begin
+              do_read_check = 1'b0;
+              `DV_CHECK_NE(item.d_data, `gmv(csr))
+            end
+          end
         end else begin // Not sw_share
           // TODO
           do_read_check = 1'b0;
@@ -425,8 +450,45 @@
   endfunction
 
   // TODO, need to check alert
-  virtual function void trigger_error(keymgr_pkg::keymgr_err_pos_e err_code);
-    void'(ral.err_code.predict(.value(1 << int'(err_code))));
+  virtual function void process_error_n_alert();
+    keymgr_pkg::keymgr_ops_e op = get_operation();
+    bit [TL_DW-1:0] err = get_err_code();
+
+    void'(ral.err_code.predict(err));
+    `uvm_info(`gfn, $sformatf("%s is issued and error code is 'b%0b", op.name, err), UVM_MEDIUM)
+  endfunction
+
+  virtual function bit [TL_DW-1:0] get_err_code();
+    keymgr_pkg::keymgr_ops_e op = get_operation();
+    bit [TL_DW-1:0]          err_code;
+
+    case (current_state)
+      keymgr_pkg::StReset: begin
+        if (op != keymgr_pkg::OpAdvance) begin
+          err_code[keymgr_pkg::ErrInvalidOp] = 1;
+        end
+      end
+      keymgr_pkg::StInit: begin
+        if (!(op inside {keymgr_pkg::OpAdvance, keymgr_pkg::OpDisable})) begin
+          err_code[keymgr_pkg::ErrInvalidOp] = 1;
+        end
+      end
+      keymgr_pkg::StCreatorRootKey, keymgr_pkg::StOwnerIntKey, keymgr_pkg::StOwnerKey: begin
+        if (op inside {keymgr_pkg::OpGenSwOut, keymgr_pkg::OpGenHwOut} &&
+            get_invalid_input_error()) begin
+          err_code[keymgr_pkg::ErrInvalidIn] = 1;
+        end
+      end
+      keymgr_pkg::StDisabled: begin
+        err_code[keymgr_pkg::ErrInvalidOp] = 1;
+      end
+      default: `uvm_fatal(`gfn, $sformatf("unexpected state %s", current_state.name))
+    endcase
+    return err_code;
+  endfunction
+
+  virtual function bit get_invalid_input_error();
+    return get_current_max_version() < `gmv(ral.key_version);
   endfunction
 
   virtual function void compare_adv_creator_data(bit exp_match, const ref byte byte_data_q[$]);
@@ -519,8 +581,9 @@
     foreach (sw_data_a_array[i]) begin
       `DV_CHECK_NE(act, sw_data_a_array[i], $sformatf("SW data at state %0s", i.name))
     end
-    foreach (hw_data_a_array[i]) begin
-      `DV_CHECK_NE(act, hw_data_a_array[i], $sformatf("HW data at state %0s", i.name))
+    foreach (cfg.keymgr_vif.keys_a_array[i, j]) begin
+      `DV_CHECK_NE(act, cfg.keymgr_vif.keys_a_array[i][j],
+                   $sformatf("key at state %0s from %0s", i, j))
     end
   endfunction
 
@@ -552,8 +615,14 @@
     return op;
   endfunction
 
+  virtual function keymgr_pkg::keymgr_working_state_e get_next_state(keymgr_pkg::keymgr_working_state_e cur);
+    if (cfg.keymgr_vif.keymgr_en != lc_ctrl_pkg::On) return keymgr_pkg::StInvalid;
+    else                                             return keymgr_env_pkg::get_next_state(cur);
+  endfunction
+
   // TODO, need designer to update for #4680
   virtual function void wipe_hw_keys();
+    if (current_state != keymgr_pkg::StReset) current_state = keymgr_pkg::StInvalid;
   endfunction
 
   virtual function void reset(string kind = "HARD");
@@ -565,7 +634,7 @@
     adv_data_a_array.delete();
     id_data_a_array.delete();
     sw_data_a_array.delete();
-    hw_data_a_array.delete();
+
   endfunction
 
   function void check_phase(uvm_phase phase);
diff --git a/hw/ip/keymgr/dv/env/seq_lib/keymgr_cfgen_vseq.sv b/hw/ip/keymgr/dv/env/seq_lib/keymgr_cfgen_vseq.sv
index 0e036c2..81c5736 100644
--- a/hw/ip/keymgr/dv/env/seq_lib/keymgr_cfgen_vseq.sv
+++ b/hw/ip/keymgr/dv/env/seq_lib/keymgr_cfgen_vseq.sv
@@ -26,7 +26,7 @@
     // randomly add 0-100 cycle delay, backdoor check op_status again
     // When status is still OpWip, call write_cfgen_gated_reg and the write will be ignored
     while (!regular_vseq_done) begin
-      bit [TL_DW-1] op_status_val, cfgen_val;
+      bit [TL_DW-1:0] op_status_val, cfgen_val;
       uint delay;
 
       forever begin
@@ -57,7 +57,7 @@
   endtask
 
   virtual task write_cfgen_gated_reg();
-    bit [TL_DW-1] val = $urandom;
+    bit [TL_DW-1:0] val = $urandom;
 
     `uvm_info(`gfn, "Write cfgen gated reg, and this write should be ignored", UVM_HIGH)
     // since it's timing sensitive, only write one of these reg
diff --git a/hw/ip/keymgr/dv/env/seq_lib/keymgr_lc_disable_vseq.sv b/hw/ip/keymgr/dv/env/seq_lib/keymgr_lc_disable_vseq.sv
index c27a6e0..463937d 100644
--- a/hw/ip/keymgr/dv/env/seq_lib/keymgr_lc_disable_vseq.sv
+++ b/hw/ip/keymgr/dv/env/seq_lib/keymgr_lc_disable_vseq.sv
@@ -54,7 +54,7 @@
         cfg.clk_rst_vif.wait_clks(delay_cycles);
 
         forever begin
-          bit [TL_DW-1] op_status_val;
+          bit [TL_DW-1:0] op_status_val;
           csr_rd(ral.op_status, op_status_val);
 
           if (op_status_val == keymgr_pkg::OpWip) break;
diff --git a/hw/ip/keymgr/dv/tb.sv b/hw/ip/keymgr/dv/tb.sv
index 56c498c..a480a82 100644
--- a/hw/ip/keymgr/dv/tb.sv
+++ b/hw/ip/keymgr/dv/tb.sv
@@ -25,6 +25,10 @@
   keymgr_if keymgr_if(.clk(clk), .rst_n(rst_n));
   keymgr_kmac_intf keymgr_kmac_intf(.clk(clk), .rst_n(rst_n));
 
+  // connect KDF interface for assertion check
+  assign keymgr_if.kmac_data_req = keymgr_kmac_intf.kmac_data_req;
+  assign keymgr_if.kmac_data_rsp = keymgr_kmac_intf.kmac_data_rsp;
+
   `DV_ALERT_IF_CONNECT
 
   // edn_clk, edn_rst_n and edn_if are defined and driven in below macro