//
//----------------------------------------------------------------------
//   Copyright 2007-2011 Mentor Graphics Corporation
//   Copyright 2007-2011 Cadence Design Systems, Inc. 
//   Copyright 2010-2011 Synopsys, Inc.
//   Copyright 2013      NVIDIA Corporation
//   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.
//----------------------------------------------------------------------

`ifndef UVM_OBJECTION_SVH
`define UVM_OBJECTION_SVH

typedef class uvm_objection_context_object;
typedef class uvm_objection;
typedef class uvm_sequence_base;
typedef class uvm_objection_callback;
typedef uvm_callbacks #(uvm_objection,uvm_objection_callback) uvm_objection_cbs_t;
typedef class uvm_cmdline_processor;

class uvm_objection_events;
  int waiters;
  event raised;
  event dropped;
  event all_dropped;
endclass

//------------------------------------------------------------------------------
// Title: Objection Mechanism
//------------------------------------------------------------------------------
// The following classes define the objection mechanism and end-of-test
// functionality, which is based on <uvm_objection>.
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//
// Class: uvm_objection
//
//------------------------------------------------------------------------------
// Objections provide a facility for coordinating status information between
// two or more participating components, objects, and even module-based IP.
//
// Tracing of objection activity can be turned on to follow the activity of
// the objection mechanism. It may be turned on for a specific objection
// instance with <uvm_objection::trace_mode>, or it can be set for all 
// objections from the command line using the option +UVM_OBJECTION_TRACE.
//------------------------------------------------------------------------------

class uvm_objection extends uvm_report_object;
  `uvm_register_cb(uvm_objection, uvm_objection_callback)

  protected bit     m_trace_mode;
  protected int     m_source_count[uvm_object];
  protected int     m_total_count [uvm_object];
  protected time    m_drain_time  [uvm_object];
  protected uvm_objection_events m_events [uvm_object];
  /*protected*/ bit     m_top_all_dropped;

  protected uvm_root m_top;
     
  static uvm_objection m_objections[$];

  //// Drain Logic

  // The context pool holds used context objects, so that
  // they're not constantly being recreated.  The maximum
  // number of contexts in the pool is equal to the maximum
  // number of simultaneous drains you could have occuring,
  // both pre and post forks.
  //
  // There's the potential for a programmability within the
  // library to dictate the largest this pool should be allowed
  // to grow, but that seems like overkill for the time being.
  local static uvm_objection_context_object m_context_pool[$];

  // These are the active drain processes, which have been
  // forked off by the background process.  A raise can
  // use this array to kill a drain.
`ifndef UVM_USE_PROCESS_CONTAINER   
  local process m_drain_proc[uvm_object];
