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

import com.sun.electric.database.hierarchy.*;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.text.Name;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.network.Global;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.io.output.CellModelPrefs;

import java.util.*;
import java.io.*;

/**
 * User: gainsley
 * Date: Nov 15, 2005
 */
public class ScanChainXML extends HierarchyEnumerator.Visitor {

    private HashMap<Cell,JtagController> jtagControllers;
    private JtagController currentJtagController;
    private HashMap<Cell,Cell> cellsToFlatten;
    private HashMap<Cell,Set<TraceElement>> elements;
    private HashMap<String,String> dataNetMap;      // key: full hierarchical name, value: flat name
    private HashMap<Starter,Starter> starters;
    private HashMap<Starter,String> duplicateStarters; // key: chain, value: chain to duplicate
    private HashMap<String,String> startFromExport; // key: export name, value: chain name
    private List<String> chipNames;
    private boolean uniqueEntityNames = true;
    private int currentChipName = 0;
    private String outputFile;
    private PrintWriter out;
    private File outFile;
    private boolean debugTracing;
    private boolean optimize;
    private String chipTDI;
    private String chipTDO;
    private boolean printIgnoredElements;
    private List<String> scanElementInstanceNames;
    private boolean generateScanDataNets;
    private HashMap<Cell,Integer> elementsFlatCount;          // flat usage count

    public ScanChainXML() {
        cellsToFlatten = new HashMap<Cell,Cell>();
        elements = new HashMap<Cell,Set<TraceElement>>();
        dataNetMap = new HashMap<String,String>();
        starters = new HashMap<Starter,Starter>();
        duplicateStarters = new HashMap<Starter,String>();
        startFromExport = new HashMap<String,String>();
        jtagControllers = new HashMap<Cell,JtagController>();
        chipNames = new ArrayList<String>();
        outputFile = null;
        out = new PrintWriter(System.out);
        outFile = null;
        debugTracing = false;
        optimize = true;
        chipTDI = "TDI";
        chipTDO = "TDO";
        printIgnoredElements = true;
        scanElementInstanceNames = new ArrayList<String>();
        generateScanDataNets = true;
        elementsFlatCount = new HashMap<Cell,Integer>();

    }

    /**
     * Start tracing all the scan chains from the any instances of specified
     * jtag controller
     */
    public void start(String libName, String cellName) {
        Library lib = Library.findLibrary(libName);
        if (lib == null) {
            System.out.println("Did not find library "+libName+" for starting chain analysis in cell "+cellName);
            return;
        }
        Cell cell = lib.findNodeProto(cellName);
        if (cell == null) {
            System.out.println("Did not find cell "+cellName+" for starting chain analysis, in library "+libName);
            return;
        }

        // extract chains from hierarchy
        HierarchyEnumerator.enumerateCell(cell, VarContext.globalContext, this, Netlist.ShortResistors.ALL);

        // issue warnings for hierarchical instance names specified that were not found
        for (String name : scanElementInstanceNames) {
            System.out.println("Warning: instance not found in hierarchy: "+name);
        }

        List<Starter> chains = new ArrayList<Starter>();

        // the jtag controllers start from the TDI export
        Set<TraceElement> ents = elements.get(cell);
        Entity jtagControllerChain = null;
        for (TraceElement e : ents) {
            if (!(e instanceof Entity)) continue;
            if (e.getInport() != null && e.getInport().equals(chipTDI)) {
                jtagControllerChain = (Entity)e;
                break;
            }
        }
        if (jtagControllerChain == null && startFromExport.keySet().size() == 0) {
            System.out.println("Unable to find JtagController chain that starts with export "+chipTDI+" and ends with export "+chipTDO+". Aborting");
            return;
        }

        // get starters at this cell
        for (Starter s : starters.keySet()) {
            if (s.getCell() == cell) {
                chains.add(s);
            }
        }

        // create list of jtag instances to represent chain of jtag controllers
        // Each jtag controller is set in a "chip", because the test code can
        // handle multiple chips, but not multiple jtag controllers per chip.
        List<JtagInstance> jtags = new ArrayList<JtagInstance>();
        Set<Entity> entitiesUsed = new TreeSet<Entity>();

        if (jtagControllerChain != null) {
            List<VarContext> contexts = jtagControllerChain.getContextsOfJtagInstances();
            for (VarContext context : contexts) {
                // create a new jtag controller for each instance
                Nodable no = context.getNodable();
                Cell jtagCell = (Cell)no.getProto();
                JtagController controller = jtagControllers.get(jtagCell);
                if (controller == null) {
                    controller = jtagControllers.get(jtagCell.contentsView());
                }
                if (controller == null) {
                    System.out.println("Unable to find jtag controller for "+context.getInstPath(".")+", cell "+jtagCell.describe(false));
                    continue;
                }
                JtagInstance inst = new JtagInstance(context, controller, getNextChipName());
                jtags.add(inst);
            }

            // Find out which starter chains belong to which jtag instances
            for (Starter s : chains) {
                VarContext context = s.getContextOfFirstElement(VarContext.globalContext, jtagControllers);
                if (context == null) {
                    System.out.println("No context found for starter "+s.getKey());
                    continue;
                }
                /* String hier = */ context.getInstPath(".");
                //System.out.println("Starter "+s.getKey()+" has context "+hier);
                boolean used = false;
                for (JtagInstance inst : jtags) {
                    if (context.getInstPath(".").equals(inst.getContext().getInstPath("."))) {
                        // if there are duplicate chains from this one, add copy
                        for (Map.Entry<Starter,String> dup : duplicateStarters.entrySet()) {
                            Starter copy = dup.getKey();
                            String origChain = dup.getValue();
                            if (copy.getCell() == inst.controller.getCell() && origChain.equals(s.getChain())) {
                                copy.copyContentsOf(s);
                                inst.addStarter(copy);
                            }
                        }
                        inst.addStarter(s);
                        // Determine which Entity definitions should be written to the file
                        entitiesUsed.addAll(s.getEntitiesUsed());
                        used = true;
                        break;
                    }
                }
                if (!used)
                    System.out.println("Warning: starter "+s.getKey()+" not used by any jtag controllers");
            }
        }
        Collections.sort(chains);

        //
        // Get list of chains to write when starting from Exports
        //
        Set<TraceElement> topCellEntities = elements.get(cell);
        List<Starter> chainsFromExports = new ArrayList<Starter>();
        for (String export : startFromExport.keySet()) {
            if (jtagControllerChain == null) {
                // create dummy controller chain
                jtagControllerChain = new Entity(cell, "TDI", "TDO", false, new ArrayList<Instance>(), null);
            }
            String chainName = startFromExport.get(export);
            TraceElement chain = null;
            for (TraceElement e : topCellEntities) {
                if (e.getInport() != null && e.getInport().equals(export)) {
                    chain = e;
                    break;
                }
            }
            if (chain != null && (chain instanceof Entity)) {
                Entity ent = (Entity)chain;
                Starter starter = new Starter(ent.getCell(), ent.getOutport(),
                        ent.getInstances(), chainName, 0, null, null);
                chainsFromExports.add(starter);
                chains.add(starter);
                entitiesUsed.addAll(starter.getEntitiesUsed());
            }
        }
        if (chainsFromExports.size() != 0) {
            // create dummy jtag instance
            if (currentJtagController == null) {
                System.out.println("the jtag controller has not been defined yet");
                throw new IllegalStateException();
            }
            JtagInstance inst = new JtagInstance(VarContext.globalContext, currentJtagController, getNextChipName());
            for (Starter s : chainsFromExports) {
                inst.addStarter(s);
            }
            jtags.add(inst);
        }

        // write header
        out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        out.println("\n<!--");
        out.println("    Document      : "+outputFile);
        out.println("    Author        : automatically generated by Electric");
        out.println("    Description   : none");
        out.println("-->\n");
        out.println();
        out.println("<!DOCTYPE ChainG SYSTEM \"file:ChainG.dtd\" [");

        // print out flat data net specifications as entities
        if (generateScanDataNets) {
            for (Starter chain : chains) {
                if (chain.getLength() == 0) continue;
                out.println("<!ENTITY "+chain.getChain()+"_dataNets '");
                chain.writeDataNets(out, new StringBuffer("  "), VarContext.globalContext, dataNetMap);
                out.println("'>");
            }
        }

        //
        // Perform any optimizations
        // This needs to occur after writing the dataNets, but before
        // writing the entities and chains for the hierarchical scan chain
        //
        if (optimize) {
            optimize(chains);

        }

        // print entities
        for (Entity ent : entitiesUsed) {
            ent.writeDefinition(out);
        }
        out.println("]>");
        out.println();

        // write chains
        StringBuffer indent = new StringBuffer();
        out.println("<ChainG>");
        indent.append("\t");
        out.println(indent+"<system>");
        indent.append("\t");

        int totalFlatCount = 0;
        for (JtagInstance jtag : jtags) {
            List<Starter> starters = jtag.getStarters();
            if (starters == null || starters.size() == 0) {
                System.out.println("No chains defined as starting from jtag instance "+jtag.getHierName());
                continue;
            }
            System.out.println("Writing chains for jtag instance "+jtag.getHierName());
            Collections.sort(starters, new StarterByOpcodeCompare());
            String chipName;
            int lengthIR;
            chipName = jtag.getChipName();
            lengthIR = jtag.controller.lengthIR;

            // write out all chains
            out.println(indent+"<chip name=\""+chipName+"\" lengthIR=\""+lengthIR+"\">");
            indent.append("\t");
            for (Starter chain : starters) {
                int length = chain.getLength();
                totalFlatCount += length;
                System.out.println("Chain "+chain.chain+" has length "+length);
                if (length == 0) continue;
                chain.write(out, indent);
            }
            indent.setLength(indent.length() - 1);
            out.println(indent+"</chip>");
        }
        if (generateScanDataNets) {
            // write out all data nets
            out.println(indent+"<scandatanets>");
            indent.append("\t");
            for (Starter chain : chains) {
                if (chain.getLength() == 0) continue;
                out.println(indent+"<datachain name=\""+chain.getChain()+"\"> &"+chain.getChain()+"_dataNets; </datachain>");
            }
            indent.setLength(indent.length() - 1);
            out.println(indent+"</scandatanets>");
        }


        indent.setLength(indent.length() - 1);
        out.println(indent+"</system>");
        indent.setLength(indent.length() - 1);
        out.println("</ChainG>");

        out.flush();

        if (outFile != null) {
            System.out.println("Wrote XML file to "+outFile.getAbsolutePath());
            out.flush();
            out.close();
        } else {
            System.out.println("Wrote XML file to console");
        }

        System.out.println("Total Flat Count by Summation of Chains: "+totalFlatCount);
        System.out.println("Total Flat Count by Hierarchy Enumeration: "+elementsFlatCount.get(cell));
        if (elementsFlatCount.get(cell) != totalFlatCount) {
            System.out.println("Mismatch, printing Hierarchy Enumerated count by cell:");
            printFlatCount(cell, 0, new HashMap<Cell,Cell>());
            System.out.println("Please Note that the count may mismatch if the boundary scan chain is counted twice by the XML");
        }
    }

