[dv/chip] otp_ctrl_vendor_test_csr_access test

This PR implements the V3 chip level test
`otp_ctrl_vendor_test_csr_access` according to issue #17417.

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 108e65e..36d75f7 100644
--- a/hw/top_earlgrey/data/chip_testplan.hjson
+++ b/hw/top_earlgrey/data/chip_testplan.hjson
@@ -2845,11 +2845,13 @@
             - Boot the chip successively in raw, test_*, dev, prod and rma LC states.
             - Verify that the SW is able to access the vendor test control and status registers in
               raw, test_* and rma LC states.
+              In open source environment, this check is implemented by probing the OTP_CTRL's
+              `lc_otp_vendor_test_i` port.
             - Verify that in dev / prod LC states, the vendor status always reads back 0s regardless
               of what is programmed into the vendor test control register.
             '''
       stage: V3
-      tests: []
+      tests: ["chip_sw_otp_ctrl_vendor_test_csr_access"]
     }
 
     // FLASH (pre-verified IP) integration tests:
diff --git a/hw/top_earlgrey/dv/chip_sim_cfg.hjson b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
index 66ee722..9f460b2 100644
--- a/hw/top_earlgrey/dv/chip_sim_cfg.hjson
+++ b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
@@ -739,6 +739,12 @@
       run_opts: ["+use_otp_image=OtpTypeLcStRma", "+otp_clear_secret2=1"]
     }
     {
+      name: chip_sw_otp_ctrl_vendor_test_csr_access
+      uvm_test_seq: chip_sw_otp_ctrl_vendor_test_csr_access_vseq
+      sw_images: ["//sw/device/tests/sim_dv:otp_ctrl_vendor_test_csr_access_test:1"]
+      en_run_modes: ["sw_test_mode_test_rom"]
+    }
+    {
       // Set higher reseed value to reach all kmac_data to lc_ctrl toggle coverage.
       name: chip_sw_lc_ctrl_transition
       uvm_test_seq: chip_sw_lc_ctrl_transition_vseq
diff --git a/hw/top_earlgrey/dv/env/chip_env.core b/hw/top_earlgrey/dv/env/chip_env.core
index 7c8dd18..69e4012 100644
--- a/hw/top_earlgrey/dv/env/chip_env.core
+++ b/hw/top_earlgrey/dv/env/chip_env.core
@@ -95,6 +95,7 @@
       - seq_lib/chip_sw_lc_walkthrough_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_lc_walkthrough_testunlocks_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_lc_ctrl_program_error_vseq.sv: {is_include_file: true}
+      - seq_lib/chip_sw_otp_ctrl_vendor_test_csr_access_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_spi_device_tx_rx_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_spi_host_tx_rx_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_spi_passthrough_collision_vseq.sv: {is_include_file: true}
diff --git a/hw/top_earlgrey/dv/env/chip_if.sv b/hw/top_earlgrey/dv/env/chip_if.sv
index 2cf0780..729141a 100644
--- a/hw/top_earlgrey/dv/env/chip_if.sv
+++ b/hw/top_earlgrey/dv/env/chip_if.sv
@@ -976,6 +976,10 @@
   `DV_CREATE_SIGNAL_PROBE_FUNCTION(signal_probe_pinmux_periph_to_dio_oe_i,
       `PINMUX_HIER.periph_to_dio_oe_i)
 
+  // Signal probe function for `vendor_test_ctrl` request from LC_CTRL to OTP_CTRL.
+  `DV_CREATE_SIGNAL_PROBE_FUNCTION(signal_probe_otp_vendor_test_ctrl,
+      `OTP_CTRL_HIER.lc_otp_vendor_test_i)
+
 `undef TOP_HIER
 `undef ADC_CTRL_HIER
 `undef AES_HIER
diff --git a/hw/top_earlgrey/dv/env/seq_lib/chip_sw_otp_ctrl_vendor_test_csr_access_vseq.sv b/hw/top_earlgrey/dv/env/seq_lib/chip_sw_otp_ctrl_vendor_test_csr_access_vseq.sv
new file mode 100644
index 0000000..5cf36fe
--- /dev/null
+++ b/hw/top_earlgrey/dv/env/seq_lib/chip_sw_otp_ctrl_vendor_test_csr_access_vseq.sv
@@ -0,0 +1,87 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// This open source vendor_test partition status will always return 0 because the generic_otp ties
+// the status to 0.
+// For close source testing, it is recommended to expand this sequence to check
+// otp_vendor_test_status register value under different LC states.
+class chip_sw_otp_ctrl_vendor_test_csr_access_vseq extends chip_sw_base_vseq;
+  `uvm_object_utils(chip_sw_otp_ctrl_vendor_test_csr_access_vseq)
+
+  `uvm_object_new
+
+  rand lc_state_e      lc_state;
+  rand bit [TL_DW-1:0] w_data;
+  bit [TL_DW-1:0] otp_vendor_test_status;
+
+  constraint lc_state_c {
+    lc_state dist {
+        LcStRaw :/ 1,
+        [LcStTestUnlocked0 : LcStTestUnlocked7] :/ 4,
+        LcStDev :/ 1,
+        LcStProd :/ 1,
+        LcStProdEnd :/ 1,
+        LcStRma :/ 1
+    };
+  }
+
+  virtual task pre_start();
+    // Select lc jtag
+    cfg.chip_vif.tap_straps_if.drive(JtagTapLc);
+    super.pre_start();
+  endtask
+
+  virtual function void backdoor_override_otp();
+    cfg.mem_bkdr_util_h[Otp].otp_write_lc_partition_state(lc_state);
+  endfunction
+
+  virtual task dut_init(string reset_kind = "HARD");
+    super.dut_init(reset_kind);
+    backdoor_override_otp();
+  endtask
+
+  virtual task body();
+    super.body();
+
+    wait_lc_ready(.allow_err(1));
+
+    // Claim the mux interface via JTAG.
+    jtag_riscv_agent_pkg::jtag_write_csr(ral.lc_ctrl.claim_transition_if.get_offset(),
+                                         p_sequencer.jtag_sequencer_h,
+                                         prim_mubi_pkg::MuBi8True);
+
+    // Write random value to the otp_vendor_test_ctrl register.
+    jtag_riscv_agent_pkg::jtag_write_csr(ral.lc_ctrl.otp_vendor_test_ctrl.get_offset(),
+                                         p_sequencer.jtag_sequencer_h, w_data);
+
+    jtag_riscv_agent_pkg::jtag_read_csr(ral.lc_ctrl.otp_vendor_test_status.get_offset(),
+                                        p_sequencer.jtag_sequencer_h, otp_vendor_test_status);
+
+    check_otp_vendor_test_status();
+  endtask
+
+  virtual task check_otp_vendor_test_status();
+    logic [TL_DW-1:0] vendor_test_ctrl =
+                      cfg.chip_vif.signal_probe_otp_vendor_test_ctrl(SignalProbeSample);
+
+    // In open source prim_otp module, the vendor_test_status output is tied to 0.
+    `DV_CHECK_EQ(otp_vendor_test_status, 0)
+
+    // Probe vendor_test_ctrl from OTP_CTRL port to ensure that in certain lc states, the
+    // vendor_test_req is not gated.
+    if (lc_state inside {LcStRaw, [LcStTestUnlocked0 : LcStTestUnlocked7], LcStRma}) begin
+      `DV_CHECK_EQ(vendor_test_ctrl, w_data);
+    end else begin
+      `DV_CHECK_EQ(vendor_test_ctrl, 0);
+    end
+  endtask
+
+  task post_start();
+    // Some LC state does not enable CPU so sw cannot return a pass status.
+    override_test_status_and_finish(.passed(1));
+
+    super.post_start();
+  endtask : post_start
+
+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 5e5bd42..07ff42f 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
@@ -41,6 +41,7 @@
 `include "chip_sw_lc_ctrl_scrap_vseq.sv"
 `include "chip_sw_lc_walkthrough_vseq.sv"
 `include "chip_sw_lc_walkthrough_testunlocks_vseq.sv"
+`include "chip_sw_otp_ctrl_vendor_test_csr_access_vseq.sv"
 `include "chip_sw_spi_device_tx_rx_vseq.sv"
 `include "chip_sw_spi_host_tx_rx_vseq.sv"
 `include "chip_sw_spi_passthrough_vseq.sv"
diff --git a/sw/device/tests/sim_dv/BUILD b/sw/device/tests/sim_dv/BUILD
index f9bb7ce..2d5ee1e 100644
--- a/sw/device/tests/sim_dv/BUILD
+++ b/sw/device/tests/sim_dv/BUILD
@@ -288,6 +288,23 @@
     ],
 )
 
+opentitan_functest(
+    name = "otp_ctrl_vendor_test_csr_access_test",
+    srcs = ["otp_ctrl_vendor_test_csr_access_test.c"],
+    targets = ["dv"],
+    deps = [
+        "//hw/top_earlgrey/sw/autogen:top_earlgrey",
+        "//sw/device/lib/base:bitfield",
+        "//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/test_framework:check",
+        "//sw/device/lib/testing/test_framework:ottf_main",
+    ],
+)
+
 cc_library(
     name = "lc_ctrl_transition_impl",
     srcs = ["lc_ctrl_transition_impl.c"],
diff --git a/sw/device/tests/sim_dv/otp_ctrl_vendor_test_csr_access_test.c b/sw/device/tests/sim_dv/otp_ctrl_vendor_test_csr_access_test.c
new file mode 100644
index 0000000..0d8c198
--- /dev/null
+++ b/sw/device/tests/sim_dv/otp_ctrl_vendor_test_csr_access_test.c
@@ -0,0 +1,45 @@
+// 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/test_framework/check.h"
+#include "sw/device/lib/testing/test_framework/ottf_test_config.h"
+#include "sw/device/silicon_creator/lib/base/chip.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+
+static dif_lc_ctrl_t lc;
+static dif_otp_ctrl_t otp;
+
+OTTF_DEFINE_TEST_CONFIG();
+
+static void init_peripherals(void) {
+  dif_otp_ctrl_config_t config = {
+      .check_timeout = 100000,
+      .integrity_period_mask = 0x3ffff,
+      .consistency_period_mask = 0x3ffffff,
+  };
+  // Life cycle
+  CHECK_DIF_OK(dif_lc_ctrl_init(
+      mmio_region_from_addr(TOP_EARLGREY_LC_CTRL_BASE_ADDR), &lc));
+  // OTP
+  CHECK_DIF_OK(dif_otp_ctrl_init(
+      mmio_region_from_addr(TOP_EARLGREY_OTP_CTRL_CORE_BASE_ADDR), &otp));
+  CHECK_DIF_OK(dif_otp_ctrl_configure(&otp, config));
+}
+
+/**
+ * A simple SW test to enable OTP_CTRL and LC_CTRL.
+ * The main sequence is driven by JTAG agent in SV sequence
+ * `chip_sw_otp_ctrl_vendor_test_csr_access_vseq.sv`.
+ */
+bool test_main(void) {
+  dif_lc_ctrl_state_t state;
+  init_peripherals();
+  return true;
+}