/******************************************************************************
    Copyright (c) 1998-2000 CoWare, Inc.    ALL RIGHTS RESERVED

The following code is derived, directly or indirectly, from the SystemC
source code released by Synopsys, Inc Copyright (c) 1996-2001, Synopsys,
Inc., CoWare, Inc., Frontier Design, Inc. and others. All Rights reserved.
The contents of this file are subject to the restrictions and limitations
set forth in the SystemC Open Source License Version 2.0 (the "License");
You may not use this file except in compliance with such restrictions and
limitations. You may obtain instructions on how to receive a copy of the
License at http://www.systemc.org/.  Software distributed by Contributors
under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
ANY KIND, either express or implied. See the License for the specific
language governing rights and limitations under the License."

******************************************************************************/

/****************************************************************
    sc_abstract_protocol -- abstract protocol implementation which has rpc

    Authors
	: Harish Sarin, CoWare Inc. (harish@coware.com)
	: Cesar Quiroz, CoWare Inc. (cesar@coware.com)
	: Vijay Kumar,  CoWare Inc. (vijay@coware.com)

    MODIFICATION LOG 
    Modifier Name & Affiliation:
    Description of Modification:

****************************************************************/
#ifndef SC_ABSTRACT_PROTOCOL_H
#define SC_ABSTRACT_PROTOCOL_H

#include "sc_protocol.h"


// these are the bunch of signals(in future this can be more complicated)
// which connects the two sides of the protocol
template<class Tlink>
class sc_rpc_link : public sc_rpc_link_b {
 public:
	sc_rpc_link(const char *NAME) : sc_rpc_link_b(NAME) { }	
	virtual ~sc_rpc_link() { }
	void set_write_flag(bool t) { bWritten=t; }
	bool get_write_flag() { return bWritten; }
	
	Tlink& read() {	return data;}
	void write(const Tlink& val) { data=val; }

	Tlink data;
};

/////////////////////////////////////////////////////////////////////////
// this class is defined to encapsulate the properties of
// abstract ports into one class
class sc_rpc_protocol : public sc_port_protocol {
 public:
	sc_rpc_protocol(sc_protocol* p) : sc_port_protocol(p) {
		sensitive_procs = new proc_list;
	}
	virtual ~sc_rpc_protocol() { }
	virtual void p_add_procs(sc_async_process_handle h) {
		sensitive_procs->push_back(h);
	}
	void create_sensitivity(proc_list& list) {
		if((!sensitive_procs) || (sensitive_procs->size()==0)) {
			if(is_leaf()) {
				cerr << "SystemC Error : Port \'"<<name()<<"' must have atleast one " <<
					"sensitive process attached" << endl;
			}
			return;
		}
		for(int i=0; i<sensitive_procs->size(); i++) {
			sc_async_process_handle h=sensitive_procs->fetch(i);
			list.push_back(h);
		}
		delete_sensitive_procs();
	}
	void delete_sensitive_procs() {
		if (sensitive_procs) {
			sensitive_procs->erase_all();
			delete sensitive_procs;
		}
		sensitive_procs = 0;
	}
};

////////////////////////////////////////////////////////////////////
//
// Implementation of indexed protocols
//
//
template<class T,uint64 N>
class sc_indexed : public sc_protocol {
 public:
	uint64 range;
	static sc_protocol* instance() {
		static sc_indexed<T,N> *singleton=0;
		if(singleton==0)
			singleton = new sc_indexed<T,N>;
		return singleton;
	}

	sc_indexed() { range=N; }
	
	/////////////////////////////////////////////////////////////////////////
	// these are the bunch of signals which connects the two sides of the protocol

	class sc_indexed_link : public sc_rpc_link<T> {
     public:
		sc_indexed_link(const char *NAME) : sc_rpc_link<T>(NAME){ index=0; }
		uint64 get_address() { return index; }
		void set_address(uint64 i) { index=i; }
	private:
		uint64 index; // used to implement indexed port
	};

	virtual sc_link_protocol* make_link_protocol(const char *NAME) {
		return static_cast<sc_link_protocol *>(new sc_indexed_link(NAME));
	}

	/////////////////////////////////////////////////////////////////////////
	// this class is defined to encapsulate the properties of
	// abstract ports into one class instead of redefining it for every
	// 'master' 'slave' etc.
	class protocol_b : public sc_rpc_protocol {
	public:
		// to avoid redundat dynamic_casts
		sc_indexed_link* link_protocol;
		
		protocol_b() : sc_rpc_protocol(instance()) {
			link_protocol = 0;
		}
		virtual ~protocol_b() { }

		virtual BIND_STATUS bind(sc_link_mp_b& l) {
			BIND_STATUS status;
			status = sc_port_protocol::bind(l);
			link_protocol =
				dynamic_cast<sc_indexed<T,N>::sc_indexed_link *> (
						 l.get_link_protocol());
			if(0==link_protocol) {
				cerr<<"SystemC Error: Incopatible protocol : "<<
					"Cannot bind port `"<<name()<<"\' to link `"<<
					l.name()<<"\'"<<endl;
				return BIND_STATUS_UNKNOWN;
			}
			return status;
		}
	};


