/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Spice.java
 * Original C Code written by Steven M. Rubin and Sid Penstone
 * Translated to Java by Steven M. Rubin.
 *
 * Copyright (c) 2004, 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.io.output;

import com.sun.electric.database.geometry.GeometryHandler;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.geometry.PolyBase;
import com.sun.electric.database.geometry.PolyMerge;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.EDatabase;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.HierarchyEnumerator;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.hierarchy.View;
import com.sun.electric.database.network.Global;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.network.NetworkTool;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.Version;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.CodeExpression;
import com.sun.electric.database.variable.ElectricObject;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.database.variable.UserInterface;
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.Foundry;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.PrimitiveNodeSize;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.TransistorSize;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.generator.sclibrary.SCLibraryGen;
import com.sun.electric.tool.io.input.SimulationData;
import com.sun.electric.tool.io.input.spicenetlist.SpiceNetlistReader;
import com.sun.electric.tool.io.input.spicenetlist.SpiceSubckt;
import com.sun.electric.tool.ncc.basic.NccCellAnnotations;
import com.sun.electric.tool.ncc.basic.NccCellAnnotations.NamePattern;
import com.sun.electric.tool.simulation.SimulationTool;
import com.sun.electric.tool.user.Exec;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.dialogs.ExecDialog;
import com.sun.electric.tool.user.ui.TopLevel;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.FixpTransform;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;

import javax.swing.SwingUtilities;

/**
 * This is the Simulation Interface tool.
 */
public class Spice extends Topology
{
    private static final boolean DETECT_SPICE_PARAMS = true;
    private static final boolean USE_JAVA_CODE = true;
    private static final boolean PREFER_GLOBAL_NAME_OVER_RAILS = true;

	/** key of Variable holding generic Spice templates. */		public static final Variable.Key SPICE_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template");
	/** key of Variable holding Spice 2 templates. */			public static final Variable.Key SPICE_2_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template_spice2");
	/** key of Variable holding Spice 3 templates. */			public static final Variable.Key SPICE_3_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template_spice3");
	/** key of Variable holding HSpice templates. */			public static final Variable.Key SPICE_H_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template_hspice");
	/** key of Variable holding PSpice templates. */			public static final Variable.Key SPICE_P_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template_pspice");
	/** key of Variable holding Xyce templates. */				public static final Variable.Key SPICE_XYCE_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template_xyce");
	/** key of Variable holding GnuCap templates. */			public static final Variable.Key SPICE_GC_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template_gnucap");
	/** key of Variable holding Smart Spice templates. */		public static final Variable.Key SPICE_SM_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template_smartspice");
	/** key of Variable holding Assure templates. */		    public static final Variable.Key SPICE_A_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template_assura");
	/** key of Variable holding Calibre templates. */		    public static final Variable.Key SPICE_C_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template_calibre");
	/** key of Variable holding Ngspice templates. */		    public static final Variable.Key SPICE_NG_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template_ngspice");
	/** key of Variable holding JoSIM templates. */			    public static final Variable.Key SPICE_JO_TEMPLATE_KEY = Variable.newKey("ATTR_SPICE_template_josim");
	/** key of Variable holding Spice model file. */		    public static final Variable.Key SPICE_NETLIST_FILE_KEY = Variable.newKey("ATTR_SPICE_netlist_file");
	/** key of Variable holding SPICE code. */					public static final Variable.Key SPICE_CARD_KEY = Variable.newKey("SIM_spice_card");
	/** key of Variable holding SPICE declaration. */			public static final Variable.Key SPICE_DECLARATION_KEY = Variable.newKey("SIM_spice_declaration");
	/** key of Variable holding SPICE model. */					public static final Variable.Key SPICE_MODEL_KEY = Variable.newKey("SIM_spice_model");
	/** key of Variable holding SPICE flat code. */				public static final Variable.Key SPICE_CODE_FLAT_KEY = Variable.newKey("SIM_spice_code_flat");
	/** key of deprecated Variable holding model file */        public static final Variable.Key SPICE_MODEL_FILE_KEY = Variable.newKey("SIM_spice_behave_file");
    /** key of Variable holding generic CDL templates. */		public static final Variable.Key CDL_TEMPLATE_KEY = Variable.newKey("ATTR_CDL_template");
	/** Prefix for spice extension. */                          public static final String SPICE_EXTENSION_PREFIX = "Extension ";
	/** Prefix for spice null extension. */                     public static final String SPICE_NOEXTENSION_PREFIX = "N O N E ";

	/** maximum subcircuit name length */						private static final int SPICEMAXLENSUBCKTNAME     = 70;
    /** maximum subcircuit name length */						private static final int CDLMAXLENSUBCKTNAME       = 40;
	/** legal characters in a spice deck */						private static final String SPICELEGALCHARS        = "!#$%*+-/<>[]_@";
	/** legal characters in a spice deck */						private static final String PSPICELEGALCHARS       = "!#$%*+-/<>[]_";
	/** legal characters in a CDL deck */						private static final String CDLNOBRACKETLEGALCHARS = "!#$%*+-/<>_";
    /** A mark for uniquify */                                  private static final Set<Variable.Key> UNIQUIFY_MARK = Collections.emptySet();

	/** default Technology to use. */							private Technology layoutTechnology;
	/** Technology of top cell. */								private Technology curTech;
	/** Mask shrink factor (default =1) */						private double maskScale;
	/** True to write CDL format */								private boolean useCDL;
	/** Legal characters */										private String legalSpiceChars;
	/** Template Key for current spice engine */				private Variable.Key preferedEngineTemplateKey;
    /** Special case for HSpice for Assura */					private boolean assuraHSpice = false;
	/** Spice type: 2, 3, H, P, etc */							private SimulationTool.SpiceEngine spiceEngine;
	/** those cells that have overridden models */				private Map<Cell,String> modelOverrides = new HashMap<Cell,String>();
    /** Parameters used for Spice */                            private Map<NodeProto,Set<Variable.Key>> allSpiceParams = new HashMap<NodeProto,Set<Variable.Key>>();
    /** for RC parasitics */                                    private SpiceParasiticsGeneral parasiticInfo;
    /** Networks exempted during parasitic ext */				private SpiceExemptedNets exemptedNets;
    /** Whether or not to write empty subcircuits */			private boolean writeEmptySubckts = true;

    /** Flat measurements file */								private FlatSpiceCodeVisitor spiceCodeFlat = null;

    /** map of "parameterized" cells not covered by Topology */	private Map<Cell,Cell> uniquifyCells;
    /** uniqueID */                                             private int uniqueID;
    /** map of shortened instance names */                      private Map<String,Integer> uniqueNames;
    /** local copy of preferences */                            private SpicePreferences localPrefs;

	public static class SpicePreferences extends OutputPreferences
    {
		public boolean                      cdl;
        public SimulationTool.SpiceEngine   engine = SimulationTool.getFactorySpiceEngine();
        public String                       level = SimulationTool.getFactorySpiceLevel();
        public int                          shortResistors = SimulationTool.getFactorySpiceShortResistors();
        public String                       runChoice = SimulationTool.getFactorySpiceRunChoice();
        public String                       runDir = SimulationTool.getFactorySpiceRunDir();
        public boolean                      useRunDir = SimulationTool.getFactorySpiceUseRunDir();
        public boolean                      outputOverwrite = SimulationTool.getFactorySpiceOutputOverwrite();
        public boolean                      runProbe = SimulationTool.getFactorySpiceRunProbe();
        public String                       runProgram = SimulationTool.getFactorySpiceRunProgram();
        public String                       runProgramArgs = SimulationTool.getFactorySpiceRunProgramArgs();
        public String                       partsLibrary = SimulationTool.getFactorySpicePartsLibrary();
        public String                       headerCardInfo = SimulationTool.getFactorySpiceHeaderCardInfo();
        public String                       trailerCardInfo = SimulationTool.getFactorySpiceTrailerCardInfo();

        public SimulationTool.SpiceParasitics parasiticsLevel = SimulationTool.getFactorySpiceParasiticsLevel();
    	public boolean                      parasiticsUseVerboseNaming = SimulationTool.isFactoryParasiticsUseVerboseNaming();
        public boolean                      parasiticsBackAnnotateLayout = SimulationTool.isFactoryParasiticsBackAnnotateLayout();
        public boolean                      parasiticsExtractPowerGround = SimulationTool.isFactoryParasiticsExtractPowerGround();
        public boolean                      parasiticsUseExemptedNetsFile = SimulationTool.isFactoryParasiticsUseExemptedNetsFile();
        public boolean                      parasiticsIgnoreExemptedNets = SimulationTool.isFactoryParasiticsIgnoreExemptedNets();
        public boolean                      parasiticsExtractsR = SimulationTool.isFactoryParasiticsExtractsR();
        public boolean                      parasiticsExtractsC = SimulationTool.isFactoryParasiticsExtractsC();

        public SimulationTool.SpiceGlobal   globalTreatment = SimulationTool.getFactorySpiceGlobalTreatment();
        public int                          maxLineLen = SimulationTool.getFactorySpiceMaxCharsPerLine();
        public boolean                      writePwrGndInTopCell = SimulationTool.isFactorySpiceWritePwrGndInTopCell();
        public boolean                      useCellParameters = SimulationTool.isFactorySpiceUseCellParameters();
        public boolean                      writeTransSizeInLambda = SimulationTool.isFactorySpiceWriteTransSizeInLambda();
        public boolean                      writeSubcktTopCell = SimulationTool.isFactorySpiceWriteSubcktTopCell();
        public boolean                      writeTopCellInstance = true;
        public boolean                      writeEmptySubckts = true;
        public boolean                      writeFinalDotEnd = true;
        public boolean                      ignoreParasiticResistors = false;
        public String                       extractedNetDelimiter = SimulationTool.getFactorySpiceExtractedNetDelimiter();
        public boolean                      ignoreModelFiles = SimulationTool.isFactorySpiceIgnoreModelFiles();

        public String                       cdlLibName = SimulationTool.getFactoryCDLLibName();
        public String                       cdlLibPath = SimulationTool.getFactoryCDLLibPath();
        public boolean                      cdlConvertBrackets = SimulationTool.isFactoryCDLConvertBrackets();
        public String                       cdlIncludeFile = SimulationTool.getFactoryCDLIncludeFile();

        public Map<Cell,String>             modelFiles = Collections.emptyMap();
        public String                       workdir = "";

        public boolean						supportAscDescOrdering = true; // false is for cases where Lexicographical must be enforced.

		public SpicePreferences() { this(false, false); }
		public SpicePreferences(boolean factory, boolean cdl)
		{
            super(factory);
			this.cdl = cdl;
            if (!factory)
                fillPrefs();
		}

        private void fillPrefs() {
            engine                          = SimulationTool.getSpiceEngine();
            level                           = SimulationTool.getSpiceLevel();
            shortResistors                  = SimulationTool.getSpiceShortResistors();
            runChoice                       = SimulationTool.getSpiceRunChoice();
            runDir                          = SimulationTool.getSpiceRunDir();
            useRunDir                       = SimulationTool.getSpiceUseRunDir();
            outputOverwrite                 = SimulationTool.getSpiceOutputOverwrite();
            runProbe                        = SimulationTool.getSpiceRunProbe();
            runProgram                      = SimulationTool.getSpiceRunProgram();
            runProgramArgs                  = SimulationTool.getSpiceRunProgramArgs();
            partsLibrary                    = SimulationTool.getSpicePartsLibrary();
            headerCardInfo                  = SimulationTool.getSpiceHeaderCardInfo();
            trailerCardInfo                 = SimulationTool.getSpiceTrailerCardInfo();

            parasiticsLevel                 = SimulationTool.getSpiceParasiticsLevel();
            parasiticsUseVerboseNaming      = SimulationTool.isParasiticsUseVerboseNaming();
            parasiticsBackAnnotateLayout    = SimulationTool.isParasiticsBackAnnotateLayout();
            parasiticsExtractPowerGround    = SimulationTool.isParasiticsExtractPowerGround();
            parasiticsUseExemptedNetsFile   = SimulationTool.isParasiticsUseExemptedNetsFile();
            parasiticsIgnoreExemptedNets    = SimulationTool.isParasiticsIgnoreExemptedNets();
            parasiticsExtractsR             = SimulationTool.isParasiticsExtractsR();
            parasiticsExtractsC             = SimulationTool.isParasiticsExtractsC();

            globalTreatment                 = SimulationTool.getSpiceGlobalTreatment();
            maxLineLen                      = SimulationTool.getSpiceMaxCharsPerLine();
            writePwrGndInTopCell            = SimulationTool.isSpiceWritePwrGndInTopCell();
            useCellParameters               = SimulationTool.isSpiceUseCellParameters();
            writeTransSizeInLambda          = SimulationTool.isSpiceWriteTransSizeInLambda();
            writeSubcktTopCell              = SimulationTool.isSpiceWriteSubcktTopCell();
            writeTopCellInstance            = SimulationTool.isSpiceWriteTopCellInstance();
            writeEmptySubckts               = SimulationTool.isSpiceWriteEmptySubckts();
            writeFinalDotEnd                = SimulationTool.isSpiceWriteFinalDotEnd();
            ignoreParasiticResistors        = NetworkTool.isIgnoreResistors();
            extractedNetDelimiter           = SimulationTool.getSpiceExtractedNetDelimiter();
            ignoreModelFiles                = SimulationTool.isSpiceIgnoreModelFiles();

            cdlLibName                      = SimulationTool.getCDLLibName();
            cdlLibPath                      = SimulationTool.getCDLLibPath();
            cdlConvertBrackets              = SimulationTool.isCDLConvertBrackets();
            cdlIncludeFile                  = SimulationTool.getCDLIncludeFile();

            modelFiles                      = CellModelPrefs.spiceModelPrefs.getUnfilteredFileNames(EDatabase.clientDatabase());
            workdir                         = User.getWorkingDirectory();
        }

        @Override
        public Output doOutput(Cell cell, VarContext context, String filePath)
        {
    		Spice out = new Spice(this);
    		out.useCDL = cdl;
            out.spiceEngine = engine;
            out.curTech = cell.getTechnology();
    		if (out.openTextOutputStream(filePath)) return out.finishWrite();
    		if (out.writeCell(cell, context)) return out.finishWrite();
    		if (out.closeTextOutputStream()) return out.finishWrite();
    		System.out.println(filePath + " written");

    		// write CDL support file if requested
    		if (out.useCDL)
    		{
    			// write the control files
    			String deckFile = filePath;
    			String deckPath = "";
    			int lastDirSep = deckFile.lastIndexOf(File.separatorChar);
    			if (lastDirSep > 0)
    			{
    				deckPath = deckFile.substring(0, lastDirSep);
    				deckFile = deckFile.substring(lastDirSep+1);
    			}

//    			String templateFile = deckPath + File.separator + cell.getName() + ".cdltemplate";
    			String templateFile = filePath + "template";
    			if (out.openTextOutputStream(templateFile)) return out.finishWrite();

    			String libName = cdlLibName;
    			String libPath = cdlLibPath;
    			out.printWriter.print("cdlInKeys = list(nil\n");
    			out.printWriter.print("    'searchPath             \"" + deckFile + "");
    			if (libPath.length() > 0)
    				out.printWriter.print("\n                             " + libPath);
    			out.printWriter.print("\"\n");
    			out.printWriter.print("    'cdlFile                \"" + deckPath + File.separator + deckFile + "\"\n");
    			out.printWriter.print("    'userSkillFile          \"\"\n");
    			out.printWriter.print("    'opusLib                \"" + libName + "\"\n");
    			out.printWriter.print("    'primaryCell            \"" + cell.getName() + "\"\n");
    			out.printWriter.print("    'caseSensitivity        \"lower\"\n");
    			out.printWriter.print("    'hierarchy              \"flatten\"\n");
    			out.printWriter.print("    'cellTable              \"\"\n");
    			out.printWriter.print("    'viewName               \"netlist\"\n");
    			out.printWriter.print("    'viewType               \"\"\n");
    			out.printWriter.print("    'pr                     nil\n");
    			out.printWriter.print("    'skipDevice             nil\n");
    			out.printWriter.print("    'schemaLib              \"sample\"\n");
    			out.printWriter.print("    'refLib                 \"\"\n");
    			out.printWriter.print("    'globalNodeExpand       \"full\"\n");
    			out.printWriter.print(")\n");
    			if (out.closeTextOutputStream()) return out.finishWrite();
    			System.out.println(templateFile + " written");
    		}

            String runSpice = runChoice;
            if (!runSpice.equals(SimulationTool.spiceRunChoiceDontRun)) {
                String command = runProgram + " " + runProgramArgs;

                // see if user specified custom directory to run process in
                String workdir = User.getWorkingDirectory();
                String rundir = workdir;
                if (useRunDir) {
                    rundir = runDir;
                }
                File dir = new File(rundir);

                int start = filePath.lastIndexOf(File.separator);
                if (start == -1) start = 0; else {
                    start++;
                    if (start > filePath.length()) start = filePath.length();
                }
                int end = filePath.lastIndexOf(".");
                if (end == -1) end = filePath.length();
                String filename_noext = filePath.substring(start, end);
                String filename = filePath.substring(start, filePath.length());

                // replace variables in command and arguments
                command = command.replaceAll("\\$\\{WORKING_DIR}", Matcher.quoteReplacement(workdir));
                command = command.replaceAll("\\$\\{USE_DIR}", Matcher.quoteReplacement(rundir));
                command = command.replaceAll("\\$\\{FILEPATH}", Matcher.quoteReplacement(filePath));
                command = command.replaceAll("\\$\\{FILENAME}", Matcher.quoteReplacement(filename));
                command = command.replaceAll("\\$\\{FILENAME_NO_EXT}", Matcher.quoteReplacement(filename_noext));

                // set up run probe
                Exec.FinishedListener l = new SpiceFinishedListener();

                String[] commandWords = command.split(" ");
                if (runSpice.equals(SimulationTool.spiceRunChoiceRunIgnoreOutput))
                {
                    Exec e = new Exec(commandWords, null, dir, null, null);
                    if (runProbe) e.addFinishedListener(l);
                    e.start();
                }
                if (runSpice.equals(SimulationTool.spiceRunChoiceRunReportOutput))
                {
                    ExecDialog dialog = new ExecDialog(TopLevel.getCurrentJFrame(), false);
                    if (runProbe) dialog.addFinishedListener(l);
                    dialog.startProcess(commandWords, null, dir);
                }
                System.out.println("Running spice command: "+command);
            }

            if (parasiticsBackAnnotateLayout && out.parasiticInfo != null)
            {
                out.parasiticInfo.backAnnotate();
            }
            return out.finishWrite();
        }
    }

