/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: StdCellParams.java
 *
 * Copyright (c) 2003, 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.generator.layout;

import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortCharacteristic;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.SizeOffset;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.ncc.Ncc;
import com.sun.electric.tool.ncc.NccOptions;
import com.sun.electric.tool.ncc.result.NccResults;
import com.sun.electric.tool.Job;
import com.sun.electric.util.math.DBMath;


/** The bottom of the PMOS well and the top of the NMOS well are at
 * y=0.  PMOS tracks are numbered from 1 to nbPmosTracks(). These
 * numbers correspond to the lowest and highest, respectively,
 * unblocked tracks in the PMOS region.  NMOS tracks are numbered from
 * -1 to -nbNmosTracks().  These numbers correspond to the highest and
 * lowest, respectively, unblocked tracks in the NMOS region. */
public class StdCellParams {
    // ---------------------- private classes --------------------------------
	private static class TrackBlockages {
		private static class Blockage {
			double bot, top;
			Blockage(double center, double width) {
				bot = center - width / 2;
				top = center + width / 2;
			}
		}
		private ArrayList<Blockage> blockages = new ArrayList<Blockage>();
		private double space;

		TrackBlockages(double s) {
			this.space = s;
		}

		void addBlockage(double center, double width) {
			blockages.add(new Blockage(center, width));
		}
		boolean isBlocked(double center, double width) {
			double top = center + width / 2 + space;
			double bot = center + width / 2 - space;
			for (int i = 0; i < blockages.size(); i++) {
				Blockage b = blockages.get(i);
				if (b.bot < top && b.top > bot)
					return true;
			}
			return false;
		}
	}

	private static void error(boolean pred, String msg) {
		Job.error(pred, msg);
	}

	// -------------------------- private data ---------------------------------
	private static final double DEF_SIZE = LayoutLib.DEF_SIZE;
    private static final Variable.Key ATTR_X = Variable.newKey("ATTR_X");
    private static final Variable.Key ATTR_S = Variable.newKey("ATTR_S");
    private static final Variable.Key ATTR_SN = Variable.newKey("ATTR_SN");
    private static final Variable.Key ATTR_SP = Variable.newKey("ATTR_SP");



    // ===============================================================
    /* These variable's initial values are Technology dependent */

	private double nmosWellHeight;
	private double pmosWellHeight;
	private double gndY;
	private double vddY;
	private double gndWidth;
	private double vddWidth;
	private double trackPitch;
	private double trackWidth;
    private double m1TrackWidth;
    private double m2TrackWidth;
	private double metalSpace;
    private double pmosTrackOffset;
    private double nmosTrackOffset;
	// An "enable" style Nand gate has a weak pullup. This is how much
	// weaker than normal the PMOS is.
	private double enableGateStrengthRatio;

	// Separate well ties from power and ground to allow threshold
	// control via substrate bias.
	private boolean separateWellTies;
	// maximum distance from well tie to any point in well
	private double maxWellTieRadius;
//	private String pmosWellTieName = "vnw";
//	private String nmosWellTieName = "vsb";
	private double nmosWellTieY, pmosWellTieY;

	private double minGateWid;
	private double maxMosWidth;

    private double gridResolution;
    private double drcRingSpace;

    // for well contacts
    private double wellConSelectHeight;         // height of select layer around well tap
    private double wellConSelectOffset;         // offset of select from anchor point

    // ======================================================

	// Critera for reducing the number of distict sizes.
	// Default to 10% error.
	private double sizeErr = 0.1;

	private static final double selectOverhangsDiffCont = 4.5;
	private static final double m1OverhangsDiffCont = 2;
	private static final double m1Space = 3;

	private ArrayList<Double> nmosTracks = new ArrayList<Double>();
	private ArrayList<Double> pmosTracks = new ArrayList<Double>();
//	private int botNmosTrack, topNmosTrack, botPmosTrack, topPmosTrack;
	private Library schemLib = null;
	private Library layoutLib = null;
	private boolean doubleStrapGate = false;
	private boolean exhaustivePlace = true;
	private int nbPlacerPerms = 40000;
	private boolean simpleName = false;
	private String vddExportName = "vdd";
	private String gndExportName = "gnd";
	private PortCharacteristic vddExportRole = PortCharacteristic.PWR;
	private PortCharacteristic gndExportRole = PortCharacteristic.GND;
	private TechType tech;
    final EditingPreferences ep;
    private boolean is90nm;

    // ------------------------ private methods -----------------------------

    private void initMoCMOS() {
        nmosWellHeight = 70;
        pmosWellHeight = 70;
        gndY = -21.0;
        vddY = 21.0;
        gndWidth = 10;
        vddWidth = 10;
        trackPitch = 7;
        trackWidth = 4;
        m1TrackWidth = 4;
        m2TrackWidth = 4;
        metalSpace = 3;
        pmosTrackOffset = trackPitch / 2;
        nmosTrackOffset = -trackPitch / 2;
        // An "enable" style Nand gate has a weak pullup. This is how much
        // weaker than normal the PMOS is.
        enableGateStrengthRatio = .1;

        // Separate well ties from power and ground to allow threshold
        // control via substrate bias.
        separateWellTies = false;
        // maximum distance from well tie to any point in well
        maxWellTieRadius = 300;
//        pmosWellTieName = "vnw";
//        nmosWellTieName = "vsb";
        nmosWellTieY = 0;
        pmosWellTieY = 0;

        minGateWid = 3;
        maxMosWidth = 45;
        gridResolution = 0.5;
        drcRingSpace = 3;
        wellConSelectHeight = 9;
        wellConSelectOffset = 0;
    }

    private void initCMOS90() {
        nmosWellHeight = 84;
        pmosWellHeight = 84;
        gndY = -24.5;
        vddY = 24.5;
        gndWidth = 9;
        vddWidth = 9;
        trackPitch = 7;
        trackWidth = 3.4;
        m1TrackWidth = 3.4;
        m2TrackWidth = 2.8;
        metalSpace = 2.4;
        pmosTrackOffset = 7;
        nmosTrackOffset = -77;
        // An "enable" style Nand gate has a weak pullup. This is how much
        // weaker than normal the PMOS is.
        enableGateStrengthRatio = .1;

        // Separate well ties from power and ground to allow threshold
        // control via substrate bias.
        separateWellTies = false;
        // maximum distance from well tie to any point in well
        maxWellTieRadius = 300;
//        pmosWellTieName = "vnw";
//        nmosWellTieName = "vsb";
        nmosWellTieY = 0;
        pmosWellTieY = 0;

        minGateWid = 5.2;
        maxMosWidth = 45;
        gridResolution = 0.1;
        drcRingSpace = 0;
        wellConSelectHeight = 8.4;
        wellConSelectOffset = 0;
        is90nm = true;
    }