    /**
     * Specify a scan chain element.  When an instance of this is found, it is will
     * be parsed as one bit in the scan chain.
     * @param libName name of the library containing the cell
     * @param cellName name of the cell to be defined as a scan chain element.
     * @param access what can be done with the data. A combination of "R" for read,
     * "W" for write, and "S" for shadow. For example: "RW".
     * @param clears the state set after master clear, "H" for high, "L" for low, "-" for unused.
     * @param inport the name of input data port, typically "sin".
     * May contain index info, such as "s[1]"
     * @param outport the name of the output data port, typically "sout".
     * May contain index info, such as "ss[1]"
     */
    public void addScanChainElement(String libName, String cellName, String access, String clears,
                                    String inport, String outport) {
        addScanChainElementAllViews(getCell(libName, cellName), access, clears, inport, outport, false, "", "", null);
    }

    /**
     * Specify a scan chain element.  When an instance of this is found, it is will
     * be parsed as one bit in the scan chain.
     * @param libName name of the library containing the cell
     * @param cellName name of the cell to be defined as a scan chain element.
     * @param access what can be done with the data. A combination of "R" for read,
     * "W" for write, and "S" for shadow. For example: "RW".
     * @param clears the state set after master clear, "H" for high, "L" for low, "-" for unused.
     * @param inport the name of input data port, typically "sin".
     * May contain index info, such as "s[1]"
     * @param outport the name of the output data port, typically "sout".
     * May contain index info, such as "ss[1]"
     * @param dataport the name of the port the scan data is read from and written to. May include options
     * R, W, or I for (Readable,Writable,Inverted) in parenthesis at the end. Ex: dout(RW)
     * @param dataport2 another port for data like dataport, with the same format.
     */
    public void addScanChainElement(String libName, String cellName, String access, String clears,
                                    String inport, String outport, String dataport, String dataport2) {
        addScanChainElementAllViews(getCell(libName, cellName), access, clears, inport, outport, false, dataport, dataport2, null);
    }

    /**
     * Specify a pass through element.  Pass through elements are found in series in
     * the scan chain, but are not scan chain elements themselves.  Examples of this are
     * inverters and buffers that buffer the scan chain data.
     * @param libName name of the library containing the cell
     * @param cellName name of the cell to be defined as a pass through element
     * @param inport the name of the input port that passes data through
     * May contain index info, such as "s[1]"
     * @param outport the name of the output port that passes data through
     * May contain index info, such as "ss[1]"
     */
    public void addPassThroughCell(String libName, String cellName, String inport, String outport) {
        addScanChainElementAllViews(getCell(libName, cellName), null, null, inport, outport, true, "", "", null);
    }

    /**
     * Specify a pass through element.  Pass through elements are found in series in
     * the scan chain, but are not scan chain elements themselves.  Examples of this are
     * inverters and buffers that buffer the scan chain data.
     * This version limits the pass through to a particular instance specified
     * by the instance name field.
     * @param libName name of the library containing the cell
     * @param cellName name of the cell to be defined as a pass through element
     * @param inport the name of the input port that passes data through
     * May contain index info, such as "s[1]"
     * @param outport the name of the output port that passes data through
     * May contain index info, such as "ss[1]"
     * @param instanceName limit to the particular instance name.
     */
    public void addPassThroughCell(String libName, String cellName, String inport, String outport, String instanceName) {
        addScanChainElementAllViews(getCell(libName, cellName), null, null, inport, outport, true, "", "", instanceName);
        if (instanceName != null)
            scanElementInstanceNames.add(instanceName);
    }

    /**
     * Specify a cell to flatten.  The XML is hierarchical, but sometimes you don't need
     * or want all that hierarchy.  This specifies a cell that will be flattened
     * @param libName the library that contains the cell
     * @param cellName the name of the cell
     */
    public void addCellToFlatten(String libName, String cellName) {
        Cell cell = getCell(libName, cellName);
        if (cell != null) {
            cellsToFlatten.put(cell, cell);
        }
        cell = getCellOtherView(cell);
        if (cell != null) {
            cellsToFlatten.put(cell, cell);
        }
    }

    /**
     * Specify the JTAG Controller.  All scan chains are assumed to start, and end, at the
     * JTAG Controller.  This specifies the jtag controller.
     * This method is ignored if "startFromExport" is used.
     * @param jtagLib the name of the library that holds the jtag controller cell
     * @param jtagCellName the name of the cell that is the jtag controller
     * @param lengthIR the number of instruction register bits in the jtag controller.
     */
    public void setJtagController(String jtagLib, String jtagCellName, int lengthIR) {
        setJtagController(jtagLib, jtagCellName, lengthIR, "TDI", "TDOb");
    }

    /**
     * Specify the JTAG Controller.  All scan chains are assumed to start, and end, at the
     * JTAG Controller.  This specifies the jtag controller.
     * This method is ignored if "startFromExport" is used.
     * @param jtagLib the name of the library that holds the jtag controller cell
     * @param jtagCellName the name of the cell that is the jtag controller
     * @param lengthIR the number of instruction register bits in the jtag controller.
     */
    public void setJtagController(String jtagLib, String jtagCellName, int lengthIR, String inport, String outport) {
        Cell cell = getCell(jtagLib, jtagCellName);
        currentJtagController = new JtagController(cell, inport, outport, false, lengthIR);
        addJtagControllerAllViews(cell, inport, outport, lengthIR);
    }

    /**
     * Set the port name for the chip TDI signal. This is the input data to the
     * first jtag controller on chip.
     * @param TDIport
     */
    public void setChipTDI(String TDIport) {
        chipTDI = TDIport;
    }

    /**
     * Set the port name for the chip TDO signal. This is the output data from the
     * last jtag controller on the chip.
     * @param TDOport
     */
    public void setChipTDO(String TDOport) {
        chipTDO = TDOport;
    }

    /**
     * Add a port to the JTAG Controller that serves as a starting point for a scan chain.
     * A JTAG Controller may have several ports that each have a scan chain attached.
     * The JTAG Controller must have already been specified using setJtagController.
     * This method is ignored if "startFromExport" is used.
     * @param opcode the opcode for this scan chain
     * @param soutPortName the port name that outputs data for the scan chain.
     * May contain index info, such as "leaf1[1]"
     * @param sinPortName the port name that scan data returns to.
     * May contain index info, such as "leaf1[8]"
     * @param chainName the name given to this scan chain
     * @throws IllegalStateException if current Jtag Controller is undefined
     */
    public void addJtagPort(int opcode, String soutPortName, String sinPortName, String chainName) throws JobException {
        if (currentJtagController == null) {
            System.out.println("Can't add port "+soutPortName+" because the jtag controller has not been defined yet");
            throw new IllegalStateException();
        }
        //currentJtagController.addPort(soutPortName, chainName, opcode);
        addJtagPortStarterAllViews(currentJtagController.getCell(), sinPortName, soutPortName, chainName, opcode);
/*        Starter s = new Starter(currentJtagController.getCell(), soutPortName, null, chainName, opcode, null);
        // special case: for boundary scan chains we specify two chains
        // from the same port. The algorithm assumes unique chains, so
        // we have to duplicate the chain when printing it out, not during enumeration
        for (Starter olds : starters.keySet()) {
            if (olds.getCell() == currentJtagController.getCell() &&
                    olds.getOutport().equals(soutPortName)) {
                // already specified
                duplicateStarters.put(s, olds.getChain());
                return;
            }
        }
        starters.put(s, chainName);
        add(s);
        // also add as an end element
        ScanChainElement e = new ScanChainElement(currentJtagController.getCell(), "", "", sinPortName, null, true, "", "");
        add(e);*/
    }

    /**
     * Start tracing a chain from the specified export in the start cell.  This
     * is used to trace a section of the scan chain. This
     * traces only one chain.
     * @param exportName the export that starts the chain
     * @param chainName the name for the chain
     */
    public void startFromExport(String exportName, String chainName) {
        startFromExport.put(exportName, chainName);
    }

    /**
     * Specify the name of the chip. Used when writing the chip name to the file.
     * @param name the chip name
     */
    public void setChipName(String name) {
        chipNames.add(name);
    }

    /**
     * Method to say whether entity names must be unique.
     * The default is "true".
     * @param unique true if entity names must be unique.
     */
    public void setUniqueEntityNames(boolean unique) {
        uniqueEntityNames = unique;
    }

    /**
     * Set true to generate scan data nets in the output file, false not to.
     * Then is set to true by default.
     * @param generate whether or not to generate the scan data nets in the output file.
     */
    public void generateScanDataNets(boolean generate) {
        generateScanDataNets = generate;
    }

    /**
     * Set true to print any ignored instances, such as instances with grounded scan-in,
     * or false to not print out any info messages.
     * @param print true to print, false to not print. True by default.
     */
    public void setPrintIgnoredInstances(boolean print) {
        printIgnoredElements = print;
    }

    private String getNextChipName() {
        String name;
        if (currentChipName >= chipNames.size()) {
            name = "dummy"+currentChipName;
            System.out.println("Warning: creating dummy chip name because not enough calls to setChipName() vs number of jtag controllers");
        } else {
            name = chipNames.get(currentChipName);
        }
        currentChipName++;
        return name;
    }

    /**
     * Set the output file.  This may be an absolute or relative path.  If this
     * option is not specified, the output goes to the Electric console.
     * @param file the name of the file.
     */
    public void setOutput(String file) {
        // try to open outputFile
        outputFile = file;
        try {
            outFile = new File(outputFile);
            out = new PrintWriter(new BufferedWriter(new FileWriter(outFile)));
        } catch (IOException e) {
            System.out.println(e.getMessage() + "\nWriting XML to console");
            outFile = null;
            outputFile = null;
        }
    }

    /**
     * Call this method to print out tracing debug information
     */
    public void debugTracing() {
        debugTracing = true;
    }

