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

package csr_utils_pkg;
  // dep packages
  import uvm_pkg::*;
  import dv_utils_pkg::*;
  import dv_base_reg_pkg::*;
  export dv_base_reg_pkg::csr_field_t, dv_base_reg_pkg::decode_csr_or_field;

  // macro includes
  `include "uvm_macros.svh"
  `include "dv_macros.svh"

  // local types and variables
  uint        outstanding_accesses        = 0;
  uint        default_timeout_ns          = 2_000_000; // 2ms
  uint        default_spinwait_timeout_ns = 10_000_000; // 10ms
  string      msg_id                      = "csr_utils";
  bit         default_csr_blocking        = 1;
  uvm_check_e default_csr_check           = UVM_CHECK;
  bit         under_reset                 = 0;
  int         max_outstanding_accesses    = 100;

  function automatic void increment_outstanding_access();
    outstanding_accesses++;
  endfunction

  function automatic void decrement_outstanding_access();
    outstanding_accesses--;
  endfunction

  task automatic wait_no_outstanding_access();
    wait(outstanding_accesses == 0);
  endtask

  function automatic void clear_outstanding_access();
    outstanding_accesses = 0;
  endfunction

  function automatic bit has_outstanding_access();
    return outstanding_accesses > 0;
  endfunction

  // timeout may happen if we issue too many non-blocking accesses at once
  // limit the nonblocking items to be up to max outstanding
  task automatic wait_if_max_outstanding_accesses_reached(int max = max_outstanding_accesses);
    wait(outstanding_accesses <= max);
  endtask

  function automatic void reset_asserted();
    under_reset = 1;
  endfunction

  function automatic void reset_deasserted();
    under_reset = 0;
  endfunction

  // get mem object from address
  function automatic uvm_mem get_mem_by_addr(uvm_reg_block ral, uvm_reg_addr_t addr);
    uvm_mem mem;
    addr[1:0] = 0;
    mem = ral.default_map.get_mem_by_offset(addr);
    `DV_CHECK_NE_FATAL(mem, null, $sformatf("Can't find any mem with addr 0x%0h", addr), msg_id)
    return mem;
  endfunction

  // get mem access like RW, RO
  function automatic string get_mem_access_by_addr(uvm_reg_block ral, uvm_reg_addr_t addr);
    uvm_mem mem = get_mem_by_addr(ral, addr);
    return mem.get_access();
  endfunction

  // This function return mirrored value of reg/field of given RAL
  function automatic uvm_reg_data_t get_reg_fld_mirror_value(uvm_reg_block ral,
                                                             string        reg_name,
                                                             string        field_name  = "");
    uvm_reg         csr;
    uvm_reg_field   fld;
    uvm_reg_data_t  result;
    string          msg_id = {csr_utils_pkg::msg_id, "::get_reg_fld_mirror_value"};
    csr = ral.get_reg_by_name(reg_name);
    `DV_CHECK_NE_FATAL(csr, null, "", msg_id)
    // return field mirror value if field_name is passed, else return reg mirror value
    if (field_name != "") begin
      fld = csr.get_field_by_name(field_name);
      `DV_CHECK_NE_FATAL(fld, null, "", msg_id)
      result = fld.get_mirrored_value();
    end
    else begin
      result = csr.get_mirrored_value();
    end
    return result;
  endfunction : get_reg_fld_mirror_value

  // get updated reg value by using new specific field value
  function automatic uvm_reg_data_t get_csr_val_with_updated_field(uvm_reg_field   field,
                                                                   uvm_reg_data_t  csr_value,
                                                                   uvm_reg_data_t  field_value);
    uvm_reg_data_t  mask = (1 << field.get_n_bits()) - 1;
    uint            shift = field.get_lsb_pos();
    csr_value = csr_value & ~(mask << shift) | ((mask & field_value) << shift);
    return csr_value;
  endfunction

  // wait until current csr op is complete
  task automatic csr_wait(input uvm_reg csr);
    `uvm_info(msg_id, $sformatf("%0s: wait_busy: %0b",
                                csr.get_full_name(), csr.m_is_busy), UVM_HIGH)
    wait(csr.m_is_busy == 1'b0);
    `uvm_info(msg_id, $sformatf("%0s: done wait_busy: %0b",
                                csr.get_full_name(), csr.m_is_busy), UVM_HIGH)
  endtask

  // Use `csr_wr` to construct `csr_update` to avoid replicated codes to handle nonblocking,
  // shadow writes etc
  task automatic csr_update(input  uvm_reg      csr,
                            input  uvm_check_e  check = default_csr_check,
                            input  uvm_path_e   path = UVM_DEFAULT_PATH,
                            input  bit          blocking = default_csr_blocking,
                            input  uint         timeout_ns = default_timeout_ns,
                            input  uvm_reg_map  map = null,
                            input  bit          en_shadow_wr = 1);
    uvm_reg_field fields[$];
    uvm_reg_data_t value;

    // below is partial replication of the uvm_reg_field::update() logic in UVM1.2 source code
    if (!csr.needs_update()) return;
    csr.get_fields(fields);
    // Concatenate the write-to-update values from each field
    // Fields are stored in LSB or MSB order
    value = 0;
    foreach (fields[i]) begin
      value |= fields[i].XupdateX() << fields[i].get_lsb_pos();
    end

    csr_wr(.ptr(csr), .value(value), .check(check), .path(path), .blocking(blocking), .backdoor(0),
           .timeout_ns(timeout_ns), .predict(0), .map(map), .en_shadow_wr(en_shadow_wr));
  endtask

  task automatic csr_wr(input uvm_object     ptr,
                        input uvm_reg_data_t value,
                        input uvm_check_e    check = default_csr_check,
                        input uvm_path_e     path = UVM_DEFAULT_PATH,
                        input bit            blocking = default_csr_blocking,
                        input bit            backdoor = 0,
                        input uint           timeout_ns = default_timeout_ns,
                        input bit            predict = 0,
                        input uvm_reg_map    map = null,
                        input bit            en_shadow_wr = 1);
    if (backdoor) begin
      csr_poke(ptr, value, check, predict);
    end else begin
      csr_field_t csr_or_fld = decode_csr_or_field(ptr);

      // if it's a field write, still do full CSR write and use mirrored value for the other fields
      if (csr_or_fld.field != null) begin
        // get full CSR value
        value = get_csr_val_with_updated_field(csr_or_fld.field, `gmv(csr_or_fld.csr), value);
      end

      if (blocking) begin
        csr_wr_sub(csr_or_fld.csr, value, check, path, timeout_ns, predict, map, en_shadow_wr);
      end else begin
        fork
          begin
            csr_wr_sub(csr_or_fld.csr, value, check, path, timeout_ns, predict, map, en_shadow_wr);
          end
        join_none
        // Add #0 to ensure that this thread starts executing before any subsequent call
        #0;
      end
    end
  endtask

  // subroutine of csr_wr, don't use it directly
  task automatic csr_wr_sub(input uvm_reg        csr,
                            input uvm_reg_data_t value,
                            input uvm_check_e    check = default_csr_check,
                            input uvm_path_e     path = UVM_DEFAULT_PATH,
                            input uint           timeout_ns = default_timeout_ns,
                            input bit            predict = 0,
                            input uvm_reg_map    map = null,
                            input bit            en_shadow_wr = 1);
    fork
      begin : isolation_fork
        string        msg_id = {csr_utils_pkg::msg_id, "::csr_wr"};

        fork
          begin
            dv_base_reg dv_reg;
            `downcast(dv_reg, csr, "", fatal, msg_id)

            increment_outstanding_access();
            csr_pre_write_sub(csr, en_shadow_wr);

            csr_wr_and_predict_sub(.csr(csr), .value(value), .check(check), .path(path),
                                   .predict(predict), .map(map));
            if (en_shadow_wr && dv_reg.get_is_shadowed()) begin
              csr_wr_and_predict_sub(.csr(csr), .value(value), .check(check), .path(path),
                                     .predict(predict), .map(map));
            end

            csr_post_write_sub(csr, en_shadow_wr);
            decrement_outstanding_access();
          end
          begin
            `DV_WAIT_TIMEOUT(timeout_ns, msg_id,
                             $sformatf("Timeout waiting to csr_wr %0s (addr=0x%0h)",
                                       csr.get_full_name(), csr.get_address()))
          end
        join_any
        disable fork;
      end : isolation_fork
    join
  endtask

  // internal task, don't use it directly
  task automatic csr_wr_and_predict_sub(uvm_reg        csr,
                                        uvm_reg_data_t value,
                                        uvm_check_e    check,
                                        uvm_path_e     path,
                                        bit            predict,
                                        uvm_reg_map    map);
    uvm_status_e status;
    csr.write(.status(status), .value(value), .path(path), .map(map), .prior(100));

    if (under_reset) return;
    if (check == UVM_CHECK) begin
      `DV_CHECK_EQ(status, UVM_IS_OK,
                   $sformatf("trying to write csr %0s", csr.get_full_name()),
                   error, msg_id)
    end
    // Only update the predicted value if status is ok (otherwise the write isn't completed
    // successfully and the design shouldn't have accepted the written value)
    if (status == UVM_IS_OK && predict) begin
      void'(csr.predict(.value(value), .kind(UVM_PREDICT_WRITE)));
    end
  endtask

  task automatic csr_pre_write_sub(uvm_reg csr, bit en_shadow_wr);
    dv_base_reg dv_reg;
    `downcast(dv_reg, csr, "", fatal, msg_id)
    if (dv_reg.get_is_shadowed() && en_shadow_wr) begin
      dv_reg.atomic_en_shadow_wr.get(1);
    end
  endtask

  task automatic csr_post_write_sub(uvm_reg csr, bit en_shadow_wr);
    dv_base_reg dv_reg;
    `downcast(dv_reg, csr, "", fatal, msg_id)
    if (dv_reg.get_is_shadowed() && en_shadow_wr) begin
      dv_reg.atomic_en_shadow_wr.put(1);
    end
  endtask

  // backdoor write csr
  task automatic csr_poke(input uvm_object      ptr,
                          input uvm_reg_data_t  value,
                          input uvm_check_e     check = default_csr_check,
                          input bit             predict = 0,
                          input bkdr_reg_path_e kind = BkdrRegPathRtl);
    csr_field_t   csr_or_fld = decode_csr_or_field(ptr);
    uvm_status_e  status;
    string        msg_id = {csr_utils_pkg::msg_id, "::csr_poke"};
    uvm_reg_data_t old_mirrored_val;

    if (csr_or_fld.field != null) begin
      old_mirrored_val = csr_or_fld.field.get_mirrored_value();
      csr_or_fld.field.poke(.status(status), .value(value), .kind(kind.name));
    end else begin
      old_mirrored_val = csr_or_fld.csr.get_mirrored_value();
      csr_or_fld.csr.poke(.status(status), .value(value), .kind(kind.name));
    end
    if (check == UVM_CHECK && status != UVM_IS_OK) begin
      string str;
      uvm_hdl_path_concat paths[$];
      csr_or_fld.csr.get_full_hdl_path(paths, kind.name);
      foreach (paths[0].slices[i]) str = $sformatf("%0s\n%0s", str, paths[0].slices[i].path);
      `uvm_fatal(msg_id, $sformatf("poke failed for %0s, check below paths %0s",
                                   ptr.get_full_name(), str))
    end
    // poke always updates predict value, if predict == 0, revert back to old mirrored value
    if (!predict || kind == BkdrRegPathRtlShadow) begin
      if (csr_or_fld.field != null) begin
        void'(csr_or_fld.field.predict(.value(old_mirrored_val), .kind(UVM_PREDICT_DIRECT),
                                       .path(UVM_BACKDOOR)));
      end else begin
        void'(csr_or_fld.csr.predict(.value(old_mirrored_val), .kind(UVM_PREDICT_DIRECT),
                                     .path(UVM_BACKDOOR)));
      end
    end
  endtask

  task automatic csr_rd(input  uvm_object     ptr, // accept reg or field
                        output uvm_reg_data_t value,
                        input  uvm_check_e    check = default_csr_check,
                        input  uvm_path_e     path = UVM_DEFAULT_PATH,
                        input  bit            blocking = default_csr_blocking,
                        input  bit            backdoor = 0,
                        input  uint           timeout_ns = default_timeout_ns,
                        input  uvm_reg_map    map = null);
    uvm_status_e status;
    if (blocking) begin
      csr_rd_sub(.ptr(ptr), .value(value), .status(status), .check(check), .path(path),
                 .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map));
    end else begin
      `DV_CHECK_EQ(backdoor, 0, "Don't enable backdoor with blocking = 0", error, msg_id)
      fork
        csr_rd_sub(.ptr(ptr), .value(value), .status(status), .check(check), .path(path),
                   .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map));
      join_none
      // Add #0 to ensure that this thread starts executing before any subsequent call
      #0;
    end
  endtask

  // subroutine of csr_rd, don't use it directly
  task automatic csr_rd_sub(input  uvm_object     ptr, // accept reg or field
                            output uvm_reg_data_t value,
                            output uvm_status_e   status,
                            input  bit            backdoor = 0,
                            input  uvm_check_e    check = default_csr_check,
                            input  uvm_path_e     path = UVM_DEFAULT_PATH,
                            input  uint           timeout_ns = default_timeout_ns,
                            input  uvm_reg_map    map = null);
    if (backdoor) begin
        csr_peek(ptr, value, check);
        status = UVM_IS_OK;
        return;
    end
    fork
      begin : isolation_fork
        csr_field_t   csr_or_fld;
        string        msg_id = {csr_utils_pkg::msg_id, "::csr_rd"};

        fork
          begin
            increment_outstanding_access();
            csr_or_fld = decode_csr_or_field(ptr);
            if (csr_or_fld.field != null) begin
              csr_or_fld.field.read(.status(status), .value(value), .path(path), .map(map),
                                    .prior(100));
            end else begin
              csr_or_fld.csr.read(.status(status), .value(value), .path(path), .map(map),
                                  .prior(100));
            end
            if (check == UVM_CHECK && !under_reset) begin
              `DV_CHECK_EQ(status, UVM_IS_OK,
                           $sformatf("trying to read csr/field %0s", ptr.get_full_name()),
                           error, msg_id)
            end
            decrement_outstanding_access();
          end
          begin
            `DV_WAIT_TIMEOUT(timeout_ns, msg_id,
                             $sformatf("Timeout waiting to csr_rd %0s (addr=0x%0h)",
                                       ptr.get_full_name(), csr_or_fld.csr.get_address()))
          end
        join_any
        disable fork;
      end : isolation_fork
    join
  endtask

  // backdoor read csr
  // uvm_reg::peek() returns a 2-state value, directly get data from hdl path
  task automatic csr_peek(input uvm_object      ptr,
                          output uvm_reg_data_t value,
                          input uvm_check_e     check = default_csr_check,
                          input bkdr_reg_path_e kind = BkdrRegPathRtl);
    string      msg_id = {csr_utils_pkg::msg_id, "::csr_peek"};
    csr_field_t csr_or_fld = decode_csr_or_field(ptr);
    uvm_reg     csr = csr_or_fld.csr;

    if (csr.has_hdl_path(kind.name)) begin
      uvm_hdl_path_concat paths[$];

      csr.get_full_hdl_path(paths, kind.name);
      foreach (paths[0].slices[i]) begin
        uvm_reg_data_t field_val;
        if (uvm_hdl_read(paths[0].slices[i].path, field_val)) begin
          if (check == UVM_CHECK) `DV_CHECK_EQ($isunknown(value), 0, "", error, msg_id)
          value |= field_val << paths[0].slices[i].offset;
        end else begin
          `uvm_fatal(msg_id, $sformatf("uvm_hdl_read failed for %0s", csr.get_full_name()))
        end
      end
    end else begin
      `uvm_fatal(msg_id, $sformatf("No backdoor defined for %0s path's %0s",
                                   csr.get_full_name(), kind.name))
    end

    // if it's field, only return field value
    if (csr_or_fld.field != null) value = get_field_val(csr_or_fld.field, value);
  endtask

  task automatic csr_rd_check(input  uvm_object     ptr,
                              input  uvm_check_e    check = default_csr_check,
                              input  uvm_path_e     path = UVM_DEFAULT_PATH,
                              input  bit            blocking = default_csr_blocking,
                              input  bit            backdoor = 0,
                              input  uint           timeout_ns = default_timeout_ns,
                              input  bit            compare = 1'b1,
                              input  bit            compare_vs_ral = 1'b0,
                              input  uvm_reg_data_t compare_mask = '1,
                              input  uvm_reg_data_t compare_value = 0,
                              input  string         err_msg = "",
                              input  uvm_reg_map    map = null);
    fork
      begin : isolation_fork
        fork
          begin
            csr_field_t     csr_or_fld;
            uvm_status_e    status;
            uvm_reg_data_t  obs;
            uvm_reg_data_t  exp;
            string          msg_id = {csr_utils_pkg::msg_id, "::csr_rd_check"};

            csr_or_fld = decode_csr_or_field(ptr);

            csr_rd_sub(.ptr(ptr), .value(obs), .status(status), .check(check), .path(path),
                       .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map));

            // get mirrored value after read to make sure the read reg access is updated
            if (csr_or_fld.field != null) begin
              exp = csr_or_fld.field.get_mirrored_value();
            end else begin
              exp = csr_or_fld.csr.get_mirrored_value();
            end
            if (compare && status == UVM_IS_OK && !under_reset) begin
              obs = obs & compare_mask;
              exp = (compare_vs_ral ? exp : compare_value) & compare_mask;
              `DV_CHECK_EQ(obs, exp, {"Regname: ", ptr.get_full_name(), " ", err_msg},
                    error, msg_id)
            end
          end
        join_none
        if (blocking) wait fork;
        // Add #0 to ensure that this thread starts executing before any subsequent call
        else #0;
      end : isolation_fork<commit_msg>[dv, csr_utils_pkg] Fix csr_read for field accesses<commit_after>// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

package csr_utils_pkg;
  // dep packages
  import uvm_pkg::*;
  import dv_utils_pkg::*;
  import dv_base_reg_pkg::*;
  export dv_base_reg_pkg::csr_field_t, dv_base_reg_pkg::decode_csr_or_field;

  // macro includes
  `include "uvm_macros.svh"
  `include "dv_macros.svh"

  // local types and variables
  uint        outstanding_accesses        = 0;
  uint        default_timeout_ns          = 2_000_000; // 2ms
  uint        default_spinwait_timeout_ns = 10_000_000; // 10ms
  string      msg_id                      = "csr_utils";
  bit         default_csr_blocking        = 1;
  uvm_check_e default_csr_check           = UVM_CHECK;
  bit         under_reset                 = 0;
  int         max_outstanding_accesses    = 100;

  function automatic void increment_outstanding_access();
    outstanding_accesses++;
  endfunction

  function automatic void decrement_outstanding_access();
    outstanding_accesses--;
  endfunction

  task automatic wait_no_outstanding_access();
    wait(outstanding_accesses == 0);
  endtask

  function automatic void clear_outstanding_access();
    outstanding_accesses = 0;
  endfunction

  function automatic bit has_outstanding_access();
    return outstanding_accesses > 0;
  endfunction

  // timeout may happen if we issue too many non-blocking accesses at once
  // limit the nonblocking items to be up to max outstanding
  task automatic wait_if_max_outstanding_accesses_reached(int max = max_outstanding_accesses);
    wait(outstanding_accesses <= max);
  endtask

  function automatic void reset_asserted();
    under_reset = 1;
  endfunction

  function automatic void reset_deasserted();
    under_reset = 0;
  endfunction

  // get mem object from address
  function automatic uvm_mem get_mem_by_addr(uvm_reg_block ral, uvm_reg_addr_t addr);
    uvm_mem mem;
    addr[1:0] = 0;
    mem = ral.default_map.get_mem_by_offset(addr);
    `DV_CHECK_NE_FATAL(mem, null, $sformatf("Can't find any mem with addr 0x%0h", addr), msg_id)
    return mem;
  endfunction

  // get mem access like RW, RO
  function automatic string get_mem_access_by_addr(uvm_reg_block ral, uvm_reg_addr_t addr);
    uvm_mem mem = get_mem_by_addr(ral, addr);
    return mem.get_access();
  endfunction

  // This function return mirrored value of reg/field of given RAL
  function automatic uvm_reg_data_t get_reg_fld_mirror_value(uvm_reg_block ral,
                                                             string        reg_name,
                                                             string        field_name  = "");
    uvm_reg         csr;
    uvm_reg_field   fld;
    uvm_reg_data_t  result;
    string          msg_id = {csr_utils_pkg::msg_id, "::get_reg_fld_mirror_value"};
    csr = ral.get_reg_by_name(reg_name);
    `DV_CHECK_NE_FATAL(csr, null, "", msg_id)
    // return field mirror value if field_name is passed, else return reg mirror value
    if (field_name != "") begin
      fld = csr.get_field_by_name(field_name);
      `DV_CHECK_NE_FATAL(fld, null, "", msg_id)
      result = fld.get_mirrored_value();
    end
    else begin
      result = csr.get_mirrored_value();
    end
    return result;
  endfunction : get_reg_fld_mirror_value

  // get updated reg value by using new specific field value
  function automatic uvm_reg_data_t get_csr_val_with_updated_field(uvm_reg_field   field,
                                                                   uvm_reg_data_t  csr_value,
                                                                   uvm_reg_data_t  field_value);
    uvm_reg_data_t  mask = (1 << field.get_n_bits()) - 1;
    uint            shift = field.get_lsb_pos();
    csr_value = csr_value & ~(mask << shift) | ((mask & field_value) << shift);
    return csr_value;
  endfunction

  // wait until current csr op is complete
  task automatic csr_wait(input uvm_reg csr);
    `uvm_info(msg_id, $sformatf("%0s: wait_busy: %0b",
                                csr.get_full_name(), csr.m_is_busy), UVM_HIGH)
    wait(csr.m_is_busy == 1'b0);
    `uvm_info(msg_id, $sformatf("%0s: done wait_busy: %0b",
                                csr.get_full_name(), csr.m_is_busy), UVM_HIGH)
  endtask

  // Use `csr_wr` to construct `csr_update` to avoid replicated codes to handle nonblocking,
  // shadow writes etc
  task automatic csr_update(input  uvm_reg      csr,
                            input  uvm_check_e  check = default_csr_check,
                            input  uvm_path_e   path = UVM_DEFAULT_PATH,
                            input  bit          blocking = default_csr_blocking,
                            input  uint         timeout_ns = default_timeout_ns,
                            input  uvm_reg_map  map = null,
                            input  bit          en_shadow_wr = 1);
    uvm_reg_field fields[$];
    uvm_reg_data_t value;

    // below is partial replication of the uvm_reg_field::update() logic in UVM1.2 source code
    if (!csr.needs_update()) return;
    csr.get_fields(fields);
    // Concatenate the write-to-update values from each field
    // Fields are stored in LSB or MSB order
    value = 0;
    foreach (fields[i]) begin
      value |= fields[i].XupdateX() << fields[i].get_lsb_pos();
    end

    csr_wr(.ptr(csr), .value(value), .check(check), .path(path), .blocking(blocking), .backdoor(0),
           .timeout_ns(timeout_ns), .predict(0), .map(map), .en_shadow_wr(en_shadow_wr));
  endtask

  task automatic csr_wr(input uvm_object     ptr,
                        input uvm_reg_data_t value,
                        input uvm_check_e    check = default_csr_check,
                        input uvm_path_e     path = UVM_DEFAULT_PATH,
                        input bit            blocking = default_csr_blocking,
                        input bit            backdoor = 0,
                        input uint           timeout_ns = default_timeout_ns,
                        input bit            predict = 0,
                        input uvm_reg_map    map = null,
                        input bit            en_shadow_wr = 1);
    if (backdoor) begin
      csr_poke(ptr, value, check, predict);
    end else begin
      csr_field_t csr_or_fld = decode_csr_or_field(ptr);

      // if it's a field write, still do full CSR write and use mirrored value for the other fields
      if (csr_or_fld.field != null) begin
        // get full CSR value
        value = get_csr_val_with_updated_field(csr_or_fld.field, `gmv(csr_or_fld.csr), value);
      end

      if (blocking) begin
        csr_wr_sub(csr_or_fld.csr, value, check, path, timeout_ns, predict, map, en_shadow_wr);
      end else begin
        fork
          begin
            csr_wr_sub(csr_or_fld.csr, value, check, path, timeout_ns, predict, map, en_shadow_wr);
          end
        join_none
        // Add #0 to ensure that this thread starts executing before any subsequent call
        #0;
      end
    end
  endtask

  // subroutine of csr_wr, don't use it directly
  task automatic csr_wr_sub(input uvm_reg        csr,
                            input uvm_reg_data_t value,
                            input uvm_check_e    check = default_csr_check,
                            input uvm_path_e     path = UVM_DEFAULT_PATH,
                            input uint           timeout_ns = default_timeout_ns,
                            input bit            predict = 0,
                            input uvm_reg_map    map = null,
                            input bit            en_shadow_wr = 1);
    fork
      begin : isolation_fork
        string msg_id = {csr_utils_pkg::msg_id, "::csr_wr"};

        fork
          begin
            dv_base_reg dv_reg;
            `downcast(dv_reg, csr, "", fatal, msg_id)

            increment_outstanding_access();
            csr_pre_write_sub(csr, en_shadow_wr);

            csr_wr_and_predict_sub(.csr(csr), .value(value), .check(check), .path(path),
                                   .predict(predict), .map(map));
            if (en_shadow_wr && dv_reg.get_is_shadowed()) begin
              csr_wr_and_predict_sub(.csr(csr), .value(value), .check(check), .path(path),
                                     .predict(predict), .map(map));
            end

            csr_post_write_sub(csr, en_shadow_wr);
            decrement_outstanding_access();
          end
          begin
            `DV_WAIT_TIMEOUT(timeout_ns, msg_id,
                             $sformatf("Timeout waiting to csr_wr %0s (addr=0x%0h)",
                                       csr.get_full_name(), csr.get_address()))
          end
        join_any
        disable fork;
      end : isolation_fork
    join
  endtask

  // internal task, don't use it directly
  task automatic csr_wr_and_predict_sub(uvm_reg        csr,
                                        uvm_reg_data_t value,
                                        uvm_check_e    check,
                                        uvm_path_e     path,
                                        bit            predict,
                                        uvm_reg_map    map);
    uvm_status_e status;
    csr.write(.status(status), .value(value), .path(path), .map(map), .prior(100));

    if (under_reset) return;
    if (check == UVM_CHECK) begin
      `DV_CHECK_EQ(status, UVM_IS_OK,
                   $sformatf("trying to write csr %0s", csr.get_full_name()),
                   error, msg_id)
    end
    // Only update the predicted value if status is ok (otherwise the write isn't completed
    // successfully and the design shouldn't have accepted the written value)
    if (status == UVM_IS_OK && predict) begin
      void'(csr.predict(.value(value), .kind(UVM_PREDICT_WRITE)));
    end
  endtask

  task automatic csr_pre_write_sub(uvm_reg csr, bit en_shadow_wr);
    dv_base_reg dv_reg;
    `downcast(dv_reg, csr, "", fatal, msg_id)
    if (dv_reg.get_is_shadowed() && en_shadow_wr) begin
      dv_reg.atomic_en_shadow_wr.get(1);
    end
  endtask

  task automatic csr_post_write_sub(uvm_reg csr, bit en_shadow_wr);
    dv_base_reg dv_reg;
    `downcast(dv_reg, csr, "", fatal, msg_id)
    if (dv_reg.get_is_shadowed() && en_shadow_wr) begin
      dv_reg.atomic_en_shadow_wr.put(1);
    end
  endtask

  // backdoor write csr
  task automatic csr_poke(input uvm_object      ptr,
                          input uvm_reg_data_t  value,
                          input uvm_check_e     check = default_csr_check,
                          input bit             predict = 0,
                          input bkdr_reg_path_e kind = BkdrRegPathRtl);
    csr_field_t   csr_or_fld = decode_csr_or_field(ptr);
    uvm_status_e  status;
    string        msg_id = {csr_utils_pkg::msg_id, "::csr_poke"};
    uvm_reg_data_t old_mirrored_val;

    if (csr_or_fld.field != null) begin
      old_mirrored_val = csr_or_fld.field.get_mirrored_value();
      csr_or_fld.field.poke(.status(status), .value(value), .kind(kind.name));
    end else begin
      old_mirrored_val = csr_or_fld.csr.get_mirrored_value();
      csr_or_fld.csr.poke(.status(status), .value(value), .kind(kind.name));
    end
    if (check == UVM_CHECK && status != UVM_IS_OK) begin
      string str;
      uvm_hdl_path_concat paths[$];
      csr_or_fld.csr.get_full_hdl_path(paths, kind.name);
      foreach (paths[0].slices[i]) str = $sformatf("%0s\n%0s", str, paths[0].slices[i].path);
      `uvm_fatal(msg_id, $sformatf("poke failed for %0s, check below paths %0s",
                                   ptr.get_full_name(), str))
    end
    // poke always updates predict value, if predict == 0, revert back to old mirrored value
    if (!predict || kind == BkdrRegPathRtlShadow) begin
      if (csr_or_fld.field != null) begin
        void'(csr_or_fld.field.predict(.value(old_mirrored_val), .kind(UVM_PREDICT_DIRECT),
                                       .path(UVM_BACKDOOR)));
      end else begin
        void'(csr_or_fld.csr.predict(.value(old_mirrored_val), .kind(UVM_PREDICT_DIRECT),
                                     .path(UVM_BACKDOOR)));
      end
    end
  endtask

  task automatic csr_rd(input  uvm_object     ptr, // accept reg or field
                        output uvm_reg_data_t value,
                        input  uvm_check_e    check = default_csr_check,
                        input  uvm_path_e     path = UVM_DEFAULT_PATH,
                        input  bit            blocking = default_csr_blocking,
                        input  bit            backdoor = 0,
                        input  uint           timeout_ns = default_timeout_ns,
                        input  uvm_reg_map    map = null);
    uvm_status_e status;
    if (blocking) begin
      csr_rd_sub(.ptr(ptr), .value(value), .status(status), .check(check), .path(path),
                 .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map));
    end else begin
      `DV_CHECK_EQ(backdoor, 0, "Don't enable backdoor with blocking = 0", error, msg_id)
      fork
        csr_rd_sub(.ptr(ptr), .value(value), .status(status), .check(check), .path(path),
                   .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map));
      join_none
      // Add #0 to ensure that this thread starts executing before any subsequent call
      #0;
    end
  endtask

  // subroutine of csr_rd, don't use it directly
  task automatic csr_rd_sub(input  uvm_object     ptr, // accept reg or field
                            output uvm_reg_data_t value,
                            output uvm_status_e   status,
                            input  bit            backdoor = 0,
                            input  uvm_check_e    check = default_csr_check,
                            input  uvm_path_e     path = UVM_DEFAULT_PATH,
                            input  uint           timeout_ns = default_timeout_ns,
                            input  uvm_reg_map    map = null);
    if (backdoor) begin
      csr_peek(ptr, value, check);
      status = UVM_IS_OK;
      return;
    end
    fork
      begin : isolation_fork
        csr_field_t   csr_or_fld;
        string        msg_id = {csr_utils_pkg::msg_id, "::csr_rd"};

        fork
          begin
            increment_outstanding_access();
            csr_or_fld = decode_csr_or_field(ptr);
            if (csr_or_fld.field != null) begin
              csr_or_fld.field.read(.status(status), .value(value), .path(path), .map(map),
                                    .prior(100));
            end else begin
              csr_or_fld.csr.read(.status(status), .value(value), .path(path), .map(map),
                                  .prior(100));
            end
            if (check == UVM_CHECK && !under_reset) begin
              `DV_CHECK_EQ(status, UVM_IS_OK,
                           $sformatf("trying to read csr/field %0s", ptr.get_full_name()),
                           error, msg_id)
            end
            decrement_outstanding_access();
          end
          begin
            `DV_WAIT_TIMEOUT(timeout_ns, msg_id,
                             $sformatf("Timeout waiting to csr_rd %0s (addr=0x%0h)",
                                       ptr.get_full_name(), csr_or_fld.csr.get_address()))
          end
        join_any
        disable fork;
      end : isolation_fork
    join
  endtask

  // backdoor read csr
  // uvm_reg::peek() returns a 2-state value, directly get data from hdl path
  task automatic csr_peek(input uvm_object      ptr,
                          output uvm_reg_data_t value,
                          input uvm_check_e     check = default_csr_check,
                          input bkdr_reg_path_e kind = BkdrRegPathRtl);
    string      msg_id = {csr_utils_pkg::msg_id, "::csr_peek"};
    csr_field_t csr_or_fld = decode_csr_or_field(ptr);
    uvm_reg     csr = csr_or_fld.csr;

    if (csr.has_hdl_path(kind.name)) begin
      uvm_hdl_path_concat paths[$];

      csr.get_full_hdl_path(paths, kind.name);
      foreach (paths[0].slices[i]) begin
        uvm_reg_data_t field_val;
        if (uvm_hdl_read(paths[0].slices[i].path, field_val)) begin
          if (check == UVM_CHECK) `DV_CHECK_EQ($isunknown(value), 0, "", error, msg_id)
          value |= field_val << paths[0].slices[i].offset;
        end else begin
          `uvm_fatal(msg_id, $sformatf("uvm_hdl_read failed for %0s", csr.get_full_name()))
        end
      end
    end else begin
      `uvm_fatal(msg_id, $sformatf("No backdoor defined for %0s path's %0s",
                                   csr.get_full_name(), kind.name))
    end

    // if it's field, only return field value
    if (csr_or_fld.field != null) value = get_field_val(csr_or_fld.field, value);
  endtask

  task automatic csr_rd_check(input  uvm_object     ptr,
                              input  uvm_check_e    check = default_csr_check,
                              input  uvm_path_e     path = UVM_DEFAULT_PATH,
                              input  bit            blocking = default_csr_blocking,
                              input  bit            backdoor = 0,
                              input  uint           timeout_ns = default_timeout_ns,
                              input  bit            compare = 1'b1,
                              input  bit            compare_vs_ral = 1'b0,
                              input  uvm_reg_data_t compare_mask = '1,
                              input  uvm_reg_data_t compare_value = 0,
                              input  string         err_msg = "",
                              input  uvm_reg_map    map = null);
    fork
      begin : isolation_fork
        fork
          begin
            csr_field_t     csr_or_fld;
            uvm_status_e    status;
            uvm_reg_data_t  obs;
            uvm_reg_data_t  exp;
            uvm_reg_data_t  reset_val;
            string          msg_id = {csr_utils_pkg::msg_id, "::csr_rd_check"};

            csr_or_fld = decode_csr_or_field(ptr);

            csr_rd_sub(.ptr(ptr), .value(obs), .status(status), .check(check), .path(path),
                       .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map));

            // get mirrored value after read to make sure the read reg access is updated
            if (csr_or_fld.field != null) begin
              exp = csr_or_fld.field.get_mirrored_value();
              reset_val = csr_or_fld.field.get_reset();
            end else begin
              exp = csr_or_fld.csr.get_mirrored_value();
              reset_val = csr_or_fld.csr.get_reset();
            end
            if (compare && status == UVM_IS_OK && !under_reset) begin
              obs = obs & compare_mask;
              exp = (compare_vs_ral ? exp : compare_value) & compare_mask;
              `DV_CHECK_EQ(obs, exp, $sformatf("Regname: %0s reset value: 0x%0h %0s",
                                               ptr.get_full_name(), reset_val, err_msg),
                           error, msg_id)
            end
          end
        join_none
        if (blocking) wait fork;
        // Add #0 to ensure that this thread starts executing before any subsequent call
        else #0;
      end : isolation_fork
{'fraction_non_alphanumeric': 0.08303873045800927, 'fraction_numerical': 0.005813496739728179, 'mean_word_length': 1.789481373265157, 'pattern_counts': {'":': 12, '<': 25, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 50, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2537684', 'n_tokens_mistral': 12286, 'n_tokens_neox': 11862, 'n_words': 2707}