/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Silos.java
 * Input/output tool: Silos netlist output
 * Written 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.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Nodable;
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.prototype.PortCharacteristic;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.Version;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.simulation.SimulationTool;
import com.sun.electric.util.TextUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Date;
import java.util.Iterator;

/**
 * This is the netlister for Silos.
 */
public class Silos extends Topology
{
	/** split lines into 80 characters. */			private static final int MAXSTR =  79;
	/** Maximum macro name length. */				private static final int MAXNAME =  12;

	/** key of Variable holding node name. */		public static final Variable.Key SILOS_NODE_NAME_KEY = Variable.newKey("SIM_silos_node_name");
	/** key of Variable holding global names. */	public static final Variable.Key SILOS_GLOBAL_NAME_KEY = Variable.newKey("SIM_silos_global_name");
	/** key of Variable holding behavior file. */	public static final Variable.Key SILOS_BEHAVIOR_FILE_KEY = Variable.newKey("SIM_silos_behavior_file");
	/** key of Variable holding model. */			public static final Variable.Key SILOS_MODEL_KEY = Variable.newKey("SC_silos");
	private SilosPreferences localPrefs;

	public static class SilosPreferences extends OutputPreferences
    {
        public SilosPreferences(boolean factory) { super(factory); }

        public Output doOutput(Cell cell, VarContext context, String filePath)
        {
    		Silos out = new Silos(this);
    		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");
            return out.finishWrite();
        }
    }

	/**
	 * Creates a new instance of the Silos netlister.
	 */
	Silos(SilosPreferences sp) { localPrefs = sp; }

	protected void start()
	{
		// parameters to the output-line-length limit and how to break long lines
		setOutputWidth(MAXSTR, true);
		setContinuationString("+");

		// write header information
		writeWidthLimited("\n$ CELL " + topCell.describe(false) +
			" FROM LIBRARY " + topCell.getLibrary().getName() + "\n");
		emitCopyright("$ ", "");
		if (localPrefs.includeDateAndVersionInOutput)
		{
			writeWidthLimited("$ CELL CREATED ON " + TextUtils.formatDate(topCell.getCreationDate()) + "\n");
			writeWidthLimited("$ VERSION " + topCell.getVersion() + "\n");
			writeWidthLimited("$ LAST REVISED " + TextUtils.formatDate(topCell.getRevisionDate()) + "\n");
			writeWidthLimited("$ SILOS netlist written by Electric VLSI Design System, version " + Version.getVersion() + "\n");
			writeWidthLimited("$ WRITTEN ON " + TextUtils.formatDate(new Date()) + "\n");
		} else
		{
			writeWidthLimited("$ SILOS netlist written by Electric VLSI Design System\n");
		}

		// First look for any global sources
		for(Iterator<NodeInst> it = topCell.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();
			if (ni.isCellInstance()) continue;	// only real sources

			PrimitiveNode.Function nodetype = ni.getFunction();
			if (nodetype != PrimitiveNode.Function.SOURCE) continue;

			Variable var = ni.getVar(SILOS_GLOBAL_NAME_KEY);
			String name = "";
			if (var != null)	// is this a global source ?
			{
				name = var.getObject().toString();
				writeWidthLimited(".GLOBAL " + convertSpecialNames(name) + "\n");
			}

			// Get the source type
			var = ni.getVar(Spice.SPICE_MODEL_KEY);
			if (var == null)
			{
				reportError("Unspecified source:");
				writeWidthLimited("$$$$$ Unspecified source: \n");
			} else	// There is more
			{
				boolean clktype = false;	// Extra data required if variable there
				String msg = var.getObject().toString();
				String line = "";
				char lastChr = 0;
				for(int i=0; i<msg.length(); i++)
				{
					lastChr = msg.charAt(i);
					if (lastChr == '/') break;
					switch (lastChr)
					{
						case 'g':	// a global clock (THIS IS WRONG!!!)
							line = name + " .CLK ";
							clktype = true;
							break;
						case 'h':	// a fixed high source (THIS IS WRONG!!!)
							line = name + " .CLK 0 S1 $ HIGH LEVEL";
							break;
						case 'l':	// a fixed low source (THIS IS WRONG!!!)
							line = name + " .CLK 0 S0 $ LOW LEVEL";
							break;
					}
				}
				if (lastChr == '/' && clktype) line += msg.substring(msg.indexOf('/')+1);
				writeWidthLimited(line + "\n");
			}
		}
	}