    /** Initialize Tracks after setting up parameters */
	private void init() {
		TrackBlockages blockages = new TrackBlockages(metalSpace);

		// The symmetric nand2 and nand3 reserve 2 metal-2 tracks for
		// internal use only to connect the output.
		blockages.addBlockage(11, 4);
		blockages.addBlockage(-11, 4);

		// Power and ground also block metal-2 tracks
		blockages.addBlockage(vddY, vddWidth);
		blockages.addBlockage(gndY, gndWidth);

		generateTracks(blockages);

		nmosWellTieY = gndY;
		pmosWellTieY = vddY;
		if (separateWellTies) {
			// Place well ties close to top and bottom of cells
			nmosWellTieY = getTrackY(- (nbNmosTracks() - 1));
			blockages.addBlockage(nmosWellTieY, trackWidth);
			pmosWellTieY = getTrackY(nbPmosTracks() - 1);
			blockages.addBlockage(pmosWellTieY, trackWidth);
			// recompute available tracks with new blockages
			generateTracks(blockages);
		}
	}
	private boolean is90nm() {
		return is90nm;
	}

	// create a list of tracks that don't overlap vdd or gnd
	private void generateTracks(TrackBlockages blockages) {
		// this may be called multiple times if setSeparateWellTies is called
		// 0 is an illegal index
		nmosTracks.clear();
		pmosTracks.clear();

		// tracks in PMOS region
		for (double y = pmosTrackOffset; y < pmosWellHeight; y += trackPitch) {
			if (!blockages.isBlocked(y, trackWidth))
				pmosTracks.add(Double.valueOf(y));
		}

		// tracks in NMOS region
		for (double y = nmosTrackOffset;
			y > -nmosWellHeight;
			y -= trackPitch) {
			if (!blockages.isBlocked(y, trackWidth))
				nmosTracks.add(Double.valueOf(y));
		}
	}

	private double quantizeMantissa(double mantissa) {
		double szRatio = (1 + sizeErr) / (1 - sizeErr);
		// find n such that (szRatio^n) is closest to mantissa
		double logBaseSzRatio = Math.log(mantissa) / Math.log(szRatio);
		double floorN = Math.floor(logBaseSzRatio);
		double ceilN = Math.ceil(logBaseSzRatio);
		double hiApprox = Math.pow(szRatio, ceilN);
		double loApprox = Math.pow(szRatio, floorN);

		return (hiApprox - mantissa < mantissa - loApprox)
			? (hiApprox)
			: (loApprox);
	}

	private int calcNbGroups(double maxAvailWid, double totWid, int groupSz) {
		int nbGroups = (int) Math.ceil(totWid / maxAvailWid / groupSz);

		// If groupSz is even then we always create an even number of
		// fingers and we don't have to add more fingers to reduce
		// diffusion capacitance.
		if (groupSz % 2 == 0)
			return nbGroups;

		// If nbGroups is even then we always create an even number of
		// fingers and we don't have to add more fingers to reduce
		// diffusion capacitance
		if (nbGroups % 2 == 0)
			return nbGroups;

        // if totWid is less than maxAvailWid and groupSz is 1, just
        // use 1 gate (no fingers)
        if ((totWid < maxAvailWid) && (groupSz == 1))
            return 1;

		// try adding one more group to get an even number of fingers
		int roundupGroups = nbGroups + 1;
		double wid = totWid / groupSz / roundupGroups;

		// Don't fold if gate width is less than width of diffusion
		// contact.
		if (wid >= tech.getDiffContWidth())
			nbGroups = roundupGroups;

		return nbGroups;
	}

	private void fillDiffAndSelectNotch(PortInst prevPort, PortInst thisPort,
                                        FoldedMos mosLeft, FoldedMos mosRight, boolean fillDiffNotch) {
		double diffWid = mosLeft.getPhysWidth();
		Cell f = mosLeft.getSrcDrn(0).getNodeInst().getParent();
//		PrimitiveNode diffCont =
//			mosLeft instanceof FoldedPmos ? tech.pdm1 : tech.ndm1;
		ArcProto diffArc = mosLeft instanceof FoldedPmos ? tech.pdiff() : tech.ndiff();
		NodeProto diffNode =
			mosLeft instanceof FoldedPmos ? tech.pdNode() : tech.ndNode();

		double prevX = LayoutLib.roundCenterX(prevPort);
		double thisX = LayoutLib.roundCenterX(thisPort);
		double dist = thisX - prevX;

		// If they overlap perfectly or if they're so far apart there's no
		// select notch then no notches of any kind are possible.
		if (dist == 0 || (dist >= selectOverhangsDiffCont * 2 + tech.getSelectSpacingRule()))
			return;

		// Fill a notch with diffusion.
		//
		// I can't fill notches using a diffusion arc because the span in
		// the Y coordinate of the port of the diffusion contact may not
		// include the center of such an arc. This occurs for MOS
		// transistors with a single contact cut.
		//
		// I therefore fill the notch using a diffusion node.
		double mosY = mosLeft.getMosCenterY();
        double mosRightY = mosRight.getMosCenterY();
        // if they are not aligned along Y, the extra select node might not cover top transistor.
        if (!DBMath.areEquals(mosY, mosRightY))
        {
            double activePlusSelect = diffWid/2 + tech.getSelectSurroundDiffInTrans();
            double topY = -activePlusSelect, bottomY = activePlusSelect;
            double sign = 1;

            if (mosY > mosRightY)
            {
                topY += mosY; bottomY += mosRightY; sign = -1;
            }
            else
            {
                topY += mosRightY; bottomY += mosY;
            }
            double diff = DBMath.round(topY - bottomY);
            if (DBMath.isGreaterThan(diff, 0))
            {
                // round diff to multiple of min precision otherwise diff/2 (or node center will generate DRC errors)
//                double minRe = f.getTechnology().getResolution()*2;
                double minRe = getGridResolution()*2;
                if (minRe > 0 && DBMath.hasRemainder(diff, minRe))
                {
                    diff += getGridResolution(); // to ceil correctlu otherwise it could leave a notch
                    diff = ((int)(Math.ceil(diff/minRe))) * minRe;
                }
                diffWid += diff;
                mosY += DBMath.round(sign * diff/2);
            }
        }
        double a = mosLeft.getDiffContWidth();
        double b = mosLeft.getGateWidth();

        Rectangle2D diffNodeBnd = LayoutLib.calculateNodeInst(diffNode, thisX-dist/2, mosY,
                dist, diffWid, ep);
        if (fillDiffNotch)
        {
            NodeInst dFill = LayoutLib.newNodeInst(diffNode, ep, diffNodeBnd, 0, f);
            double contY = LayoutLib.roundCenterY(thisPort); // contact is always on grid
            LayoutLib.newArcInst(diffArc, ep, DEF_SIZE, thisPort, thisX, contY,
                                 dFill.getOnlyPortInst(),
                                 LayoutLib.roundCenterX(dFill.getOnlyPortInst()),
                                 contY);

            // Never place wide arcs directly onto the FoldedMos
            // diffusion-metal1 contacts because they become bad width hints.
            if (dist < m1OverhangsDiffCont * 2 + m1Space) {
                // m1 is 1 lambda narrower than diffusion contact width
                double m1Wid = mosLeft.getDiffContWidth() - 1;
                NodeInst mFill =
                    LayoutLib.newNodeInst(tech.m1Node(), ep, thisX-dist/2, contY,	dist,
                                          m1Wid, 0, f);
                LayoutLib.newArcInst(tech.m1(), ep, DEF_SIZE, thisPort, mFill.getOnlyPortInst());
            }
        }
        // Rounding values to avoid out-of-grid values
        double roundedPosY = DBMath.round(diffNodeBnd.getY() - diffNodeBnd.getHeight()/2);
        Rectangle2D selectRec = new Rectangle2D.Double(diffNodeBnd.getX() - dist/2, roundedPosY,
                diffNodeBnd.getWidth(), diffNodeBnd.getHeight());
        double selectDiff = (a - b);
//        if (DBMath.isGreaterThan(selectDiff, 0))
		    addSelAroundDiff(diffNode, selectRec, selectDiff, f);
	}