`else
  local process_container_c m_drain_proc[uvm_object];
`endif
   
  // These are the contexts which have been scheduled for
  // retrieval by the background process, but which the
  // background process hasn't seen yet.
  local static uvm_objection_context_object m_scheduled_list[$];

  // Once a context is seen by the background process, it is
  // removed from the scheduled list, and placed in the forked
  // list.  At the same time, it is placed in the scheduled
  // contexts array.  A re-raise can use the scheduled contexts
  // array to detect (and cancel) the drain.
  local uvm_objection_context_object m_scheduled_contexts[uvm_object];
  local uvm_objection_context_object m_forked_list[$];

  // Once the forked drain has actually started (this occurs
  // ~1 delta AFTER the background process schedules it), the
  // context is removed from the above array and list, and placed
  // in the forked_contexts list.  
  local uvm_objection_context_object m_forked_contexts[uvm_object];

  protected bit m_prop_mode = 1;
  protected bit m_cleared; /* for checking obj count<0 */


  // Function: new
  //
  // Creates a new objection instance. Accesses the command line
  // argument +UVM_OBJECTION_TRACE to turn tracing on for
  // all objection objects.

  function new(string name="");
    uvm_cmdline_processor clp;
    uvm_coreservice_t cs_ ;
    string trace_args[$];
    super.new(name);
    cs_ = uvm_coreservice_t::get();
    m_top  = cs_.get_root();
     
    set_report_verbosity_level(m_top.get_report_verbosity_level());

    // Get the command line trace mode setting
    clp = uvm_cmdline_processor::get_inst();
    if(clp.get_arg_matches("+UVM_OBJECTION_TRACE", trace_args)) begin
      m_trace_mode=1;
    end
    m_objections.push_back(this);
  endfunction


  // Function: trace_mode
  //
  // Set or get the trace mode for the objection object. If no
  // argument is specified (or an argument other than 0 or 1)
  // the current trace mode is unaffected. A trace_mode of
  // 0 turns tracing off. A trace mode of 1 turns tracing on.
  // The return value is the mode prior to being reset.

   function bit trace_mode (int mode=-1);
    trace_mode = m_trace_mode;
    if(mode == 0) m_trace_mode = 0;
    else if(mode == 1) m_trace_mode = 1;
   endfunction

  // Function- m_report
  //
  // Internal method for reporting count updates

  function void m_report(uvm_object obj, uvm_object source_obj, string description, int count, string action);
    string desc;
    int _count = m_source_count.exists(obj) ? m_source_count[obj] : 0;
    int _total = m_total_count.exists(obj) ? m_total_count[obj] : 0;
    if (!uvm_report_enabled(UVM_NONE,UVM_INFO,"OBJTN_TRC") || !m_trace_mode) return;

    //desc = description == "" ? "" : {" ", description, "" };
    if (source_obj == obj)

      uvm_report_info("OBJTN_TRC", 
        $sformatf("Object %0s %0s %0d objection(s)%s: count=%0d  total=%0d",
           obj.get_full_name()==""?"uvm_top":obj.get_full_name(), action,
           count, description != ""? {" (",description,")"}:"", _count, _total), UVM_NONE);
    else begin
      int cpath = 0, last_dot=0;
      string sname = source_obj.get_full_name(), nm = obj.get_full_name();
      int max = sname.len() > nm.len() ? nm.len() : sname.len();

      // For readability, only print the part of the source obj hierarchy underneath
      // the current object.
      while((sname[cpath] == nm[cpath]) && (cpath < max)) begin
        if(sname[cpath] == ".") last_dot = cpath;
        cpath++;
      end 

      if(last_dot) sname = sname.substr(last_dot+1, sname.len());
      uvm_report_info("OBJTN_TRC",
        $sformatf("Object %0s %0s %0d objection(s) %0s its total (%s from source object %s%s): count=%0d  total=%0d",
           obj.get_full_name()==""?"uvm_top":obj.get_full_name(), action=="raised"?"added":"subtracted",
            count, action=="raised"?"to":"from", action, sname, 
            description != ""?{", ",description}:"", _count, _total), UVM_NONE);
    end
  endfunction


  // Function- m_get_parent
  //
  // Internal method for getting the parent of the given ~object~.
  // The ultimate parent is uvm_top, UVM's implicit top-level component. 

  function uvm_object m_get_parent(uvm_object obj);
    uvm_component comp;
    uvm_sequence_base seq;
    if ($cast(comp, obj)) begin
      obj = comp.get_parent();
    end
    else if ($cast(seq, obj)) begin
       obj = seq.get_sequencer();
    end
    else
      obj = m_top;
    if (obj == null)
      obj = m_top;
    return obj;
  endfunction


  // Function- m_propagate
  //
  // Propagate the objection to the objects parent. If the object is a
  // component, the parent is just the hierarchical parent. If the object is
  // a sequence, the parent is the parent sequence if one exists, or
  // it is the attached sequencer if there is no parent sequence. 
  //
  // obj : the uvm_object on which the objection is being raised or lowered
  // source_obj : the root object on which the end user raised/lowered the 
  //   objection (as opposed to an anscestor of the end user object)a
  // count : the number of objections associated with the action.
  // raise : indicator of whether the objection is being raised or lowered. A
  //   1 indicates the objection is being raised.

  function void m_propagate (uvm_object obj,
                             uvm_object source_obj,
                             string description,
                             int count,
                             bit raise,
                             int in_top_thread);
    if (obj != null && obj != m_top) begin
      obj = m_get_parent(obj);
      if(raise)
        m_raise(obj, source_obj, description, count);
      else
        m_drop(obj, source_obj, description, count, in_top_thread);
    end
  endfunction


  // Group: Objection Control

  // Function: set_propagate_mode
  // Sets the propagation mode for this objection.
  //
  // By default, objections support hierarchical propagation for
  // components.  For example, if we have the following basic
  // component tree:
  //
  //| uvm_top.parent.child
  //
  // Any objections raised by 'child' would get propagated
  // down to parent, and then to uvm_test_top.  Resulting in the
  // following counts and totals:
  //
  //|                      | count | total |
  //| uvm_top.parent.child |     1 |    1  |
  //| uvm_top.parent       |     0 |    1  |
  //| uvm_top              |     0 |    1  |
  //|
  // 
  // While propagations such as these can be useful, if they are
  // unused by the testbench then they are simply an unnecessary
  // performance hit.  If the testbench is not going to use this
  // functionality, then the performance can be improved by setting
  // the propagation mode to 0.
  //
  // When propagation mode is set to 0, all intermediate callbacks
  // between the ~source~ and ~top~ will be skipped.  This would
  // result in the following counts and totals for the above objection:
  //  
  //|                      | count | total |
  //| uvm_top.parent.child |     1 |    1  |
  //| uvm_top.parent       |     0 |    0  |
  //| uvm_top              |     0 |    1  |
  //|
  //
  // Since the propagation mode changes the behavior of the objection,
  // it can only be safely changed if there are no objections ~raised~ 
  // or ~draining~.  Any attempts to change the mode while objections
  // are ~raised~ or ~draining~ will result in an error.
  //
  function void set_propagate_mode (bit prop_mode);
     if (!m_top_all_dropped && (get_objection_total() != 0)) begin
        `uvm_error("UVM/BASE/OBJTN/PROP_MODE",
                   {"The propagation mode of '", this.get_full_name(),
                    "' cannot be changed while the objection is raised ",
                    "or draining!"})
        return;
     end

     m_prop_mode = prop_mode;
  endfunction : set_propagate_mode

  // Function: get_propagate_mode
  // Returns the propagation mode for this objection.
  function bit get_propagate_mode();
     return m_prop_mode;
  endfunction : get_propagate_mode
   
  // Function: raise_objection
  //
  // Raises the number of objections for the source ~object~ by ~count~, which
  // defaults to 1.  The ~object~ is usually the ~this~ handle of the caller.
  // If ~object~ is not specified or ~null~, the implicit top-level component,
  // <uvm_root>, is chosen.
  //
  // Raising an objection causes the following.
  //
  // - The source and total objection counts for ~object~ are increased by
  //   ~count~. ~description~ is a string that marks a specific objection
  //   and is used in tracing/debug.
  //
  // - The objection's <raised> virtual method is called, which calls the
  //   <uvm_component::raised> method for all of the components up the 
  //   hierarchy.
  //

  virtual function void raise_objection (uvm_object obj=null,
                                         string description="",
                                         int count=1);
    if(obj == null)
      obj = m_top;
    m_cleared = 0;
    m_top_all_dropped = 0;
    m_raise (obj, obj, description, count);
  endfunction


  // Function- m_raise

  function void m_raise (uvm_object obj,
                         uvm_object source_obj,
                         string description="",
                         int count=1);
    int idx;
    uvm_objection_context_object ctxt;

    // Ignore raise if count is 0
    if (count == 0)
      return;

    if (m_total_count.exists(obj))
      m_total_count[obj] += count;
    else 
      m_total_count[obj] = count;

    if (source_obj==obj) begin
      if (m_source_count.exists(obj))
        m_source_count[obj] += count;
      else
        m_source_count[obj] = count;
    end
  
    if (m_trace_mode)
      m_report(obj,source_obj,description,count,"raised");

    raised(obj, source_obj, description, count);

      // Handle any outstanding drains...

    // First go through the scheduled list
    idx = 0;
    while (idx < m_scheduled_list.size()) begin
        if ((m_scheduled_list[idx].obj == obj) &&
            (m_scheduled_list[idx].objection == this)) begin
            // Caught it before the drain was forked
            ctxt = m_scheduled_list[idx];
            m_scheduled_list.delete(idx);
            break;
        end
        idx++;
    end

    // If it's not there, go through the forked list
    if (ctxt == null) begin
        idx = 0;
        while (idx < m_forked_list.size()) begin
            if (m_forked_list[idx].obj == obj) begin
                // Caught it after the drain was forked,
                // but before the fork started
                ctxt = m_forked_list[idx];
                m_forked_list.delete(idx);
                m_scheduled_contexts.delete(ctxt.obj);
                break;
            end
            idx++;
        end
    end

    // If it's not there, go through the forked contexts
    if (ctxt == null) begin
        if (m_forked_contexts.exists(obj)) begin
            // Caught it with the forked drain running
            ctxt = m_forked_contexts[obj];
            m_forked_contexts.delete(obj);
            // Kill the drain
`ifndef UVM_USE_PROCESS_CONTAINER	   
            m_drain_proc[obj].kill();
            m_drain_proc.delete(obj);