	///////////////////////////////////////////////////////////////////////
	//
	// Implementation of master protocols'
	//
	
	// sc_master will look for this class in every protocol implementation
	class sc_master_protocol : public protocol_b {
	public:
		// bind methods
		sc_master_protocol() : protocol_b() { }
		BIND_STATUS bind(sc_link_mp_b& l) {
			BIND_STATUS status;
			status = protocol_b::bind(l);
			if(BIND_STATUS_OK!=status)
				return status;
			link_protocol->masters++;
			return status;
		}
		virtual BIND_STATUS bind(sc_master_protocol& p) {
			return sc_port_protocol::bind(p);
		}

		void operator () (){
			link_protocol->run_slave();
		}
		sc_master_protocol& operator [] (uint64 i) {
			if(i<0 || i>=N) {
				cerr<<"SystemC error: index out of range: port `"<<
					name()<<"\'"<<endl;
				return *this;
			}                     			
			link_protocol->set_address(i);
			return *this;
		}
        };

	/////////////////////////////////////////////////////////////////////////
	// sc_inoutmaster will look for this class in every protocol implementation
        class sc_inoutm_protocol : public protocol_b {
	public:
		sc_inoutm_protocol() : protocol_b() { }
		// bind methods
		BIND_STATUS bind(sc_link_mp_b& l) {
			BIND_STATUS status;
			status = protocol_b::bind(l);
			if(BIND_STATUS_OK!=status)
				return status;
			link_protocol->inout_masters++;
			return status;
		}
		virtual BIND_STATUS bind(sc_inoutm_protocol& p) {
			return sc_port_protocol::bind(p);
		}

		T& read() const {
			link_protocol->set_write_flag(false);
			link_protocol->set_direction(false);
			link_protocol->run_inoutslave();
			link_protocol->run_outslave();
			return link_protocol->read();
		}
		void write(const T& val) {
			link_protocol->set_write_flag(false);
			link_protocol->write(val);
			link_protocol->set_direction(true);
			link_protocol->run_inslave();
			link_protocol->run_inoutslave();
		}

		sc_inoutm_protocol& operator = (const T& val) {
			write(val);
			return *this;
		}
		sc_inoutm_protocol& operator [] (uint64 i) {
			if(i<0 || i>=N) {
				cerr<<" SystemC error: index out of range: port `"<<
					name()<<"\'"<<endl;
				return *this;
			}                     			
			link_protocol->set_address(i);
			return *this;
		}
		operator const T& () const {
		   return read();
		}
	};
	
	/////////////////////////////////////////////////////////////////////////
	// sc_inmaster will look for this class in every protocol implementation
	class sc_inm_protocol : public protocol_b {
	public:
		sc_inm_protocol() : protocol_b() { }
		// bind methods
		BIND_STATUS bind(sc_link_mp_b& l) {
			BIND_STATUS status;
			status = protocol_b::bind(l);
			if(BIND_STATUS_OK!=status)
				return status;
			link_protocol->in_masters++;
			return status;
		}
		virtual BIND_STATUS bind(sc_inm_protocol& p) {
			return sc_port_protocol::bind(p);
		}

		virtual BIND_STATUS bind(sc_inoutm_protocol& p) {
			return sc_port_protocol::bind(p);
		}
		
		T& read() const {
			link_protocol->set_write_flag(false);
			link_protocol->set_direction(false);
			link_protocol->run_inoutslave();
			link_protocol->run_outslave();
			return link_protocol->read();
		}

		sc_inm_protocol& operator = (const T& val) {
			write(val);
			return *this;
		}
		sc_inm_protocol& operator [] (uint64 i) {
			if(i<0 || i>=N) {
				cerr<<"SystemC error: index out of range: port `"<<
					name()<<"\'" <<endl;
				return *this;
			}                     			
			link_protocol->set_address(i);
			return *this;
		}
		operator const T& () const {
		   return read();
		}

	      
	};

	/////////////////////////////////////////////////////////////////////////
	// sc_outmaster will look for this class in every protocol implementation
	class sc_outm_protocol : public protocol_b {
	public:
		sc_outm_protocol() : protocol_b() { }
		// bind methods
		BIND_STATUS bind(sc_link_mp_b& l) {
			BIND_STATUS status;
			status = protocol_b::bind(l);
			if(BIND_STATUS_OK!=status)
				return status;
			link_protocol->out_masters++;
			return status;
		}
		virtual BIND_STATUS bind(sc_outm_protocol& p) {
			return sc_port_protocol::bind(p);
		}
		virtual BIND_STATUS bind(sc_inoutm_protocol& p) {
			return sc_port_protocol::bind(p);
		}

