/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: SCRunBase.java
 * Written by Jonathan Gainsley.
 *
 * Copyright (c) 2009, Static Free Software. 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.sctiming;

import com.sun.electric.database.geometry.ERectangle;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.hierarchy.View;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.tool.io.output.Spice;
import com.sun.electric.tool.lang.EvalJavaBsh;
import com.sun.electric.tool.simulation.SimulationTool;
import com.sun.electric.tool.user.User;
import com.sun.electric.util.TextUtils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

/**
 *
 */
public class SCRunBase {

    /**
     * Gates with pre-configured timing arcs
     */
    public enum GateType {
        /** Inverter */                         INV,
        /** High threshold inverter */          INVHT,
        /** Low threshold inverter */           INVLT,
        /** Clock threshold inverter */         INVCLK,
        /** 2 input NAND */                     NAND2EN,
        /** 2 input NAND */                     NAND2CLKEN,
        /** 2 input NAND */                     NAND2,
        /** 3 input NAND */                     NAND3,
        /** 2 input NOR */                      NOR2,
        /** 2 input XOR */                      XOR2
        /** DDR flop */                         //DDR, FLOPDDR,
        /** SDR flop */                         //SDR, FLOPSDR
    }

    /**
     * Delay type, Max or Min path timing
     */
    public enum DelayType {
        /** Max delay */                        MAX,
        /** Min delay */                        MIN,
    }