	private static FoldedMos getRightMos(Object a) {
		if (a instanceof FoldedMos)  return (FoldedMos) a;
		error(!(a instanceof FoldedMos[]), "not FoldedMos or FoldedMos[]");
		FoldedMos[] moss = (FoldedMos[]) a;
		return moss[moss.length - 1];
	}

	private static String trkMsg(Object key, Cell schem) {
		return "Track assignment for export: "
			+ key
			+ " in Cell: "
			+ schem.getName()
			+ ".\n    ";
	}

	//----------------------------- public constants --------------------------
	/** This class allows the user to specify which source/drains
	 * wireVddGnd() should connect to power or ground.  Most users will
	 * be happy with the predefined instances: ODD or EVEN. */
	public interface SelectSrcDrn {
		public boolean connectThisOne(int mosNdx, int srcDrnNdx);
	}

	public static final SelectSrcDrn EVEN = new SelectSrcDrn() {
		public boolean connectThisOne(int mosNdx, int srcDrnNdx) {
			return srcDrnNdx % 2 == 0;
		}
	};

	public static final SelectSrcDrn ODD = new SelectSrcDrn() {
		public boolean connectThisOne(int mosNdx, int srcDrnNdx) {
			return srcDrnNdx % 2 == 1;
		}
	};

	//------------------------ public methods --------------------------------------

	//------------------------------------------------------------------------------
	// Allow user to customize standard cell characteristics
	//
	/** Attach the well ties to special busses rather than to Vdd and Gnd. */
	public void setDoubleStrapGate(boolean val) {
		doubleStrapGate = val;
	}

	/** GasP generators use exhaustive search for gate placement */
	public void setExhaustivePlace(boolean val) {
		exhaustivePlace = val;
	}

	/** Set number of permuations GasP placer should try before giving
	 * up. */
	public void setNbPlacerPerms(int i) {
		nbPlacerPerms = i;
	}

	/** Units of lambda */
	public void setNmosWellHeight(double h) {
		nmosWellHeight = h;
		init();
	}

	/** Units of lambda */
	public void setPmosWellHeight(double h) {
		pmosWellHeight = h;
		init();
	}

	/** Set the maximum width of a each MOS finger. Units of lambda */
	public void setMaxMosWidth(double wid) {
		maxMosWidth = wid;
	}

    /** Set the starting offset from 0 of the pmos tracks */
    public void setPmosTrackOffset(double offset) {
        pmosTrackOffset = offset;
        init();
    }

    /** Set the starting offset from 0 of the nmos tracks */
    public void setNmosTrackOffset(double offset) {
        nmosTrackOffset = offset;
        init();
    }

    /** Set the track width */
    public void setTrackWidth(double width) {
        trackWidth = width;
        init();
    }

    /** Set the width of the Vdd track */
    public void setVddWidth(double width) {
        vddWidth = width;
        init();
    }

    /** Set the width of the Gnd track */
    public void setGndWidth(double width) {
        gndWidth = width;
        init();
    }

    public void setM1TrackWidth(double width) {
        m1TrackWidth = width;
    }
    public double getM1TrackWidth() { return m1TrackWidth; }

    public void setM2TrackWidth(double width) {
        m2TrackWidth = width;
    }
    public double getM2TrackWidth() { return m2TrackWidth; }

    public double getDRCRingSpacing() { return drcRingSpace; }

    public double getM1TrackAboveVDD() {
        double trackX;
        if (is90nm()) {
            // vddTop + m1_m1_sp + m1_wid/2 + m1_xcon_extension
            trackX = getVddY() + getVddWidth()/2 + 2.4 + getM1TrackWidth()/2 + 2.8;
        } else {
            // vddTop + m1_m1_sp + m1_wid/2
            trackX = getVddY() + getVddWidth()/2 + 3 + 2;
        }
        return trackX;
    }

    public double getM1TrackBelowGnd() {
        double trackX;
        double gndBot = getGndY() - getGndWidth()/2;
        if (is90nm()) {
            // gndBot - m1_m1_sp - m1_wid/2 - m1_xcon_extension
            trackX = gndBot - 2.4 - getM1TrackWidth()/2 - 2.8;
        } else {
            // gndBot - m1_m1_sp - m1_wid/2
            trackX = gndBot - getM1TrackWidth() - 2;
        }
        return trackX;
    }


    public double getWellOverhangDiff() {
        if (is90nm()) return 8;
        else return 6;
    }

    public double getGridResolution() { return gridResolution; }

    /** Get the fold pitch for folded transistor, given the number of series transistors */
    public double getFoldPitch(int nbSeries) {
        if (is90nm())
            return (8 + (nbSeries - 1) * (3 + 2));
        else
            return (8 + (nbSeries - 1) * (3 + 2));
    }

	/** Turn on Network Consistency Checking after each gate is generated.
	 *<p> This just checks topology and ignores sizes. */
	public void enableNCC(String libName) {
		schemLib = Library.findLibrary(libName);
		error(schemLib==null, "Please open the PurpleFour Library");
	}

