//-----------------------------------------------------------------------------
// Copyright 2007-2018 Cadence Design Systems, Inc.
// Copyright 2017-2018 Cisco Systems, Inc.
// Copyright 2014-2020 Intel Corporation
// Copyright 2021-2022 Marvell International Ltd.
// Copyright 2007-2014 Mentor Graphics Corporation
// Copyright 2013-2020 NVIDIA Corporation
// Copyright 2018 Qualcomm, Inc.
// Copyright 2013-2018 Synopsys, Inc.
//   All Rights Reserved Worldwide
//
//   Licensed under the Apache License, Version 2.0 (the
//   "License"); you may not use this file except in
//   compliance with the License.  You may obtain a copy of
//   the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in
//   writing, software distributed under the License is
//   distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
//   CONDITIONS OF ANY KIND, either express or implied.  See
//   the License for the specific language governing
//   permissions and limitations under the License.
//-----------------------------------------------------------------------------

//------------------------------------------------------------------------------
//
// CLASS -- NODOCS -- uvm_comparer
//
// The uvm_comparer class provides a policy object for doing comparisons. The
// policies determine how miscompares are treated and counted. Results of a
// comparison are stored in the comparer object. The <uvm_object::compare>
// and <uvm_object::do_compare> methods are passed a uvm_comparer policy
// object.
//
//------------------------------------------------------------------------------