	/**
	 * Constructor for the Spice netlister.
	 */
	Spice(SpicePreferences sp) { localPrefs = sp; }

	/**
	 * Method called once by the traversal mechanism.
	 * Initializes Spice netlisting and writes headers.
	 */
	protected void start()
	{
		// find the proper technology to use if this is schematics
        if (topCell.getTechnology().isLayout())
            layoutTechnology = topCell.getTechnology();
        else
            layoutTechnology = Schematics.getDefaultSchematicTechnology();

		// make sure key is cached
		preferedEngineTemplateKey = SPICE_TEMPLATE_KEY;
        assuraHSpice = false;
		switch (spiceEngine)
		{
			case SPICE_ENGINE_2:         preferedEngineTemplateKey = SPICE_2_TEMPLATE_KEY;    break;
			case SPICE_ENGINE_3:         preferedEngineTemplateKey = SPICE_3_TEMPLATE_KEY;    break;
			case SPICE_ENGINE_H:         preferedEngineTemplateKey = SPICE_H_TEMPLATE_KEY;    break;
			case SPICE_ENGINE_P:         preferedEngineTemplateKey = SPICE_P_TEMPLATE_KEY;    break;
			case SPICE_ENGINE_G:         preferedEngineTemplateKey = SPICE_GC_TEMPLATE_KEY;   break;
			case SPICE_ENGINE_S:         preferedEngineTemplateKey = SPICE_SM_TEMPLATE_KEY;   break;
			case SPICE_ENGINE_XYCE:      preferedEngineTemplateKey = SPICE_XYCE_TEMPLATE_KEY; break;
			case SPICE_ENGINE_O:                                                              break;
			case SPICE_ENGINE_H_ASSURA:  preferedEngineTemplateKey = SPICE_A_TEMPLATE_KEY;  assuraHSpice = true; break;
			case SPICE_ENGINE_H_CALIBRE: preferedEngineTemplateKey = SPICE_C_TEMPLATE_KEY;  assuraHSpice = true; break;
            case SPICE_ENGINE_NG:        preferedEngineTemplateKey = SPICE_NG_TEMPLATE_KEY;   break;
            case SPICE_ENGINE_JO:        preferedEngineTemplateKey = SPICE_JO_TEMPLATE_KEY;   break;
		}
        if (useCDL) {
            preferedEngineTemplateKey = CDL_TEMPLATE_KEY;
        }
        if (assuraHSpice || !localPrefs.writeEmptySubckts) {
            writeEmptySubckts = false;
        }

        // get the mask scale
		maskScale = 1.0;

        // set up the parameterized cells
        uniquifyCells = new HashMap<Cell,Cell>();
        uniqueID = 0;
        uniqueNames = new HashMap<String,Integer>();
        markCellsToUniquify(topCell);

		// setup the legal characters
        switch (spiceEngine)
        {
            case SPICE_ENGINE_P:
            case SPICE_ENGINE_G:
                legalSpiceChars = PSPICELEGALCHARS;
                break;
            default:
                legalSpiceChars = SPICELEGALCHARS;
                break;
        }

		// start writing the spice deck
		if (useCDL)
		{
			// setup bracket conversion for CDL
			if (localPrefs.cdlConvertBrackets)
				legalSpiceChars = CDLNOBRACKETLEGALCHARS;

			multiLinePrint(true, "* First line is ignored\n");
            // see if include file specified
            String headerPath = TextUtils.getFilePath(topCell.getLibrary().getLibFile());
            String filePart = localPrefs.cdlIncludeFile;
            if (!filePart.equals("")) {
                String fileName = headerPath + filePart;
                File test = new File(fileName);
                if (test.exists())
                {
                    addIncludeFile(filePart, "Primitives", false);
                } else {
                    reportWarning("Warning: CDL Include file not found: "+fileName);
                }
            }
		} else
		{
			writeHeader(topCell);
	        if (spiceEngine == SimulationTool.SpiceEngine.SPICE_ENGINE_JO)
	        {
				multiLinePrint(true, ".model jmitll jj(rtype=1, vg=2.8mV, cap=0.07pF, r0=160, rN=16, icrit=0.1mA)\n");
	        }
            spiceCodeFlat = new FlatSpiceCodeVisitor(filePath+".flatcode", this);
            HierarchyEnumerator.enumerateCell(topCell, VarContext.globalContext, spiceCodeFlat, getShortResistorsFlat());
            spiceCodeFlat.close();
		}

        if (localPrefs.parasiticsUseExemptedNetsFile) {
            String headerPath = TextUtils.getFilePath(topCell.getLibrary().getLibFile());
            exemptedNets = new SpiceExemptedNets(new File(headerPath + File.separator + "exemptedNets.txt"));
        }
	}

	/**
	 * Method called once at the end of netlisting.
	 */
	protected void done()
	{
		if (!useCDL)
		{
			writeTrailer(topCell);
            if (localPrefs.writeFinalDotEnd)
                multiLinePrint(false, ".END\n");
		}
	}

