//
//------------------------------------------------------------------------------
// Copyright 2011 AMD
// Copyright 2007-2018 Cadence Design Systems, Inc.
// Copyright 2014 Cisco Systems, Inc.
// Copyright 2020-2022 Marvell International Ltd.
// Copyright 2007-2011 Mentor Graphics Corporation
// Copyright 2013-2020 NVIDIA Corporation
// Copyright 2011 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_event_base
//
// The uvm_event_base class is an abstract wrapper class around the SystemVerilog event
// construct.  It provides some additional services such as setting callbacks
// and maintaining the number of waiters.
//
//------------------------------------------------------------------------------

// @uvm-ieee 1800.2-2020 auto 10.1.1.1
virtual class uvm_event_base extends uvm_object;

        `uvm_object_abstract_utils(uvm_event_base)

	protected event      m_event;
	protected int        num_waiters;
	protected bit        on;
	protected time       trigger_time=0;

	// Function -- NODOCS -- new
	//
	// Creates a new event object.

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

	//---------//
	// waiting //
	//---------//

	// Task -- NODOCS -- wait_on
	//
	// Waits for the event to be activated for the first time.
	//
	// If the event has already been triggered, this task returns immediately.
	// If ~delta~ is set, the caller will be forced to wait a single delta #0
	// before returning. This prevents the caller from returning before
	// previously waiting processes have had a chance to resume.
	//
	// Once an event has been triggered, it will be remain "on" until the event
	// is <reset>.

	// @uvm-ieee 1800.2-2020 auto 10.1.1.2.2
	virtual task wait_on (bit delta = 0);
		if (on) begin
			if (delta)
				#0;
			return;
		end
		num_waiters++;
		@on;
	endtask


	// Task -- NODOCS -- wait_off
	//
	// If the event has already triggered and is "on", this task waits for the
	// event to be turned "off" via a call to <reset>.
	//
	// If the event has not already been triggered, this task returns immediately.
	// If ~delta~ is set, the caller will be forced to wait a single delta #0
	// before returning. This prevents the caller from returning before
	// previously waiting processes have had a chance to resume.

	// @uvm-ieee 1800.2-2020 auto 10.1.1.2.3
	virtual task wait_off (bit delta = 0);
		if (!on) begin
			if (delta)
				#0;
			return;
		end
		num_waiters++;
		@on;
	endtask


	// Task -- NODOCS -- wait_trigger
	//
	// Waits for the event to be triggered. 
	//
	// If one process calls wait_trigger in the same delta as another process
	// calls <uvm_event#(T)::trigger>, a race condition occurs. If the call to wait occurs
	// before the trigger, this method will return in this delta. If the wait
	// occurs after the trigger, this method will not return until the next
	// trigger, which may never occur and thus cause deadlock.

	// @uvm-ieee 1800.2-2020 auto 10.1.1.2.4
	virtual task wait_trigger ();
		num_waiters++;
		@m_event;
	endtask


	// Task -- NODOCS -- wait_ptrigger
	//
	// Waits for a persistent trigger of the event. Unlike <wait_trigger>, this
	// views the trigger as persistent within a given time-slice and thus avoids
	// certain race conditions. If this method is called after the trigger but
	// within the same time-slice, the caller returns immediately.

	// @uvm-ieee 1800.2-2020 auto 10.1.1.2.5
	virtual task wait_ptrigger ();
		if (m_event.triggered)
			return;
		num_waiters++;
		@m_event;
	endtask


	// Function -- NODOCS -- get_trigger_time
	//
	// Gets the time that this event was last triggered. If the event has not been
	// triggered, or the event has been reset, then the trigger time will be 0.

	// @uvm-ieee 1800.2-2020 auto 10.1.1.2.6
	virtual function time get_trigger_time ();
		return trigger_time;
	endfunction


	//-------//
	// state //
	//-------//

	// Function -- NODOCS -- is_on
	//
	// Indicates whether the event has been triggered since it was last reset. 
	//
	// A return of 1 indicates that the event has triggered.

	// @uvm-ieee 1800.2-2020 auto 10.1.1.2.7
	virtual function bit is_on ();
		return (on == 1);
	endfunction


	// Function -- NODOCS -- is_off
	//
	// Indicates whether the event has been triggered or been reset.
	//
	// A return of 1 indicates that the event has not been triggered.

	virtual function bit is_off ();
		return (on == 0);
	endfunction


	// Function -- NODOCS -- reset
	//
	// Resets the event to its off state. If ~wakeup~ is set, then all processes
	// currently waiting for the event are activated before the reset.
	//
	// No callbacks are called during a reset.

	// @uvm-ieee 1800.2-2020 auto 10.1.1.2.8
	virtual function void reset (bit wakeup = 0);
	  event e;
	  if (wakeup)
	    ->m_event;
	  m_event = e;
	  num_waiters = 0;
	  on = 0;
	  trigger_time = 0;
	endfunction



	//--------------//
	// waiters list //
	//--------------//

	// Function -- NODOCS -- cancel
	//
	// Decrements the number of waiters on the event. 
	//
	// This is used if a process that is waiting on an event is disabled or
	// activated by some other means.

	// @uvm-ieee 1800.2-2020 auto 10.1.1.2.9
	virtual function void cancel ();
		if (num_waiters > 0)
			num_waiters--;
	endfunction


	// Function -- NODOCS -- get_num_waiters
	//
	// Returns the number of processes waiting on the event.

	// @uvm-ieee 1800.2-2020 auto 10.1.1.2.10
	virtual function int get_num_waiters ();
		return num_waiters;
	endfunction


	virtual function void do_print (uvm_printer printer);
		printer.print_field_int("num_waiters", num_waiters, $bits(num_waiters), UVM_DEC, ".", "int");
		printer.print_field_int("on", on, $bits(on), UVM_BIN, ".", "bit");
		printer.print_time("trigger_time", trigger_time);
	endfunction


	virtual function void do_copy (uvm_object rhs);
		uvm_event_base e;
		super.do_copy(rhs);
		if(!$cast(e, rhs) || (e==null)) return;

		m_event = e.m_event;
		num_waiters = e.num_waiters;
		on = e.on;
		trigger_time = e.trigger_time;

	endfunction

endclass

// Class: uvm_event#(T)
// This is an implementation of uvm_event#(T) as described in 1800.2 with
// the addition of API described below.

// @uvm-ieee 1800.2-2020 auto 10.1.2.1
class uvm_event#(type T=uvm_object) extends uvm_event_base;

        typedef uvm_event#(T) this_type;
        typedef uvm_event_callback#(T) cb_type;

        // Type: cbs_type
        // Callback typedef for this event type.
        //
        // | typedef uvm_callbacks#(this_type, cb_type) cbs_type;
        // @uvm-contrib Potential Contribution to 1800.2
        typedef uvm_callbacks#(this_type, cb_type) cbs_type;
   
        // Not using `uvm_register_cb(this_type, cb_type)
        // so as to try and get ~slightly~ better debug
        // output for names.
        static local function bit m_register_cb();
	   return uvm_callbacks#(this_type,cb_type)::m_register_pair(
                                                "uvm_pkg::uvm_event#(T)",
                                                "uvm_pkg::uvm_event_callback#(T)"
				                                     );
	endfunction : m_register_cb
        static local bit m_cb_registered = m_register_cb();
   
        `uvm_object_param_utils(this_type)

        // Better type name for debug
        virtual function string get_type_name();
	   return "uvm_pkg::uvm_event#(T)";
	endfunction : get_type_name

	local T trigger_data;
        local T default_data;

	// Function -- NODOCS -- new
	//
	// Creates a new event object.

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

	// Function -- NODOCS -- reset
	//
	// Resets the event to its off state and clears trigger data to its default state.

	virtual function void reset (bit wakeup = 0);
		trigger_data = get_default_data();
          super.reset(wakeup);
        endfunction : reset

	// Task -- NODOCS -- wait_trigger_data
	//
	// This method calls <uvm_event_base::wait_trigger> followed by <get_trigger_data>.

	// @uvm-ieee 1800.2-2020 auto 10.1.2.2.2
	virtual task wait_trigger_data (output T data);
		wait_trigger();
		data = get_trigger_data();
	endtask


	// Task -- NODOCS -- wait_ptrigger_data
	//
	// This method calls <uvm_event_base::wait_ptrigger> followed by <get_trigger_data>.

	// @uvm-ieee 1800.2-2020 auto 10.1.2.2.3
	virtual task wait_ptrigger_data (output T data);
		wait_ptrigger();
		data = get_trigger_data();
	endtask


	//------------//
	// triggering //
	//------------//

	// Function -- NODOCS -- trigger
	//
	// Triggers the event, resuming all waiting processes.
	//
	// An optional ~data~ argument can be supplied with the enable to provide
	// trigger-specific information.

	// @uvm-ieee 1800.2-2020 auto 10.1.2.2.4
	virtual function void trigger (T data=get_default_data());
	  int   skip;
	  cb_type cb_q[$];
	  skip=0;
	  cbs_type::get_all(cb_q, this);
          
	  // Call all pre_trigger, bail out after
	  // if any return !0
	  foreach (cb_q[i])
	    skip += cb_q[i].pre_trigger(this, data);
	  if (skip==0) begin
	    on = 1;
	    trigger_time = $realtime;
	    trigger_data = data;
	    ->m_event;
	    foreach (cb_q[i])
	      cb_q[i].post_trigger(this, data);
	    num_waiters = 0;
	  end
	endfunction


	// Function -- NODOCS -- get_trigger_data
	//
	// Gets the data, if any, provided by the last call to <trigger>.

	// @uvm-ieee 1800.2-2020 auto 10.1.2.2.5
	virtual function T get_trigger_data ();
		return trigger_data;
	endfunction

        // Function -- NODOCS -- default data

        // @uvm-ieee 1800.2-2020 auto 10.1.2.2.6
        virtual function T get_default_data();
	   return default_data;
	endfunction : get_default_data

        // @uvm-ieee 1800.2-2020 auto 10.1.2.2.6
        virtual function void set_default_data(T data);
	   default_data = data;
	endfunction : set_default_data

	virtual function void do_print (uvm_printer printer);
	   uvm_event#(uvm_object) oe;
	   cb_type cb_q[$];
	   
	   super.do_print(printer);

	   // Printing the callbacks
	   cbs_type::get_all(cb_q, this);
           printer.print_array_header("callbacks", cb_q.size(), "queue");
	   foreach(cb_q[e])
	     printer.print_object($sformatf("[%0d]", e), cb_q[e], "[");
           printer.print_array_footer(cb_q.size());
	   
	   if ($cast(oe, this)) begin
	     printer.print_object("trigger_data", oe.get_trigger_data());
	   end
	   else begin
	      uvm_event#(string) se;
	      if ($cast(se, this))
		printer.print_string("trigger_data", se.get_trigger_data());
	   end
	endfunction
	
	virtual function void do_copy (uvm_object rhs);
	   this_type e;
	   cb_type cb_q[$];
	   super.do_copy(rhs);
	   if(!$cast(e, rhs) || (e==null)) return;
	   trigger_data = e.trigger_data;

	   begin
	      // Copying the callbacks is VERY ugly
	      //

	      // First we retrieve any existing callbacks for this
	      // instance, and delete them.  Note that this can
	      // bump into Mantis 6450, which would result in
	      // incorrect behavior until that mantis is fixed.
              // \todo Remove this note when 6450 is resolved.
	      cbs_type::get_all(cb_q, this);
	      foreach(cb_q[i])
		cbs_type::delete(this, cb_q[i]);

	      // We now have an empty instance queue, which we'll fill
	      // using the rhs.
	      cb_q.delete();
	      cbs_type::get_all(cb_q, e);
	      foreach(cb_q[i])
		cbs_type::add(this, cb_q[i]);

	   end
	endfunction

endclass