`else
            m_drain_proc[obj].p.kill();
            m_drain_proc.delete(obj);
`endif
	   
        end
    end

    if (ctxt == null) begin
        // If there were no drains, just propagate as usual

        if (!m_prop_mode && obj != m_top)
          m_raise(m_top,source_obj,description,count);
        else if (obj != m_top)
          m_propagate(obj, source_obj, description, count, 1, 0);
    end
    else begin
        // Otherwise we need to determine what exactly happened
        int diff_count;

        // Determine the diff count, if it's positive, then we're
        // looking at a 'raise' total, if it's negative, then
        // we're looking at a 'drop', but not down to 0.  If it's
        // a 0, that means that there is no change in the total.
        diff_count = count - ctxt.count;

        if (diff_count != 0) begin
            // Something changed
            if (diff_count > 0) begin
                // we're looking at an increase in the total
                if (!m_prop_mode && obj != m_top)
                  m_raise(m_top, source_obj, description, diff_count);
                else if (obj != m_top)
                  m_propagate(obj, source_obj, description, diff_count, 1, 0);
            end
            else begin
                // we're looking at a decrease in the total
                // The count field is always positive...
                diff_count = -diff_count;
                if (!m_prop_mode && obj != m_top)
                  m_drop(m_top, source_obj, description, diff_count);
                else if (obj != m_top)
                  m_propagate(obj, source_obj, description, diff_count, 0, 0);
            end
        end

        // Cleanup
        ctxt.clear();
        m_context_pool.push_back(ctxt);
    end
        
  endfunction
  

  // Function: drop_objection
  //
  // Drops the number of objections for the source ~object~ by ~count~, which
  // defaults to 1.  The ~object~ is usually the ~this~ handle of the caller.
  // If ~object~ is not specified or ~null~, the implicit top-level component,
  // <uvm_root>, is chosen.
  //
  // Dropping an objection causes the following.
  //
  // - The source and total objection counts for ~object~ are decreased by
  //   ~count~. It is an error to drop the objection count for ~object~ below
  //   zero.
  //
  // - The objection's <dropped> virtual method is called, which calls the
  //   <uvm_component::dropped> method for all of the components up the 
  //   hierarchy.
  //
  // - If the total objection count has not reached zero for ~object~, then
  //   the drop is propagated up the object hierarchy as with
  //   <raise_objection>. Then, each object in the hierarchy will have updated
  //   their ~source~ counts--objections that they originated--and ~total~
  //   counts--the total number of objections by them and all their
  //   descendants.
  //
  // If the total objection count reaches zero, propagation up the hierarchy
  // is deferred until a configurable drain-time has passed and the 
  // <uvm_component::all_dropped> callback for the current hierarchy level
  // has returned. The following process occurs for each instance up
  // the hierarchy from the source caller:
  //
  // A process is forked in a non-blocking fashion, allowing the ~drop~
  // call to return. The forked process then does the following:
  //
  // - If a drain time was set for the given ~object~, the process waits for
  //   that amount of time.
  //
  // - The objection's <all_dropped> virtual method is called, which calls the
  //   <uvm_component::all_dropped> method (if ~object~ is a component).
  //
  // - The process then waits for the ~all_dropped~ callback to complete.
  //
  // - After the drain time has elapsed and all_dropped callback has
  //   completed, propagation of the dropped objection to the parent proceeds
  //   as described in <raise_objection>, except as described below.
  //
  // If a new objection for this ~object~ or any of its descendants is raised
  // during the drain time or during execution of the all_dropped callback at
  // any point, the hierarchical chain described above is terminated and the
  // dropped callback does not go up the hierarchy. The raised objection will
  // propagate up the hierarchy, but the number of raised propagated up is
  // reduced by the number of drops that were pending waiting for the 
  // all_dropped/drain time completion. Thus, if exactly one objection
  // caused the count to go to zero, and during the drain exactly one new
  // objection comes in, no raises or drops are propagated up the hierarchy,
  //
  // As an optimization, if the ~object~ has no set drain-time and no
  // registered callbacks, the forked process can be skipped and propagation
  // proceeds immediately to the parent as described. 

  virtual function void drop_objection (uvm_object obj=null,
                                        string description="",
                                        int count=1);
    if(obj == null)
      obj = m_top;
    m_drop (obj, obj, description, count, 0);
  endfunction


  // Function- m_drop

  function void m_drop (uvm_object obj,
                        uvm_object source_obj,
                        string description="",
                        int count=1,
                        int in_top_thread=0);

    // Ignore drops if the count is 0
    if (count == 0)
      return;

    if (!m_total_count.exists(obj) || (count > m_total_count[obj])) begin
      if(m_cleared)
        return;
      uvm_report_fatal("OBJTN_ZERO", {"Object \"", obj.get_full_name(), 
        "\" attempted to drop objection '",this.get_name(),"' count below zero"});
      return;
    end

    if (obj == source_obj) begin
      if (!m_source_count.exists(obj) || (count > m_source_count[obj])) begin
        if(m_cleared)
          return;
        uvm_report_fatal("OBJTN_ZERO", {"Object \"", obj.get_full_name(), 
          "\" attempted to drop objection '",this.get_name(),"' count below zero"});
        return;
      end
      m_source_count[obj] -= count;
    end

    m_total_count[obj] -= count;

    if (m_trace_mode)
      m_report(obj,source_obj,description,count,"dropped");
    
    dropped(obj, source_obj, description, count);
  
    // if count != 0, no reason to fork
    if (m_total_count[obj] != 0) begin
      if (!m_prop_mode && obj != m_top)
        m_drop(m_top,source_obj,description, count, in_top_thread);
      else if (obj != m_top) begin
        this.m_propagate(obj, source_obj, description, count, 0, in_top_thread);
      end

    end
    else begin
        uvm_objection_context_object ctxt;
        if (m_context_pool.size())
          ctxt = m_context_pool.pop_front();
        else
          ctxt = new;

        ctxt.obj = obj;
        ctxt.source_obj = source_obj;
        ctxt.description = description;
        ctxt.count = count;
        ctxt.objection = this;
        // Need to be thread-safe, let the background
        // process handle it.

        // Why don't we look at in_top_thread here?  Because
        // a re-raise will kill the drain at object that it's
        // currently occuring at, and we need the leaf-level kills
        // to not cause accidental kills at branch-levels in
        // the propagation.

        // Using the background process just allows us to
        // separate the links of the chain.
        m_scheduled_list.push_back(ctxt);

    end // else: !if(m_total_count[obj] != 0)

  endfunction


  // Function: clear
  //
  // Immediately clears the objection state. All counts are cleared and the
  // any processes waiting on a call to wait_for(UVM_ALL_DROPPED, uvm_top)
  // are released.
  //
  // The caller, if a uvm_object-based object, should pass its 'this' handle
  // to the ~obj~ argument to document who cleared the objection.
  // Any drain_times set by the user are not affected. 
  //
  virtual function void clear(uvm_object obj=null);
    string name;
    uvm_objection_context_object ctxt;
    int  idx;

    if (obj==null)
      obj=m_top;
    name = obj.get_full_name();
    if (name == "")
      name = "uvm_top";
    else
      name = obj.get_full_name();
    if (!m_top_all_dropped && get_objection_total(m_top))
      uvm_report_warning("OBJTN_CLEAR",{"Object '",name,
            "' cleared objection counts for ",get_name()});
    //Should there be a warning if there are outstanding objections?
    m_source_count.delete();
    m_total_count.delete();

    // Remove any scheduled drains from the static queue
    idx = 0;
    while (idx < m_scheduled_list.size()) begin
        if (m_scheduled_list[idx].objection == this) begin
            m_scheduled_list[idx].clear();
            m_context_pool.push_back(m_scheduled_list[idx]);
            m_scheduled_list.delete(idx);
        end
        else begin
            idx++;
        end
    end

    // Scheduled contexts and m_forked_lists have duplicate
    // entries... clear out one, free the other.
    m_scheduled_contexts.delete();
    while (m_forked_list.size()) begin
        m_forked_list[0].clear();
        m_context_pool.push_back(m_forked_list[0]);
        void'(m_forked_list.pop_front());
    end

    // running drains have a context and a process
    foreach (m_forked_contexts[o]) begin
