/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: LogicLevel.java
 * Written by Tom O'Neill, Sun Microsystems.
 *
 * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) 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 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.simulation.test;

/**
 * Class for setting Vdd and a logic level on a chip, when both values are
 * provided by power supplies. The logic level will be 0 V if false or equal to
 * Vdd if true. One use is for implementing master clear to an external pin. It
 * implements <code>PowerChannel</code> for Vdd, so that changes to Vdd in
 * <code>ChainTest</code> will also change the master clear voltage.
 * <p>
 * 
 * Using this class ensures that the logic voltage tracks changes to Vdd if the
 * logic state is true. The changes in the two voltage levels occur
 * simultaneously, preventing either of the failure modes likely if the two
 * voltages were set separately: 1) Vdd temporarily exceeds the logic voltage,
 * resulting in an artificial "false" interpretation of the logic level; 2) the
 * logic voltage temporarily exceeds Vdd, resulting in the logic voltage trying
 * to power up the chip through its ESD diodes. These failure modes are
 * prevented if Vdd and the logic voltage are never allowed to differ by more
 * than about 0.5 Vdd or 0.7 V, respectively.
 * <p>
 * 
 * Note: when using this class, the user should not use the
 * <code>PowerChannel</code> methods directly to control the voltages.
 * <p>
 * 
 * To synchronize two logic levels with Vdd, create a <code>LogicLevel</code>
 * object for one and then use that as the vddChannel for a second
 * <code>LogicLevel</code> object. For example, for <code>PowerChannel</code>
 * objects <code>vddChannel</code>,<code>logicChannel</code>, and
 * <code>logicChannel2</code>, one can use:
 * 
 * <pre>
 * LogicLevel level = new LogicLevel(vddChannel, logicChannel, false);LogicLevel level2 = new LogicLevel(level, logicChannel2, false); 
 *         
 *        
 *       
 *      
 *     
 *    
 *   
 *  
 * </pre>
 * 
 * In this case, the only methods one should invoke in the <code>level</code>
 * object are <code>isLogicStateHigh()</code> and <code>setLogicState()</code>.
 * I.e., control of Vdd should only be performed using <code>level2</code>.
 */
public class LogicLevel extends PowerChannel implements LogicSettable {

    /** Object for setting chip Vdd */
    private final PowerChannel vddChannel;

    /** Object for setting voltage of the logic level */
    private final PowerChannel logicChannel;

    /** Current state of the logic level */
    private boolean logicState;

    /**
     * Maximum difference between Vdd and logic voltages when logic level is
     * true, used while ramping Vdd up or down. We set it small enough so that
     * it doesn't matter whether Vdd leads or trails the logic level.
     */
    public final static float MAX_VOLTS_DEVIATION = 0.2f;

    /* Name of the logic signal */
    private String logicName;

    /**
     * Instantiate a LogicLevel object in the specified <code>logicState</code>.
     * That is, the voltage on <code>logicChannel</code> is set to 0 V (if
     * <code>logicState==false</code>) or to the voltage on vddChannel (if
     * <code>logicState=true</code>). Requirements: <code>vddChannel</code>
     * and <code>logicChannel</code> must be instantiated, but do not need to
     * be channels on the same power supply.
     * 
     * @param vddChannel
     *            Channel of a power supply that supplies V_DD
     * @param logicChannel
     *            Channel of a power supply that supplies V_DD
     * @param logicState
     *            Desired initial state (high or low) of the logic level
     */
    public LogicLevel(PowerChannel vddChannel, PowerChannel logicChannel,
            boolean logicState) {
        this.name = vddChannel.getName();
        this.vddChannel = vddChannel;
        this.logicName = logicChannel.getName();
        this.logicChannel = logicChannel;
        this.setLogicState(logicState);
        logInit("Initializing LogicLevel " + this.name + " with "
                + this.logicName + " in the " + logicState + " state");
    }