	protected void done()
	{
	}

	/**
	 * Method to write cellGeom
	 */
	protected void writeCellTopology(Cell cell, String cellName, CellNetInfo cni, VarContext context, Topology.MyCellInfo info)
	{
		// Read a behavior file if it is available
		for(Cell oCell : cell.getCellsInGroup())
		{
			Variable var = oCell.getVar(SILOS_BEHAVIOR_FILE_KEY);
			if (var != null)
			{
				String headerPath = TextUtils.getFilePath(cell.getLibrary().getLibFile());
				String fileName = headerPath + var.getObject().toString();
				File test = new File(fileName);
				if (!test.exists())
				{
					reportError("Cannot find SILOS behavior file " + fileName + " on " + cell);
				} else
				{
					BufferedReader br;
					try
					{
						FileReader fr = new FileReader(test);
						br = new BufferedReader(fr);
						for(;;)
						{
							String line = br.readLine();
							if (line == null) break;
							writeWidthLimited(line + "\n");
						}
						br.close();
						fr.close();
					} catch (IOException e) {}
					return;
				}
			}
		}

		// There was no behavior file, get the SILOS model from the library if it exists
		for(Iterator<Cell> it = cell.getCellGroup().getCells(); it.hasNext(); )
		{
			Cell oCell = it.next();
			Variable var = oCell.getVar(SILOS_MODEL_KEY);
			if (var != null && var.getObject() instanceof String[])
			{
				String [] model = (String [])var.getObject();
				for(int i = 0; i < model.length; i++)
					writeWidthLimited(model[i] + "\n");
				writeWidthLimited("\n");
				return;
			}
		}

		// gather networks in the cell
		Netlist netList = cni.getNetList();

		// write the module header
		if (cell != topCell)
		{
			writeWidthLimited("\n");
			String name = cni.getParameterizedName();
			if (name.length() > MAXNAME)
			{
				String msg = ".MACRO name " + name + " is too long;";
				name = name.substring(0, MAXNAME);
				msg += " truncated to " + name;
                reportWarning(msg);
            }
			writeWidthLimited(".MACRO " + convertName(name));
			for(Iterator<CellAggregateSignal> it = cni.getCellAggregateSignals(); it.hasNext(); )
			{
				CellAggregateSignal cas = it.next();
				if (cas.getExport() == null) continue;
				if (cas.isSupply()) continue;
				writeWidthLimited(" " + convertSubscripts(cas.getNameWithIndices()));
			}
			writeWidthLimited("\n");
		} else writeWidthLimited("\n");

		// write the cell instances
		for(Iterator<Nodable> nIt = netList.getNodables(); nIt.hasNext(); )
		{
			Nodable no = nIt.next();
			if (no.isCellInstance())
			{
				String nodeName = parameterizedName(no, context);
				CellNetInfo subCni = getCellNetInfo(nodeName);
				writeWidthLimited("(" + no.getName() + " " + convertName(nodeName));
				for(Iterator<CellAggregateSignal> sIt = subCni.getCellAggregateSignals(); sIt.hasNext(); )
				{
					CellAggregateSignal cas = sIt.next();
					if (cas.isSupply()) continue;

					// ignore networks that aren't exported
					PortProto pp = cas.getExport();
					if (pp == null) continue;

					int low = cas.getLowIndex(), high = cas.getHighIndex();
					if (low > high)
					{
						// single signal
						Network net = netList.getNetwork(no, pp, cas.getExportIndex());
						CellSignal cs = cni.getCellSignal(net);
						writeWidthLimited(" " + cs.getName());
					} else
					{
						int total = high - low + 1;
						CellSignal [] outerSignalList = new CellSignal[total];
						for(int j=low; j<=high; j++)
						{
							CellSignal cInnerSig = cas.getSignal(j-low);
							Network net = netList.getNetwork(no, cas.getExport(), cInnerSig.getExportIndex());
							outerSignalList[j-low] = cni.getCellSignal(net);
						}
						writeBus(outerSignalList, low, high, cas.isDescending(),
							cas.getName(), cni.getPowerNet(), cni.getGroundNet());
					}
				}
				writeWidthLimited("\n");
			}
		}

		// write the primitives
		for(Iterator<Nodable> nIt = netList.getNodables(); nIt.hasNext(); )
		{
			Nodable no = nIt.next();

			// not interested in passive nodes (ports electrically connected)
			if (!no.isCellInstance())
			{
				NodeInst ni = (NodeInst)no;
				PrimitiveNode.Function nodeType = getPrimitiveType(ni);
				if (nodeType == PrimitiveNode.Function.UNKNOWN) continue;
				if (nodeType.isFET())
				{
					// Transistors need a part number
					writeWidthLimited(getNodeInstName(ni));
					writeWidthLimited(" ");
					writeWidthLimited(getPrimitiveName(ni, false));

					// write the names of the port(s)
					for(Iterator<PortProto> pIt = ni.getProto().getPorts(); pIt.hasNext(); )
					{
						PortProto pp = pIt.next();
						writeWidthLimited(getPortProtoName(cell == topCell, null, ni, pp, cell, netList, cni));
					}
					writeWidthLimited("\n");
					continue;
				}
				if (nodeType.isFlipFlop())
				{
					// flip-flops need a part number
					writeWidthLimited(getNodeInstName(ni));
					writeWidthLimited(" ");
					writeWidthLimited(getPrimitiveName(ni, false));

					// write the names of the port(s)
					writeFlipFlop(cell == topCell, ni, cell, nodeType, netList, cni);
					continue;
				}

				if (nodeType == PrimitiveNode.Function.METER || nodeType == PrimitiveNode.Function.SOURCE)
				{
					if (cell != topCell) reportWarning("WARNING: Global Clock in a sub-cell");
					continue;
				}

				if (nodeType == PrimitiveNode.Function.GATEAND || nodeType == PrimitiveNode.Function.GATEOR ||
					nodeType == PrimitiveNode.Function.GATEXOR || nodeType == PrimitiveNode.Function.BUFFER)
				{
					// Gates use their output port as a name
					PortProto outPP = null;
					for(Iterator<PortProto> pIt = ni.getProto().getPorts(); pIt.hasNext(); )
					{
						PortProto pp = pIt.next();
						if (pp.getCharacteristic() == PortCharacteristic.OUT)
						{
							// find the name of the output port
							writeWidthLimited(getPortProtoName(cell == topCell, null, ni, pp, cell, netList, cni));

							// record that we used it
							outPP = pp;

							// determine if this proto is negated
							Connection con = null;
							for(Iterator<Connection> aIt = ni.getConnections(); aIt.hasNext(); )
							{
								Connection c = aIt.next();
								if (c.getPortInst().getPortProto() == pp) { con = c;   break; }
							}

							boolean negated = false;
							if (con != null && con.isNegated()) negated = true;
							writeWidthLimited(" " + getPrimitiveName(ni, negated));
							break;
						}
					}
					if (outPP == null)
						reportError("Could not find an output connection on " + ni.getProto().getName());

					// get the fall and rise times
					writeWidthLimited(getRiseTime(ni));
					writeWidthLimited(getFallTime(ni));

					// write the rest of the ports only if they're connected
					for(Iterator<Connection> aIt = ni.getConnections(); aIt.hasNext(); )
					{
						Connection con = aIt.next();
						PortProto pp = con.getPortInst().getPortProto();
						if (pp == outPP) continue;
						writeWidthLimited(getPortProtoName(cell == topCell, con, ni, pp, cell, netList, cni));
					}
					writeWidthLimited("\n");
					continue;
				}

				if (nodeType == PrimitiveNode.Function.CAPAC)
				{
					// find a connected port for the node name
					for(Iterator<Connection> aIt = ni.getConnections(); aIt.hasNext(); )
					{
						Connection con = aIt.next();

						// write port name as output
						PortProto pp = con.getPortInst().getPortProto();
						writeWidthLimited(getPortProtoName(cell == topCell, null, ni, pp, cell, netList, cni));
						writeWidthLimited(" " + getPrimitiveName(ni, false));

						double j = getCapacitanceInMicroFarads(ni, context);
						if (j >= 0)
						{
							writeWidthLimited(" " + TextUtils.formatDouble(j, 0));
						} else
						{
							reportWarning("Warning: capacitor with no value on " + ni);
						}
						writeWidthLimited("\n");
						break;
					}
					continue;
				}

				if (nodeType == PrimitiveNode.Function.RESIST)
				{
					// sorry! can't handle the resistive gate yet
					continue;
				}
			}
		}
		if (cell != topCell)
			writeWidthLimited(".EOM\n");
	}