`ifndef UVM_USE_PROCESS_CONTAINER       
        m_drain_proc[o].kill();
        m_drain_proc.delete(o);
`else
        m_drain_proc[o].p.kill();
        m_drain_proc.delete(o);
`endif
       
        m_forked_contexts[o].clear();
        m_context_pool.push_back(m_forked_contexts[o]);
        m_forked_contexts.delete(o);
    end

    m_top_all_dropped = 0;
    m_cleared = 1;
    if (m_events.exists(m_top))
      ->m_events[m_top].all_dropped;

  endfunction

  // m_execute_scheduled_forks
  // -------------------------

  // background process; when non
  static task m_execute_scheduled_forks();
    while(1) begin
      wait(m_scheduled_list.size() != 0);
      if(m_scheduled_list.size() != 0) begin
          uvm_objection_context_object c;
          uvm_objection o;
          // Save off the context before the fork
          c = m_scheduled_list.pop_front();
          // A re-raise can use this to figure out props (if any)
          c.objection.m_scheduled_contexts[c.obj] = c;
          // The fork below pulls out from the forked list
          c.objection.m_forked_list.push_back(c);
          // The fork will guard the m_forked_drain call, but
          // a re-raise can kill m_forked_list contexts in the delta
          // before the fork executes.
          fork : guard
              automatic uvm_objection objection = c.objection;
              begin
                  // Check to maike sure re-raise didn't empty the fifo
                  if (objection.m_forked_list.size() > 0) begin
                      uvm_objection_context_object ctxt;
	              ctxt = objection.m_forked_list.pop_front();
                      // Clear it out of scheduled
                      objection.m_scheduled_contexts.delete(ctxt.obj);
                      // Move it in to forked (so re-raise can figure out props)
                      objection.m_forked_contexts[ctxt.obj] = ctxt;
                      // Save off our process handle, so a re-raise can kill it...
`ifndef UVM_USE_PROCESS_CONTAINER		     
                      objection.m_drain_proc[ctxt.obj] = process::self();