	/**
	 * Method called by traversal mechanism to write one level of hierarchy in the Spice netlist.
	 * This could be the top level or a subcircuit.
	 * The bulk of the Spice netlisting happens here.
	 * @param cell the cell to write.
	 * @param cellName the name of the cell to use in the header.
	 * @param cni information from the hierarchy traverser.
	 * @param context the hierarchical cell context.
	 * @param info more information from the hierarchy traverser.
	 */
	protected void writeCellTopology(Cell cell, String cellName, CellNetInfo cni, VarContext context, Topology.MyCellInfo info)
	{
		// if this is the top level cell, write globals
		if (cell == topCell)
		{
            Netlist netList = cni.getNetList();
            Global.Set globals = netList.getGlobals();
            int globalSize = globals.size();
    		if (localPrefs.globalTreatment == SimulationTool.SpiceGlobal.USEGLOBALBLOCK)
            {
                if (globalSize > 0)
                {
                    StringBuilder infstr = new StringBuilder();
                    switch (spiceEngine)
                    {
                        case SPICE_ENGINE_XYCE:
                            infstr.append("\n*** Globals\n");
                            break;
                        default:
                            infstr.append("\n.global");
                            break;
                    }
                    for(int i=0; i<globalSize; i++)
                    {
                        Global global = globals.get(i);
                        String name = global.getName();
                        if (global == Global.power) { if (getPowerName(null) != null) name = getPowerName(null); }
                        if (global == Global.ground) { if (getGroundName(null) != null) name = getGroundName(null); }
                        switch (spiceEngine)
                        {
                            case SPICE_ENGINE_XYCE:
                            	infstr.append(" $" + name);
                                break;
                            default:
                            	infstr.append(" " + name);
                                break;
                        }
                    }
                    infstr.append("\n");
                    multiLinePrint(false, infstr.toString());
                }
            } else
    		{
        		// make sure power and ground appear at the top level
    			if (cni.getPowerNet() == null)
    				System.out.println("WARNING: cannot find power at top level of circuit");
    			if (cni.getGroundNet() == null)
    				System.out.println("WARNING: cannot find ground at top level of circuit");
    		}
        }

		// create electrical nets (SpiceNet) for every Network in the cell
		Netlist netList = cni.getNetList();
		Map<Network,SpiceNet> spiceNetMap = new HashMap<Network,SpiceNet>();
        for(Iterator<Network> it = netList.getNetworks(); it.hasNext(); )
        {
            Network net = it.next();
            SpiceNet spNet = new SpiceNet();
            spiceNetMap.put(net, spNet);
        }

        // for non-simple parasitics, create a SpiceSegmentedNets object to deal with them
        SimulationTool.SpiceParasitics spLevel = localPrefs.parasiticsLevel;
        if (useCDL || cell.getView() != View.LAYOUT) spLevel = SimulationTool.SpiceParasitics.SIMPLE;
        SpiceSegmentedNets segmentedNets = null;
        if (spLevel != SimulationTool.SpiceParasitics.SIMPLE)
        {
            // make the parasitics info object if it does not already exist
            if (parasiticInfo == null)
            {
                if (spLevel == SimulationTool.SpiceParasitics.RC_PROXIMITY)
                {
            		parasiticInfo = new SpiceParasitic(localPrefs);
                } else if (spLevel == SimulationTool.SpiceParasitics.RC_CONSERVATIVE)
                {
            		parasiticInfo = new SpiceRCSimple(localPrefs);
                }
            }
            segmentedNets = parasiticInfo.initializeSegments(cell, cni, layoutTechnology, exemptedNets, info);
        }

		// count the number of different transistor types
		int nmosTrans = 0, pmosTrans = 0;
		for(Iterator<NodeInst> aIt = cell.getNodes(); aIt.hasNext(); )
		{
			NodeInst ni = aIt.next();
			addNodeInformation(netList, spiceNetMap, ni);
			PrimitiveNode.Function fun = ni.getFunction();
//			if (fun.isBipolar() || fun == PrimitiveNode.Function.TRANS) bipolarTrans++; else
			if (fun.isNTypeTransistor()) nmosTrans++; else
			if (fun.isPTypeTransistor()) pmosTrans++;
		}

		// accumulate geometry of all arcs
		for(Iterator<ArcInst> aIt = cell.getArcs(); aIt.hasNext(); )
		{
			ArcInst ai = aIt.next();

			// don't count non-electrical arcs
			if (ai.getProto().getFunction() == ArcProto.Function.NONELEC) continue;

			// ignore busses
//			if (ai->network->buswidth > 1) continue;
			Network net = netList.getNetwork(ai, 0);
			SpiceNet spNet = spiceNetMap.get(net);
			if (spNet == null) continue;

			addArcInformation(spNet.merge, ai);
		}

		// get merged polygons so far
		for(Iterator<Network> it = netList.getNetworks(); it.hasNext(); )
		{
			Network net = it.next();
			SpiceNet spNet = spiceNetMap.get(net);

            for (Layer layer : spNet.merge.getKeySet())
			{
				List<PolyBase> polyList = spNet.merge.getMergedPoints(layer, true);
				if (polyList == null) continue;
                if (polyList.size() > 1)
                    Collections.sort(polyList, GeometryHandler.shapeSort);
				for(PolyBase poly : polyList)
				{
					// compute perimeter and area
					double perim = poly.getPerimeter();
					double area = poly.getArea();

					// accumulate this information
                    double scale = layoutTechnology.getScale(); // scale to convert units to nanometers
                    if (layer.isDiffusionLayer()) {
                        spNet.diffArea += area * maskScale * maskScale;
                        spNet.diffPerim += perim * maskScale;
                    } else {
                        area = area * scale * scale / 1000000; // area in square microns
                        perim = perim * scale / 1000;           // perimeter in microns
//                      spNet.nonDiffCapacitance += layer.getCapacitance() * area * maskScale * maskScale;
//                      spNet.nonDiffCapacitance += layer.getEdgeCapacitance() * perim * maskScale;
                    }
				}
			}
		}

		// make sure the ground net is number zero
		Network groundNet = cni.getGroundNet();
		Network powerNet = cni.getPowerNet();
		if (pmosTrans != 0 && powerNet == null)
		{
			String message = "WARNING: no power connection for P-transistor wells in " + cell;
			dumpMessage(message, false);
		}
		if (nmosTrans != 0 && groundNet == null)
		{
			String message = "WARNING: no ground connection for N-transistor wells in " + cell;
			dumpMessage(message, false);
		}

		// generate header for subcircuit or top-level cell
        boolean forceEval = useCDL || !localPrefs.useCellParameters || detectSpiceParams(cell) == UNIQUIFY_MARK;
        String topLevelInstance = "";
		if (cell == topCell && !useCDL && !localPrefs.writeSubcktTopCell)
		{
			multiLinePrint(true, "\n*** TOP LEVEL CELL: " + cell.describe(false) + "\n");
		} else
		{
            if (!writeEmptySubckts) {
                if (cellIsEmpty(cell))
                    return;
            }

			multiLinePrint(true, "\n*** SUBCIRCUIT " + cellName + " FROM CELL " + cell.describe(false) + "\n");

			StringBuilder infstr = new StringBuilder();
			infstr.append(".SUBCKT " + cellName);
			for(Iterator<CellSignal> sIt = cni.getCellSignals(); sIt.hasNext(); )
			{
				CellSignal cs = sIt.next();
                if (ignoreSubcktPort(cs)) continue;
                if (!cs.isGlobal() && cs.getExport() == null) continue;

                // special case for parasitic extraction
                if (parasiticInfo != null && !cs.isGlobal() && cs.getExport() != null)
                {
                	parasiticInfo.writeSubcircuitHeader(cs, infstr);
                } else
                {
                    infstr.append(" " + getPortName(cs));
                }
			}

			Global.Set globals = netList.getGlobals();
			int globalSize = globals.size();
            if (cell == topCell && localPrefs.writeSubcktTopCell) {
                // create top level instantiation
                if (localPrefs.writeTopCellInstance)
                    topLevelInstance = infstr.toString().replaceFirst("\\.SUBCKT ", "X") + " " + cellName;
            }
			if (!useCDL && localPrefs.useCellParameters)
			{
				// add in parameters to this cell
				boolean firstParam = true;
                Set<Variable.Key> spiceParams = detectSpiceParams(cell);
				for(Iterator<Variable> it = cell.getParameters(); it.hasNext(); )
				{
					Variable paramVar = it.next();
                    if (DETECT_SPICE_PARAMS && !spiceParams.contains(paramVar.getKey())) continue;
                    String value = paramVar.getPureValue(-1);

                    if (USE_JAVA_CODE) {
                    	if (info.getParentInst() != null)
                    		value = evalParam(context, info.getParentInst(), paramVar, forceEval);
                    } else if (!isNetlistableParam(cell, paramVar))
                        continue;
                    switch (spiceEngine)
                    {
                        case SPICE_ENGINE_O:
                        case SPICE_ENGINE_XYCE:
                            if (firstParam)
                            {
                                infstr.append(" param:");
                            	firstParam = false;
                            }
                            break;
						default:
							break;
                    }
                    infstr.append(" " + paramVar.getTrueName() + "=" + value);
				}
			}
			infstr.append("\n");
			multiLinePrint(false, infstr.toString());

			// write global comments
			if (localPrefs.globalTreatment == SimulationTool.SpiceGlobal.USEGLOBALBLOCK)
			{
				for(int i=0; i<globalSize; i++)
				{
					Global global = globals.get(i);
					Network net = netList.getNetwork(global);
					CellSignal cs = cni.getCellSignal(net);
					multiLinePrint(true, "** GLOBAL " + cs.getName() + "\n");
				}
			}
		}

		// write out any directly-typed SPICE declarations for the cell
		if (!useCDL)
		{
			boolean firstDecl = true;
			for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
			{
				NodeInst ni = it.next();
				if (ni.getProto() != Generic.tech().invisiblePinNode) continue;
				Variable cardVar = ni.getVar(SPICE_DECLARATION_KEY);
				if (cardVar == null) continue;
				if (firstDecl)
				{
					firstDecl = false;
					multiLinePrint(true, "\n* Spice Declaration nodes in cell " + cell + "\n");
				}
				emitEmbeddedSpice(cardVar, context, segmentedNets, info, false, forceEval);
			}
		}

		// third pass through the node list, print it this time
		for(Iterator<Nodable> nIt = netList.getNodables(); nIt.hasNext(); )
		{
			Nodable no = nIt.next();
			NodeProto niProto = no.getProto();

            // handle sub-cell calls
			if (no.isCellInstance())
			{
				Cell subCell = (Cell)niProto;

				// get the SPICE template on the prototype (if any)
				Variable varTemplate = getEngineTemplate(subCell);
				if (varTemplate != null)
				{
					if (varTemplate.getObject() instanceof Object[])
					{
						Object [] manyLines = (Object [])varTemplate.getObject();
						for(int i=0; i<manyLines.length; i++)
						{
							String line = manyLines[i].toString();
							StringBuilder infstr = replacePortsAndVars(line, no, context, cni, segmentedNets, info, false, forceEval);
		                    // Writing MFactor if available. Not sure here
							if (i == 0) writeMFactor(context, no, infstr);
							infstr.append('\n');
							multiLinePrint(false, infstr.toString());
						}
					} else
					{
						String line = varTemplate.getObject().toString();
						if (varTemplate.isCode())
							line = varTemplate.withDispPart(TextDescriptor.DispPos.VALUE).describe(-1, context.push(no), subCell);
						StringBuilder infstr = replacePortsAndVars(line, no, context, cni, segmentedNets, info, false, forceEval);

	                    // Writing MFactor if available. Not sure here
						writeMFactor(context, no, infstr);

						infstr.append('\n');
						multiLinePrint(false, infstr.toString());
					}
					continue;
				}

				// get the ports on this node (in proper order)
				CellNetInfo subCni = getCellNetInfo(parameterizedName(no, context));
				if (subCni == null) continue;

                if (!writeEmptySubckts)
                {
                    // do not instantiate if empty
                    if (cellIsEmpty((Cell)niProto)) continue;
                }

				String modelChar = "X";
				if (no.getName() != null) modelChar += getSafeNetName(no.getName(), false);
				StringBuilder infstr = new StringBuilder();
				infstr.append(modelChar);
				for(Iterator<CellSignal> sIt = subCni.getCellSignals(); sIt.hasNext(); )
				{
					CellSignal subCS = sIt.next();
	                if (ignoreSubcktPort(subCS)) continue;
					PortProto pp = subCS.getExport();
	                if (!subCS.isGlobal() && pp == null) continue;

	                // If global pwr/vdd will be included in the subcircuit
                    // Preparing code for bug #1828
//					if (!localPrefs.writePwrGndInTopCell && pp!= null && subCS.isGlobal() && (subCS.isGround() || subCS.isPower()))
//						continue;

                    Network net;
                    int exportIndex = subCS.getExportIndex();

                    // This checks if we are netlisting a schematic top level with swapped-in layout subcells
                    if (pp != null && cell.isSchematic() && (subCni.getCell().getView() == View.LAYOUT))
                    {
                        // find equivalent port from layout to schematic
                        Network subNet = subCS.getNetwork();  // layout network name
                        boolean found = false;
                        for (Iterator<Export> eIt = subCell.getExports(); eIt.hasNext(); )
                        {
                            Export ex = eIt.next();
                            for (int i=0; i<ex.getNameKey().busWidth(); i++)
                            {
                                String exName = ex.getNameKey().subname(i).toString();
                                if (exName.equals(subNet.getName()))
                                {
                                    pp = ex;
                                    exportIndex = i;
                                    found = true;
                                    break;
                                }
                            }
                            if (found) break;
                        }
                        if (!found)
                        {
                            if (pp.isGround() && pp.getName().startsWith("gnd")) {
                                infstr.append(" gnd");
                            } else if (pp.isPower() && pp.getName().startsWith("vdd")) {
                                infstr.append(" vdd");
                            } else {
                                System.out.println("No matching export on schematic/icon found for export "+
                                        subNet.getName()+" in cell "+subCni.getCell().describe(false));
                                infstr.append(" unknown");
                            }
                            continue;
                        }
                    }

                    if (subCS.isGlobal())
                    {
                        net = netList.getNetwork(no, subCS.getGlobal());
                    } else {
                        net = netList.getNetwork(no, pp, exportIndex);
                    }
					if (net == null)
					{
						reportWarning("Warning: cannot find network for signal " + subCS.getName() + " in cell " +
							subCni.getCell().describe(false));
						continue;
					}
					CellSignal cs = cni.getCellSignal(net);

                    // special case for parasitic extraction
					SpiceSegmentedNets subSN = null;
                    if (parasiticInfo != null && !cs.isGlobal())
                    	subSN = parasiticInfo.getSegmentedNets((Cell)no.getProto());
                    if (subSN != null)
                    {
                    	parasiticInfo.getParasiticName(no, subCS.getNetwork(), subSN, infstr);
                    } else
                    {
                        String name = getPortName(cs);
                        if (parasiticInfo != null)
                        {
                            name = segmentedNets.getNetName(no.getNodeInst().findPortInstFromEquivalentProto(pp));
                        }
                        if (cs.isGlobal() && spiceEngine == SimulationTool.SpiceEngine.SPICE_ENGINE_XYCE) {
                        	infstr.append(" $" + name);
                        } else {
                        	infstr.append(" " + name);
                        }
                    }
				}

                if (useCDL && SimulationTool.isCDLLashOn()) infstr.append(" /"); else infstr.append(" ");
                String subCellName = subCni.getParameterizedName();

                // make sure to use the correct icon cell name if there are more than one
                NodeInst ni = no.getNodeInst();
                if (ni != null)
                {
                	String alternateSubCellName = getIconCellName((Cell)ni.getProto());
                	if (alternateSubCellName != null) subCellName = alternateSubCellName;
                }
                infstr.append(getSafeCellName(subCellName));

				if (!useCDL && localPrefs.useCellParameters)
				{
					// add in parameters to this instance
                    Set<Variable.Key> spiceParams = detectSpiceParams(subCell);
					for(Iterator<Variable> it = subCell.getParameters(); it.hasNext(); )
					{
						Variable paramVar = it.next();
                        if (DETECT_SPICE_PARAMS && !spiceParams.contains(paramVar.getKey())) continue;
                        if (!USE_JAVA_CODE && !isNetlistableParam(subCell, paramVar)) continue;
                        Variable instVar = no.getParameter(paramVar.getKey());
                        String paramStr = "??";
						if (instVar != null)
						{
                            if (USE_JAVA_CODE)
                            {
                                paramStr = evalParam(context, no, instVar, forceEval);
                            } else
                            {
                                Object obj = null;
                                if (isNetlistableParam(subCell, instVar))
                                {
                                    obj = context.evalSpice(instVar, false);
                                } else
                                {
                                    obj = context.evalVar(instVar, no);
                                }
                                if (obj != null)
                                    paramStr = formatParam(String.valueOf(obj), instVar.getUnit(), false);
                            }
                        }
						infstr.append(" " + paramVar.getTrueName() + "=" + paramStr);
					}
				}

				// Writing MFactor if available.
                writeMFactor(context, no, infstr);

				infstr.append("\n");
				multiLinePrint(false, infstr.toString());
				continue;
			}

			// get the type of this node
			NodeInst ni = (NodeInst)no;

			// look for a SPICE template on the primitive
			String line = ((PrimitiveNode)ni.getProto()).getSpiceTemplate();
			if (line != null)
			{
				StringBuilder infstr = replacePortsAndVars(line, no, context, cni, segmentedNets, info, false, forceEval);
                // Writing MFactor if available. Not sure here
				writeMFactor(context, no, infstr);

				infstr.append('\n');
				multiLinePrint(false, infstr.toString());
				continue;
			}

			// handle resistors, inductors, capacitors, diodes, and Josephson Junctions
			PrimitiveNode.Function fun = ni.getFunction();
			if (fun.isResistor() || fun.isCapacitor() ||
                fun == PrimitiveNode.Function.INDUCT ||
				fun == PrimitiveNode.Function.DIODE || fun == PrimitiveNode.Function.DIODEZ ||
                fun == PrimitiveNode.Function.JOSEPHSON)
			{
				if (fun.isResistor())
				{
                    if ((fun.isComplexResistor() && isShortExplicitResistors()) ||
                        (fun == PrimitiveNode.Function.RESIST && isShortResistors()))
                        	continue;
					Variable resistVar = ni.getVar(Schematics.SCHEM_RESISTANCE);
					String extra = "";
                    String partName = "R";
					if (resistVar != null)
					{
                        if (USE_JAVA_CODE) {
                            extra = evalParam(context, ni, resistVar, forceEval);
                        } else {
                            if (resistVar.getCode() == CodeExpression.Code.SPICE) {
                                if (!useCDL && localPrefs.useCellParameters) {
                                    Object obj = context.evalSpice(resistVar, false);
                                    extra = String.valueOf(obj);
                                } else {
                                    extra = resistVar.describe(context, ni);
                                }
                            }
                            if (extra == "")
                                extra = resistVar.describe(context, ni);
                            if (TextUtils.isANumber(extra))
                            {
                                double pureValue = TextUtils.atof(extra);
                                extra = localPrefs.formatDoublePostFix(pureValue); //displayedUnits(pureValue, TextDescriptor.Unit.RESISTANCE, TextUtils.UnitScale.NONE);
                            } else
                                extra = formatParam(extra, resistVar.getUnit(), false);
                        }
					} else {
                        if (fun.isComplexResistor())
                        {
                            partName = "XR";
                            double width = ni.getLambdaBaseYSize();
                            double length = ni.getLambdaBaseXSize();
                            if (localPrefs.writeTransSizeInLambda)
                            {
                                extra = " L="+length+" W="+width;
                            } else
                            {
                                extra = " L="+formatParam(length+"*LAMBDA", TextDescriptor.Unit.NONE, false)+
                                	" W="+formatParam(width+"*LAMBDA", TextDescriptor.Unit.NONE, false);
                            }
                            String prepend = "";
                            if (fun == PrimitiveNode.Function.RESPPOLY) prepend = "rppolywo"; else
                            	if (fun == PrimitiveNode.Function.RESNPOLY) prepend = "rnpolywo"; else
                            		if (fun == PrimitiveNode.Function.RESPPOLYM) prepend = "rppolywo_m"; else
                                    	if (fun == PrimitiveNode.Function.RESNPOLYM) prepend = "rnpolywo_m"; else
                                    		if (fun == PrimitiveNode.Function.RESPNSPOLY) prepend = "rpponsrpo"; else   // made up value
                                    			if (fun == PrimitiveNode.Function.RESNNSPOLY) prepend = "rnponsrpo"; else  // made up value
                                    				if (fun == PrimitiveNode.Function.RESPWELL) prepend = "rpwod "; else
                                    					if (fun == PrimitiveNode.Function.RESNWELL) prepend = "rnwod "; else
                                    						if (fun == PrimitiveNode.Function.RESPACTIVE) prepend = "rpaod "; else
                                    							if (fun == PrimitiveNode.Function.RESNACTIVE) prepend = "rnaod ";
                            if (layoutTechnology == Technology.getCMOS90Technology() ||
                                (cell.getView() == View.LAYOUT && cell.getTechnology() == Technology.getCMOS90Technology()))
                            {
                                if (fun == PrimitiveNode.Function.RESPPOLY) prepend = "GND rpporpo"; else
                                	if (fun == PrimitiveNode.Function.RESNPOLY) prepend = "GND rnporpo";
                            }
                            extra = prepend + extra;
                        }
                    }
					writeTwoPort(ni, partName, extra, cni, netList, context, segmentedNets);
				} else if (fun.isCapacitor())
				{
					Variable capacVar = ni.getVar(Schematics.SCHEM_CAPACITANCE);
					String extra = "";
					if (capacVar != null)
					{
                        if (USE_JAVA_CODE) {
                            extra = evalParam(context, no, capacVar, forceEval);
                        } else {
                            if (capacVar.getCode() == CodeExpression.Code.SPICE) {
                                if (!useCDL && localPrefs.useCellParameters) {
                                    Object obj = context.evalSpice(capacVar, false);
                                    extra = String.valueOf(obj);
                                } else {
                                    extra = capacVar.describe(context, ni);
                                }
                            }
                            if (extra == "")
                                extra = capacVar.describe(context, ni);
                            if (TextUtils.isANumber(extra))
                            {
                                double pureValue = TextUtils.atof(extra);
                                extra = localPrefs.formatDoublePostFix(pureValue); // displayedUnits(pureValue, TextDescriptor.Unit.CAPACITANCE, TextUtils.UnitScale.NONE);
                            } else
                                extra = formatParam(extra, capacVar.getUnit(), false);
                        }
					}
					writeTwoPort(ni, "C", extra, cni, netList, context, segmentedNets);
				} else if (fun == PrimitiveNode.Function.INDUCT)
				{
					Variable inductVar = ni.getVar(Schematics.SCHEM_INDUCTANCE);
					String extra = "";
					if (inductVar != null)
					{
                        if (USE_JAVA_CODE) {
                            extra = evalParam(context, no, inductVar, forceEval);
                        } else {
                            if (inductVar.getCode() == CodeExpression.Code.SPICE) {
                                if (!useCDL && localPrefs.useCellParameters) {
                                    Object obj = context.evalSpice(inductVar, false);
                                    extra = String.valueOf(obj);
                                } else {
                                    extra = inductVar.describe(context, ni);
                                }
                            }
                            if (extra == "")
                                extra = inductVar.describe(context, ni);
                            if (TextUtils.isANumber(extra))
                            {
                                double pureValue = TextUtils.atof(extra);
                                extra = localPrefs.formatDoublePostFix(pureValue); // displayedUnits(pureValue, TextDescriptor.Unit.INDUCTANCE, TextUtils.UnitScale.NONE);
                            } else
                                extra = formatParam(extra, inductVar.getUnit(), false);
                        }
					}
					writeTwoPort(ni, "L", extra, cni, netList, context, segmentedNets);
				} else if (fun == PrimitiveNode.Function.DIODE || fun == PrimitiveNode.Function.DIODEZ)
				{
					Variable diodeVar = ni.getVar(Schematics.SCHEM_DIODE);
					String extra = "";
					if (diodeVar != null)
						extra = diodeVar.describe(context, ni);
					if (extra.length() == 0) extra = "DIODE";
					writeTwoPort(ni, "D", extra, cni, netList, context, segmentedNets);
				} else if (fun == PrimitiveNode.Function.JOSEPHSON)
				{
					String extra = "jmitll";
					double area = ni.getLambdaBaseXSize() * Math.PI / 10.0;

					// get the area for schematic JJs
					if (!ni.getProto().getTechnology().isLayout())
					{
			            Object areaObj = context.evalVar(ni.getVar(Schematics.ATTR_AREA));
			            area = VarContext.objectToDouble(areaObj, -1);
					}
					extra += " area=" + TextUtils.formatDouble(area);
					writeTwoPort(ni, "B", extra, cni, netList, context, segmentedNets);
				}
				continue;
			}

			// the default is to handle everything else as a transistor
			if (((PrimitiveNode)niProto).getGroupFunction() != PrimitiveNode.Function.TRANS)
				continue;

			Network gateNet = netList.getNetwork(ni.getTransistorGatePort());
			CellSignal gateCs = cni.getCellSignal(gateNet);
			Network sourceNet = netList.getNetwork(ni.getTransistorSourcePort());
			CellSignal sourceCs = cni.getCellSignal(sourceNet);
			Network drainNet = netList.getNetwork(ni.getTransistorDrainPort());
			CellSignal drainCs = cni.getCellSignal(drainNet);
			CellSignal biasCs = null;
			PortInst biasPort = ni.getTransistorBiasPort();
			if (biasPort != null)
			{
				biasCs = cni.getCellSignal(netList.getNetwork(biasPort));
			}

			// make sure transistor is connected to nets
			if (gateCs == null || sourceCs == null || drainCs == null)
			{
				String message = "WARNING: " + ni + " not fully connected in " + cell;
				dumpMessage(message, false);
			}

			// get model information
			String modelName = null;
            String defaultBulkName = null;
			Variable modelVar = ni.getVar(SPICE_MODEL_KEY);
			if (modelVar != null) modelName = modelVar.getObject().toString();

            // special case for ST090 technology which has stupid non-standard transistor
            // models which are subcircuits
            boolean st090laytrans = false;
            boolean tsmc090laytrans = false;
            if (cell.getView() == View.LAYOUT && layoutTechnology == Technology.getCMOS90Technology()) {
                if (layoutTechnology.getSelectedFoundry().getType() == Foundry.Type.TSMC)
                    tsmc090laytrans = true;
                else if (layoutTechnology.getSelectedFoundry().getType() == Foundry.Type.ST)
                    st090laytrans = true;
            }

			String modelChar = "";
			if (fun == PrimitiveNode.Function.TRANSREF)					// self-referential transistor
			{
				modelChar = "X";
				if (biasCs == null) biasCs = cni.getCellSignal(groundNet);
				modelName = niProto.getName();
			} else if (fun == PrimitiveNode.Function.TRANMOS)			// NMOS (Enhancement) transistor
			{
				modelChar = "M";
				if (biasCs == null) biasCs = cni.getCellSignal(groundNet);
                defaultBulkName = "gnd";
				if (modelName == null) modelName = "N";
                if (st090laytrans) {
                    modelChar = "XM";
                    modelName = "nsvt";
                }
                if (tsmc090laytrans) {
                    modelName = "nch";
                }
            } else if (fun == PrimitiveNode.Function.TRA4NMOS)			// NMOS (Complementary) 4-port transistor
			{
				modelChar = "M";
				if (modelName == null) modelName = "N";
                if (st090laytrans) {
                    modelChar = "XM";
                    modelName = "nsvt";
                }
                if (tsmc090laytrans) {
                    modelName = "nch";
                }
			} else if (fun == PrimitiveNode.Function.TRADMOS)			// DMOS (Depletion) transistor
			{
				modelChar = "M";
				if (biasCs == null) biasCs = cni.getCellSignal(groundNet);
				if (modelName == null) modelName = "D";
			} else if (fun == PrimitiveNode.Function.TRA4DMOS)			// DMOS (Depletion) 4-port transistor
			{
				modelChar = "M";
				if (modelName == null) modelName = "D";
			} else if (fun == PrimitiveNode.Function.TRAPMOS)			// PMOS (Complementary) transistor
			{
				modelChar = "M";
				if (biasCs == null) biasCs = cni.getCellSignal(powerNet);
                defaultBulkName = "vdd";
				if (modelName == null) modelName = "P";
                if (st090laytrans) {
                    modelChar = "XM";
                    modelName = "psvt";
                }
                if (tsmc090laytrans) {
                    modelName = "pch";
                }
            } else if (fun == PrimitiveNode.Function.TRA4PMOS)			// PMOS (Complementary) 4-port transistor
			{
				modelChar = "M";
				if (modelName == null) modelName = "P";
                if (st090laytrans) {
                    modelChar = "XM";
                    modelName = "psvt";
                }
                if (tsmc090laytrans) {
                    modelName = "pch";
                }
            } else if (fun == PrimitiveNode.Function.TRANPN)			// NPN (Junction) transistor
			{
				modelChar = "Q";
				if (modelName == null) modelName = "NBJT";
			} else if (fun == PrimitiveNode.Function.TRA4NPN)			// NPN (Junction) 4-port transistor
			{
				modelChar = "Q";
				if (modelName == null) modelName = "NBJT";
			} else if (fun == PrimitiveNode.Function.TRAPNP)			// PNP (Junction) transistor
			{
				modelChar = "Q";
				if (modelName == null) modelName = "PBJT";
			} else if (fun == PrimitiveNode.Function.TRA4PNP)			// PNP (Junction) 4-port transistor
			{
				modelChar = "Q";
				if (modelName == null) modelName = "PBJT";
			} else if (fun == PrimitiveNode.Function.TRANJFET)			// NJFET (N Channel) transistor
			{
				modelChar = "J";
				biasCs = null;
				if (modelName == null) modelName = "NJFET";
			} else if (fun == PrimitiveNode.Function.TRA4NJFET)			// NJFET (N Channel) 4-port transistor
			{
				modelChar = "J";
				if (modelName == null) modelName = "NJFET";
			} else if (fun == PrimitiveNode.Function.TRAPJFET)			// PJFET (P Channel) transistor
			{
				modelChar = "J";
				biasCs = null;
				if (modelName == null) modelName = "PJFET";
			} else if (fun == PrimitiveNode.Function.TRA4PJFET)			// PJFET (P Channel) 4-port transistor
			{
				modelChar = "J";
				if (modelName == null) modelName = "PJFET";
			} else if (fun == PrimitiveNode.Function.TRADMES ||			// DMES (Depletion) transistor
				fun == PrimitiveNode.Function.TRA4DMES)					// DMES (Depletion) 4-port transistor
			{
				modelChar = "Z";
				biasCs = null;
				modelName = "DMES";
			} else if (fun == PrimitiveNode.Function.TRAEMES ||			// EMES (Enhancement) transistor
				fun == PrimitiveNode.Function.TRA4EMES)					// EMES (Enhancement) 4-port transistor
			{
				modelChar = "Z";
				biasCs = null;
				modelName = "EMES";
 			} else if (fun == PrimitiveNode.Function.TRANMOSCN)			// NMOS (carbon-nanotube) transistor
			{
				modelChar = "X";
				biasCs = cni.getCellSignal(groundNet);
				defaultBulkName = "gnd";
				modelName = "NCNFET";
			} else if (fun == PrimitiveNode.Function.TRA4NMOSCN)		// NMOS (carbon-nanotube) 4-port transistor
			{
				modelChar = "X";
				modelName = "NCNFET";
			} else if (fun == PrimitiveNode.Function.TRAPMOSCN)			// PMOS (carbon-nanotube) transistor
			{
				modelChar = "X";
				biasCs = cni.getCellSignal(powerNet);
				defaultBulkName = "vdd";
				modelName = "PCNFET";
			} else if (fun == PrimitiveNode.Function.TRA4PMOSCN)		// PMOS (carbon-nanotube) 4-port transistor
			{
				modelChar = "X";
				modelName = "PCNFET";
			} else if (fun == PrimitiveNode.Function.TRANS)				// special transistor
			{
				modelChar = "Q";
			}
			if (ni.getName() != null) modelChar += getSafeNetName(ni.getName(), false);
			StringBuilder infstr = new StringBuilder();
			String drainName = getPortName(drainCs);
			String gateName = getPortName(gateCs);
			String sourceName = getPortName(sourceCs);
            if (segmentedNets != null) {
                drainName = segmentedNets.getNetName(ni.getTransistorDrainPort());
                gateName = segmentedNets.getNetName(ni.getTransistorGatePort());
                sourceName = segmentedNets.getNetName(ni.getTransistorSourcePort());
            }
			infstr.append(modelChar + " " + drainName + " " + gateName + " " + sourceName);
			if (biasCs != null) {
                String biasName = getPortName(biasCs);
                if (segmentedNets != null && ni.getTransistorBiasPort() != null)
                {
                    String bn = segmentedNets.getNetName(ni.getTransistorBiasPort());
                    if (bn != null) biasName = bn;
                }
                infstr.append(" " + biasName);
            } else {
                if (cell.getView() == View.LAYOUT && defaultBulkName != null)
                    infstr.append(" " + defaultBulkName);
            }
			if (modelName != null) infstr.append(" " + modelName);

			// compute length and width (or area for nonMOS transistors)
			TransistorSize size = ni.getTransistorSize(context);
            if (size == null)
                reportWarning("Warning: transistor has null size " + ni.describe(false));
            else
            {
                boolean spiceH = false;
                switch (spiceEngine)
                {
                    case SPICE_ENGINE_H:
                    case SPICE_ENGINE_H_ASSURA:
                    case SPICE_ENGINE_NG:
                        spiceH = true;
                        break;
					default:
						break;
                }

                // write the length
            	Double foundLen = null;
                Variable varLen = ni.getVar(Schematics.ATTR_LENGTH);
                if (varLen != null && varLen.getCode() == CodeExpression.Code.SPICE &&
                	!useCDL && localPrefs.useCellParameters)
                {
                	// write as a parameter
                    infstr.append(" L=" + evalParam(context, no, varLen, forceEval));
                } else
                {
                	// write the value, start by getting gate length subtraction in lambda
                    double lengthSubtraction = layoutTechnology.getGateLengthSubtraction() /
                    	layoutTechnology.getScale() * 1000;
	                if (size.getDoubleLength() > 0)
	                {
	                    double l = maskScale * size.getDoubleLength();
	                    l -= lengthSubtraction;

	                    // make into microns (convert to nanometers then divide by 1000)
	                    if (!localPrefs.writeTransSizeInLambda)
	                        l *= layoutTechnology.getScale() / 1000.0;

	                    if (fun == PrimitiveNode.Function.TRANMOS  || fun == PrimitiveNode.Function.TRA4NMOS ||
	                        fun == PrimitiveNode.Function.TRAPMOS || fun == PrimitiveNode.Function.TRA4PMOS ||
	                        fun == PrimitiveNode.Function.TRADMOS || fun == PrimitiveNode.Function.TRA4DMOS ||
	                        ((fun == PrimitiveNode.Function.TRANJFET || fun == PrimitiveNode.Function.TRAPJFET ||
	                          fun == PrimitiveNode.Function.TRADMES || fun == PrimitiveNode.Function.TRAEMES) &&
                             spiceH))
//	                          (spiceEngine == SimulationTool.SpiceEngine.SPICE_ENGINE_H || spiceEngine == SimulationTool.SpiceEngine.SPICE_ENGINE_H_ASSURA)))
	                    {
	                        // schematic transistors may be text
	                        if (size.getDoubleLength() == 0 && size.getLength() instanceof String)
	                        {
                                infstr.append(" L=" + formatParam((String)size.getLength(), TextDescriptor.Unit.DISTANCE, false));
	                            if (lengthSubtraction != 0) infstr.append(" - " + lengthSubtraction);
	                        } else
	                        {
	                            infstr.append(" L=" + localPrefs.formatDouble(l));
	                            if (!localPrefs.writeTransSizeInLambda && !st090laytrans) infstr.append("U");
	                        }
	                    }
	                	foundLen = Double.valueOf(l);
	                } else
	                {
	                    // get gate length subtraction in lambda
	                    if (size.getDoubleLength() == 0 && size.getLength() instanceof String)
	                    {
                            infstr.append(" L=" + formatParam((String)size.getLength(), TextDescriptor.Unit.DISTANCE, false));
	                        if (lengthSubtraction != 0) infstr.append(" - " + lengthSubtraction);
	                    }
	                }
                }

                // write the width
            	Double foundWid = null;
                Variable varWid = ni.getVar(Schematics.ATTR_WIDTH);
                if (varWid != null && varWid.getCode() == CodeExpression.Code.SPICE &&
                	!useCDL && localPrefs.useCellParameters)
                {
                	// write as a parameter
                    infstr.append(" W=" + evalParam(context, no, varWid, forceEval));
                } else
                {
                	// write the value
	                if (size.getDoubleWidth() > 0)
	                {
	                    double w = maskScale * size.getDoubleWidth();

	                    // make into microns (convert to nanometers then divide by 1000)
	                    if (!localPrefs.writeTransSizeInLambda)
	                        w *= layoutTechnology.getScale() / 1000.0;

	                    if (fun == PrimitiveNode.Function.TRANMOS  || fun == PrimitiveNode.Function.TRA4NMOS ||
	                        fun == PrimitiveNode.Function.TRAPMOS || fun == PrimitiveNode.Function.TRA4PMOS ||
	                        fun == PrimitiveNode.Function.TRADMOS || fun == PrimitiveNode.Function.TRA4DMOS ||
	                        ((fun == PrimitiveNode.Function.TRANJFET || fun == PrimitiveNode.Function.TRAPJFET ||
	                          fun == PrimitiveNode.Function.TRADMES || fun == PrimitiveNode.Function.TRAEMES) &&
                             spiceH))
//	                          (spiceEngine == SimulationTool.SpiceEngine.SPICE_ENGINE_H || spiceEngine == SimulationTool.SpiceEngine.SPICE_ENGINE_H_ASSURA)))
	                    {
	                        if ((size.getDoubleWidth() == 0) && (size.getWidth() instanceof String)) {
	                            infstr.append(" W="+formatParam((String)size.getWidth(), TextDescriptor.Unit.DISTANCE, false));
	                        } else {
	                            infstr.append(" W=" + localPrefs.formatDouble(w));
	                            if (!localPrefs.writeTransSizeInLambda && !st090laytrans) infstr.append("U");
	                        }
	                    }
	                	foundWid = Double.valueOf(w);
	                } else
	                {
	                    // get gate length subtraction in lambda
	                    if (size.getDoubleWidth() == 0 && size.getWidth() instanceof String)
	                        infstr.append(" W="+formatParam((String)size.getWidth(), TextDescriptor.Unit.DISTANCE, false));
	                }
                }

				// write area if appropriate
				if (fun != PrimitiveNode.Function.TRANMOS && fun != PrimitiveNode.Function.TRA4NMOS &&
					fun != PrimitiveNode.Function.TRAPMOS && fun != PrimitiveNode.Function.TRA4PMOS &&
					fun != PrimitiveNode.Function.TRADMOS && fun != PrimitiveNode.Function.TRA4DMOS &&
					fun != PrimitiveNode.Function.TRANMOSCN && fun != PrimitiveNode.Function.TRA4NMOSCN &&
					fun != PrimitiveNode.Function.TRAPMOSCN && fun != PrimitiveNode.Function.TRA4PMOSCN)
				{
					if (foundLen != null && foundWid != null)
					{
						infstr.append(" AREA=" + localPrefs.formatDouble(foundLen.doubleValue()*foundWid.doubleValue()));
						if (!localPrefs.writeTransSizeInLambda) infstr.append("P");
					}
				}
			}

			// make sure transistor is connected to nets
			SpiceNet spNetGate = spiceNetMap.get(gateNet);
			SpiceNet spNetSource = spiceNetMap.get(sourceNet);
			SpiceNet spNetDrain = spiceNetMap.get(drainNet);
			if (spNetGate == null || spNetSource == null || spNetDrain == null) continue;

			// compute area of source and drain
			if (!useCDL)
			{
				if (fun == PrimitiveNode.Function.TRANMOS || fun == PrimitiveNode.Function.TRA4NMOS ||
					fun == PrimitiveNode.Function.TRAPMOS || fun == PrimitiveNode.Function.TRA4PMOS ||
					fun == PrimitiveNode.Function.TRADMOS || fun == PrimitiveNode.Function.TRA4DMOS)
				{
					double as = 0, ad = 0, ps = 0, pd = 0;
					if (spNetSource.transistorCount != 0)
					{
						as = spNetSource.diffArea / spNetSource.transistorCount;
						ps = spNetSource.diffPerim / spNetSource.transistorCount;
						if (!localPrefs.writeTransSizeInLambda)
						{
							as *= layoutTechnology.getScale() * layoutTechnology.getScale() / 1000000.0;
							ps *= layoutTechnology.getScale() / 1000.0;
						}
					}
					if (spNetDrain.transistorCount != 0)
					{
						ad = spNetDrain.diffArea / spNetDrain.transistorCount;
						pd = spNetDrain.diffPerim / spNetDrain.transistorCount;
						if (!localPrefs.writeTransSizeInLambda)
						{
							ad *= layoutTechnology.getScale() * layoutTechnology.getScale() / 1000000.0;
							pd *= layoutTechnology.getScale() / 1000.0;
						}
					}
					if (as > 0.0)
					{
						infstr.append(" AS=" + localPrefs.formatDouble(as));
						if (!localPrefs.writeTransSizeInLambda && !st090laytrans) infstr.append("P");
					}
					if (ad > 0.0)
					{
						infstr.append(" AD=" + localPrefs.formatDouble(ad));
						if (!localPrefs.writeTransSizeInLambda && !st090laytrans) infstr.append("P");
					}
					if (ps > 0.0)
					{
						infstr.append(" PS=" + localPrefs.formatDouble(ps));
						if (!localPrefs.writeTransSizeInLambda && !st090laytrans) infstr.append("U");
					}
					if (pd > 0.0)
					{
						infstr.append(" PD=" + localPrefs.formatDouble(pd));
						if (!localPrefs.writeTransSizeInLambda && !st090laytrans) infstr.append("U");
					}
				}
			}

			// Writing MFactor if available.
			writeMFactor(context, ni, infstr);

			infstr.append("\n");
			multiLinePrint(false, infstr.toString());
		}

		// print resistances and capacitances
		if (segmentedNets != null)
		{
			if (spLevel == SimulationTool.SpiceParasitics.RC_PROXIMITY)
			{
				parasiticInfo.writeNewSpiceCode(cell, cni, layoutTechnology, this);
			} else {
				// write caps
				int capCount = 0;
				multiLinePrint(true, "** Extracted Parasitic Capacitors ***\n");
		        for (SpiceSegmentedNets.NetInfo netInfo : segmentedNets.getUniqueSegments()) {
	                if (netInfo.getCap() > cell.getTechnology().getMinCapacitance()) {
	                    if (netInfo.getName().equals("gnd")) continue;           // don't write out caps from gnd to gnd
	                    multiLinePrint(false, "C" + capCount + " " + netInfo.getName() + " 0 " + localPrefs.formatDouble(netInfo.getCap()) + "fF\n");
	                    capCount++;
	                }
	            }

	            // write resistors
	            int resCount = 0;
	            multiLinePrint(true, "** Extracted Parasitic Resistors ***\n");
	            for (Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); ) {
	                ArcInst ai = it.next();
	                Double res = segmentedNets.getRes(ai);
	                if (res == null) continue;
	                String n0 = segmentedNets.getNetName(ai.getHeadPortInst());
	                String n1 = segmentedNets.getNetName(ai.getTailPortInst());
	                int arcPImodels = SpiceSegmentedNets.getNumPISegments(res.doubleValue(), layoutTechnology.getMaxSeriesResistance());
	                if (arcPImodels > 1) {
	                    // have to break it up into smaller pieces
	                    double segCap = segmentedNets.getArcCap(ai)/(arcPImodels+1);
	                    double segRes = res.doubleValue()/arcPImodels;
	                    String segn0 = n0;
	                    String segn1 = n0;
	                    for (int i=0; i<arcPImodels; i++) {
	                        segn1 = n0 + "##" + i;
	                        // print cap on intermediate node
	                        if (i == (arcPImodels-1))
	                            segn1 = n1;

	                        multiLinePrint(false, "R"+resCount+" "+segn0+" "+segn1+" "+localPrefs.formatDouble(segRes)+"\n");
	                        resCount++;
	                        if (i < (arcPImodels-1)) {
	                            if (!segn1.equals("gnd") && segCap > layoutTechnology.getMinCapacitance()) {
	                                String capVal = localPrefs.formatDouble(segCap);
	                                if (!capVal.equals("0.00")) {
	                                    multiLinePrint(false, "C"+capCount+" "+segn1+" 0 "+capVal+"fF\n");
	                                    capCount++;
	                                }
	                            }
	                        }
	                        segn0 = segn1;
	                    }
	                } else {
	                    multiLinePrint(false, "R" + resCount + " " + n0 + " " + n1 + " " + localPrefs.formatDouble(res.doubleValue()) + "\n");
	                    resCount++;
	                }
	            }
            }
		}