    private void addScanChainElementAllViews(Cell cell, String access, String clears,
                                    String inport, String outport, boolean passThrough,
                                    String dataport, String dataport2,
                                    String instanceName) {
        if (cell == null) return;
        for (Cell otherCell : cell.getCellsInGroup()) {
            if (otherCell.isSchematic() || otherCell.getView() == View.LAYOUT) {
                ScanChainElement e = new ScanChainElement(otherCell, access, clears, inport, outport,
                        passThrough, dataport, dataport2, instanceName);
                add(e);
            }
        }
    }
    
    private void addJtagPortStarterAllViews(Cell cell, String sinPortName, String soutPortName, String chainName, int opcode) {
        if (cell == null) return;
        for (Cell otherCell : cell.getCellsInGroup()) {
            if (otherCell.isSchematic() || otherCell.getView() == View.LAYOUT) {
                Starter s = new Starter(otherCell, soutPortName, null, chainName, opcode, null, null);
                // special case: for boundary scan chains we specify two chains
                // from the same port. The algorithm assumes unique chains, so
                // we have to duplicate the chain when printing it out, not during enumeration
                boolean add = true;
                for (Starter olds : starters.keySet()) {
                    if (olds.getCell() == otherCell &&
                            olds.getOutport().equals(soutPortName)) {
                        // already specified
                        duplicateStarters.put(s, olds.getChain());
                        add = false;
                    }
                }
                if (add) {
                    starters.put(s, s);
                    add(s);
                    // ender
                    ScanChainElement e = new ScanChainElement(otherCell, "", "", sinPortName, null, true, "", "", null);
                    add(e);
                }
            }
        }
    }

    private void addJtagControllerAllViews(Cell cell, String inport, String outport, int lengthIR) {
        if (cell == null) return;
        for (Cell otherCell : cell.getCellsInGroup()) {
            if (otherCell.isSchematic() || otherCell.getView() == View.LAYOUT) {
                JtagController jtag = new JtagController(otherCell, inport, outport, false, lengthIR);
                jtagControllers.put(otherCell, jtag);
                add(jtag);
            }
        }
    }

    private Cell getCellOtherView(Cell cell) {
        View otherView = null;
        if (cell.getView() == View.LAYOUT) otherView = View.SCHEMATIC;
        if (cell.isSchematic()) otherView = View.LAYOUT;
        if (cell.isIcon()) otherView = View.LAYOUT;
        Cell otherCell = cell.otherView(otherView);
        return otherCell;
    }

    private Cell getCell(String libName, String cellName) {
        Library lib = Library.findLibrary(libName);
        if (lib == null) {
            System.out.println("Warning: Did not find library "+libName+" for cell "+cellName);
            return null;
        }
        Cell cell = lib.findNodeProto(cellName);
        if (cell == null) {
            System.out.println("Warning: Did not find cell "+cellName+", in library "+libName);
            return null;
        }
        if (cell.isIcon()) {
            cell = cell.otherView(View.SCHEMATIC);
            if (cell == null) {
                System.out.println("Warning: Did not find cell schematic cell for "+cellName+"{ic}, in library "+libName);
                return null;
            }
        }
        return cell;
    }

//    private Cell getCell(Cell cell, View view) {
//        if (view == null) return null;
//        for (Cell acell : cell.getCellsInGroup()) {
//            if (acell.getView() == view) {
//                return acell;
//            }
//        }
//        return null;
//    }

    private void add(TraceElement e) {
        Set<TraceElement> traceElements = elements.get(e.cell);
        if (traceElements == null) {
            traceElements = new TreeSet<TraceElement>();
            elements.put(e.getCell(), traceElements);
        }
        for (TraceElement ele : traceElements) {
            // same definition
            if (ele.getClass() != e.getClass()) continue;
            if (ele.getCell() != e.getCell()) continue;
            boolean namesSame = false;
            if (ele.getInstanceName() != null && e.getInstanceName() != null) {
                if (ele.getInstanceName().equals(e.getInstanceName()))
                    namesSame = true;
            }
            if (ele.getInstanceName() == null && e.getInstanceName() == null)
                namesSame = true;
            if (!namesSame) continue;
            boolean inportsSame = false;
            if (ele.inport == e.inport) inportsSame = true;
            if (ele.inport != null && e.inport != null && ele.inport.equals(e.inport)) inportsSame = true;
            if (!inportsSame) continue;
            boolean outportsSame = false;
            if (ele.outport == e.outport) inportsSame = true;
            if (ele.outport != null && e.outport != null && ele.outport.equals(e.outport)) outportsSame = true;
            if (inportsSame && outportsSame) {
                System.out.println("Warning, already cell "+e.cell.describe(false)+", inport "+e.inport+" already added");
                return;
            }
        }
        traceElements.add(e);
    }

    // ------------------------------------------------------------------


    public boolean enterCell(HierarchyEnumerator.CellInfo info) {
        Cell cell = info.getCell();
        Set<TraceElement> traceElements = elements.get(cell);
        if (traceElements == null) return true;

        boolean scanChainElement = false;
        for (TraceElement e : traceElements) {
            if (e instanceof ScanChainElement) {
                // We are inside a ScanChainElement
                // get the data net, and do not enumerate
                ScanChainElement sc = (ScanChainElement)e;
                for (DataNet datanet : sc.getDataNets())
                    mapDataNet(info, datanet);
                scanChainElement = true;
            }
        }
        if (scanChainElement) return false;
        return true;
    }

    /**
     * Map a data net from an element to a flat hierarchy name
     * @param info
     * @param dataNet
     */
    private void mapDataNet(HierarchyEnumerator.CellInfo info, DataNet dataNet) {
        if (dataNet == null) return;
        Network net = getNetwork(info.getNetlist(), dataNet.getName());
        String flatContext = "";
        String netName = dataNet.getName();
        if (net != null) {
            // may go up hierarchy
            HierarchyEnumerator.NetDescription desc = info.netIdToNetDescription(info.getNetID(net));
            netName = desc.getNet().getName();
            flatContext = desc.getCellInfo().getContext().getInstPath(".x");            
        }
        if (net == null) {
            // see if name is hierarchical (go down hierarchy)
            VarContext netContext = info.getContext();
            String name = dataNet.getName();
            Cell cell = info.getCell();
            if (name.contains(".")) {
                String hier[] = name.split("\\.");
                // go down hierarchy
                for (int i=0; i<hier.length-1; i++) {
                    String instName = hier[i];
                    for (Iterator<Nodable> nit = cell.getNodables(); nit.hasNext(); ) {
                        Nodable no = nit.next();
                        if (no.getName().equals(instName) && no.isCellInstance()) {
                            Cell proto = (Cell)no.getProto();
                            cell = proto.getEquivalent();
                            if (cell == null) cell = proto;
                            netContext = netContext.push(no);
                        }
                    }
                }
                // last field is net name, check for it in cell
                net = getNetwork(cell.getNetlist(), hier[hier.length-1]);
                flatContext = netContext.getInstPath(".x");
                netName = hier[hier.length-1];
            }
        }
        if (net == null) {
            System.out.println("Error: Cannot find data net "+dataNet.getName()+" in cell "+info.getCell()+", please check dataNet spec for this cell");
            return;
        }
        String contextualName = ScanChainElement.getDataNetPath(dataNet, info.getContext());
        if (flatContext != null && !flatContext.equals("")) {
            flatContext = "x"+flatContext+".";
        } else {
            flatContext = "";
        }
        netName = netName.replace('.', '_');
        String flatName = flatContext + netName;
        dataNetMap.put(contextualName, flatName);
        //if (debugTracing) System.out.println("  mapping dataNet "+contextualName+" --> "+flatName);
    }

