/*
	out = output
   Copyright (C) 1998 T. Scott Dattalo
   Copyright (C) 2006 Roy R. Rankin

This file is part of gpsim.

gpsim is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

gpsim is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with gpasm; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */



#include <stdio.h>
#include <iostream>
#include <string>

#include "pic-ioports.h"
#include "trace.h"
#include "processor.h"
#include "pir.h"
#include "stimuli.h"
#include "comparator.h"


void COMPARATOR_MODULE::initialize( PIR_SET *pir_set,
	PinModule *pin_vr0, PinModule *pin_cm0, 
	PinModule *pin_cm1, PinModule *pin_cm2, PinModule *pin_cm3, 
	PinModule *pin_cm4, PinModule *pin_cm5)
{
    cmcon = new CMCON;
    cmcon->assign_pir_set(pir_set);
    cmcon->setINpin(0, pin_cm0);
    cmcon->setINpin(1, pin_cm1);
    cmcon->setINpin(2, pin_cm2);
    cmcon->setINpin(3, pin_cm3);
    cmcon->setOUTpin(0, pin_cm4);
    cmcon->setOUTpin(1, pin_cm5);
    vrcon.setIOpin(pin_vr0);
    cmcon->_vrcon = &vrcon;
    vrcon._cmcon = cmcon;
}

//--------------------------------------------------
//
//--------------------------------------------------
class CMSignalSource : public SignalControl
{
public:
  CMSignalSource()
    : m_state('0')
  {
  }
  char getState()
  {
    return m_state;
  }
  void putState(bool new_val)
  {
	m_state = new_val?'1':'0';
  }
private:
  char m_state;
};

CM_stimulus::CM_stimulus(CMCON * arg, const char *cPname,double _Vth, double _Zth)
  : stimulus(cPname, _Vth, _Zth)
{
	_cmcon = arg;
}

void   CM_stimulus::set_nodeVoltage(double v)
{
        _cmcon->get();	// recalculate comparator values
        nodeVoltage = v;
}


/*
    Setup the configuration for the comparators. Must be called
    for each comparator and each mode(CN2:CM0) that can be used.
	il1 = input Vin- when CIS == 0
	ih1 = input Vin+ when CIS == 0
	il2 = input Vin- when CIS == 1
	ih2 = input Vin+ when CIS == 1
	out = output

	if input == VREF, reference voltage is used.
*/

void CMCON::set_configuration(int comp, int mode, int il1, int ih1, int il2,
int ih2, int out)
{
    if (comp > cMaxComparators || comp < 1 )
    {
	cout << "CMCON::set_configuration comp=" << comp << " out of range\n";
	return;
    }
    if (mode > cMaxConfigurations)
    {
	cout << "CMCON::set_configuration mode too large\n";
	return;
    }
    m_configuration_bits[comp-1][mode] = (il1 << CFG_SHIFT*4) | 
	(ih1 << CFG_SHIFT*3) |
	(il2 << CFG_SHIFT*2) | (ih2 << CFG_SHIFT) | out;
} 

CMCON::CMCON(void)
{
  value.put(0);
}
void CMCON::setINpin(int i, PinModule *newPinModule)
{
    cm_input[i] = newPinModule;
    cm_input_pin[i] = strdup(newPinModule->getPin().name().c_str());
}
void CMCON::setOUTpin(int i, PinModule *newPinModule)
{
    cm_output[i] = newPinModule;
    cm_output_pin[i] = strdup(newPinModule->getPin().name().c_str());
}
void CMCON::assign_pir_set(PIR_SET *new_pir_set)
{
    pir_set = new_pir_set;
}