`else
		     begin
			process_container_c c = new(process::self());
			objection.m_drain_proc[ctxt.obj]=c;
		     end
`endif		     
                      // Execute the forked drain
                      objection.m_forked_drain(ctxt.obj, ctxt.source_obj, ctxt.description, ctxt.count, 1);
                      // Cleanup if we survived (no re-raises)
                      objection.m_drain_proc.delete(ctxt.obj);
                      objection.m_forked_contexts.delete(ctxt.obj);
                      // Clear out the context object (prevent memory leaks)
                      ctxt.clear();
                      // Save the context in the pool for later reuse
                      m_context_pool.push_back(ctxt);
                  end
              end
          join_none : guard
      end
    end
  endtask


  // m_forked_drain
  // -------------

  task m_forked_drain (uvm_object obj,
                       uvm_object source_obj,
                       string description="",
                       int count=1,
                       int in_top_thread=0);

      int diff_count;

      if (m_drain_time.exists(obj))
        `uvm_delay(m_drain_time[obj])
      
      if (m_trace_mode)
        m_report(obj,source_obj,description,count,"all_dropped");
      
      all_dropped(obj,source_obj,description, count);
          
          // wait for all_dropped cbs to complete
      wait fork;

      /* NOT NEEDED - Any raise would have killed us!
      if(!m_total_count.exists(obj))
        diff_count = -count;
      else
        diff_count = m_total_count[obj] - count;
      */

      // we are ready to delete the 0-count entries for the current
      // object before propagating up the hierarchy. 
      if (m_source_count.exists(obj) && m_source_count[obj] == 0)
        m_source_count.delete(obj);
          
      if (m_total_count.exists(obj) && m_total_count[obj] == 0)
        m_total_count.delete(obj);

      if (!m_prop_mode && obj != m_top)
        m_drop(m_top,source_obj,description, count, 1);
      else if (obj != m_top)
        m_propagate(obj, source_obj, description, count, 0, 1);

  endtask


  // m_init_objections
  // -----------------

  // Forks off the single background process
  static function void m_init_objections();
    fork 
      uvm_objection::m_execute_scheduled_forks();
    join_none
  endfunction

  // Function: set_drain_time
  //
  // Sets the drain time on the given ~object~ to ~drain~.
  //
  // The drain time is the amount of time to wait once all objections have
  // been dropped before calling the all_dropped callback and propagating
  // the objection to the parent. 
  //
  // If a new objection for this ~object~ or any of its descendants is raised
  // during the drain time or during execution of the all_dropped callbacks,
  // the drain_time/all_dropped execution is terminated. 

  // AE: set_drain_time(drain,obj=null)?
  function void set_drain_time (uvm_object obj=null, time drain);
    if (obj==null)
      obj = m_top;
    m_drain_time[obj] = drain;
  endfunction
  

  //----------------------
  // Group: Callback Hooks
  //----------------------

  // Function: raised
  //
  // Objection callback that is called when a <raise_objection> has reached ~obj~.
  // The default implementation calls <uvm_component::raised>.

  virtual function void raised (uvm_object obj,
                                uvm_object source_obj,
                                string description,
                                int count);
    uvm_component comp;
    if ($cast(comp,obj))    
      comp.raised(this, source_obj, description, count);
    `uvm_do_callbacks(uvm_objection,uvm_objection_callback,raised(this,obj,source_obj,description,count))
    if (m_events.exists(obj))
       ->m_events[obj].raised;
  endfunction


  // Function: dropped
  //
  // Objection callback that is called when a <drop_objection> has reached ~obj~.
  // The default implementation calls <uvm_component::dropped>.

  virtual function void dropped (uvm_object obj,
                                 uvm_object source_obj,
                                 string description,
                                 int count);
    uvm_component comp;
    if($cast(comp,obj))    
      comp.dropped(this, source_obj, description, count);
    `uvm_do_callbacks(uvm_objection,uvm_objection_callback,dropped(this,obj,source_obj,description,count))
    if (m_events.exists(obj))
       ->m_events[obj].dropped;
  endfunction


  // Function: all_dropped
  //
  // Objection callback that is called when a <drop_objection> has reached ~obj~,
  // and the total count for ~obj~ goes to zero. This callback is executed
  // after the drain time associated with ~obj~. The default implementation 
  // calls <uvm_component::all_dropped>.

  virtual task all_dropped (uvm_object obj,
                            uvm_object source_obj,
                            string description,
                            int count);
    uvm_component comp;
    if($cast(comp,obj))    
      comp.all_dropped(this, source_obj, description, count);
    `uvm_do_callbacks(uvm_objection,uvm_objection_callback,all_dropped(this,obj,source_obj,description,count))
    if (m_events.exists(obj))
       ->m_events[obj].all_dropped;
    if (obj == m_top)
      m_top_all_dropped = 1;
  endtask


  //------------------------
  // Group: Objection Status
  //------------------------

  // Function: get_objectors
  //
  // Returns the current list of objecting objects (objects that
  // raised an objection but have not dropped it).

  function void get_objectors(ref uvm_object list[$]);
    list.delete();
    foreach (m_source_count[obj]) list.push_back(obj); 
  endfunction


  // Task: wait_for
  //
  // Waits for the raised, dropped, or all_dropped ~event~ to occur in
  // the given ~obj~. The task returns after all corresponding callbacks
  // for that event have been executed.
  //
  task wait_for(uvm_objection_event objt_event, uvm_object obj=null);

     if (obj==null)
       obj = m_top;

     if (!m_events.exists(obj)) begin
       m_events[obj] = new;
     end

     m_events[obj].waiters++;
     case (objt_event)
       UVM_RAISED:      @(m_events[obj].raised);
       UVM_DROPPED:     @(m_events[obj].dropped);
       UVM_ALL_DROPPED: @(m_events[obj].all_dropped);
     endcase
     
     m_events[obj].waiters--;

     if (m_events[obj].waiters == 0)
       m_events.delete(obj);

   endtask


   task wait_for_total_count(uvm_object obj=null, int count=0);
     if (obj==null)
       obj = m_top;

     if(!m_total_count.exists(obj) && count == 0)
       return;
     if (count == 0)
        wait (!m_total_count.exists(obj) && count == 0);
     else
        wait (m_total_count.exists(obj) && m_total_count[obj] == count);
   endtask
   

  // Function: get_objection_count
  //
  // Returns the current number of objections raised by the given ~object~.

  function int get_objection_count (uvm_object obj=null);
    if (obj==null)
      obj = m_top;

    if (!m_source_count.exists(obj))
      return 0;
    return m_source_count[obj];
  endfunction
  

  // Function: get_objection_total
  //
  // Returns the current number of objections raised by the given ~object~ 
  // and all descendants.

  function int get_objection_total (uvm_object obj=null);
 
    if (obj==null)
      obj = m_top;

    if (!m_total_count.exists(obj))
      return 0;
    else
      return m_total_count[obj];
     
  endfunction
  

  // Function: get_drain_time
  //
  // Returns the current drain time set for the given ~object~ (default: 0 ns).

  function time get_drain_time (uvm_object obj=null);
    if (obj==null)
      obj = m_top;

    if (!m_drain_time.exists(obj))
      return 0;
    return m_drain_time[obj];
  endfunction


  // m_display_objections

  protected function string m_display_objections(uvm_object obj=null, bit show_header=1);

    static string blank="                                                                                   ";
    
    string s;
    int total;
    uvm_object list[string];
    uvm_object curr_obj;
    int depth;
    string name;
    string this_obj_name;
    string curr_obj_name;
  
    foreach (m_total_count[o]) begin
      uvm_object theobj = o; 
      if ( m_total_count[o] > 0)
        list[theobj.get_full_name()] = theobj;
    end

    if (obj==null)
      obj = m_top;

    total = get_objection_total(obj);
    
    s = $sformatf("The total objection count is %0d\n",total);

    if (total == 0)
      return s;

    s = {s,"---------------------------------------------------------\n"};
    s = {s,"Source  Total   \n"};
    s = {s,"Count   Count   Object\n"};
    s = {s,"---------------------------------------------------------\n"};

  
    this_obj_name = obj.get_full_name();
    curr_obj_name = this_obj_name;

    do begin

      curr_obj = list[curr_obj_name];
  
      // determine depth
      depth=0;
      foreach (curr_obj_name[i])
        if (curr_obj_name[i] == ".")
          depth++;

      // determine leaf name
      name = curr_obj_name;
      for (int i=curr_obj_name.len()-1;i >= 0; i--)
        if (curr_obj_name[i] == ".") begin
           name = curr_obj_name.substr(i+1,curr_obj_name.len()-1); 
           break;
        end
      if (curr_obj_name == "")
        name = "uvm_top";
      else
        depth++;

      // print it
      s = {s, $sformatf("%-6d  %-6d %s%s\n",
         m_source_count.exists(curr_obj) ? m_source_count[curr_obj] : 0,
         m_total_count.exists(curr_obj) ? m_total_count[curr_obj] : 0,
         blank.substr(0,2*depth), name)};

    end while (list.next(curr_obj_name) &&
        curr_obj_name.substr(0,this_obj_name.len()-1) == this_obj_name);
  
    s = {s,"---------------------------------------------------------\n"};

    return s;

  endfunction
  

  function string convert2string();
    return m_display_objections(m_top,1);
  endfunction
  
  
  // Function: display_objections
  // 
  // Displays objection information about the given ~object~. If ~object~ is
  // not specified or ~null~, the implicit top-level component, <uvm_root>, is
  // chosen. The ~show_header~ argument allows control of whether a header is
  // output.

  function void display_objections(uvm_object obj=null, bit show_header=1);
	string m = m_display_objections(obj,show_header);
    `uvm_info("UVM/OBJ/DISPLAY",m,UVM_NONE)
  endfunction


  // Below is all of the basic data stuff that is needed for a uvm_object
  // for factory registration, printing, comparing, etc.

  typedef uvm_object_registry#(uvm_objection,"uvm_objection") type_id;
  static function type_id get_type();
    return type_id::get();
  endfunction

  function uvm_object create (string name="");
    uvm_objection tmp = new(name);
    return tmp;
  endfunction

  virtual function string get_type_name ();
    return "uvm_objection";
  endfunction

  function void do_copy (uvm_object rhs);
    uvm_objection _rhs;
    $cast(_rhs, rhs);
    m_source_count = _rhs.m_source_count;
    m_total_count  = _rhs.m_total_count;
    m_drain_time   = _rhs.m_drain_time;
    m_prop_mode    = _rhs.m_prop_mode;
  endfunction

endclass

// TODO: change to plusarg
//`define UVM_DEFAULT_TIMEOUT 9200s