	public void setVddExportName(String vddNm) {vddExportName=vddNm;}
	public String getVddExportName() {return vddExportName;}
	public void setGndExportName(String gndNm) {gndExportName=gndNm;}
	public String getGndExportName() {return gndExportName;}
	public void setVddExportRole(PortCharacteristic vddRole) {
		vddExportRole = vddRole;
	}
	public PortCharacteristic getVddExportRole() {
		return vddExportRole;
	}
	public void setGndExportRole(PortCharacteristic gndRole) {
		gndExportRole = gndRole;
	}
	public PortCharacteristic getGndExportRole() {
		return gndExportRole;
	}

	//------------------------------------------------------------------------------
	// Utilities for gate generators

    public StdCellParams(Technology tech, EditingPreferences ep) {
        this.tech = TechType.getTechType(tech);
        this.ep = ep;
        if (tech == Technology.getCMOS90Technology()) {
            initCMOS90();
        } else {
            initMoCMOS();
        }
    }

    void setOutputLibrary(Library lib) {
        this.layoutLib = lib;
    }
    Library getOutputLibrary() { return layoutLib; }

    public TechType getTechType() { return tech; }
    
    public EditingPreferences getEditingPreferences() { return ep; }
    
    public double getNmosWellHeight() {
		return nmosWellHeight;
	}
	public double getPmosWellHeight() {
		return pmosWellHeight;
	}
	public boolean getDoubleStrapGate() {
		return doubleStrapGate;
	}
	public boolean getExhaustivePlace() {
		return exhaustivePlace;
	}
	public int getNbPlacerPerms() {
		return nbPlacerPerms;
	}
	public double getCellBot() {
		return -nmosWellHeight;
	}
	public double getCellTop() {
		return pmosWellHeight;
	}
	public double getGndY() {
		return gndY;
	}
	public void setGndY(double y) {
		gndY = y;
		init();
	}
	public double getVddY() {
		return vddY;
	}
	public void setVddY(double y) {
		vddY = y;
		init();
	}
	public double getGndWidth() {
		return gndWidth;
	}
	public double getVddWidth() {
		return vddWidth;
	}
	/** Gets the Y coordinate of the ith available track. Available
	 * tracks exclude Vdd, Gnd, and reserved tracks. */
	public double getTrackY(int i) {
		error(i==0, "StdCellParams.getTrackY: 0 is an illegal track index");
		return i>0
			? pmosTracks.get(i - 1).doubleValue()
			: nmosTracks.get(-i - 1).doubleValue();
	}
	/** A physical track number enumerates all tracks regardless of
	 * whether the track is blocked. The value 0 is illegal. Tracks 1
	 * and higher are PMOS tracks. Tracks -1 and lower are NMOS
	 * tracks */
	public double getPhysTrackY(int i) {
		error(i==0, "StdCellParams.getPhysTrackY: 0 is illegal track index");
		return i>0
		    ? trackPitch/2 + (i-1)*trackPitch
		    : -trackPitch/2 + (i+1)*trackPitch;
	}
	/*
	public HashMap physTracksToYCoords(HashMap physTracks) {
	  HashMap yCoords = new HashMap();
	  for (String key : physTracks.keySet();) {
	    int physTrk = physTracks.get(key).intValue();
	    Double y = Double.valueOf(getPhysTrackY(physTrk));
	    yCoords.put(key, y);
	  }
	  return yCoords;
	}
	*/
	public double getTrackPitch() {return trackPitch;}

	/** Get the number of NMOS tracks. The indices of NMOS tracks
	 * range from -1 to -nbNmosTracks(). The value 0 is an illegal
	 * index. */
	public int nbNmosTracks() {
		return nmosTracks.size();
	}

	/** Get the number of PMOS tracks. The indices of PMOS tracks
	 * range from 1 to nbPmosTracks().  The value 0 is an illegal
	 * index. */
	public int nbPmosTracks() {return pmosTracks.size();}

	public boolean getSeparateWellTies() {return separateWellTies;}
	/** Connect well ties to separate exports rather than vdd and gnd */
	public void setSeparateWellTies(boolean b) {
		separateWellTies = b;
		init();
	}

	public double getNmosWellTieY() {return nmosWellTieY;}
	public double getPmosWellTieY() {return pmosWellTieY;}
	public double getNmosWellTieWidth() {
		return separateWellTies ? trackWidth : gndWidth;
	}
	public double getPmosWellTieWidth() {
		return separateWellTies ? trackWidth : vddWidth;
	}
	public String getNmosWellTieName() {
		return separateWellTies ? "vsb" : gndExportName;
	}
	public String getPmosWellTieName() {
		return separateWellTies ? "vnw" : vddExportName;
	}
	public PortCharacteristic getNmosWellTieRole() {
		return separateWellTies ? PortCharacteristic.IN : gndExportRole;
	}
	public PortCharacteristic getPmosWellTieRole() {
		return separateWellTies ? PortCharacteristic.IN : vddExportRole;
	}
	public void setSimpleName(boolean b) {simpleName = b;}
	public boolean getSimpleName() {return simpleName;}

	// maximum distance between well contacts
	public double getWellTiePitch() {
		double tieToPwellTop = 0 - getNmosWellTieY();
		double tieToPwellBot = getNmosWellTieY() - -nmosWellHeight;
		double tieToPwellTopBot = Math.max(tieToPwellTop, tieToPwellBot);
		// Right triangle:
		//  hypotenuse is maxWellTieRadius
		//  delta Y is maxGndToWellTopBot
		//  delta X is nmosWellTieDistance
		double nmosWellTieDistance =
			Math.sqrt(
				Math.pow(maxWellTieRadius, 2) - Math.pow(tieToPwellTopBot, 2));

		double tieToNwellTop = pmosWellHeight - getPmosWellTieY();
		double tieToNwellBot = getPmosWellTieY() - 0;
		double tieToNwellTopBot = Math.max(tieToNwellTop, tieToNwellBot);
		double pmosWellTieDistance =
			Math.sqrt(
				Math.pow(maxWellTieRadius, 2) - Math.pow(tieToNwellTopBot, 2));

		double tiePitch =
			2 * Math.min(nmosWellTieDistance, pmosWellTieDistance);

		// Safety margin: add twice as many ties as necessary
		return (int) tiePitch/2;
	}
	

	// An "enable" style Nand gate has a weak pullup. This is how much
	// weaker than normal the PMOS is.
	public double getEnableGateStrengthRatio() {
		return enableGateStrengthRatio;
	}

    /** round to nearest multiple of 1/2 lambda for MoCMOS,
     * nearest multiple of 0.2 for CMOS90 */
	public double roundGateWidth(double w) {
        if (is90nm()) {
		    double size = Math.rint(w * 5) / 5;
            if (size < minGateWid) {
                System.out.println("Warning: gate width of "+size+" too small, using "+minGateWid);
                return minGateWid;
            }
            return size;
        } else {
            return Math.rint(w * 2) / 2;
        }
	}