double CMCON::comp_voltage(int ind, int invert)
{
    double Voltage;

    switch(ind)
    {
    case VREF:
	Voltage = _vrcon->get_Vref();
	break;

    case NO_IN:
	Voltage = invert ? 5. : 0.;
	break;

    default:
	Voltage = cm_input[ind]->getPin().get_nodeVoltage();
	break;
    }
    return Voltage;
}
/*
**	get()
**		read the comparator inputs and set C2OUT and C1OUT
**		as required. Also drive output pins if required.
*/
unsigned int CMCON::get() 
{ 
    unsigned int cmcon_val = value.get();
    int mode = cmcon_val & 0x07;
    int i;
                                                                                
    for (i = 0; i < 2; i++)
    {
	double Vhigh;
	double Vlow;
	bool out_true;
	int out;
	int invert_bit = (i == 0) ? C1INV : C2INV;
	int output_bit = (i == 0) ? C1OUT : C2OUT;
	int shift = (cmcon_val & CIS) ? CFG_SHIFT : CFG_SHIFT*3;

	if ((m_configuration_bits[i][mode] & CFG_MASK) != ZERO)
        {
	    Vhigh = comp_voltage( 
		(m_configuration_bits[i][mode] >> shift) & CFG_MASK,
		cmcon_val & invert_bit);
	    Vlow = comp_voltage( 
		(m_configuration_bits[i][mode] >> (shift + CFG_SHIFT)) & CFG_MASK,
		(cmcon_val & invert_bit) == 0);
		
	    if (Vhigh > Vlow)
	    	out_true = (cmcon_val & invert_bit)?false:true;
            else
	    	out_true = (cmcon_val & invert_bit)?true:false;

   	    if (out_true)  
	    	cmcon_val |= output_bit;
   	    else
	    	cmcon_val &= ~output_bit;

	    if ( (out = m_configuration_bits[i][mode] & CFG_MASK) < 2)
	    {
	    	cm_source[out]->putState(out_true);
	    	cm_output[out]->updatePinModule();
	    	update();
	    }
	}
	else			// Don't care about inputs, register value 0
	    cmcon_val &= ~output_bit;
   }

   if (value.get() ^ cmcon_val) // change of state
   {
	// Generate interupt ?
	if (pir_set)
		pir_set->set_cmif();
   }
   value.put(cmcon_val);
   return(cmcon_val);
}

void CMCON::put(unsigned int new_value)
{
  unsigned int mode = new_value & 0x7;
  unsigned int in_mask = 0;
  unsigned int out_mask = 0;
  unsigned int configuration;
  int i;
                                                                                
  if (verbose)
      cout << "CMCON::put(new_value) =" << hex << new_value << endl;

  trace.raw(write_trace.get() | value.get());


  // Determine used input and output pins
  for(i = 0; i < 2; i++)
  {
     configuration = m_configuration_bits[i][mode];
     if ((configuration & CFG_MASK) < 2)
	out_mask |= (1 << (configuration & CFG_MASK));
     for(int j = 0; j < 4; j++)
     {
	configuration >>= CFG_SHIFT;
	if ((configuration & CFG_MASK) < 4)
		in_mask |= (1 << (configuration & CFG_MASK));
     }
  }

  if (verbose)
      cout << "CMCON::put in_mask=" << in_mask << " out_mask=" << out_mask << endl;

  if ((mode != 0) && (mode != 7) && ! cm_stimulus[0])	// initialize stimulus
  {
	cm_stimulus[0] = new CM_stimulus(this, "cm_stimulus_1", 0, 1e12);
	cm_stimulus[1] = new CM_stimulus(this, "cm_stimulus_2", 0, 1e12);
	cm_stimulus[2] = new CM_stimulus(this, "cm_stimulus_3", 0, 1e12);
	cm_stimulus[3] = new CM_stimulus(this, "cm_stimulus_4", 0, 1e12);
  }
  //
  // setup outputs
  //
  for( i = 0; i < 2; i++)
  {
      if (out_mask & (1<<i))
      {
          if ( ! cm_source[i])
		cm_source[i] = new CMSignalSource();
	  cm_output[i]->setSource(cm_source[i]);
      }
      else if (cm_source[i])
      {
	    cm_output[i]->setSource(0);
      }
  }
  //
  // setup inputs
  for(i = 0; i < 4; i++)
  {
	const char *name = cm_input[i]->getPin().GUIname().c_str();

	if (cm_input[i]->getPin().snode)
        {
	    if (in_mask & (1 << i))
		(cm_input[i]->getPin().snode)->attach_stimulus(cm_stimulus[i]);
	    else
		(cm_input[i]->getPin().snode)->detach_stimulus(cm_stimulus[i]);
	}
	// rewrite GUI name as required
	if (in_mask & (1 << i) ) 
	{
	    char newname[20];

	    if (strncmp(name, "an", 2))
	    {
	    	sprintf(newname, "an%d", i);
	    	cm_input[i]->getPin().newGUIname(newname);
	    }
	}
	else
	{
	    if (!strncmp(name, "an", 2))
		cm_input[i]->getPin().newGUIname(cm_input[i]->getPin().name().c_str());
	}
	 
   }

  value.put(new_value);
  if (verbose)
      cout << "CMCON_1::put() val=0x" << hex << new_value <<endl;
  get();	// update comparator values
      
}
//--------------------------------------------------
//	Voltage reference
//--------------------------------------------------

