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

import java.util.List;
import java.util.ArrayList;

/**
 * Represents an entire scan chain, covering all of the scan chain elements at a
 * single address on the chip's JTAG controller. Sometimes called a "root" scan
 * chain, to emphasize distinction from a sub-chain. I/O to the chip occurs to
 * the entire root scan chain, rather than to individual
 * <code>SubchainNode</code>s.
 * <p>
 * 
 * By convention, the first bit in a BitVector or in a string always represents
 * the last bit scanned into or out of the chip. Thus 1) the bit and character
 * indices match the position of the corresponding scan chain element along the
 * s_in chain, 2) the strings match the left-to-right order in which scan chain
 * elements appear in most schematics, and 3) the order is consistent with the
 * order of scan chain nodes in the XML file.
 * <p>
 */

public class ChainNode extends SubchainNode {

    /**
     * Little-endian character string (e.g., "101010") representation of the
     * root scan chain's address at the JTAG controller. Bits 6 and 7 (the read
     * and write enable) are ignored during I/O with the chip.
     */
    final private String opcode;

    /**
     * Scan chain bit pattern to be shifted into the chip during the next call
     * to this.shift().
     */
    protected BitVector inBits;

    /**
     * Scan chain bit pattern read back from the chip after last call to
     * this.shift(). Should only be modified by Netscan.
     */
    protected BitVector outBits;

    /**
     * Expected value of outBits during the next call to this.shift(). Usually
     * this is just the previous value of inBits, but it can be modified by
     * Master Clears and reading from the chip.
     */
    protected BitVector outBitsExpected;

    /** Expected value of outBits for the previous call to this.shift(). */
    protected BitVector oldOutBitsExpected;

    /**
     * State of the scan chain elements' shadow register, for those that have
     * one.
     */
    protected BitVector shadowState;

    /** Whether any data has been shifted to this scan chain */
    private boolean initialized = false;

    /**
     * A list of shift listeners
     */
    private List<ShiftListener> listeners;


    /**
     * Constructor for a root scan chain.
     * 
     * @param name
     *            node name
     * @param opcode
     *            on-chip address of root scan chain
     * @param newLength
     *            number of scan chain elements in node
     * @param comment
     *            comment attached to this node
     */
    public ChainNode(String name, String opcode, int newLength, String comment) {
        super(name, newLength, comment);

        this.opcode = opcode;
        listeners = new ArrayList<ShiftListener>();

        createBitVectors();
    }

    public String toString() {
        return super.toString() + " (op=" + opcode + ")";
    }

    /**
     * Little-endian character string (e.g., "101010") representation of the
     * root scan chain's address at the JTAG controller. These are the low-order
     * 6 bits of the instruction register needed to access the chain.
     * 
     * @return Address of root scan chain (little endian)
     */
    String getOpcode() {
        return opcode;
    }

    /**
     * Set most recent bit sequence that was shifted out of the root scan chain
     * during this.shift(). Should only be used by
     * {@link NetscanGeneric#netScan_DR}.
     */
    void setOutBits(BitVector newOutBits) {
        outBits.put(0, newOutBits);
    }

    /**
     * Get the scan chain bit pattern to be shifted into the chip during the next call
     * to this.shift().
     * @return the in bits
     */
    public BitVector getInBits() { return inBits; }

    /**
     * Get the scan chain bit pattern read back from the chip after last call to
     * this.shift(). Should only be modified by Netscan.
     * @return the out bits
     */
    public BitVector getOutBits() { return outBits; }

    /**
     * Get expected value of outBits during the next call to this.shift(). Usually
     * this is just the previous value of inBits, but it can be modified by
     * Master Clears and reading from the chip.
     * @return the out bits expected
     */
    public BitVector getOutBitsExpected() { return outBitsExpected; }

    /**
     * Get the expected value of outBits for the previous call to this.shift().
     * @return the old out bits expected
     */
    public BitVector getOldOutBitsExpected() { return oldOutBitsExpected; }
    
    /**
     * Get the state of the scan chain elements' shadow register, for those that have
     * one.
     * @return the shadow state bits
     */
    public BitVector getShadowState() { return shadowState; }

    /**
     * This is for ChainG display
     * @return the outbits
     */
    protected BitVector getOutBitsIndiscriminate() { return outBits; }

    /**
     * Returns ancestor <code>ChipNode</code>.
     * 
     * @return Chip node that is the ancestor to this <code>ChainNode</code>
     */
    ChipNode getParentChip() {
        MyTreeNode node = this;
        while (node.getClass() != ChipNode.class) {
            node = node.getParent();
            if (node == null) {
                Infrastructure.fatal(node
                        + " does not have a ChipNode as an ancestor");
            }
        }
        return (ChipNode) node;
    }