typedef class uvm_cmdline_processor;



//------------------------------------------------------------------------------
//
// Class- uvm_test_done_objection DEPRECATED
//
// Provides built-in end-of-test coordination
//------------------------------------------------------------------------------

class uvm_test_done_objection extends uvm_objection;

   protected static uvm_test_done_objection m_inst;
  protected bit m_forced;

  // For communicating all objections dropped and end of phasing
  local  bit m_executing_stop_processes;
  local  int m_n_stop_threads;


  // Function- new DEPRECATED
  //
  // Creates the singleton test_done objection. Users must not call
  // this method directly.

  function new(string name="uvm_test_done");
    super.new(name);
  endfunction


  // Function- qualify DEPRECATED
  //
  // Checks that the given ~object~ is derived from either <uvm_component> or
  // <uvm_sequence_base>.

  virtual function void qualify(uvm_object obj=null,
                                bit is_raise,
                                string description);
    uvm_component c;
    uvm_sequence_base s;
    string nm = is_raise ? "raise_objection" : "drop_objection";
    string desc = description == "" ? "" : {" (\"", description, "\")"};
    if(! ($cast(c,obj) || $cast(s,obj))) begin
      uvm_report_error("TEST_DONE_NOHIER", {"A non-hierarchical object, '",
        obj.get_full_name(), "' (", obj.get_type_name(),") was used in a call ",
        "to uvm_test_done.", nm,"(). For this objection, a sequence ",
        "or component is required.", desc });
    end
  endfunction

  