    /** Obsolete, use getState() instead */
    public String toString() {
        Infrastructure
                .nonfatal("Please change toString() reference to getState()");
        return getState();
    }

    /**
     * Returns the name and state (voltage, current) of the channel
     * 
     * @return the name and state of the channel
     */
    public String getState() {
        return super.getState() + "; " + getLogicName() + ": "
                + isLogicStateHigh();
    }

    /**
     * Returns the name of the logic signal
     * 
     * @return name of the logic signal
     */
    public String getLogicName() {
        return logicName;
    }

    /**
     * Reads back the voltage provided by the Vdd channel of the power supply.
     * 
     * @return voltage drawn over the Vdd channel, in Amps
     */
    public float readVoltage() {
        return vddChannel.readVoltage();
    }

    /**
     * Get the Vdd channel's voltage
     * 
     * @return current voltage for the Vdd channel, in Volts
     */
    public float getVoltageSetpoint() {
        return vddChannel.getVoltageSetpoint();
    }

    /**
     * Set the Vdd channel's voltage to the value provided. If the logic level
     * is high, then we must ramp up the two voltages simultaneously to prevent
     * either of two problems: 1) the level exceeds Vdd, in which case the logic
     * pin may be trying to power the chip; 2) the level is below Vdd, in which
     * case it may erroneously appear false.
     * 
     * @param volts
     *            new voltage for the channel, in Volts
     */
    public void setVoltageNoWait(float volts) {
        logSet("LogicLevel setting " + getName() + " to " + volts
                + " V (nowait)");

        // If logicLevel is false, no impediment to setting the final Vdd
        if (this.isLogicStateHigh() == false) {
            this.vddChannel.setVoltageNoWait(volts);
            return;
        }

        // If logicLevel is true, must ramp the voltages simultaneously
        float vdd = this.vddChannel.getVoltageSetpoint();
        float logicVolts = vdd;
        float step0;
        if ((volts - vdd) >= 0) {
            step0 = MAX_VOLTS_DEVIATION;
        } else {
            step0 = -MAX_VOLTS_DEVIATION;
        }
        float step = step0;
        System.out.print("LogicLevel setting " + getName() + " to " + volts
                + " V...");
        while (Math.abs(vdd - volts) > MAX_VOLTS_DEVIATION) {
            vdd = nextVoltage(vdd, step, volts);
            this.vddChannel.setVoltageNoWait(vdd);
            step = 2 * step0;
            logicVolts = nextVoltage(logicVolts, step, volts);
            this.logicChannel.setVoltageNoWait(logicVolts);
            System.out.print(".");
        }

        // Want to achieve "volts" exactly, so take out of loop
        this.logicChannel.setVoltageNoWait(volts);
        this.vddChannel.setVoltageNoWait(volts);
        System.out.println(".done");
    }

    /**
     * Compute next voltage value, without allowing overshoot.
     * 
     * @param old
     *            current voltage
     * @param step
     *            voltage step size (may be negative)
     * @param end
     *            final voltage
     */
    private float nextVoltage(float old, float step, float end) {
        float next = old + step;
        if (step > 0) {
            next = Math.min(next, end);
        } else {
            next = Math.max(next, end);
        }
        return next;
    }

    /**
     * Set the channel's voltage and wait until it reaches requested value.
     * 
     * @param volts
     *            new voltage for the channel, in Volts
     */
    public void setVoltageWait(float volts) {
        logSet("LogicLevel setting " + getName() + " to " + volts + " V (wait)");
        setVoltageNoWait(volts);
        if (this.isLogicStateHigh()) {
            logicChannel.waitForVoltage(volts);
        }
        vddChannel.waitForVoltage(volts);
    }

    /**
     * Reads back the current provided by the Vdd channel of the power supply.
     * 
     * @return current drawn over the Vdd channel, in Amps
     */
    public float readCurrent() {
        return vddChannel.readCurrent();
    }

