[dv/chip] Lc_walkthrough test part1

This PR adds one walkthrough test from RAW to ProdEnd state.
Upcoming PRs will walkthrough other paths of LC state transitions.

Signed-off-by: Cindy Chen <chencindy@opentitan.org>
diff --git a/hw/top_earlgrey/data/chip_testplan.hjson b/hw/top_earlgrey/data/chip_testplan.hjson
index 546a777..9df2022 100644
--- a/hw/top_earlgrey/data/chip_testplan.hjson
+++ b/hw/top_earlgrey/data/chip_testplan.hjson
@@ -2744,7 +2744,7 @@
              Verify that the features that should indeed be disabled are indeed disabled.
              '''
       milestone: V2
-      tests: []
+      tests: ["chip_sw_lc_walkthrough"]
     }
     {
       name: chip_sw_device_ownership
diff --git a/hw/top_earlgrey/dv/chip_sim_cfg.hjson b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
index 26ee36d..2e9c17b 100644
--- a/hw/top_earlgrey/dv/chip_sim_cfg.hjson
+++ b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
@@ -410,6 +410,14 @@
       reseed: 15
     }
     {
+      name: chip_sw_lc_walkthrough
+      uvm_test_seq: chip_sw_lc_walkthrough_vseq
+      sw_images: ["sw/device/tests/lc_walkthrough_test:1"]
+      en_run_modes: ["sw_test_mode_test_rom"]
+      run_opts: ["+use_otp_image=LcStRaw"]
+      reseed: 1
+    }
+    {
       name: chip_sw_rstmgr_sw_req
       uvm_test_seq: chip_sw_base_vseq
       sw_images: ["sw/device/tests/rstmgr_sw_req_test:1"]
diff --git a/hw/top_earlgrey/dv/env/chip_env.core b/hw/top_earlgrey/dv/env/chip_env.core
index 1709d5a..8b5d5f3 100644
--- a/hw/top_earlgrey/dv/env/chip_env.core
+++ b/hw/top_earlgrey/dv/env/chip_env.core
@@ -51,6 +51,7 @@
       - seq_lib/chip_sw_flash_ctrl_lc_rw_en_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_flash_rma_unlocked_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_lc_ctrl_transition_vseq.sv: {is_include_file: true}
+      - seq_lib/chip_sw_lc_walkthrough_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_spi_tx_rx_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_rom_ctrl_integrity_check_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_sram_ctrl_execution_main_vseq.sv: {is_include_file: true}
diff --git a/hw/top_earlgrey/dv/env/seq_lib/chip_sw_lc_walkthrough_vseq.sv b/hw/top_earlgrey/dv/env/seq_lib/chip_sw_lc_walkthrough_vseq.sv
new file mode 100644
index 0000000..f0f7d3a
--- /dev/null
+++ b/hw/top_earlgrey/dv/env/seq_lib/chip_sw_lc_walkthrough_vseq.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
+
+class chip_sw_lc_walkthrough_vseq extends chip_sw_base_vseq;
+  `uvm_object_utils(chip_sw_lc_walkthrough_vseq)
+
+  `uvm_object_new
+
+  // LC sends two 64-bit msg as input token.
+  localparam uint TokenWidthBit  = kmac_pkg::MsgWidth * 2;
+  localparam uint TokenWidthByte = TokenWidthBit / 8;
+
+  rand bit [7:0] lc_exit_token[TokenWidthByte];
+  rand bit [7:0] lc_unlock_token[TokenWidthByte];
+  bit [7:0] otp_exit_token[TokenWidthByte];
+  bit [7:0] otp_unlock_token[TokenWidthByte];
+
+  // Reassign `select_jtag` variable to drive LC JTAG tap and disable mubi assertion errors.
+  virtual task pre_start();
+    select_jtag = SelectLCJtagTap;
+    otp_raw_img_mubi_assertion_ctrl(.enable(0));
+    super.pre_start();
+  endtask
+
+  virtual task body();
+    bit [TokenWidthBit-1:0] otp_exit_token_bits, otp_unlock_token_bits;
+    super.body();
+
+    otp_exit_token_bits = dec_otp_token_from_lc_csrs(lc_exit_token);
+    otp_unlock_token_bits = dec_otp_token_from_lc_csrs(lc_unlock_token);
+
+    otp_unlock_token = {<< 8{otp_unlock_token_bits}};
+    otp_exit_token = {<< 8{otp_exit_token_bits}};
+
+    `uvm_info(`gfn, $sformatf("OTP unlock token %0h and OTP exit token %0h",
+              otp_unlock_token_bits, otp_exit_token_bits), UVM_LOW)
+
+    // Override the C test tokens with random data.
+    sw_symbol_backdoor_overwrite("kLcExitToken", lc_exit_token);
+    sw_symbol_backdoor_overwrite("kOtpExitToken", otp_exit_token);
+    sw_symbol_backdoor_overwrite("kOtpUnlockToken", otp_unlock_token);
+
+    wait_lc_ready(1);
+    jtag_lc_state_transition(DecLcStRaw, DecLcStTestUnlocked0);
+    apply_reset();
+
+    wait (cfg.sw_logger_vif.printed_log == "Written and locked OTP secret0 partition!");
+    apply_reset();
+
+    wait (cfg.sw_logger_vif.printed_log == "Waiting for LC transtition done and reboot.");
+    wait_lc_status(LcTransitionSuccessful);
+    apply_reset();
+  endtask
+
+endclass
diff --git a/hw/top_earlgrey/dv/env/seq_lib/chip_vseq_list.sv b/hw/top_earlgrey/dv/env/seq_lib/chip_vseq_list.sv
index 6de71fa..2597f49 100644
--- a/hw/top_earlgrey/dv/env/seq_lib/chip_vseq_list.sv
+++ b/hw/top_earlgrey/dv/env/seq_lib/chip_vseq_list.sv
@@ -19,6 +19,7 @@
 `include "chip_sw_flash_ctrl_lc_rw_en_vseq.sv"
 `include "chip_sw_flash_rma_unlocked_vseq.sv"
 `include "chip_sw_lc_ctrl_transition_vseq.sv"
+`include "chip_sw_lc_walkthrough_vseq.sv"
 `include "chip_sw_spi_tx_rx_vseq.sv"
 `include "chip_sw_rom_ctrl_integrity_check_vseq.sv"
 `include "chip_sw_sram_ctrl_execution_main_vseq.sv"
diff --git a/sw/device/tests/sim_dv/BUILD b/sw/device/tests/sim_dv/BUILD
index 407312e..077558a 100644
--- a/sw/device/tests/sim_dv/BUILD
+++ b/sw/device/tests/sim_dv/BUILD
@@ -129,6 +129,24 @@
 )
 
 opentitan_functest(
+    name = "lc_walkthrough_test",
+    srcs = ["lc_walkthrough_test.c"],
+    targets = ["dv"],
+    deps = [
+        "//hw/top_earlgrey/sw/autogen:top_earlgrey",
+        "//sw/device/lib/base:memory",
+        "//sw/device/lib/base:mmio",
+        "//sw/device/lib/dif:lc_ctrl",
+        "//sw/device/lib/dif:otp_ctrl",
+        "//sw/device/lib/runtime:log",
+        "//sw/device/lib/testing:lc_ctrl_testutils",
+        "//sw/device/lib/testing:otp_ctrl_testutils",
+        "//sw/device/lib/testing/test_framework:check",
+        "//sw/device/lib/testing/test_framework:ottf_main",
+    ],
+)
+
+opentitan_functest(
     name = "clkmgr_external_clk_src_for_lc_test",
     srcs = ["clkmgr_external_clk_src_for_lc_test.c"],
     targets = ["dv"],
diff --git a/sw/device/tests/sim_dv/lc_walkthrough_test.c b/sw/device/tests/sim_dv/lc_walkthrough_test.c
new file mode 100644
index 0000000..f462a97
--- /dev/null
+++ b/sw/device/tests/sim_dv/lc_walkthrough_test.c
@@ -0,0 +1,166 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/base/memory.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/dif/dif_lc_ctrl.h"
+#include "sw/device/lib/dif/dif_otp_ctrl.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/lc_ctrl_testutils.h"
+#include "sw/device/lib/testing/otp_ctrl_testutils.h"
+#include "sw/device/lib/testing/test_framework/check.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+
+#define LC_TOKEN_SIZE 16
+
+static dif_lc_ctrl_t lc;
+static dif_otp_ctrl_t otp;
+
+/**
+ * Track LC state transition tokens.
+ *
+ * These tokens will be further randomized and overridden by the testbench.
+ */
+
+// LC exit token value for LC state transition.
+static volatile const uint8_t kLcExitToken[LC_TOKEN_SIZE] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+};
+
+// LC exit token value in OTP secret0 partition.
+static volatile const uint8_t kOtpExitToken[LC_TOKEN_SIZE] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+};
+
+// LC unlock token value in OTP secret0 partition.
+static volatile const uint8_t kOtpUnlockToken[LC_TOKEN_SIZE] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+};
+
+static void check_lc_state_transition_count(uint8_t exp_lc_count) {
+  LOG_INFO("Read LC count and check with expect_val=%0d", exp_lc_count);
+  uint8_t lc_count;
+  CHECK_DIF_OK(dif_lc_ctrl_get_attempts(&lc, &lc_count),
+               "Read lc_count register failed!");
+  CHECK(lc_count == exp_lc_count,
+        "LC_count error, expected %0d but actual count is %0d", exp_lc_count,
+        lc_count);
+}
+
+/**
+ * Walkthrough LC state: RAW -> TestUnlock0 -> ProdEnd.
+ *
+ * 1). Preload OTP RAW image file.
+ * 2). DV sequence drives JTAG interface to write RawUnlockToken and
+ * transfers LC state to TestUnlock0 state.
+ * 3). In TestUnlock0 state, SW programs OTP secret0 partition to write
+ * ExitToken and TestUnlockToken.
+ * 4). DV sequence issues reset to lock OTP secret0 partition.
+ * 5). SW requests a LC state transfer to ProdEnd state with the correct
+ * TestLockToken.
+ * 6). DV sequence issues reset and SW check if LC transfers to ProdEnd state.
+ */
+
+bool test_main(void) {
+  LOG_INFO("Start LC walkthrough ProdEnd test.");
+
+  mmio_region_t lc_reg = mmio_region_from_addr(TOP_EARLGREY_LC_CTRL_BASE_ADDR);
+  CHECK_DIF_OK(dif_lc_ctrl_init(lc_reg, &lc));
+
+  mmio_region_t otp_reg =
+      mmio_region_from_addr(TOP_EARLGREY_OTP_CTRL_CORE_BASE_ADDR);
+  CHECK_DIF_OK(dif_otp_ctrl_init(otp_reg, &otp));
+
+  LOG_INFO("Read and check LC state and count.");
+  dif_lc_ctrl_state_t curr_state;
+  CHECK_DIF_OK(dif_lc_ctrl_get_state(&lc, &curr_state));
+
+  if (curr_state == kDifLcCtrlStateTestUnlocked0) {
+    check_lc_state_transition_count(1);
+    bool secret0_locked;
+    CHECK_DIF_OK(dif_otp_ctrl_is_digest_computed(
+        &otp, kDifOtpCtrlPartitionSecret0, &secret0_locked));
+
+    if (secret0_locked == false) {
+      // Write LC tokens to OTP secret0 partition.
+      uint64_t otp_unlock_token_0 = 0;
+      uint64_t otp_unlock_token_1 = 0;
+      for (int i = 0; i < LC_TOKEN_SIZE; i++) {
+        if (i < LC_TOKEN_SIZE / 2) {
+          otp_unlock_token_0 =
+              otp_unlock_token_0 | ((uint64_t)kOtpUnlockToken[i] << (i * 8));
+        } else {
+          otp_unlock_token_1 =
+              otp_unlock_token_1 |
+              ((uint64_t)kOtpUnlockToken[i] << ((i - LC_TOKEN_SIZE / 2) * 8));
+        }
+      }
+
+      uint64_t otp_exit_token_0 = 0;
+      uint64_t otp_exit_token_1 = 0;
+      for (int i = 0; i < LC_TOKEN_SIZE; i++) {
+        if (i < LC_TOKEN_SIZE / 2) {
+          otp_exit_token_0 =
+              otp_exit_token_0 | ((uint64_t)kOtpExitToken[i] << (i * 8));
+        } else {
+          otp_exit_token_1 =
+              otp_exit_token_1 |
+              ((uint64_t)kOtpExitToken[i] << ((i - LC_TOKEN_SIZE / 2) * 8));
+        }
+      }
+
+      CHECK_DIF_OK(dif_otp_ctrl_dai_program64(&otp, kDifOtpCtrlPartitionSecret0,
+                                              /*address=*/0x0,
+                                              /*value=*/otp_unlock_token_0));
+      otp_ctrl_testutils_wait_for_dai(&otp);
+
+      CHECK_DIF_OK(dif_otp_ctrl_dai_program64(&otp, kDifOtpCtrlPartitionSecret0,
+                                              /*address=*/0x8,
+                                              /*value=*/otp_unlock_token_1));
+      otp_ctrl_testutils_wait_for_dai(&otp);
+
+      CHECK_DIF_OK(dif_otp_ctrl_dai_program64(&otp, kDifOtpCtrlPartitionSecret0,
+                                              /*address=*/0x10,
+                                              /*value=*/otp_exit_token_0));
+      otp_ctrl_testutils_wait_for_dai(&otp);
+
+      CHECK_DIF_OK(dif_otp_ctrl_dai_program64(&otp, kDifOtpCtrlPartitionSecret0,
+                                              /*address=*/0x18,
+                                              /*value=*/otp_exit_token_1));
+      otp_ctrl_testutils_wait_for_dai(&otp);
+
+      CHECK_DIF_OK(dif_otp_ctrl_dai_digest(&otp, kDifOtpCtrlPartitionSecret0,
+                                           /*digest=*/0));
+      otp_ctrl_testutils_wait_for_dai(&otp);
+
+      LOG_INFO("Written and locked OTP secret0 partition!");
+      return true;
+    } else {
+      // Issue a LC state transfer to ProdEnd state.
+      dif_lc_ctrl_token_t token;
+      for (int i = 0; i < LC_TOKEN_SIZE; i++) {
+        token.data[i] = kLcExitToken[i];
+      }
+      CHECK_DIF_OK(dif_lc_ctrl_mutex_try_acquire(&lc));
+      dif_lc_ctrl_settings_t settings;
+      // TODO: randomize using external or internal clock.
+      settings.clock_select = kDifLcCtrlExternalClockEn;
+      CHECK_DIF_OK(dif_lc_ctrl_transition(&lc, kDifLcCtrlStateProdEnd, &token,
+                                          &settings),
+                   "LC_transition failed!");
+
+      LOG_INFO("Waiting for LC transtition done and reboot.");
+      return true;
+    }
+  } else {
+    // Check LC enters ProdEnd state.
+    CHECK(curr_state == kDifLcCtrlStateProdEnd);
+    check_lc_state_transition_count(2);
+    return true;
+  }
+}
diff --git a/sw/device/tests/sim_dv/meson.build b/sw/device/tests/sim_dv/meson.build
index a1cc74f..9e90694 100644
--- a/sw/device/tests/sim_dv/meson.build
+++ b/sw/device/tests/sim_dv/meson.build
@@ -156,6 +156,30 @@
   }
 }
 
+lc_walkthrough_test_lib = declare_dependency(
+  link_with: static_library(
+    'lc_walkthrough_test_lib',
+    sources: [
+      'lc_walkthrough_test.c',
+    ],
+    dependencies: [
+      sw_lib_dif_lc_ctrl,
+      sw_lib_dif_otp_ctrl,
+      sw_lib_mmio,
+      sw_lib_runtime_hart,
+      sw_lib_runtime_log,
+      sw_lib_testing_otp_ctrl_testutils,
+      sw_lib_testing_lc_ctrl_testutils,
+      top_earlgrey,
+    ],
+  ),
+)
+sw_tests += {
+  'lc_walkthrough_test': {
+    'library': lc_walkthrough_test_lib,
+  }
+}
+
 pwrmgr_main_power_glitch_test_lib = declare_dependency(
   link_with: static_library(
     'pwrmgr_main_power_glitch_test_lib',