	/**
	 * Method to add a bus of signals named "name" to the infinite string "infstr".  If "name" is zero,
	 * do not include the ".NAME()" wrapper.  The signals are in "outerSignalList" and range in index from
	 * "lowindex" to "highindex".  They are described by a bus with characteristic "tempval"
	 * (low bit is on if the bus descends).  Any unconnected networks can be numbered starting at
	 * "*unconnectednet".  The power and grounds nets are "pwrnet" and "gndnet".
	 */
	private void writeBus(CellSignal [] outerSignalList, int lowIndex, int highIndex, boolean descending,
		String name, Network pwrNet, Network gndNet)
	{
		// presume writing the bus as a whole
		boolean breakBus = false;

		// see if all of the nets on this bus are distinct
		int j = lowIndex+1;
		for( ; j<=highIndex; j++)
		{
			CellSignal cs = outerSignalList[j-lowIndex];
			int k = lowIndex;
			for( ; k<j; k++)
			{
				CellSignal oCs = outerSignalList[k-lowIndex];
				if (cs == oCs) break;
			}
			if (k < j) break;
		}
		if (j <= highIndex)
		{
			breakBus = true;
		} else
		{
			// bus entries must have the same root name and go in order
			String lastnetname = null;
			for(j=lowIndex; j<=highIndex; j++)
			{
				CellSignal wl = outerSignalList[j-lowIndex];
				String thisnetname = wl.getName();
				if (wl.getExport() != null)
				{
					if (wl.isDescending())
					{
						if (!descending) break;
					} else
					{
						if (descending) break;
					}
				}

				int openSquare = thisnetname.indexOf('[');
				if (openSquare < 0) break;
				if (j > lowIndex)
				{
					int li = 0;
					for( ; li < lastnetname.length(); li++)
					{
						if (thisnetname.charAt(li) != lastnetname.charAt(li)) break;
						if (lastnetname.charAt(li) == '[') break;
					}
					if (lastnetname.charAt(li) != '[' || thisnetname.charAt(li) != '[') break;
					int thisIndex = TextUtils.atoi(thisnetname.substring(li+1));
					int lastIndex = TextUtils.atoi(lastnetname.substring(li+1));
					if (thisIndex != lastIndex + 1) break;
				}
				lastnetname = thisnetname;
			}
			if (j <= highIndex) breakBus = true;
		}

		writeWidthLimited(" ");
		if (breakBus)
		{
			int start = lowIndex, end = highIndex;
			int order = 1;
			if (descending)
			{
				start = highIndex;
				end = lowIndex;
				order = -1;
			}
			for(int k=start; ; k += order)
			{
				if (k != start)
					writeWidthLimited("-");
				CellSignal cs = outerSignalList[k-lowIndex];
				writeWidthLimited(cs.getName());
				if (k == end) break;
			}
		} else
		{
			CellSignal lastCs = outerSignalList[0];
			String lastNetName = lastCs.getName();
			int openSquare = lastNetName.indexOf('[');
			CellSignal cs = outerSignalList[highIndex-lowIndex];
			String netName = cs.getName();
			int i = netName.indexOf('[');
			if (i < 0) writeWidthLimited(netName); else
			{
				writeWidthLimited(netName.substring(0, i));
				if (descending)
				{
					int first = TextUtils.atoi(netName.substring(i+1));
					int second = TextUtils.atoi(lastNetName.substring(openSquare+1));
					writeWidthLimited("[" + first + ":" + second + "]");
				} else
				{
					int first = TextUtils.atoi(netName.substring(i+1));
					int second = TextUtils.atoi(lastNetName.substring(openSquare+1));
					writeWidthLimited("[" + second + ":" + first + "]");
				}
			}
		}
	}

