[dv/otp_ctrl] initial commit for OTP sanity sequence

This PR supports OTP sanity sequence. This PR covers all the sequence
for DAI interface.
There will be a following PR that supports exercising all interfaces
within OTP.

Signed-off-by: Cindy Chen <chencindy@google.com>
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv
index 4277f77..7cb1f80 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv
@@ -17,6 +17,10 @@
     if (!uvm_config_db#(pwr_otp_vif)::get(this, "", "pwr_otp_vif", cfg.pwr_otp_vif)) begin
       `uvm_fatal(get_full_name(), "failed to get pwr_otp_vif from uvm_config_db")
     end
+    if (!uvm_config_db#(lc_provision_en_vif)::get(this, "", "lc_provision_en_vif",
+                                                  cfg.lc_provision_en_vif)) begin
+      `uvm_fatal(get_full_name(), "failed to get lc_provision_en_vif from uvm_config_db")
+    end
     if (!uvm_config_db#(mem_bkdr_vif)::get(this, "", "mem_bkdr_vif", cfg.mem_bkdr_vif)) begin
       `uvm_fatal(`gfn, "failed to get mem_bkdr_vif from uvm_config_db")
     end
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv
index 04502fd..e237d4d 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv
@@ -5,9 +5,9 @@
 class otp_ctrl_env_cfg extends cip_base_env_cfg #(.RAL_T(otp_ctrl_reg_block));
 
   // ext component cfgs
-  //pwr_vif pwr_vif;
-  pwr_otp_vif  pwr_otp_vif;
-  mem_bkdr_vif mem_bkdr_vif;
+  pwr_otp_vif         pwr_otp_vif;
+  lc_provision_en_vif lc_provision_en_vif;
+  mem_bkdr_vif        mem_bkdr_vif;
 
   `uvm_object_utils_begin(otp_ctrl_env_cfg)
   `uvm_object_utils_end
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv
index e8245ef..b1fa13d 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv
@@ -12,23 +12,72 @@
   import cip_base_pkg::*;
   import csr_utils_pkg::*;
   import otp_ctrl_ral_pkg::*;
+  import otp_ctrl_pkg::*;
+  import lc_ctrl_pkg::*;
 
   // macro includes
   `include "uvm_macros.svh"
   `include "dv_macros.svh"
 
   // parameters
+  parameter uint DIGEST_SIZE              = 8;
+  parameter uint CREATOR_WINDOW_BASE_ADDR = 'h1000;
+  parameter uint OWNER_WINDOW_BASE_ADDR   = 'h1300;
+  parameter uint TEST_ACCESS_BASE_ADDR    = 'h2000;
+  parameter uint WINDOW_SIZE              = 512 * 4;
+
+  // lc does not have digest
+  parameter bit[10:0] DIGESTS_ADDR [NumPart-1] = {
+      PartInfo[CreatorSwCfgIdx].offset + PartInfo[CreatorSwCfgIdx].size - DIGEST_SIZE,
+      PartInfo[OwnerSwCfgIdx].offset   + PartInfo[OwnerSwCfgIdx].size   - DIGEST_SIZE,
+      PartInfo[HwCfgIdx].offset        + PartInfo[HwCfgIdx].size        - DIGEST_SIZE,
+      PartInfo[Secret0Idx].offset      + PartInfo[Secret0Idx].size      - DIGEST_SIZE,
+      PartInfo[Secret1Idx].offset      + PartInfo[Secret1Idx].size      - DIGEST_SIZE,
+      PartInfo[Secret2Idx].offset      + PartInfo[Secret2Idx].size      - DIGEST_SIZE};
 
   // types
   typedef virtual pins_if #(3) pwr_otp_vif;
+  typedef virtual pins_if #(4) lc_provision_en_vif;
   typedef virtual mem_bkdr_if mem_bkdr_vif;
 
-  typedef enum {
+  typedef enum bit [1:0] {
     OtpOperationDone,
-    OtpErr
+    OtpErr,
+    NumOtpCtrlIntr
   } otp_intr_e;
 
+  // TODO: needs update once design finalize
+  typedef enum bit [6:0] {
+    OtpCheckPending = 7'b0_000_001,
+    OtpIdle         = 7'b0_000_010,
+    OtpKeyFsmErr    = 7'b0_000_100,
+    OtpScrmblFsmErr = 7'b0_001_000,
+    OtpLfsrFsmErr   = 7'b0_010_000,
+    OtpChkTimeout   = 7'b0_100_000,
+    OptPartErrs     = 7'b1_000_000
+  } otp_status_e;
+
   // functions
+  function automatic int get_part_index(bit [TL_DW-1:0] addr);
+    int index;
+    for (index = 0; index < otp_ctrl_pkg::NumPart; index++) begin
+      if (PartInfo[index].offset > addr) begin
+        index--;
+        break;
+      end
+    end
+    return index;
+  endfunction
+
+  function automatic bit is_secret(bit [TL_DW-1:0] addr);
+    int part_index = get_part_index(addr);
+    if (part_index inside {[Secret0Idx:Secret2Idx]}) return 0;
+    else return 1;
+  endfunction
+
+  function automatic bit [TL_AW-1:0] get_sw_window_addr(bit [TL_AW-1:0] dai_addr);
+    get_sw_window_addr = dai_addr + CREATOR_WINDOW_BASE_ADDR;
+  endfunction
 
   // package sources
   `include "otp_ctrl_env_cfg.sv"
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 344d64d..7f635b0 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
@@ -33,10 +33,11 @@
 
   virtual task process_tl_access(tl_seq_item item, tl_channels_e channel = DataChannel);
     uvm_reg csr;
-    bit     do_read_check   = 1'b1;
-    bit     write           = item.is_write();
-    uvm_reg_addr_t csr_addr = get_normalized_addr(item.a_addr);
-
+    bit     do_read_check     = 1'b0;
+    bit     write             = item.is_write();
+    uvm_reg_addr_t csr_addr   = get_normalized_addr(item.a_addr);
+    bit [TL_AW-1:0] addr_mask = cfg.csr_addr_map_size - 1;
+ 
     bit addr_phase_read   = (!write && channel == AddrChannel);
     bit addr_phase_write  = (write && channel == AddrChannel);
     bit data_phase_read   = (!write && channel == DataChannel);
@@ -46,8 +47,13 @@
     if (csr_addr inside {cfg.csr_addrs}) begin
       csr = ral.default_map.get_reg_by_offset(csr_addr);
       `DV_CHECK_NE_FATAL(csr, null)
-    end
-    else begin
+    // memories
+    // TODO: memory read check, change hardcoded to parameters once design finalized
+    end else if ((csr_addr & addr_mask) inside
+        {[CREATOR_WINDOW_BASE_ADDR : CREATOR_WINDOW_BASE_ADDR + WINDOW_SIZE],
+         [TEST_ACCESS_BASE_ADDR : TEST_ACCESS_BASE_ADDR + WINDOW_SIZE]}) begin
+      return;
+    end else begin
       `uvm_fatal(`gfn, $sformatf("Access unexpected addr 0x%0h", csr_addr))
     end
 
@@ -72,7 +78,7 @@
         // FIXME
       end
       default: begin
-        `uvm_fatal(`gfn, $sformatf("invalid csr: %0s", csr.get_full_name()))
+        //`uvm_fatal(`gfn, $sformatf("invalid csr: %0s", csr.get_full_name()))
       end
     endcase
 
