//----------------------------------------------------------------------
//   Copyright 2007-2011 Mentor Graphics Corporation
//   Copyright 2007-2009 Cadence Design Systems, Inc. 
//   Copyright 2010 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_HEARTBEAT_SVH
`define UVM_HEARTBEAT_SVH

typedef enum {
  UVM_ALL_ACTIVE,
  UVM_ONE_ACTIVE,
  UVM_ANY_ACTIVE,
  UVM_NO_HB_MODE
} [docs]uvm_heartbeat_modes;

typedef class [docs]uvm_heartbeat_callback;
typedef uvm_callbacks #(uvm_objection,uvm_heartbeat_callback) [docs]uvm_heartbeat_cbs_t;


//------------------------------------------------------------------------------
//
// Class: uvm_heartbeat
//
//------------------------------------------------------------------------------
// Heartbeats provide a way for environments to easily ensure that their
// descendants are alive. A uvm_heartbeat is associated with a specific
// objection object. A component that is being tracked by the heartbeat
// object must raise (or drop) the synchronizing objection during
// the heartbeat window. 
//
// The uvm_heartbeat object has a list of participating objects. The heartbeat
// can be configured so that all components (UVM_ALL_ACTIVE), exactly one
// (UVM_ONE_ACTIVE), or any component (UVM_ANY_ACTIVE) must trigger the
// objection in order to satisfy the heartbeat condition.
//------------------------------------------------------------------------------

typedef class [docs]uvm_objection_callback;
class [docs]uvm_heartbeat extends uvm_object;

  protected uvm_objection m_objection;
  protected uvm_heartbeat_callback m_cb;
  protected uvm_component   m_cntxt;
  protected uvm_heartbeat_modes   m_mode;
  protected uvm_component   m_hblist[$];
  protected uvm_event#(uvm_object)       m_event;
  protected bit             m_started;
  protected event           m_stop_event;

  // Function: new
  //
  // Creates a new heartbeat instance associated with ~cntxt~. The context
  // is the hierarchical location that the heartbeat objections will flow
  // through and be monitored at. The ~objection~ associated with the heartbeat 
  // is optional, if it is left ~null~ but it must be set before the heartbeat
  // monitor will activate.
  //
  //| uvm_objection myobjection = new("myobjection"); //some shared objection
  //| class myenv extends uvm_env;
  //|    uvm_heartbeat hb = new("hb", this, myobjection);
  //|    ...
  //| endclass

  function [docs]new(string name, uvm_component cntxt, uvm_objection objection=null);
     uvm_coreservice_t cs;
    super.new(name);
    m_objection = objection;
    cs  = uvm_coreservice_t::get();
     
    //if a cntxt is given it will be used for reporting.
    if(cntxt != null) m_cntxt = cntxt;
    else m_cntxt = cs.get_root();

    m_cb = new({name,"_cb"},m_cntxt);

  endfunction


  // Function: set_mode
  //
  // Sets or retrieves the heartbeat mode. The current value for the heartbeat
  // mode is returned. If an argument is specified to change the mode then the
  // mode is changed to the new value.

  function uvm_heartbeat_modes [docs]set_mode (uvm_heartbeat_modes mode = UVM_NO_HB_MODE);
    set_mode = m_mode;
    if(mode == UVM_ANY_ACTIVE || mode == UVM_ONE_ACTIVE || mode == UVM_ALL_ACTIVE)
      m_mode = mode;
  endfunction


  // Function: set_heartbeat 
  //
  // Sets up the heartbeat event and assigns a list of objects to watch. The
  // monitoring is started as soon as this method is called. Once the
  // monitoring has been started with a specific event, providing a new
  // monitor event results in an error. To change trigger events, you
  // must first <stop> the monitor and then <start> with a new event trigger.
  //
  // If the trigger event ~e~ is ~null~ and there was no previously set
  // trigger event, then the monitoring is not started. Monitoring can be 
  // started by explicitly calling <start>.

  function void [docs]set_heartbeat (uvm_event#(uvm_object) e, ref uvm_component comps[$]);
    uvm_object c;
    foreach(comps[i]) begin
      c = comps[i];
      if(!m_cb.cnt.exists(c)) 
        m_cb.cnt[c]=0;
      if(!m_cb.last_trigger.exists(c)) 
        m_cb.last_trigger[c]=0;
    end
    if(e==null && m_event==null) return;
    start(e);
  endfunction

  // Function: add
  //
  // Add a single component to the set of components to be monitored.
  // This does not cause monitoring to be started. If monitoring is
  // currently active then this component will be immediately added
  // to the list of components and will be expected to participate
  // in the currently active event window.

  function void [docs]add (uvm_component comp);
    uvm_object c = comp;
    if(m_cb.cnt.exists(c)) return;
    m_cb.cnt[c]=0;
    m_cb.last_trigger[c]=0;
  endfunction

  // Function: remove
  //
  // Remove a single component to the set of components being monitored.
  // Monitoring is not stopped, even if the last component has been
  // removed (an explicit stop is required).

  function void [docs]remove (uvm_component comp);
    uvm_object c = comp;
    if(m_cb.cnt.exists(c)) m_cb.cnt.delete(c);
    if(m_cb.last_trigger.exists(c)) m_cb.last_trigger.delete(c);
  endfunction


  // Function: start
  //
  // Starts the heartbeat monitor. If ~e~ is ~null~ then whatever event
  // was previously set is used. If no event was previously set then
  // a warning is issued. It is an error if the monitor is currently
  // running and ~e~ is specifying a different trigger event from the
  // current event.

  function void [docs]start (uvm_event#(uvm_object) e=null);
    if(m_event == null && e == null) begin
      m_cntxt.uvm_report_warning("NOEVNT", { "start() was called for: ",
        get_name(), " with a null trigger and no currently set trigger" },
        UVM_NONE);
      return;
    end
    if((m_event != null) && (e != m_event) && m_started) begin
      m_cntxt.uvm_report_error("ILHBVNT", { "start() was called for: ",
        get_name(), " with trigger ", e.get_name(), " which is different ",
        "from the original trigger ", m_event.get_name() }, UVM_NONE);
      return;
    end  
    if(e != null) m_event = e;
    m_enable_cb();
    m_start_hb_process();
  endfunction

  // Function: stop
  //
  // Stops the heartbeat monitor. Current state information is reset so
  // that if <start> is called again the process will wait for the first
  // event trigger to start the monitoring.

  function void [docs]stop ();
    m_started = 0;
    ->m_stop_event;
    m_disable_cb();
  endfunction

  function void m_start_hb_process();
    if(m_started) return;
    m_started = 1;
    fork
      m_hb_process;
    join_none
  endfunction

  protected bit m_added;
  function void m_enable_cb;
    void'(m_cb.callback_mode(1));
    if(m_objection == null) return;
    if(!m_added) 
      uvm_heartbeat_cbs_t::add(m_objection, m_cb);
    m_added = 1;
  endfunction

  function void m_disable_cb;
    void'(m_cb.callback_mode(0));
  endfunction

  task m_hb_process;
    uvm_object obj;
    bit  triggered;
    time last_trigger=0;
    fork
      begin
        // The process waits for the event trigger. The first trigger is
        // ignored, but sets the first start window. On susequent triggers
        // the monitor tests that the mode criteria was full-filled.
        while(1) begin
          m_event.wait_trigger();
          if(triggered) begin
            case (m_mode)
              UVM_ALL_ACTIVE:              
                begin
                  foreach(m_cb.cnt[idx]) begin
                    obj = idx;
                    if(!m_cb.cnt[obj]) begin
                      m_cntxt.uvm_report_fatal("HBFAIL", $sformatf("Did not recieve an update of %s for component %s since last event trigger at time %0t : last update time was %0t",
                        m_objection.get_name(), obj.get_full_name(), 
                        last_trigger, m_cb.last_trigger[obj]), UVM_NONE);
                    end
                  end
                end 
              UVM_ANY_ACTIVE:              
                begin
                  if(m_cb.cnt.num() && !m_cb.objects_triggered()) begin
                    string s;
                    foreach(m_cb.cnt[idx]) begin
                      obj = idx;
                      s={s,"\n  ",obj.get_full_name()};
                    end
                    m_cntxt.uvm_report_fatal("HBFAIL", $sformatf("Did not recieve an update of %s on any component since last event trigger at time %0t. The list of registered components is:%s",
                      m_objection.get_name(), last_trigger, s), UVM_NONE); 
                  end
                end 
              UVM_ONE_ACTIVE:              
                begin
                  if(m_cb.objects_triggered() > 1) begin
                    string s;
                    foreach(m_cb.cnt[idx])  begin
                      obj = idx;
                      if(m_cb.cnt[obj]) $swrite(s,"%s\n  %s (updated: %0t)",
                         s, obj.get_full_name(), m_cb.last_trigger[obj]);
                    end
                    m_cntxt.uvm_report_fatal("HBFAIL", $sformatf("Recieved update of %s from more than one component since last event trigger at time %0t. The list of triggered components is:%s",
                      m_objection.get_name(), last_trigger, s), UVM_NONE); 
                  end
                  if(m_cb.cnt.num() && !m_cb.objects_triggered()) begin
                    string s;
                    foreach(m_cb.cnt[idx]) begin
                      obj = idx;
                      s={s,"\n  ",obj.get_full_name()};
                    end
                    m_cntxt.uvm_report_fatal("HBFAIL", $sformatf("Did not recieve an update of %s on any component since last event trigger at time %0t. The list of registered components is:%s",
                      m_objection.get_name(), last_trigger, s), UVM_NONE); 
                  end
                end 
            endcase
          end 
          m_cb.reset_counts();
          last_trigger = $realtime;
          triggered = 1;
        end
      end
      @(m_stop_event);
    join_any
    disable fork;
  endtask
endclass


class [docs]uvm_heartbeat_callback extends uvm_objection_callback;
  int  cnt [uvm_object];
  time last_trigger [uvm_object];
  uvm_object target;
  uvm_coreservice_t cs = uvm_coreservice_t::get();

  function [docs]new(string name, uvm_object target);
    super.new(name);
    if (target != null)
       this.target = target;
    else
       this.target = cs.get_root();
  endfunction

  virtual function void [docs]raised (uvm_objection objection,
                                uvm_object obj,
                                uvm_object source_obj,
                                string description,
                                int count);
    if(obj == target) begin
      if(!cnt.exists(source_obj))
        cnt[source_obj] = 0;
      cnt[source_obj] = cnt[source_obj]+1;
      last_trigger[source_obj] = $realtime;
    end
  endfunction

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

  function void [docs]reset_counts;
    foreach(cnt[i]) cnt[i] = 0;
  endfunction

  function int [docs]objects_triggered;
    objects_triggered = 0; 
    foreach(cnt[i])
      if (cnt[i] != 0)
        objects_triggered++;
  endfunction

endclass

`endif