	/**
	 * Method to return a string describing the SILOS type of nodeinst "ni"
	 * if 'neg' is true, then the negated version is needed
	 */
	private String getPrimitiveName(NodeInst ni, boolean neg)
	{
		PrimitiveNode.Function f = getPrimitiveType(ni);
		if (f.isNTypeTransistor()) return ".NMOS";
		if (f.isPTypeTransistor()) return ".PMOS";

		if (f == PrimitiveNode.Function.BUFFER)
		{
			if (neg) return ".INV";
			return ".BUF";
		}
		if (f == PrimitiveNode.Function.GATEXOR)
		{
			if (neg) return ".XNOR";
			return ".XOR";
		}
		if (f == PrimitiveNode.Function.GATEAND)
		{
			if (neg) return ".NAND";
			return ".NAND";
		}
		if (f == PrimitiveNode.Function.GATEOR)
		{
			if (neg) return ".NOR";
			return ".OR";
		}

		if (f == PrimitiveNode.Function.RESIST) return ".RES";
		if (f == PrimitiveNode.Function.CAPAC) return ".CAP";

		if (f == PrimitiveNode.Function.FLIPFLOPRSMS || f == PrimitiveNode.Function.FLIPFLOPRSP) return ".SRPEFF";
		if (f == PrimitiveNode.Function.FLIPFLOPRSN) return ".SRNEFF";
		if (f == PrimitiveNode.Function.FLIPFLOPJKMS || f == PrimitiveNode.Function.FLIPFLOPJKP) return ".JKPEFF";
		if (f == PrimitiveNode.Function.FLIPFLOPJKN) return ".JKNEFF";
		if (f == PrimitiveNode.Function.FLIPFLOPDMS || f == PrimitiveNode.Function.FLIPFLOPDP) return ".DPEFF";
		if (f == PrimitiveNode.Function.FLIPFLOPDN) return ".DNEFF";
		if (f == PrimitiveNode.Function.FLIPFLOPTMS || f == PrimitiveNode.Function.FLIPFLOPTP) return ".TPEFF";
		if (f == PrimitiveNode.Function.FLIPFLOPTN) return ".TNEFF";

		return convertName(ni.getProto().getName());
	}

