/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: ChainTest.java * Written by Tom O'Neill, Sun Microsystems. * * Copyright (c) 2004 Sun Microsystems and Static Free Software * * 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 Electric(tm); see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, Mass 02111-1307, USA. */ package com.sun.electric.tool.simulation.test; import java.io.FileWriter; import java.io.PrintWriter; import java.util.Random; /** * Class provides various methods for testing scan chains, including generation * of Schmoo plots and verification of scan chain lengths. Can also be used to * verify chain functionality or to measure or measure scan chain lengths. */ public class ChainTest { /** * Whether to assert the scan chain read line during chain testing. (Ignored * by <code>testOneChainShadow()</code> and * <code>schmooPlotShadow()</code>.) */ public boolean readEnable = false; /** * Whether to assert the scan chain write line during chain testing. * (Ignored by {@link #testOneChainShadow}and {@link #schmooPlotShadow}.) */ public boolean writeEnable = false; /** * Default TCK frequency increment for Schmoo plots, in kHz * * @see #schmooPlot */ public static final int DEFAULT_KHZ_STEP = 1000; /** * Number of times {@link #measureLength} should shift zeros into * chain to be sure it is full of zeros, when chain length is not known for * certain */ public static final int LENGTH_MULTIPLIER = 100; static int seed = 1256; //random number seed static Random rand = new Random(seed); /** Index number of current Schmoo plot */ static int count = 1; /** Object containing scan chain model/APIs */ private ChainControl control; /** Number of iterations used in testing a scan chain */ private int numTests = 5; /** Generic interface for setting Vdd */ PowerChannel vddSupply; /** Voltage range for Schmoo plot */ private int mvLow, mvHigh, mvStep = Infrastructure.DEFAULT_MV_STEP; /** Frequency range for Schmoo plot */ private int khzLow, khzHigh, khzStep = DEFAULT_KHZ_STEP; /** * Create a scan chain tester for a particular experimental setup. Sets * initial Vdd and JTAG TCK frequency ranges for Schmoo plots to a small * range around the nominal values. * * @see #schmooPlot * @param control * Object providing scan chain programming * @param vddSupply * Object providing control of chip Vdd */ public ChainTest(ChainControl control, PowerChannel vddSupply) { this.control = control; this.vddSupply = vddSupply; mvLow = roundMillivolts(0.95f * 1000.f * control.getJtagVolts()); mvHigh = roundMillivolts(1.05f * 1000.f * control.getJtagVolts()); if (mvLow == mvHigh) mvLow = mvHigh - Infrastructure.DEFAULT_MV_STEP; khzLow = roundKHz(0.8f * control.getJtagKhz()); khzHigh = control.getJtagKhz(); if (khzLow == khzHigh) khzLow = khzHigh - DEFAULT_KHZ_STEP; } /** * Create a scan chain tester. * * @param control * Object providing scan chain programming */ public ChainTest(ChainControl control){ this(control,null); this.vddSupply = new ManualPowerChannel("fake",false); } /** * Measure the number of scan chain elements in a root scan chain. * Optionally prints error message if length is different from claimed * length. Correct functioning requires that assumed length is within a * factor of LENGTH_MULTIPLIER of the true length. * * @param chainRoot * path to root scan chain, starting at the chip node * @param severity * action when length differs from expected length * @return number of scan chain elements in chainRoot * @see Infrastructure#SEVERITY_NOMESSAGE * @see Infrastructure#SEVERITY_WARNING * @see Infrastructure#SEVERITY_NONFATAL * @see Infrastructure#SEVERITY_FATAL */ public int measureLength(String chainRoot, int severity) { if (readEnable || writeEnable) { System.err.println("ChainTest.measureLength() warning: " + "results may be incorrect when readEnable " + "or writeEnable is true"); } int expectedLength = control.getLength(chainRoot); BitVector inBits = new BitVector(expectedLength, "measureLength()-expectedLength"); inBits.set(0, expectedLength, false); // Fill chain with zeros, assuming it is at most LENGTH_MULTIPLIER // times the actual length control.setInBits(chainRoot, inBits); for (int ind = 0; ind < LENGTH_MULTIPLIER; ind++) { control.shift(chainRoot, readEnable, writeEnable, Infrastructure.SEVERITY_FATAL, Infrastructure.SEVERITY_NOMESSAGE, Infrastructure.SEVERITY_NOMESSAGE); } BitVector outBits = control.getOutBits(chainRoot); if (outBits.isEmpty() == false) { Infrastructure.fatal("Have shifted " + expectedLength + " zeroes into chain " + chainRoot + " " + LENGTH_MULTIPLIER + " times in a row, and the final shift scanned out at " + "least one non-zero bit. The chain is broken or its" + "true length is more than " + LENGTH_MULTIPLIER + "times the claimed length of " + expectedLength); } // Single-shift in ones until we start seeing them inBits.set(expectedLength - 1); control.setInBits(chainRoot, inBits); int ind = 0; boolean bit = false; do { bit = control.shiftOneBit(chainRoot, readEnable, writeEnable, Infrastructure.SEVERITY_FATAL); ind++; } while (bit == false); ind--; if (ind != expectedLength) { Infrastructure.error(severity, "Chain " + chainRoot + " has claimed length of " + expectedLength + ", but measured length is " + ind); } return ind; } /** * Compares lengths of all scan chains in the system to their claimed * values. * * @param severity * action when length differs from expected length * @return true if all chains had expected length * @see Infrastructure#SEVERITY_NOMESSAGE * @see Infrastructure#SEVERITY_WARNING * @see Infrastructure#SEVERITY_NONFATAL * @see Infrastructure#SEVERITY_FATAL */ public boolean testLengths(int severity) { boolean status = true; String[] roots = control.getChainPaths(); for (int iroot = 0; iroot < roots.length; iroot++) { System.out.print("Verifying length, chain " + iroot + ": " + roots[iroot] + " "); int length = measureLength(roots[iroot], severity); if (length == control.getLength(roots[iroot])) { System.out.println("passed"); } else if (severity == Infrastructure.SEVERITY_NOMESSAGE) { System.out.println("failed; change length to " + length); status = false; } } return status; } /** * Test a single root scan chain by shifting in and out up to * <code>numTests</code> random bit sequences. If incorrect bits are * shifted out or another shift error occurs, returns immediately. * * @param chainRoot * Path to root scan chain, starting at chip (e.g., * "miniHeater.eScan") * @param errTestSeverity * action when consistency check fails * @return <tt>true</tt> if bits written, read were always the same * @see Infrastructure#SEVERITY_NOMESSAGE * @see Infrastructure#SEVERITY_WARNING * @see Infrastructure#SEVERITY_NONFATAL * @see Infrastructure#SEVERITY_FATAL */ public boolean testOneChain(String chainRoot, int errTestSeverity) { int len = this.control.getLength(chainRoot); // Set bits to be written to a random sequence control.setInBits(chainRoot, getRandomBits(len)); // Shift the random sequence into the scan chain. Ignore // any errors, because they might be due to a previous // shift at a bad voltage/frequency combination. control.shift(chainRoot, readEnable, writeEnable, Infrastructure.SEVERITY_NOMESSAGE, Infrastructure.SEVERITY_NOMESSAGE, Infrastructure.SEVERITY_NOMESSAGE); for (int iTest = 0; iTest < numTests; iTest++) { // Set bits to be written to a random sequence control.setInBits(chainRoot, getRandomBits(len)); // Shift again, see if the bits scanned out are the same as those // scanned in during the previous shift. Fatal exception // if no bits can be compared (should be impossible). boolean result = control.shift(chainRoot, readEnable, writeEnable, Infrastructure.SEVERITY_NOMESSAGE, Infrastructure.SEVERITY_FATAL, errTestSeverity); if (result == false) return false; } //end for return true; } /** * Test a single root scan chain by writing and reading up to * <code>numTests</code> random bit sequences to/from the shadow registers * of a RWS (readable/writeable shadow register) scan chain. If an error is * found, returns immediately. * <p> * WARNING: Do not use on scan chains that can put the chip in undesirable * states, such as the Heater chip power scan chains. Also, incorrect * results may result when master clear is on. * <p> * Ignores state of <code>readEnable</code> and <code>writeEnable</code> * member variables. * * @param chainRoot * Path to root scan chain, starting at chip (e.g., * "miniHeater.eScan") * @param errTestSeverity * action when consistency check fails * @return <tt>true</tt> if bits written, read were always the same * @see Infrastructure#SEVERITY_NOMESSAGE * @see Infrastructure#SEVERITY_WARNING * @see Infrastructure#SEVERITY_NONFATAL * @see Infrastructure#SEVERITY_FATAL */ public boolean testOneChainShadow(String chainRoot, int errTestSeverity) { ChainNode chain = (ChainNode) control.findNode(chainRoot); if (chain.isReadable() == false || chain.isWriteable() == false || chain.usesShadow() == false) { Infrastructure.fatal("Chain " + chain + " does not have RW shadow register, as required" + " to use testOneChainShadow()"); } int len = this.control.getLength(chainRoot); // Clear out any error conditions from previous shifts control.setInBits(chainRoot, getRandomBits(len)); control.shift(chainRoot, false, false, Infrastructure.SEVERITY_NOMESSAGE, Infrastructure.SEVERITY_NOMESSAGE, Infrastructure.SEVERITY_NOMESSAGE); for (int iTest = 0; iTest < numTests; iTest++) { // Set bits to be written to a random sequence control.setInBits(chainRoot, getRandomBits(len)); // Write the random sequence into the scan chain's shadow register control.shift(chainRoot, false, true, Infrastructure.SEVERITY_NOMESSAGE, Infrastructure.SEVERITY_NOMESSAGE, Infrastructure.SEVERITY_NOMESSAGE); // This should force an error if the chain is not RWS control.setInBits(chainRoot, getRandomBits(len)); // Read back from the shadow register, should get the same result if (control.shift(chainRoot, true, false, Infrastructure.SEVERITY_NOMESSAGE, Infrastructure.SEVERITY_FATAL, errTestSeverity) == false) { return false; } } //end for return true; } /** * Test all scan chains within a given chip by reading and writing random * sequences. Optionally prints a message telling whether each root scan * chain passes or fails. * * @param chipName * of the chip (e.g., "miniHeater") * @param errTestSeverity * action when data register bit consistency check fails * @return Number of scan chains that failed * @see #testOneChain * @see Infrastructure#SEVERITY_NOMESSAGE * @see Infrastructure#SEVERITY_WARNING * @see Infrastructure#SEVERITY_NONFATAL * @see Infrastructure#SEVERITY_FATAL */ public int testAllChains(String chipName, int errTestSeverity) { int numFail = 0; String[] roots = control.getChainPaths(chipName); for (int iroot = 0; iroot < roots.length; iroot++) { System.out.print("Trying dual shift to chain " + iroot + ": " + roots[iroot] + "... "); if (testOneChain(roots[iroot], errTestSeverity)) { System.out.println(" passed."); } else { numFail++; System.out.println(" failed."); } } if (numFail > 0) System.err.println(numFail + " out of " + roots.length + " chains failed on chip " + chipName); return numFail; } //end testAllChains /** * Test all scan chains in the system. Optionally prints a message telling * whether each root scan chain passes or fails. * * @param errTestSeverity * action when consistency check fails * @return True if all scan chains pass * @see #testOneChain * @see Infrastructure#SEVERITY_NOMESSAGE * @see Infrastructure#SEVERITY_WARNING * @see Infrastructure#SEVERITY_NONFATAL * @see Infrastructure#SEVERITY_FATAL */ public int testAllChains(int errTestSeverity) { int numFail = 0; String[] chips = control.getChips(); for (int ichip = 0; ichip < chips.length; ichip++) { numFail += testAllChains(chips[ichip], errTestSeverity); } if (numFail > 0) System.err.println(numFail + " chains failed in system!"); return numFail; } //end testAllChains /** * Saves a Schmoo Plot to <code>failFile</code> and <code>passFile</code>. * Method {@link #testOneChain(String, int)}is used to determine if the * chain passes at each voltage and frequency specified. * <p> * Currently the JTAG tester is left at the default Vdd value, even though * the chip voltage is changed. This is required for correct JTAG tester * functioning, and does not noticeably pull up the chip voltage. * * @param chainRoot * Path to root scan chain to test (e.g., "miniHeater.eScan") * @param failFile * File containing voltage, frequency pairs that failed * @param passFile * File containing voltage, frequency pairs that passed * @see #setKhzRange * @see #setVddRange */ public void schmooPlot(String chainRoot, String failFile, String passFile) { schmooPlot(chainRoot, failFile, passFile, false, false); } /** * Saves a Schmoo Plot to <code>failFile</code> and <code>passFile</code>. * Method {@link #testOneChain(String, int)}is used to determine if the * chain passes at each voltage and frequency specified. * <p> * Currently the JTAG tester is left at the default Vdd value, even though * the chip voltage is changed. This is required for correct JTAG tester * functioning, and does not noticeably pull up the chip voltage. * * @param chainRoot * Path to root scan chain to test (e.g., "miniHeater.eScan") * @param failFile * File containing voltage, frequency pairs that failed * @param passFile * File containing voltage, frequency pairs that passed * @param pulseReset * Reset jtag tester (using TRST) before each pass * @see #setKhzRange * @see #setVddRange */ public void schmooPlot(String chainRoot, String failFile, String passFile, boolean pulseReset) { schmooPlot(chainRoot, failFile, passFile, false, pulseReset); } /** * Like {@link #schmooPlot}, but uses {@link #testOneChainShadow}instead * of {@link #testOneChain}. See warnings at {@link #testOneChainShadow}! * * @param chainRoot * Path to root scan chain to test (e.g., "miniHeater.eScan") * @param failFile * File containing voltage, frequency pairs that failed * @param passFile * File containing voltage, frequency pairs that passed * @param pulseReset * Reset jtag tester (using TRST) before each pass * @see #setKhzRange * @see #setVddRange */ public void schmooPlotShadow(String chainRoot, String failFile, String passFile, boolean pulseReset) { schmooPlot(chainRoot, failFile, passFile, true, pulseReset); } // Actual Schmoo plot implementation, using either testOneChain() or // testOneChainShadow() as specified by <code>shadow</code> private void schmooPlot(String chainRoot, String failFile, String passFile, boolean shadow, boolean pulseReset) { System.out.println("Generating Schmoo plot for chain " + chainRoot); try { PrintWriter fail = new PrintWriter(new FileWriter(failFile)); fail.println("# voltage, frequency pairs that failed in Schmoo of " + chainRoot); PrintWriter pass = new PrintWriter(new FileWriter(passFile)); pass.println("# voltage, frequency pairs that passed in Schmoo of " + chainRoot); for (int vdd_mV = mvLow; vdd_mV <= mvHigh; vdd_mV += mvStep) { float vdd = (float) vdd_mV / 1000; this.vddSupply.setVoltageWait(vdd); System.out.println("Setting Vdd = " + vdd + " V"); if (pulseReset) { System.out.println("Resetting Jtag Tester"); control.jtag.reset(); } for (int kiloHerz = khzLow; kiloHerz <= khzHigh; kiloHerz += khzStep) { control.jtag.configure(control.getJtagVolts(), kiloHerz); boolean status; if (shadow) status = testOneChainShadow(chainRoot, Infrastructure.SEVERITY_NOMESSAGE); else status = testOneChain(chainRoot, Infrastructure.SEVERITY_NOMESSAGE); if (status) { pass.println(vdd_mV + " " + kiloHerz); } else { fail.println(vdd_mV + " " + kiloHerz); } System.out.println("freq " + kiloHerz + "kHz, voltage " + vdd + " V"); } //end inner for } //end outer for fail.close(); pass.close(); } //end try catch (Exception e) { System.err.println("exception occurred" + e); } //end catch // Return to sane levels //this.vddSupply.setVoltageWait(control.getJtagVolts()); System.out.println("Please set vdd back to something correct"); control.jtag.configure(control.getJtagVolts(), control.getJtagKhz()); count++; System.out.println("finished testing " + chainRoot); } //end schmooPlot /** * Convienence method tests general scan chain functionality during bringup * of a chip or system. For each scan chain in the chip, verifies the length * and tests shifting data in and out. Then generate a Schmoo plot for the * longest scan chain in the system, using whatever Vdd and KHz ranges have * been set. * * @param testLengths * whether to test the lengths (can be slow) */ public void bringup(boolean testLengths) { if (testLengths) { if (!testLengths(Infrastructure.SEVERITY_NONFATAL)) { Infrastructure.fatal("Fix lengths in xml file and run again"); } } testAllChains(Infrastructure.SEVERITY_FATAL); // Find longest chain in system int maxLength = -1; String maxName = null; String[] chains = control.getChainPaths(); for (int ichain = 0; ichain < chains.length; ichain++) { int length = control.getLength(chains[ichain]); if (length > maxLength) { maxLength = length; maxName = chains[ichain]; } } // Now generate Schmoo plot for the longest chain schmooPlot(maxName, "fail." + maxName + ".dat", "pass." + maxName + ".dat", false); } /** * Returns highest frequency included in Schmoo plot * * @return Highest frequency included in Schmoo plot, in kHz */ public int getKhzHigh() { return khzHigh; } /** * Returns lowest frequency included in Schmoo plot * * @return Lowest frequency included in Schmoo plot, in kHz */ public int getKhzLow() { return khzLow; } /** * Returns frequency step in Schmoo plot * * @return Frequency step in Schmoo plot, in kHz */ public int getKhzStep() { return khzStep; } /** * Number of iterations used in testing a scan chain. * * @return Number of iterations used in testing a scan chain * @see #testOneChain * @see #testAllChains */ public int getNumTests() { return this.numTests; } /** * Returns high Vdd value in Schmoo plot, in Volts * * @return High Vdd value in Schmoo plot, in Volts */ public float getVddHigh() { return (this.mvHigh / 1000.f); } /** * Returns low Vdd value in Schmoo plot, in Volts * * @return Low Vdd value in Schmoo plot, in Volts */ public float getVddLow() { return (this.mvLow / 1000.f); } /** * Returns Vdd step size in Schmoo plot, in Volts * * @return Vdd step size in Schmoo plot, in Volts */ public float getVddStep() { return (this.mvStep / 1000.f); } /** * Set range of JTAG TCK frequencies covered by Schmoo plot * * @param khzLow * Low frequency in Schmoo plot, in kHz * @param khzHigh * High frequency in Schmoo plot, in kHz * @param khzStep * Frequency step size in Schmoo plot, in kHz */ public void setKhzRange(int khzLow, int khzHigh, int khzStep) { this.khzLow = khzLow; this.khzHigh = khzHigh; this.khzStep = khzStep; } /** * Sets number of iterations used in testing a scan chain * * @param numTests * Number of iterations used in testing a scan chain * @see #testOneChain * @see #testAllChains */ public void setNumTests(int numTests) { this.numTests = numTests; } /** * Set Vdd range for Schmoo plot (values only kept to nearest 0.001 V) * * @param vddLow * Low Vdd value in Schmoo plot, in Volts * @param vddHigh * High Vdd value in Schmoo plot, in Volts * @param vddStep * Vdd step size in Schmoo plot, in Volts */ public void setVddRange(float vddLow, float vddHigh, float vddStep) { this.mvLow = Math.round(vddLow * 1000.f); this.mvHigh = Math.round(vddHigh * 1000.f); this.mvStep = Math.round(vddStep * 1000.f); } /** * Returns an bit vector with random true/false values * * @param numBits * Number of bits to return * @return random bit vector */ public static BitVector getRandomBits(int numBits) { BitVector bits = new BitVector(numBits, "getRandomBits()-bits"); for (int ind = 0; ind < numBits; ind++) { if (rand.nextBoolean()) { bits.set(ind); } else { bits.clear(ind); } } return bits; } //end getRandomBits /* Round mV value to nearest 100 mV */ private static int roundMillivolts(float mV) { return (int) Math.round(mV / Infrastructure.DEFAULT_MV_STEP) * Infrastructure.DEFAULT_MV_STEP; } /* Round kHz value to nearest 1000 kHz */ private static int roundKHz(float kHz) { return (int) Math.round(kHz / DEFAULT_KHZ_STEP) * DEFAULT_KHZ_STEP; } } //end class