`ifndef UVM_NO_DEPRECATED
  // m_do_stop_all
  // -------------

  task m_do_stop_all(uvm_component comp);

    string name;

    // we use an external traversal to ensure all forks are 
    // made from a single threaad.
    if (comp.get_first_child(name))
      do begin
        m_do_stop_all(comp.get_child(name));
      end
      while (comp.get_next_child(name));
  
    if (comp.enable_stop_interrupt) begin
      m_n_stop_threads++;
      fork begin
        comp.stop_phase(run_ph);
        m_n_stop_threads--;
      end
      join_none
    end
  endtask
 

  // Function- stop_request DEPRECATED
  //
  // Calling this function triggers the process of shutting down the currently
  // running task-based phase. This process involves calling all components'
  // stop tasks for those components whose enable_stop_interrupt bit is set.
  // Once all stop tasks return, or once the optional global_stop_timeout
  // expires, all components' kill method is called, effectively ending the
  // current phase. The uvm_top will then begin execution of the next phase,
  // if any.

  function void stop_request();
    `uvm_info_context("STOP_REQ",
                      "Stop-request called. Waiting for all-dropped on uvm_test_done",
                      UVM_FULL,m_top);
    fork
      m_stop_request();
    join_none
  endfunction

  task m_stop_request();
    raise_objection(m_top,"stop_request called; raising test_done objection");
    uvm_wait_for_nba_region();
    drop_objection(m_top,"stop_request called; dropping test_done objection");
  endtask


  // Variable- stop_timeout DEPRECATED
  //
  // These set watchdog timers for task-based phases and stop tasks. You cannot
  // disable the timeouts. When set to 0, a timeout of the maximum time possible
  // is applied. A timeout at this value usually indicates a problem with your
  // testbench. You should lower the timeout to prevent "never-ending"
  // simulations. 

  time stop_timeout = 0;
   

  // Task- all_dropped DEPRECATED
  //
  // This callback is called when the given ~object's~ objection count reaches
  // zero; if the ~object~ is the implicit top-level, <uvm_root> then it means
  // there are no more objections raised for the ~uvm_test_done~ objection.
  // Thus, after calling <uvm_objection::all_dropped>, this method will call
  // <global_stop_request> to stop the current task-based phase (e.g. run).
  
  virtual task all_dropped (uvm_object obj,
                            uvm_object source_obj,
                            string description,
                            int count);
    if (obj != m_top) begin
      super.all_dropped(obj,source_obj,description,count);
      return;
    end

    m_top.all_dropped(this, source_obj, description, count);

    // All stop tasks are forked from a single thread within a 'guard' process
    // so 'disable fork' can be used.
  
    if(m_cleared == 0) begin
      `uvm_info_context("TEST_DONE",
          "All end-of-test objections have been dropped. Calling stop tasks",
          UVM_FULL,m_top);
      fork begin // guard
        fork
          begin
            m_executing_stop_processes = 1;
            m_do_stop_all(m_top);
            wait (m_n_stop_threads == 0);
            m_executing_stop_processes = 0;
          end
          begin
            if (stop_timeout == 0)
              wait(stop_timeout != 0);
            `uvm_delay(stop_timeout)
            `uvm_error("STOP_TIMEOUT",
              {$sformatf("Stop-task timeout of %0t expired. ", stop_timeout),
                 "'run' phase ready to proceed to extract phase"})
          end
        join_any
        disable fork;
      end
      join // guard
  
      `uvm_info_context("TEST_DONE", {"'run' phase is ready ",
                        "to proceed to the 'extract' phase"}, UVM_LOW,m_top)

    end

    if (m_events.exists(obj))
      ->m_events[obj].all_dropped;
    m_top_all_dropped = 1;

  endtask


  // Function- raise_objection DEPRECATED
  //
  // Calls <uvm_objection::raise_objection> after calling <qualify>. 
  // If the ~object~ is not provided or is ~null~, then the implicit top-level
  // component, ~uvm_top~, is chosen.

  virtual function void raise_objection (uvm_object obj=null, 
                                         string description="",
                                         int count=1);
    if(obj==null)
      obj=m_top;
    else
      qualify(obj, 1, description);

    if (m_executing_stop_processes) begin
      string desc = description == "" ? "" : {"(\"", description, "\") "};
      `uvm_warning("ILLRAISE", {"The uvm_test_done objection was ",
        "raised ", desc, "during processing of a stop_request, i.e. stop ",
        "task execution. The objection is ignored by the stop process"})
        return;
    end

    super.raise_objection(obj,description,count);

  endfunction


  // Function- drop_objection DEPRECATED
  //
  // Calls <uvm_objection::drop_objection> after calling <qualify>. 
  // If the ~object~ is not provided or is ~null~, then the implicit top-level
  // component, ~uvm_top~, is chosen.

  virtual function void drop_objection (uvm_object obj=null, 
                                        string description="",
                                        int count=1);
    if(obj==null)
      obj=m_top;
    else
      qualify(obj, 0, description);
    super.drop_objection(obj,description,count);
  endfunction


  // Task- force_stop DEPRECATED
  //
  // Forces the propagation of the all_dropped() callback, even if there are still
  // outstanding objections. The net effect of this action is to forcibly end
  // the current phase.

  virtual task force_stop(uvm_object obj=null);
    uvm_report_warning("FORCE_STOP",{"Object '",
       (obj!=null?obj.get_name():"<unknown>"),"' called force_stop"});
    m_cleared = 1;
    all_dropped(m_top,obj,"force_stop() called",1);
    clear(obj);
  endtask
`endif


  // Below are basic data operations needed for all uvm_objects
  // for factory registration, printing, comparing, etc.

  typedef uvm_object_registry#(uvm_test_done_objection,"uvm_test_done") type_id;
  static function type_id get_type();
    return type_id::get();
  endfunction

  function uvm_object create (string name="");
    uvm_test_done_objection tmp = new(name);
    return tmp;
  endfunction

  virtual function string get_type_name ();
    return "uvm_test_done";
  endfunction

  static function uvm_test_done_objection get();
    if(m_inst == null)
      m_inst = uvm_test_done_objection::type_id::create("run");
    return m_inst;
  endfunction