	/**
	 * Method to return the SILOS type of a node
	 * Read the contents of the added string, make it available to
	 * the caller
	 */
	private PrimitiveNode.Function getPrimitiveType(NodeInst ni)
	{
		if (ni.isCellInstance()) return null;

		PrimitiveNode.Function func = ni.getFunction();
		if (func.isPTypeTransistor()) return PrimitiveNode.Function.TRAPMOS;
		if (func.isNTypeTransistor()) return PrimitiveNode.Function.TRANMOS;
		if (func == PrimitiveNode.Function.GATEAND || func == PrimitiveNode.Function.GATEOR ||
			func == PrimitiveNode.Function.GATEXOR || func == PrimitiveNode.Function.BUFFER ||
			func == PrimitiveNode.Function.RESIST || func == PrimitiveNode.Function.CAPAC ||
			func == PrimitiveNode.Function.SOURCE || func == PrimitiveNode.Function.METER ||
			func == PrimitiveNode.Function.FLIPFLOPRSMS || func == PrimitiveNode.Function.FLIPFLOPRSP || func == PrimitiveNode.Function.FLIPFLOPRSN ||
			func == PrimitiveNode.Function.FLIPFLOPJKMS || func == PrimitiveNode.Function.FLIPFLOPJKP || func == PrimitiveNode.Function.FLIPFLOPJKN ||
			func == PrimitiveNode.Function.FLIPFLOPDMS || func == PrimitiveNode.Function.FLIPFLOPDP || func == PrimitiveNode.Function.FLIPFLOPDN ||
			func == PrimitiveNode.Function.FLIPFLOPTMS || func == PrimitiveNode.Function.FLIPFLOPTP || func == PrimitiveNode.Function.FLIPFLOPTN)
				return func;
		return(PrimitiveNode.Function.UNKNOWN);
	}