VRCON::VRCON(void)
{
  value.put(0);
}
void VRCON::setIOpin(PinModule *newPinModule)
{
    vr_PinModule = newPinModule;
    pin_name = strdup(newPinModule->getPin().name().c_str());
}

void VRCON::put(unsigned int new_value)
{
                                                                                
  new_value &= 0xef;	// Bit 4 always 0
  unsigned int old_value = value.get();
  unsigned int diff = new_value ^ old_value;
                                                                                
  trace.raw(write_trace.get() | value.get());
                                                                                
  if (verbose & 2)
  	cout << "VRCON::put old=" << hex << old_value << " new=" << new_value << endl;
  if (!diff)
	return;

  value.put(new_value);
  if (new_value & VREN)		// Vreference enable set
  {
    	double VDD =  ((Processor *)cpu)->get_Vdd();
	vr_Rhigh = (8 + (16 - new_value & 0x0f)) * 2000.;
	vr_Rlow = (new_value & 0x0f) * 2000.;
	if (! (new_value & VRR))	// High range ?
	    vr_Rlow += 16000.;
	
	vr_Vref = VDD * vr_Rlow / (vr_Rhigh + vr_Rlow);
	if (verbose)
	{
	    cout << "VRCON::put Rhigh=" <<vr_Rhigh << " Rlow=" << vr_Rlow 
		<< " Vout=" << vr_Vref << endl;
	}
	if (new_value & VROE)	// output voltage to pin
	{

	    if (! vr_pu)
	    {
		vr_pu = new stimulus("vref_pu", VDD, vr_Rhigh);
	    }

	    if (! vr_pd)
		vr_pd = new stimulus("vref_pd", 0.0, vr_Rlow);
	    if (strcmp("Vref", vr_PinModule->getPin().name().c_str()))
 	    	vr_PinModule->getPin().newGUIname("Vref");

	    if (vr_PinModule->getPin().snode)
	    {
		vr_pu->set_Zth(vr_Rhigh);
		vr_pd->set_Zth(vr_Rlow);
	    	vr_PinModule->getPin().snode->attach_stimulus(vr_pu);
	    	vr_PinModule->getPin().snode->attach_stimulus(vr_pd);
		vr_PinModule->getPin().snode->update();
	    }
	}
	else 	// not outputing voltage to pin
	{
	    if (!strcmp("Vref", vr_PinModule->getPin().name().c_str()))
 	    	vr_PinModule->getPin().newGUIname(pin_name);
	    if (diff & 0x0f)	// did value of vreference change ?
		_cmcon->get();
	    if(vr_PinModule && vr_PinModule->getPin().snode)
	    {
                vr_PinModule->getPin().snode->detach_stimulus(vr_pu);
                vr_PinModule->getPin().snode->detach_stimulus(vr_pd);
                vr_PinModule->getPin().snode->update();
	    }
	}
  }
  else	// vref disable
  {
    if (vr_PinModule && !strcmp("Vref", vr_PinModule->getPin().name().c_str()))
 	  vr_PinModule->getPin().newGUIname(pin_name);

    if(vr_PinModule && vr_PinModule->getPin().snode)
    {
          vr_PinModule->getPin().snode->detach_stimulus(vr_pu);
          vr_PinModule->getPin().snode->detach_stimulus(vr_pd);
          vr_PinModule->getPin().snode->update();
    }
  }
}




syntax highlighted by Code2HTML, v. 0.9.1