    /**
     * Update shadowState data structure in response to a master clear. Call
     * this on a transition of master clear to <tt>HI</tt> _or_ <tt>LO</tt>,
     * or you may get false complaints about data shifted out not equalling
     * expected results.
     */
    void processMasterClear() {
        for (int ind = 0; ind < getLength(); ind++) {
            SubchainNode node = findNodeAtIndex(ind);
            if (node.usesShadow() || node.usesDualPortedShadow()) {
                int clears = node.getClearBehavior();

                // Note clearing has no effect on elements with CLEARS_NOT.
                // Otherwise sets the shadow register to HI, LO, or to an
                // unknown (invalid) state.
                if (clears == TestNode.CLEARS_LO) {
                    shadowState.clear(ind);
                } else if (clears == TestNode.CLEARS_HI) {
                    shadowState.set(ind);
                } else if (clears == TestNode.CLEARS_UNKNOWN) {
                    shadowState.invalidate(ind);
                }
            }
        }
    }

    /**
     * Resets all inBits to zero, or to clears state if clearable and specified to do so
     * @param useMasterClearState true to reset to clears state if clearable.
     */
    public void resetInBits(boolean useMasterClearState) {
        for (int i=0; i<getLength(); i++) {
            SubchainNode node = findNodeAtIndex(i);
            if (useMasterClearState && node.getClearBehavior() == TestNode.CLEARS_HI)
                inBits.set(i, true);
            else
                inBits.set(i, false);
        }
    }

    /**
     * Invalidate expected scan chain element values for this scan chain. Call
     * this after a period at a low voltage, or you may get false complaints
     * about data shifted out not equalling expected results. Sets
     * {@link ChainNode#initialized}&nbsp;to false to suppress "no bits being
     * compared" warning on next shift.
     */
    void invalidate() {
        outBitsExpected.invalidate();
        initialized = false;
    }

    /**
     * Shift data in this.inBits into the appropriate scan chain on the chip.
     * The previous data on the chip is shifted out into this.outBits, which is
     * then compared with expectation.
     * 
     * @param jtag
     *            JTAG tester object to perform shift with
     * @param readEnable
     *            whether to set opcode's read-enable bit
     * @param writeEnable
     *            whether to set opcode's write-enable bit
     * @param irBadSeverity
     *            action when bits scanned out of IR are wrong
     * @param noTestSeverity
     *            action when no consistency check is possible
     * @param errTestSeverity
     *            action when consistency check fails
     * @param logger
     *            Object with logging properties to use
     * 
     * @return true if the bits scanned out equal their expected values
     * @see Infrastructure#SEVERITY_NOMESSAGE
     * @see Infrastructure#SEVERITY_WARNING
     * @see Infrastructure#SEVERITY_NONFATAL
     * @see Infrastructure#SEVERITY_FATAL
     */
    boolean shift(JtagTester jtag, boolean readEnable, boolean writeEnable,
            int irBadSeverity, int noTestSeverity, int errTestSeverity,
            Logger logger) {
        logger.logOther("------ " + getPathString() + ", R=" + readEnable
                + ", W=" + writeEnable);

        jtag.shift(this, readEnable, writeEnable, irBadSeverity);

        boolean noErrors = checkOutBits(readEnable, noTestSeverity,
                errTestSeverity);

        // After shift, all elements should hold inBits
        oldOutBitsExpected.putIndiscriminate(0, outBitsExpected);
        outBitsExpected.put(0, inBits);

        // Update the expected bits from any shadow registers
        // Note: this implies that shadow registers are not modified by the chip
        if (readEnable) {
            for (int ind = 0; ind < getLength(); ind++) {
                SubchainNode node = findNodeAtIndex(ind);
                if (node.isReadable() && (node.usesShadow() && !node.usesDualPortedShadow())) {
                    if (shadowState.isValid(ind)) {
                        boolean state = shadowState.get(ind);
                        outBitsExpected.set(ind, state);
                    }
                }
            }
        }
        // Update the shadowState of any elements that wrote to their
        // shadow registers
        if (writeEnable) {
            for (int ind = 0; ind < getLength(); ind++) {
                SubchainNode node = findNodeAtIndex(ind);
                if (node.isWriteable() && (node.usesShadow() || node.usesDualPortedShadow())) {
                    boolean state = inBits.get(ind);
                    shadowState.set(ind, state);
                }
            }
        }
        shiftCompleted();

        return noErrors;
    }