		// write out any directly-typed SPICE cards
		if (!useCDL)
		{
			boolean firstDecl = true;
			for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
			{
				NodeInst ni = it.next();
				if (ni.getProto() != Generic.tech().invisiblePinNode) continue;
				Variable cardVar = ni.getVar(SPICE_CARD_KEY);
				if (cardVar == null) continue;
				if (firstDecl)
				{
					firstDecl = false;
					multiLinePrint(true, "\n* Spice Code nodes in cell " + cell + "\n");
				}
				emitEmbeddedSpice(cardVar, context, segmentedNets, info, false, forceEval);
			}
		}

        // finally, if this is the top level,
        // write out some very small resistors between any networks
        // that are asserted will be connected by the NCC annotation "exportsConnectedByParent"
        // this should really be done internally in the network tool with a switch, but
        // this hack works here for now
        NccCellAnnotations anna = NccCellAnnotations.getAnnotations(cell);
        if (cell == topCell && anna != null) {
            // each list contains all name patterns that are be shorted together
            if (anna.getExportsConnected().hasNext()) {
                multiLinePrint(true, "\n*** Exports shorted due to NCC annotation 'exportsConnectedByParent':\n");
            }

            for (Iterator<List<NamePattern>> it = anna.getExportsConnected(); it.hasNext(); ) {
                List<NamePattern> list = it.next();
                List<Network> netsToConnect = new ArrayList<Network>();
                // each name pattern can match any number of exports in the cell
                for (NccCellAnnotations.NamePattern pat : list) {
                    for (Iterator<PortProto> it3 = cell.getPorts(); it3.hasNext(); ) {
                        Export e = (Export)it3.next();
                        String name = e.getName();
                        // keep track of networks to short together
                        if (pat.matches(name)) {
                            Network net = netList.getNetwork(e, 0);
                            if (!netsToConnect.contains(net))
                                netsToConnect.add(net);
                        }
                    }
                }
                // connect all nets in list of nets to connect
                String name = null;
                for (Network net : netsToConnect) {
                    if (name != null) {
                        multiLinePrint(false, "R"+name+" "+name+" "+net.getName()+" 0.001\n");
                    }
                    name = net.getName();
                }
            }
        }

