[dv/otp_ctrl] otp init failure test

This PR adds a otp_init failure test. This test will cause otp init
failure by creating init check errors. Scb is disabled in this test.

Signed-off-by: Cindy Chen <chencindy@google.com>
diff --git a/hw/ip/otp_ctrl/data/otp_ctrl_testplan.hjson b/hw/ip/otp_ctrl/data/otp_ctrl_testplan.hjson
index 6cb9c99..ba6d9c4 100644
--- a/hw/ip/otp_ctrl/data/otp_ctrl_testplan.hjson
+++ b/hw/ip/otp_ctrl/data/otp_ctrl_testplan.hjson
@@ -72,6 +72,23 @@
       tests: ["otp_ctrl_check_fail"]
     }
     {
+      name: init_fail
+      desc: '''
+            This test is based on OTP_CTRL smoke test and creats OTP_CTRL's initialization failure:
+            - Write and read OTP memory via DAI interface
+            - Lock HW partitions
+            - Keep writing to OTP memory via DAI interface
+            - Issue reset and power initialization
+
+            This test will check:
+            - Otp_initialization failure triggers fatal alert
+            - Status register reflect the correct error
+            - Otp_ctrl's power init output stays 0
+            '''
+      milestone: V2
+      tests: ["otp_ctrl_init_fail"]
+    }
+    {
       name: regwen_during_otp_init
       desc: '''
             DIRECT_ACCESS_REGWEN is RO reg and it gates bunch of write access of other registers,
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.core b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.core
index 2ca7b2a..f381319 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.core
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.core
@@ -25,6 +25,7 @@
       - seq_lib/otp_ctrl_wake_up_vseq.sv: {is_include_file: true}
       - seq_lib/otp_ctrl_smoke_vseq.sv: {is_include_file: true}
       - seq_lib/otp_ctrl_partition_walk_vseq.sv: {is_include_file: true}
+      - seq_lib/otp_ctrl_init_fail_vseq.sv: {is_include_file: true}
       - seq_lib/otp_ctrl_dai_lock_vseq.sv: {is_include_file: true}
       - seq_lib/otp_ctrl_dai_errs_vseq.sv: {is_include_file: true}
       - seq_lib/otp_ctrl_macro_errs_vseq.sv: {is_include_file: true}
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
index 95a121f..fdaf3f0 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
@@ -76,71 +76,73 @@
   // 2. After reset deasserted, if power otp_init request is on, and if testbench uses backdoor to
   //    clear OTP memory to all zeros, clear all digests and re-calculate secret partitions
   virtual task process_otp_power_up();
-    forever begin
-      @(posedge cfg.otp_ctrl_vif.pwr_otp_init_i) begin
-        if (cfg.backdoor_clear_mem && cfg.en_scb) begin
-          bit [SCRAMBLE_DATA_SIZE-1:0] data = descramble_data(0, Secret0Idx);
-          otp_a        = '{default:0};
-          otp_lc_data  = '{default:0};
-          // secret partitions have been scrambled before writing to OTP.
-          // here calculate the pre-srambled raw data when clearing internal OTP to all 0s.
-          for (int i = SECRET0_START_ADDR; i <= SECRET0_END_ADDR; i++) begin
-            otp_a[i] = ((i - SECRET0_START_ADDR) % 2) ? data[SCRAMBLE_DATA_SIZE-1:TL_DW] :
-                                                        data[TL_DW-1:0];
+    if (cfg.en_scb) begin
+      forever begin
+        @(posedge cfg.otp_ctrl_vif.pwr_otp_init_i) begin
+          if (cfg.backdoor_clear_mem) begin
+            bit [SCRAMBLE_DATA_SIZE-1:0] data = descramble_data(0, Secret0Idx);
+            otp_a        = '{default:0};
+            otp_lc_data  = '{default:0};
+            // secret partitions have been scrambled before writing to OTP.
+            // here calculate the pre-srambled raw data when clearing internal OTP to all 0s.
+            for (int i = SECRET0_START_ADDR; i <= SECRET0_END_ADDR; i++) begin
+              otp_a[i] = ((i - SECRET0_START_ADDR) % 2) ? data[SCRAMBLE_DATA_SIZE-1:TL_DW] :
+                                                          data[TL_DW-1:0];
+            end
+            data = descramble_data(0, Secret1Idx);
+            for (int i = SECRET1_START_ADDR; i <= SECRET1_END_ADDR; i++) begin
+              otp_a[i] = ((i - SECRET1_START_ADDR) % 2) ? data[SCRAMBLE_DATA_SIZE-1:TL_DW] :
+                                                          data[TL_DW-1:0];
+            end
+            data = descramble_data(0, Secret2Idx);
+            for (int i = SECRET2_START_ADDR; i <= SECRET2_END_ADDR; i++) begin
+              otp_a[i] = ((i - SECRET2_START_ADDR) % 2) ? data[SCRAMBLE_DATA_SIZE-1:TL_DW] :
+                                                          data[TL_DW-1:0];
+            end
+            predict_digest_csrs();
+            `uvm_info(`gfn, "clear internal memory and digest", UVM_HIGH)
           end
-          data = descramble_data(0, Secret1Idx);
-          for (int i = SECRET1_START_ADDR; i <= SECRET1_END_ADDR; i++) begin
-            otp_a[i] = ((i - SECRET1_START_ADDR) % 2) ? data[SCRAMBLE_DATA_SIZE-1:TL_DW] :
-                                                        data[TL_DW-1:0];
-          end
-          data = descramble_data(0, Secret2Idx);
-          for (int i = SECRET2_START_ADDR; i <= SECRET2_END_ADDR; i++) begin
-            otp_a[i] = ((i - SECRET2_START_ADDR) % 2) ? data[SCRAMBLE_DATA_SIZE-1:TL_DW] :
-                                                        data[TL_DW-1:0];
-          end
-          predict_digest_csrs();
-          `uvm_info(`gfn, "clear internal memory and digest", UVM_HIGH)
         end
-      end
-      @(posedge cfg.otp_ctrl_vif.pwr_otp_done_o || cfg.under_reset) begin
-        if (!cfg.under_reset) begin
-          otp_ctrl_part_pkg::otp_hw_cfg_data_t exp_hwcfg_data;
-          otp_ctrl_pkg::otp_keymgr_key_t       exp_keymgr_data;
-          bit [otp_ctrl_pkg::KeyMgrKeyWidth-1:0] exp_keymgr_key0, exp_keymgr_key1;
+        @(posedge cfg.otp_ctrl_vif.pwr_otp_done_o || cfg.under_reset) begin
+          if (!cfg.under_reset) begin
+            otp_ctrl_part_pkg::otp_hw_cfg_data_t exp_hwcfg_data;
+            otp_ctrl_pkg::otp_keymgr_key_t       exp_keymgr_data;
+            bit [otp_ctrl_pkg::KeyMgrKeyWidth-1:0] exp_keymgr_key0, exp_keymgr_key1;
 
-          predict_digest_csrs();
+            predict_digest_csrs();
 
-          if (cfg.otp_ctrl_vif.lc_esc_on == 0) begin
-            // Dai access is unlocked because the power init is done
-            void'(ral.direct_access_regwen.predict(1));
+            if (cfg.otp_ctrl_vif.lc_esc_on == 0) begin
+              // Dai access is unlocked because the power init is done
+              void'(ral.direct_access_regwen.predict(1));
 
-            // Dai idle is set because the otp init is done
-            exp_status[OtpDaiIdleIdx] = 1;
-          end
+              // Dai idle is set because the otp init is done
+              exp_status[OtpDaiIdleIdx] = 1;
+            end
 
-          // Hwcfg_o gets data from OTP HW cfg partition
-          exp_hwcfg_data = cfg.otp_ctrl_vif.lc_esc_on ?
-                           otp_ctrl_part_pkg::PartInvDefault[HwCfgOffset*8 +: HwCfgSize*8] :
-                           otp_hw_cfg_data_t'({<<32 {otp_a[HwCfgOffset/4 +: HwCfgSize/4]}});
-          `DV_CHECK_EQ(cfg.otp_ctrl_vif.otp_hw_cfg_o.data, exp_hwcfg_data)
+            // Hwcfg_o gets data from OTP HW cfg partition
+            exp_hwcfg_data = cfg.otp_ctrl_vif.lc_esc_on ?
+                             otp_ctrl_part_pkg::PartInvDefault[HwCfgOffset*8 +: HwCfgSize*8] :
+                             otp_hw_cfg_data_t'({<<32 {otp_a[HwCfgOffset/4 +: HwCfgSize/4]}});
+            `DV_CHECK_EQ(cfg.otp_ctrl_vif.otp_hw_cfg_o.data, exp_hwcfg_data)
 
-          // Otp_keymgr outputs creator root key shares from the secret2 partition.
-          // Depends on lc_seed_hw_rd_en_i, it will output the real keys or a constant
-          exp_keymgr_data.valid = get_otp_digest_val(Secret2Idx) != 0;
-          if (cfg.otp_ctrl_vif.lc_seed_hw_rd_en_i == lc_ctrl_pkg::On) begin
-            exp_keymgr_data.key_share0 =
-                {<<32 {otp_a[CreatorRootKeyShare0Offset/4 +: CreatorRootKeyShare0Size/4]}};
-            exp_keymgr_data.key_share1 =
-                {<<32 {otp_a[CreatorRootKeyShare1Offset/4 +: CreatorRootKeyShare1Size/4]}};
-          end else begin
-            exp_keymgr_data.key_share0 =
-                PartInvDefault[CreatorRootKeyShare0Offset*8 +: CreatorRootKeyShare0Size*8];
-            exp_keymgr_data.key_share1 =
-                PartInvDefault[CreatorRootKeyShare1Offset*8 +: CreatorRootKeyShare1Size*8];
-          end
-          // Check keymgr_key_o in otp_ctrl_if
-          if (cfg.otp_ctrl_vif.lc_esc_on == 0) begin
-            `DV_CHECK_EQ(cfg.otp_ctrl_vif.keymgr_key_o, exp_keymgr_data)
+            // Otp_keymgr outputs creator root key shares from the secret2 partition.
+            // Depends on lc_seed_hw_rd_en_i, it will output the real keys or a constant
+            exp_keymgr_data.valid = get_otp_digest_val(Secret2Idx) != 0;
+            if (cfg.otp_ctrl_vif.lc_seed_hw_rd_en_i == lc_ctrl_pkg::On) begin
+              exp_keymgr_data.key_share0 =
+                  {<<32 {otp_a[CreatorRootKeyShare0Offset/4 +: CreatorRootKeyShare0Size/4]}};
+              exp_keymgr_data.key_share1 =
+                  {<<32 {otp_a[CreatorRootKeyShare1Offset/4 +: CreatorRootKeyShare1Size/4]}};
+            end else begin
+              exp_keymgr_data.key_share0 =
+                  PartInvDefault[CreatorRootKeyShare0Offset*8 +: CreatorRootKeyShare0Size*8];
+              exp_keymgr_data.key_share1 =
+                  PartInvDefault[CreatorRootKeyShare1Offset*8 +: CreatorRootKeyShare1Size*8];
+            end
+            // Check keymgr_key_o in otp_ctrl_if
+            if (cfg.otp_ctrl_vif.lc_esc_on == 0) begin
+              `DV_CHECK_EQ(cfg.otp_ctrl_vif.keymgr_key_o, exp_keymgr_data)
+            end
           end
         end
       end
diff --git a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_init_fail_vseq.sv b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_init_fail_vseq.sv
new file mode 100644
index 0000000..8d9cdd9
--- /dev/null
+++ b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_init_fail_vseq.sv
@@ -0,0 +1,99 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// Otp_ctrl_init_fail_vseq is developed to check if OTP_CTRL reacts correctly if initialization
+// failed.
+// Note that coreboard is disabled in this test and all checks are done within sequence.
+// This test writes and reads to OTP_memory via DAI interface, then triggers digest calculations.
+// Afterwards instead of issuing reset, this sequence continues to write to DAI interface.
+// If any of the hardware partition is updated, then in next power cycle, the initialization will
+// fail.
+// This sequence will check the following items:
+// - Otp_initialization failure triggers fatal alert
+// - Status register reflect the correct error
+// - Otp_ctrl's power init output stays 0
+
+class otp_ctrl_init_fail_vseq extends otp_ctrl_smoke_vseq;
+  `uvm_object_utils(otp_ctrl_init_fail_vseq)
+
+  `uvm_object_new
+
+  rand uint         num_to_lock_digests;
+  bit [NumPart-1:0] init_err;
+
+  constraint lock_digest_c {num_to_lock_digests < num_dai_op;}
+  constraint num_iterations_c {num_dai_op inside {[20:100]};}
+
+  virtual task pre_start();
+    super.pre_start();
+    num_to_lock_digests.rand_mode(0);
+  endtask
+
+  task body();
+    for (uint i = 1; i <= num_dai_op; i++) begin
+      bit [TL_DW-1:0] tlul_val;
+
+      `DV_CHECK_RANDOMIZE_FATAL(this)
+
+      // recalculate part_idx in case some test turn off constraint dai_wr_legal_addr_c
+      part_idx = get_part_index(dai_addr);
+      `uvm_info(`gfn, $sformatf("starting dai access seq %0d/%0d with addr %0h in partition %0d %0d",
+                i, num_dai_op, dai_addr, part_idx, num_to_lock_digests), UVM_MEDIUM)
+
+      // OTP write via DAI
+      dai_wr(dai_addr, wdata0, wdata1);
+      used_dai_addr_q.push_back(dai_addr);
+
+      if (i > num_to_lock_digests && part_idx inside {[HwCfgIdx: Secret2Idx]}) begin
+        init_err[part_idx] = 1;
+      end
+
+      // OTP read via DAI, check data in scb
+      dai_rd_check(dai_addr, wdata0, wdata1);
+
+      // If write sw partitions, check tlul window
+      if (is_sw_part(dai_addr)) begin
+        uvm_reg_addr_t tlul_addr = cfg.ral.get_addr_from_offset(get_sw_window_offset(dai_addr));
+        tl_access(.addr(tlul_addr), .write(0), .data(tlul_val), .blocking(1), .check_rsp(1),
+                  .check_exp_data(1), .exp_data(wdata0));
+      end
+
+      if (i == num_to_lock_digests) cal_hw_digests('1);
+
+      csr_rd_check(.ptr(ral.status), .compare_value(1'b1 << OtpDaiIdleIdx));
+    end
+
+    do_otp_ctrl_init = 0;
+    do_otp_pwr_init = (init_err == 0) ? 1 : 0;
+    dut_init();
+
+    if (init_err) begin
+      bit [TL_DW-1:0] exp_status;
+
+      cfg.otp_ctrl_vif.drive_pwr_otp_init(1);
+      // Wait until OTP_INIT process the error
+      wait(cfg.m_alert_agent_cfg["fatal_check_error"].vif.alert_tx_final.alert_p);
+      check_fatal_alert_nonblocking("fatal_check_error");
+
+      cfg.otp_ctrl_vif.drive_pwr_otp_init(0);
+
+      // Wait until all partitions finish initialization
+      cfg.clk_rst_vif.wait_clks($urandom_range(2000, 4000));
+
+      foreach(init_err[i]) begin
+        if (init_err[i]) exp_status |= 1'b1 << i;
+      end
+      csr_rd_check(.ptr(ral.status), .compare_value(exp_status));
+
+      `DV_CHECK_EQ(cfg.otp_ctrl_vif.pwr_otp_done_o, 0)
+      `DV_CHECK_EQ(cfg.otp_ctrl_vif.pwr_otp_idle_o, 0)
+
+      // Issue reset to stop fatal alert
+      dut_init();
+
+    end else begin
+      csr_rd_check(.ptr(ral.status), .compare_value(1'b1 << OtpDaiIdleIdx));
+    end
+  endtask
+endclass
diff --git a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_vseq_list.sv b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_vseq_list.sv
index 9e466da..cdd70f7 100644
--- a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_vseq_list.sv
+++ b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_vseq_list.sv
@@ -7,6 +7,7 @@
 `include "otp_ctrl_smoke_vseq.sv"
 `include "otp_ctrl_common_vseq.sv"
 `include "otp_ctrl_partition_walk_vseq.sv"
+`include "otp_ctrl_init_fail_vseq.sv"
 `include "otp_ctrl_dai_lock_vseq.sv"
 `include "otp_ctrl_dai_errs_vseq.sv"
 `include "otp_ctrl_macro_errs_vseq.sv"
diff --git a/hw/ip/otp_ctrl/dv/otp_ctrl_sim_cfg.hjson b/hw/ip/otp_ctrl/dv/otp_ctrl_sim_cfg.hjson
index b86fe5b..b1c97d7 100644
--- a/hw/ip/otp_ctrl/dv/otp_ctrl_sim_cfg.hjson
+++ b/hw/ip/otp_ctrl/dv/otp_ctrl_sim_cfg.hjson
@@ -66,6 +66,12 @@
     }
 
     {
+      name: otp_ctrl_init_fail
+      uvm_test_seq: otp_ctrl_init_fail_vseq
+      run_opts: ["+en_scb=0"]
+    }
+
+    {
       name: otp_ctrl_parallel_lc_req
       uvm_test_seq: otp_ctrl_parallel_lc_req_vseq
     }