    /**
     * Set the Vdd channel's current limit to the value provided
     * 
     * @param amps
     *            new current limit for the channel, in Amps
     */
    public void setCurrent(float amps) {
        logSet("LogicLevel set " + getName() + " current limit to " + amps);
        vddChannel.setCurrent(amps);
    }

    /**
     * Get the Vdd channel's current limit setpoint
     * 
     * @return current limit setpoint for the channel, in Amps
     */
    public float getCurrentSetpoint() {
        return vddChannel.getCurrentSetpoint();
    }

    /**
     * @return Current value for Logic State
     */
    public boolean isLogicStateHigh() {
        return this.logicState;
    }

    /**
     * If logicState is true, sets voltage for logic level to same values as
     * Vdd. Otherwise sets it to zero.
     * 
     * @param logicState
     *            New value for logic state
     */
    public void setLogicState(boolean logicState) {
        logSet("LogicLevel setting " + getLogicName() + " to " + logicState);
        this.logicState = logicState;
        if (logicState == false) {
            this.logicChannel.setVoltageWait(0.f);
        } else {
            float volts = this.vddChannel.getVoltageSetpoint();
            this.logicChannel.setVoltageWait(volts);
        }
    }

    /**
     * Reads back the current provided by the logic channel of the power supply.
     * 
     * @return current drawn over the logic channel, in Amps
     */
    public float readLogicCurrent() {
        return logicChannel.readCurrent();
    }

    /**
     * Get the logic channel's voltage
     * 
     * @return current voltage for the logic channel, in Volts
     */
    public float getLogicVoltageSetpoint() {
        return logicChannel.getVoltageSetpoint();
    }

    /**
     * Unit test exhibits use with one power and two logic channels, then
     * exercises a single power/logic combination with a real power supply.
     * 
     * @param args
     *            ignored
     */
    public static void main(String[] args) {

        // One power, two logic channels
        PowerChannel vddChannel = new ManualPowerChannel("vdd", false);
        PowerChannel logicChannel = new ManualPowerChannel("log1", false);
        PowerChannel logicChannel2 = new ManualPowerChannel("log2", false);
        LogicLevel level = new LogicLevel(vddChannel, logicChannel, false);
        LogicLevel level2 = new LogicLevel(level, logicChannel2, false);
        level.setLogicState(true);
        level2.setLogicState(true);
        level2.setVoltageWait(1.2f);
        level2.setLogicState(false);
        level.setLogicState(false);

        System.out.println("Hit return to move on to real supply:");
        Infrastructure.readln();

        Infrastructure.gpibControllers = new int[] { 1 };
	//        vddChannel = new Pst3202Channel("vdd", "hPst3202", 1);
        //logicChannel = new Pst3202Channel("log", "hPst3202", 3);
        vddChannel = new HP6624AChannel("vdd", "hHP6624A", 1);
        logicChannel = new HP6624AChannel("log", "hHP6624A", 3);
        //        vddChannel = new ManualPowerChannel("vdd", false);
        //        logicChannel = new ManualPowerChannel("log", false);
        level = new LogicLevel(vddChannel, logicChannel, false);

        System.out.println(level.getState());
        level.setLogicState(false);
        level.setVoltageWait(1.0f);
        System.out.println("After false ramp down: " + level.getState());
        System.out.println("  logic: " + logicChannel.getVoltageSetpoint()
                + " V, " + logicChannel.readCurrent() + " A");
        level.setLogicState(true);
        level.setVoltageWait(1.75f);
        System.out.println("After true ramp up: " + level.getState());
        System.out.println("  logic: " + logicChannel.getVoltageSetpoint()
                + " V, " + logicChannel.readCurrent() + " A");
        level.setVoltageWait(1.1f);
        System.out.println("After true ramp down: " + level.getState());
        System.out.println("  logic: " + logicChannel.getVoltageSetpoint()
                + " V, " + logicChannel.readCurrent() + " A");

    }
}