diff --git a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv
index d543184..bee784a 100644
--- a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv
+++ b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv
@@ -13,10 +13,15 @@
   // various knobs to enable certain routines
   bit do_otp_ctrl_init = 1'b1;
 
+  rand bit [NumOtpCtrlIntr-1:0] en_intr;
+
   `uvm_object_new
 
   virtual task dut_init(string reset_kind = "HARD");
-    super.dut_init();
+    super.dut_init(reset_kind);
+    cfg.pwr_otp_vif.drive_pin(0, 0);
+    cfg.lc_provision_en_vif.drive(lc_ctrl_pkg::Off);
+    // reset power init pin and lc_provision_en pin
     if (do_otp_ctrl_init) otp_ctrl_init();
   endtask
 
@@ -27,9 +32,51 @@
 
   // setup basic otp_ctrl features
   virtual task otp_ctrl_init();
-    cfg.pwr_otp_vif.drive_pin(0, 0);
     // reset memory to avoid readout X
     cfg.mem_bkdr_vif.clear_mem();
+    csr_wr(ral.intr_enable, en_intr);
+  endtask
+
+  // this task triggers an OTP write sequence via the DAI interface
+  virtual task dai_wr(bit [TL_DW-1:0] addr,
+                      bit [TL_DW-1:0] wdata0,
+                      bit [TL_DW-1:0] wdata1 = 0);
+    csr_wr(ral.direct_access_address, addr);
+    csr_wr(ral.direct_access_wdata_0, wdata0);
+    if (!is_secret(addr)) csr_wr(ral.direct_access_wdata_1, wdata1);
+    csr_wr(ral.direct_access_cmd, int'(otp_ctrl_pkg::DaiWrite));
+    csr_spinwait(ral.intr_state, 1 << OtpOperationDone);
+    csr_wr(ral.intr_state, 1'b1 << OtpOperationDone);
+  endtask : dai_wr
+
+  // this task triggers an OTP readout sequence via the DAI interface
+  virtual task dai_rd(bit [TL_DW-1:0]        addr,
+                      output bit [TL_DW-1:0] rdata0,
+                      output bit [TL_DW-1:0] rdata1);
+    csr_wr(ral.direct_access_address, addr);
+    csr_wr(ral.direct_access_cmd, int'(otp_ctrl_pkg::DaiRead));
+    csr_spinwait(ral.intr_state, 1 << OtpOperationDone);
+
+    csr_rd(ral.direct_access_rdata_0, rdata0);
+    if (!is_secret(addr)) csr_rd(ral.direct_access_rdata_1, rdata1);
+    csr_wr(ral.intr_state, 1'b1 << OtpOperationDone);
+  endtask : dai_rd
+
+  // this task exercises an OTP digest calculation via the DAI interface
+  virtual task cal_digest(int part_idx);
+    csr_wr(ral.direct_access_address, DIGESTS_ADDR[part_idx]);
+    csr_wr(ral.direct_access_cmd, otp_ctrl_pkg::DaiDigest);
+    csr_spinwait(ral.intr_state, 1 << OtpOperationDone);
+    csr_wr(ral.intr_state, 1 << OtpOperationDone);
+  endtask
+
+  // this task provisions all HW partitions
+  // SW partitions could not be provisioned via DAI interface
+  // LC partitions cannot be locked
+  virtual task cal_hw_digests();
+    for (int i = int'(HwCfgIdx); i < int'(LifeCycleIdx); i++) begin
+      cal_digest(i);
+    end
   endtask
 
 endclass : otp_ctrl_base_vseq
diff --git a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_sanity_vseq.sv b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_sanity_vseq.sv
index 1e5f84a..3392e9d 100644
--- a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_sanity_vseq.sv
+++ b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_sanity_vseq.sv
@@ -3,17 +3,101 @@
 // SPDX-License-Identifier: Apache-2.0
 
 // basic sanity test vseq
+`define PART_ADDR_RANGE(i) \
+    {[PartInfo[``i``].offset : (PartInfo[``i``].offset + PartInfo[``i``].size - DIGEST_SIZE - 1)]}
+
 class otp_ctrl_sanity_vseq extends otp_ctrl_base_vseq;
   `uvm_object_utils(otp_ctrl_sanity_vseq)
 
   `uvm_object_new
 
