// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

class dv_base_vseq #(type RAL_T               = dv_base_reg_block,
                     type CFG_T               = dv_base_env_cfg,
                     type COV_T               = dv_base_env_cov,
                     type VIRTUAL_SEQUENCER_T = dv_base_virtual_sequencer) extends uvm_sequence;
  `uvm_object_param_utils(dv_base_vseq #(RAL_T, CFG_T, COV_T, VIRTUAL_SEQUENCER_T))
  `uvm_declare_p_sequencer(VIRTUAL_SEQUENCER_T)

  // number of iterations to run the test seq - please override constraint in extended vseq
  // randomization for this is disabled in pre_start since we don't want to re-randomize it again
  rand uint num_trans;

  constraint num_trans_c {
    num_trans inside {[1:20]};
  }

  // handles for ease of op
  CFG_T   cfg;
  RAL_T   ral;
  COV_T   cov;

  // knobs to enable pre_start routines
  bit do_dut_init       = 1'b1;
  bit do_apply_reset    = 1'b1;
  bit do_wait_for_reset = 1'b1;

  // knobs to enable post_start routines
  bit do_dut_shutdown   = 1'b1;

  `uvm_object_new

  task pre_start();
    super.pre_start();
    cfg = p_sequencer.cfg;
    cov = p_sequencer.cov;
    ral = cfg.ral;
    if (do_dut_init) dut_init("HARD");
    num_trans.rand_mode(0);
  endtask

  task body();
    `uvm_fatal(`gtn, "Need to override this when you extend from this class!")
  endtask : body

  task post_start();
    super.post_start();
    if (do_dut_shutdown) dut_shutdown();
  endtask

  /*
   * startup, reset and shutdown related tasks
   */
  virtual task dut_init(string reset_kind = "HARD");
    if (do_apply_reset)         apply_reset(reset_kind);
    else if (do_wait_for_reset) wait_for_reset(reset_kind);
    // delay after reset for tl agent check seq_item_port empty
    #1ps;
  endtask

  virtual task apply_reset(string kind = "HARD");
    if (kind == "HARD") begin
      csr_utils_pkg::reset_asserted();
      cfg.clk_rst_vif.apply_reset();
      csr_utils_pkg::reset_deasserted();
    end
    if (cfg.has_ral) ral.reset(kind);
  endtask

  virtual task wait_for_reset(string reset_kind     = "HARD",
                              bit wait_for_assert   = 1,
                              bit wait_for_deassert = 1);
    if (wait_for_assert) begin
      `uvm_info(`gfn, "waiting for rst_n assertion...", UVM_MEDIUM)
      @(negedge cfg.clk_rst_vif.rst_n);
    end
    if (wait_for_deassert) begin
      `uvm_info(`gfn, "waiting for rst_n de-assertion...", UVM_MEDIUM)
      @(posedge cfg.clk_rst_vif.rst_n);
    end
    `uvm_info(`gfn, "wait_for_reset done", UVM_HIGH)
  endtask

  // dut shutdown - this is called in post_start if do_dut_shutdown bit is set
  virtual task dut_shutdown();
    csr_utils_pkg::wait_no_outstanding_access();
  endtask

  // function to add csr exclusions of the given type using the csr_excl_item item
  // arg csr_test_type: this the the type of csr test run - we may want additional exclusions
  // depending on what test seq we are running
  // arg csr_excl: this is the csr exclusion object that maintains the list of exclusions
  // the same object handle is to be passed to csr sequences in csr_seq_lib so that they can query
  // those exclusions
  virtual function void add_csr_exclusions(string           csr_test_type,
                                           csr_excl_item    csr_excl,
                                           string           scope = "ral");
    `uvm_info(`gfn, "no exclusion item added from this function", UVM_DEBUG)
  endfunction

  // TODO: temp support, can delete this once all IPs update their exclusion in hjson
  virtual function csr_excl_item add_and_return_csr_excl(string csr_test_type);
    add_csr_exclusions(csr_test_type, ral.csr_excl);
    ral.csr_excl.print_exclusions();
    return ral.csr_excl;
  endfunction

  // wrapper task around run_csr_vseq - the purpose is to be able to call this directly for actual
  // csr tests (as opposed to higher level stress test that could also run csr seq as a fork by
  // calling run_csr_vseq(..) task)
  virtual task run_csr_vseq_wrapper(int num_times = 1);
    string        csr_test_type;
    csr_excl_item csr_excl;

    // env needs to have a ral instance
    `DV_CHECK_EQ_FATAL(cfg.has_ral, 1'b1)

    // get csr_test_type from plusarg
    void'($value$plusargs("csr_%0s", csr_test_type));

    // create csr exclusions before running the csr seq
    csr_excl = add_and_return_csr_excl(csr_test_type);

    // run the csr seq
    for (int i = 1; i <= num_times; i++) begin
      `uvm_info(`gfn, $sformatf("running csr %0s vseq iteration %0d/%0d",
                                csr_test_type, i, num_times), UVM_LOW)
      run_csr_vseq(.csr_test_type(csr_test_type), .csr_excl(csr_excl));
    end
  endtask

  // capture the entire csr seq as a task that can be overridden if desired
  // arg csr_test_type: what csr test to run {hw_reset, rw, bit_bash, aliasing}
  // arg csr_excl: csr exclusion object - needs to be created and exclusions set before call
  // arg num_test_csrs:instead of testing the entire ral model or passing test chunk info via
  // plusarg, provide ability to set a random number of csrs to test from higher level sequence
  virtual task run_csr_vseq(string          csr_test_type = "",
                            csr_excl_item   csr_excl = null,
                            int             num_test_csrs = 0,
                            bit             do_rand_wr_and_reset = 1);
    csr_base_seq  m_csr_seq;

    // env needs to have a ral instance
    `DV_CHECK_EQ_FATAL(cfg.has_ral, 1'b1)

    // check which csr test type
    case (csr_test_type)
      "hw_reset": csr_base_seq::type_id::set_type_override(csr_hw_reset_seq::get_type());
      "rw"      : csr_base_seq::type_id::set_type_override(csr_rw_seq::get_type());
      "bit_bash": csr_base_seq::type_id::set_type_override(csr_bit_bash_seq::get_type());
      "aliasing": csr_base_seq::type_id::set_type_override(csr_aliasing_seq::get_type());
      "mem_walk": csr_base_seq::type_id::set_type_override(csr_mem_walk_seq::get_type());
      default   : `uvm_fatal(`gfn, $sformatf("specified opt is invalid: +csr_%0s", csr_test_type))
    endcase

    // if hw_reset test, then write all CSRs first and reset the whole dut
    if (csr_test_type == "hw_reset" && do_rand_wr_and_reset) begin
      string        reset_type = "HARD";
      csr_write_seq m_csr_write_seq;

      // run write-only sequence to randomize the csr values
      m_csr_write_seq = csr_write_seq::type_id::create("m_csr_write_seq");
      m_csr_write_seq.models.push_back(ral);
      m_csr_write_seq.set_csr_excl_item(csr_excl);
      m_csr_write_seq.external_checker = cfg.en_scb;
      m_csr_write_seq.start(null);

      // run dut_shutdown before asserting reset
      dut_shutdown();

      // issue reset
      void'($value$plusargs("do_reset=%0s", reset_type));
      dut_init(reset_type);
    end

    // create base csr seq and pass our ral
    m_csr_seq = csr_base_seq::type_id::create("m_csr_seq");
    m_csr_seq.num_test_csrs = num_test_csrs;
    m_csr_seq.models.push_back(ral);
    m_csr_seq.set_csr_excl_item(csr_excl);
    m_csr_seq.external_checker = cfg.en_scb;
    m_csr_seq.start(null);
  endtask

endclass