    /**
     * Characterize all pre-configured cells in the current Electric library.
     * @param settings the settings to use
     * @return true on success, false on failure.
     */
    public boolean characterizeCells(SCSettings settings, DelayType delayType, Spice.SpicePreferences esp) {
        Library lib = Library.getCurrent();
        if (lib == null) {
            System.out.println("No current library to characterize");
            return false;
        }

        // set up directories
        File libraryDir = null;
        if (lib.getLibFile() != null)
            libraryDir = TextUtils.getFile(lib.getLibFile()).getParentFile();
        else
            libraryDir = new File(User.getWorkingDirectory());
        File outputDir = new File(libraryDir, lib.getName()+"_chardata"+delayType.toString());
        if (!outputDir.exists()) {
            if (!outputDir.mkdir()) {
                System.out.println("Cannot make char data directory "+outputDir.getPath());
                return false;
            }
        }
        System.out.println("Set output directory to "+outputDir.getPath());

        settings.setLibrary(lib.getName());
        LibData.Group library = settings.getLibrary();

        // see if lib file already exists
        File libertyFile = new File(libraryDir, lib.getName()+"."+delayType.toString()+".lib");
        if (libertyFile.exists()) {
            // read it
            LibData libdata = parseExisting(libertyFile);
            if (libdata != null) {
                System.out.println("Found existing liberty file "+libertyFile.getPath());
                System.out.println("  Using settings in existing liberty file");
                System.out.println("  Characterizing new cells only");
                settings.getSettingsFromLibData(libdata);
                library = libdata.getLibrary();
            } else {
                System.out.println("Error reading existing liberty file");
                return false;
            }
        }

        // See if extracted netlist exists
        Map<String,String> extractedCells = new HashMap<String,String>();
        File extractedNetlist = new File(User.getWorkingDirectory(), lib.getName()+".sp");
        if (extractedNetlist.exists()) {
            try {
                BufferedReader in = new BufferedReader(new FileReader(extractedNetlist));
                String line = null;
                while ( (line = in.readLine()) != null) {
                    if (line.startsWith(".SUBCKT")) {
                        String [] parts = line.split("\\s+");
                        if (parts.length >= 2) {
                            extractedCells.put(parts[1], parts[1]);
                        }
                    }
                }
                in.close();
            } catch (IOException ee) {
                System.out.println(ee.getMessage());
            }
        }


//        boolean warned = false;
        // Characterize all cells in library for which we have characterization settings
        List<String> messages = new ArrayList<String>();
        for (Iterator<Cell> it = lib.getCells(); it.hasNext(); ) {
            Cell c = it.next();
            if (c.getView() == View.LAYOUT) {
                if (c != c.getNewestVersion()) continue;

                String verilogCellName = Spice.getSafeNetName(c.getName(), SimulationTool.SpiceEngine.SPICE_ENGINE_H);

                // if data already in liberty file, ignore
                List<LibData.Group> existing = library.getGroups("cell", verilogCellName);
                if (existing.size() > 0) {
                    messages.add("Info: Skipped cell "+c.getName()+", already exists in liberty file data");
                    continue;
                }

                SCTiming timing = getSCTimingSetup(c, settings);
                String cname = c.getName();
                if (timing == null) {
                    messages.add("Warning: Skipping "+cname+": Unable to find timing arcs");
                    continue;
                }

                File cellDir = new File(outputDir, c.getName());
                if (!cellDir.exists()) {
                    if (!cellDir.mkdir()) {
                        messages.add("Error: Cannot make cell char data directory "+cellDir.getPath()+"; skipping cell "+cname);
                        continue;
                    }
                }

                // Use either star rcxt extracted netlist or write netlist from Electric layout
                File outputFile = new File(cellDir, c.getName()+".sp");

                String cellName = Spice.getSafeNetName(c.getName(), SimulationTool.SpiceEngine.SPICE_ENGINE_H);
                if (extractedCells.containsKey(cellName)) {
                    try {
                        PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(outputFile)));
                        writer.println("* Characterization file for Cell "+cellName);
                        writer.println("*");
                        writer.println(".include '"+extractedNetlist.getAbsolutePath()+"'");
                        writer.println(".include '"+settings.commonHeaderFile+"'");
                        writer.println();
                        writer.close();
                    } catch (IOException e) {
                        System.out.println(e.getMessage());
                    }
                    timing.setTopCellName(cellName);
                } else {
                    // set correct settings for spice netlisting
                    Spice.SpicePreferences sp = new Spice.SpicePreferences(true, false);
                    sp.writeSubcktTopCell = true;
                    sp.writeTopCellInstance = false;
                    sp.writeTransSizeInLambda = true; // false; // true originally
                    sp.writeEmptySubckts = false;
//                    sp.cdlIgnoreResistors = false;
                    sp.useCellParameters = true;
                    sp.parasiticsLevel = SimulationTool.SpiceParasitics.RC_CONSERVATIVE;
                    sp.writeFinalDotEnd = false;
                    sp.parasiticsUseExemptedNetsFile = false;
                    sp.parasiticsExtractsR = true;
                    sp.parasiticsExtractsC = true;
                    if (settings.commonHeaderFile.length() > 0)
                        sp.headerCardInfo = settings.commonHeaderFile;
                    
                    // From other side
                    /*
                    sp.writeSubcktTopCell = true;
                    sp.writeTransSizeInLambda = false;
                    sp.writeEmptySubckts = false;
                    sp.cdlIgnoreResistors = false;
                    sp.useCellParameters = false;
                    sp.headerCardInfo = "";
                    sp.trailerCardInfo = "";
                    sp.runProbe = false;
                    sp.runChoice = SimulationTool.spiceRunChoiceDontRun;
                    sp.ignoreModelFiles = true;
                    */
                    
                    // write spice netlist
                    sp.doOutput(c, VarContext.globalContext, outputFile.getPath());

                    timing.setTopCellName(c.getName());
                }

                timing.setOutputDir(cellDir.getPath());
                timing.setTopCell(c);
                timing.setTopCellNameLiberty(verilogCellName);
                timing.setInputFile(outputFile.getPath());

                try {
                    timing.characterize_(delayType);
                } catch (SCTimingException e) {
                    messages.add("Error: Failed to characterize cell "+c.describe(false)+": "+e.getMessage());
                    continue;
                }

                timing.getCellLibData(library);
                // set area of cell
                List<LibData.Group> list = library.getGroups("cell", verilogCellName);
                ERectangle rect = c.getBounds();
                double scale = c.getTechnology().getScale() / 1000; // lambda to microns
                double area = rect.getWidth() * rect.getHeight() * scale * scale;
                for (LibData.Group g : list) {
                    g.putAttribute("area", area);
                }
            }
        }

        System.out.println("\nCharacterization Complete\n");
        for (String message : messages) {
            System.out.println(message);
        }

        if (libertyFile.exists()) {
            File temp = new File(libertyFile.getPath());
            File backup = new File(libertyFile.getParent(), libertyFile.getName()+".old");
            System.out.println("Renaming existing liberty file to "+backup.getName());
            if (!temp.renameTo(backup)) {
                System.out.println("Warning, unable to rename "+libertyFile.getName()+" to "+backup.getName());
            }
        }

        LibData libData = new LibData();
        libData.setLibrary(library);
        libData.write(libertyFile.getPath());
        System.out.println("Wrote liberty file to "+libertyFile.getPath());
        return true;
    }

    protected LibData parseExisting(File libertyFile) {
        return null;
    }
    
    /**
     * Get an SCTiming object with Timing Arcs for the given cell
     * @param cell the cell
     * @param settings global settings
     * @return SCTiming object for characterization
     */
    public static SCTiming getSCTimingSetup(Cell cell, SCSettings settings) {
        SCTiming timing = null;
        for (Cell acell : cell.getCellsInGroup()) {
            if (acell.getView() == View.DOC && acell == acell.getNewestVersion()) {
                timing = getSetupFromScript(acell.getTextViewContents(), settings);
                break;
	        }
        }
        if (timing == null) {
            String cname = cell.getName().toLowerCase();
            for (GateType type : GateType.values()) {
                if (cname.matches(type.toString().toLowerCase()+"_x[0-9.]+")) {
                    String size = cname.substring(cname.lastIndexOf("x")+1, cname.length());
                    double xsize = Double.parseDouble(size);
                    timing = getSCTimingSetup(type, settings, xsize);
                    break;
                }
            }
        }
        return timing;
    }

    private static Pattern cellsize1 = Pattern.compile(".*?_([0-9.]+)x");
    private static Pattern cellsize2 = Pattern.compile(".*?_x([0-9.]+)");

    public static double getCellSize(String cellname) {
        String s = null;
        Matcher m1 = cellsize1.matcher(cellname);
        if (m1.matches()) {
            s = m1.group(1);
        } else {
            Matcher m2 = cellsize2.matcher(cellname);
            if (m2.matches()) {
                s = m2.group(1);
            }
        }
        if (s != null) {
            try {
                return Double.valueOf(s);
            } catch (NumberFormatException e) {
                return 1.0;
            }
        }
        return 1.0;
    }

    public static SCTiming getSetupFromScript(String [] script, SCSettings settings) {
        EvalJavaBsh bsh = new EvalJavaBsh();
        bsh.doEvalLine("import com.sun.electric.tool.simulation.sctiming.*;");
        SCTiming timing = new SCTiming();
        timing.setSettings(settings);

        bsh.setVariable("timing", timing);
        bsh.setVariable("settings", settings);
        boolean run = false;
        boolean scriptFound = false;
        for (String line : script) {
            if (line.toLowerCase().startsWith("--start characterization script")) {
                run = true;
                scriptFound = true;
                continue;
            }
            if (line.toLowerCase().startsWith("--end characterization script")) {
                run = false;
                continue;
            }
            if (run)
                bsh.doEvalLine(line);
        }
        Object t = bsh.getVariable("timing");
        if (scriptFound && t instanceof SCTiming)
            return (SCTiming)t;
        return null;
    }

    /**
     * Get an SCTiming object with Timing Arcs for the given gate type
     * @param type recognized gate type
     * @param settings global settings
     * @return SCTiming object for characterization
     */
    public static SCTiming getSCTimingSetup(GateType type, SCSettings settings, double xsize) {
        switch (type) {
            case INV: return getSetupInv(settings, xsize);
            case INVHT: return getSetupInv(settings, xsize);
            case INVLT: return getSetupInv(settings, xsize);
            case INVCLK: return getSetupInv(settings, xsize);
            case NAND2: return getSetupNand2(settings, xsize);
            case NAND2EN: return getSetupNand2en(settings, xsize);
            case NAND2CLKEN: return getSetupNand2en(settings, xsize);
            case NAND3: return getSetupNand3(settings, xsize);
            case NOR2: return getSetupNor2(settings, xsize);
            //case XOR2: return getSetupXor2(settings, xsize);
            //case DDR: return getSetupFlopDDR(settings);
            //case FLOPDDR: return getSetupFlopDDR(settings);
            //case SDR: return getSetupFlopSDR(settings);
            //case FLOPSDR: return getSetupFlopSDR(settings);
            default: break;
        }
        return null;
    }

    /**
     * Get an SCTiming object with Timing Arcs for an Inverter type gate
     * @param settings global settings
     * @return SCTiming object for characterization
     */
    public static SCTiming getSetupInv(SCSettings settings, double xsize) {
        SCTiming timing = new SCTiming();
        timing.setSettings(settings);
        timing.setFunctionCombinational("out", "!in");

        String outputLoads = getLoadSweep(xsize);
        String buffers = getInputBufferSweep(xsize);

        Arc arc = new Arc();
        arc.setInputTransition("in", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("in", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        return timing;
    }

    /**
     * Get an SCTiming object with Timing Arcs for a NAND2 type gate
     * @param settings global settings
     * @return SCTiming object for characterization
     */
    public static SCTiming getSetupNand2(SCSettings settings, double xsize) {
        SCTiming timing = new SCTiming();
        timing.setSettings(settings);
        timing.setFunctionCombinational("out", "!(ina * inb)");

        String outputLoads = getLoadSweep(xsize);
        String buffers = getInputBufferSweep(xsize);

        Arc arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addStableInput("inb", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addStableInput("inb", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addStableInput("ina", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addStableInput("ina", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        return timing;
    }

    /**
     * Get an SCTiming object with Timing Arcs for a NAND2EN type gate
     * NOTE: this assumes inb is the enable input
     * @param settings global settings
     * @return SCTiming object for characterization
     */
    public static SCTiming getSetupNand2en(SCSettings settings, double xsize) {
        SCTiming timing = new SCTiming();
        timing.setSettings(settings);
        timing.setFunctionCombinational("out", "!(ina * inb)");

        String outputLoads = getLoadSweep(xsize);
        String buffers = getInputBufferSweep(xsize);

        Arc arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addStableInput("inb", PinEdge.Transition.STABLE1);
        //arc.setOutputLoadSweep("2 4 8 12 24 26 28 30");
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addStableInput("inb", PinEdge.Transition.STABLE1);
        //arc.setOutputLoadSweep("2 4 8 12 24 26 28 30");
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addStableInput("ina", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addStableInput("ina", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        return timing;
    }

    /**
     * Get an SCTiming object with Timing Arcs for a NAND3 type gate
     * @param settings global settings
     * @return SCTiming object for characterization
     */
    public static SCTiming getSetupNand3(SCSettings settings, double xsize) {
        SCTiming timing = new SCTiming();
        timing.setSettings(settings);
        timing.setFunctionCombinational("out", "!(ina * inb * inc)");

        String outputLoads = getLoadSweep(xsize);
        String buffers = getInputBufferSweep(xsize);

        Arc arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addStableInput("inb", PinEdge.Transition.STABLE1);
        arc.addStableInput("inc", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addStableInput("inb", PinEdge.Transition.STABLE1);
        arc.addStableInput("inc", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addStableInput("ina", PinEdge.Transition.STABLE1);
        arc.addStableInput("inc", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addStableInput("ina", PinEdge.Transition.STABLE1);
        arc.addStableInput("inc", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inc", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addStableInput("ina", PinEdge.Transition.STABLE1);
        arc.addStableInput("inb", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inc", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addStableInput("ina", PinEdge.Transition.STABLE1);
        arc.addStableInput("inb", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        return timing;
    }

    /**
     * Get an SCTiming object with Timing Arcs for a NOR2 type gate
     * @param settings global settings
     * @return SCTiming object for characterization
     */
    public static SCTiming getSetupNor2(SCSettings settings, double xsize) {
        SCTiming timing = new SCTiming();
        timing.setSettings(settings);
        timing.setFunctionCombinational("out", "!(ina + inb)");

        String outputLoads = getLoadSweep(xsize);
        String buffers = getInputBufferSweep(xsize);

        Arc arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addStableInput("inb", PinEdge.Transition.STABLE0);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addStableInput("inb", PinEdge.Transition.STABLE0);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addStableInput("ina", PinEdge.Transition.STABLE0);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addStableInput("ina", PinEdge.Transition.STABLE0);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        return timing;
    }

    /**
     * Get an SCTiming object with Timing Arcs for a XOR2 type gate
     * @param settings global settings
     * @return SCTiming object for characterization
     */
    public static SCTiming getSetupXor2(SCSettings settings, double xsize) {
        SCTiming timing = new SCTiming();
        timing.setSettings(settings);
        timing.setFunctionCombinational("out", "ina ^ inb");

        String outputLoads = getLoadSweep(xsize);
        String buffers = getInputBufferSweep(xsize);

        Arc arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addDependentStableInput("inb", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addDependentStableInput("inb", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addDependentStableInput("inb", PinEdge.Transition.STABLE0);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("ina", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addDependentStableInput("inb", PinEdge.Transition.STABLE0);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addDependentStableInput("ina", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addDependentStableInput("ina", PinEdge.Transition.STABLE1);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.RISE);
        arc.setOutputTransition("out", PinEdge.Transition.RISE);
        arc.addDependentStableInput("ina", PinEdge.Transition.STABLE0);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("inb", PinEdge.Transition.FALL);
        arc.setOutputTransition("out", PinEdge.Transition.FALL);
        arc.addDependentStableInput("ina", PinEdge.Transition.STABLE0);
        arc.setOutputLoadSweep(outputLoads);
        arc.setInputBufferSweep(buffers);
        timing.addTimingArc(arc);

        return timing;
    }

    // params for load and input buffer sweeps
    private static double lowerBound = 0.00001;
    private static int upperBound = 310;
    private static int numSizes = 8;

    /**
     * Get sweep of loads based on dut xsize
     * @param xsize size of gate to characterize
     * @return list of sizes to use for loads
     */
    public static String getLoadSweep(double xsize) {
        // config, hard coded for now.
        double minStepup = 0.5;
        double maxStepup = 12;

//        double minSize = xsize*minStepup;
//        double maxSize = xsize*maxStepup;
//        double stepX = (maxSize - minSize) / numSizes;
        double su = Math.pow(maxStepup/minStepup, 1.0/(numSizes-1));

        StringBuffer loads = new StringBuffer();
        for (int i=0; i<numSizes; i++) {
            //double size = minSize+(i)*stepX;
            double size = xsize * minStepup * Math.pow(su, i);
            if (size < lowerBound || size > upperBound) continue;
            loads.append(TextUtils.formatDouble(size,2)+" ");
        }
        return loads.toString().trim();
    }

    public static String getInputBufferSweep(double xsize) {
        double minStepup = 1.0/12;
        double maxStepup = 2.0;
        double su = Math.pow(maxStepup/minStepup, 1.0/(numSizes-1));

        StringBuffer loads = new StringBuffer();
        for (int i=numSizes-1; i>=0; i--) {
            double size = xsize * minStepup * Math.pow(su, i);
            size = size/2; // account for second inverter in buffer that is double size
            if (size < lowerBound || size > upperBound) continue;
            loads.append(TextUtils.formatDouble(size,2)+" ");
        }
        return loads.toString().trim();
    }

    /**
     * Get an SCTiming object with Timing Arcs for a DDR Flop type gate
     * @param settings global settings
     * @return SCTiming object for characterization
     */
    public static SCTiming getSetupFlopDDR(SCSettings settings) {
        SCTiming timing = new SCTiming();
        timing.setSettings(settings);
        timing.setFunctionFlipFlop("Q", "QB", "D", "clkt");

        Arc arc = new Arc();
        arc.setInputTransition("D", PinEdge.Transition.RISE);
        arc.setOutputTransition("Q", PinEdge.Transition.RISE);
        arc.setClkTransition("clkt", PinEdge.Transition.RISE);
        arc.setClkFalseTransition("clkf", PinEdge.Transition.FALL);
        arc.addDUTInitialCondition("topnode2", settings.vdd);
        arc.addDUTInitialCondition("topnode1", 0);
        arc.addDUTInitialCondition("botnode2", settings.vdd);
        arc.addDUTInitialCondition("botnode1", 0);
        arc.setInputTransition("clr", PinEdge.Transition.STABLE0);
        arc.setInputTransition("init", PinEdge.Transition.STABLE0);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("D", PinEdge.Transition.FALL);
        arc.setOutputTransition("Q", PinEdge.Transition.FALL);
        arc.setClkTransition("clkt", PinEdge.Transition.RISE);
        arc.setClkFalseTransition("clkf", PinEdge.Transition.FALL);
        arc.addDUTInitialCondition("topnode2", 0);
        arc.addDUTInitialCondition("topnode1", settings.vdd);
        arc.addDUTInitialCondition("botnode2", 0);
        arc.addDUTInitialCondition("botnode1", settings.vdd);
        arc.setInputTransition("clr", PinEdge.Transition.STABLE0);
        arc.setInputTransition("init", PinEdge.Transition.STABLE0);
        timing.addTimingArc(arc);

        return timing;
    }

    /**
     * Get an SCTiming object with Timing Arcs for a SDR Flop type gate
     * @param settings global settings
     * @return SCTiming object for characterization
     */
    public static SCTiming getSetupFlopSDR(SCSettings settings) {
        SCTiming timing = new SCTiming();
        timing.setSettings(settings);
        timing.setFunctionFlipFlop("Q", "QB", "D", "clkt");

        Arc arc = new Arc();
        arc.setInputTransition("D", PinEdge.Transition.RISE);
        arc.setOutputTransition("Q", PinEdge.Transition.RISE);
        arc.setClkTransition("clkt", PinEdge.Transition.RISE);
        arc.setClkFalseTransition("clkf", PinEdge.Transition.FALL);
        timing.addTimingArc(arc);

        arc = new Arc();
        arc.setInputTransition("D", PinEdge.Transition.FALL);
        arc.setOutputTransition("Q", PinEdge.Transition.FALL);
        arc.setClkTransition("clkt", PinEdge.Transition.RISE);
        arc.setClkFalseTransition("clkf", PinEdge.Transition.FALL);
        timing.addTimingArc(arc);

        return timing;
    }
}