+  randc bit [TL_AW-1:0]          dai_addr;
+  rand  bit [TL_DW-1:0]          wdata0, wdata1;
+  rand  int                      num_dai_wr;
+  rand  otp_ctrl_pkg::part_idx_e part_idx;
+
+  // TODO: temp -> no life-cycle partition involved
+  constraint partition_index_c {
+    part_idx inside {[CreatorSwCfgIdx:Secret2Idx]};
+  }
+
+  constraint dai_addr_c {
+    if (part_idx == CreatorSwCfgIdx) dai_addr inside `PART_ADDR_RANGE(CreatorSwCfgIdx);
+    if (part_idx == OwnerSwCfgIdx)   dai_addr inside `PART_ADDR_RANGE(OwnerSwCfgIdx);
+    if (part_idx == HwCfgIdx)        dai_addr inside `PART_ADDR_RANGE(HwCfgIdx);
+    if (part_idx == Secret0Idx)      dai_addr inside `PART_ADDR_RANGE(Secret0Idx);
+    if (part_idx == Secret1Idx)      dai_addr inside `PART_ADDR_RANGE(Secret1Idx);
+    if (part_idx == Secret2Idx)      dai_addr inside `PART_ADDR_RANGE(Secret2Idx);
+    if (part_idx == LifeCycleIdx)    dai_addr inside `PART_ADDR_RANGE(LifeCycleIdx);
+
+    dai_addr % 4 == 0;
+    if (part_idx inside {[Secret0Idx:Secret2Idx]}) dai_addr % 8 == 0;
+  }
+
+  constraint num_dai_wr_c {num_dai_wr inside {[1:20]};}
+
+  virtual task dut_init(string reset_kind = "HARD");
+    super.dut_init(reset_kind);
+    // drive pwr_otp_req pin
+    cfg.pwr_otp_vif.drive_pin(0, 1);
+    wait(cfg.pwr_otp_vif.pins[2] == 1);
+    cfg.lc_provision_en_vif.drive(lc_ctrl_pkg::On);
+  endtask
+
   virtual task pre_start();
     super.pre_start();
+    num_dai_wr.rand_mode(0);
   endtask
 
   task body();
+    for (int i = 1; i <= num_trans; i++) begin
+      bit [TL_DW-1:0] rdata, tlul_rdata;
+      `uvm_info(`gfn, $sformatf("starting seq %0d/%0d", i, num_trans), UVM_MEDIUM)
+      do_otp_ctrl_init = 1;
+      dut_init();
+      do_otp_ctrl_init = 0;
+
+      // after otp-init done, check status
+      cfg.clk_rst_vif.wait_clks(1);
+      csr_rd_check(.ptr(ral.status), .compare_value(OtpIdle));
+
+      for (int i = 0; i < num_dai_wr; i++) begin
+        bit [TL_DW-1:0] rdata0, rdata1;
+        `DV_CHECK_RANDOMIZE_FATAL(this)
+
+        // OTP write via DAI
+        dai_wr(dai_addr, wdata0, wdata1);
+
+        // OTP read via DAI
+        dai_rd(dai_addr, rdata0, rdata1);
+
+        // check read data
+        `DV_CHECK_EQ(wdata0, rdata0, $sformatf("read data0 mismatch at addr %0h", dai_addr))
+        if (!is_secret(dai_addr)) begin
+          `DV_CHECK_EQ(wdata1, rdata1, $sformatf("read data1 mismatch at addr %0h", dai_addr))
+        end
+
+        // if write sw partitions, check tlul window
+        if (part_idx inside {CreatorSwCfgIdx, OwnerSwCfgIdx}) begin
+          bit [TL_AW-1:0] tlul_addr = get_sw_window_addr(dai_addr);
+
+          // random issue reset, OTP content should not be cleared
+          if ($urandom_range(0, 1)) dut_init();
+          tl_access(.addr(tlul_addr), .write(0), .data(tlul_rdata), .blocking(1));
+          `DV_CHECK_EQ(tlul_rdata, rdata0, $sformatf("mem read out mismatch at addr %0h", tlul_addr))
+        end
+      end
+
+      // check no error
+      csr_rd_check(.ptr(ral.status), .compare_value(OtpIdle));
+
+      // lock HW digests
+      `uvm_info(`gfn, "Trigger HW digest calculation", UVM_HIGH)
+      cal_hw_digests();
+      csr_rd_check(.ptr(ral.status), .compare_value(OtpIdle));
+    end
   endtask : body
 
 endclass : otp_ctrl_sanity_vseq
 
+`undef PART_ADDR_RANGE
diff --git a/hw/ip/otp_ctrl/dv/tb.sv b/hw/ip/otp_ctrl/dv/tb.sv
index ab971ec..f22803e 100644
--- a/hw/ip/otp_ctrl/dv/tb.sv
+++ b/hw/ip/otp_ctrl/dv/tb.sv
@@ -15,6 +15,7 @@
 
   wire clk, rst_n;
   wire devmode;
+  wire [3:0] lc_provision_en;
   // TODO: use standard req/rsp agent
   wire [2:0] pwr_otp;
   wire [NUM_MAX_INTERRUPTS-1:0] interrupts;
@@ -26,6 +27,7 @@
   pins_if #(1) devmode_if(devmode);
   // TODO: use standard req/rsp agent
   pins_if #(3) pwr_otp_if(pwr_otp);
+  pins_if #(4) lc_provision_en_if(lc_provision_en);
   tl_if tl_if(.clk(clk), .rst_n(rst_n));
 
   // dut
@@ -38,19 +40,40 @@
     // interrupt
     .intr_otp_operation_done_o (intr_otp_operation_done),
     .intr_otp_error_o          (intr_otp_error),
-    // TODO: add remaining IOs and hook them
+    // alert
     .alert_rx_i                ('0),
-    .otp_edn_rsp_i             ('0),
-    .pwr_otp_req_i             (pwr_otp[0]),
-    .pwr_otp_rsp_o             (pwr_otp[2:1]),
-    .lc_otp_program_req_i      ('0),
-    .lc_otp_token_req_i        ('0),
-    .lc_escalate_en_i          ('0),
-    .lc_provision_en_i         ('0),
+    .alert_tx_o                (  ),
+    // ast
+    .otp_ast_pwr_seq_o         (  ),
+    .otp_ast_pwr_seq_h_i       ('0),
+    // edn
+    .otp_edn_o                 (  ),
+    .otp_edn_i                 ('0),
+    // pwrmgr
+    .pwr_otp_i                 (pwr_otp[0]),
+    .pwr_otp_o                 (pwr_otp[2:1]),
+    // lc
+    .lc_otp_program_i          ('0),
+    .lc_otp_program_o          (  ),
+    .lc_otp_token_i            ('0),
+    .lc_otp_token_o            (  ),
+    .lc_escalate_en_i          (lc_ctrl_pkg::Off),
+    .lc_provision_en_i         (lc_provision_en),
     .lc_dft_en_i               ('0),
-    .flash_otp_key_req_i       ('0),
-    .sram_otp_key_req_i        ('0),
-    .otbn_otp_key_req_i        ('0)
+    .otp_lc_data_o             (  ),
+    // keymgr
+    .otp_keymgr_key_o          (  ),
+    // flash
+    .flash_otp_key_i           ('0),
+    .flash_otp_key_o           (  ),
+    // sram
+    .sram_otp_key_i            ('0),
+    .sram_otp_key_o            (  ),
+    // otbn
+    .otbn_otp_key_i            ('0),
+    .otbn_otp_key_o            (  ),
+    // hw cfg
+    .hw_cfg_o                  (  )
   );
 
   // bind mem_bkdr_if
@@ -69,8 +92,11 @@
     uvm_config_db#(intr_vif)::set(null, "*.env", "intr_vif", intr_if);
     uvm_config_db#(pwr_otp_vif)::set(null, "*.env", "pwr_otp_vif", pwr_otp_if);
     uvm_config_db#(devmode_vif)::set(null, "*.env", "devmode_vif", devmode_if);
+    uvm_config_db#(lc_provision_en_vif)::set(null, "*.env", "lc_provision_en_vif",
+                                             lc_provision_en_if);
     uvm_config_db#(virtual tl_if)::set(null, "*.env.m_tl_agent*", "vif", tl_if);
-    uvm_config_db#(mem_bkdr_vif)::set(null, "*.env", "mem_bkdr_vif", `OTP_CTRL_MEM_HIER.mem_bkdr_if);
+    uvm_config_db#(mem_bkdr_vif)::set(null, "*.env", "mem_bkdr_vif",
+                                      `OTP_CTRL_MEM_HIER.mem_bkdr_if);
     $timeformat(-12, 0, " ps", 12);
     run_test();
   end