    /**
     * Shift one bit of data in this.inBits into the appropriate scan chain on
     * the chip. No consistency checking is performed on bit scanned out. This
     * is provided only for measuring chain length in ChainTest, and should not
     * otherwise be used.
     * 
     * @param jtag
     *            JTAG tester object to perform shift with
     * @param readEnable
     *            whether to set opcode's read-enable bit
     * @param writeEnable
     *            whether to set opcode's write-enable bit
     * @param irBadSeverity
     *            action when bits scanned out of IR are wrong
     * @param logger
     *            Object with logging properties to use
     * @return the bit that got shifted out
     * 
     * @see Infrastructure#SEVERITY_NOMESSAGE
     * @see Infrastructure#SEVERITY_WARNING
     * @see Infrastructure#SEVERITY_NONFATAL
     * @see Infrastructure#SEVERITY_FATAL
     */
    boolean shiftOneBit(JtagTester jtag, boolean readEnable,
            boolean writeEnable, int irBadSeverity, Logger logger) {
        logger.logOther("***** " + getPathString() + ", inBits=" + inBits);

        /*
         * Temporarily change chain length to 1. Note outBits could potentially
         * be clobbered after each length change (currently it isn't), so safest
         * to store intermediate outbit.
         */
        int length = getLength();
        setLength(1);
        jtag.shift(this, readEnable, writeEnable, irBadSeverity);
        boolean outbit = outBits.get(0);
        setLength(length);

        /*
         * Since this is only needed during development of the scan chain XML
         * file, it's not worth figuring out resulting state of chain
         */
        outBitsExpected.invalidate();
        oldOutBitsExpected.invalidate();
        shadowState.invalidate();

        return outbit;
    }

    /**
     * Called when the length of a node underneath this root node changes,
     * recomputes the root node's length. In addition, resizes the inBits and
     * outBits arrays to account for the change. Previous array contents are
     * overwritten with 0. Note method overrides that in parent.
     */
    void lengthChanged() {

        // Recompute current node's length, taking children into account
        this.computeLength();

        createBitVectors();
    }

    /**
     * Find descendent of current <code>ChainNode</code> that contains the
     * scan chain element at index bitIndex.
     * 
     * @param bitIndex
     *            index of scan chain element
     * @return scan chain node containing the specified element.
     */
    SubchainNode findNodeAtIndex(int bitIndex) {
        if (bitIndex < 0 || bitIndex > this.getLength()) {
            throw new IllegalArgumentException("bitIndex " + bitIndex +
                    " not in allowed range 0.." + this.getLength());
        }
        SubchainNode node = this;
        SubchainNode parent = null;
        for (int indChild=0, nodeIndex = 0;; indChild++) {
            // Find index in scan chain of next sibling node
            int nextIndex = nodeIndex + node.getLength();

            if (nextIndex <= bitIndex) {
                // Target not within this node, accumlate length and try
                // next sibling
                nodeIndex = nextIndex;
            } else {
                // Within this node. If leaf node, we have found it. Else,
                // try first child.
                int nkids = node.getChildCount();
                if (nkids == 0) {
                    return node;
                }
                parent = node;
                indChild = 0;
            }
            node = (SubchainNode) parent.getChildAt(indChild);
        }
    }

 
    /*
     * Called by constructor or when length changed, replaces the BitVector
     * members with new versions of the correct length.
     */
    protected void createBitVectors() {
        int newLength = getLength();
        if (newLength < 0)
            newLength = 0;

        inBits = new BitVector(newLength, getPathString() + ".inBits");
        outBitsExpected = new BitVector(newLength, getPathString()
                + ".outBitsExpected");
        oldOutBitsExpected = new BitVector(newLength, getPathString()
                + ".oldOutBitsExpected");
        outBits = new BitVector(newLength, getPathString() + ".outBits");
        shadowState = new BitVector(newLength, getPathString() + ".shadowState");
    }

    /**
     * Checks if the bits scanned out of the scan chain equal their expected
     * values. Optionally prints a warning to stderr if deviations are found.
     * 
     * @param readEnable
     *            whether readable chain elements read values in
     * @param noTestSeverity
     *            action when no consistency check is possible
     * @param errTestSeverity
     *            action when consistency check fails
     * @return true if no deviations found
     */
    private boolean checkOutBits(boolean readEnable, int noTestSeverity,
            int errTestSeverity) {

        int length = getLength();

        /*
         * All elements, except those that are unpredictable or have read a
         * value from another part of the chip, should now have the value in the
         * previous inBits
         */
        for (int ind = 0; ind < length; ind++) {
            SubchainNode node = findNodeAtIndex(ind);
            if (node.isUnpredictable()) {
                outBitsExpected.invalidate(ind);
            } else if (readEnable && node.isReadable()) {

                /*
                 * Can only know read value of an element with a shadow register
                 * that is in a known state
                 */
                if (node.usesShadow() && shadowState.isValid(ind)) {
                    outBitsExpected.set(ind, shadowState.get(ind));
                } else {
                    outBitsExpected.invalidate(ind);
                }
            }
        }

        // If no bits being compared, optionally print warning message
        if (outBitsExpected.isInvalid()) {
            if (initialized || noTestSeverity == Infrastructure.SEVERITY_FATAL
                    || noTestSeverity == Infrastructure.SEVERITY_NONFATAL) {
                Infrastructure.error(noTestSeverity, getPathString()
                        + ".shift() warning: no bits being compared, "
                        + "see ${TEST_ROOT}/FAQ.html");
            }
        }
        initialized = true;

        // Create a BitVector that has a bit set for every deviation
        BitVector errors = new BitVector(length, "checkOutBits()-errors");
        for (int iBit = 0; iBit < length; iBit++) {
            if (outBitsExpected.isValid(iBit)
                    && outBits.get(iBit) != outBitsExpected.get(iBit)) {
                errors.set(iBit);
            } else {
                errors.clear(iBit);
            }
        }

        boolean noErrors = errors.isEmpty();
        if (noErrors == false) {
            Infrastructure.error(errTestSeverity, getPathString()
                    + ".shift() error:\n  expected: " + outBitsExpected
                    + "\n  outBits: " + outBits
                    + "\nFor details, see the Appendix in 'Using the Test"
                    + "\nSoftware Library' for details about this error.");
        }
        return noErrors;
    }