// @uvm-ieee 1800.2-2020 auto 16.3.1
class uvm_comparer extends uvm_policy;
	
   // @uvm-ieee 1800.2-2020 auto 16.3.2.3
   `uvm_object_utils(uvm_comparer)

  // @uvm-ieee 1800.2-2020 auto 16.3.2.2
  extern virtual function void flush();

  // @uvm-ieee 1800.2-2020 auto 16.3.3.5
  extern virtual function uvm_policy::recursion_state_e object_compared(
     uvm_object lhs,
     uvm_object rhs,
     uvm_recursion_policy_enum recursion,
     output bit ret_val
  );

  // @uvm-ieee 1800.2-2020 auto 16.3.3.8
  extern virtual function string get_miscompares();

  extern virtual function int unsigned get_result();

  extern virtual function void set_result(int unsigned result) ;

  // @uvm-ieee 1800.2-2020 auto 16.3.4.1
  extern virtual function void set_recursion_policy( uvm_recursion_policy_enum policy);

  // @uvm-ieee 1800.2-2020 auto 16.3.4.1
  extern virtual function uvm_recursion_policy_enum get_recursion_policy();

  // @uvm-ieee 1800.2-2020 auto 16.3.4.2
  extern virtual function void set_check_type( bit enabled );

  // @uvm-ieee 1800.2-2020 auto 16.3.4.2
  extern virtual function bit get_check_type();

  // @uvm-ieee 1800.2-2020 auto 16.3.5.1
  extern virtual function void set_show_max (int unsigned show_max);

  extern virtual function int unsigned get_show_max ();

  // @uvm-ieee 1800.2-2020 auto 16.3.5.2
  extern virtual function void set_verbosity (int unsigned verbosity);

  extern virtual function int unsigned get_verbosity ();

  // @uvm-ieee 1800.2-2020 auto 16.3.5.3
  extern virtual function void set_severity (uvm_severity severity);

  // @uvm-ieee 1800.2-2020 auto 16.3.5.3
  extern virtual function uvm_severity get_severity ();

  // @uvm-ieee 1800.2-2020 auto 16.3.6
  extern virtual function void set_threshold (int unsigned threshold);

  extern virtual function int unsigned get_threshold ();

  //@uvm-compat
  bit physical = 1;

  //@uvm-compat
  bit abstract = 1 ;

  typedef struct {
     recursion_state_e state;
     bit ret_val;
  } state_info_t ;
  state_info_t m_recur_states[uvm_object /*LHS*/][uvm_object /*RHS*/][uvm_recursion_policy_enum /*recursion*/];

  // Variable -- NODOCS -- policy
  //
  // Determines whether comparison is UVM_DEEP, UVM_REFERENCE, or UVM_SHALLOW.
  // @uvm-compat , for compatibility with 1.2
  uvm_recursion_policy_enum policy = UVM_DEFAULT_POLICY;

  // Variable -- NODOCS -- show_max
  //
  // Sets the maximum number of messages to send to the printer for miscompares
  // of an object. 

  // @uvm-compat , for compatibility with 1.2
  int unsigned show_max = 1;

  // Variable -- NODOCS -- verbosity
  //
  // Sets the verbosity for printed messages. 
  //
  // The verbosity setting is used by the messaging mechanism to determine
  // whether messages should be suppressed or shown.

  // @uvm-compat , for compatibility with 1.2
  int unsigned verbosity = UVM_LOW;


  // Variable -- NODOCS -- sev
  //
  // Sets the severity for printed messages. 
  //
  // The severity setting is used by the messaging mechanism for printing and
  // filtering messages.

  // @uvm-compat , for compatibility with 1.2
  uvm_severity sev = UVM_INFO;


  // Variable -- NODOCS -- miscompares
  //
  // This string is reset to an empty string when a comparison is started. 
  //
  // The string holds the last set of miscompares that occurred during a
  // comparison.

  // @uvm-compat , for compatibility with 1.2
  string miscompares = "";

  // Variable -- NODOCS -- check_type
  //
  // This bit determines whether the type, given by <uvm_object::get_type_name>,
  // is used to verify that the types of two objects are the same. 
  //
  // This bit is used by the <compare_object> method. In some cases it is useful
  // to set this to 0 when the two operands are related by inheritance but are
  // different types.

  // @uvm-compat , for compatibility with 1.2
  bit check_type = 1;

  // Variable -- NODOCS -- result
  // 
  // This bit stores the number of miscompares for a given compare operation.
  // You can use the result to determine the number of miscompares that
  // were found.

  // @uvm-compat , for compatibility with 1.2
  int unsigned result = 0;

  local int unsigned m_threshold;

	// @uvm-ieee 1800.2-2020 auto 16.3.2.1
	function new(string name="");
	   super.new(name);
	   m_threshold = 1;
	endfunction


  // @uvm-ieee 1800.2-2020 auto 16.3.2.4
  static function void set_default (uvm_comparer comparer) ;
     uvm_coreservice_t coreservice ;
     coreservice = uvm_coreservice_t::get() ;
     coreservice.set_default_comparer(comparer) ;
  endfunction

  // @uvm-ieee 1800.2-2020 auto 16.3.2.5
  static function uvm_comparer get_default () ;
     uvm_coreservice_t coreservice ;
     coreservice = uvm_coreservice_t::get() ;
     return coreservice.get_default_comparer() ;
  endfunction

   
  // Function -- NODOCS -- compare_field
  //
  // Compares two integral values. 
  //
  // The ~name~ input is used for purposes of storing and printing a miscompare.
  //
  // The left-hand-side ~lhs~ and right-hand-side ~rhs~ objects are the two
  // objects used for comparison. 
  //
  // The size variable indicates the number of bits to compare; size must be
  // less than or equal to 4096. 
  //
  // The radix is used for reporting purposes, the default radix is hex.

  // @uvm-ieee 1800.2-2020 auto 16.3.3.1
  virtual function bit compare_field (string name, 
                                      uvm_bitstream_t lhs, 
                                      uvm_bitstream_t rhs, 
                                      int size,
                                      uvm_radix_enum radix=UVM_NORADIX); 
    uvm_bitstream_t mask;
    string msg;
  
    if(size <= 64)
      return compare_field_int(name, lhs, rhs, size, radix);
  
    mask = -1;
    mask >>= (UVM_STREAMBITS-size);
    if((lhs & mask) !== (rhs & mask)) begin
      case (radix)
        UVM_BIN: begin
              $swrite(msg, "%s: lhs = 'b%0b : rhs = 'b%0b", 
                       name, lhs&mask, rhs&mask);
             end
        UVM_OCT: begin
              $swrite(msg, "%s: lhs = 'o%0o : rhs = 'o%0o", 
                       name, lhs&mask, rhs&mask);
             end
        UVM_DEC: begin
              $swrite(msg, "%s: lhs = %0d : rhs = %0d", 
                       name, lhs&mask, rhs&mask);
             end
        UVM_TIME: begin
            $swrite(msg, "%s: lhs = %0t : rhs = %0t", 
               name, lhs&mask, rhs&mask);
        end
        UVM_STRING: begin
              $swrite(msg, "%s: lhs = %0s : rhs = %0s", 
                       name, lhs&mask, rhs&mask);
             end
        UVM_ENUM: begin
              //Printed as decimal, user should cuse compare string for enum val
              $swrite(msg, "%s: lhs = %0d : rhs = %0d", 
                       name, lhs&mask, rhs&mask);
              end
        default: begin
              $swrite(msg, "%s: lhs = 'h%0x : rhs = 'h%0x", 
                       name, lhs&mask, rhs&mask);
             end
      endcase
      print_msg(msg);
      return 0;
    end
    return 1;
  endfunction

  
  
  // Function -- NODOCS -- compare_field_int
  //
  // This method is the same as <compare_field> except that the arguments are
  // small integers, less than or equal to 64 bits. It is automatically called
  // by <compare_field> if the operand size is less than or equal to 64.

  // @uvm-ieee 1800.2-2020 auto 16.3.3.2
  virtual function bit compare_field_int (string name, 
                                          uvm_integral_t lhs, 
                                          uvm_integral_t rhs, 
                                          int size,
                                          uvm_radix_enum radix=UVM_NORADIX); 
    logic [63:0] mask;
    string msg;
    if(size > 64) begin
        `uvm_error("UVM/COMPARER/INT/BAD_SIZE",$sformatf("compare_field_int cannot be called with operand size of more than 64 bits. Input argument size=%0d",size))
        return 0;
    end
    
    mask = -1;
    mask >>= (64-size);
    if((lhs & mask) !== (rhs & mask)) begin
      case (radix)
        UVM_BIN: begin
              $swrite(msg, "%s: lhs = 'b%0b : rhs = 'b%0b", 
                       name, lhs&mask, rhs&mask);
             end
        UVM_OCT: begin
              $swrite(msg, "%s: lhs = 'o%0o : rhs = 'o%0o", 
                       name, lhs&mask, rhs&mask);
             end
        UVM_DEC: begin
              $swrite(msg, "%s: lhs = %0d : rhs = %0d", 
                       name, lhs&mask, rhs&mask);
             end
        UVM_TIME: begin
            $swrite(msg, "%s: lhs = %0t : rhs = %0t", 
               name, lhs&mask, rhs&mask);
        end
        UVM_STRING: begin
              $swrite(msg, "%s: lhs = %0s : rhs = %0s", 
                       name, lhs&mask, rhs&mask);
             end
        UVM_ENUM: begin
              //Printed as decimal, user should cuse compare string for enum val
              $swrite(msg, "%s: lhs = %0d : rhs = %0d", 
                       name, lhs&mask, rhs&mask);
              end
        default: begin
              $swrite(msg, "%s: lhs = 'h%0x : rhs = 'h%0x", 
                       name, lhs&mask, rhs&mask);
             end
      endcase
      print_msg(msg);
      return 0;
    end
    return 1;
  endfunction


  // Function -- NODOCS -- compare_field_real
  //
  // This method is the same as <compare_field> except that the arguments are
  // real numbers.

  // @uvm-ieee 1800.2-2020 auto 16.3.3.3
  virtual function bit compare_field_real (string name, 
                                          real lhs, 
                                          real rhs);
    string msg;
  
    if(lhs != rhs) begin
      $swrite(msg, name, ": lhs = ", lhs, " : rhs = ", rhs);
      print_msg(msg);
      return 0;
    end
    return 1;
  endfunction

  // Stores the passed-in names of the objects in the hierarchy
  local string m_object_names[$];
  local function string m_current_context(string name="");
    if (m_object_names.size()  == 0)
      return name; //??
    else if ((m_object_names.size() == 1) && (name==""))
      return m_object_names[0];
    else begin
      string     full_name;
      foreach(m_object_names[i]) begin
        if (i == m_object_names.size() - 1)
          full_name = {full_name, m_object_names[i]};
        else
          full_name  = {full_name, m_object_names[i], "."};
      end
      if (name != "")
        return {full_name, ".", name};
      else
        return full_name;
    end
  endfunction : m_current_context
  
  // Function -- NODOCS -- compare_object
  //
  // Compares two class objects using the <policy> knob to determine whether the
  // comparison should be deep, shallow, or reference. 
  //
  // The name input is used for purposes of storing and printing a miscompare. 
  //
  // The ~lhs~ and ~rhs~ objects are the two objects used for comparison. 
  //
  // The ~check_type~ determines whether or not to verify the object
  // types match (the return from ~lhs.get_type_name()~ matches
  // ~rhs.get_type_name()~).

  // @uvm-ieee 1800.2-2020 auto 16.3.3.4
  virtual function bit compare_object (string name,
                                       uvm_object lhs,
                                       uvm_object rhs);
    int old_result ;
    uvm_field_op field_op ;
    uvm_policy::recursion_state_e prev_state;
    bit ret_val = 1;

    // Fast Pass
    if (rhs == lhs)
      return ret_val;

    // Push the name on the stack
    m_object_names.push_back(name);

    // Reference Fail
    if (policy == UVM_REFERENCE && lhs != rhs) begin
      print_msg_object(lhs, rhs);
      ret_val = 0;
    end

    // Fast fail on null
    if (ret_val && (rhs == null || lhs == null)) begin
      print_msg_object(lhs, rhs);
      // if ((get_active_object_depth() == 0) && (lhs != null)) begin
      //   uvm_report_info("MISCMP",
      //                   $sformatf("%0d Miscompare(s) for object %s@%0d vs. null", 
      //                             result, 
      //                             lhs.get_name(),
      //                             lhs.get_inst_id()),
      //                   get_verbosity());
      // end
      ret_val = 0;
    end

    // Hierarchical comparison
    if (ret_val) begin
      // Warn on possible infinite loop
      prev_state      = object_compared(lhs,rhs,get_recursion_policy(),ret_val);
      if (prev_state != uvm_policy::NEVER) // 
        `uvm_warning("UVM/COPIER/LOOP", {"Possible loop when comparing '", 
                                         lhs.get_full_name(), "' to '", rhs.get_full_name(), "'"})
    

      push_active_object(lhs);
      m_recur_states[lhs][rhs][get_recursion_policy()]  = '{uvm_policy::STARTED,0};
      old_result = get_result();

      // Check typename
      // Implemented as if Mantis 6602 was accepted
      if (get_check_type() && (lhs.get_object_type() != rhs.get_object_type())) begin
        if(lhs.get_type_name() != rhs.get_type_name()) begin
          print_msg({"type: lhs = \"", lhs.get_type_name(), "\" : rhs = \"", rhs.get_type_name(), "\""});
        end
        else begin
          print_msg({"get_object_type() for ",lhs.get_name()," does not match get_object_type() for ",rhs.get_name()}); 
        end  
      end

      field_op = uvm_field_op::m_get_available_op();
      field_op.set(UVM_COMPARE,this,rhs);
      lhs.do_execute_op(field_op);
      if (field_op.user_hook_enabled()) begin
        ret_val = lhs.do_compare(rhs,this);
      end
      field_op.m_recycle();

      // If do_compare() returned 1, check for a change
      // in the result count.
      if (ret_val && (get_result() > old_result))
        ret_val = 0;

      // Save off the comparison result
      m_recur_states[lhs][rhs][get_recursion_policy()]  = '{uvm_policy::FINISHED,ret_val};
      void'(pop_active_object());
    end // if (ret_val)

    // Pop the name off the stack
    void'(m_object_names.pop_back());

    // Only emit a message on a miscompare, and only if
    // we're at the top level
    if (!ret_val && (get_active_object_depth() == 0)) begin
      string msg ;

      // If there are stored results
      if(get_result()) begin
        // If there's a display limit that we've hit
        if (get_show_max() && (get_show_max() < get_result())) 
          $swrite(msg, "%0d Miscompare(s) (%0d shown) for object ",
                  result, show_max);
        // Else there's either no limit, or we didn't hit it
        else
          $swrite(msg, "%0d Miscompare(s) for object ", result);
      end

      uvm_pkg::uvm_report(sev, "MISCMP", $sformatf("%s%s@%0d vs. %s@%0d", msg,
                                                   (lhs == null) ? "<null>" : lhs.get_name(), 
                                                   (lhs == null) ? 0 : lhs.get_inst_id(), 
                                                   (rhs == null) ? "<null>" : rhs.get_name(), 
                                                   (rhs == null) ? 0 : rhs.get_inst_id()),
			  get_verbosity(), `uvm_file, `uvm_line);
        
    end // if (!ret_val && (get_active_object_depth() == 1))
    
    return ret_val;
  endfunction
  
  
  // Function -- NODOCS -- compare_string
  //
  // Compares two string variables. 
  //
  // The ~name~ input is used for purposes of storing and printing a miscompare. 
  //
  // The ~lhs~ and ~rhs~ objects are the two objects used for comparison.

  // @uvm-ieee 1800.2-2020 auto 16.3.3.6
  virtual function bit compare_string (string name,
                                       string lhs,
                                       string rhs);
    string msg;
    if(lhs != rhs) begin
      msg = { name, ": lhs = \"", lhs, "\" : rhs = \"", rhs, "\""};
      print_msg(msg);
      return 0;
    end
    return 1;
  endfunction


  // Function -- NODOCS -- print_msg
  //
  // Causes the error count to be incremented and the message, ~msg~, to be
  // appended to the <miscompares> string (a newline is used to separate
  // messages). 
  //
  // If the message count is less than the <show_max> setting, then the message
  // is printed to standard-out using the current verbosity and severity
  // settings. See the <verbosity> and <sev> variables for more information.

  // @uvm-ieee 1800.2-2020 auto 16.3.3.7
  function void print_msg (string msg);
     
    string tmp = m_current_context(msg);
    result++;
    if((get_show_max() == 0) ||
       (get_result() <= get_show_max())) begin
      msg = {"Miscompare for ", tmp};
       uvm_pkg::uvm_report(sev, "MISCMP", msg, get_verbosity(), `uvm_file, `uvm_line);
    end
    miscompares = { miscompares, tmp, "\n" };
  endfunction



  // Internal methods - do not call directly

  // print_msg_object
  // ----------------

  function void print_msg_object(uvm_object lhs, uvm_object rhs);
    string tmp = $sformatf("%s: lhs = @%0d : rhs = @%0d",
                           m_current_context(), 
                           (lhs != null ? lhs.get_inst_id() : 0), 	
                           (rhs != null ? rhs.get_inst_id() : 0));
    result++;
    
    if((get_show_max() == 0) ||
       (get_result() <= get_show_max())) begin
       uvm_pkg::uvm_report(sev, 
                           "MISCMP", 
                           {"Miscompare for ", tmp}, 
                           get_verbosity(), 
                           `uvm_file, 
                           `uvm_line);
    end

    miscompares = { miscompares, tmp, "\n" };
  endfunction

  int depth;                      //current depth of objects
  bit compare_map[uvm_object][uvm_object];

endclass

function void uvm_comparer::flush();
  miscompares = "" ;
  check_type = 1 ;
  result = 0 ;
  m_recur_states.delete();
endfunction

function uvm_policy::recursion_state_e uvm_comparer::object_compared(
  uvm_object lhs,
  uvm_object rhs,
  uvm_recursion_policy_enum recursion,
  output bit ret_val
);
  if (!m_recur_states.exists(lhs)) return NEVER ;
  else if (!m_recur_states[lhs].exists(rhs)) return NEVER ;
  else if (!m_recur_states[lhs][rhs].exists(recursion)) return NEVER ;
  else begin
     if (m_recur_states[lhs][rhs][recursion].state == FINISHED) 
        ret_val = m_recur_states[lhs][rhs][recursion].ret_val;
     return m_recur_states[lhs][rhs][recursion].state ;
  end
endfunction

function string uvm_comparer::get_miscompares();
   return miscompares ;
endfunction

function int unsigned uvm_comparer::get_result();
   return result ;
endfunction

function void uvm_comparer::set_result(int unsigned result);
   this.result = result ;
endfunction

function void uvm_comparer::set_recursion_policy( uvm_recursion_policy_enum policy);
   this.policy = policy ;
endfunction

function uvm_recursion_policy_enum uvm_comparer::get_recursion_policy();
   return policy ;
endfunction

function void uvm_comparer::set_check_type( bit enabled );
   check_type = enabled ;
endfunction

function bit uvm_comparer::get_check_type();
   return check_type ;
endfunction

function void uvm_comparer::set_show_max (int unsigned show_max);
   this.show_max = show_max ;
endfunction

function int unsigned uvm_comparer::get_show_max();
   return show_max ;
endfunction

function void uvm_comparer::set_verbosity (int unsigned verbosity);
   this.verbosity = verbosity ;
endfunction

function int unsigned uvm_comparer::get_verbosity();
   return verbosity ;
endfunction

function void uvm_comparer::set_severity (uvm_severity severity);
   sev = severity ;
endfunction

function uvm_severity uvm_comparer::get_severity();
   return sev ;
endfunction

function void uvm_comparer::set_threshold (int unsigned threshold);
   m_threshold = threshold;
endfunction

function int unsigned uvm_comparer::get_threshold();
   return m_threshold;
endfunction

