/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Generate.java
 * Generate VHDL from a circuit
 * Written by Steven M. Rubin, Sun Microsystems.
 *
 * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.io.output;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
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.NodeProto;
import com.sun.electric.database.prototype.PortCharacteristic;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.PrefPackage;
import com.sun.electric.database.text.Version;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.topology.NodeInst;
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.PrimitiveNode;
import com.sun.electric.technology.PrimitivePort;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.simulation.SimulationTool;
import com.sun.electric.tool.user.CompileVHDL;
import com.sun.electric.util.TextUtils;

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.prefs.Preferences;

/**
 * This is the VHDL generation facility.
 */
public class GenerateVHDL extends Topology
{
	/** special codes during VHDL generation */
	/** ordinary block */			private static final int BLOCKNORMAL   = 0;
	/** a MOS transistor */			private static final int BLOCKMOSTRAN  = 1;
	/** a buffer */					private static final int BLOCKBUFFER   = 2;
	/** an and, or, xor */			private static final int BLOCKPOSLOGIC = 3;
	/** an inverter */				private static final int BLOCKINVERTER = 4;
	/** a nand */					private static final int BLOCKNAND     = 5;
	/** a nor */					private static final int BLOCKNOR      = 6;
	/** an xnor */					private static final int BLOCKXNOR     = 7;
	/** a settable D flip-flop */	private static final int BLOCKFLOPDS   = 8;
	/** a resettable D flip-flop */	private static final int BLOCKFLOPDR   = 9;
	/** a settable T flip-flop */	private static final int BLOCKFLOPTS  = 10;
	/** a resettable T flip-flop */	private static final int BLOCKFLOPTR  = 11;
	/** a general flip-flop */		private static final int BLOCKFLOP    = 12;

	private static final String NORMALCONTINUATIONSTRING = "    ";
	private static final String COMMENTCONTINUATIONSTRING = "-- ";

    private final VHDLPreferences vp;

    public static class VHDLPreferences extends PrefPackage {
        private static final String KEY_VHDL = "SchematicVHDLStringFor";

        public Map<PrimitiveNode,String> vhdlNames = new HashMap<PrimitiveNode,String>();

        public VHDLPreferences(boolean factory)
        {
            super(factory);

            Preferences techPrefs = getPrefRoot().node(TECH_NODE);
            Schematics schTech = Schematics.tech();
            for (Iterator<PrimitiveNode> it = schTech.getNodes(); it.hasNext(); ) {
                PrimitiveNode pn = it.next();

                String key = KEY_VHDL + pn.getName();
                String factoryVhdl = schTech.getFactoryVHDLNames(pn);
                String vhdl = techPrefs.get(key, factoryVhdl);
                vhdlNames.put(pn, vhdl);
            }
        }

        /**
         * Store annotated option fields of the subclass into the speciefied Preferences subtree.
         * @param prefRoot the root of the Preferences subtree.
         * @param removeDefaults remove from the Preferences subtree options which have factory default value.
         */
        @Override
        public void putPrefs(Preferences prefRoot, boolean removeDefaults) {
            super.putPrefs(prefRoot, removeDefaults);
            Preferences techPrefs = prefRoot.node(TECH_NODE);
            Schematics schTech = Schematics.tech();
            for (Map.Entry<PrimitiveNode,String> e: vhdlNames.entrySet()) {
                PrimitiveNode pn = e.getKey();
                String key = KEY_VHDL + pn.getName();
                String factoryVhdl = schTech.getFactoryVHDLNames(pn);
                String vhdl = e.getValue();
                if (removeDefaults && vhdl.equals(factoryVhdl))
                    techPrefs.remove(key);
                else
                    techPrefs.put(key, vhdl);
            }
        }
    }

    private GenerateVHDL(VHDLPreferences vp) {
        this.vp = vp;
    }

	/**
	 * Method to convert a cell to a list of strings with VHDL in them.
	 * @param cell the Cell to convert.
	 * @return a list of strings with VHDL in them (null on error).
	 */
	public static List<String> convertCell(Cell cell, VHDLPreferences vp)
	{
		// cannot make VHDL for cell with no ports
		if (cell.getNumPorts() == 0)
		{
			System.out.println("Cannot convert " + cell.describe(false) + " to VHDL: it has no ports");
			return null;
		}

		GenerateVHDL out = new GenerateVHDL(vp);
		out.openStringsOutputStream();
		out.setOutputWidth(80, false);
		out.setContinuationString(NORMALCONTINUATIONSTRING);

		// generate the VHDL
		if (out.writeCell(cell, null)) return null;

		// return the array of strings with VHDL
		return out.closeStringsOutputStream();
	}

	/**
	 * Method to start the output.
	 * Writes the header.
	 */
	protected void start()
	{
		setContinuationString(COMMENTCONTINUATIONSTRING);
		writeWidthLimited("-- VHDL automatically generated by the Electric VLSI Design System, version " +
			Version.getVersion() + "\n");
		setContinuationString(NORMALCONTINUATIONSTRING);
	}

	protected void done() {}