	/** quantize size.  Temporary hack because it doesn't control errors
	 * precisely */
	public double roundSize(double s) {
		if (s == 0)
			return s;
		double q = quantizeSize(s);
//		double e = (s - q) / s;
//		double qe = Math.rint(e * 100000) / 100000;
		//System.out.println("desired: "+s+" quantized: "+q+" error: "+qe);
		return q;
		//return ((int) (s*10+.5)) / 10.0;
	}

    public double roundToGrid(double s) {
        return ( ((int)(s/gridResolution)) * gridResolution );
    }

	public void setSizeQuantizationError(double err) {
		error(err >= 1, "quantization error must be less than 1.0");
		error(err < 0, "quantization error must be positive");
		sizeErr = err;
	}

	public double quantizeSize(double desiredSize) {
		// express desiredSize as (mantisa * 10^exponent)
		double exponent = Math.floor(Math.log(desiredSize) / Math.log(10));
		// 1.0 <= mantissa < 10
		double mantissa = desiredSize / Math.pow(10, exponent);

		double quantMant = sizeErr!=0 ? quantizeMantissa(mantissa) : mantissa;

		// now round the quantized mantissa to 3 decimal places
		double roundMant = Math.rint(quantMant * 100) / 100;

		return Math.pow(10, exponent) * roundMant;
	}

	/** Add qualifiers to Cell name to reflect StdCell parameters
	 * "_NH70" for NMOS region height of 70 lambda
	 * "_PH70" for PMOS region height of 70 lambda
	 * "_MW70" for maximum transistor width of 70 lambda */
	public String parameterizedName(String nm) {
		if (!vddExportName.equals("vdd")) nm += "_pwr";
		if (simpleName) return nm;
		return nm
			+"_NH"+nmosWellHeight+"_PH"+pmosWellHeight
			+"_MW"+maxMosWidth+"_VY"
			+vddY+"_GY"+ gndY;
	}

	/** Add qualifiers to Cell name to reflect StdCell parameters and part strength
	 * "_NH70" for NMOS region height of 70 lambda
	 * "_PH70" for PMOS region height of 70 lambda
	 * "_MW70" for maximum transistor width of 70 lambda
	 * "_X12.5" for strength of 12.5
	 * "{lay}" to indicate this is a layout Cell */
	public String sizedName(String nm, double sz) {
		String num = "" + (sz + 1000); // Add leading zeros to size
		// so Gallery sorts properly.
		num = num.substring(1);
		return parameterizedName(nm) + "_X" + num + "{lay}";
	}

	private NodeInst addNmosWell(double loX, double hiX, double y, Cell cell) {
		NodeInst well =
			LayoutLib.newNodeInst(tech.pwell(), ep, (loX+hiX)/2, y-nmosWellHeight/2,
								hiX-loX, nmosWellHeight, 0, cell);
		well.setHardSelect();
		return well;
	}
	private NodeInst addPmosWell(double loX, double hiX, double y, Cell cell) {
		NodeInst well =
			LayoutLib.newNodeInst(tech.nwell(), ep, (loX+hiX)/2, y+pmosWellHeight/2,
								  hiX-loX, pmosWellHeight, 0, cell);
		well.setHardSelect();
		return well;
	}

	public NodeInst addNmosWell(double loX, double hiX, Cell cell) {
		return addNmosWell(loX, hiX, 0, cell);
	}
	public NodeInst addPmosWell(double loX, double hiX, Cell cell) {
		return addPmosWell(loX, hiX, 0, cell);
	}

	/** Given an array of NodeInsts in a row, add wells to both ends of
	    the row to bring the row to minX and maxX. */
	public void addWellsForRow(
		ArrayList<NodeInst> row,
		double minX,
		double maxX,
		Cell cell) {
		NodeInst first = row.get(row.size() - 1);
		double rowMinX = first.getBounds().getMinX();
		if (rowMinX < minX) {
			addPmosWell(minX, rowMinX, first.getAnchorCenterY(), cell);
			addNmosWell(minX, rowMinX, first.getAnchorCenterY(), cell);
		}
		NodeInst last = row.get(row.size() - 1);
		double rowMaxX = last.getBounds().getMaxX();
		if (rowMaxX < maxX) {
			addPmosWell(rowMaxX, maxX, first.getAnchorCenterY(), cell);
			addNmosWell(rowMaxX, maxX, first.getAnchorCenterY(), cell);
		}
	}

	/** essential bounds for PMOS only cells */
	public void addPstackEssentialBounds(double loX, double hiX, Cell cell) {
		LayoutLib.newNodeInst(tech.essentialBounds(), ep, loX, 0, DEF_SIZE, DEF_SIZE,
							  180, cell);
		LayoutLib.newNodeInst(tech.essentialBounds(), ep, hiX, pmosWellHeight, DEF_SIZE, 
							  DEF_SIZE, 0, cell);
	}
	/** essential bounds for NMOS only cells */
	public void addNstackEssentialBounds(double loX, double hiX, Cell cell) {
		LayoutLib.newNodeInst(tech.essentialBounds(), ep, loX, -nmosWellHeight, DEF_SIZE, 
							  DEF_SIZE, 180, cell);
		LayoutLib.newNodeInst(tech.essentialBounds(), ep, hiX, 0, DEF_SIZE,
							  DEF_SIZE, 0, cell);
	}
	/** essential bounds for cells with both NMOS and PMOS */
	public void addEssentialBounds(double loX, double hiX, Cell cell) {
		LayoutLib.newNodeInst(tech.essentialBounds(), ep, loX, -nmosWellHeight, DEF_SIZE, 
		                      DEF_SIZE, 180, cell);
		LayoutLib.newNodeInst(tech.essentialBounds(), ep, hiX, pmosWellHeight, DEF_SIZE, 
			                  DEF_SIZE, 0, cell);
	}

	/** Print a warning if strength is less than the minimum allowable.
	 * Always return at least the minimum allowable strength. */
	public double checkMinStrength(
		double specified,
		double minAllowable,
		String gateNm) {
		if (specified<minAllowable) {
			System.out.println("Can't make: "+gateNm+" this small: X="
			                   +specified+", Using X="+minAllowable
			                   +" instead");
		}
		return Math.max(specified, minAllowable);
	}