    int findRun(int indStart) {
        SubchainNode start = findNodeAtIndex(indStart);
        int clears = start.getClearBehavior();
        boolean read = start.isReadable();
        boolean write = start.isWriteable();
        boolean shadow = start.usesShadow();
        boolean unpredictable = start.isUnpredictable();

        int ind;
        for (ind = indStart; ind < getLength(); ind++) {
            SubchainNode subchain = findNodeAtIndex(ind);
            if (subchain.getClearBehavior() != clears
                    || subchain.isReadable() != read
                    || subchain.isWriteable() != write
                    || subchain.usesShadow() != shadow
                    || subchain.isUnpredictable() != unpredictable) {
                return ind;
            }
        }

        return ind;
    }

    /** Helper for CompareXML */
    void compare(ChainNode that, String thisFile, String thatFile) {
        //       System.out.println("Differences for chain " + this);
        super.compare(that, thisFile, thatFile);
        int length = getLength();
        if (that.getLength() != length) {
            System.out.println("**** Chain " + thisFile + ":" + this
                    + " has length " + length + ", but " + thatFile + ":"
                    + that + " has " + that.getLength()
                    + ".  Aborting comparison");
            Infrastructure.exit(1);
        }

        int thisIndex = 0, thatIndex = 0;
        while (thisIndex < length && thatIndex < length) {
            SubchainNode thisSubchain = findNodeAtIndex(thisIndex);
            SubchainNode thatSubchain = that.findNodeAtIndex(thatIndex);

            int thisStartIndex = thisIndex;
            int thatStartIndex = thatIndex;
            thisIndex = findRun(thisIndex);
            thatIndex = that.findRun(thatIndex);

            //            System.out.println("Comparing run from " + thisStartIndex
            //                    + " to " + thisIndex + ", starting at "
            //                    + thisSubchain.getPathString());
            if ((thisIndex - thisStartIndex) != (thatIndex - thatStartIndex)) {
                System.out.println("**** " + thisFile
                        + " has subchain run of length "
                        + +(thisIndex - thisStartIndex)
                        + " starting at subchain "
                        + thisSubchain.getPathString() + ", but " + thatFile
                        + " has run of length " + (thatIndex - thatStartIndex)
                        + " starting at subchain "
                        + thatSubchain.getPathString());
            }
            String thisState = thisSubchain.getState();
            String thatState = thatSubchain.getState();
            if (thisState.equals(thatState) == false) {
                System.out.println("**** Subchain run starting at " + thisFile
                        + ":" + thisSubchain.getPathString() + " has mode "
                        + thisState + ", but run starting at " + thatFile
                        + ": " + thatSubchain.getPathString() + " has mode "
                        + thatState);
            }
        }
    }

    public static interface ShiftListener {
        public void shiftCompleted(ChainNode node);
    }

    public void addListener(ShiftListener l) {
        listeners.add(l);
    }

    public void removeListener(ShiftListener l) {
        listeners.remove(l);
    }

    private void shiftCompleted() {
        for (ShiftListener l : listeners) {
            l.shiftCompleted(this);
        }
    }


    public static void main(String[] args) {
        String filename, path;
        int index;
        if (args.length >= 3) {
            filename = args[0];
            path = args[1];
            index = Integer.parseInt(args[2]);
        } else {
            filename = "heater.xml";
            path = "heater.pScan";
            index = 40;
        }
        ChainModel cm = new ChainModel(filename);
        ChainNode node = (ChainNode) cm.findNode(path);
        SubchainNode found = node.findNodeAtIndex(index);
        System.out.println(found.getPathString());
    }
}