	/**
	 * Method to return a string describing the SILOS part name of nodeinst
	 * "ni"
	 */
	private String getNodeInstName(NodeInst ni)
	{
		Variable var = ni.getVar(SILOS_NODE_NAME_KEY);
		if (var != null) return var.describe(-1);

		String name = ni.getName();
		if (name.length() > 0)
		{
			if (Character.isLetter(name.charAt(0))) return name;
			if (name.charAt(0) == '[') return convertSubscripts(name);
		}
		return "U" + name;
	}

	/**
	 * Find a name to write for the port prototype, pp, on the node instance, ni
	 * The node instance is located within the prototype, np
	 * If there is an arc connected to the port, use the net name, or NETn.
	 * If there are more than one arc (that are not electrically connected)
	 * on the port, concatenate the names (with spaces between them).
	 * If there is no arc, but the port is an export, use the exported name.
	 * If the port is a power or ground port, ignore it
	 * If this is not the top level cell (ie. a .macro) remove [] notation.
	 */
	private String getPortProtoName(boolean top, Connection con, NodeInst ni, PortProto pp, Cell np, Netlist netList, CellNetInfo cni)
	{
		if (pp.isPower() || pp.isGround()) return "";

		if (con == null)
		{
			for(Iterator<Connection> it = ni.getConnections(); it.hasNext(); )
			{
				Connection c = it.next();
				PortInst pi = c.getPortInst();
				if (pi.getPortProto() != pp) continue;
				con = c;
			}
		}
		boolean negated = false;
		if (con != null && con.isNegated() && pp.getCharacteristic() == PortCharacteristic.IN) negated = true;

		Network net = null;
		if (con != null)
		{
			net = netList.getNetwork(con.getArc(), 0);
		} else
		{
			PortInst pi = ni.findPortInstFromEquivalentProto(pp);
			net = netList.getNetwork(pi);
		}
		if (net != null)
		{
			CellSignal cs = cni.getCellSignal(net);
			StringBuffer infstr = new StringBuffer();
			infstr.append(" ");
			if (negated) infstr.append("-");
			infstr.append(cs.getName());
			return infstr.toString();
		}

		// nothing connected to this port...leave a position
		return " .SKIP";
	}

	/**
	 * Method returns a string containing the rise time, as stored in
	 * the variable SIM_rise_delay on node instance ni.
	 * SIM_rise_delay can be multiple numbers (e.g. "rise_time,fanout")
	 * This function returns a string.
	 * A space is inserted as the first character in the string.
	 * Returns an empty string if no variable found.
	 */
	private String getRiseTime(NodeInst ni)
	{
		Variable var = ni.getVar(SimulationTool.RISE_DELAY_KEY);
		if (var != null) return var.describe(-1);
		return "";
	}

	/**
	 * Method returns a string containing the fall time, as stored in
	 * the variable SIM_fall_delay on node instance ni.
	 * SIM_fall_delay can be either an integer or a string
	 * (e.g. "fall_time,fanout")
	 * This function returns a string.
	 * A space is inserted as the first character in the string.
	 * Returns an empty string if no variable found.
	 */
	private String getFallTime(NodeInst ni)
	{
		Variable var = ni.getVar(SimulationTool.FALL_DELAY_KEY);
		if (var != null) return var.describe(-1);
		return "";
	}

	/**
	 * Method to return an integer as the capacitance defined
	 * by "SCHEM_capacitance" variable on an instance of a capacitor.
	 * The returned units are in microfarads (is this right?).
	 * Return -1 if nothing found.
	 */
	private double getCapacitanceInMicroFarads(NodeInst ni, VarContext context)
	{
		Variable var = ni.getVar(Schematics.SCHEM_CAPACITANCE);
		if (var != null)
		{
			String cap = context.evalVar(var).toString();
			char lastChar = 0;
			int len = cap.length();
			if (len > 0) lastChar = cap.charAt(len-1);
			if (lastChar == 'f' || lastChar == 'F') cap = cap.substring(0, len-1);
			double farads = VarContext.objectToDouble(cap, 0.0);
			double microFarads = farads * 1000000.0;
			return microFarads;
		}
		return -1;
	}