    public void exitCell(HierarchyEnumerator.CellInfo info) {

        Set<TraceElement> processedElements = elements.get(info.getCell());
        if (processedElements != null) {
            boolean foundinst = false;
            for (TraceElement e : processedElements) {
                if (e.getInstanceName() != null) {
                    foundinst = true; break;
                }
            }
            if (!foundinst) return;     // already cached, no instances named
        }

        // see if we have already built an entity for this cell
        //if (!doNotSkip && elements.get(info.getCell()) != null) return;           // already cached

/*
        if (info.getContext() == VarContext.globalContext) {
            System.out.println("global context marker for breakpoint");
        }
*/

        String currentHier = info.getContext().getInstPath(".");
        if (debugTracing) System.out.println("---------------------------");
        if (debugTracing) System.out.println("Tracing cell "+info.getCell().describe(false)+" "+currentHier);

        // check for scan chain elements
        HashMap<String,Instance> instancesByInput = new HashMap<String,Instance>();
        HashMap<String,Instance> instancesByOutput = new HashMap<String,Instance>();
        List<Instance> instancesWithNoInput = new ArrayList<Instance>();

        boolean instanceElementPresent = false;
        int flatCount = 0;
        for (Iterator<NodeInst> it = info.getCell().getNodes(); it.hasNext(); ) {
            NodeInst ni = it.next();
            if (!ni.isCellInstance()) continue;            // not a cell, continue
            if (ni.isIconOfParent()) continue;
            Cell subcell = (Cell)ni.getProto();
            Cell contents = subcell.contentsView();
            if (contents != null) subcell = contents;          // get contents view

            if (info.getCell().isSchematic() && enumerateLayoutView(subcell)) {
                Cell laycell = subcell.otherView(View.LAYOUT);
                if (laycell != null) subcell = laycell;
            }

            Set<TraceElement> traceElements = elements.get(subcell);
            if (traceElements == null) continue;

            // Each cell could have several elements defined for it,
            // since an element is defined by a Cell + input port pair
            boolean instOk = false;
            for (TraceElement e : traceElements) {

                // Arrays here are handled for cells and pass throughs, but
                // special cased for scan chain element so that we have one entry with
                // a length > 1

                // Expand arrays of non-scan-chain-element instances
                if (ni.getNameKey().busWidth() > 1 && !(e instanceof ScanChainElement)) {
                    // expand arrayed nodeinsts that are not scan chain elements
                    for (int i=0; i<ni.getNameKey().busWidth(); i++) {
                        Nodable no = Netlist.getNodableFor(ni, i);
                        instOk = createInstance(no, info, instancesByInput, instancesByOutput,
                                instancesWithNoInput, e);
                    }
                } else {
                    // this is a scan chain element, or a single cell instance
                    // just create instance for the nodeinst
                    // but check if instanceName is set. If so, must match, otherwise, ignore
                    String instanceName = info.getContext().push(ni).getInstPath(".");
                    if (e.getInstanceName() != null) {
                        if (e.getInstanceName().equals(instanceName)) {
                            instOk = createInstance(ni, info, instancesByInput, instancesByOutput,
                                    instancesWithNoInput, e);
                            scanElementInstanceNames.remove(instanceName);
                        }
                        instanceElementPresent = true;
                    } else {
                        // no instance name means all instances should be recognized
                        instOk = createInstance(ni, info, instancesByInput, instancesByOutput,
                                instancesWithNoInput, e);
                    }
                    // increment flat count for parent cell if scan chain element
                    if (e instanceof ScanChainElement) {
                        ScanChainElement sc = (ScanChainElement)e;
                        if (!sc.isPassThrough()) {
                            // add in count of scan chain elements
                            if (instOk)
                                flatCount += ni.getNameKey().busWidth();
                        }
                    }
                }
            }
            Integer ii = elementsFlatCount.get(subcell);
            if (ii != null) {
                flatCount += (ii * ni.getNameKey().busWidth());
            }
        }
        elementsFlatCount.put(info.getCell(), Integer.valueOf(flatCount));

        // instances with no input are starters.
        // also, instances with no previous are local starters (their input is likely exported)
        for (Instance inst : instancesByInput.values()) {
            if (inst.prev == null) {
                instancesWithNoInput.add(inst);
            }
        }

        // get traceElements so we can put newly created Entities in them
        Set<TraceElement> traceElements = elements.get(info.getCell());
        if (traceElements == null) {
            traceElements = new TreeSet<TraceElement>();
            elements.put(info.getCell(), traceElements);
        }

        // create Entity.  It may be a starter if start of chain is in this cell
        String entName = null;
        if (instanceElementPresent) {
            entName = info.getContext().getInstPath(".");
        }
//        int startercount = 0;

        // find the start instances
        for (Instance inst : instancesWithNoInput) {
            // this is a start instance, follow it to the end, and make an Entity out of it
            Instance last = inst;
            List<Instance> instances = new ArrayList<Instance>();
            instances.add(last);
            while (last.next != null) {
                last = last.next;
                instances.add(last);
            }
            // check if input of start is exported, last check is to make sure it's a real export
            // and not a power/ground export
            String inex = null;
            if (inst.innet != null && inst.innet.isExported() && inst.innet.getExports().hasNext()) {
                inex = inst.innet.getName();
            }
            // check if output of end is exported
            String outex = null;
            if (last.outnet != null && last.outnet.isExported() && last.outnet.getExports().hasNext()) {
                outex = last.outnet.getName();
            }

            Entity ent;
            String entity = "Entity";
            // check if first instance is the start of a chain
            if (starters.containsKey(inst.e) && (inst.e instanceof Starter)) {
                // this entity will be the new starter
                // starters are always flat
                entName = info.getContext().getInstPath(".");
                Starter oldStart = (Starter)inst.e;
                ent = new Starter(info.getCell(), outex, instances, oldStart.chain, oldStart.opcode, oldStart, entName);

                //System.out.println("Creating Starter for cell "+info.getCell().describe(false)+
                //        ", in="+inex+", out="+outex+", chain="+oldStart.chain);

                starters.put((Starter)ent, (Starter)ent);
//                startercount++;

                //starters.remove(inst.e);
                entity = "Starter";
                if (outex == null && last.e.outport != null) {
                    // not an ender (ends have null outport definitions), and not exported, must be error
                    String outnet = last.outnet == null ? null : last.outnet.getName();
                    if (inst.e.isPassThrough()) {
                        if (printIgnoredElements) {
                            System.out.println("Warning: pass-through instance "+last.no.getName()+" in cell "+info.getCell().describe(false)+
                                    " has nothing connected to its output port "+last.e.outport+"("+outnet+")");
                        }
                    } else {
                        System.out.println("Error: instance "+last.no.getName()+" in cell "+info.getCell().describe(false)+
                                " has nothing connected to its output port "+last.e.outport+"("+outnet+")");
                    }
                    continue;
                }
            } else {
                // create Entity

                // check if this chain is just a passthrough chain, and has no real elements
                boolean passThrough = true;
                for (Instance entinst : instances) {
                    if (!entinst.getTraceElement().isPassThrough()) {
                        passThrough = false;
                        break;
                    }
                }

                // if totally unconnected and this chain contains only passthrough elements, don't issue an error
                if (inex == null && inst.getTraceElement().getInport() != null &&
                    outex == null && last.getTraceElement().getOutport() != null) {
                    if (passThrough) {
                        String innet = inst.innet == null ? null : inst.innet.getName();
                        String outnet = last.outnet == null ? null : last.outnet.getName();
                        if (printIgnoredElements) {
                            System.out.println("Warning: Ignoring pass-through chain with both networks exported: "+info.getCell().describe(false)+" from input net \""+
                                    innet+"\" to output net \""+outnet+"\"");
                        }
                        continue;
                    }
                }

                if (inex == null && inst.e.inport != null) {
                    // not a starter, and start network is not exported.  Must be error
                    String innet = inst.innet == null ? null : inst.innet.getName();
                    if (passThrough) {
                        if (printIgnoredElements) {
                            System.out.println("Warning: Ignoring pass-through instance with unconnected input port: "+inst.no.getName()+" in cell "+info.getCell().describe(false)+
                                    ", inport "+inst.e.inport+"("+innet+")");
                        }
                    } else {
                        System.out.println("Error: instance "+inst.no.getName()+" in cell "+info.getCell().describe(false)+
                                " has nothing connected to its input port "+inst.e.inport+"("+innet+")");
                    }
                    continue;
                }
                if (outex == null && last.e.outport != null) {
                    // not an ender (ends have null outport definitions), and not exported, must be error
                    String outnet = last.outnet == null ? null : last.outnet.getName();
                    if (passThrough) {
                        if (printIgnoredElements) {
                            System.out.println("Warning: Ignoring pass-through instance with unconnected output port: "+last.no.getName()+" in cell "+info.getCell().describe(false)+
                                    ", outport "+last.e.outport+"("+outnet+")");
                        }
                    } else {
                        System.out.println("Error: instance "+last.no.getName()+" in cell "+info.getCell().describe(false)+
                                " has nothing connected to its output port "+last.e.outport+"("+outnet+")");
                    }
                    continue;
                }
                ent = new Entity(info.getCell(), inex, outex, passThrough, instances, entName);
            }
            if (debugTracing) System.out.println("Creating "+entity+" for cell "+info.getCell().describe(false)+
                    ", in="+inex+", out="+outex+", key="+ent.getKey());
            traceElements.add(ent);
        }
/*
        if (startercount > 0) {
            if (entName != null)
                System.out.println("Created "+startercount+" starters in "+entName);
            else
                System.out.println("Created "+startercount+" starters in "+info.getCell().describe(false));
        }
*/
    }

    private boolean createInstance(Nodable ni, HierarchyEnumerator.CellInfo info,
                                HashMap<String,Instance> instancesByInput,
                                HashMap<String,Instance> instancesByOutput,
                                List<Instance> instancesWithNoInput, TraceElement e) {
        Instance inst = new Instance(ni, info.getContext(), ni.getNameKey().busWidth(), e);
        // Hook up Instances in linked list, which denotes order of chain.
        Nodable inno = ni;
        Nodable outno = ni;
        if ((ni instanceof NodeInst) && ni.getNameKey().busWidth() > 1) {
            NodeInst nin = (NodeInst)ni;
            inno = Netlist.getNodableFor(nin, 0);
            outno = Netlist.getNodableFor(nin, nin.getNameKey().busWidth()-1);
        }
        Network globalGnd = info.getNetlist().getNetwork(Global.ground);
        if (e.inport != null) {
            // hook up to predecessor instance
            Network innet = getNetwork(inno, info.getNetlist(), e.inport);
            Instance prev = null;
            if (innet != null) {
                prev = instancesByOutput.get(innet.getName());
                // ignore grounded sin, as long as it is not from a previous element
                if (innet == globalGnd) {
                    if (printIgnoredElements) {
                       System.out.println("Warning: Ignoring instance with grounded sin: "+inst.getName()+" in cell "+info.getCell().describe(false));
                    }
                    return false;
                }
                if (prev != null) {
                    // make sure one instance not driving two instances
                    //checkBadFanout(prev, prev.next, inst);
                    inst.prev = prev;
                    prev.next = inst;
                    if (debugTracing) System.out.println("  Chaining "+prev.describeOutnet()+" -> "+inst.describeInnet());
                }
                // check for merging of chains
                Instance branch = instancesByInput.get(innet.getName());
                if (branch != null) {
                    if (prev != null) {
                        System.out.println("Error! Chain branches from: "+prev.describeOutput());
                    } else {
                        System.out.println("Error! Chain branches from net "+innet.getName()+" in cell "+ni.getParent().describe(false));
                    }
                    System.out.println("       into: "+inst.describeInput()+" (key="+inst.getTraceElement().getKey()+")");
                    System.out.println("       into: "+branch.describeInput()+" (key="+branch.getTraceElement().getKey()+")");
                }
                inst.innet = innet;
                instancesByInput.put(innet.getName(), inst);
            } else {
                System.out.println("Error: Can't find input network for port "+e.getInport()+" on element "+e.getCell().describe(false));
            }
        } else {
            instancesWithNoInput.add(inst);
        }

        if (e.outport != null) {
            // hook up to successor instance
            Network outnet = getNetwork(outno, info.getNetlist(), e.outport);
            Instance next = null;
            if (outnet != null) {
                next = instancesByInput.get(outnet.getName());
                if (next != null) {
                    //checkBadFanin(inst,  next.prev, next);
                    inst.next = next;
                    next.prev = inst;
                    if (debugTracing) System.out.println("  Chaining "+inst.describeOutnet()+" -> "+next.describeInnet());
                }
                Instance merge = instancesByOutput.get(outnet.getName());
                if (merge != null) {
                    if (next != null) {
                        System.out.println("Error! Chain merges into "+next.describeInput());
                    } else {
                        System.out.println("Error! Chain merges into net "+outnet.getName()+" in cell "+ni.getParent().describe(false));
                    }
                    System.out.println("       from: "+inst.describeOutput()+" (key="+inst.getTraceElement().getKey()+")");
                    System.out.println("       from: "+merge.describeOutput()+" (key="+merge.getTraceElement().getKey()+")");
                }
                inst.outnet = outnet;
                instancesByOutput.put(outnet.getName(), inst);
            } else {
                System.out.println("Error: Can't find output network for port "+e.getOutport()+" on element "+e.getCell().describe(false));
            }
        }
        if (debugTracing) {
            String innet = inst.innet == null ? null : inst.innet.getName();
            String outnet = inst.outnet == null ? null : inst.outnet.getName();
            System.out.println("  Created instance "+inst.no.getName()+
                " in="+e.inport+"("+innet+")"+
                " out="+e.outport+"("+outnet+") for element "+e.getKey());
        }
        return true;
    }