	/**
	 * Method to write one level of hierarchy.
	 */
	protected void writeCellTopology(Cell cell, String cellName, CellNetInfo cni, VarContext context, Topology.MyCellInfo info)
	{
		// write the header
		writeWidthLimited("\n");
		setContinuationString(COMMENTCONTINUATIONSTRING);
		writeWidthLimited("-------------------- Cell " + cell.describe(false) + " --------------------\n");
		setContinuationString(NORMALCONTINUATIONSTRING);
		Netlist nl = cni.getNetList();

		// write the entity section
		String properCellName = getSafeCellName(cell.getName());
		writeWidthLimited("entity " + addString(properCellName, null) + " is port(" + addPortList(cni) + ");\n");
		writeWidthLimited("  end " + addString(properCellName, null)  + ";\n");

		// write the "architecture" line
		writeWidthLimited("\n");
		writeWidthLimited("architecture " + addString(properCellName, null)  + "_BODY of " +
			addString(properCellName, null) + " is\n");

		// find all negated arcs
		int instNum = 1;
		Map<ArcInst,Integer> negatedHeads = new HashMap<ArcInst,Integer>();
		Map<ArcInst,Integer> negatedTails = new HashMap<ArcInst,Integer>();
		for(Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); )
		{
			ArcInst ai = it.next();
			if (ai.getHead().isNegated()) negatedHeads.put(ai, new Integer(instNum++));
			if (ai.getTail().isNegated()) negatedTails.put(ai, new Integer(instNum++));
		}

		// write prototypes for each node; gather information for NAND, NOR, and XNOR
		Set<Integer> multiInputNAND = new HashSet<Integer>();
		Set<Integer> multiInputNOR = new HashSet<Integer>();
		Set<Integer> multiInputNXOR = new HashSet<Integer>();
		boolean gotInverters = false;
		Set<String> cellNamesWritten = new HashSet<String>();
		for(Iterator<Nodable> it = nl.getNodables(); it.hasNext(); )
		{
			Nodable no = it.next();
			AnalyzePrimitive ap = new AnalyzePrimitive(no, negatedHeads, negatedTails, vp);
			String pt = ap.getPrimName();
			if (pt == null) continue;
			int special = ap.getSpecial();

			// write only once per prototype
			if (special == BLOCKINVERTER)
			{
				gotInverters = true;
				continue;
			}
			if (special == BLOCKNAND)
			{
				multiInputNAND.add(new Integer(TextUtils.atoi(pt.substring(4))));
				continue;
			}
			if (special == BLOCKNOR)
			{
				multiInputNOR.add(new Integer(TextUtils.atoi(pt.substring(3))));
				continue;
			}
			if (special == BLOCKXNOR)
			{
				multiInputNXOR.add(new Integer(TextUtils.atoi(pt.substring(4))));
				continue;
			}

			// ignore component with no ports
			if (no.getProto().getNumPorts() == 0) continue;

			pt = getSafeCellName(pt);
			if (cellNamesWritten.contains(pt)) continue;
			cellNamesWritten.add(pt);

			if (no.isCellInstance())
			{
				String parameterizedName = parameterizedName(no, context);
				CellNetInfo subCni = getCellNetInfo(parameterizedName);
				if (subCni == null)
				{
					System.out.println("ERROR: no subcell information for: " + parameterizedName);
					continue;
				}
				writeWidthLimited("  component " + addString(pt, null) + " port(" + addPortList(subCni) + ");\n");
			} else
			{
				writeWidthLimited("  component " + addString(pt, null) + " port(" + addPortListPrim(no, special) + ");\n");
			}
			writeWidthLimited("    end component;\n");
		}

		// write prototype for multi-input NAND, NOR, and XNOR
		for(Integer i : multiInputNAND)
		{
			String compName = "nand" + i;
			cellNamesWritten.add(compName);
			writeWidthLimited("  component " + compName + " port(");
			for(int j=1; j<=i.intValue(); j++)
			{
				if (j > 1) writeWidthLimited(", ");
				writeWidthLimited("a" + j);
			}
			writeWidthLimited(": in BIT; y: out BIT);\n");
			writeWidthLimited("    end component;\n");
		}
		for(Integer i : multiInputNOR)
		{
			String compName = "nor" + i;
			cellNamesWritten.add(compName);
			writeWidthLimited("  component " + compName + " port(");
			for(int j=1; j<=i.intValue(); j++)
			{
				if (j > 1) writeWidthLimited(", ");
				writeWidthLimited("a" + j);
			}
			writeWidthLimited(": in BIT; y: out BIT);\n");
			writeWidthLimited("    end component;\n");
		}
		for(Integer i : multiInputNXOR)
		{
			String compName = "xnor" + i;
			cellNamesWritten.add(compName);
			writeWidthLimited("  component " + compName + " port(");
			for(int j=1; j<=i.intValue(); j++)
			{
				if (j > 1) writeWidthLimited(", ");
				writeWidthLimited("a" + j);
			}
			writeWidthLimited(": in BIT; y: out BIT);\n");
			writeWidthLimited("    end component;\n");
		}

		// write inverter prototype if applicable
		if (negatedHeads.size() > 0 || negatedTails.size() > 0) gotInverters = true;
		if (gotInverters)
		{
			cellNamesWritten.add("inverter");
			writeWidthLimited("  component inverter port(a: in BIT; y: out BIT);\n");
			writeWidthLimited("    end component;\n");
		}

		// write internal signals that were used
		SignalNameLine snl = new SignalNameLine();
		for(Iterator<CellSignal> it = cni.getCellSignals(); it.hasNext(); )
		{
			CellSignal cs = it.next();
			if (cs.getExport() != null) continue;
			if (!cs.getNetwork().getArcs().hasNext()) continue;
			String sigName = addString(cs.getName(), cell);
			snl.addSignalName(sigName);
		}
		for(ArcInst ai : negatedHeads.keySet())
		{
			Integer index = negatedHeads.get(ai);
			String sigName = "PINV" + index.intValue();
			snl.addSignalName(sigName);
		}
		for(ArcInst ai : negatedTails.keySet())
		{
			Integer index = negatedTails.get(ai);
			String sigName = "PINV" + index.intValue();
			snl.addSignalName(sigName);
		}
		snl.finish();