		// now we're finished writing the subcircuit.
		if (cell != topCell || useCDL || localPrefs.writeSubcktTopCell)
		{
			multiLinePrint(false, ".ENDS " + cni.getParameterizedName() + "\n");
		}
        if (cell == topCell && localPrefs.writeSubcktTopCell) {
            multiLinePrint(false, "\n\n"+topLevelInstance+"\n\n");
        }
	}

    /****************************** PARAMETERS ******************************/

	/**
	 * Method to create a parameterized name for nodable.
	 * If the node is not parameterized, returns zero.
	 * If it returns a name, that name must be deallocated when done.
	 */
	protected String parameterizedName(Nodable no, VarContext context)
	{
		Cell cell = (Cell)no.getProto();
		String uniqueName = getUniqueCellName(cell);
		if (uniqueName == null)
		{
			uniqueName = cell.getName();
			String msg = "Cell " + cell.describe(true) + " is missing information. Corresponding schematic might be missing";
			msg += " Taking '" + uniqueName + "' now.";
			dumpMessage(msg, true);
		}
		StringBuilder uniqueCellName = new StringBuilder(uniqueName);

		if (spiceEngine != SimulationTool.SpiceEngine.SPICE_ENGINE_JO &&
			uniquifyCells.get(cell) != null && modelOverrides.get(cell) == null)
		{
			// if this cell is marked to be make unique, make a unique name out of the var context
			VarContext vc = context.push(no);
			uniqueCellName.append("_"+vc.getInstPath("."));
		} else
		{
			boolean useCellParams = !useCDL && localPrefs.useCellParameters;
			if (canParameterizeNames() && no.isCellInstance() && !SCLibraryGen.isStandardCell(cell))
			{
				// if there are parameters, append them to this name
				Set<Variable.Key> spiceParams = detectSpiceParams(cell);
				List<Variable> paramValues = new ArrayList<Variable>();
				for(Iterator<Variable> it = no.getParameters(); it.hasNext(); )
				{
					Variable var = it.next();
					if (spiceEngine != SimulationTool.SpiceEngine.SPICE_ENGINE_JO)
					{
						if (DETECT_SPICE_PARAMS && !spiceParams.contains(var.getKey())) continue;
						if (USE_JAVA_CODE)
						{
							if (useCellParams && !spiceParams.contains(null)) continue;
						} else
						{
							if (useCellParams && !var.isJava()) continue;
						}
					}
					paramValues.add(var);
				}
				for(Variable var : paramValues)
				{
					String eval = var.describe(context, no);
					if (eval == null) continue;
					uniqueCellName.append("-" + eval.toString());
				}
			}
		}

		uniqueCellName = getUniqueCellName(uniqueCellName);
//		// if it is over the length limit, truncate it
//		int limit = maxNameLength();
//		if (limit > 0 && uniqueCellName.length() > limit)
//		{
//			Integer i = uniqueNames.get(uniqueCellName.toString());
//			if (i == null) {
//				i = Integer.valueOf(uniqueID);
//				uniqueID++;
//				uniqueNames.put(uniqueCellName.toString(), i);
//			}
//			int numOfCharactersToDelete = uniqueCellName.length() - limit + 10;
//			StringBuilder uniqueCellName2 = new StringBuilder(uniqueCellName.delete(0, numOfCharactersToDelete));
//			uniqueCellName = uniqueCellName.delete(limit-10, uniqueCellName.length());
//			uniqueCellName.append("-ID"+i);
//		}

		// make it safe
		return getSafeCellName(uniqueCellName.toString());
	}

    /**
     * Method to determine if the cell name must be truncated due to character limit.
     * If yes, function will remove the first characters of the name string as
     * variations of the same cell would differ in the last characters.
     * @param uniqueCellName StringBuilder representing the nname
     * @return String
     */
    private StringBuilder getUniqueCellName(StringBuilder uniqueCellName)
    {
        // if it is over the length limit, truncate it
        int limit = maxNameLength();
        if (limit > 0 && uniqueCellName.length() > limit)
        {
            Integer i = uniqueNames.get(uniqueCellName.toString());
            if (i == null) {
                i = Integer.valueOf(uniqueID);
                uniqueID++;
                uniqueNames.put(uniqueCellName.toString(), i);
            }
            int numOfCharactersToDelete = uniqueCellName.length() - limit + 10;
            uniqueCellName = uniqueCellName.delete(0, numOfCharactersToDelete);
            uniqueCellName.append("-ID"+i);
        }
        return uniqueCellName;
    }

    /**
     * Method to return the CodeExpression on a Variable.
     * Prints warnings if there is a syntax error in the code.
     * @param eObj the ElectricObject that holds the Variable.
     * @param var the Variable.
     * @return the CodeExpression object on the Variable.
     */
    private CodeExpression getCodeExpression(ElectricObject eObj, Variable var)
    {
        CodeExpression ce = var.getCodeExpression();
        if (ce != null)
        {
        	String parseExceptionDesc = var.describeParseException(eObj);
        	if (parseExceptionDesc != null)
                System.out.println("WARNING: " + parseExceptionDesc);
        }
        return ce;
    }

    /**
     * Returns true if variable can be netlisted as a spice parameter (unevaluated expression).
     * If not, it should be evaluated before netlisting.
     * @param var the var to check
     * @return true if the variable should be netlisted as a spice parameter
     */
    private boolean isNetlistableParam(ElectricObject eObj, Variable var)
    {
        if (USE_JAVA_CODE)
        {
            CodeExpression ce = getCodeExpression(eObj, var);
            if (ce == null) return true;
            if (ce.getHSpiceText(localPrefs.getUnitsOfPrecision(), false) != null) return true;
            return false;
        }
        if (var.isJava()) return false;
        return true;
    }

    /**
     * Returns text representation of instance parameter
     * on specified Nodable in specified VarContext
     * The text representation can be either spice parameter expression or
     * a value evaluated in specified VarContext
     * @param context specified VarContext
     * @param no specified Nodable
     * @param instParam instance parameter
     * @param forceEval true to always evaluate parameter
     * @return true if the variable should be netlisted as a spice parameter
     */
    private String evalParam(VarContext context, Nodable no, Variable instParam, boolean forceEval)
    {
        return evalParam(context, no, instParam, forceEval, false, false);
    }

    /**
     * Returns text representation of instance parameter
     * on specified Nodable in specified VarContext
     * The text representation can be either spice parameter expression or
     * a value evaluated in specified VarContext
     * @param context specified VarContext
     * @param no specified Nodable
     * @param instParam instance parameter
     * @param forceEval true to always evaluate parameter
     * @param wrapped true if the string is already "wrapped" (with parenthesis) and does not need to have quotes around it.
     * @return true if the variable should be netlisted as a spice parameter
     */
    private String evalParam(VarContext context, Nodable no, Variable instParam, boolean forceEval, boolean inPars, boolean wrapped) {
        assert USE_JAVA_CODE;
        Object obj = null;
        if (!forceEval) {
            CodeExpression ce = getCodeExpression(no.getNodeInst(), instParam);
            if (ce != null)
                obj = ce.getHSpiceText(localPrefs.getUnitsOfPrecision(), inPars);
        }
        if (obj == null)
            obj = context.evalVar(instParam, no);

        if (obj instanceof Number)
            obj = localPrefs.formatDoublePostFix(((Number)obj).doubleValue());
        return formatParam(String.valueOf(obj), instParam.getUnit(), wrapped);
    }

    private Set<Variable.Key> detectSpiceParams(NodeProto np)
    {
        Set<Variable.Key> params = allSpiceParams.get(np);
        if (params != null) return params;
        if (np instanceof PrimitiveNode) {
            params = getImportantVars((PrimitiveNode)np);
//            if (!params.isEmpty())
//                printlnParams(pn + " depends on:", params);
        } else {
            Cell cell = (Cell)np;
            params = getTemplateVars(cell);
            if (params == null) {
                if (cell.isIcon() && cell.contentsView() != null) {
                    Cell schCell = cell.contentsView();
                    params = detectSpiceParams(schCell);
//                    printlnParams(cell + " inherits from " + schCell, params);
                } else {
                    params = new HashSet<Variable.Key>();
                    boolean uniquify = false;
                    for (Iterator<NodeInst> nit = cell.getNodes(); nit.hasNext();) {
                        NodeInst ni = nit.next();
                        if (ni.isIconOfParent()) continue;
                        Set<Variable.Key> protoParams = detectSpiceParams(ni.getProto());
                        if (protoParams == UNIQUIFY_MARK)
                            uniquify = true;
                        for (Variable.Key protoParam: protoParams) {
                            Variable var = ni.getParameterOrVariable(protoParam);
                            if (var == null) continue;
                            CodeExpression ce = getCodeExpression(ni, var);
                            if (ce == null) continue;
                            if (!isNetlistableParam(ni, var)) uniquify = true;
                            Set<Variable.Key> depends = ce.dependsOn();
                            params.addAll(depends);
//                            if (!depends.isEmpty())
//                                printlnParams("\t" + ni + " added", depends);
                        }
                        if (ni.getProto() == Generic.tech().invisiblePinNode) {
                            findVarsInTemplate(ni.getVar(Spice.SPICE_DECLARATION_KEY), cell, false, params);
                            findVarsInTemplate(ni.getVar(Spice.SPICE_CARD_KEY), cell, false, params);
                        }
                    }
                    if (uniquify/* && USE_UNIQUIFY_MARK*/)
                        params = UNIQUIFY_MARK;
//                    printlnParams(cell + " collected params", params);
                }
            }
        }
        allSpiceParams.put(np, params);
        return params;
    }

    /**
     * Method to tell which Variables are important for primitive node in this netlister
     * @param pn primitive node to tell
     * @return a set of important variables or null if all variables may be important
     */
    protected Set<Variable.Key> getImportantVars(PrimitiveNode pn)
    {
        Set<Variable.Key> importantVars = new HashSet<Variable.Key>();

        // look for a SPICE template on the primitive
        String line = pn.getSpiceTemplate();
        if (line != null) {
            findVarsInLine(line, pn, true, importantVars);
            // Writing MFactor if available. Not sure here
            // No, this causes problems because "ATTR_M" does not actually exist on prototype
            //importantVars.add(SimulationTool.M_FACTOR_KEY);
            return importantVars;
        }

        // handle resistors, inductors, capacitors, and diodes
        PrimitiveNode.Function fun = pn.getFunction();
        if (fun.isResistor()) {
            importantVars.add(Schematics.SCHEM_RESISTANCE);
        } else if (fun.isCapacitor()) {
            importantVars.add(Schematics.SCHEM_CAPACITANCE);
        } else if (fun == PrimitiveNode.Function.INDUCT) {
            importantVars.add(Schematics.SCHEM_INDUCTANCE);
        } else if (fun == PrimitiveNode.Function.DIODE || fun == PrimitiveNode.Function.DIODEZ) {
            importantVars.add(Schematics.SCHEM_DIODE);
        } else if (pn.getGroupFunction() == PrimitiveNode.Function.TRANS) {
            // model information
            importantVars.add(SPICE_MODEL_KEY);

            // compute length and width (or area for nonMOS transistors)
            if (pn.getTechnology() instanceof Schematics) {
                importantVars.add(Schematics.ATTR_LENGTH);
                importantVars.add(Schematics.ATTR_WIDTH);
                importantVars.add(Schematics.ATTR_AREA);
            } else {
                importantVars.add(NodeInst.TRACE);
            }
            importantVars.add(SimulationTool.M_FACTOR_KEY);
        } else if (pn == Generic.tech().invisiblePinNode) {
            importantVars.add(Spice.SPICE_DECLARATION_KEY);
            importantVars.add(Spice.SPICE_CARD_KEY);
        }
        return importantVars;
    }

    /****************************** TEMPLATE WRITING ******************************/

	private void emitEmbeddedSpice(Variable cardVar, VarContext context, SpiceSegmentedNets segNets, HierarchyEnumerator.CellInfo info, boolean flatNetNames, boolean forceEval)
	{
		Object obj = cardVar.getObject();
		if (!(obj instanceof String) && !(obj instanceof String[])) return;
		if (!cardVar.isDisplay()) return;
		if (obj instanceof String)
		{
	        StringBuilder buf = replacePortsAndVars((String)obj, context.getNodable(), context.pop(), null, segNets, info, flatNetNames, forceEval);
			buf.append('\n');
			String msg = buf.toString();
			boolean isComment = false;
			if (msg.startsWith("*")) isComment = true;
			multiLinePrint(isComment, msg);
		} else
		{
			String [] strings = (String [])obj;
			for(int i=0; i<strings.length; i++)
			{
	            StringBuilder buf = replacePortsAndVars(strings[i], context.getNodable(), context.pop(), null, segNets, info, flatNetNames, forceEval);
				buf.append('\n');
				String msg = buf.toString();
				boolean isComment = false;
				if (msg.startsWith("*")) isComment = true;
				multiLinePrint(isComment, msg);
	        }
		}
	}

    /**
     * Method to determine which Spice engine is being targeted and to get the proper
     * template variable.
     * Looks in associated icon/schematics cells to find it.
     * @param cell the Cell being netlisted.
     * @return a Variable with the proper Spice template (null if none).
     */
    private Variable getEngineTemplate(Cell cell)
    {
        Variable varTemplate = getEngineTemplateJustThis(cell);

        // try associated icon/schematic if no template found
        if (varTemplate == null)
        {
        	if (cell.isIcon())
        	{
        		Cell schCell = cell.contentsView();
        		if (schCell != null)
        	        varTemplate = getEngineTemplateJustThis(schCell);
        	} else
        	{
        		Cell iconCell = cell.iconView();
        		if (iconCell != null)
        	        varTemplate = getEngineTemplateJustThis(iconCell);
        	}
        }
        return varTemplate;
    }

    /**
     * Method to determine which Spice engine is being targeted and to get the proper
     * template variable.
     * @param cell the Cell being netlisted.
     * @return a Variable with the proper Spice template (null if none).
     */
    private Variable getEngineTemplateJustThis(Cell cell)
    {
        Variable varTemplate = cell.getVar(preferedEngineTemplateKey);

        // If not looking for the default spice templates, and special one is not found, try default
        if (varTemplate == null && preferedEngineTemplateKey != SPICE_TEMPLATE_KEY)
            varTemplate = cell.getVar(SPICE_TEMPLATE_KEY);
        return varTemplate;
    }

    private Set<Variable.Key> getTemplateVars(Cell cell)
    {
        Variable varTemplate = getEngineTemplate(cell);
        if (varTemplate == null) return null;
        Set<Variable.Key> depends = new HashSet<Variable.Key>();
        findVarsInTemplate(varTemplate, cell, true, depends);
        return depends;
    }

    private void findVarsInTemplate(Variable varTemplate, NodeProto cell, boolean isPortReplacement, Set<Variable.Key> vars)
    {
        if (varTemplate == null) return;
        Object value = varTemplate.getObject();
        if (value instanceof Object[])
        {
            for (Object o : ((Object[]) value))
            {
                findVarsInLine(o.toString(), cell, isPortReplacement, vars);
            }
        } else
        {
            findVarsInLine(value.toString(), cell, isPortReplacement, vars);
        }
    }

    /**
     * Replace ports and variables in 'line'.  Ports and Variables should be
     * referenced via $(name)
     * @param line the string to search and replace within
     * @param no the nodable up the hierarchy that has the parameters on it
     * @param context the context of the nodable
     * @param cni the cell net info of cell in which the nodable exists
     * (if null, no port name replacement will be done)
     * @param segNets
     * @param info
     * @param flatNetNames
     * @param forceEval always evaluate parameters to numbers
     * @return the modified line
     */
    private StringBuilder replacePortsAndVars(String line, Nodable no, VarContext context,
                                       CellNetInfo cni, SpiceSegmentedNets segNets,
                                       HierarchyEnumerator.CellInfo info, boolean flatNetNames, boolean forceEval) {
    	StringBuilderQuoteParity infstr = new StringBuilderQuoteParity();
        NodeProto prototype = null;
    	PrimitiveNode prim = null;
    	if (no != null)
    	{
    		prototype = no.getProto();
    		if (prototype instanceof PrimitiveNode) prim = (PrimitiveNode)prototype;
    	}

        for(int pt = 0; pt < line.length(); pt++)
        {
        	// see if the character is part of a substitution expression
            char chr = line.charAt(pt);
            if (chr != '$' || pt+1 >= line.length() || line.charAt(pt+1) != '(')
            {
            	// not part of substitution: just emit it
                infstr.append(chr);
                continue;
            }

            // may be part of substitution expression: look for closing parenthesis
            int start = pt + 2;
            for(pt = start; pt < line.length(); pt++)
                if (line.charAt(pt) == ')') break;

            // do the parameter substitution
            String paramName = line.substring(start, pt);

            PortProto pp = null;
            int ppIndex = -1;
            if (prototype != null)
            {
            	if (prototype instanceof Cell)
            	{
            		Cell subCell = (Cell)prototype;
            		Netlist nl = subCell.getNetlist();
            		for(Iterator<Export> it = subCell.getExports(); it.hasNext(); )
            		{
            			Export e = it.next();
            			int width = nl.getBusWidth(e);
            			for(int i=0; i<width; i++)
            			{
            				Network net = nl.getNetwork(e, i);
                			for(Iterator<String> nIt = net.getNames(); nIt.hasNext(); )
                			{
                				String netName = nIt.next();
                				if (netName.equals(paramName))
                				{
                					pp = e;
                					ppIndex = i;
                					break;
                				}
                			}
                			if (ppIndex >= 0) break;
            			}
            			if (ppIndex >= 0) break;
            		}
            	} else
            	{
            		pp = prototype.findPortProto(paramName);
            	}
            }
            Variable.Key varKey;

            if (paramName.equalsIgnoreCase("node_name") && no != null)
            {
            	String nodeName = getSafeNetName(no.getName(), false);
//            	nodeName = nodeName.replaceAll("[\\[\\]]", "_");
                infstr.append(nodeName);
            } else if (paramName.equalsIgnoreCase("width") && prim != null)
            {
            	NodeInst ni = (NodeInst)no;
                PrimitiveNodeSize npSize = ni.getPrimitiveDependentNodeSize(context);
            	if (npSize != null) infstr.append(npSize.getWidth().toString());
            } else if (paramName.equalsIgnoreCase("length") && prim != null)
            {
            	NodeInst ni = (NodeInst)no;
                PrimitiveNodeSize npSize = ni.getPrimitiveDependentNodeSize(context);
                if (npSize != null) infstr.append(npSize.getLength().toString());
            } else if (cni != null && pp != null)
            {
                // port name found: use its spice node
            	if (ppIndex < 0) ppIndex = 0;
                Network net = cni.getNetList().getNetwork(no, pp, ppIndex);
                CellSignal cs = cni.getCellSignal(net);
                String portName = getPortName(cs);
                if (segNets != null) {
                    PortInst pi = no.getNodeInst().findPortInstFromEquivalentProto(pp);
                    portName = segNets.getNetName(pi);
                }
                if (flatNetNames) {
                    portName = info.getUniqueNetName(net, ".");
                }
				if (cs.isGlobal() && spiceEngine == SimulationTool.SpiceEngine.SPICE_ENGINE_XYCE) {
					infstr.append("$" + portName);
				} else {
					infstr.append(portName);
				}
            } else if (no != null && (varKey = Variable.findKey("ATTR_" + paramName)) != null)
            {
                // no port name found, look for variable name
                Variable attrVar = null;
                //Variable.Key varKey = Variable.findKey("ATTR_" + paramName);
                if (varKey != null) {
                    attrVar = no.getParameterOrVariable(varKey);
                }
                if (attrVar == null) infstr.append("??"); else
                {
                    String pVal = "?";
                    Variable parentVar = attrVar;
                    if (prototype != null && prototype instanceof Cell)
                        parentVar = ((Cell)prototype).getParameterOrVariable(attrVar.getKey());
                    if (USE_JAVA_CODE) {
                        pVal = evalParam(context, no, attrVar, forceEval, true, infstr.inParens());
                        if (infstr.inQuotes()) pVal = trimSingleQuotes(pVal);
                    } else {
                        if (!useCDL && localPrefs.useCellParameters &&
                                parentVar.getCode() == CodeExpression.Code.SPICE) {
                            Object obj = context.evalSpice(attrVar, false);
                            if (obj != null)
                                pVal = obj.toString();
                        } else {
                            pVal = String.valueOf(context.evalVar(attrVar, no));
                        }
//                    if (attrVar.getCode() != TextDescriptor.Code.NONE)
                        if (infstr.inQuotes()) pVal = trimSingleQuotes(pVal); else
                            pVal = formatParam(pVal, attrVar.getUnit(), infstr.inParens());
                    }
                    infstr.append(pVal);
                }
            } else {
                // look for the network name
                boolean found = false;
                String hierName = null;
                String [] names = paramName.split("\\.");
                if (names.length > 1 && flatNetNames) {
                    // hierarchical name, down hierarchy
                    Netlist thisNetlist = info.getNetlist();
                    VarContext thisContext = context;
                    if (no != null) {
                        // push it back on, it got popped off in "embedSpice..."
                        thisContext = thisContext.push(no);
                    }
                    for (int i=0; i<names.length-1; i++) {
                        boolean foundno = false;
                        for (Iterator<Nodable> it = thisNetlist.getNodables(); it.hasNext(); ) {
                            Nodable subno = it.next();
                            if (subno.getName().equals(names[i])) {
                                if (subno.getProto() instanceof Cell) {
                                    thisNetlist = thisNetlist.getNetlist(subno);
                                    thisContext = thisContext.push(subno);
                                }
                                foundno = true;
                                continue;
                            }
                        }
                        if (!foundno) {
                            System.out.println("Unable to find "+names[i]+" in "+paramName);
                            break;
                        }
                    }
                    Network net = findNet(thisNetlist, names[names.length-1]);
                    if (net != null) {
                        HierarchyEnumerator.NetNameProxy proxy = new HierarchyEnumerator.NetNameProxy(
                                thisContext, ".x", net);
                        Global g = getGlobal(proxy.getNet());
                        if (g != null)
                            hierName = g.getName();
                        else
                            hierName = proxy.toString();
                    }

                } else {
                    // net may be exported and named at higher level, use getUniqueName
                    Network net = findNet(info.getNetlist(), paramName);
                    if (net != null) {
                        if (flatNetNames) {
                            HierarchyEnumerator.NetNameProxy proxy = info.getUniqueNetNameProxy(net, ".x");
                            Global g = getGlobal(proxy.getNet());
                            if (g != null)
                                hierName = g.getName();
                            else
                                hierName = proxy.toString();
                        } else {
                            hierName = getPortName(cni.getCellSignal(net));
                        }
                    }
                }

                // convert to spice format
                if (hierName != null) {
                    if (flatNetNames) {
                        if (hierName.indexOf(".x") > 0) {
                            hierName = "x"+hierName;
                        }
                        // remove x in front of net name
                        int i = hierName.lastIndexOf(".x");
                        if (i > 0)
                            hierName = hierName.substring(0, i+1) + hierName.substring(i+2);
                        else {
                            i = hierName.lastIndexOf("."+paramName);
                            if (i > 0) {
                                hierName = hierName.substring(0, i) + "_" + hierName.substring(i+1);
                            }
                        }
                    }
                    infstr.append(hierName);
                    found = true;
                }
                if (!found) {
                    System.out.println("Cannot find parameter $("+paramName+") in cell "+
                    	(prototype != null ? prototype.describe(false) : context.getInstPath(".")));
                }
            }
        }
        return infstr.getStringBuilder();
    }

    private static class StringBuilderQuoteParity
    {
    	private StringBuilder sb = new StringBuilder();
    	private int quoteCount;
    	private int parenDepth;

    	void append(String str)
    	{
    		sb.append(str);
    		for(int i=0; i<str.length(); i++)
    		{
    			char ch = str.charAt(i);
    			if (ch == '\'') quoteCount++; else
    			if (ch == '(') parenDepth++; else
    			if (ch == ')') parenDepth--;
    		}
    	}

    	void append(char c)
    	{
    		sb.append(c);
    		if (c == '\'') quoteCount++; else
			if (c == '(') parenDepth++; else
			if (c == ')') parenDepth--;
    	}

    	boolean inQuotes() { return (quoteCount&1) != 0; }

    	boolean inParens() { return parenDepth > 0; }

    	StringBuilder getStringBuilder() { return sb; }
    }

    /**
     * Find variables in 'line'.  Ports and variables should be referenced via $(name)
     * @param line the string to search and replace within
     * @param prototype of instance that has the parameters on it
     * @param isPortReplacement true if port name replacement will be done)
     */
    private void findVarsInLine(String line, NodeProto prototype, boolean isPortReplacement, Set<Variable.Key> vars) {
    	PrimitiveNode prim = null;
    	if (prototype != null) {
    		if (prototype instanceof PrimitiveNode) prim = (PrimitiveNode)prototype;
    	}

        for(int pt = 0; pt < line.length(); pt++) {
        	// see if the character is part of a substitution expression
            char chr = line.charAt(pt);
            if (chr != '$' || pt+1 >= line.length() || line.charAt(pt+1) != '(') {
            	// not part of substitution: just emit it
                continue;
            }

            // may be part of substitution expression: look for closing parenthesis
            int start = pt + 2;
            for(pt = start; pt < line.length(); pt++)
                if (line.charAt(pt) == ')') break;

            // do the parameter substitution
            String paramName = line.substring(start, pt);

            PortProto pp = null;
            if (prototype != null) {
                pp = prototype.findPortProto(paramName);
            }
            Variable.Key varKey;

            if (paramName.equalsIgnoreCase("node_name") && prototype != null) continue;
            if (paramName.equalsIgnoreCase("width") && prim != null) continue;
            if (paramName.equalsIgnoreCase("length") && prim != null) continue;
            if (isPortReplacement && pp != null) continue;
            if (prototype != null && (varKey = Variable.findKey("ATTR_" + paramName)) != null) {
                if (prototype instanceof PrimitiveNode || ((Cell) prototype).getParameterOrVariable(varKey) != null)
                    vars.add(varKey);
            }
        }
    }

    private Network findNet(Netlist netlist, String netName)
    {
        Network foundnet = null;
        for (Iterator<Network> it = netlist.getNetworks(); it.hasNext(); ) {
            Network net = it.next();
            if (net.hasName(netName)) {
                foundnet = net;
                break;
            }
        }
        return foundnet;
    }

    /**
     * Get the global associated with this net. Returns null if net is
     * not a global.
     * @param net
     * @return global associated with this net, or null if not global
     */
    private Global getGlobal(Network net)
    {
        Netlist netlist = net.getNetlist();
        for (int i=0; i<netlist.getGlobals().size(); i++) {
            Global g = netlist.getGlobals().get(i);
            if (netlist.getNetwork(g) == net)
                return g;
        }
        return null;
    }

    /****************************** SUBCLASSED METHODS FOR THE TOPOLOGY ANALYZER ******************************/

	/**
	 * Method to adjust a cell name to be safe for Spice output.
	 * @param name the cell name.
	 * @return the name, adjusted for Spice output.
	 */
	protected String getSafeCellName(String name)
	{
		return getSafeNetName(name, false);
	}

	/** Method to return the proper name of Power */
	protected String getPowerName(Network net)
	{
		if (net != null)
		{
			// favor "vdd" if it is present
			for(Iterator<String> it = net.getNames(); it.hasNext(); )
			{
				String netName = it.next();
				if (netName.equalsIgnoreCase("vdd")) return "vdd";
			}
		}
		return null;
	}

	/** Method to return the proper name of Ground */
	protected String getGroundName(Network net)
	{
        switch (spiceEngine)
        {
            case SPICE_ENGINE_2:
            case SPICE_ENGINE_P:
            case SPICE_ENGINE_G:
                return "0";
			default:
				break;
        }

		if (net != null)
		{
			// favor "gnd" if it is present
			for(Iterator<String> it = net.getNames(); it.hasNext(); )
			{
				String netName = it.next();
				if (netName.equalsIgnoreCase("gnd")) return "gnd";
			}
		}
		return null;
	}

	/** Method to return the proper name of a Global signal */
	protected String getGlobalName(Global glob) { return glob.getName(); }

    /** Method to report that export names do NOT take precedence over
     * arc names when determining the name of the network. */
    protected boolean isNetworksUseExportedNames() { return false; }

	/** Method to report that library names are NOT always prepended to cell names. */
	protected boolean isLibraryNameAlwaysAddedToCellName() { return true; }

	/** Method to report that aggregate names (busses) are not used. */
	protected boolean isAggregateNamesSupported() { return false; }

	/** Abstract method to decide whether aggregate names (busses) can have gaps in their ranges. */
	protected boolean isAggregateNameGapsSupported() { return false; }

	/** Method to report whether input and output names are separated. */
	protected boolean isSeparateInputAndOutput() { return false; }

	/** Abstract method to decide whether netlister is case-sensitive (Verilog) or not (Spice). */
	protected boolean isCaseSensitive() { return false; }

	/** Method to decide whether signal sorting supports ascending/descending order */
	protected boolean doesSupportDescAsc() { return localPrefs.supportAscDescOrdering; }

    /**
	 * Method to adjust a network name to be safe for Spice output.
	 * Spice has a list of legal punctuation characters that it allows.
	 */
	protected String getSafeNetName(String name, boolean bus)
	{
        return getSafeNetName(name, legalSpiceChars, spiceEngine);
    }

    /**
     * Method called during hierarchy traversal.
     * Called at the end of the enter cell phase.
     */
    protected void enterCell(HierarchyEnumerator.CellInfo info)
    {
        if (exemptedNets != null)
            exemptedNets.setExemptedNets(info);
    }

    /** Method to report that not to choose best export name among exports connected to signal. */
    protected boolean isChooseBestExportName() { return false; }

    /** If the netlister has requirements not to netlist certain cells and their
     * subcells, override this method.
     * If this cell has a spice template, skip it
     */
    protected boolean skipCellAndSubcells(Cell cell)
	{
		// skip if there is a template
        Variable varTemplate = null;
        varTemplate = getEngineTemplate(cell);
        if (varTemplate != null) return true;

		// look for a model file for the current cell, can come from preference or on cell
        String fileName = null;
        String unfilteredFileName = localPrefs.modelFiles.get(cell);
        if (CellModelPrefs.isUseModelFromFile(unfilteredFileName)) {
            fileName = CellModelPrefs.getModelFile(unfilteredFileName);
        }
        varTemplate = cell.getVar(SPICE_NETLIST_FILE_KEY);
        if (varTemplate != null) {
            Object obj = varTemplate.getObject();
            if (obj instanceof String) {
                String str = (String)obj;
                if (!str.equals("") && !str.equals("*Undefined"))
                    fileName = str;
            }
        }
        if ((fileName != null && !localPrefs.ignoreModelFiles) || CellModelPrefs.isUseVerilogView(unfilteredFileName)) {
            if (!modelOverrides.containsKey(cell))
            {
                String absFileName = fileName;
                boolean isVerilog = false;
                if (absFileName == null)
                {
                	// referring to a Verilog view
                    Cell verilogCell = cell.otherView(View.VERILOG);
                    if (verilogCell == null)
                    {
                    	System.out.println("Verilog cell for " + cell.describe(false) + " requested but it does not exist");
                    	return false;
                    }
                    File spiceFile = new File(filePath);
                    File verilogFile = new File(spiceFile.getParent(), cell.getName() + ".va");
                    fileName = absFileName = verilogFile.getPath();
                	try
                	{
                		PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
                        String lines[] = verilogCell.getTextViewContents();
                        for(int i=0; i<lines.length; i++)
                    		printWriter.println(lines[i]);
                		printWriter.close();
                	} catch (IOException e)
                	{
                		System.out.println("Error writing Verilog file");
                	}
                    isVerilog = true;
                } else
                {
                	// referring to a model file
	                if (!fileName.startsWith("/") && !fileName.startsWith("\\")) {
	                    File spiceFile = new File(filePath);
	                    absFileName = (new File(spiceFile.getParent(), fileName)).getPath();
	                }
                }
                boolean alreadyIncluded = false;
                for (String includeFile : modelOverrides.values()) {
                    if (absFileName.equals(includeFile))
                        alreadyIncluded = true;
                }
                if (alreadyIncluded) {
                    multiLinePrint(true, "\n* " + cell + " is described in this file:\n");
                    multiLinePrint(true, "* "+fileName+" (already included) \n");
                } else {
                    addIncludeFile(fileName, cell.describe(false), isVerilog);
                }
                modelOverrides.put(cell, absFileName);
            }
            return true;
        }

        return false;
    }

	/**
	 * Since the Spice netlister should write a separate copy of schematic cells for each icon,
	 * this override returns true.
	 */
	protected boolean isWriteCopyForEachIcon() { return false; }

    /**
     * Method called when a cell is skipped.
     * Performs any checking to validate that no error occurs.
     */
    protected void validateSkippedCell(HierarchyEnumerator.CellInfo info) {
        String fileName = modelOverrides.get(info.getCell());
        if (fileName != null && !fileName.endsWith(".va")) {
            // validate included file
            SpiceNetlistReader reader = new SpiceNetlistReader();
            try {
                reader.readFile(fileName, false);
                HierarchyEnumerator.CellInfo parentInfo = info.getParentInfo();
                Nodable no = info.getParentInst();
                String parameterizedName = info.getCell().getName();
                if (no != null && parentInfo != null) {
                    parameterizedName = parameterizedName(no, parentInfo.getContext());
                }
                CellNetInfo cni = getCellNetInfo(parameterizedName);
                SpiceSubckt subckt = reader.getSubckt(parameterizedName);
                if (cni != null && subckt != null) {
                    if (subckt == null) {
                        reportError("Error: No subckt for "+parameterizedName+" found in included file: "+fileName);
                    } else {
                        List<String> signals = new ArrayList<String>();
                        for (Iterator<CellSignal> sIt = cni.getCellSignals(); sIt.hasNext(); ) {
                            CellSignal cs = sIt.next();
                            if (ignoreSubcktPort(cs)) continue;
                            signals.add(getPortName(cs));
                        }
                        List<String> subcktSignals = subckt.getPorts();
                        if (signals.size() != subcktSignals.size()) {
                            reportWarning("Warning: wrong number of ports for subckt "+
                                    parameterizedName+": expected "+signals.size()+", but found "+
                                    subcktSignals.size()+", in included file "+fileName);
                        }
                        int len = Math.min(signals.size(), subcktSignals.size());
                        for (int i=0; i<len; i++) {
                            String s1 = signals.get(i);
                            String s2 = subcktSignals.get(i);
                            if (!s1.equalsIgnoreCase(s2)) {
                                reportWarning("Warning: port "+i+" of subckt "+parameterizedName+
                                        " is named "+s1+" in Electric, but "+s2+" in included file "+fileName);
                            }
                        }
                    }
                }
            } catch (FileNotFoundException e) {
                reportError("Error validating included file for cell "+info.getCell().describe(true)+": "+e.getMessage());
            }
        }
    }

    /** Tell the Hierarchy enumerator how to short resistors */
    @Override
    protected Netlist.ShortResistors getShortResistors() {
    	// should use the value of localPrefs.shortResistors
//    	switch (localPrefs.shortResistors)
//    	{
//    		case 0: return Netlist.ShortResistors.NO;
//    		case 1: return Netlist.ShortResistors.PARASITIC;
//    		case 2: return Netlist.ShortResistors.ALL;
//    	}

        // this option is used for writing spice netlists for LVS and RCX (it always returns FALSE)
        if (localPrefs.ignoreParasiticResistors)
            return Netlist.ShortResistors.PARASITIC;
        return Netlist.ShortResistors.NO;
    }

	/**
	 * Method to tell whether the topological analysis should mangle cell names that are parameterized.
	 */
	protected boolean canParameterizeNames() { return true; } //return !useCDL; }

	/**
	 * Method to tell set a limit on the number of characters in a name.
	 * @return the limit to name size (SPICE limits to 32 character names?????).
	 */
	protected int maxNameLength() { if (useCDL) return CDLMAXLENSUBCKTNAME; return SPICEMAXLENSUBCKTNAME; }

    protected boolean enumerateLayoutView(Cell cell) {
        return CellModelPrefs.isUseLayoutView(localPrefs.modelFiles.get(cell));
    }

    private Netlist.ShortResistors getShortResistorsFlat() {
        return Netlist.ShortResistors.ALL;
    }

	/******************** DECK GENERATION SUPPORT ********************/

    /**
     * Method to write M factor information into a given string buffer
     * @param context the context of the nodable for finding M-factors farther up the hierarchy.
     * @param no Nodable representing the node
     * @param infstr Buffer where to write to
     */
    private void writeMFactor(VarContext context, Nodable no, StringBuilder infstr)
    {
        Variable mVar = no.getVar(SimulationTool.M_FACTOR_KEY);
        if (mVar == null) return;
        Object value = context.evalVar(mVar);

        // check for M=@M, and warn user that this is a bad idea, and we will not write it out
        if (mVar.getObject().toString().equals("@M") || (mVar.getObject().toString().equals("P(\"M\")")))
        {
            reportWarning("Warning: M=@M [eval=" + value + "] on " + no.getName() +
            	" is a bad idea, not writing it out: " + context.push(no).getInstPath("."));
            return;
        }

        infstr.append(" M=" + formatParam(value.toString(), TextDescriptor.Unit.NONE, false));
    }

	/**
	 * write a header for "cell" to spice deck "sim_spice_file"
	 * The model cards come from a file specified by tech:~.SIM_spice_model_file
	 * or else tech:~.SIM_spice_header_levelN
	 * The spice model file can be located in el_libdir
	 */
	private void writeHeader(Cell cell)
	{
		// Print the header line for SPICE
		multiLinePrint(true, "*** SPICE deck for cell " + cell.noLibDescribe() +
			" from library " + cell.getLibrary().getName() + "\n");
		emitCopyright("*** ", "");
		if (localPrefs.includeDateAndVersionInOutput)
		{
			multiLinePrint(true, "*** Created on " + TextUtils.formatDate(topCell.getCreationDate()) + "\n");
			multiLinePrint(true, "*** Last revised on " + TextUtils.formatDate(topCell.getRevisionDate()) + "\n");
			multiLinePrint(true, "*** Written on " + TextUtils.formatDate(new Date()) +
				" by Electric VLSI Design System, version " + Version.getVersion() + "\n");
		} else
		{
			multiLinePrint(true, "*** Written by Electric VLSI Design System\n");
		}

        String foundry = layoutTechnology.getSelectedFoundry() == null ? "" : (", foundry "+layoutTechnology.getSelectedFoundry().toString());
        multiLinePrint(true, "*** Layout tech: "+layoutTechnology.getTechName()+foundry+"\n");
        multiLinePrint(true, "*** UC SPICE *** , MIN_RESIST " + layoutTechnology.getMinResistance() +
			", MIN_CAPAC " + layoutTechnology.getMinCapacitance() + "FF\n");
        boolean useParasitics = !useCDL &&
        	localPrefs.parasiticsLevel != SimulationTool.SpiceParasitics.SIMPLE &&
            cell.getView() == View.LAYOUT;
        if (useParasitics) {
            for (Layer layer : layoutTechnology.getLayersSortedByRule(Layer.LayerSortingType.ByHeight)) {
                double edgecap = layer.getEdgeCapacitance();
                double areacap = layer.getCapacitance();
                double res = layer.getResistance();
                if (edgecap != 0 || areacap != 0 || res != 0) {
                    multiLinePrint(true, "***    "+layer.getName()+":\tareacap="+areacap+"FF/um^2,\tedgecap="+edgecap+"FF/um,\tres="+res+"ohms/sq\n");
                }
            }
        }
//        multiLinePrint(false, ".OPTIONS NOMOD NOPAGE\n");

        if (localPrefs.useCellParameters) {
            multiLinePrint(false, ".options parhier=local\n");
        }
        // if sizes to be written in lambda, tell spice conversion factor
		if (localPrefs.writeTransSizeInLambda)
		{
			double scale = layoutTechnology.getScale();
			multiLinePrint(true, "*** Lambda Conversion ***\n");
			multiLinePrint(false, ".opt scale=" + localPrefs.formatDouble(scale / 1000.0) + "U\n\n");
		}

		// see if spice model/option cards from file if specified
		String headerFile = localPrefs.headerCardInfo;
		if (headerFile.length() > 0 && !headerFile.startsWith(SPICE_NOEXTENSION_PREFIX))
		{
			if (headerFile.startsWith(SPICE_EXTENSION_PREFIX))
			{
				// extension specified: look for a file with the cell name and that extension
				String headerPath = TextUtils.getFilePath(TextUtils.makeURLToFile(filePath));
                String ext = headerFile.substring(SPICE_EXTENSION_PREFIX.length());
                if (ext.startsWith(".")) ext = ext.substring(1);
				String filePart = cell.getName() + "." + ext;
				String fileName = headerPath + filePart;
				File test = new File(fileName);
				if (test.exists())
				{
					addIncludeFile(filePart, "Model cards", false);
                    System.out.println("Spice Header Card '" + fileName + "' is included");
                    return;
				}
                reportWarning("Spice Header Card '" + fileName + "' cannot be loaded");
            } else
			{
				// normal header file specified
				File test = new File(headerFile);
				if (!test.exists())
					reportWarning("Warning: cannot find model file '" + headerFile + "'");
				addIncludeFile(headerFile, "Model cards", false);
				return;
			}
		}

		// no header files: write predefined header for this level and technology
		int level = TextUtils.atoi(localPrefs.level);
		String [] header = null;
		switch (level)
		{
			case 1: header = layoutTechnology.getSpiceHeaderLevel1();   break;
			case 2: header = layoutTechnology.getSpiceHeaderLevel2();   break;
			case 3: header = layoutTechnology.getSpiceHeaderLevel3();   break;
		}
		if (header != null)
		{
			for(int i=0; i<header.length; i++)
				multiLinePrint(false, header[i] + "\n");
			return;
		}
	}

	/**
	 * Write a trailer from an external file, defined as a variable on
	 * the current technology in this library: tech:~.SIM_spice_trailer_file
	 * if it is available.
	 */
	private void writeTrailer(Cell cell)
	{
		// get spice trailer cards from file if specified
		String trailerFile = localPrefs.trailerCardInfo;
		if (trailerFile.length() > 0 && !trailerFile.startsWith(SPICE_NOEXTENSION_PREFIX))
		{
			if (trailerFile.startsWith(SPICE_EXTENSION_PREFIX))
			{
				// extension specified: look for a file with the cell name and that extension
				String trailerpath = TextUtils.getFilePath(TextUtils.makeURLToFile(filePath));
                String ext = trailerFile.substring(SPICE_EXTENSION_PREFIX.length());
                if (ext.startsWith(".")) ext = ext.substring(1);
				String filePart = cell.getName() + "." + ext;
				String fileName = trailerpath + filePart;
				File test = new File(fileName);
				if (test.exists())
				{
					addIncludeFile(filePart, "Trailer cards", false);
                    System.out.println("Spice Trailer Card '" + fileName + "' is included");
				}
                else
                {
                    reportWarning("Spice Trailer Card '" + fileName + "' cannot be loaded");
                }
			} else
			{
				// normal trailer file specified
				addIncludeFile(trailerFile, "Trailer cards", false);
                System.out.println("Spice Trailer Card '" + trailerFile + "' is included");
			}
		}
	}

	/**
	 * Function to write a two port device to the file. Complain about any missing connections.
	 * Determine the port connections from the exports in the instance
	 * prototype. Get the part number from the 'part' number value;
	 * increment it. The type of device is declared in type; extra is the string
	 * data acquired before calling here.
	 * If the device is connected to the same net at both ends, do not
	 * write it. Is this OK?
	 */
	private void writeTwoPort(NodeInst ni, String partName, String extra, CellNetInfo cni, Netlist netList,
		VarContext context, SpiceSegmentedNets segmentedNets)
	{
		PortInst port0 = ni.getPortInst(0);
		PortInst port1 = ni.getPortInst(1);
		Network net0 = netList.getNetwork(port0);
		Network net1 = netList.getNetwork(port1);
		CellSignal cs0 = cni.getCellSignal(net0);
		CellSignal cs1 = cni.getCellSignal(net1);

		// make sure the component is connected to nets
		if (cs0 == null || cs1 == null)
		{
			String message = "WARNING: " + ni + " component not fully connected in " + ni.getParent();
			dumpMessage(message, false);
		}
		if (cs0 != null && cs1 != null && cs0 == cs1)
		{
			String message = "WARNING: " + ni + " component appears to be shorted on net " + net0.toString() +
				" in " + ni.getParent();
			dumpMessage(message, false);
			return;
		}

		if (ni.getName() != null) partName += getSafeNetName(ni.getName(), false);

        // add M factor if there
        StringBuilder sbExtra = new StringBuilder(extra);
        writeMFactor(context, ni, sbExtra);

        String name0 = getPortName(cs0);
        String name1 = getPortName(cs1);
        if (segmentedNets != null) {
            name0 = segmentedNets.getNetName(port0);
            name1 = segmentedNets.getNetName(port1);
        }
		multiLinePrint(false, partName + " " + name1 + " " + name0 + " " + sbExtra.toString() + "\n");
	}

	/******************** SIMPLE PARASITIC CALCULATIONS (AREA/PERIM) ********************/

	/**
	 * Method to recursively determine the area of diffusion and capacitance
	 * associated with a NodeInst.  If the node is mult_layer, then
	 * determine the dominant capacitance layer, and add its area; all other
	 * layers will be added as well to the extra_area total.
	 * Continue out of the ports on a complex cell
	 */
	private void addNodeInformation(Netlist netList, Map<Network,SpiceNet> spiceNets, NodeInst ni)
	{
		// cells have no area or capacitance (for now)
		if (ni.isCellInstance()) return;  // No area for complex nodes

		PrimitiveNode.Function function = ni.getFunction();

		// initialize to examine the polygons on this node
		Technology tech = ni.getProto().getTechnology();
		FixpTransform trans = ni.rotateOut();

		// make linked list of polygons
		Poly [] polyList = tech.getShapeOfNode(ni, true, true, null);
		int tot = polyList.length;
		for(int i=0; i<tot; i++)
		{
			Poly poly = polyList[i];

			// make sure this layer connects electrically to the desired port
			PortProto pp = poly.getPort();
			if (pp == null) continue;

			// don't bother with layers without capacity
			Layer layer = poly.getLayer();
			if (layer.getTechnology() != curTech) continue;
			if (!layer.isDiffusionLayer() && layer.getCapacitance() == 0.0) continue;

			// leave out the gate capacitance of transistors
			if (layer.getFunction() == Layer.Function.GATE) continue;
			Network net = netList.getNetwork(ni, pp, 0);
			SpiceNet spNet = spiceNets.get(net);
			if (spNet == null) continue;

			// get the area of this polygon
			poly.transform(trans);
			spNet.merge.addPolygon(layer, poly);

			// count the number of transistors on this net
			if (layer.isDiffusionLayer() && function.isTransistor()) spNet.transistorCount++;
		}
	}

	/**
	 * Method to recursively determine the area of diffusion, capacitance, (NOT
	 * resistance) on an arc. If the arc contains active device diffusion, then
	 * it will contribute to the area of sources and drains, and the other layers
	 * will be ignored. This is not quite the same as the rule used for
	 * contact (node) structures. Note: the earlier version of this
	 * function assumed that diffusion arcs would always have zero capacitance
	 * values for the other layers; this produces an error if any of these layers
	 * have non-zero values assigned for other reasons. So we will check for the
	 * function of the arc, and if it contains active device, we will ignore any
	 * other layers
	 */
	private void addArcInformation(PolyMerge merge, ArcInst ai)
	{
		boolean isDiffArc = ai.isDiffusionArc();    // check arc function
        if (!isDiffArc) {
            // all cap besides diffusion is handled by segmented nets
            return;
        }

        Technology tech = ai.getProto().getTechnology();
		Poly [] arcInstPolyList = tech.getShapeOfArc(ai);
		int tot = arcInstPolyList.length;
		for(int j=0; j<tot; j++)
		{
			Poly poly = arcInstPolyList[j];
			if (poly.getStyle().isText()) continue;

			Layer layer = poly.getLayer();
			if (layer.getTechnology() != curTech) continue;

			if (layer.isDiffusionLayer() ||
				(!isDiffArc && layer.getCapacitance() > 0.0))
					merge.addPolygon(layer, poly);
		}
	}

    /******************** SUPPORT ********************/

	/**
	 * Method to determine whether a signal should be a subcircuit parameter.
	 * @param cs the signal to examine.
	 * @return true to ignore this signal in the subcircuit parameter list.
	 */
    private boolean ignoreSubcktPort(CellSignal cs)
    {
		if (localPrefs.globalTreatment != SimulationTool.SpiceGlobal.USESUBCKTPORTS)
		{
	        // ignore networks that aren't exported
	        PortProto pp = cs.getExport();
            if (pp == null) return true;
		}
        return false;
    }

	/**
     * Finds cells that must be uniquified during netlisting. These are
     * cells that have parameters which are Java Code. Because the Java Code
     * expression can return values that are different for different instances
     * in the hierarchy, the hierarchy must be flattened above that instance.
     * @param cell the cell hierarchy to check
     * @return true if the cell has been marked to unquify (which is true if
     * any cell below it has been marked).
     */
    private boolean markCellsToUniquify(Cell cell)
    {
        if (uniquifyCells.containsKey(cell)) return uniquifyCells.get(cell) != null;
        boolean mark = false;

        for (Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); ) {
            NodeInst ni = it.next();
            if (ni.isIconOfParent()) continue;

            Set<Variable.Key> varkeys = detectSpiceParams(ni.getProto());
            // check variables on instance; only uniquify cells that contain instances with Java parameters
            for (Variable.Key key : varkeys) {
                Variable var = ni.getVar(key);
                if (var != null && !isNetlistableParam(ni, var)) mark = true;
            }
            if (!ni.isCellInstance()) continue;
            Cell proto = ((Cell)ni.getProto()).contentsView();
            if (proto == null) proto = (Cell)ni.getProto();
            if (getEngineTemplate(proto) != null) continue;
            if (markCellsToUniquify(proto)) { mark = true; }
        }
        boolean isUnique = detectSpiceParams(cell) == UNIQUIFY_MARK;
        assert mark == isUnique;
        if (mark)
            uniquifyCells.put(cell, cell);
        else
            uniquifyCells.put(cell, null);
        return mark;
    }

    private static final boolean CELLISEMPTYDEBUG = false;
    private Map<Cell,Boolean> checkedCells = new HashMap<Cell,Boolean>();

    public boolean cellIsEmpty(Cell cell)
    {
        Boolean b = checkedCells.get(cell);
        if (b != null) return b.booleanValue();

        boolean empty = true;

        boolean useParasitics = !useCDL &&
            localPrefs.parasiticsLevel != SimulationTool.SpiceParasitics.SIMPLE &&
            cell.getView() == View.LAYOUT;
        if (useParasitics) return false;

        List<Cell> emptyCells = new ArrayList<Cell>();

        for (Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); ) {
            NodeInst ni = it.next();

            // if node is a cell, check if subcell is empty
            if (ni.isCellInstance()) {
                // ignore own icon
                if (ni.isIconOfParent()) {
                    continue;
                }
                Cell iconCell = (Cell)ni.getProto();
                Cell schCell = iconCell.contentsView();
                if (schCell == null) schCell = iconCell;
                if (cellIsEmpty(schCell)) {
                    if (CELLISEMPTYDEBUG) emptyCells.add(schCell);
                    continue;
                }
                empty = false;
                break;
            }

            // otherwise, this is a primitive
            PrimitiveNode.Function fun = ni.getFunction();

            // Passive devices used by spice/CDL
            if (fun.isResistor() || // == PrimitiveNode.Function.RESIST ||
                fun == PrimitiveNode.Function.INDUCT ||
                fun.isCapacitor() || // == PrimitiveNode.Function.CAPAC || fun == PrimitiveNode.Function.ECAPAC ||
                fun == PrimitiveNode.Function.DIODE || fun == PrimitiveNode.Function.DIODEZ)
            {
                empty = false;
                break;
            }

            // active devices used by Spice/CDL
            if (((PrimitiveNode)ni.getProto()).getGroupFunction() == PrimitiveNode.Function.TRANS) {
                empty = false;
                break;
            }

            // check for spice code on pins
            if (ni.getVar(SPICE_CARD_KEY) != null) {
                empty = false;
                break;
            }
        }

        // look for a model file on the current cell
        if (modelOverrides.get(cell) != null) {
            empty = false;
        }

        // check for spice template
        if (getEngineTemplate(cell) != null) {
            empty = false;
        }

        // empty
        if (CELLISEMPTYDEBUG && empty) {
            System.out.println(cell+" is empty and contains the following empty cells:");
            for (Cell c : emptyCells)
                System.out.println("   "+c.describe(true));
        }
        checkedCells.put(cell, Boolean.valueOf(empty));
        return empty;
    }

	/******************** TEXT METHODS ********************/

    /**
     * Method to adjust a network name to be safe for Spice output.
     * Spice has a list of legal punctuation characters that it allows.
     */
    public static String getSafeNetName(String name, SimulationTool.SpiceEngine engine)
    {
        String legalSpiceChars = SPICELEGALCHARS;
        switch (engine)
        {
            case SPICE_ENGINE_P:
                legalSpiceChars = PSPICELEGALCHARS;
                break;
			default:
				break;
        }
        return getSafeNetName(name, legalSpiceChars, engine);
    }

    /**
     * Method to determine the name to use for a CellSignal.
     * It may use the export name if explicit ports are being written to each subcircuit.
     * @param cs the CellSignal being converted to a string.
     * @return the name to use for that CellSignal.
     */
    private String getPortName(CellSignal cs)
    {
    	String portName = cs.getName();
    	if (PREFER_GLOBAL_NAME_OVER_RAILS && localPrefs.globalTreatment == SimulationTool.SpiceGlobal.USESUBCKTPORTS &&
    		cs.getExport() != null && !cs.getExport().getNameKey().isBus())
    	{
   			portName = getSafeNetName(cs.getExport().getName(), false); // cs.getExport().getName();
    	}
    	return portName;
    }

    /**
     * Method to adjust a network name to be safe for Spice output.
     * Spice has a list of legal punctuation characters that it allows.
     */
    private static String getSafeNetName(String name, String legalSpiceChars, SimulationTool.SpiceEngine spiceEngine)
    {
        // simple names are trivially accepted as is
		boolean allAlNum = true;
		int len = name.length();
		if (len <= 0) return name;
		for(int i=0; i<len; i++)
		{
			boolean valid = TextUtils.isLetterOrDigit(name.charAt(i));
			if (i == 0) valid = Character.isLetter(name.charAt(i));
			if (!valid)
			{
				allAlNum = false;
				break;
			}
		}
		if (allAlNum) return name;

		StringBuilder sb = new StringBuilder();
        switch (spiceEngine)
        {
            case SPICE_ENGINE_G:
            case SPICE_ENGINE_P:
            case SPICE_ENGINE_2:
                break;
            default:
                if (TextUtils.isDigit(name.charAt(0)))
                {
                    sb.append('_');
                }
                break;
        }
		for(int t=0; t<name.length(); t++)
		{
			char chr = name.charAt(t);
			boolean legalChar = TextUtils.isLetterOrDigit(chr);
			if (!legalChar)
			{
				for(int j=0; j<legalSpiceChars.length(); j++)
				{
					char legalChr = legalSpiceChars.charAt(j);
					if (chr == legalChr) { legalChar = true;   break; }
				}
			}
			if (!legalChar) chr = '_';
			sb.append(chr);
		}
		return sb.toString();
	}

    /**
     * This adds formatting to a Spice parameter value.  It adds single quotes
     * around the parameter string if they do not already exist.
     * @param param the string parameter value (without the name= part).
     * @param wrapped true if the string is already "wrapped" (with parenthesis) and does not need to have quotes around it.
     * @return a parameter string with single quotes around it
     */
    private String formatParam(String param, TextDescriptor.Unit u, boolean wrapped)
    {
    	// first remove quotes
        String value = trimSingleQuotes(param);

        // see if the result evaluates to a number
		if (TextUtils.isANumberPostFix(value)) return value;

		// if purely numeric, try converting to proper units
        try {
            Double v = Double.valueOf(value);
            if (u == TextDescriptor.Unit.DISTANCE)
            	return localPrefs.formatDoublePostFix(v.doubleValue());
            return value;
        } catch (NumberFormatException e) {}


        // not a number and some Spices like a wrapper, so enclose it
		if (!wrapped)
		{
	        // Spice2 and Spice3 don't need a wrapper
            switch (spiceEngine)
            {
                case SPICE_ENGINE_2:
                case SPICE_ENGINE_3:
                case SPICE_ENGINE_JO:
                    return value;
                case SPICE_ENGINE_O:
                	value = "{" + value + "}";
                    break;
                default:
                    value = "'" + value + "'";
                    break;
            }
		}
        return value;
    }

    private String trimSingleQuotes(String param)
    {
        if (param.startsWith("'") && param.endsWith("'")) {
            return param.substring(1, param.length()-1);
        }
        return param;
    }

	/**
	 * Method to insert an "include" of file "filename" into the stream.
	 */
	private void addIncludeFile(String fileName, String description, boolean verilog)
	{
		if (verilog)
		{
            multiLinePrint(true, "* " + description + " described in this file:\n");
            multiLinePrint(false, ".hdl "+ fileName + "\n");
            return;
		}
        if (useCDL)
        {
            multiLinePrint(true, "* " + description + " described in this file:\n");
            multiLinePrint(false, ".include "+ fileName + "\n");
            return;
        }

        switch (spiceEngine)
        {
            case SPICE_ENGINE_2:
            case SPICE_ENGINE_3:
            case SPICE_ENGINE_G:
            case SPICE_ENGINE_S:
                multiLinePrint(true, "* " + description + " described in this file:\n");
    			multiLinePrint(false, ".include " + fileName + "\n");
                break;
            case SPICE_ENGINE_H:
            case SPICE_ENGINE_H_ASSURA:
            case SPICE_ENGINE_H_CALIBRE:
            case SPICE_ENGINE_NG:
                multiLinePrint(true, "* " + description + " described in this file:\n");
    			multiLinePrint(false, ".include '" + fileName + "'\n");
                break;
            case SPICE_ENGINE_P:
                multiLinePrint(true, "* " + description + " described in this file:\n");
    			multiLinePrint(false, ".INC " + fileName + "\n");
                break;
            case SPICE_ENGINE_JO:
            	// must copy file
                multiLinePrint(true, "* " + description + " copied from file: " + fileName + "\n");
            	URL url = TextUtils.makeURLToFile(fileName);
            	try
            	{
            		URLConnection urlCon = url.openConnection();
            		InputStreamReader is = new InputStreamReader(urlCon.getInputStream());
            		LineNumberReader lineReader = new LineNumberReader(is);
            		for(;;)
            		{
            			String buf = lineReader.readLine();
            			if (buf == null) break;
            			multiLinePrint(false, buf + "\n");
            		}
            		lineReader.close();
            	} catch (IOException e)
            	{
            		System.out.println("Error reading " + fileName);
            		return;
            	}
                multiLinePrint(true, "* End of " + description + " copied from file: " + fileName + "\n");
            	break;
			default:
				break;
        }
	}

	/**
	 * Method to report an error or warning message that is built in the infinite string.
	 * The message is sent to the messages window, errorLogger and also to the SPICE deck "f".
	 */
	private void dumpMessage(String message, boolean isErrorMsg)
	{
		multiLinePrint(true, "*** " + message + "\n");
        if (isErrorMsg)
            reportError(message);
        else
            reportWarning(message);
	}

	/**
	 * Formatted output to file "stream".  All spice output is in upper case.
	 * The buffer can contain no more than 1024 chars including the newlinelastMoveTo
	 * and null characters.
	 * Doesn't return anything.
	 */
	public void multiLinePrint(boolean isComment, String str)
	{
		// convert "@" characters to "_" for Opus
        switch (spiceEngine)
        {
            case SPICE_ENGINE_O:
            	str = str.replaceAll("@", "_");
                break;
			default:
				break;
        }

        // put in line continuations, if over the limit of chars per line
		String contChar = "+";
		if (isComment) contChar = "*** ";
		int lastSpace = -1;
		int count = 0;
		boolean insideQuotes = false;
		int lineStart = 0;
		for (int pt = 0; pt < str.length(); pt++)
		{
			char chr = str.charAt(pt);
			if (chr == '\n')
			{
				printWriter.print(str.substring(lineStart, pt+1));
				count = 0;
				lastSpace = -1;
				lineStart = pt+1;
			} else
			{
				if (chr == ' ' && !insideQuotes) lastSpace = pt;
				if (chr == '\'') insideQuotes = !insideQuotes;
				count++;
				if (localPrefs.maxLineLen != 0 && count >= localPrefs.maxLineLen && !insideQuotes && lastSpace > -1)
				{
					String partial = str.substring(lineStart, lastSpace+1);
					printWriter.print(partial + "\n" + contChar);
					count = count - partial.length();
					lineStart = lastSpace+1;
					lastSpace = -1;
				}
			}
		}
		if (lineStart < str.length())
		{
			String partial = str.substring(lineStart);
			printWriter.print(partial);
		}
	}

	/******************** SUPPORT CLASSES ********************/

	private static class SpiceNet
	{
		/** merged geometry for this network */			PolyMerge merge;
		/** area of diffusion */						double    diffArea;
		/** perimeter of diffusion */					double    diffPerim;
//		/** amount of capacitance in non-diffusion */	float     nonDiffCapacitance = 0;
		/** number of transistors on the net */			int       transistorCount;

		SpiceNet()
		{
            transistorCount = 0;
            diffArea = 0;
            diffPerim = 0;
            merge = new PolyMerge();
		}
	}

    private static class SpiceFinishedListener implements Exec.FinishedListener
    {
        private SpiceFinishedListener() {}

        public void processFinished(Exec.FinishedEvent e)
        {
            SwingUtilities.invokeLater(new Runnable() { public void run() {
                UserInterface ui = Job.getUserInterface();
                Cell cell = ui.needCurrentCell();
                if (cell == null) return;
            	SimulationData.plotGuessed(cell, null);
            }});
        }
    }

    public static class FlatSpiceCodeVisitor extends HierarchyEnumerator.Visitor {

        private String filePath;
        private Spice spice; // just used for file writing and formatting
        private PrintWriter spicePrintWriter;
        private SpiceSegmentedNets segNets;
        private PrintWriter printWriter;
        private int saveMaxLineLen;

        public FlatSpiceCodeVisitor(String f, Spice s) {
            filePath = f;
            spice = s;
            spicePrintWriter = spice.printWriter;
            segNets = null;
            saveMaxLineLen = spice.localPrefs.maxLineLen;
            spice.localPrefs.maxLineLen = 1000;
        }

        public boolean enterCell(HierarchyEnumerator.CellInfo info) {
            return true;
        }

        public void exitCell(HierarchyEnumerator.CellInfo info) {
            Cell cell = info.getCell();
            for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
			{
				NodeInst ni = it.next();
				if (ni.getProto() != Generic.tech().invisiblePinNode) continue;
                Variable cardVar = ni.getVar(SPICE_CODE_FLAT_KEY);
                if (cardVar != null) {
                    if (printWriter == null) {
                        try {
                            printWriter = new PrintWriter(new BufferedWriter(new FileWriter(filePath)));
                        } catch (IOException e) {
                            spice.reportWarning("Unable to open "+filePath+" for write.");
                            return;
                        }
                        spice.printWriter = printWriter;
                        segNets = new SpiceSegmentedNets(null, false, null, spice.localPrefs);
                    }
                    spice.emitEmbeddedSpice(cardVar, info.getContext(), segNets, info, true, true);
                }
			}
        }

        public boolean visitNodeInst(Nodable ni, HierarchyEnumerator.CellInfo info) {
            return true;
        }

        public void close() {
            if (printWriter != null) {
                System.out.println(filePath+" written");
                spice.printWriter = spicePrintWriter;
                printWriter.close();
            }
            spice.localPrefs.maxLineLen = saveMaxLineLen;
        }
    }
}