    public boolean visitNodeInst(Nodable ni, HierarchyEnumerator.CellInfo info) {
        // We need to push into ScanChainElements one level to
        // get at the data net.  We also need to push into everything else,
        // except primitives which cannot be pushed into

        if (ni.isCellInstance()) {
            Cell cell = (Cell)ni.getProto();
            Cell schcell = cell.contentsView();
            if (schcell == null) schcell = cell;
            if (cell.isSchematic() && enumerateLayoutView(schcell)) {
                Cell layCell = schcell.otherView(View.LAYOUT);
                if (layCell != null) {
                    HierarchyEnumerator.enumerateCell(layCell, info.getContext().push(ni), this);
                    return false;
                }
            }
        }

        return true;
    }

    private boolean enumerateLayoutView(Cell cell) {
        return CellModelPrefs.spiceModelPrefs.isUseLayoutView(cell);
    }

//    private Cell getLayoutView(Cell cell) {
//        for (Cell c : cell.getCellsInGroup()) {
//            if (c.getView() == View.LAYOUT) {
//                return c;
//            }
//        }
//        return null;
//    }

//    private void checkBadFanout(Instance prev, Instance next1, Instance next2) {
//        if (next1 == null || next2 == null) return;
//        if (prev == null) return;
//        // all non-null, issue error
//        System.out.println("Error! Chain branches from: "+prev.describeOutput());
//        System.out.println("       into: "+next1.describeInput());
//        System.out.println("       into: "+next2.describeInput());
//    }

//    private void checkBadFanin(Instance prev1, Instance prev2, Instance next) {
//        if (prev1 == null || prev2 == null) return;
//        if (next == null) return;
//        // all non-null, issue error
//        System.out.println("Error! Chain merges into "+next.describeInput());
//        System.out.println("       from: "+prev1.describeOutput());
//        System.out.println("       from: "+prev2.describeOutput());
//    }

//    private HashMap<Nodable,String> jtagControllerNames = new HashMap<Nodable,String>();
//    private void setJtagControllerName(Nodable no, String name) {
//        jtagControllerNames.put(no, name);
//    }
//    private String getJtagControllerName(Nodable no) {
//        return jtagControllerNames.get(no);
//    }

