/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: BypassJtagTester.java
* Written by Jonathan Gainsley, Sun Microsystems.
*
* Copyright (c) 2005 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.util.List;
import java.util.ArrayList;
/**
* Parent class for simulation jtag testers.
* The BypassJtagTester implements a direct read/write of scan chains,
* taking advantage of the simulator's ability to directly set and get
* node values within the circuit. This bypasses the costly act of
* shifting all the scan chain bits in serially. Instead, it writes in
* parallel at once. This mode is enabled by
* {@link SimulationModel#setBypassScanning(boolean)}
* <P>
* A further optimization is to only read from elements whose state is unknown,
* and only write to elements whose state is unknown or changing. This
* reduces the number of direct reads and writes when few bits
* change between shifts. Only scan chain elements with shadow registers,
* or dual ported shadow registers, can take advantage of this functionality.
* This option is enabled by
* {@link SimulationModel#setOptimizedDirectReadsWrites(boolean)}
*/
public abstract class BypassJtagTester extends JtagTester {
protected final SimulationModel model;
protected float tapVolt; // ignored at this time
protected double delay; // delay in ns
private static final boolean DEBUG = false;
BypassJtagTester(SimulationModel nm) {
this.model = nm;
}
void configure(float tapVolt, long kiloHerz) {
this.tapVolt = tapVolt;
this.delay = (1.0/kiloHerz * 1e6) / 2;
}
void disconnect() {}
void setLogicOutput(int index, boolean newLevel) {
System.out.println("Nanosim JtagTester does not support 'setLogicOutput("+index+", "+newLevel+"). Use LogicSettable instead.");
}
public boolean isBypassScanning() {
return model.isBypassScanning();
}
// ==========================================================
protected void doBypassScanning(ChainNode chain, boolean readEnable, boolean writeEnable) {
// this mode bypasses scanning, and reads/writes directly
// to the bits the scan chain controls, as long as those
// nets have been defined in the XML file.
if (DEBUG) System.out.println("Scanning in "+chain.getName()+" in bypass-scanning mode");
if (readEnable) {
chain.getOutBits().put(0, readDirect(chain));
} else
chain.getOutBits().putIndiscriminate(0, chain.getOutBitsExpected());
if (writeEnable) {
BitVector bitsToCheck = writeDirect(chain);
checkDataNets(chain, 0, bitsToCheck);
checkDataNets(chain, 1, bitsToCheck);
}
}
/**
* Get a list of DataNets from the chain.
* @param chain the chain to read
* @param set which set to get. Currently only two supported, so only 0 or 1
* @return a list of DataNets of the (hierarchical) data out net names.
* May contain null for undefined nets
*/
protected static List getDataNets(SubchainNode chain, int set) {
// get the scan chain data nets node corresponding to the chain
MyTreeNode system = chain.getParent().getParent(); // this is the system node
MyTreeNode scanchainnets = MyTreeNode.getNode(system, XMLIO.SCAN_CHAIN_DATA_NETS);
if (scanchainnets == null) return getDataNetsOld(chain, set); // must be using old version
SubchainNode datachain = (SubchainNode)MyTreeNode.getNode(scanchainnets, chain.getName());
if (datachain == null) {
// if more than one jtag contoller, datanet chain name is prepended with chip name
MyTreeNode chip = chain.getParent();
datachain = (SubchainNode)MyTreeNode.getNode(scanchainnets, chip.getName()+"_"+chain.getName());
}
if (datachain == null) return getDataNetsOld(chain, set);
List datanets = new ArrayList();
for (int i=0; i<datachain.getChildCount(); i++) {
SubchainNode subnode = (SubchainNode)datachain.getChildAt(i);
if (set == 0) {
datanets.add(subnode.getDataNet());
} else {
datanets.add(subnode.getDataNet2());
}
}
return datanets;
}
/**
* Get a list of DataNets from the chain.
* @param chain the chain to read
* @param set which set to get. Currently only two supported, so only 0 or 1
* @return a list of DataNets of the (hierarchical) data out net names.
* May contain null for undefined nets
* @deprecated this was used when the xml file contains data nets specified hierarchically
* along with the scan chain bits. I have since split the data nets out into a separate,
* flat listing.
*/
protected static List getDataNetsOld(SubchainNode chain, int set) {
// get names of data out bits
if (chain.getChildCount() == 0) {
List list = new ArrayList();
SubchainNode.DataNet dataNet;
if (set == 0) dataNet = chain.getDataNet();
else dataNet = chain.getDataNet2();
if (dataNet == null || dataNet.getName().equals("")) {
// pad list with correct number of null entries
for (int i=0; i<chain.getLength(); i++)
list.add(null);
return list;
}
MyTreeNode [] hier = chain.getParent().getHierarchy();
StringBuffer newPath = new StringBuffer();
for (int j=0; j<hier.length; j++) {
// remove the chip name and the chain name (3), as they are
// not part of the spice hierarchy.
if (j<3) continue;
newPath.append("X"+hier[j].getName()+".");
}
//newPath.append("X"+chain.getName()+".");
// special case: if Fake Chain, do not use the name in the
// hierarchical path to the dataNet. This is because schematics
// that do not have scanChainElements must refer to data nets
// in the schematic, which are at a level above where a scanChain.dataNet
// net would be
boolean fakeChain = false;
if (chain.getParentChain().getOpcode().equals("fakeChain")) {
// this is a fake chain
fakeChain = true;
}
Name netName = Name.findName(dataNet.getName());
if (fakeChain) {
for (int j=0; j<netName.busWidth(); j++) {
String net = netName.subname(j).toString();
// create hierarchical spice net name
SubchainNode.DataNet singleNet = new SubchainNode.DataNet(newPath.toString() + net,
dataNet.isReadable(), dataNet.isWriteable(), dataNet.isInverted());
list.add(singleNet);
//System.out.println("DataNet added: "+singleNet);
}
} else {
// chain may be bussed, bussed name should match length of chain
Name dataName = Name.findName(chain.getName());
for (int i=0; i<dataName.busWidth(); i++) {
netName = Name.findName(dataNet.getName());
for (int j=0; j<netName.busWidth(); j++) {
String net = "x" + dataName.subname(i).toString() + "." + netName.subname(j).toString();
// create hierarchical spice net name
SubchainNode.DataNet singleNet = new SubchainNode.DataNet(newPath.toString() + net,
dataNet.isReadable(), dataNet.isWriteable(), dataNet.isInverted());
list.add(singleNet);
//System.out.println("DataNet added: "+singleNet);
}
}
}
if (list.size() != chain.getLength()) {
System.out.println("Error: data net list of size "+list.size()+" does not match length of chain "+chain.getName()+" of length "+chain.getLength());
list.clear();
for (int i=0; i<chain.getLength(); i++)
list.add(null);
return list;
}
return list;
} else {
// this just contains more sub nodes
List list = new ArrayList();
for (int i=0; i<chain.getChildCount(); i++) {
SubchainNode subnode = (SubchainNode)chain.getChildAt(i);
list.addAll(getDataNets(subnode, set));
}
return list;
}
}
/**
* Check that the bits in the chain have been applied to the dataNets for the
* scan chain. This only applies for scan bits that have their "dataNet" or
* "dataNet2" property in the XML file.
* @param chain the scan chain
* @param set which set of nets to check. Only 0 and 1 currently.
* @param bitsToCheck a map of which bits to check
* @return true if a discrepancy found, false otherwise.
*/
protected boolean checkDataNets(ChainNode chain, int set, BitVector bitsToCheck) {
boolean foundDiscrepancy = false;
List dataNets = getDataNets(chain, set);
if (bitsToCheck.getNumBits() != dataNets.size()) {
System.out.println("Can't check dataNets, bitsToCheck size does not match chain length");
return false;
}
for (int i=0; i<dataNets.size(); i++) {
SubchainNode.DataNet dataNet = (SubchainNode.DataNet)dataNets.get(i);
//System.out.println(i+":\t"+netName);
if (dataNet == null) continue; // undefined net
if (dataNet.isWriteable() && bitsToCheck.get(i)) {
// check that data in inbits was written
int simState = model.getNodeState(formatDataNetName(dataNet.getName()));
int setState = chain.getInBits().get(i) ? 1 : 0;
if (dataNet.isInverted())
setState = (setState==1 ? 0 : 1);
if (simState != setState) {
System.out.println("Error! Attempted to set bit '"+formatDataNetName(dataNet.getName())+"' to "+setState+
" via the scan chain at time "+model.getSimulationTime()+", but its state is "+simState);
foundDiscrepancy = true;
} else {
if (DEBUG) {
System.out.println("Checked "+formatDataNetName(dataNet.getName())+" read "+simState+": ok");
}
}
}
}
return foundDiscrepancy;
}
/**
* Read directly from the data bits the scan chain controls, rather than
* applying "read" and scanning out the data. This is much faster than
* scanning them out.
* @param chain the scan chain
* @return a BitVector of the bits read directly out
*/
protected BitVector readDirect(ChainNode chain) {
List dataNets = getDataNets(chain, 0);
List dataNets2 = getDataNets(chain, 1);
BitVector outBits = new BitVector(chain.getOutBits().getNumBits(), "outBits");
int bitsRead = 0;
int numOptimizedReads = 0;
for (int i=0; i<outBits.getNumBits(); i++) {
SubchainNode node = chain.findNodeAtIndex(i);
if (!node.isReadable()) continue;
if (model.getOptimizedDirectReadsWrites()) {
if (node.usesShadow() && !node.usesDualPortedShadow()) {
// no need to read, just use last known good value
if (chain.getShadowState().isValid(i)) {
outBits.set(i, chain.getShadowState().get(i));
numOptimizedReads++;
continue;
}
}
}
SubchainNode.DataNet dataNet = (SubchainNode.DataNet)dataNets.get(i);
SubchainNode.DataNet dataNet2 = (SubchainNode.DataNet)dataNets2.get(i);
int state = readDirect(dataNet);
int state2 = readDirect(dataNet2);
if (state >=0 && state2 >= 0) {
// both were read and are valid, check that they match
if (state != state2) {
System.out.println("Error! Inconsistency reading directly from scan chain data bit "+i+
" of chain '"+chain.getName()+"', "+formatDataNetName(dataNet.getName())+" is "+state+" and "+
dataNet2.getName()+" is "+state2);
}
}
if (state < 0) {
state = state2; // state holds any valid state
}
if (state < 0) {
// nothing valid read, set it to whatever was shifted in
if (chain.getInBits().isValid(i))
state = chain.getInBits().get(i) ? 1 : 0;
if (state == -2) bitsRead++; // bit was read, just in undefined state
} else {
bitsRead++;
}
outBits.set(i, state==1);
}
String optReadsInfo = "";
if (numOptimizedReads > 0) {
optReadsInfo = " "+numOptimizedReads+" optimized reads.";
}
if (printInfo)
System.out.println("Info: Read directly "+bitsRead+" bits from chain '"+chain.getName()+
"' of length "+chain.getOutBits().getNumBits()+
" bits (others unchanged)."+optReadsInfo);
return outBits;
}
/**
* Read directly from a scan chain data output
* @param dataNet the data net to read from
* @return the value read. 0 or 1 valid values, -1 on error
*/
private int readDirect(SubchainNode.DataNet dataNet) {
if (dataNet == null) return -1;
if (!dataNet.isReadable()) return -1;
int state = model.getNodeState(formatDataNetName(dataNet.getName()));
if (DEBUG) System.out.println("Read directly "+(dataNet.isInverted()?"(inverted)":"")+" from net "+formatDataNetName(dataNet.getName())+": "+state);
if (state == -2) System.out.println("Warning, read intermediate (undefined) voltage state from net "+formatDataNetName(dataNet.getName())+" at time "+model.getSimulationTime());
if (state < 0) return state;
if (dataNet.isInverted()) state = state==1 ? 0 : 1;
return state;
}
/**
* Write scan chain data directly to the data bits, rather than
* scanning them in and then applying "write". This should be much
* faster than scanning them in.
* @param chain the scan chain
* @return which bits were written. True values mean the bit in that position was written.
*/
protected BitVector writeDirect(ChainNode chain) {
List dataNets = getDataNets(chain, 0);
List dataNets2 = getDataNets(chain, 1);
int dataNetsWritten = 0;
int dataNet2sWritten = 0;
int numOptimizedWrites = 0;
List writtenDataNets = new ArrayList();
BitVector bitsWritten = new BitVector(chain.getInBits().getNumBits(), "bitsWritten");
for (int i=0; i<chain.getInBits().getNumBits(); i++) {
SubchainNode node = chain.findNodeAtIndex(i);
bitsWritten.set(i, false);
if (!node.isWriteable()) continue;
if (model.getOptimizedDirectReadsWrites()) {
if (node.usesShadow() || node.usesDualPortedShadow()) {
// shadow registers can only be modified by the scan chain (or master clear)
// If the shadow state is valid, and is the same value as we want to write,
// we can just skip this write.
if (chain.getShadowState().isValid(i) && chain.getShadowState().get(i) == chain.getInBits().get(i)) {
numOptimizedWrites++;
continue;
}
}
}
if (chain.getInBits().isValid(i)) {
int state = chain.getInBits().get(i) ? 1 : 0;
SubchainNode.DataNet dataNet = (SubchainNode.DataNet)dataNets.get(i);
SubchainNode.DataNet dataNet2 = (SubchainNode.DataNet)dataNets2.get(i);
if (writeDirect(dataNet, state)) {
writtenDataNets.add(dataNet);
bitsWritten.set(i, true);
dataNetsWritten++;
}
if (writeDirect(dataNet2, state)) {
writtenDataNets.add(dataNet);
bitsWritten.set(i, true);
dataNet2sWritten++;
}
} else {
System.out.println("Could not write bit "+i+" of chain "+chain.getName()+" because it is not in a valid state");
}
// Code in ChainNode maintains shadowState
//if (node.usesShadow() || node.usesDualPortedShadow())
// chain.shadowState.set(i, state==1);
}
// apply write (above writes)
model.waitNS(delay*3);
// release writes, unless it is a fake chain control
if (!chain.getOpcode().equals("fakeChain")) {
model.releaseNodes(getNames(writtenDataNets));
}
String optWritesInfo = "";
if (numOptimizedWrites > 0) {
optWritesInfo = " "+numOptimizedWrites+" optimized writes.";
}
model.waitNS(delay*1);
if (printInfo)
System.out.println("Info: Wrote directly "+dataNetsWritten+" bits and "+
dataNet2sWritten+" secondary bits from scan chain '"+chain.getName()+ "' of length "+
chain.getInBits().getNumBits()+" bits."+optWritesInfo);
return bitsWritten;
}
/**
* Write directly to a scan chain data output
* @param dataNet the data net to write to
* @param state the state to write (0 or 1)
* @return true if written, false if not
*/
private boolean writeDirect(SubchainNode.DataNet dataNet, int state) {
if (dataNet == null) return false;
if (!dataNet.isWriteable()) return false;
if (dataNet.isInverted()) state = (state==1) ? 0 : 1;
model.setNodeState(formatDataNetName(dataNet.getName()), state);
if (DEBUG) System.out.println("Wrote directly "+state+" to net "+formatDataNetName(dataNet.getName()));
return true;
}
/**
* Perform any formatting of the data net name specific to
* the simulator, such as replaced unallowed characters.
* @param dataNetName the dataNetName from the XML file,
* contains 'x' in front of each instance, delimited by '.',
* all spice characters allowed
* @return a fitlered dataNetName
*/
String formatDataNetName(String dataNetName) {
return dataNetName;
}
private List getNames(List dataNets) {
List names = new ArrayList();
for (int i=0; i<dataNets.size(); i++) {
SubchainNode.DataNet dataNet = (SubchainNode.DataNet)dataNets.get(i);
if (dataNet == null) continue;
names.add(formatDataNetName(dataNet.getName()));
}
return names;
}
}