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

virtual class [docs]uvm_event_base extends uvm_object;
	const static string type_name = "uvm_event_base";
	protected event      m_event;
	protected int        num_waiters;
	protected bit        on;
	protected time       trigger_time=0;
	protected uvm_event_callback  callbacks[$];

	// Function: new
	//
	// Creates a new event object.

	function [docs]new (string name="");
		super.new(name);
	endfunction  

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

	// Task: 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>.

	virtual task [docs]wait_on (bit delta = 0);
		if (on) begin
			if (delta)
				#0;
			return;
		end
		num_waiters++;
		@on;
	endtask


	// Task: 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.

	virtual task [docs]wait_off (bit delta = 0);
		if (!on) begin
			if (delta)
				#0;
			return;
		end
		num_waiters++;
		@on;
	endtask


	// Task: 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.

	virtual task [docs]wait_trigger ();
		num_waiters++;
		@m_event;
	endtask


	// Task: 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.

	virtual task [docs]wait_ptrigger ();
		if (m_event.triggered)
			return;
		num_waiters++;
		@m_event;
	endtask


	// Function: 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.

	virtual function time [docs]get_trigger_time ();
		return trigger_time;
	endfunction


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

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

	virtual function bit [docs]is_on ();
		return (on == 1);
	endfunction


	// Function: 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 [docs]is_off ();
		return (on == 0);
	endfunction


	// Function: 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.

	virtual function void [docs]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: 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.

	virtual function void [docs]cancel ();
		if (num_waiters > 0)
			num_waiters--;
	endfunction


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

	virtual function int [docs]get_num_waiters ();
		return num_waiters;
	endfunction


	virtual function string [docs]get_type_name();
		return type_name;
	endfunction


	virtual function void [docs]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);
		printer.m_scope.down("callbacks");
		foreach(callbacks[e]) begin
			printer.print_object($sformatf("[%0d]",e), callbacks[e], "[");
		end
		printer.m_scope.up();
	endfunction


	virtual function void [docs]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;
		callbacks.delete();
		callbacks = e.callbacks;   

	endfunction

endclass


//------------------------------------------------------------------------------
//
// CLASS: uvm_event#(T)
//
// The uvm_event class is an extension of the abstract uvm_event_base class.  
// 
// The optional parameter ~T~ allows the user to define a data type which
// can be passed during an event trigger.
//------------------------------------------------------------------------------

class [docs]uvm_event#(type T=uvm_object) extends uvm_event_base;

	const static string type_name = "uvm_event";

	local T trigger_data;

	// Function: new
	//
	// Creates a new event object.

	function [docs]new (string name="");
		super.new(name);
	endfunction  


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

	virtual task [docs]wait_trigger_data (output T data);
		wait_trigger();
		data = get_trigger_data();
	endtask


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

	virtual task [docs]wait_ptrigger_data (output T data);
		wait_ptrigger();
		data = get_trigger_data();
	endtask


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

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

	virtual function void [docs]trigger (T data=null);
		int skip;
		skip=0;
		if (callbacks.size()) begin
			for (int i=0;i<callbacks.size();i++) begin
				uvm_event_callback#(T) tmp=callbacks[i];
				skip = skip + tmp.pre_trigger(this,data);
			end
		end
		if (skip==0) begin
			->m_event;
			if (callbacks.size()) begin
				for (int i=0;i<callbacks.size();i++) begin
					uvm_event_callback#(T) tmp=callbacks[i];
					tmp.post_trigger(this,data);
				end
			end
			num_waiters = 0;
			on = 1;
			trigger_time = $realtime;
			trigger_data = data;
		end
	endfunction


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

	virtual function T [docs]get_trigger_data ();
		return trigger_data;
	endfunction

	virtual function string [docs]get_type_name();
		return type_name;
	endfunction
	
		//-----------//
	// callbacks //
	//-----------//

	// Function: add_callback
	//
	// Registers a callback object, ~cb~, with this event. The callback object
	// may include pre_trigger and post_trigger functionality. If ~append~ is set
	// to 1, the default, ~cb~ is added to the back of the callback list. Otherwise,
	// ~cb~ is placed at the front of the callback list.

	virtual function void [docs]add_callback (uvm_event_callback#(T) cb, bit append=1);
		for (int i=0;i<callbacks.size();i++) begin
			if (cb == callbacks[i]) begin
				uvm_report_warning("CBRGED","add_callback: Callback already registered. Ignoring.", UVM_NONE);
				return;
			end
		end
		if (append)
			callbacks.push_back(cb);
		else
			callbacks.push_front(cb);
	endfunction


	// Function: delete_callback
	//
	// Unregisters the given callback, ~cb~, from this event. 

	virtual function void [docs]delete_callback (uvm_event_callback#(T) cb);
		for (int i=0;i<callbacks.size();i++) begin
			if (cb == callbacks[i]) begin
				callbacks.delete(i);
				return;
			end
		end
		uvm_report_warning("CBNTFD", "delete_callback: Callback not found. Ignoring delete request.", UVM_NONE);
	endfunction

	virtual function void [docs]do_print (uvm_printer printer);
		super.do_print(printer);
		printer.print_object("trigger_data", trigger_data);
	endfunction
	
	virtual function void [docs]do_copy (uvm_object rhs);
		uvm_event#(T) e;
		super.do_copy(rhs);
		if(!$cast(e, rhs) || (e==null)) return;
		trigger_data = e.trigger_data;
	endfunction // do_copy

   	virtual function uvm_object [docs]create(string name=""); 
		uvm_event#(T) v;
		v=new(name);
		return v;
	endfunction

endclass