		void write(const T& val) {
			link_protocol->write(val);
			link_protocol->set_direction(true);
			link_protocol->run_inslave();
			link_protocol->run_inoutslave();
		}
		sc_outm_protocol& operator = (const T& val) {
			write(val);
			return *this;
		}
		sc_outm_protocol& operator [] (uint64 i) {
			if(i<0 || i>=N) {
				cerr<<"SystemC error: index out of range: port `"<<
					name()<<"\'"<<endl;
				return *this;
			}                     			
			link_protocol->set_address(i);
			return *this;
		}
	};


	

	///////////////////////////////////////////////////////////////////////
	//
	// Implementation of slave protocols'
	//
	
	/////////////////////////////////////////////////////////////////////////
	// sc_slave will loook for this class in every protocol implementation
	class sc_slave_protocol : public protocol_b {
	public:
		// bind methods
		sc_slave_protocol() : protocol_b() { }
		BIND_STATUS bind(sc_link_mp_b& l) {
			BIND_STATUS status;
			status = protocol_b::bind(l);
			if(BIND_STATUS_OK!=status)
				return status;
			link_protocol->slaves++;
			return status;
		}
		virtual BIND_STATUS bind(sc_slave_protocol& p) {
			return sc_port_protocol::bind(p);
		}

		virtual void make_sensitive() {
			//link_protocol can be '0' due to incompatible protocols
			if(link_protocol)
				create_sensitivity(link_protocol->slaveList);
		}
		virtual ~sc_slave_protocol() { }
		uint64 get_address() { return link_protocol->get_address(); }
	};

	/////////////////////////////////////////////////////////////////////////
	// sc_inoutslave will loook for this class in every protocol implementation
	class sc_inouts_protocol : public protocol_b {
	public:
		sc_inouts_protocol() : protocol_b() { }
		// bind methods
		BIND_STATUS bind(sc_link_mp_b& l) {
			BIND_STATUS status;
			status = protocol_b::bind(l);
			if(BIND_STATUS_OK!=status)
				return status;
			link_protocol->inout_slaves++;
			return status;
		}
		virtual BIND_STATUS bind(sc_inouts_protocol& p) {
			return sc_port_protocol::bind(p);
		}
		T& read() { return link_protocol->read(); }
		void write(const T& val) {
			if(link_protocol->get_write_flag()) {
				cerr<<"SystemC Error: `"<<name()<<
					"\': Cannot write twice to the same link "<<endl;
				return;
			}
			link_protocol->write(val);
			link_protocol->set_write_flag(false);
		}

		virtual void make_sensitive() {
			//link_protocol can be '0' due to incompatible protocols
			if(link_protocol)
				create_sensitivity(link_protocol->inoutSlaveList);
		}
		uint64 get_address() { return link_protocol->get_address(); }
		virtual ~sc_inouts_protocol() { }
		bool input() { return link_protocol->get_direction(); }
	};
	
	/////////////////////////////////////////////////////////////////////////
	// sc_inslave will loook for this class in every protocol implementation
        class sc_ins_protocol : public protocol_b {
	public:
		sc_ins_protocol() : protocol_b() { }
		// bind methods
		BIND_STATUS bind(sc_link_mp_b& l) {
			BIND_STATUS status;
			status = protocol_b::bind(l);
			if(BIND_STATUS_OK!=status)
				return status;
			link_protocol->in_slaves++;
			return status;
		}
		virtual BIND_STATUS bind(sc_ins_protocol& p) {
			return sc_port_protocol::bind(p);
		}
		virtual BIND_STATUS bind(sc_inouts_protocol& p) {
			return sc_port_protocol::bind(p);
		}

		T& read() { return link_protocol->read(); }

		virtual void make_sensitive() {
			//link_protocol can be '0' due to incompatible protocols
			if(link_protocol)
			create_sensitivity(link_protocol->inSlaveList);
		}
		uint64 get_address() { return link_protocol->get_address(); }
		virtual ~sc_ins_protocol() { }
	};

	/////////////////////////////////////////////////////////////////////////
	// sc_outslave will loook for this class in every protocol implementation
	class sc_outs_protocol : public protocol_b {
	public:
		sc_outs_protocol() : protocol_b() { }
		// bind methods
		BIND_STATUS bind(sc_link_mp_b& l) {
			BIND_STATUS status;
			status = protocol_b::bind(l);
			if(BIND_STATUS_OK!=status)
				return status;
			link_protocol->out_slaves++;
			return status;
		}
		virtual BIND_STATUS bind(sc_outs_protocol& p) {
			return sc_port_protocol::bind(p);
		}
		virtual BIND_STATUS bind(sc_inouts_protocol& p) {
			return sc_port_protocol::bind(p);
		}

		void write(const T& val) {
			if(link_protocol->get_write_flag()) {
				cerr<<"SystemC Error: `"<<name()<<
					"\': Cannot write twice to the same link "<<endl;
				return;
			}
			link_protocol->write(val);
			link_protocol->set_write_flag(false);
		}
		virtual void make_sensitive() {
			//link_protocol can be '0' due to incompatible protocols
			if(link_protocol)
			create_sensitivity(link_protocol->outSlaveList);
		}
		
		uint64 get_address() { return link_protocol->get_address(); }
		virtual ~sc_outs_protocol() { }
	};

};	


#endif