    private void printFlatCount(Cell cell, int indent, Map<Cell,Cell> alreadyPrinted) {
        if (elementsFlatCount.get(cell) == null) return;
        if (elementsFlatCount.get(cell) == 0) return;
        if (alreadyPrinted.containsKey(cell)) return;
        alreadyPrinted.put(cell, cell);
        StringBuffer buf = new StringBuffer();
        for (int i=0; i<indent; i++) {
            buf.append(" ");
        }
        System.out.println(buf+cell.describe(false)+": "+elementsFlatCount.get(cell));
        for (Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); ) {
            NodeInst ni = it.next();
            if (!ni.isCellInstance()) continue;            // not a cell, continue
            if (ni.isIconOfParent()) continue;
            Cell c = (Cell)ni.getProto();
            Cell contents = c.contentsView();
            if (contents != null) c = contents;          // get contents view

            if (cell.isSchematic() && enumerateLayoutView(c)) {
                Cell laycell = c.otherView(View.LAYOUT);
                if (laycell != null) c = laycell;
            }
            printFlatCount(c, indent+2, alreadyPrinted);
        }
        
    }

    // ------------------------------------------------------------------

    private static class TraceElement implements Comparable {
        private final Cell cell;
        private final String inport;
        private final String outport;
        private final boolean passThrough;
        private String hierInstanceName;          // if limited to a particular instance

        private TraceElement(Cell cell, String inport, String outport,
                             boolean passThrough, String hierInstanceName) {
            this.cell = cell;
            this.inport = inport;
            this.outport = outport;
            this.passThrough = passThrough;
            this.hierInstanceName = hierInstanceName;
        }
        public static String getKey(Cell cell, String inport, String hierInstanceName) {
            String name = (hierInstanceName == null) ? "" : "_"+hierInstanceName; 
            String key = cell.getLibrary().getName()+"_"+cell.getName()+"_"+inport+name;
            key = key.replaceAll("[\\[\\]@]", "_");
            return key;
        }
        public String getKey() {
            return getKey(cell, inport, hierInstanceName);
        }
        public String getInstanceName() { return hierInstanceName; }
//        public void setInstanceName(String name) { hierInstanceName = name; }
        public Cell getCell() { return cell; }
        public int compareTo(Object o) {
            TraceElement e = (TraceElement)o;
            return getKey().compareTo(e.getKey());
        }
        public boolean equals(Object o) {
            TraceElement e = (TraceElement)o;
            return getKey().equals(e.getKey());
        }
        public String getOutport() { return outport; }
        public String getInport() { return inport; }
        public boolean isPassThrough() { return passThrough; }
    }

    /** Defines a Scan Chain Element, which contains one bit of scan storage */
    private static class ScanChainElement extends TraceElement {
        private final String access;
        private final String clears;
        private final List<DataNet> dataports;

        private ScanChainElement(Cell cell, String access, String clears, String inport, String outport,
                                 boolean passThrough, String dataport, String dataport2, String hierInstanceName) {
            super(cell, inport, outport, passThrough, hierInstanceName);
            this.access = access;
            this.clears = clears;
            dataports = new ArrayList<DataNet>();
            if (dataport != null && !dataport.equals(""))
                dataports.add(new DataNet(dataport));
            if (dataport2 != null && !dataport2.equals(""))
                dataports.add(new DataNet(dataport2));
        }
        public List<DataNet> getDataNets() { return dataports; }
/*
        public DataNet getDataNet() { return dataport; }
        public DataNet getDataNet2() { return dataport2; }
*/
        public static String getDataNetPath(DataNet dataNet, VarContext context) {
            if (dataNet == null) return null;
            return context.getInstPath(".")+"."+dataNet.getName();
        }
        public String getAccess() { return access; }
        public String getClears() { return clears; }
        public boolean combinable(ScanChainElement other) {
            if (access == null && other.getAccess() != null) return false;
            if (access != null && other.getAccess() == null) return false;
            if (access != null && other.getAccess() != null && !access.equals(other.getAccess())) return false;
            if (clears == null && other.getClears() != null) return false;
            if (clears != null && other.getClears() == null) return false;
            if (clears != null && other.getClears() != null && !clears.equals(other.getClears())) return false;
            return true;
        }
    }

    /**
     * The networks that the scan chain elements reads from or
     * writes to.
     */
    private static class DataNet {
        public final String net;
        public final String options;        // options including parenthesis

        /**
         * Creates a data net object that describes a net that is written or read
         * to by a scan chain element.
         * @param netName the name of the data net, including any options prepended
         * in parenthesis. R for readable, W for writable, I for inverted.
         * Example: net26 or net26(R) or net26(RWI).
         */
        public DataNet(String netName) {
            int i = netName.indexOf('(');
            if (i != -1) {
                net = netName.substring(0, i);
                options = netName.substring(i, netName.length());
            } else {
                net = netName;
                options = "";
            }
        }

        /**
         * Creates a data net object that describes a net that is written or read
         * to by a scan chain element.
         * @param net the net name
         * @param options options describing the network: R for readable, W for
         * writable, I for inverted. Should be encased in parenthesis, such as
         * (RW).
         */
//        public DataNet(String net, String options) {
//            this.net = net;
//            this.options = options;
//        }

        public String toString() { return net+options; }
        public String getName() { return net; }
        public String getOptions() { return options; }
    }

    /**
     * Defines the scan chain elements for a Cell + Input Port pair.
     */
    private class Entity extends TraceElement {
        private List<Instance> instances;
        private Entity(Cell cell, String inport, String outport, boolean passThrough,
                       List<Instance> instances, String hierInstanceName) {
            super(cell, inport, outport, passThrough, hierInstanceName);
            this.instances = new ArrayList<Instance>();
            if (instances != null) this.instances.addAll(instances);
        }
        public void writeDefinition(PrintWriter out) {
            if (getLength() == 0) return;

            out.println("<!ENTITY "+getKey()+" '");
            List<String> usedNames = new ArrayList<String>();
            for (Instance inst : instances) {
                inst.write(out, new StringBuffer("\t"), usedNames);
            }
            out.println("'>");
        }
        public void writeContents(PrintWriter out, StringBuffer indent) {
            List<String> usedNames = new ArrayList<String>();
            for (Instance inst : instances) {
                inst.write(out, indent, usedNames);
            }
        }
        public void writeDataNets(PrintWriter out, StringBuffer indent, VarContext context, HashMap<String,String> dataNetMap) {
            for (Instance inst : instances) {
                inst.writeDataNets(out, indent, context, dataNetMap);
            }
        }
        public int getLength() {
            int len = 0;
            for (Instance inst : instances) {
                len += inst.getLength();
            }
            return len;
        }
        public int flattenInstances(HashMap<Cell,Cell> cellsToFlatten, HashMap<Entity,Cell> alreadyFlattened) {
            int flattened = 0;
            if (alreadyFlattened.containsKey(this)) return 0;
            if (this instanceof Starter) return 0;              // can't flatten starters?

            for (int i=0; i<instances.size(); i++) {
                Instance inst = instances.get(i);
                // flatten instance if it's entity contains only 1 thing
                TraceElement ele = inst.getTraceElement();
                if (ele instanceof Entity) {
                    Entity ent = (Entity)ele;
                    // make sure the sub element is complete optimized in terms of flattening
                    flattened += ent.flattenInstances(cellsToFlatten, alreadyFlattened);

                    if (ent.getRealInstances().size() == 1 ||
                            cellsToFlatten.containsKey(ent.getCell())) {
                        // flatten, bring contents of instance into this entity
                        instances.remove(i);
                        List<Instance> newInstances = new ArrayList<Instance>();
                        for (Instance subinst : ent.getRealInstances()) {
                            Instance newinst = new Instance(subinst.getNodable(),
                                    subinst.getContext(), subinst.getLength(), subinst.getTraceElement());
                            if (ent.getRealInstances().size() == 1) {
                                // keep upper level name
                                newinst.setNameOverride(inst.getName());
                            } else {
                                // use combination of instances to prevent name conflicts
                                newinst.setNameOverride(inst.getName()+"."+subinst.getName());
                            }
                            newInstances.add(newinst);
                        }
                        instances.addAll(i, newInstances);
                        flattened++;
                        break;          // have to break and start over because we modified list
                    }
                }
            }
            alreadyFlattened.put(this, getCell());

            return flattened;
        }
        // returns the scan chain element if it is combinable, otherwise returns null
        private int combineInstances() {
            List<ArrayedNameList> arrayedNames = new ArrayList<ArrayedNameList>();
            int combosDone = 0;
            int maxDimension = 0;
            // build list of arrayed names.  Cannot combine unless it is a scanChainElement
            for (Instance inst : instances) {
                if (inst.getTraceElement() instanceof ScanChainElement) {
                    ArrayedNameList name = new ArrayedNameList(inst.getName());
                    arrayedNames.add(name);
                    int m = name.numDimensions();
                    if (m > maxDimension) maxDimension = m;
                } else {
                    arrayedNames.add(null);
                }
            }
            // start from lowest dimension
            for (int m=0; m<maxDimension; m++) {
                // check if any names are combinable, and if their instances are combinable
                ArrayedNameList last = null;
                for (int i=0; i<arrayedNames.size(); i++) {
                    ArrayedNameList next = arrayedNames.get(i);
                    if (last == null || next == null) {
                        last = next;
                        continue;
                    }
                    // see if combinable both by name, and by scan chain element type
                    Instance lastinst = instances.get(i-1);
                    Instance nextinst = instances.get(i);
                    ScanChainElement lastE = (ScanChainElement)lastinst.getTraceElement();
                    ScanChainElement nextE = (ScanChainElement)nextinst.getTraceElement();
                    ArrayedNameList combined = last.combineSequentialNumeric(next, m);
                    if (lastE.combinable(nextE) && combined != null) {
                        // can combine them
                        Instance newInst = new Instance(lastinst.getNodable(), lastinst.getContext(),
                                lastinst.getLength()+nextinst.getLength(), lastinst.getTraceElement());
                        newInst.setNameOverride(combined.toString());
                        instances.remove(i-1);      // remove last
                        instances.remove(i-1);      // remove next
                        arrayedNames.remove(i-1);   // keep name indices aligned with instances
                        arrayedNames.remove(i-1);
                        instances.add(i-1, newInst);   // replace both by newInst
                        arrayedNames.add(i-1, combined);
                        next = combined;
                        i--;                        // adjust index
                        combosDone++;
                    }
                    last = next;
                }
            }
            return combosDone;
        }
        protected List<Instance> getInstances() { return instances; }
        protected List<Instance> getRealInstances() {
            List<Instance> realinstances = new ArrayList<Instance>();
            for (Instance inst : instances) {
                if (inst.getTraceElement() instanceof ScanChainElement) {
                    ScanChainElement ele = (ScanChainElement)inst.getTraceElement();
                    if (ele.isPassThrough()) continue;
                }
                realinstances.add(inst);
            }
            return realinstances;
        }
//        protected List<Instance> getFlatInstances() {
//            List<Instance> flatinstances = new ArrayList<Instance>();
//            for (Instance inst : instances) {
//                TraceElement e = inst.e;
//                if (e.isPassThrough()) continue;
//                if (e instanceof Entity) {
//                    Entity ent = (Entity)e;
//                    flatinstances.addAll(ent.getFlatInstances());
//                }
//                else {
//                    flatinstances.add(inst);
//                }
//            }
//            return flatinstances;
//        }
        public List<VarContext> getContextsOfJtagInstances() {
            List<VarContext> contexts = new ArrayList<VarContext>();
            getContextsOfJtagInstances(contexts, VarContext.globalContext);
            return contexts;
        }
        private void getContextsOfJtagInstances(List<VarContext> contexts, VarContext parentContext) {
            for (Instance inst : instances) {
                if (inst.e instanceof Entity) {
                    Entity ent = (Entity)inst.e;
                    ent.getContextsOfJtagInstances(contexts, parentContext.push(inst.getNodable()));
                    continue;
                }
                if (inst.e instanceof JtagController) {
                    contexts.add(parentContext.push(inst.getNodable()));
                }
            }
        }
        // may return null if no valid elements in instances (this ignores passthroughs
        public VarContext getContextOfFirstElement(VarContext parentContext, HashMap<Cell,JtagController> jtagControllers) {
            for (Instance inst : instances) {
                if (inst.e.isPassThrough()) continue;
                if (inst.e instanceof Entity) {
                    Entity ent = (Entity)inst.e;
                    if (jtagControllers.get(ent.getCell()) != null) {
                        // this is a jtag controller instance
                        return parentContext.push(inst.getNodable());
                    }
                    VarContext context = ent.getContextOfFirstElement(parentContext.push(inst.getNodable()), jtagControllers);
                    if (context == null) continue;
                    return context;
                }
                if (inst.e instanceof ScanChainElement || inst.e instanceof JtagController) {
                    VarContext context = parentContext.push(inst.getNodable());
                    return context;
                }
            }
            return null;
        }

        public void copyContentsOf(Entity ent) {
            instances.clear();
            instances.addAll(ent.getInstances());
        }
        public Set<Entity> getEntitiesUsed() {
            Set<Entity> entities = new TreeSet<Entity>();
            for (Instance inst : instances) {
                TraceElement ele = inst.getTraceElement();
                if (ele instanceof Entity) {
                    Entity ent = (Entity)ele;
                    entities.add(ent);
                    entities.addAll(ent.getEntitiesUsed());
                }
            }
            return entities;
        }
        public String toString() { return getKey(); }
    }

    private class Starter extends Entity {
        private final String chain;
        private final int opcode;
        private String chipName;
        private Starter child;
        private Starter(Cell cell, String outport, List<Instance> instances, String chain, int opcode,
                        Starter child, String hierInstanceName) {
            super(cell, null, outport, false, instances, hierInstanceName);
            this.chain = chain;
            this.opcode = opcode;
            this.chipName = "Temp_";
            this.child = child;
        }
        public void write(PrintWriter out, StringBuffer indent) {
            out.println(indent+"<chain name=\""+chain+"\" opcode=\""+Integer.toBinaryString(opcode)+"\">");
            indent.append("\t");
            writeContents(out, indent);
            indent.setLength(indent.length() - 1);
            out.println(indent+"</chain>");
        }
//        public void writeData(PrintWriter out, StringBuffer indent, VarContext context, HashMap<String,String> dataNetMap) {
//            out.println(indent+"<chain name=\""+chain+"\" opcode=\""+Integer.toBinaryString(opcode)+"\">");
//            indent.append("\t");
//            writeDataNets(out, indent, context, dataNetMap);
//            indent.setLength(indent.length() - 1);
//            out.println(indent+"</chain>");
//        }
        public String getKey() {
            return getKey(getCell(), getOrigStarterInstanceName()+"_"+chain, getInstanceName());
        }
        public void setChipName(String chipName) {
            this.chipName = chipName;
            if (child != null) child.setChipName(chipName);
        }
        public String getChipName() {
            return chipName;
        }
        public String getChain() {
            String chipName = getChipName();
            return chipName+"_"+chain;
            //return getOrigStarterInstanceName()+"_"+chain;
        }
        public String getOrigStarterInstanceName() {
            if (child == null) return getInstanceName();
            String name = child.getOrigStarterInstanceName();
            if (name == null) name = getInstanceName();
            return name;
        }
    }

    public static class StarterByOpcodeCompare implements Comparator<Starter> {

        public int compare(Starter s1, Starter s2) {
            int compareChipName = s1.getChipName().compareTo(s2.getChipName());
            if (compareChipName == 0) {
                if (s1.opcode == s2.opcode) return 0;
                if (s1.opcode < s2.opcode) return -1;
                return 1;
            }
            return compareChipName;
        }
    }

    /**
     * An instance of a Trace Element: either a real scan chain element,
     * or an Entity, which is just a cell contain scan chain elements.
     */
    private class Instance {
        private final Nodable no;
        private final VarContext context;
        private final int length;
        private final TraceElement e;
        private Instance prev;              // for building linked list of instances in order
        private Instance next;
        private Network innet;
        private Network outnet;
        private String nameOverride;
        private Instance(Nodable no, VarContext context, int length, TraceElement e) {
            this.no = no;
            this.context = context;
            this.length = length;
            this.e = e;
            this.prev = null;
            this.next = null;
            this.innet = null;
            this.outnet = null;
            this.nameOverride = null;
        }
        public String describeOutput() {
            return no.getParent().getName()+"."+no.getName()+"."+e.outport;
        }
        public String describeInput() {
            return no.getParent().getName()+"."+no.getName()+"."+e.inport;
        }
        public String describeOutnet() {
            return no.getName()+"["+e.outport+"("+outnet+")]";
        }
        public String describeInnet() {
            return no.getName()+"["+e.inport+"("+innet+")]";
        }
        public void write(PrintWriter out, StringBuffer indent, List<String> usedNames) {
            String name = getName();

            if (uniqueEntityNames)
            {
                // the next line prevents a name from being used twice...is it right? ...smr
            	if (usedNames.contains(name) && e.inport != null) name = name +"_" + e.inport;
            }

            if (e instanceof Entity) {
                // just reference entity
                Entity ent = (Entity)e;
                if (ent.getLength() == 0) return;
                out.println(indent+"<subchain name=\""+name+"\"> &"+e.getKey()+"; </subchain>");
                usedNames.add(name);
            } else {
                // scan chain element
                ScanChainElement ele = (ScanChainElement)e;
                if (ele.isPassThrough()) return;
                out.print(indent+"<subchain name=\""+name+"\"");
                out.print(" length=\""+length+"\"");
                if (ele.access != null) out.print(" access=\""+ele.access+"\"");
                if (ele.clears != null) out.print(" clears=\""+ele.clears+"\"");
                out.println(" />");
            }
        }
        public void writeDataNets(PrintWriter out, StringBuffer indent, VarContext context, HashMap<String,String> dataNetMap) {
            if (e instanceof Entity) {
                // just reference entity
                Entity ent = (Entity)e;
                ent.writeDataNets(out, indent, context.push(no), dataNetMap);
            } else {
                // scan chain element
                ScanChainElement ele = (ScanChainElement)e;
                if (ele.isPassThrough()) return;
                for (int i=0; i<no.getNameKey().busWidth(); i++) {
                    Nodable thisno = no;
                    if (no instanceof NodeInst) {
                        thisno = Netlist.getNodableFor((NodeInst)no, i);
                    }

                    VarContext subContext = context.push(thisno);
                    out.print(indent+"<datanet name=\""+subContext.getInstPath(".")+"\"");


                    if (ele.getDataNets().size() > 0) {
                        String flatName = getFlatDataNet(ele.getDataNets().get(0), subContext, dataNetMap);
                        if (flatName != null) out.print(" net=\""+flatName+"\"");
                    }
                    if (ele.getDataNets().size() > 1) {
                        String flatName2 = getFlatDataNet(ele.getDataNets().get(1), subContext, dataNetMap);
                        if (flatName2 != null) out.print(" net2=\""+flatName2+"\"");
                    }

                    out.println(" />");
                }
            }
        }
        private String getFlatDataNet(DataNet dataNet, VarContext context, HashMap<String,String> dataNetMap) {
            if (dataNet == null) return null;           // null data net, no error
            String contextName = ScanChainElement.getDataNetPath(dataNet, context);
            if (contextName == null) return null;
            String flatName = dataNetMap.get(contextName);
            if (flatName == null) {
                //System.out.println("Can't find mapping for dataNet "+contextName);
                //flatName = "*"+contextName;
                return null;
            }
            return flatName+dataNet.getOptions();
        }

        public int getLength() {
            if (e instanceof ScanChainElement) {
                ScanChainElement ele = (ScanChainElement)e;
                if (ele.isPassThrough()) return 0;
                return length;
            }
            else {
                // just reference entity
                Entity ent = (Entity)e;
                return ent.getLength();
            }
        }

        public void setNameOverride(String name) { this.nameOverride = name; }
        public String getName() { return ( (nameOverride == null) ? no.getName() : nameOverride); }
        protected TraceElement getTraceElement() { return e; }
        protected Nodable getNodable() { return no; }
        protected VarContext getContext() { return context; }
    }

    /** Defines the Jtag controller from which the scan chains start and end */
    private static class JtagController extends TraceElement {
        public final int lengthIR;
        private JtagController(Cell cell, String inport, String outport, boolean passThrough, int lengthIR) {
            super(cell, inport, outport, passThrough, null);
            this.lengthIR = lengthIR;
        }
//        public int getLengthIR() { return lengthIR; }
    }

    private static class JtagInstance {
        private final JtagController controller;
        private String chipName;
        private VarContext context;
        private List<Starter> starters;
        private JtagInstance(VarContext context, JtagController controller, String chipName) {
            this.context = context;
            this.controller = controller;
            this.chipName = chipName;
            this.starters = new ArrayList<Starter>();
        }
        public String getHierName() { return context.getInstPath("."); }
        public String getChipName() { return chipName; }
        public VarContext getContext() { return context; }
        public void addStarter(Starter s) {
            starters.add(s);
            s.setChipName(chipName);
        }
        public List<Starter> getStarters() { return starters; }
    }

    private static class Range {
        private int start;
        private int end;
        public Range(int start, int end) {
            this.start = start;
            this.end = end;
        }
        public int getStart() { return start; }
        public int getEnd() { return end; }
//        public int getRange() { return Math.abs(end-start); }
        /**
         * Return true if the order is ascending (start < end),
         * or false otherwise.
         * @param includeSingular if range is 0 (start == end),
         * then this method will return the state of this argument
         */
        public boolean isAscending(boolean includeSingular) {
            if (start < end) return true;
            if (start == end) return includeSingular;
            return false;
        }
        /**
         * Return true if the order is descending (end < start),
         * or false otherwise.
         * @param includeSingular if range is 0 (start == end),
         * then this method will return the state of this argument
         */
        public boolean isDescending(boolean includeSingular) {
            if (start > end) return true;
            if (start == end) return includeSingular;
            return false;
        }
        public boolean equals(Object o) {
            Range r = (Range)o;
            if (getStart() == r.getStart() && getEnd() == r.getEnd()) return true;
            return false;
        }
        public String toString() {
            if (start == end) return String.valueOf(start);
            return (start + ":" + end);
        }
        /**
         * Combines the two ranges if possible, otherwise returns null.
         * Ranges must be non-overlapping to be combinable.
         * @param next the next range
         * @return the combined range, or null if not possible
         */
        public Range combine(Range next) {
            // start and end must differ by 1
            int diff = Math.abs(getEnd()-next.getStart());
            if (diff != 1) return null;
            // order (ascending/descending) must be the same
            if (isAscending(true) && next.isAscending(true)) {
                return new Range(getStart(), next.getEnd());
            }
            if (isDescending(true) && next.isDescending(true)) {
                return new Range(getStart(), next.getEnd());
            }
            return null;
        }
    }

    private static class ArrayIndex {
        private List<Object> indices;
        boolean numericOnly;
        /**
         * indices is a string representing some set of indices, of the format:
         * <ul>
         * <li>indices := block{,block}*
         * <li>block := letter | range
         * <li>range := digit | digit : digit
         * </ul>
         * @param indices
         */
        public ArrayIndex(String indices) {
            this.indices = new ArrayList<Object>();
            this.numericOnly = true;

            // split by commas
            String [] parts = indices.split(",");
            for (int i=0; i<parts.length; i++)
                addBlock(parts[i]);
        }
        public ArrayIndex(List<Object> indices) {
            numericOnly = true;
            this.indices = indices;
            for (int i=0; i<indices.size(); i++) {
                if (!(indices.get(i) instanceof Range))
                    numericOnly = false;
            }
        }
        private void addBlock(String block) {
            block = block.trim();
            if (block.matches("\\d+")) {
                // a number
                int start = Integer.parseInt(block);
                indices.add(new Range(start, start));
            } else if (block.matches("\\d+:\\d+")) {
                // a range
                String [] range = block.split(":");
                int start = Integer.parseInt(range[0]);
                int end = Integer.parseInt(range[1]);
                indices.add(new Range(start, end));
            } else if (block.matches("\\w+")) {
                indices.add(block);
            } else {
                System.out.println("Invalid array index "+block);
            }
        }
        public boolean isNumericOnly() { return numericOnly; }
        public boolean equals(Object o) {
            ArrayIndex a = (ArrayIndex)o;
            if (getNumIndices() != a.getNumIndices()) return false;
            for (int i=0; i<indices.size(); i++) {
                Object index = a.indices.get(i);
                if (!indices.get(i).equals(index))      // either string, or a Range object
                    return false;
            }
            return true;
        }
        public int getNumIndices() { return indices.size(); }
        public Object getIndex(int index) {
            if (index <0 || index > (indices.size()-1)) return null;
            return indices.get(index);
        }
        public String toString() {
            StringBuffer str = new StringBuffer();
            for (Iterator it = indices.iterator(); it.hasNext(); ) {
                Object next = it.next();
                if (next instanceof String)
                    str.append((String)next);
                if (next instanceof Range)
                    str.append(((Range)next).toString());
                if (it.hasNext())
                    str.append(",");
            }
            return str.toString();
        }
        /**
         * Tries to combine the two array indices if they are sequential
         * numeric indices
         * @param next
         * @return
         */
        public ArrayIndex combineSequentialNumeric(ArrayIndex next) {
            if (!isNumericOnly()) return null;
            if (!next.isNumericOnly()) return null;
            Object thisLastObj = getIndex(getNumIndices()-1);
            Object otherFirstObj = next.getIndex(0);
            if (!(thisLastObj instanceof Range) || !(otherFirstObj instanceof Range)) return null;
            Range thisLast = (Range)thisLastObj;
            Range otherFirst = (Range)otherFirstObj;
            // both should be ranges
            if (thisLast == null || otherFirst == null) return null;
            Range newRange = thisLast.combine(otherFirst);
            if (newRange == null) return null;
            List<Object> indices = new ArrayList<Object>(this.indices);
            indices.remove(getNumIndices()-1);
            indices.add(newRange);
            return new ArrayIndex(indices);
        }
    }

    private static class ArrayedName {
        private String name;
        private List<ArrayIndex> indices;  // order of most sig to least sig, unrolling traverses least sig first
        public ArrayedName(String arrayedName) {
            // break name by brackets
            indices = new ArrayList<ArrayIndex>();
            String [] parts = arrayedName.trim().split("[\\[\\]]");
            for (int i=0; i<parts.length; i++) {
                if (i == 0) {
                    // this is the name
                    if (parts[0].equals("")) System.out.println("Invalid name for "+arrayedName);
                    name = parts[0];
                    continue;
                }

                String str = parts[i].trim();
                if (str.equals("")) continue;
                ArrayIndex index = new ArrayIndex(parts[i]);
                indices.add(index);
            }
            // reverse order so that unrolling starts from first indices
            Collections.reverse(indices);
        }
        public ArrayedName(String name, List<ArrayIndex> indices) {
            this.name = name;
            this.indices = indices;
        }
        public String getName() { return name; }
        public int numDimensions() { return indices.size(); }
        public ArrayIndex getIndex(int i) {
            if (i < 0 || i > (indices.size()-1)) {
                return null;
            }
            return indices.get(i);
        }
        /**
         * Tries to combine this name with another name if they are in numeric sequence.
         * This means the ArrayIndex at the specified degree must be completely numeric.
         * If successful, returns the new combined name.  Otherwise, returns null.
         * @param dimension which dimension of a multi-dimensional array to use, starting from 0
         * at the least significant: name[1:4][1:3], 1:3 is dimension 0, 1:4 is dimension 1
         * @return the new combined name, or null if none possible
         */
        public ArrayedName combineSequentialNumeric(ArrayedName next, int dimension) {
            // check dimension in question, make sure it exists
            ArrayIndex index = getIndex(dimension);
            if (index == null) return null;
            if (!index.isNumericOnly()) return null;

            // make sure names are the same
            if (!name.equals(next.getName())) return null;
            // make sure same dimensions
            if (numDimensions() != next.numDimensions()) return null;

            // make sure all dimensions besides the one in question are the same
            for (int i=0; i<numDimensions(); i++) {
                if (i == dimension) continue;
                ArrayIndex curi = getIndex(i);
                ArrayIndex nexti = next.getIndex(i);
                if (!curi.equals(nexti)) {
                    return null;
                }
            }

            // see if dimension in question is sequential between the two
            ArrayIndex nextIndex = next.getIndex(dimension);
            ArrayIndex combined = index.combineSequentialNumeric(nextIndex);
            if (combined == null) return null;

            // sequential, create a new ArrayedName
            ArrayList<ArrayIndex> newIndices = new ArrayList<ArrayIndex>();
            for (int i=0; i<numDimensions(); i++) {
                if (i == dimension) {
                    newIndices.add(combined);
                    continue;
                }
                newIndices.add(getIndex(i));
            }
            return new ArrayedName(getName(), newIndices);
        }
        public String toString() {
            StringBuffer str = new StringBuffer(name);
            // have to go backwards
            for (int i=indices.size()-1; i>=0; i--) {
                ArrayIndex ind = getIndex(i);
                str.append("[");
                str.append(ind.toString());
                str.append("]");
            }
            return str.toString();
        }

        /** Unit Test */
//        public static void main(String [] args) {
//            ArrayedName n1 = new ArrayedName("foo[1]");
//            ArrayedName n2 = new ArrayedName("foo[2]");
//            printTest(n1, n2, 0);
//            printTest(n1, n2, 1);
//
//            n1 = new ArrayedName("foo[1:2][5:4][1:2]");
//            n2 = new ArrayedName("foo[1:2][3:1][1:2]");
//            printTest(n1, n2, 1);
//            printTest(n1, n2, 2);
//        }
//        public static void printTest(ArrayedName n1, ArrayedName n2, int dim) {
//            System.out.println("Combining dimension "+dim+" of: "+n1+" and "+ n2+" --> "+n1.combineSequentialNumeric(n2, dim));
//        }
    }

    // Parses a string of the form: ArrayedName,ArrayedName,....ArrayedName.
    private static class ArrayedNameList {
        private List<ArrayedName> arrayedNames;
        public ArrayedNameList(String name) {
            arrayedNames = new ArrayList<ArrayedName>();
            List<String> names = new ArrayList<String>();
            int openBrackets = 0;
            int last = 0;
            for (int i=0; i<name.length(); i++) {
                char c = name.charAt(i);
                if (c == '[') openBrackets++;
                if (c == ']') openBrackets--;
                if (c == ',' && openBrackets == 0) {
                    int start = last;
                    if (start >= 0 && i > start) {
                        names.add(name.substring(start, i));
                    }
                    last = i+1;     // skip ,
                }
            }
            if (openBrackets == 0 && last < name.length()) {
                names.add(name.substring(last, name.length()));
            }

            for (String s : names) {
                arrayedNames.add(new ArrayedName(s));
            }
        }
        private ArrayedNameList(List<ArrayedName> arrayedNames) {
            this.arrayedNames = arrayedNames;
        }
        public int getNumNames() { return arrayedNames.size(); }
        public ArrayedName get(int index) {
            if (index < 0 || index > (getNumNames()-1)) return null;
            return arrayedNames.get(index);
        }
        public int numDimensions() {
            int maxDimension = 0;
            for (int i=0; i<getNumNames(); i++) {
                int m = get(i).numDimensions();
                if (m > maxDimension) maxDimension = m;
            }
            return maxDimension;
        }

        public ArrayedNameList combineSequentialNumeric(ArrayedNameList next, int dimension) {
            ArrayedName thisLast = get(getNumNames()-1);
            ArrayedName nextFirst = next.get(0);
            if (thisLast != null && nextFirst != null) {
                // try to combine them
                ArrayedName combined = thisLast.combineSequentialNumeric(nextFirst, dimension);
                if (combined != null) {
                    // build new list with combined name
                    List<ArrayedName> newList = new ArrayList<ArrayedName>();
                    for (int i=0; i<getNumNames()-1; i++)
                        newList.add(get(i));        // skip last
                    newList.add(combined);          // add combined
                    for (int i=1; i<next.getNumNames(); i++) {
                        newList.add(next.get(i));   // skip first
                    }
                    return new ArrayedNameList(newList);
                }
            }
            return null;
        }
        public String toString() {
            StringBuffer buf = new StringBuffer();
            for (int i=0; i<getNumNames(); i++) {
                ArrayedName name = get(i);
                buf.append(name.toString());
                if (i < (getNumNames()-1)) // more to come
                    buf.append(',');
            }
            return buf.toString();
        }

        /** Unit Test */
//        public static void main(String [] args) {
//            ArrayedNameList n1 = new ArrayedNameList("foo[1],foo[2]");
//            ArrayedNameList n2 = new ArrayedNameList("foo[3],foo[4]");
//            printTest(n1, n2, 0);
//            printTest(n1, n2, 1);
//
//            n1 = new ArrayedNameList("foo[1,a],bar[2:4]");
//            n2 = new ArrayedNameList("bar[5],xxx[2]");
//            printTest(n1, n2, 0);
//            printTest(n1, n2, 1);
//
//            n1 = new ArrayedNameList("foo[1,a],bar[3:5][2:4]");
//            n2 = new ArrayedNameList("bar[6][2:4],xxx[2]");
//            printTest(n1, n2, 0);
//            printTest(n1, n2, 1);
//        }
//        public static void printTest(ArrayedNameList n1, ArrayedNameList n2, int dim) {
//            System.out.println("Combining dimension "+dim+" of: "+n1+" and "+ n2+" --> "+n1.combineSequentialNumeric(n2, dim));
//        }

    }

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

    /**
     * Optimize the specification of the entities and scan chain
     */
    private void optimize(List<Starter> chains) {
//        int done = 1;
        //while (done != 0) {
//            done = 0;
            /* done += */ flatten(chains);
            /* done += */ combineInstances();
        //}
    }

    /**
     * Flatten out specified entities, and flatten out entities that
     * only contain one subchain.  This does one pass through the entities,
     * several passes are required to optimize everything.
     */
    private int flatten(List<Starter> chains) {
        int done = 0;
        HashMap<Entity,Cell> alreadyFlattened = new HashMap<Entity,Cell>();
        for (Starter chain : chains) {
            done += chain.flattenInstances(cellsToFlatten, alreadyFlattened);
        }
        return done;
    }
    private int combineInstances() {
        int done = 0;
        for (Set<TraceElement> list : elements.values()) {
            for (TraceElement e : list) {
                if (e instanceof Entity) {
                    Entity ent = (Entity)e;
                    // check if we can flatten any instances
                    done += ent.combineInstances();
                }
            }
        }
        return done;
    }

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

    /**
     * A Port holds information about a single port, that may be part of a bussed portinst
     */