endclass



// Have a pool of context objects to use
class uvm_objection_context_object;
    uvm_object obj;
    uvm_object source_obj;
    string description;
    int    count;
    uvm_objection objection;

    // Clears the values stored within the object,
    // preventing memory leaks from reused objects
    function void clear();
        obj = null;
        source_obj = null;
        description = "";
        count = 0;
        objection = null;
    endfunction : clear
endclass

// Typedef - Exists for backwards compat
typedef uvm_objection uvm_callbacks_objection;
   
//------------------------------------------------------------------------------
//
// Class: uvm_objection_callback
//
//------------------------------------------------------------------------------
// The uvm_objection is the callback type that defines the callback 
// implementations for an objection callback. A user uses the callback
// type uvm_objection_cbs_t to add callbacks to specific objections.
//
// For example:
//
//| class my_objection_cb extends uvm_objection_callback;
//|   function new(string name);
//|     super.new(name);
//|   endfunction
//|
//|   virtual function void raised (uvm_objection objection, uvm_object obj, 
//|       uvm_object source_obj, string description, int count);
//|       `uvm_info("RAISED","%0t: Objection %s: Raised for %s", $time, objection.get_name(),
//|       obj.get_full_name());
//|   endfunction
//| endclass
//| ...
//| initial begin
//|   my_objection_cb cb = new("cb");
//|   uvm_objection_cbs_t::add(null, cb); //typewide callback
//| end


class uvm_objection_callback extends uvm_callback;
  function new(string name);
    super.new(name);
  endfunction

  // Function: raised
  //
  // Objection raised callback function. Called by <uvm_objection::raised>.

  virtual function void raised (uvm_objection objection, uvm_object obj, 
      uvm_object source_obj, string description, int count);
  endfunction

  // Function: dropped
  //
  // Objection dropped callback function. Called by <uvm_objection::dropped>.

  virtual function void dropped (uvm_objection objection, uvm_object obj, 
      uvm_object source_obj, string description, int count);
  endfunction

  // Function: all_dropped
  //
  // Objection all_dropped callback function. Called by <uvm_objection::all_dropped>.

  virtual task all_dropped (uvm_objection objection, uvm_object obj, 
      uvm_object source_obj, string description, int count);
  endtask

endclass


`endif