		// write the instances
		writeWidthLimited("\n");
		writeWidthLimited("begin\n");
		for(Iterator<Nodable> it = nl.getNodables(); it.hasNext(); )
		{
			Nodable no = it.next();

			// ignore component with no ports
			if (no.getProto().getNumPorts() == 0) continue;

			int special = BLOCKNORMAL;
			String pt = no.getProto().getName();
			if (!no.isCellInstance())
			{
				AnalyzePrimitive ap = new AnalyzePrimitive(no, negatedHeads, negatedTails, vp);
				pt = ap.getPrimName();
				if (pt == null) continue;
				special = ap.getSpecial();
			}

			String instname = getSafeCellName(no.getName());
			writeWidthLimited("  " + addString(instname, null));

			// make sure the instance name doesn't conflict with a prototype name
			if (cellNamesWritten.contains(instname)) writeWidthLimited("NV");

			if (no.isCellInstance())
			{
				String parameterizedName = parameterizedName(no, context);
				CellNetInfo subCni = getCellNetInfo(parameterizedName);
				if (subCni == null)
				{
					System.out.println("STILL NO SUBCELL INFORMATION FOR "+parameterizedName);
					continue;
				}
				writeWidthLimited(": " + addString(getSafeCellName(pt), null) + " port map(" +
					addRealPortsCell(subCni, no, negatedHeads, negatedTails, nl) + ");\n");
			} else
			{
				writeWidthLimited(": " + addString(getSafeCellName(pt), null) + " port map(" +
					addRealPortsPrim(no, special, negatedHeads, negatedTails, nl) + ");\n");
			}
		}

		// write pseudo-nodes for all negated arcs
		for(ArcInst ai : negatedHeads.keySet())
		{
			Integer index = negatedHeads.get(ai);
			writeWidthLimited("  PSEUDO_INVERT" + index.intValue() + ": inverter port map(");
			Network net = nl.getNetwork(ai, 0);
			if (ai.getHeadPortInst().getPortProto().getBasePort().getCharacteristic() == PortCharacteristic.OUT)
			{
				writeWidthLimited("PINV" + index.intValue() + ", " + addString(net.describe(false), cell));
			} else
			{
				writeWidthLimited(addString(net.describe(false), cell) + ", PINV" + index.intValue());
			}
			writeWidthLimited(");\n");
		}
		for(ArcInst ai : negatedTails.keySet())
		{
			Integer index = negatedTails.get(ai);
			writeWidthLimited("  PSEUDO_INVERT" + index.intValue() + ": inverter port map(");
			Network net = nl.getNetwork(ai, 0);
			if (ai.getTailPortInst().getPortProto().getBasePort().getCharacteristic() == PortCharacteristic.OUT)
			{
				writeWidthLimited("PINV" + index.intValue() + ", " + addString(net.describe(false), cell));
			} else
			{
				writeWidthLimited(addString(net.describe(false), cell) + ", PINV" + index.intValue());
			}
			writeWidthLimited(");\n");
		}