	/** Calculate the number of folds and the width of a MOS
	 * transistor. Given that there is a limited physical height into
	 * which a MOS transistor must fit, divide the total required width:
	 * totWid into fingers.  Each finger must have width less than or
	 * equal to spaceAvailWid.
	 *
	 * <p> If it is possible, allocate an even number of fingers so that
	 * the left most and right most diffusion contacts may be connected
	 * to power or ground to reducing the capacitance of the inner
	 * switching diffusion contacts.
	 * @param spaceAvailWid the height in the standard cell that is
	 * available for the diffusion of the MOS transistor.
	 * @param totWid the total electrical width required.
	 * @param groupSz This method creates fingers in multiples of
	 * groupSz. For example, if groupSz is 2, then only even numbers of
	 * fingers are created. This is needed when one FoldedMos is
	 * actually going to be wired up as 2 identical, independent
	 * transistors, for example the 2 PMOS pullups for a 2-input NAND
	 * gate. */
	public FoldsAndWidth calcFoldsAndWidth(
		double spaceAvailWid,
		double totWid,
		int groupSz) {
		if (totWid == 0)
			return null;
		double maxAvailWid = Math.min(spaceAvailWid, maxMosWidth);
		int nbGroups = calcNbGroups(maxAvailWid, totWid, groupSz);

		double gateWid = roundGateWidth(totWid / groupSz / nbGroups);

		// If we're unfortunate, rounding up gate width causes gate's width
		// to exceed space available.
		if (gateWid > maxAvailWid) {
			nbGroups = calcNbGroups(maxAvailWid - .5, totWid, groupSz);
			gateWid = roundGateWidth(totWid / groupSz / nbGroups);
		}

		double physWid = Math.max(tech.getDiffContWidth(), gateWid);
		if (gateWid < minGateWid)
			return null;
		return new FoldsAndWidth(nbGroups * groupSz, gateWid, physWid);
	}

	/** Fix notch errors between adjacent source/drain regions.
	 *
	 * <p>Mos transistors with source/drain regions that are too close
	 * to each other result in notch errors for diffusion, metal1,
	 * and/or select. Fix these notch errors by running diffusion,
	 * and/or metal1 between the adjacent diffusion regions.
	 * @param moss An array of adjacent FoldedMos transistors arranged
     * @param fillDiffNotch*/
	public void fillDiffAndSelectNotches(FoldedMos[] moss, boolean fillDiffNotch) {
		error(moss.length == 0, "fillDiffAndSelectNotches: no transistors?");
		FoldedMos mos = moss[0];

		for (int i = 1; i < moss.length; i++) {
			PortInst thisPort = moss[i].getSrcDrn(0);
			PortInst prevPort =
				moss[i - 1].getSrcDrn(moss[i - 1].nbSrcDrns() - 1);
			fillDiffAndSelectNotch(prevPort, thisPort, mos, moss[i], fillDiffNotch);
		}
	}

	/** Wire pmos or nmos to vdd or gnd, respectively.  Add an export
	 * if there is none. */
	public void wireVddGnd(FoldedMos[] moss, SelectSrcDrn select, Cell p) {
		FoldedMos mos = moss[0];

		PortInst leftDiff = mos.getSrcDrn(0);
		Cell f = leftDiff.getNodeInst().getParent();
		double busWid = mos instanceof FoldedPmos ? vddWidth : gndWidth;
		double busY = mos instanceof FoldedPmos ? vddY : gndY;
		TrackRouter net = new TrackRouterH(tech.m2(), busWid, busY, tech, ep, p);

		String exportNm = mos instanceof FoldedPmos ? vddExportName : gndExportName;
		if (f.findPortProto(exportNm)==null) {
			// The export doesn't yet exist.  Create and export a metal2 pin
			// aligned with the first diffusion.
			double x = leftDiff.getBounds().getCenterX();
			NodeInst pinProt = LayoutLib.newNodeInst(tech.m2pin(), ep, x,
			                                         busY, DEF_SIZE, DEF_SIZE, 0, f);
			PortInst pin = pinProt.getOnlyPortInst();
			Export e = Export.newInst(f, pin, exportNm, ep);
			PortCharacteristic role =	
				mos instanceof FoldedPmos ? vddExportRole : gndExportRole;
			e.setCharacteristic(role);

			// Connect the export to itself using a standard width power or
			// ground strap.  The width of this strap serves as a hint to
			// Electric as to the width to use to connect to this export at
			// the next level up.
			LayoutLib.newArcInst(tech.m2(), ep, busWid, pin, pin);

			net.connect(pin);
		}
		double diffY = LayoutLib.roundCenterY(leftDiff);
		double notchLoY = Math.min(busY - busWid / 2, diffY);
		double notchHiY = Math.max(busY + busWid / 2, diffY);
		PortInst lastDiff = null;
		for (int i=0; i<moss.length; i++) {
			for (int j=0; j<moss[i].nbSrcDrns(); j++) {
				if (select.connectThisOne(i, j)) {
					PortInst thisDiff = moss[i].getSrcDrn(j);
					net.connect(thisDiff);

					if (lastDiff!=null) {
						// Check to see if we just created a notch.
						double leftX = LayoutLib.roundCenterX(lastDiff);
						double rightX = LayoutLib.roundCenterX(thisDiff);
						error(leftX>rightX,
							  "wireVddGnd: trans not sorted left to right");
						double deltaX = rightX - leftX;
						if (deltaX>0
							&& deltaX < m1OverhangsDiffCont*2+m1Space) {
							// Fill notches, sigh! (This is starting to lose 
							// it's novelty value.)
							//
							// Make height integral number of lambdas so 
							// centerY will be on .5 lambda grid and connecting
							// to center won't generate CIF resolution errors.
							double dY = Math.ceil(notchHiY - notchLoY);
							NodeInst patchNode = 
							  LayoutLib.newNodeInst(tech.m1Node(), ep, (leftX+rightX)/2, notchLoY+dY/2,
							  deltaX, dY, 0, f);
							PortInst patch = patchNode.getOnlyPortInst();
							LayoutLib.newArcInst(tech.m1(), ep, DEF_SIZE, patch, thisDiff);
						}
					}
					lastDiff = thisDiff;
				}
			}
		}
	}

	public void wireVddGnd(FoldedMos mos, SelectSrcDrn select, Cell p) {
		wireVddGnd(new FoldedMos[] { mos }, select, p);
	}

	public boolean nccEnabled() {
		return schemLib != null;
	}

	/** Perform Network Consistency Check if the user has so requested */
	public void doNCC(Cell layout, String schemNm) {
		if (schemLib == null) return;
		Cell schem = schemLib.findNodeProto(schemNm);
		error(schem == null, "can't find schematic: " + schemNm);

		NccOptions options = new NccOptions();
		options.howMuchStatus = 0;

		NccResults results = Ncc.compare(schem, null, layout, null, options);
		error(!results.match(), 
			  "layout not topologically identical to schematic!");
	}