//    private static class Port {
//        private PortProto pp;
//        private int index;
//        private Name name;
//        private Nodable no;
//
//        private Port(Name name, Nodable no, PortProto pp, int index) {
//            this.name = name;
//            this.no = no;
//            this.pp = pp;
//            this.index = index;
//        }
//        public String toString() { if (name == null) return null; return no.getName() +":"+name.toString(); }
////        public void print() {
////            System.out.println("  Name: "+name);
////            System.out.println("  No: "+no);
////            System.out.println("  int: "+index);
////            System.out.println("  pp: "+pp);
////        }
//    }

//    private static class ExPort {
//        private Export ex;
//        private int index;
//        private Name name;
//
//        private ExPort(Name name, Export ex, int index) {
//            this.name = name;
//            this.ex = ex;
//            this.index = index;
//        }
//        public String toString() { if (name == null) return null; return name.toString(); }
////        public void print() {
////            System.out.println("  Name: "+name);
////            System.out.println("  Ex: "+ex);
////            System.out.println("  int: "+index);
////        }
//    }

//    private ExPort getExportedPort(Port port) {
//        if (port == null) return null;
//
//        Cell cell = port.no.getParent();
//
//        // list of all portinsts on net
//        Netlist netlist = cell.getNetlist(true);
//        Network net = netlist.getNetwork(port.no, port.pp, port.index);
//
//        for (Iterator<PortProto> it = cell.getPorts(); it.hasNext(); ) {
//            Export ex = (Export)it.next();
//            Name name = ex.getNameKey();
//            for (int i=0; i<name.busWidth(); i++) {
//                if (netlist.getNetwork(ex, i) == net)
//                    return new ExPort(name.subname(i), ex, i);
//            }
//        }
//        return null;
//    }

    /**
     * Get the network attached to port 'portName' on nodable 'no'.
     * @param no
     * @param portName
     * @return attached Network
     */
    private static Network getNetwork(Nodable no, Netlist netlist, String portName) {
        for (Iterator<PortProto> it = no.getProto().getPorts(); it.hasNext(); ) {
            PortProto pp = it.next();
            Name name = pp.getNameKey();
            for (int i=0; i<name.busWidth(); i++) {
                Name subname = name.subname(i);
                if (subname.toString().equals(portName)) {
                    return netlist.getNetwork(no, pp, i);
                }
            }
        }
        System.out.println("Error: Could not find port "+portName+" on "+no.getProto().describe(false)+"("+no.getName()+") in cell "+no.getParent().describe(false));
        return null;
    }

    private static Network getNetwork(Netlist netlist, String name) {
        for (Iterator<Network> it = netlist.getNetworks(); it.hasNext(); ) {
            Network net = it.next();
            if (net.hasName(name)) return net;
        }
        return null;
    }

    /**
     * Get a port from a port name on a Nodable
     * @param no the nodable
     * @param portName the port name including bus index, such as foo[1][2]
     * @return a Port, or null if none found
     */
//    private static Port getPort(Nodable no, String portName) {
//        if (portName == null) return null;
//        for (Iterator<PortProto> it = no.getProto().getPorts(); it.hasNext(); ) {
//            PortProto pp = it.next();
//            Name name = pp.getNameKey();
//            for (int i=0; i<name.busWidth(); i++) {
//                Name subname = name.subname(i);
//                if (subname.toString().equals(portName)) {
//                    return new Port(subname, no, pp, i);
//                }
//            }
//        }
//        System.out.println("Could not find "+portName+" on "+no.getName());
//        return null;
//    }

    /**
     * Get an export in a cell from a exportName. The export
     * name may include a bus index, such as foo[1][2]
     * @param cell the cell
     * @param exportName the export name
     * @return an ExPort
     */
//    private static ExPort getExPort(Cell cell, String exportName) {
//        for (Iterator<PortProto> it = cell.getPorts(); it.hasNext(); ) {
//            Export ex = (Export)it.next();
//            Name name = ex.getNameKey();
//            for (int i=0; i<name.busWidth(); i++) {
//                Name subname = name.subname(i);
//                if (subname.toString().equals(exportName)) {
//                    return new ExPort(subname, ex, i);
//                }
//            }
//        }
//        return null;
//    }

}