		// finish the cell
		writeWidthLimited("end " + addString(properCellName, null) + "_BODY;\n");
	}

	/****************************** METHODS TO WRITE LIST OF CELL PARAMETERS ******************************/

	/**
	 * Method to write actual signals that connect to a cell instance.
	 * @param cni signal information for the cell being instantiated.
	 * @param no the instance node.
	 * @param negatedHeads map of arcs with negated head ends.
	 * @param negatedTails map of arcs with negated tail ends.
	 * @param nl the Netlist for the Cell containing the instance.
	 * @return a string with the connection signals.
	 */
	private String addRealPortsCell(CellNetInfo cni, Nodable no, Map<ArcInst,Integer> negatedHeads,
		Map<ArcInst,Integer> negatedTails, Netlist nl)
	{
		Cell subCell = (Cell)no.getProto();
		Netlist subNL = cni.getNetList();
		boolean first = false;
		StringBuffer infstr = new StringBuffer();
		for(int pass = 0; pass < 5; pass++)
		{
			for(Iterator<Export> it = subCell.getExports(); it.hasNext(); )
			{
				Export e = it.next();
				if (!matchesPass(e.getCharacteristic(), pass)) continue;

				int exportWidth = subNL.getBusWidth(e);
				for(int i=0; i<exportWidth; i++)
				{
					Network net = nl.getNetwork(no, e, i);

					// get connection
					boolean portNamed = false;
					for(Iterator<Connection> cIt = no.getNodeInst().getConnections(); cIt.hasNext(); )
					{
						Connection con = cIt.next();
						PortProto otherPP = con.getPortInst().getPortProto();
						if (otherPP instanceof Export)
						{
							Export otherE = (Export)otherPP;
					        Cell equiv = otherE.getParent().getEquivalent();
							otherPP = otherE.findEquivalent(equiv);
						}
						if (otherPP == e)
						{
							ArcInst ai = con.getArc();
							if (ai.getProto().getFunction() != ArcProto.Function.NONELEC)
							{
								if (con.isNegated())
								{
									Integer index;
									if (con.getEndIndex() == ArcInst.HEADEND) index = negatedHeads.get(ai); else
										index = negatedTails.get(ai);
									if (index != null)
									{
										if (first) infstr.append(", ");   first = true;
										String sigName = "PINV" + index.intValue();
										infstr.append(sigName);
										continue;
									}
								}
							}
							break;
						}
					}
					if (portNamed) continue;

					// write connection
					String sigName = addString(net.getName(), null);
					if (!net.isExported() && !net.getArcs().hasNext()) sigName = "open";
					if (first) infstr.append(", ");   first = true;
					infstr.append(sigName);
				}
			}
		}
		return infstr.toString();
	}

	/**
	 * Method to determine whether a port connects to well/substrate.
	 * This is used on transistors to detect the bias port.
	 * @param pp the port in question.
	 * @return true if the port connects to well/substrate arcs.
	 */
	private boolean isBiasPort(PrimitivePort pp)
	{
		ArcProto [] connections = pp.getConnections();
		for(int i=0; i<connections.length; i++)
		{
			ArcProto ap = connections[i];
			if (ap.getTechnology() == Generic.tech()) continue;
			if (ap.getFunction() == ArcProto.Function.WELL) return true;
		}
		return false;
	}

	/**
	 * Method to write actual signals that connect to a primitive instance.
	 * @param no the primitive node.
	 * @param special a code describing special features of the primitive.
	 * @param negatedHeads map of arcs with negated head ends.
	 * @param negatedTails map of arcs with negated tail ends.
	 * @param nl the Netlist for the Cell containing the instance.
	 * @return a string with the connection signals.
	 */
	private String addRealPortsPrim(Nodable no, int special, Map<ArcInst,Integer> negatedHeads,
		Map<ArcInst,Integer> negatedTails, Netlist nl)
	{
		NodeProto np = no.getProto();
		boolean first = false;
		StringBuffer infstr = new StringBuffer();
		for(int pass = 0; pass < 5; pass++)
		{
			for(Iterator<PortProto> it = np.getPorts(); it.hasNext(); )
			{
				PortProto pp = it.next();

				// ignore the bias port of 4-port transistors
				if (np == Schematics.tech().transistor4Node)
				{
					if (pp.getName().equals("b")) continue;
				}

				// ignore the bias port of layout transistors
				if (np.getFunction().isTransistor() && isBiasPort((PrimitivePort)pp)) continue;

				if (!matchesPass(pp.getCharacteristic(), pass)) continue;

				if (special == BLOCKMOSTRAN)
				{
					// ignore electrically connected ports
					boolean connected = false;
					for(Iterator<PortProto> oIt = np.getPorts(); oIt.hasNext(); )
					{
						PrimitivePort oPp = (PrimitivePort)oIt.next();
						if (oPp == pp) break;
						if (oPp.getTopology() == ((PrimitivePort)pp).getTopology()) { connected = true;   break; }
					}
					if (connected) continue;
				}
				if (special == BLOCKPOSLOGIC || special == BLOCKBUFFER || special == BLOCKINVERTER ||
					special == BLOCKNAND || special == BLOCKNOR || special == BLOCKXNOR)
				{
					// ignore ports not named "a" or "y"
					if (!pp.getName().equals("a") && !pp.getName().equals("y")) continue;
				}
				if (special == BLOCKFLOPTS || special == BLOCKFLOPDS)
				{
					// ignore ports not named "i1", "ck", "preset", or "q"
					if (!pp.getName().equals("i1") && !pp.getName().equals("ck") &&
						!pp.getName().equals("preset") && !pp.getName().equals("q")) continue;
				}
				if (special == BLOCKFLOPTR || special == BLOCKFLOPDR)
				{
					// ignore ports not named "i1", "ck", "clear", or "q"
					if (!pp.getName().equals("i1") && !pp.getName().equals("ck") &&
						!pp.getName().equals("clear") && !pp.getName().equals("q")) continue;
				}

				// if multiple connections, get them all
				if (pp.getBasePort().isIsolated())
				{
					for(Iterator<Connection> cIt = no.getNodeInst().getConnections(); cIt.hasNext(); )
					{
						Connection con = cIt.next();
						if (con.getPortInst().getPortProto() != pp) continue;
						ArcInst ai = con.getArc();
						ArcProto.Function fun = ai.getProto().getFunction();
						if (fun == ArcProto.Function.NONELEC) continue;
						String sigName = "open";
						Network net = nl.getNetwork(ai, 0);
						if (net != null)
							sigName = addString(net.describe(false), no.getParent());
						if (con.isNegated())
						{
							Integer index;
							if (con.getEndIndex() == ArcInst.HEADEND) index = negatedHeads.get(ai); else
								index = negatedTails.get(ai);
							if (index != null) sigName = "PINV" + index.intValue();
						}
						if (first) infstr.append(", ");   first = true;
						infstr.append(sigName);
					}
					continue;
				}

				// get connection
				boolean portNamed = false;
				for(Iterator<Connection> cIt = no.getNodeInst().getConnections(); cIt.hasNext(); )
				{
					Connection con = cIt.next();
					PortProto otherPP = con.getPortInst().getPortProto();
//					if (otherPP instanceof Export) otherPP = ((Export)otherPP).getEquivalent();
					boolean aka = false;
					if (otherPP instanceof PrimitivePort && pp instanceof PrimitivePort)
					{
						if (((PrimitivePort)otherPP).getTopology() == ((PrimitivePort)pp).getTopology()) aka = true;
					}
					if (otherPP == pp || aka)
					{
						ArcInst ai = con.getArc();
						if (ai.getProto().getFunction() != ArcProto.Function.NONELEC)
						{
							if (con.isNegated())
							{
								Integer index;
								if (con.getEndIndex() == ArcInst.HEADEND) index = negatedHeads.get(ai); else
									index = negatedTails.get(ai);
								if (index != null)
								{
									if (first) infstr.append(", ");   first = true;
									String sigName = "PINV" + index.intValue();
									infstr.append(sigName);
									continue;
								}
							}

							int wid = nl.getBusWidth(ai);
							for(int i=0; i<wid; i++)
							{
								if (first) infstr.append(", ");   first = true;
								Network subNet = nl.getNetwork(ai, i);
								String subNetName = getOneNetworkName(subNet);
								String sigName = addString(subNetName, no.getParent());
								infstr.append(sigName);
							}
							portNamed = true;
						}
						break;
					}
				}
				if (portNamed) continue;

				for(Iterator<Export> eIt = no.getNodeInst().getExports(); eIt.hasNext(); )
				{
					Export e = eIt.next();
					PortProto otherPP = e.getOriginalPort().getPortProto();
					if (otherPP instanceof Export)
					{
						Export otherE = (Export)otherPP;
				        Cell equiv = otherE.getParent().getEquivalent();
						otherPP = otherE.findEquivalent(equiv);
					}
					if (otherPP == pp)
					{
						int wid = nl.getBusWidth(e);
						for(int i=0; i<wid; i++)
						{
							if (first) infstr.append(", ");   first = true;
							Network subNet = nl.getNetwork(e, i);
							String subNetName = getOneNetworkName(subNet);
							infstr.append(addString(subNetName, no.getParent()));
						}
						portNamed = true;
						break;
					}
				}
				if (portNamed) continue;

				// port is not connected or an export
				if (first) infstr.append(", ");   first = true;
				infstr.append("open");
			}
		}
		return infstr.toString();
	}

	/**
	 * Method to return a list of signals connected to a primitive.
	 * @param no the primitive Nodable being written.
	 * @param special special situation for that Nodable.
	 * If "special" is BLOCKPOSLOGIC, BLOCKBUFFER or BLOCKINVERTER, only include input port "a" and output port "y".
	 * If "special" is BLOCKFLOPTS or BLOCKFLOPDS, only include input ports "i1", "ck", "preset" and output port "q".
	 * If "special" is BLOCKFLOPTR or BLOCKFLOPDR, only include input ports "i1", "ck", "clear" and output port "q".
	 */
	private String addPortListPrim(Nodable no, int special)
	{
		// emit special flip-flop ports
		if (special == BLOCKFLOPTS || special == BLOCKFLOPDS)
			return "i1, ck, preset: in BIT; q: out BIT";
		if (special == BLOCKFLOPTR || special == BLOCKFLOPDR)
			return "i1, ck, clear: in BIT; q: out BIT";

		String before = "";
		StringBuffer infstr = new StringBuffer();
		PrimitiveNode pnp = (PrimitiveNode)no.getProto();
		for(int pass = 0; pass < 5; pass++)
		{
			boolean didsome = false;
			for(Iterator<PrimitivePort> it = pnp.getPrimitivePorts(); it.hasNext(); )
			{
				PrimitivePort pp = it.next();
				if (!matchesPass(pp.getCharacteristic(), pass)) continue;
				String portName = pp.getName();
				if (special == BLOCKPOSLOGIC || special == BLOCKBUFFER || special == BLOCKINVERTER)
				{
					// ignore ports not named "a" or "y"
					if (!portName.equals("a") && !portName.equals("y")) continue;
				}

				// ignore the bias port of layout transistors
				if (pnp.getFunction().isTransistor() && isBiasPort(pp)) continue;

				if (pp.getBasePort().isIsolated())
				{
					int inst = 1;
					for(Iterator<Connection> cIt = no.getNodeInst().getConnections(); cIt.hasNext(); )
					{
						Connection con = cIt.next();
						if (con.getPortInst().getPortProto() != pp) continue;
						infstr.append(before);   before = ", ";
						String exportName = addString(portName, null) + (inst++);
						infstr.append(exportName);
					}
				} else
				{
					// ignore if connected to an earlier port
					boolean seen = false;
					for(Iterator<PrimitivePort> eIt = pnp.getPrimitivePorts(); eIt.hasNext(); )
					{
						PrimitivePort ePp = eIt.next();
						if (ePp == pp) break;
						if (ePp.getTopology() == pp.getTopology()) seen = true;
					}
					if (seen) continue;

					infstr.append(before);   before = ", ";
					infstr.append(addString(portName, null));
				}
				didsome = true;
			}
			if (didsome)
			{
				if (pass == 0)
				{
					infstr.append(": in BIT");
				} else if (pass == 1 || pass == 2 || pass == 3)
				{
					infstr.append(": out BIT");
				} else
				{
					infstr.append(": inout BIT");
				}
				before = "; ";
			}
		}
		return infstr.toString();
	}

	/**
	 * Method to construct a list of export names for a cell.
	 * @param cni the cell information.
	 * @return a list of export names for the Cell.
	 */
	private String addPortList(CellNetInfo cni)
	{
		String before = "";
		StringBuffer infstr = new StringBuffer();
		for(int pass = 0; pass < 5; pass++)
		{
			boolean didsome = false;
			for(Iterator<CellSignal> it = cni.getCellSignals(); it.hasNext(); )
			{
				CellSignal cs = it.next();
				Export e = cs.getExport();
				if (e == null) continue;
				if (!matchesPass(e.getCharacteristic(), pass)) continue;
				infstr.append(before);   before = ", ";
				infstr.append(addString(cs.getName(), null));
				didsome = true;
			}
			if (didsome)
			{
				if (pass == 0)
				{
					infstr.append(": in BIT");
				} else if (pass == 1 || pass == 2 || pass == 3)
				{
					infstr.append(": out BIT");
				} else
				{
					infstr.append(": inout BIT");
				}
				before = "; ";
			}
		}
		return infstr.toString();
	}

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

	/**
	 * Method to determine whether a type of export goes in a particular pass of output.
	 * Ports are written in 5 passes: input, output, power, ground, and everything else.
	 * @param ch the PortCharacteristic of the port.
	 * @param pass the pass number (0-4).
	 * @return true of the given type of port goes in the given pass.
	 */
	private boolean matchesPass(PortCharacteristic ch, int pass)
	{
		switch (pass)
		{
			case 0:			// must be an input port
				return ch == PortCharacteristic.IN;
			case 1:			// must be an output port
				return ch == PortCharacteristic.OUT;
			case 2:			// must be a power port
				return ch == PortCharacteristic.PWR;
			case 3:			// must be a ground port
				return ch == PortCharacteristic.GND;
		}
		return ch != PortCharacteristic.IN && ch != PortCharacteristic.OUT &&
			ch != PortCharacteristic.PWR && ch != PortCharacteristic.GND;
	}

	/**
	 * Method to return a single name for a Network.
	 * Choose the first if there are more than one.
	 * @param net the Network to name.
	 * @return the name of the Network.
	 */
	private String getOneNetworkName(Network net)
	{
		Iterator<String> nIt = net.getNames();
		if (nIt.hasNext()) return nIt.next();
		return net.describe(false);
	}

	/**
	 * Class to determine the VHDL name and special factors for a node.
	 */
	private static class AnalyzePrimitive
	{
		private String primName;
		private int special;

		/**
		 * Method to get the name of this analyzed primitive node.
		 * @return the name of this analyzed primitive node.
		 */
		private String getPrimName() { return primName; }

		/**
		 * Method to return the special code for this analyzed primitive node:
		 * @return the special code for the analyzed primitive node:<BR>
		 * BLOCKNORMAL: no special port arrangements necessary.<BR>
		 * BLOCKMOSTRAN: only output ports that are not electrically connected.<BR>
		 * BLOCKBUFFER: only include input port "a" and output port "y".<BR>
		 * BLOCKPOSLOGIC: only include input port "a" and output port "y".<BR>
		 * BLOCKINVERTER: only include input port "a" and output port "y".<BR>
		 * BLOCKNAND: only include input port "a" and output port "y".<BR>
		 * BLOCKNOR: only include input port "a" and output port "y".<BR>
		 * BLOCKXNOR: only include input port "a" and output port "y".<BR>
		 * BLOCKFLOPTS: only include input ports "i1", "ck", "preset" and output port "q".<BR>
		 * BLOCKFLOPTR: only include input ports "i1", "ck", "clear" and output port "q".<BR>
		 * BLOCKFLOPDS: only include input ports "i1", "ck", "preset" and output port "q".<BR>
		 * BLOCKFLOPDR: only include input ports "i1", "ck", "clear" and output port "q".<BR>
		 * BLOCKFLOP: include input ports "i1", "i2", "ck", "preset", "clear", and output ports "q" and "qb".
		 */
		private int getSpecial() { return special; }

		/**
		 * Constructor which analyzes a primitive node.
		 * @param no the primitive node.
		 * @param negatedHeads map of arcs with negated head ends.
		 * @param negatedTails map of arcs with negated tail ends.
		 */
		private AnalyzePrimitive(Nodable no, Map<ArcInst,Integer> negatedHeads, Map<ArcInst,Integer> negatedTails, VHDLPreferences vp)
		{
			// cell instances are easy
			special = BLOCKNORMAL;
			if (no.isCellInstance()) { primName = no.getProto().getName();   return; }
			NodeInst ni = no.getNodeInst();

			// get the primitive function
			PrimitiveNode.Function k = ni.getFunction();
			primName = null;
			if (k == PrimitiveNode.Function.TRADMOS || k == PrimitiveNode.Function.TRA4DMOS)
			{
				primName = "DMOStran";
				special = BLOCKMOSTRAN;
			} else if (k.isNTypeTransistor())
			{
				primName = "nMOStran";
				Variable var = no.getVar(SimulationTool.WEAK_NODE_KEY);
				if (var != null) primName = "nMOStranWeak";
				special = BLOCKMOSTRAN;
			} else if (k.isPTypeTransistor())
			{
				primName = "PMOStran";
				Variable var = no.getVar(SimulationTool.WEAK_NODE_KEY);
				if (var != null) primName = "PMOStranWeak";
				special = BLOCKMOSTRAN;
			} else if (k == PrimitiveNode.Function.TRANPN || k == PrimitiveNode.Function.TRA4NPN)
			{
				primName = "NPNtran";
			} else if (k == PrimitiveNode.Function.TRAPNP || k == PrimitiveNode.Function.TRA4PNP)
			{
				primName = "PNPtran";
			} else if (k == PrimitiveNode.Function.TRANJFET || k == PrimitiveNode.Function.TRA4NJFET)
			{
				primName = "NJFET";
			} else if (k == PrimitiveNode.Function.TRAPJFET || k == PrimitiveNode.Function.TRA4PJFET)
			{
				primName = "PJFET";
			} else if (k == PrimitiveNode.Function.TRADMES || k == PrimitiveNode.Function.TRA4DMES)
			{
				primName = "DMEStran";
			} else if (k == PrimitiveNode.Function.TRAEMES || k == PrimitiveNode.Function.TRA4EMES)
			{
				primName = "EMEStran";
			} else if (k == PrimitiveNode.Function.FLIPFLOPRSMS || k == PrimitiveNode.Function.FLIPFLOPRSN || k == PrimitiveNode.Function.FLIPFLOPRSP)
			{
				primName = "rsff";
				special = BLOCKFLOP;
			} else if (k == PrimitiveNode.Function.FLIPFLOPJKMS || k == PrimitiveNode.Function.FLIPFLOPJKN || k == PrimitiveNode.Function.FLIPFLOPJKP)
			{
				primName = "jkff";
				special = BLOCKFLOP;
			} else if (k == PrimitiveNode.Function.FLIPFLOPDMS || k == PrimitiveNode.Function.FLIPFLOPDN || k == PrimitiveNode.Function.FLIPFLOPDP)
			{
				primName = "dsff";
				special = BLOCKFLOPDS;
				for(Iterator<Connection> it = ni.getConnections(); it.hasNext(); )
				{
					Connection con = it.next();
					if (con.getPortInst().getPortProto().getName().equals("clear"))
					{
						primName = "drff";
						special = BLOCKFLOPDR;
						break;
					}
				}
			} else if (k == PrimitiveNode.Function.FLIPFLOPTMS || k == PrimitiveNode.Function.FLIPFLOPTN || k == PrimitiveNode.Function.FLIPFLOPTP)
			{
				primName = "tsff";
				special = BLOCKFLOPTS;
				for(Iterator<Connection> it = ni.getConnections(); it.hasNext(); )
				{
					Connection con = it.next();
					if (con.getPortInst().getPortProto().getName().equals("clear"))
					{
						primName = "trff";
						special = BLOCKFLOPTR;
						break;
					}
				}
			} else if (k == PrimitiveNode.Function.BUFFER)
			{
				primName = vp.vhdlNames.get(Schematics.tech().bufferNode);
				int slashPos = primName.indexOf('/');
				special = BLOCKBUFFER;
				for(Iterator<Connection> it = ni.getConnections(); it.hasNext(); )
				{
					Connection con = it.next();
					if (!con.getPortInst().getPortProto().getName().equals("y")) continue;
					if (con.isNegated())
					{
						if (slashPos >= 0) primName = primName.substring(slashPos+1);
						special = BLOCKINVERTER;
						if (con.getEndIndex() == ArcInst.HEADEND) negatedHeads.remove(con.getArc()); else
							negatedTails.remove(con.getArc());
						break;
					}
				}
				if (special == BLOCKBUFFER)
				{
					if (slashPos >= 0) primName = primName.substring(0, slashPos);
				}
			} else if (k == PrimitiveNode.Function.GATEAND)
			{
				primName = vp.vhdlNames.get(Schematics.tech().andNode);
				int slashPos = primName.indexOf('/');
				int inPort = 0;
				Connection isNeg = null;
				for(Iterator<Connection> it = ni.getConnections(); it.hasNext(); )
				{
					Connection con = it.next();
					if (con.getPortInst().getPortProto().getName().equals("a")) inPort++;
					if (!con.getPortInst().getPortProto().getName().equals("y")) continue;
					if (con.isNegated()) isNeg = con;
				}
				if (isNeg != null)
				{
					if (slashPos >= 0) primName = primName.substring(slashPos+1);
					special = BLOCKNAND;
					if (isNeg.getEndIndex() == ArcInst.HEADEND) negatedHeads.remove(isNeg.getArc()); else
						negatedTails.remove(isNeg.getArc());
				} else
				{
					if (slashPos >= 0) primName = primName.substring(0, slashPos);
					special = BLOCKPOSLOGIC;
				}
				primName += inPort;
			} else if (k == PrimitiveNode.Function.GATEOR)
			{
				primName = vp.vhdlNames.get(Schematics.tech().orNode);
				int slashPos = primName.indexOf('/');
				int inPort = 0;
				Connection isNeg = null;
				for(Iterator<Connection> it = ni.getConnections(); it.hasNext(); )
				{
					Connection con = it.next();
					if (con.getPortInst().getPortProto().getName().equals("a")) inPort++;
					if (!con.getPortInst().getPortProto().getName().equals("y")) continue;
					if (con.isNegated()) isNeg = con;
				}
				if (isNeg != null)
				{
					if (slashPos >= 0) primName = primName.substring(slashPos+1);
					special = BLOCKNOR;
					if (isNeg.getEndIndex() == ArcInst.HEADEND) negatedHeads.remove(isNeg.getArc()); else
						negatedTails.remove(isNeg.getArc());
				} else
				{
					if (slashPos >= 0) primName = primName.substring(0, slashPos);
					special = BLOCKPOSLOGIC;
				}
				primName += inPort;
			} else if (k == PrimitiveNode.Function.GATEXOR)
			{
				primName = vp.vhdlNames.get(Schematics.tech().xorNode);
				int slashPos = primName.indexOf('/');
				int inPort = 0;
				Connection isNeg = null;
				for(Iterator<Connection> it = ni.getConnections(); it.hasNext(); )
				{
					Connection con = it.next();
					if (con.getPortInst().getPortProto().getName().equals("a")) inPort++;
					if (!con.getPortInst().getPortProto().getName().equals("y")) continue;
					if (con.isNegated()) isNeg = con;
				}
				if (isNeg != null)
				{
					if (slashPos >= 0) primName = primName.substring(slashPos+1);
					special = BLOCKXNOR;
					if (isNeg.getEndIndex() == ArcInst.HEADEND) negatedHeads.remove(isNeg.getArc()); else
						negatedTails.remove(isNeg.getArc());
				} else
				{
					if (slashPos >= 0) primName = primName.substring(0, slashPos);
					special = BLOCKPOSLOGIC;
				}
				primName += inPort;
			} else if (k == PrimitiveNode.Function.MUX)
			{
				primName = vp.vhdlNames.get(Schematics.tech().muxNode);
				int inPort = 0;
				for(Iterator<Connection> it = ni.getConnections(); it.hasNext(); )
				{
					Connection con = it.next();
					if (con.getPortInst().getPortProto().getName().equals("a")) inPort++;
				}
				primName += inPort;
			} else if (k == PrimitiveNode.Function.CONPOWER)
			{
				primName = "power";
			} else if (k == PrimitiveNode.Function.CONGROUND)
			{
				primName = "ground";
			}
			if (primName == null)
			{
				// if the node has an export with power/ground, make it that
				for(Iterator<Export> it = ni.getExports(); it.hasNext(); )
				{
					Export e = it.next();
					if (e.isPower())
					{
						primName = "power";
						break;
					}
					if (e.isGround())
					{
						primName = "ground";
						break;
					}
				}
			}
		}
	}

	/**
	 * Class to aggregate signal names and write them on length-limited lines.
	 */
	private class SignalNameLine
	{
		private boolean hasContent = false;

		public void addSignalName(String sigName)
		{
			if (!hasContent)
			{
				writeWidthLimited("\n");
				writeWidthLimited("  signal ");
			} else
			{
				writeWidthLimited(", ");
			}
			hasContent = true;
			writeWidthLimited(sigName);
		}

		public void finish()
		{
			if (hasContent)
				writeWidthLimited(": BIT;\n");
		}
	}

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

	/**
	 * Method to adjust a cell name to be safe for Verilog output.
	 * @param name the cell name.
	 * @return the name, adjusted for Verilog output.
	 */
	protected String getSafeCellName(String name)
	{
		if (name.length() == 0) return name;
		char first = name.charAt(0);
		if (!Character.isLetter(first)) name = "E_" + name;
		for(int i=0; i<name.length(); i++)
		{
			char ch = name.charAt(i);
			if (Character.isLetterOrDigit(ch)) continue;
			if (ch == '_') continue;
			name = name.substring(0, i) + "_" + name.substring(i+1);
		}
		return name;
	}

	/** Method to tell the netlister to deal with all Cells. */
	protected boolean skipCellAndSubcells(Cell cell) { return false; }

	/** Method to return the proper name of Power (just use whatever name is there) */
	protected String getPowerName(Network net) { return net.getName(); }

	/** Method to return the proper name of Ground (just use whatever name is there) */
	protected String getGroundName(Network net) { return net.getName(); }

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

	/**
	 * Method to tell the netlister 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 not always prepended to cell names. */
	protected boolean isLibraryNameAlwaysAddedToCellName() { return false; }

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

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

	/** Method to tell netlister not to separate input and output names (this module does more detailed separation). */
	protected boolean isSeparateInputAndOutput() { return false; }

	/** Method to tell the netlister to be case-sensitive. */
	protected boolean isCaseSensitive() { return true; }

	/** Method to tell the netlister how to short resistors */
	protected Netlist.ShortResistors getShortResistors() { return Netlist.ShortResistors.ALL; }

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

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

	/**
	 * Method to add the string "orig" to the infinite string.
	 * If "environment" is not NONODEPROTO, it is the cell in which this signal is
	 * to reside, and if that cell has nodes with this name, the signal must be renamed.
	 */
	private String addString(String orig, Cell environment)
	{
		// remove all nonVHDL characters while adding to current string
		StringBuffer sb = new StringBuffer();
		boolean nonAlnum = false;
		for(int i=0; i<orig.length(); i++)
		{
			char chr = orig.charAt(i);
			if (Character.isLetterOrDigit(chr)) sb.append(chr); else
			{
				sb.append('_');
				nonAlnum = true;
			}
		}

		// if there were nonalphanumeric characters, this cannot be a VHDL keyword
		if (!nonAlnum)
		{
			// check for VHDL keyword clashes
			if (CompileVHDL.isKeyword(orig) != null)
			{
				sb.append('_');
				return sb.toString();
			}

			// "bit" isn't a keyword, but the compiler can't handle it
			if (orig.equalsIgnoreCase("bit"))
			{
				sb.append('_');
				return sb.toString();
			}
		}

		// see if there is a name clash
		if (environment != null)
		{
			for(Iterator<NodeInst> it = environment.getNodes(); it.hasNext(); )
			{
				NodeInst ni = it.next();
				if (!ni.isCellInstance()) continue;
				if (orig.equals(ni.getProto().getName()))
				{
					sb.append('_');
					break;
				}
			}
		}
		return sb.toString();
	}
}