	/**
	 * Method to write the ports of a flip-flop;
	 * get them in the Electric order, then rewrite them
	 * 'ni' is the current NODEINST, found in 'np' cell
	 */
	private static final int JORD = 0;
	private static final int K	  = 1;
	private static final int Q	  = 2;
	private static final int QB   = 3;
	private static final int CK	  = 4;
	private static final int PRE  = 5;
	private static final int CLR  = 6;

	private void writeFlipFlop(boolean top, NodeInst ni, Cell np, PrimitiveNode.Function type, Netlist netList, CellNetInfo cni)
	{
		String [] portNames = new String[7];
		Iterator<PortProto> it = ni.getProto().getPorts();
		for(int i=0; i<7; i++)
		{
			if (!it.hasNext()) break;
			PortProto pp = it.next();
			portNames[i] = getPortProtoName(top, null, ni, pp, np, netList, cni);
		}
		if (portNames[PRE].equals(" .SKIP") && portNames[CLR].equals(" .SKIP"))
		{
			portNames[CLR] = "";		// If neither on, don't print
			portNames[PRE] = "";
		}
		if (type == PrimitiveNode.Function.FLIPFLOPDMS)
		{
			writeWidthLimited(portNames[CK] + portNames[JORD] + portNames[PRE] + portNames[CLR] + " /" + portNames[Q]+ portNames[QB]);
		} else
		{
			writeWidthLimited(portNames[CK] + portNames[JORD] + portNames[K] + portNames[PRE] + portNames[CLR] + " /" + portNames[Q] + portNames[QB]);
		}
	}

	/**
	 * Method to convert special names to SILOS format
	 */
	private String convertSpecialNames(String str)
	{
		if (str.equals("vdd")) return ".VDD";
		if (str.equals("vss")) return ".VSS";
		if (str.equals("vcc")) return ".VCC";
		if (str.equals("gnd")) return ".GND";
		if (str.equals("low")) return ".GND";
		if (str.equals("hig")) return ".VDD";
		return str;
	}

	/**
	 * replace subscripted name with __ format
	 */
	private String convertSubscripts(String string)
	{
		StringBuffer sb = new StringBuffer();
		for(int i=0; i<string.length(); i++)
		{
			char chr = string.charAt(i);
			if (chr == '[') { sb.append("__"); continue; }
			if (chr == ']') continue;
			sb.append(chr);
		}
		return sb.toString();
	}

	/**
	 * routine to replace all non-printing characters
	 * in the string "p" with the letter "X" and return the string
	 * We will not permit a digit in the first location; replace it
	 * with '_'
	 */
	private String convertName(String p)
	{
		int len = p.length();
		if (len <= 0) return p;
		boolean defined = true;
		for(int i=0; i<len; i++) if (!Character.isDefined(p.charAt(i))) { defined = false;   break; }
		if (defined && !TextUtils.isDigit(p.charAt(0))) return p;

		StringBuffer sb = new StringBuffer();
		if (TextUtils.isDigit(p.charAt(0))) sb.append("_");
		for(int i=0; i<len; i++)
		{
			char t = p.charAt(i);
			if (Character.isDefined(t)) sb.append(t); else
				sb.append("_");
		}
		return sb.toString();
	}

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

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

	/** Method to return the proper name of Power */
	protected String getPowerName(Network net) { return ".VDD"; }

	/** Method to return the proper name of Ground */
	protected String getGroundName(Network net) { return ".GND"; }

	/** 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 take precedence over
	 * arc names when determining the name of the network. */
	protected boolean isNetworksUseExportedNames() { return true; }

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

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

	/** 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 true; }

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

	/**
	 * Method to adjust a network name to be safe for Silos output.
	 */
	protected String getSafeNetName(String name, boolean bus) { return name; }

    /** Tell the Hierarchy enumerator how to short resistors */
    @Override
    protected Netlist.ShortResistors getShortResistors() { return Netlist.ShortResistors.ALL; }

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