	public static double getSize(NodeInst iconInst, VarContext context) {
		Variable var = iconInst.getParameterOrVariable(ATTR_X);
		if (var==null)
			var = iconInst.getParameterOrVariable(ATTR_S);
		if (var==null)
			var = iconInst.getParameterOrVariable(ATTR_SP);
		if (var==null)
			var = iconInst.getParameterOrVariable(ATTR_SN);

		if (var==null) {
			System.out.println("can't find size, using 40");
			return 40;
		}
		Object val = context.evalVar(var);
		if (val instanceof Number)  return ((Number)val).doubleValue();
		error(true, "an Icon's size isn't a numeric value");
		return 0;
	}

	/** Look for parts in layoutLib */
	public Cell findPart(String partNm, double sz) {
		return findPart(sizedName(partNm, sz));
	}
	public Cell findPart(String partNm) {
		return layoutLib.findNodeProto(partNm);
	}
	public Cell newPart(String partNm, double sz) {
		return newPart(sizedName(partNm, sz));
	}
	public Cell newPart(String partNm) {
		error(findPart(partNm) != null, "Cell already exists: " + partNm);
		Cell p = Cell.newInst(layoutLib, partNm);
        p.setTechnology(tech.getTechnology());
		return p;
	}

	public static double getRightDiffX(FoldedMos m) {
		return LayoutLib.roundCenterX(m.getSrcDrn(m.nbSrcDrns() - 1));
	}

	public static double getRightDiffX(FoldedMos[] moss) {
		return getRightDiffX(getRightMos(moss));
	}

	/** Find the X coordinate of the right most diffusion. Objects a and
	 * b may be either a FoldedMos or an array of FoldedMos'es */
	public static double getRightDiffX(Object a, Object b) {
		FoldedMos ra = getRightMos(a);
		FoldedMos rb = getRightMos(b);
		return Math.max(getRightDiffX(ra), getRightDiffX(rb));
	}

	public void addEssentialBoundsFromChildren(Cell cell, TechType tech) {
		double loX, loY, hiX, hiY;
		loX = loY = Double.MAX_VALUE;
		hiX = hiY = -Double.MAX_VALUE;
		for (Iterator<NodeInst> it=cell.getNodes(); it.hasNext();) {
			Rectangle2D b = it.next().getBounds();
			loX = Math.min(loX, b.getMinX());
			loY = Math.min(loY, b.getMinY());
			hiX = Math.max(hiX, b.getMaxX());
			hiY = Math.max(hiY, b.getMaxY());
		}
		LayoutLib.newNodeInst(tech.essentialBounds(), ep, loX, loY, 
		                      DEF_SIZE, DEF_SIZE, 180, cell);
		LayoutLib.newNodeInst(tech.essentialBounds(), ep, hiX, hiY,
		                      DEF_SIZE, DEF_SIZE, 0, cell);
	}

	public HashMap<String,Object> getSchemTrackAssign(Cell schem) {
		HashMap<String,Object> schAsgn = new HashMap<String,Object>();
		for (Iterator<PortProto> it = schem.getPorts(); it.hasNext();) {
			Export e = (Export) it.next();
			Object val = e.getParameterOrVariable("ATTR_track");
			String key = e.getName();
			if (val == null)
				continue;
			schAsgn.put(key, val);
		}
		validateTrackAssign(schAsgn, schem);
		return schAsgn;
	}

	public void validateTrackAssign(HashMap<String,Object> asgn, Cell s) {
		HashMap<Object,String> trkToExp = new HashMap<Object,String>();
		for (String k : asgn.keySet()) {
			Object v = asgn.get(k);
			// check types of key and value
			error(!(v instanceof Integer),
				  trkMsg(k,s)+"Value not Integer: "+v);

			// range check track number
			int track = ((Integer) v).intValue();
			error(track==0,
				  trkMsg(k,s)+"Track must be <=-1 or >=1, 0 is illegal");
			/*
			error(track<min, trkMsg(k,s) + "Track too negative: "+track+
			  ". Only: "+(-min)+" NMOS tracks are available in this cell.");
			error(track>max, trkMsg(k,s) + "Track too positive: "+track+
			  ". Only: "+max+" PMOS tracks are available in this cell.");
			*/

			String oldK = trkToExp.get(v);
			if (oldK != null) {
				// Issue warning if two exports assigned to the same track
				System.out.println(
					trkMsg(k, s)
						+ "Track: "
						+ v
						+ " is shared by export: "
						+ oldK);
			}
			trkToExp.put(v, k);
		}
	}

	/** Add select node to ensure there is select surrounding the
	 * specified diffusion node*/
	public void addSelAroundDiff(NodeProto prot, Rectangle2D diffNodeBnd, double activeGateDiff,
                                 Cell cell) {
		error(prot!=tech.pdNode() && prot!=tech.ndNode(),
			  "addSelectAroundDiff: only works with MOSIS CMOS diff nodes");
		NodeProto sel = prot == tech.pdNode() ? tech.pselNode() : tech.nselNode();
        // Note that transistors are rotated in 90 degrees with res
		double w = diffNodeBnd.getWidth();
		double h = diffNodeBnd.getHeight();
        // add the select surround if gate is longer than contact
            h += tech.selectSurroundDiffAlongGateInTrans() * 2;
        if (activeGateDiff > 0)
            h -= activeGateDiff;
//            w =+ tech.getSelectSurroundDiffInTrans() * 2;

		NodeInst node = LayoutLib.newNodeInst(sel, ep, diffNodeBnd.getCenterX(), diffNodeBnd.getCenterY(), w, h, 0, cell);

        System.out.println(node.getParent().getName() + " Node " + node + " " + h + " cent " + diffNodeBnd.getCenterY());
	}

    public static class SelectFill {
        public final NodeInst nselNode;
        public final NodeInst pselNode;
        public SelectFill(NodeInst nselNode, NodeInst pselNode) {
            this.nselNode = nselNode;
            this.pselNode = pselNode;
        }
    }
    /**
     * This fills the cell with N/P select over current N/P select areas, and over
     * poly.  This assumes that all Pmos devices and PWell is at y > 0, and all
     * Nmos devices and NWell is at y < 0.
     * @param cell the cell to fill
     @param pSelect
     * @param nSelect
     * @param includePoly
     */
    public SelectFill fillSelect(Cell cell, boolean pSelect, boolean nSelect, 
    		                     boolean includePoly) {
        double selSurroundPoly = tech.getSelectSurroundOverPoly();
        if (selSurroundPoly < 0) return null;// do nothing.
        Rectangle2D pselectBounds = LayoutLib.getBounds(cell, Layer.Function.IMPLANTP);
        Rectangle2D nselectBounds = LayoutLib.getBounds(cell, Layer.Function.IMPLANTN);
        if (includePoly)
        {
            Rectangle2D polyBounds = LayoutLib.getBounds(cell, Layer.Function.POLY1);
            polyBounds.setRect(polyBounds.getX()-selSurroundPoly,
                    polyBounds.getY()-selSurroundPoly, polyBounds.getWidth() + 2*selSurroundPoly,
                    polyBounds.getHeight() + 2*selSurroundPoly);
            // merge select and poly bounds
            pselectBounds = pselectBounds.createUnion(polyBounds);
            nselectBounds = nselectBounds.createUnion(polyBounds);
        }
        // pselect above y=0, nselect below
        pselectBounds.setRect(pselectBounds.getMinX(), 0,
                pselectBounds.getMaxX()-pselectBounds.getMinX(), pselectBounds.getMaxY());
        nselectBounds.setRect(nselectBounds.getMinX(), nselectBounds.getMinY(),
                nselectBounds.getMaxX()-nselectBounds.getMinX(), -nselectBounds.getMinY());
        // fill it in
        pselectBounds = LayoutLib.roundBounds(pselectBounds);
        nselectBounds = LayoutLib.roundBounds(nselectBounds);
        NodeInst pni = null, nni = null;

        if (pSelect)
        {
            pni = LayoutLib.newNodeInst(tech.pselNode(), ep, pselectBounds.getCenterX(), pselectBounds.getCenterY(),
                    pselectBounds.getWidth(), pselectBounds.getHeight(), 0, cell);
            pni.setHardSelect();
        }
        if (nSelect)
        {
            nni = LayoutLib.newNodeInst(tech.nselNode(), ep, nselectBounds.getCenterX(), nselectBounds.getCenterY(),
                    nselectBounds.getWidth(), nselectBounds.getHeight(), 0, cell);
            nni.setHardSelect();
        }
        return new SelectFill(nni, pni);
    }

    /**
     * Note that nwellX denotes the X coord of the nwell contact cut, which
     * goes in the Nwell (well that holds PMOS devices).
     */
    public boolean addWellCon(SelectFill selFill, PortInst gndPort, PortInst vddPort, Cell cell) {
        // see if there is space
        double nselMaxY = Math.abs(selFill.nselNode.getYSize());
        double pselMaxY = Math.abs(selFill.pselNode.getYSize());

        boolean added = false;
        double pspace = getNmosWellHeight() - nselMaxY;
        // the space needed is the well contact select height plus the metal1-metal1 spacing,
        // or plus the select to select spacing, whichever is greater.
        double m1m1_sp = 2.4;
        double psel_sp = 4.8;
        double nsel_sp = 4.8;
        Technology techy = cell.getTechnology();
        if (pspace > (wellConSelectHeight+m1m1_sp) + 2*gridResolution) {
            // there is space, create it
            double pwellX = roundToGrid(gndPort.getBounds().getCenterX());
            double pwellY = -roundToGrid(nselMaxY + 0.5*wellConSelectHeight + wellConSelectOffset) - gridResolution;
            SizeOffset so = tech.pwm1Y().getProtoSizeOffset();
            NodeInst ni = LayoutLib.newNodeInst(tech.pwm1Y(), ep, pwellX, pwellY,
                    tech.pwm1Y().getDefWidth(ep) - so.getHighXOffset() - so.getLowXOffset(),
                    tech.pwm1Y().getDefHeight(ep) -so.getHighYOffset() - so.getLowYOffset(), 0, cell);
            LayoutLib.newArcInst(tech.m1(), ep, getM1TrackWidth(), ni.getOnlyPortInst(), gndPort);
            added = true;
            // if the space to the end of the cell is less than the select to select spacing,
            // fill it in with the appropriate select.
            double distFromEdge = getNmosWellHeight() - (Math.abs(pwellY) + 0.5*wellConSelectHeight);
            if (distFromEdge < psel_sp) {
                // get size of select layer
                Poly [] polys = techy.getShapeOfNode(ni);
                if (polys != null) {
                    Layer.Function function = Layer.Function.IMPLANTP;
                    Rectangle2D bounds = null;
                    for (int i=0; i<polys.length; i++) {
                        if (polys[i] == null) continue;
                        if (polys[i].getLayer().getFunction() == function) {
                            bounds = polys[i].getBox();
                        }
                    }
                    if (bounds != null) {
                        // fill over the well contact.  Round up bottom of fill so it is on lambda grid
                        double fillBot = (int)(Math.abs(pwellY) - 0.5*wellConSelectHeight) + 1;
                        double fillH = getNmosWellHeight() - fillBot;
                        ni = LayoutLib.newNodeInst(tech.pselNode(), ep, pwellX, -1*roundToGrid(fillH/2 + fillBot),
                                bounds.getWidth(), roundToGrid(fillH), 0, cell);
                        ni.setHardSelect();
                    }
                }
            }
        }
        double nspace = getPmosWellHeight() - pselMaxY;
        if (nspace > (wellConSelectHeight+m1m1_sp) + 2*gridResolution) {
            double nwellX = roundToGrid(vddPort.getBounds().getCenterX());
            double nwellY = roundToGrid(pselMaxY + 0.5*wellConSelectHeight + wellConSelectOffset) + gridResolution;
            SizeOffset so = tech.nwm1Y().getProtoSizeOffset();
            NodeInst ni = LayoutLib.newNodeInst(tech.nwm1Y(), ep, nwellX, nwellY,
                    tech.nwm1Y().getDefWidth(ep) - so.getHighXOffset() - so.getLowXOffset(),
                    tech.nwm1Y().getDefHeight(ep) - so.getHighYOffset() - so.getLowYOffset(), 0, cell);
            LayoutLib.newArcInst(tech.m1(), ep, getM1TrackWidth(), ni.getOnlyPortInst(), vddPort);
            added = true;
            // if the space to the end of the cell is less than the select to select spacing,
            // fill it in with the appropriate select.
            double distFromEdge = getPmosWellHeight() - (Math.abs(nwellY) + 0.5*wellConSelectHeight);
            if (distFromEdge < nsel_sp) {
                // get size of select layer
                Poly [] polys = techy.getShapeOfNode(ni);
                if (polys != null) {
                    Layer.Function function = Layer.Function.IMPLANTN;
                    Rectangle2D bounds = null;
                    for (int i=0; i<polys.length; i++) {
                        if (polys[i] == null) continue;
                        if (polys[i].getLayer().getFunction() == function) {
                            bounds = polys[i].getBox();
                        }
                    }
                    if (bounds != null) {
                        // fill over the well contact.  Round up bottom of fill so it is on lambda grid
                        double fillBot = (int)(Math.abs(nwellY) - 0.5*wellConSelectHeight) + 1;
                        double fillH = getPmosWellHeight() - fillBot;
                        ni = LayoutLib.newNodeInst(tech.nselNode(), ep, nwellX, roundToGrid(fillH/2 + fillBot),
                                bounds.getWidth(), roundToGrid(fillH), 0, cell);
                        ni.setHardSelect();
                    }
                }
            }
        }

        return added;
    }

}
