/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: SCTiming.java
* Written by Jonathan Gainsley, Sun Microsystems.
*
* Copyright (c) 2009 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.sctiming;
import com.sun.electric.tool.io.input.spicenetlist.SpiceNetlistReader;
import com.sun.electric.tool.io.input.spicenetlist.SpiceSubckt;
import com.sun.electric.tool.user.MessagesStream;
import com.sun.electric.tool.user.Exec;
import com.sun.electric.database.text.Version;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.network.Netlist;
import java.util.*;
import java.io.*;
/**
* Runs a characterization of a gate.
* User: gainsley
* Date: Oct 30, 2006
*/
public class SCTiming {
/**
* This holds the param names to be swept, and their values.
* Typically this is the input drive strength, output load strength,
* and clock buffer strength.
*/
private static class SweepParam {
String param;
String[] sweep;
SweepParam(String param, String sweep) {
this.param = param;
this.sweep = sweep.trim().split("\\s+");
}
}
private String inputFile = null;
private Cell topCell = null;
public String topCellName = null;
private String topCellNameLiberty = null;
public String outputDir = ".";
private String topCellParams = "";
public SCSettings settings;
// parameter names that will be swept or measured
private String inbufStr = "inbufStr";
private String outloadStr = "outloadStr";
private String clkbufStr = "clkbufStr";
private String setupTimeName = "tmsetup";
private String setupTimeSweep = "tmsetupsweep";
private String holdTimeSweep = "tmholdsweep";
private String holdTimeName = "tmhold";
private String clk2q = "clk2q";
private String setupclk2q = "setupclk2q";
private SpiceSubckt dutSubckt;
private SpiceSubckt bufferSubckt;
private SpiceSubckt clkbufSubckt;
private SpiceSubckt loadSubckt;
private List<Arc> timingArcs;
private List<SweepParam> sweeps;
private Map<String,String> combinationalFunctions;
private FlipFlopFunction functionFlipFlop = null;
private FlipFlopFunctionSDRtoDDR functionFlipFlopSDRtoDDR = null;
private FlipFlopFunctionDDRtoSDR functionFlipFlopDDRtoSDR = null;
private LatchFunction functionLatch = null;
private TestCell testCell = null;
private PrintWriter out;
private PrintStream msg = System.out;
private SpiceNetlistReader netlistReader = null;
private boolean interfaceTiming = false;
private List<String> ignorableSubckts;
private boolean verbose = true;
private boolean printStatistics = true;
public boolean characterizationFailed = false;
private boolean useAutoStop = true;
private boolean noTiming = false;
private static final String lineComment = "*****************************************************";
/**
* Create a new SCTiming object to run timing characterizations.
* New Arcs need to be created and specified, and then added
* to the SCTiming object. SCTiming.characterize() is then
* called to run all the timing arcs and write the resulting
* data to a Liberty file.
*/
public SCTiming() {
timingArcs = new ArrayList<Arc>();
sweeps = new ArrayList<SweepParam>();
combinationalFunctions = new HashMap<String, String>();
ignorableSubckts = new ArrayList<String>();
}
/* *******************************************************
* Settings
* *******************************************************/
/**
* Set the settings for this characterization
* @param settings
*/
public void setSettings(SCSettings settings) { this.settings = settings; }
/**
* Set the input spice netlist file. This file
* should contain the top cell subckt, the input buffer
* subckt, and the output load subckt. It should also contain
* the clock buffer subkct if running a sequential test.
* @param inputFile the input file path
*/
public void setInputFile(String inputFile) { this.inputFile = inputFile; }
/**
* Set the top cell. This is the cell that
* will be characterized. All pins defined on arcs should be pins
* on this cell.
* @param topCell
*/
public void setTopCell(Cell topCell) { this.topCell = topCell; }
/**
* Set the top cell name. This is the name of the subckt that
* will be characterized. All pins defined on arcs should be pins
* on this cell.
* @param topCellName
*/
public void setTopCellName(String topCellName) { this.topCellName = topCellName; }
/**
* Set the top cell name for the Liberty file.
* @param topCellName
*/
public void setTopCellNameLiberty(String topCellName) { this.topCellNameLiberty = topCellName; }
/**
* A string of name=value parameter values for the top cell instance,
* if needed.
* @param list space separated list of name=value pairs
*/
public void setTopCellParams(String list) { this.topCellParams = list; }
/**
* Set the output directory. All files will be written to this directory.
* @param dir
*/
public void setOutputDir(String dir) { this.outputDir = dir; }
/**
* Add a timing arc to be characterized
* @param arc
*/
public void addTimingArc(Arc arc) { timingArcs.add(arc); }
/**
* Set the function for an output pin in terms of other pins.
* For example: A & B, A + B, !A
* @param outputPin the output pin name
* @param function a string describing the function.
*/
public void setFunctionCombinational(String outputPin, String function) {
combinationalFunctions.put(outputPin, function);
}
/**
* Set the function as a simple flip flop. Note this model does not support DDR flops.
* @param outputPosPin output positive (true) pin
* @param outputNegPin output negative (false) pin
* @param inputPin input data pin
* @param clockedOnPin clock pin
*/
public void setFunctionFlipFlop(String outputPosPin, String outputNegPin, String inputPin, String clockedOnPin) {
functionFlipFlop = new FlipFlopFunction(outputPosPin, outputNegPin,
inputPin, clockedOnPin, false);
combinationalFunctions.put(outputPosPin, "i"+outputPosPin);
}
/**
* Set the function as a simple DDR flip flop.
* @param outputPosPin output positive (true - Q) pin (this output changes on both pos and neg edge clk)
* @param outputNegPin output negative (false - Q bar) pin (this output changes on both pos and neg edge clk)
* @param inputPin input data pin
* @param clockedOnPin clock pin
*/
public void setFunctionFlipFlopDDR(String outputPosPin, String outputNegPin, String inputPin, String clockedOnPin) {
functionFlipFlop = new FlipFlopFunction(outputPosPin, outputNegPin,
inputPin, clockedOnPin, true);
combinationalFunctions.put(outputPosPin, "i"+outputPosPin);
}
/**
* Set the function as a simple SDR to DDR flop
* @param outputPosPin output positive (Q) pin
* @param outputNegPin output negative (Q-bar) pin
* @param inputRise input clocked on the rising edge of the clk
* @param inputFall input clocked on the falling edge of the clk
* @param clockedOnPin the clock pin
*/
public void setFunctionFlipFlopSDRtoDDR(String outputPosPin, String outputNegPin, String inputRise, String inputFall, String clockedOnPin) {
functionFlipFlopSDRtoDDR = new FlipFlopFunctionSDRtoDDR(outputPosPin, outputNegPin, inputRise, inputFall, clockedOnPin);
combinationalFunctions.put(outputPosPin, "i"+outputPosPin);
}
/**
* Set the function as a simple DDR to SDR flop
* @param outputRise the output that changes on clock rising
* @param outputFall the output that changes on clock falling
* @param inputPin the input pin
* @param clockedOnPin the clock pin
*/
public void setFunctionFlipFlopDDRtoSDR(String outputRise, String outputFall, String inputPin, String clockedOnPin) {
functionFlipFlopDDRtoSDR = new FlipFlopFunctionDDRtoSDR(outputRise, outputFall, inputPin, clockedOnPin);
combinationalFunctions.put(outputRise, "i"+outputRise);
combinationalFunctions.put(outputFall, "i"+outputFall);
}
/**
* Set the function as a latch.
* @param outputPosPin output positive (true) pin
* @param outputNegPin output negative (false) pin
* @param inputPin input data pin
* @param enablePin enables in to out
*/
public void setFunctionLatch(String outputPosPin, String outputNegPin, String inputPin, String enablePin) {
functionLatch = new LatchFunction(outputPosPin, outputNegPin, inputPin, enablePin);
combinationalFunctions.put(outputPosPin, "i"+outputPosPin);
}
/**
* Set the function of the cell as an abstracted macro model.
* See documentation for interface timing. This allows modelling of
* complex sequential macro blocks.
*/
public void setFunctionInterfaceTiming() {
interfaceTiming = true;
}
/**
* Set the test cell block for blocks with scan
* @param scanInPin scan in pin
* @param scanOutPin scan out pin
* @param scanEnPin scan en pin
*/
public void setTestCell(String scanInPin, String scanOutPin, String scanEnPin) {
testCell = new TestCell(scanInPin, scanOutPin, scanEnPin);
}
/**
* Tells spice to use .option autostop, which stops a transient simulation once (all?)
* .measurement statements have completed.
* May not work well if it doesn't wait for all, or in cases of optimizations
* @param t true to set autostop on, false to not set it.
*/
public void setAutoStop(boolean t) {
useAutoStop = t;
}
/**
* Add an ignorable subckt. The characterization code checks that all subckts are
* defined, but sometimes spice models use subckts for transistors, which are
* defined in the model file. This suppresses the error for subckts that are not
* defined.
* @param subcktName the name of the subckt
*/
public void addIgnorableSubckt(String subcktName) {
ignorableSubckts.add(subcktName);
}
/**
* For debug only, turn off timing runs
*/
public void setNoTimingMode() {
noTiming = true;
}
/* *******************************************************
* Characterization
* *******************************************************/
/**
* Run characterization on all timing arcs added to this SCTiming object,
* and write the resulting data to a Liberty file.
* @return true on success, false on error
*/
public boolean characterize(SCRunBase.DelayType delayType) {
try {
characterize_(delayType);
} catch (SCTimingException e) {
System.out.println("SCTiming: Exception: "+e.getMessage());
characterizationFailed = true;
return false;
}
return true;
}
public void characterize_(SCRunBase.DelayType delayType) throws SCTimingException {
// redirect standard output
MessagesStream.getMessagesStream().save((new File(outputDir, "SCTiming.log")).getPath());
err(inputFile == null, "Input spice file not specified");
err(topCellName == null, "Top (Test) cell not specified");
err(timingArcs.size() == 0, "No timing arcs specified");
boolean sequentialTest = false;
for (Arc a : timingArcs) {
if (a.clk != null) sequentialTest = true;
}
settings.checkSettings(sequentialTest);
msg.println("--------------------------------------------------------");
msg.println("Characterization:");
msg.println(" Cell \""+topCellName+"\"");
msg.println(" "+new Date(System.currentTimeMillis()));
msg.println();
msg.println("--------------------------------------------------------");
msg.println("Reading spice netlist '"+inputFile+"'...");
netlistReader = new SpiceNetlistReader();
try {
netlistReader.readFile(inputFile, false);
} catch (java.io.FileNotFoundException e) {
throw new SCTimingException(e.getMessage());
}
msg.println();
msg.println("Spice netlist read complete. Checking netlist...");
// check for the device to be characterized (device under test)
dutSubckt = netlistReader.getSubckt(topCellName);
err(dutSubckt == null, "Test Cell "+topCellName+" not found in spice netlist");
// check for the buffer subckt
bufferSubckt = netlistReader.getSubckt(settings.bufferCell);
err(bufferSubckt == null, "Buffer cell "+settings.bufferCell+" not found in spice netlist");
err(bufferSubckt.getParamValue(settings.bufferCellStrengthParam) == null,
"Strength param "+settings.bufferCellStrengthParam+" not found in buffer cell "+settings.bufferCell);
err(!bufferSubckt.hasPort(settings.bufferCellInputPort),
"Input port "+settings.bufferCellInputPort+" not found in buffer cell "+settings.bufferCell);
err(!bufferSubckt.hasPort(settings.bufferCellOutputPort),
"Output port "+settings.bufferCellOutputPort+" not found in buffer cell "+settings.bufferCell);
// check for the load subckt
loadSubckt = netlistReader.getSubckt(settings.loadCell);
err(loadSubckt == null, "Load cell "+settings.loadCell+" not found in spice netlist");
err(loadSubckt.getParamValue(settings.loadCellStrengthParam) == null,
"Strength param "+settings.loadCellStrengthParam+" not found in load cell "+settings.loadCell);
err(!loadSubckt.hasPort(settings.loadCellPort),
"Load port "+settings.loadCellPort+" not found in load cell "+settings.loadCell);
// if this is a sequential test, the clock buffer subckt must be present
clkbufSubckt = null;
if (sequentialTest) {
clkbufSubckt = netlistReader.getSubckt(settings.clkBufferCell);
err(clkbufSubckt == null, "Clock buffer cell"+settings.clkBufferCell+" not found in spice netlist");
err(clkbufSubckt.getParamValue(settings.clkBufferCellStrengthParam) == null,
"Strength param "+settings.clkBufferCellStrengthParam+" not found in clock buffer cell "+settings.clkBufferCell);
err(!clkbufSubckt.hasPort(settings.clkBufferCellInputPort),
"Input port "+settings.clkBufferCellInputPort+" not found in buffer cell "+settings.clkBufferCell);
err(!clkbufSubckt.hasPort(settings.clkBufferCellOutputPort),
"Output port "+settings.clkBufferCellOutputPort+" not found in buffer cell "+settings.clkBufferCell);
}
msg.println(" Netlist OK");
// verify ports
for (Arc arc : timingArcs) {
verifyPorts(topCell, dutSubckt, arc, netlistReader.getGlobalNets());
}
if (noTiming) return;
// Run timing for each arc
for (Arc arc : timingArcs) {
if (arc.clk == null) {
runCombinational(arc);
} else {
if (settings.simpleSequentialCharacterization)
runSequentialSimple(arc, delayType);
else
runSequential(arc);
}
}
}
/**
* Write a common header to the spice file
* @param out the spice file output writer
*/
private void writeHeader(PrintWriter out) {
out.println(lineComment);
out.println("* Date: "+new Date(System.currentTimeMillis()));
out.println("* Written by Electric "+ Version.getVersion());
out.println(lineComment);
out.println();
writeCommentHeader("Spice Netlist");
out.println(".include '"+inputFile+"'");
out.println();
writeCommentHeader("Options");
out.println("* VDD = " + settings.vdd);
out.println("* temp = " + settings.temp);
out.println("* Input ramp time (ps) = "+settings.inputRampTimePS);
out.println("* Sim resolution time (ps) = "+settings.simResolutionPS);
out.println("* Sim duration (ps) = "+settings.simTimePS);
out.println("* input low threshold (%) = "+settings.inputLow);
out.println("* input high threshold (%) = "+settings.inputHigh);
out.println("* input delay threshold (%) = "+settings.inputDelayThresh);
out.println("* output low threshold (%) = "+settings.outputLow);
out.println("* output high threshold (%) = "+settings.outputHigh);
out.println("* output delay threshold (%) = "+settings.outputDelayThresh);
out.println("* edge cap measure start (%) = "+settings.edgePercentForCapStart);
out.println("* edge cap measure end (%) = "+settings.edgePercentForCapEnd);
out.println("* setup time range min (ps) = "+settings.tmsetupMinGuessPS);
out.println("* setup time range guess (ps) = "+settings.tmsetupGuessPS);
out.println("* setup time range max (ps) = "+settings.tmsetupMaxGuessPS);
out.println(lineComment);
out.println();
writeCommentHeader("Option statements");
out.println(".option post");
out.println(".option optlst=1");
if (useAutoStop) out.println(".option autostop");
out.println();
writeCommentHeader("Power Supplies");
out.println(".global vdd gnd");
out.println(".param vsupply="+settings.vdd);
//out.println("Vdd vdd gnd vsupply");
out.println(".temp "+settings.temp);
out.println();
}
/**
* Run a combinational timing arc. This generates a spice deck,
* runs spice, reads the resulting measure statements, and
* saves the data for writing to the Liberty file.
* @param arc the timing arc to run
* @throws SCTimingException on error
*/
private void runCombinational(Arc arc) throws SCTimingException {
String arcDesc = arc.toString();
String outputFileName = topCellName + "_delay_"+arcDesc;
File outputFile = new File(outputDir, outputFileName+".sp");
msg.println();
msg.println("--------------------------------------------------------");
msg.println("Characterizing timing arc \""+arcDesc+"\":");
msg.println();
msg.println("Writing spice netlist to");
msg.println(" '"+outputFile.getPath()+"'...");
try {
out = new PrintWriter(new FileOutputStream(outputFile));
} catch (java.io.FileNotFoundException e) {
throw new SCTimingException(e.getMessage());
}
out.println("* "+arc.toString()+" *");
writeHeader(out);
out.println(lineComment);
out.println("* Delay Arc");
out.print("* "); out.println(arcDesc);
out.println(lineComment);
out.println();
writeCommentHeader("Parameters");
// these parameters are swept during transient sim, so it does not
// matter what they are set to here
out.println(".param "+inbufStr+"=1");
out.println(".param "+outloadStr+"=1");
out.println(".param "+clkbufStr+"=1");
out.println();
writeCommentHeader("Test Bench");
// write stable voltage source(s)
for (PinEdge in : arc.stableInputs) {
writeVoltSource(in);
}
// write input voltage source
writeVoltSource(arc.input);
// write buffer
writeBuffer(arc.input, bufferSubckt);
// write voltage sources to measure current (for capacitance test)
out.println("Vcurrent_in "+arc.input.pin+" "+arc.input.pin+"_i 0");
out.println("Vcurrent_out "+arc.output.pin+" "+arc.output.pin+"_i 0");
// instantiate the test cell
out.print("Xdut ");
for (String port : dutSubckt.getPorts()) {
if (port.equalsIgnoreCase(arc.input.pin) || port.equalsIgnoreCase(arc.output.pin))
out.print(port+"_i ");
else
out.print(port+" ");
}
out.print(topCellName);
out.println(" "+topCellParams);
// write load
writeLoad(arc.output, loadSubckt);
writeIC(arc.output);
out.println();
sweeps.clear();
String outputLoads = settings.loadCellSweep;
String inputBuffers = settings.bufferCellSweep;
if (arc.getOutputLoadSweep() != null) outputLoads = arc.getOutputLoadSweep();
if (arc.getInputBufferSweep() != null) inputBuffers = arc.getInputBufferSweep();
sweeps.add(new SweepParam(inbufStr, inputBuffers));
sweeps.add(new SweepParam(outloadStr, outputLoads));
// simulation
writeCommentHeader("Transient statement");
out.println(".tran "+settings.simResolutionPS+"ps "+settings.simTimePS+"ps SWEEP DATA = DATA_TIM");
out.println();
// write all the parameter values to be swept
writeSweepData();
out.println();
writeCommentHeader("Measure statements");
String inputSlew = arc.input.pin+"_slew";
String propDelay = "prop_delay";
String outputSlew = arc.output.pin+"_slew";
String inputCap = arc.input.pin+"_cap";
String outputCap = arc.output.pin+"_cap";
writeMeasDelay(propDelay, arc.input, arc.output);
writeMeasSlew(inputSlew, arc.input, settings.inputLow, settings.inputHigh);
writeMeasSlew(outputSlew, arc.output, settings.outputLow, settings.outputHigh);
writeMeasCap(inputCap, arc.input, "Vcurrent_in");
writeMeasCap(outputCap, arc.output, "Vcurrent_out");
out.println();
out.println(".END");
out.close();
msg.println(" Finished writing netlist.");
// run spice
runSpice(outputFileName, true);
// read output file
File mt0file = new File(outputDir, outputFileName+".mt0");
msg.println("Reading measurements file "+outputFileName+".mt0...");
TableData data = TableData.readSpiceMeasResults(mt0file.getPath());
arc.data = data;
arc.data2d_inbuf_outload = data.getTable2D(inbufStr.toLowerCase(), outloadStr.toLowerCase(), null, null);
if (verbose) {
msg.println();
msg.println(" Read data:");
data.printData();
if (arc.data2d_inbuf_outload != null)
arc.data2d_inbuf_outload.print();
}
if (printStatistics) {
msg.println();
msg.println("Statistical checks:");
msg.println("-------------------");
msg.println();
msg.println("Input slew should depend only on input buffer strength:");
printRowMeanStdDev(arc.data2d_inbuf_outload, arc.input.pin+"_slew", msg, 1e12, "ps");
msg.println();
msg.println("Output capacitance should depend only on output load size");
printColumnMeanStdDev(arc.data2d_inbuf_outload, arc.output.pin+"_cap", msg, 1e15, "fF");
msg.println();
msg.println("Input capacitance should not depend on either input buffer strength or output load:");
printMeanStdDev(arc.data2d_inbuf_outload, arc.input.pin+"_cap", msg, 1e15, "fF");
}
msg.println();
msg.println("Characterization of arc \""+arc+"\" complete.");
}
/**
* Run a sequential timing arc. This extracts setup and clock-to-Q times
* for the specified sweeps. Note that resulting data is stored
* on the Arc object.
* @param arc the timing arc to characterize
* @throws SCTimingException
*/
private void runSequential(Arc arc) throws SCTimingException {
String arcDesc = arc.toString();
// First, we get ideal clk to q delay wrt to clk slew
// This will provide a reference which will be used to evaluate setup times
msg.println();
msg.println("--------------------------------------------------------");
msg.println("Characterizing timing arc \""+arcDesc+"\":");
msg.println();
msg.println("Finding minimum setup, hold, and clk-to-Q time for \""+arcDesc+"\"");
String bufStrSweep = settings.bufferCellSweep;
String loadStrSweep = settings.loadCellSweep;
if (arc.getInputBufferSweep() != null) bufStrSweep = arc.getInputBufferSweep();
if (arc.getOutputLoadSweep() != null) loadStrSweep = arc.getOutputLoadSweep();
String [] bufStrs = bufStrSweep.trim().split("\\s+");
String [] loadStrs = loadStrSweep.trim().split("\\s+");
String [] clkStrs = settings.clkBufferCellSweep.trim().split("\\s+");
err(bufStrs.length == 0, "Buffer cell strength values empty");
err(loadStrs.length == 0, "Load cell strength values empty");
err(clkStrs.length == 0, "Clock buffer cell strength values empty");
String fileName = topCellName + "_setup_" + arcDesc;
String fileNameHold = topCellName + "_hold_" + arcDesc;
// max number of optimization iterations performed by spice
int maxSpiceIterations = 30;
// all result data will be stored in this table
TableData allData = null;
boolean runHold = true;
// We need data results for every combination
// not all results will vary wrt to each parameter
for (int c=0; c<clkStrs.length; c++) {
for (int l=0; l<loadStrs.length; l++) {
for (int b=0; b<bufStrs.length; b++) {
// Run a spice optimization to find the minimum setup time
// at which clk-to-Q can be measured. Clk-to-Q measurement
// cannot fail for the next spice optimization run.
TableData data = runSetupTimeSpiceTest(fileName, arc, bufStrs[b], clkStrs[c], loadStrs[l],
settings.tmsetupMinGuessPS, settings.tmsetupMaxGuessPS, settings.tmsetupGuessPS,
verbose, maxSpiceIterations, 0);
double tmsetupValidminPS = data.getValue(0, setupTimeSweep);
err(Double.isNaN(tmsetupValidminPS), "Error running spice simulation to get minimum setup time");
// scale back to ps
tmsetupValidminPS *= 1e12;
// add 1% to add some margin
tmsetupValidminPS = tmsetupValidminPS + Math.abs(0.1*tmsetupValidminPS);
if (verbose) {
msg.println();
msg.println("Bufstr="+bufStrs[b]+", Clkstr="+clkStrs[c]+", Loadstr="+loadStrs[l]);
msg.println("Using minimum setup time of "+tmsetupValidminPS+"ps");
msg.println();
}
// guess must be greater than or equal to min of range
double tmsetupPS = settings.tmsetupGuessPS;
if (tmsetupPS < tmsetupValidminPS) {
tmsetupPS = tmsetupValidminPS;
}
// Run a spice optimization test to find the minimum
// value of setup+clk2q. This value will determine
// our setup and clk2q values.
data = runSetupTimeSpiceTest(fileName+"_1", arc, bufStrs[b], clkStrs[c], loadStrs[l],
tmsetupValidminPS, settings.tmsetupMaxGuessPS, tmsetupPS,
verbose, maxSpiceIterations, 1);
if (verbose) {
msg.println();
data.printData();
msg.println();
}
if (data.getNumRows() == maxSpiceIterations) {
throw new SCTimingException("Max number of iterations of optimization in spice reached, result may not be valid");
}
double tmholdValidMaxPS = 1e-12;
if (runHold) {
// Run a spice optimization to find the maximum hold time
// at which clk-to-Q can be measured.
TableData dataHold = runHoldTimeSpiceTest(fileNameHold, arc, bufStrs[b], clkStrs[c], loadStrs[l],
settings.tmsetupMinGuessPS, settings.tmsetupMaxGuessPS, settings.tmsetupGuessPS,
verbose, maxSpiceIterations);
tmholdValidMaxPS = dataHold.getValue(0, holdTimeSweep);
err(Double.isNaN(tmholdValidMaxPS), "Error running spice simulation to get maximum hold time");
if (verbose) {
msg.println();
msg.println("Bufstr="+bufStrs[b]+", Clkstr="+clkStrs[c]+", Loadstr="+loadStrs[l]);
msg.println("Hold time is "+(tmholdValidMaxPS*1e12)+"ps");
msg.println();
}
}
TableData latchClk2Q = null;
if (functionLatch != null) {
latchClk2Q = runLatchClk2QSpiceTest(fileName+"_q", arc, bufStrs[b], clkStrs[c],
loadStrs[l], verbose);
}
// record results into allData object
if (allData == null) {
List<String> headers = new ArrayList<String>();
headers.add(inbufStr);
headers.add(clkbufStr);
headers.add(outloadStr);
headers.add(holdTimeName);
headers.addAll(data.getHeaders());
allData = new TableData(headers);
}
if (data.getNumRows() > 0) {
double [] datarow = data.getRow(data.getNumRows()-1);
double [] newrow = new double[datarow.length+4];
newrow[0] = Double.parseDouble(bufStrs[b]);
newrow[1] = Double.parseDouble(clkStrs[c]);
newrow[2] = Double.parseDouble(loadStrs[l]);
newrow[3] = tmholdValidMaxPS;
for (int i=4; i<newrow.length; i++) {
newrow[i] = datarow[i-4];
}
if (latchClk2Q != null) {
// replace clk-to-Q value
int cc = allData.getHeaders().indexOf("clk2q");
int cc2 = latchClk2Q.getHeaders().indexOf("clk2q");
newrow[cc] = latchClk2Q.getRow(latchClk2Q.getNumRows()-1)[cc2];
}
allData.addRow(newrow);
}
}
}
}
msg.println();
arc.data = allData;
Table2D data2d = allData.getTable2D(inbufStr.toLowerCase(), outloadStr.toLowerCase(), null, null);
Table2D data2d_clkbuf_outload = allData.getTable2D(clkbufStr.toLowerCase(), outloadStr.toLowerCase(),
inbufStr.toLowerCase(), settings.bufferCellSweepExcludeFromAveraging);
Table2D data2d_inbuf_clkbuf = allData.getTable2D(inbufStr.toLowerCase(), clkbufStr.toLowerCase(),
outloadStr.toLowerCase(), settings.loadCellSweepExcludeFromAveraging);
arc.data2d_inbuf_outload = data2d;
arc.data2d_clkbuf_outload = data2d_clkbuf_outload;
arc.data2d_inbuf_clkbuf = data2d_inbuf_clkbuf;
if (verbose) {
allData.printData();
System.out.println("----------------------------------------------------------------------------");
System.out.println("----------------------------------------------------------------------------");
System.out.println("-------------------- Input Slew vs Output Load Table2D ---------------------");
System.out.println("----------------------------------------------------------------------------");
System.out.println("----------------------------------------------------------------------------");
data2d.print();
System.out.println("----------------------------------------------------------------------------");
System.out.println("----------------------------------------------------------------------------");
System.out.println("-------------------- Clock Slew vs Output Load Table2D ---------------------");
System.out.println("----------------------------------------------------------------------------");
System.out.println("----------------------------------------------------------------------------");
data2d_clkbuf_outload.print();
System.out.println("----------------------------------------------------------------------------");
System.out.println("----------------------------------------------------------------------------");
System.out.println("-------------------- Input Slew vs Clk Slew Table2D ------------------------");
System.out.println("----------------------------------------------------------------------------");
System.out.println("----------------------------------------------------------------------------");
data2d_inbuf_clkbuf.print();
msg.println();
}
if (printStatistics) {
msg.println("Statistical checks:");
msg.println("-------------------");
msg.println();
msg.println("Clock-to-Q should depend only on clock buffer and output load size:");
//printColumnMeanStdDev(data2dClk, clk2q, msg, 1e12, "ps");
print2DMeanStdDev(data2d_clkbuf_outload, clk2q, msg, 1e12, "ps");
msg.println();
msg.println("Setup time should depend only on input buffer strength and clock buffer strength:");
//printRowMeanStdDev(data2dClk, setupTimeName, msg, 1e12, "ps");
print2DMeanStdDev(data2d_inbuf_clkbuf, setupTimeName, msg, 1e12, "ps");
msg.println();
msg.println("Input slew should depend only on input buffer strength:");
printRowMeanStdDev(data2d, arc.input.pin+"_slew", msg, 1e12, "ps");
msg.println();
msg.println("Output capacitance should depend only on output load size");
printColumnMeanStdDev(data2d, arc.output.pin+"_cap", msg, 1e15, "fF");
msg.println();
msg.println("Input capacitance should not be dependent on either input slew or output load");
printMeanStdDev(data2d, arc.input.pin+"_cap", msg, 1e15, "fF");
msg.println();
msg.println("Hold time should depend only on input buffer strength and clock buffer strength:");
//printRowMeanStdDev(data2dClk, holdTimeName, msg, 1e12, "ps");
print2DMeanStdDev(data2d_inbuf_clkbuf, holdTimeName, msg, 1e12, "ps");
msg.println();
}
msg.println();
msg.println("Characterization of arc \""+arc+"\" complete.");
}
/**
* This runs one of two spice optimizations. The first
* varies the setup time to find the minimum setup time at which
* the output is valid (i.e., a clock-to-Q measurement will not
* return "failed"). The second test varies the setup time to
* find the setup time which produces the minimum setup+clk2q time.
* The second test requires that the clock-to-Q measurement never fails,
* which is why the first optimization is run first.
* @param outputFileName the output spice file name (no extension)
* @param arc the arc to characterize
* @param inputStr the input buffer strength
* @param clkStr the clock buffer strength
* @param outputLoad output load strength
* @param tmsetupminPS min possible value of setup time
* @param tmsetupmaxPS max possible value of setup time
* @param tmsetupguessPS starting point of setup time
* @param verbose true to print messages
* @param maxSpiceIterations max number of spice iterations for the
* second optimization phase
* @param optphase 0 for finding min valid setup time, 1 for finding
* min of setup+clk2q
* @return the resulting measure data
* @throws SCTimingException
*/
private TableData runSetupTimeSpiceTest(String outputFileName, Arc arc,
String inputStr, String clkStr,
String outputLoad,
double tmsetupminPS, double tmsetupmaxPS, double tmsetupguessPS,
boolean verbose, int maxSpiceIterations,
int optphase) throws SCTimingException {
String arcDesc = arc.toString();
if (verbose) {
msg.println();
msg.println("Writing spice netlist to:");
msg.println(" '"+outputFileName+".sp'...");
msg.println(" in directory: "+outputDir);
}
try {
out = new PrintWriter(new FileOutputStream(new File(outputDir, outputFileName+".sp")));
} catch (java.io.FileNotFoundException e) {
throw new SCTimingException(e.getMessage());
}
out.println("* "+arc.toString()+" *");
writeHeader(out);
out.println(lineComment);
out.println("* Setup Time plus Clock-to-Q measurement");
out.print("* "); out.println(arcDesc);
out.println(lineComment);
out.println();
writeCommentHeader("Parameters");
out.println(".param "+inbufStr+"="+inputStr);
out.println(".param "+outloadStr+"="+outputLoad);
out.println(".param "+clkbufStr+"="+clkStr);
out.println(".param "+setupTimeSweep+"=0ps");
out.println();
writeCommentHeader("Test Bench");
// write stable voltage sources
for (PinEdge stable : arc.stableInputs) {
writeVoltSource(stable);
}
// input and clk buffers
writeBuffer(arc.input, bufferSubckt);
writeClkBuffer(arc.clk, clkbufSubckt);
writeVoltSource(arc.input);
writeVoltSource(arc.clk, setupTimeSweep);
if (arc.clkFalse != null) {
writeClkBuffer(arc.clkFalse, clkbufSubckt);
writeVoltSource(arc.clkFalse, setupTimeSweep);
}
// device under test
out.println("Vcurrent_in "+arc.input.pin+" "+arc.input.pin+"_i 0");
out.println("Vcurrent_out "+arc.output.pin+" "+arc.output.pin+"_i 0");
out.println("Vcurrent_clk "+arc.clk.pin+" "+arc.clk.pin+"_i 0");
out.print("Xdut ");
for (String port : dutSubckt.getPorts()) {
if (port.equalsIgnoreCase(arc.input.pin) || port.equalsIgnoreCase(arc.output.pin) ||
port.equalsIgnoreCase(arc.clk.pin))
out.print(port+"_i ");
else
out.print(port+" ");
}
out.print(topCellName);
out.println(" "+topCellParams);
// write load
writeLoad(arc.output, loadSubckt);
writeIC(arc.output);
for (PinEdge ic : arc.initialConditions) {
writeIC(new PinEdge("Xdut."+ic.pin, ic.stableVoltage));
}
out.println();
writeCommentHeader("Measure statements");
String inputSlew = arc.input.pin+"_slew";
String clkslew = arc.clk.pin+"_slew";
String outputSlew = arc.output.pin+"_slew";
String inputCap = arc.input.pin+"_cap";
String clkCap = arc.clk.pin+"_cap";
String outputCap = arc.output.pin+"_cap";
writeMeasSlew(clkslew, arc.clk, settings.inputLow, settings.inputHigh);
writeMeasSlew(inputSlew, arc.input, settings.inputLow, settings.inputHigh);
writeMeasSlew(outputSlew, arc.output, settings.outputLow, settings.outputHigh);
writeMeasCap(clkCap, arc.clk, "Vcurrent_clk");
writeMeasCap(inputCap, arc.input, "Vcurrent_in");
writeMeasCap(outputCap, arc.output, "Vcurrent_out");
writeMeasDelay(clk2q, arc.clk, arc.output, "0");
writeMeasDelay(setupTimeName, arc.input, arc.clk);
out.println(".meas TRAN "+setupclk2q+" PARAM='"+clk2q+"+"+setupTimeName+"' goal=0");
out.println();
sweeps.clear();
writeCommentHeader("Transient simulation");
out.println("*.tran "+settings.simResolutionPS+"ps "+settings.simTimePS+
"ps SWEEP "+setupTimeSweep+" LIN 20 "+tmsetupminPS+"ps "+tmsetupmaxPS+"ps");
out.println(".param "+setupTimeSweep+"=optrange("+tmsetupguessPS+"ps, "+
tmsetupminPS+"ps, "+tmsetupmaxPS+"ps)");
if (optphase == 0) {
// in optimization phase zero, we use passfail to
// find the minimum setup time that yields a non-failing
// clk-to-Q measurement
out.println(".model optmod OPT method=passfail");
out.println(".tran "+settings.simResolutionPS+"ps "+settings.simTimePS+
"ps SWEEP OPTIMIZE=optrange RESULTS="+clk2q+" MODEL=optmod");
} else {
// in optimization phase one, we use bisection to
// find the minimum of setup plus clk-to-Q
out.println(".model optmod OPT itropt="+maxSpiceIterations+" relin=0.001 relout=0.001");
out.println(".tran "+settings.simResolutionPS+"ps "+settings.simTimePS+
"ps SWEEP OPTIMIZE=optrange RESULTS="+setupclk2q+" MODEL=optmod");
}
// transient simulation
out.println();
out.println(".END");
out.close();
if (verbose) {
msg.println(" Finished writing netlist.");
}
runSpice(outputFileName, verbose);
File mt0file = new File(outputDir, outputFileName+".mt0");
if (verbose) {
msg.println("Reading measurements file "+mt0file.getName()+"...");
}
return TableData.readSpiceMeasResults(mt0file.getPath());
}
/**
* Flops latch and copy data to the output on a single edge, which
* means we can use one spice run to get both setup and clk-to-Q times.
* However, latches copy data on the rising edge, and latch on the falling
* edge (for transparent-when-high latches), so we need separate runs to
* determine setup and clock-to-Q times. The method for getting setup time
* for flops is used to get setup time for latches, and this method is
* used to get the clock-to-Q time for the latch
* @param outputFileName the output spice file name (no extension)
* @param arc the arc to characterize
* @param inputStr the input buffer strength
* @param clkStr the clock buffer strength
* @param outputLoad output load strength
* @return the resulting measure data
* @throws SCTimingException
*/
private TableData runLatchClk2QSpiceTest(String outputFileName, Arc arc,
String inputStr, String clkStr,
String outputLoad,
boolean verbose) throws SCTimingException {
String arcDesc = arc.toString();
if (verbose) {
msg.println();
msg.println("Writing spice netlist to:");
msg.println(" '"+outputFileName+".sp'...");
msg.println(" in directory: "+outputDir);
}
try {
out = new PrintWriter(new FileOutputStream(new File(outputDir, outputFileName+".sp")));
} catch (java.io.FileNotFoundException e) {
throw new SCTimingException(e.getMessage());
}
out.println("* "+arc.toString()+" *");
writeHeader(out);
out.println(lineComment);
out.println("* Latch Clock-to-Q measurement");
out.print("* "); out.println(arcDesc);
out.println(lineComment);
out.println();
writeCommentHeader("Parameters");
out.println(".param "+inbufStr+"="+inputStr);
out.println(".param "+outloadStr+"="+outputLoad);
out.println(".param "+clkbufStr+"="+clkStr);
out.println(".param "+setupTimeSweep+"=0ps");
out.println();
writeCommentHeader("Test Bench");
// write stable voltage sources
for (PinEdge stable : arc.stableInputs) {
writeVoltSource(stable);
}
// input and clk buffers
//writeBuffer(arc.input, bufferSubckt);
writeClkBuffer(arc.clk, clkbufSubckt);
writeVoltSource(arc.input.getFinalState());
writeVoltSource(arc.clk.getOpposite(), setupTimeSweep);
if (arc.clkFalse != null) {
writeClkBuffer(arc.clkFalse, clkbufSubckt);
writeVoltSource(arc.clkFalse.getOpposite(), setupTimeSweep);
}
// device under test
out.println("Vcurrent_in "+arc.input.pin+" "+arc.input.pin+"_i 0");
out.println("Vcurrent_out "+arc.output.pin+" "+arc.output.pin+"_i 0");
out.println("Vcurrent_clk "+arc.clk.pin+" "+arc.clk.pin+"_i 0");
out.print("Xdut ");
for (String port : dutSubckt.getPorts()) {
if (port.equalsIgnoreCase(arc.input.pin) || port.equalsIgnoreCase(arc.output.pin) ||
port.equalsIgnoreCase(arc.clk.pin))
out.print(port+"_i ");
else
out.print(port+" ");
}
out.print(topCellName);
out.println(" "+topCellParams);
// write load
writeLoad(arc.output, loadSubckt);
writeIC(arc.output);
for (PinEdge ic : arc.initialConditions) {
writeIC(new PinEdge("Xdut."+ic.pin, ic.stableVoltage));
}
out.println();
writeCommentHeader("Measure statements");
String inputSlew = arc.input.pin+"_slew";
String clkslew = arc.clk.pin+"_slew";
String outputSlew = arc.output.pin+"_slew";
String inputCap = arc.input.pin+"_cap";
String clkCap = arc.clk.pin+"_cap";
String outputCap = arc.output.pin+"_cap";
writeMeasSlew(clkslew, arc.clk, settings.inputLow, settings.inputHigh);
writeMeasSlew(inputSlew, arc.input, settings.inputLow, settings.inputHigh);
writeMeasSlew(outputSlew, arc.output, settings.outputLow, settings.outputHigh);
writeMeasCap(clkCap, arc.clk, "Vcurrent_clk");
writeMeasCap(inputCap, arc.input, "Vcurrent_in");
writeMeasCap(outputCap, arc.output, "Vcurrent_out");
writeMeasDelay(clk2q, arc.clk, arc.output, "0");
writeMeasDelay(setupTimeName, arc.input, arc.clk);
out.println(".meas TRAN "+setupclk2q+" PARAM='"+clk2q+"+"+setupTimeName+"' goal=0");
out.println();
sweeps.clear();
writeCommentHeader("Transient simulation");
out.println(".tran "+settings.simResolutionPS+"ps "+settings.simTimePS+"ps");
// transient simulation
out.println();
out.println(".END");
out.close();
if (verbose) {
msg.println(" Finished writing netlist.");
}
runSpice(outputFileName, verbose);
File mt0file = new File(outputDir, outputFileName+".mt0");
if (verbose) {
msg.println("Reading measurements file "+mt0file.getName()+"...");
}
return TableData.readSpiceMeasResults(mt0file.getPath());
}
/**
* This runs one of two spice optimizations. The first
* varies the setup time to find the minimum setup time at which
* the output is valid (i.e., a clock-to-Q measurement will not
* return "failed"). The second test varies the setup time to
* find the setup time which produces the minimum setup+clk2q time.
* The second test requires that the clock-to-Q measurement never fails,
* which is why the first optimization is run first.
* @param outputFileName the output spice file name (no extension)
* @param arc the arc to characterize
* @param inputStr the input buffer strength
* @param clkStr the clock buffer strength
* @param outputLoad output load strength
* @param tmholdminPS min possible value of setup time
* @param tmholdmaxPS max possible value of setup time
* @param tmholdguessPS starting point of setup time
* @param verbose true to print messages
* @param maxSpiceIterations max number of spice iterations for the
* second optimization phase
* @return the resulting measure data
* @throws SCTimingException timing exception
*/
private TableData runHoldTimeSpiceTest(String outputFileName, Arc arc,
String inputStr, String clkStr,
String outputLoad,
double tmholdminPS, double tmholdmaxPS, double tmholdguessPS,
boolean verbose, int maxSpiceIterations
) throws SCTimingException {
int optphase = 0;
String arcDesc = arc.toString();
if (verbose) {
msg.println();
msg.println("Writing spice netlist to:");
msg.println(" '"+outputFileName+".sp'...");
msg.println(" in directory: "+outputDir);
}
try {
out = new PrintWriter(new FileOutputStream(new File(outputDir, outputFileName+".sp")));
} catch (java.io.FileNotFoundException e) {
throw new SCTimingException(e.getMessage());
}
out.println("* "+arc.toString()+" *");
writeHeader(out);
out.println(lineComment);
out.println("* Hold Time measurement");
out.print("* "); out.println(arcDesc);
out.println(lineComment);
out.println();
writeCommentHeader("Parameters");
out.println(".param "+inbufStr+"="+inputStr);
out.println(".param "+outloadStr+"="+outputLoad);
out.println(".param "+clkbufStr+"="+clkStr);
out.println(".param "+holdTimeSweep+"=0ps");
out.println();
writeCommentHeader("Test Bench");
// write stable voltage sources
for (PinEdge stable : arc.stableInputs) {
writeVoltSource(stable);
}
// input and clk buffers
writeBuffer(arc.input, bufferSubckt);
writeClkBuffer(arc.clk, clkbufSubckt);
writeVoltSource(arc.input, holdTimeSweep);
writeVoltSource(arc.clk);
if (arc.clkFalse != null) {
writeClkBuffer(arc.clkFalse, clkbufSubckt);
writeVoltSource(arc.clkFalse);
}
// device under test
out.println("Vcurrent_in "+arc.input.pin+" "+arc.input.pin+"_i 0");
out.println("Vcurrent_out "+arc.output.pin+" "+arc.output.pin+"_i 0");
out.println("Vcurrent_clk "+arc.clk.pin+" "+arc.clk.pin+"_i 0");
out.print("Xdut ");
for (String port : dutSubckt.getPorts()) {
if (port.equalsIgnoreCase(arc.input.pin) || port.equalsIgnoreCase(arc.output.pin) ||
port.equalsIgnoreCase(arc.clk.pin))
out.print(port+"_i ");
else
out.print(port+" ");
}
out.print(topCellName);
out.println(" "+topCellParams);
// write load
writeLoad(arc.output, loadSubckt);
if (functionFlipFlop != null) {
writeIC(arc.output.getOpposite());
for (PinEdge ic : arc.initialConditions) {
ic = ic.getOpposite();
writeIC(new PinEdge("Xdut."+ic.pin, settings.vdd - ic.stableVoltage));
}
} else {
writeIC(arc.output);
for (PinEdge ic : arc.initialConditions) {
writeIC(new PinEdge("Xdut."+ic.pin, ic.stableVoltage));
}
}
out.println();
writeCommentHeader("Measure statements");
String inputSlew = arc.input.pin+"_slew";
String clkslew = arc.clk.pin+"_slew";
String outputSlew = arc.output.pin+"_slew";
String inputCap = arc.input.pin+"_cap";
String clkCap = arc.clk.pin+"_cap";
String outputCap = arc.output.pin+"_cap";
writeMeasSlew(clkslew, arc.clk, settings.inputLow, settings.inputHigh);
writeMeasSlew(inputSlew, arc.input, settings.inputLow, settings.inputHigh);
writeMeasSlew(outputSlew, arc.output.getOpposite(), settings.outputLow, settings.outputHigh);
writeMeasCap(clkCap, arc.clk, "Vcurrent_clk");
writeMeasCap(inputCap, arc.input, "Vcurrent_in");
writeMeasCap(outputCap, arc.output.getOpposite(), "Vcurrent_out");
writeMeasDelay(clk2q, arc.clk, arc.output.getOpposite(), "0");
writeMeasDelay(holdTimeName, arc.clk, arc.input);
out.println();
sweeps.clear();
writeCommentHeader("Transient simulation");
out.println("*.tran "+settings.simResolutionPS+"ps "+settings.simTimePS+
"ps SWEEP "+holdTimeSweep+" LIN 20 "+tmholdminPS+"ps "+tmholdmaxPS+"ps");
out.println(".param "+holdTimeSweep+"=optrange("+tmholdguessPS+"ps, "+
tmholdminPS+"ps, "+tmholdmaxPS+"ps)");
if (optphase == 0) {
// in optimization phase zero, we use passfail to
// find the maximum hold time that yields a non-failing
// clk-to-Q measurement
out.println(".model optmod OPT method=passfail");
out.println(".tran "+settings.simResolutionPS+"ps "+settings.simTimePS+
"ps SWEEP OPTIMIZE=optrange RESULTS="+clk2q+" MODEL=optmod");
} else {
// in optimization phase one, we use bisection to
// find the minimum of setup plus clk-to-Q
out.println(".model optmod OPT itropt="+maxSpiceIterations+" relin=0.001 relout=0.001");
out.println(".tran "+settings.simResolutionPS+"ps "+settings.simTimePS+
"ps SWEEP OPTIMIZE=optrange RESULTS="+setupclk2q+" MODEL=optmod");
}
// transient simulation
out.println();
out.println(".END");
out.close();
if (verbose) {
msg.println(" Finished writing netlist.");
}
runSpice(outputFileName, verbose);
File mt0file = new File(outputDir, outputFileName+".mt0");
if (verbose) {
msg.println("Reading measurements file "+mt0file.getName()+"...");
}
return TableData.readSpiceMeasResults(mt0file.getPath());
}
/**
* Run a sequential timing arc (simple version). This extracts setup and clock-to-Q times
* for the specified sweeps. Note that resulting data is stored
* on the Arc object.
* @param arc the timing arc to characterize
* @param delayType the delay type
* @throws SCTimingException thrown for an exception
*/
private void runSequentialSimple(Arc arc, SCRunBase.DelayType delayType) throws SCTimingException {
String arcDesc = arc.toString();
msg.println();
msg.println("--------------------------------------------------------");
msg.println("Characterizing timing arc \""+arcDesc+"\":");
msg.println();
msg.println("Finding setup, hold, and clk-to-Q time for \""+arcDesc+"\"");
String bufStrSweep = settings.bufferCellSweep;
String loadStrSweep = settings.loadCellSweep;
String loadStrSweepSetupHold = settings.loadCellSweepForSetupHold;
String clkStrSweep = settings.clkBufferCellSweep;
if (delayType == SCRunBase.DelayType.MIN) {
bufStrSweep = settings.bufferCellSweepMinTime;
loadStrSweep = settings.loadCellSweepMinTime;
loadStrSweepSetupHold = settings.loadCellSweepForSetupHoldMinTime;
clkStrSweep = settings.clkBufferCellSweepMinTime;
}
if (arc.getInputBufferSweep() != null) bufStrSweep = arc.getInputBufferSweep();
if (arc.getOutputLoadSweep() != null) {
loadStrSweep = arc.getOutputLoadSweep();
loadStrSweepSetupHold = arc.getOutputLoadSweep();
}
String [] bufStrs = bufStrSweep.trim().split("\\s+");
String [] loadStrs = loadStrSweep.trim().split("\\s+");
String [] loadStrsSetupHold = loadStrSweepSetupHold.trim().split("\\s+");
String [] clkStrs = clkStrSweep.trim().split("\\s+");
err(bufStrs.length == 0, "Buffer cell strength values empty");
err(loadStrs.length == 0, "Load cell strength values empty");
err(loadStrsSetupHold.length == 0, "Load cell strength values for Setup and Hold empty");
err(clkStrs.length == 0, "Clock buffer cell strength values empty");
String fileNameClk2q = topCellName + "_clk2q_" + arcDesc;
String fileNameSetup = topCellName + "_setup_" + arcDesc;
String fileNameHold = topCellName + "_hold_" + arcDesc;
// max number of optimization iterations performed by spice
int maxSpiceIterations = 30;
// all result data will be stored in this table
TableData setupHoldData = null;
boolean runHold = true;
// find clk2q for stable input, wrt to load and clk rise time
TableData clk2qData = null;
for (int c=0; c<clkStrs.length; c++) {
TableData data = runSimpleClk2Q(fileNameClk2q, arc, clkStrs[c], loadStrSweep,
settings.tmsetupMaxGuessPS, verbose);
if (clk2qData == null) {
List<String> headers = new ArrayList<String>();
headers.add(clkbufStr);
headers.add(outloadStr);
headers.addAll(data.getHeaders());
clk2qData = new TableData(headers);
}
for (int row=0; row<data.getNumRows(); row++) {
double [] datarow = data.getRow(row);
double [] newrow = new double[datarow.length+2];
newrow[0] = Double.parseDouble(clkStrs[c]);
newrow[1] = Double.parseDouble(loadStrs[row]);
for (int i=2; i<newrow.length; i++) {
newrow[i] = datarow[i-2];
}
clk2qData.addRow(newrow);
}
}
Table2D data2d_clkbuf_outload = clk2qData.getTable2D(clkbufStr.toLowerCase(), outloadStr.toLowerCase(), null, null);
// now find setup time that pushes out clk2q by specified amount
for (int c=0; c<clkStrs.length; c++) {
for (int l=0; l<loadStrsSetupHold.length; l++) {
for (int b=0; b<bufStrs.length; b++) {
// Run a spice optimization to find the specified clk2q pushout
TableData data = runSimpleSetupTimeSpiceTest(fileNameSetup, arc, bufStrs[b], clkStrs[c], loadStrsSetupHold[l],
settings.tmsetupMinGuessPS, settings.tmsetupMaxGuessPS, settings.tmsetupGuessPS,
verbose, maxSpiceIterations);
if (verbose) {
msg.println();
data.printData();
msg.println();
}
if (data.getNumRows() == maxSpiceIterations) {
throw new SCTimingException("Max number of iterations of optimization in spice reached, result may not be valid");
}
double tmholdValidMaxPS = 1e-12;
if (runHold) {
// Run a spice optimization to find the maximum hold time
// at which clk-to-Q can be measured.
TableData dataHold = runHoldGlitchTimeSpiceTest(fileNameHold, arc, bufStrs[b], clkStrs[c], loadStrs[l],
settings.tmsetupMinGuessPS, settings.tmsetupMaxGuessPS, settings.tmsetupGuessPS,
verbose, maxSpiceIterations);
tmholdValidMaxPS = dataHold.getValue(0, holdTimeName);
err(Double.isNaN(tmholdValidMaxPS), "Error running spice simulation to get maximum hold time");
if (verbose) {
msg.println();
msg.println("Bufstr="+bufStrs[b]+", Clkstr="+clkStrs[c]+", Loadstr="+loadStrs[l]);
msg.println("Hold time is "+(tmholdValidMaxPS*1e12)+"ps");
msg.println();
}
}
TableData latchClk2Q = null;
if (functionLatch != null) {
latchClk2Q = runLatchClk2QSpiceTest(fileNameSetup+"_q", arc, bufStrs[b], clkStrs[c],
loadStrs[l], verbose);
}
// record results into allData object
if (setupHoldData == null) {
List<String> headers = new ArrayList<String>();
headers.add(inbufStr);
headers.add(clkbufStr);
headers.add(outloadStr);
headers.add(holdTimeName);
headers.addAll(data.getHeaders());
setupHoldData = new TableData(headers);
}
if (data.getNumRows() > 0) {
double [] datarow = data.getRow(data.getNumRows()-1);
double [] newrow = new double[datarow.length+4];
newrow[0] = Double.parseDouble(bufStrs[b]);
newrow[1] = Double.parseDouble(clkStrs[c]);
newrow[2] = Double.parseDouble(loadStrs[l]);
newrow[3] = tmholdValidMaxPS;
for (int i=4; i<newrow.length; i++) {
newrow[i] = datarow[i-4];
}
if (latchClk2Q != null) {
// replace clk-to-Q value
int cc = setupHoldData.getHeaders().indexOf("clk2q");
int cc2 = latchClk2Q.getHeaders().indexOf("clk2q");
newrow[cc] = latchClk2Q.getRow(latchClk2Q.getNumRows()-1)[cc2];
}
setupHoldData.addRow(newrow);
}
}
}
}
msg.println();
arc.data = setupHoldData;
Table2D data2d_inbuf_clkbuf = setupHoldData.getTable2D(inbufStr.toLowerCase(), clkbufStr.toLowerCase(),
null, null);
arc.data2d_inbuf_outload = null;
arc.data2d_clkbuf_outload = data2d_clkbuf_outload;
arc.data2d_inbuf_clkbuf = data2d_inbuf_clkbuf;
if (verbose) {
clk2qData.printData();
setupHoldData.printData();
System.out.println("----------------------------------------------------------------------------");
System.out.println("----------------------------------------------------------------------------");
System.out.println("-------------------- Clock Slew vs Output Load Table2D ---------------------");
System.out.println("----------------------------------------------------------------------------");
System.out.println("----------------------------------------------------------------------------");
data2d_clkbuf_outload.print();
System.out.println("----------------------------------------------------------------------------");
System.out.println("----------------------------------------------------------------------------");
System.out.println("-------------------- Input Slew vs Clk Slew Table2D ------------------------");
System.out.println("----------------------------------------------------------------------------");
System.out.println("----------------------------------------------------------------------------");
data2d_inbuf_clkbuf.print();
msg.println();
}
if (printStatistics) {
msg.println("Statistical checks:");
msg.println("-------------------");
msg.println();
msg.println("Clock-to-Q should depend only on clock buffer and output load size:");
//printColumnMeanStdDev(data2dClk, clk2q, msg, 1e12, "ps");
print2DMeanStdDev(data2d_clkbuf_outload, clk2q, msg, 1e12, "ps");
msg.println();
msg.println("Pushed-out Clock-to-Q should depend only on clock buffer and output load size:");
printColumnMeanStdDev(data2d_inbuf_clkbuf, clk2q, msg, 1e12, "ps");
msg.println();
msg.println("Setup time should depend only on input buffer strength and clock buffer strength:");
//printRowMeanStdDev(data2dClk, setupTimeName, msg, 1e12, "ps");
print2DMeanStdDev(data2d_inbuf_clkbuf, setupTimeName, msg, 1e12, "ps");
msg.println();
msg.println("Input slew should depend only on input buffer strength:");
printRowMeanStdDev(data2d_inbuf_clkbuf, arc.input.pin+"_slew", msg, 1e12, "ps");
msg.println();
msg.println("Output capacitance should depend only on output load size");
printColumnMeanStdDev(data2d_clkbuf_outload, arc.output.pin+"_cap", msg, 1e15, "fF");
msg.println();
msg.println("Input capacitance should not be dependent on either input slew or output load");
printMeanStdDev(data2d_inbuf_clkbuf, arc.input.pin+"_cap", msg, 1e15, "fF");
msg.println();
msg.println("Hold time should depend only on input buffer strength and clock buffer strength:");
//printRowMeanStdDev(data2dClk, holdTimeName, msg, 1e12, "ps");
print2DMeanStdDev(data2d_inbuf_clkbuf, holdTimeName, msg, 1e12, "ps");
msg.println();
}
msg.println();
msg.println("Characterization of arc \""+arc+"\" complete.");
}
/**
* Get the clk2q given a stable input, while varying the clk slew and output load.
* Please note that the output load string passed to this function should be the
* full set of values, i.e. "1 2 3 4 5 6". The clkStr should be one value, such as "1".
* @param outputFileName output file name
* @param arc arc to characterize
* @param clkStr clock strength to use
* @param outputLoadSweep list of output loads to use, of the form "1 2 3 4 5 6"
* @param clkPulseTime the time after simulation starts that the clock is pulsed
* @param verbose true to print out messages
* @return Table of measure results
* @throws SCTimingException
*/
private TableData runSimpleClk2Q(String outputFileName, Arc arc,
String clkStr,
String outputLoadSweep,
double clkPulseTime,
boolean verbose ) throws SCTimingException {
String [] loads = outputLoadSweep.split("\\s+");
if (loads.length == 0)
throw new SCTimingException("No output load sweep in runSimpleClk2q");
String arcDesc = arc.toString();
if (verbose) {
msg.println();
msg.println("Writing spice netlist to:");
msg.println(" '"+outputFileName+".sp'...");
msg.println(" in directory: "+outputDir);
}
try {
out = new PrintWriter(new FileOutputStream(new File(outputDir, outputFileName+".sp")));
} catch (java.io.FileNotFoundException e) {
throw new SCTimingException(e.getMessage());
}
out.println("* "+arc.toString()+" *");
writeHeader(out);
out.println(lineComment);
out.println("* Clock-to-Q measurement");
out.print("* "); out.println(arcDesc);
out.println(lineComment);
out.println();
writeCommentHeader("Parameters");
out.println(".param "+outloadStr+"="+loads[0]);
out.println(".param "+clkbufStr+"="+clkStr);
out.println(".param "+setupTimeSweep+"="+clkPulseTime+"ps");
out.println();
writeCommentHeader("Test Bench");
// write stable voltage sources
for (PinEdge stable : arc.stableInputs) {
writeVoltSource(stable);
}
// input and clk buffers
writeClkBuffer(arc.clk, clkbufSubckt);
writeVoltSource(arc.input.getFinalState());
writeVoltSource(arc.clk, setupTimeSweep);
if (arc.clkFalse != null) {
writeClkBuffer(arc.clkFalse, clkbufSubckt);
writeVoltSource(arc.clkFalse, setupTimeSweep);
}
// device under test
out.println("Vcurrent_in "+arc.input.pin+" "+arc.input.pin+"_i 0");
out.println("Vcurrent_out "+arc.output.pin+" "+arc.output.pin+"_i 0");
out.println("Vcurrent_clk "+arc.clk.pin+" "+arc.clk.pin+"_i 0");
out.print("Xdut ");
for (String port : dutSubckt.getPorts()) {
if (port.equalsIgnoreCase(arc.input.pin) || port.equalsIgnoreCase(arc.output.pin) ||
port.equalsIgnoreCase(arc.clk.pin))
out.print(port+"_i ");
else
out.print(port+" ");
}
out.print(topCellName);
out.println(" "+topCellParams);
// write load
writeLoad(arc.output, loadSubckt);
writeIC(arc.output);
for (PinEdge ic : arc.initialConditions) {
writeIC(new PinEdge("Xdut."+ic.pin, ic.stableVoltage));
}
out.println();
writeCommentHeader("Measure statements");
String clkslew = arc.clk.pin+"_slew";
String outputSlew = arc.output.pin+"_slew";
String clkCap = arc.clk.pin+"_cap";
String outputCap = arc.output.pin+"_cap";
writeMeasSlew(clkslew, arc.clk, settings.inputLow, settings.inputHigh);
writeMeasSlew(outputSlew, arc.output, settings.outputLow, settings.outputHigh);
writeMeasCap(clkCap, arc.clk, "Vcurrent_clk");
writeMeasCap(outputCap, arc.output, "Vcurrent_out");
writeMeasDelay(clk2q, arc.clk, arc.output, "0");
out.println();
sweeps.clear();
writeCommentHeader("Transient simulation");
out.println(".tran "+settings.simResolutionPS+"ps "+settings.simTimePS+
"ps SWEEP "+outloadStr+" POI "+loads.length+" "+outputLoadSweep);
// transient simulation
out.println();
out.println(".END");
out.close();
if (verbose) {
msg.println(" Finished writing netlist.");
}
runSpice(outputFileName, verbose);
File mt0file = new File(outputDir, outputFileName+".mt0");
if (verbose) {
msg.println("Reading measurements file "+mt0file.getName()+"...");
}
return TableData.readSpiceMeasResults(mt0file.getPath());
}
/**
* This runs one spice optimization. It varies the setup time to
* find the setup time whereby the push out on clk2q reaches the specified value.
* @param outputFileName the output spice file name (no extension)
* @param arc the arc to characterize
* @param inputStr the input buffer strength
* @param clkStr the clock buffer strength
* @param outputLoad output load strength
* @param tmsetupminPS min possible value of setup time
* @param tmsetupmaxPS max possible value of setup time
* @param tmsetupguessPS starting point of setup time
* @param verbose true to print messages
* @param maxSpiceIterations max number of spice iterations for the
* second optimization phase
* @return the resulting measure data
* @throws SCTimingException if there was an error
*/
private TableData runSimpleSetupTimeSpiceTest(String outputFileName, Arc arc,
String inputStr, String clkStr,
String outputLoad,
double tmsetupminPS, double tmsetupmaxPS, double tmsetupguessPS,
boolean verbose, int maxSpiceIterations
) throws SCTimingException {
String arcDesc = arc.toString();
if (verbose) {
msg.println();
msg.println("Writing spice netlist to:");
msg.println(" '"+outputFileName+".sp'...");
msg.println(" in directory: "+outputDir);
}
try {
out = new PrintWriter(new FileOutputStream(new File(outputDir, outputFileName+".sp")));
} catch (java.io.FileNotFoundException e) {
throw new SCTimingException(e.getMessage());
}
out.println("* "+arc.toString()+" *");
writeHeader(out);
out.println(lineComment);
out.println("* Setup Time plus Clock-to-Q measurement");
out.print("* "); out.println(arcDesc);
out.println(lineComment);
out.println();
writeCommentHeader("Parameters");
out.println(".param "+inbufStr+"="+inputStr);
out.println(".param "+outloadStr+"="+outputLoad);
out.println(".param "+clkbufStr+"="+clkStr);
out.println(".param "+setupTimeSweep+"=0ps");
out.println();
writeCommentHeader("Test Bench");
// write stable voltage sources
for (PinEdge stable : arc.stableInputs) {
writeVoltSource(stable);
}
// input and clk buffers
writeBuffer(arc.input, bufferSubckt);
writeClkBuffer(arc.clk, clkbufSubckt);
writeVoltSource(arc.input, setupTimeSweep);
writeVoltSource(arc.clk);
if (arc.clkFalse != null) {
writeClkBuffer(arc.clkFalse, clkbufSubckt);
writeVoltSource(arc.clkFalse);
}
// device under test
out.println("Vcurrent_in "+arc.input.pin+" "+arc.input.pin+"_i 0");
out.println("Vcurrent_out "+arc.output.pin+" "+arc.output.pin+"_i 0");
out.println("Vcurrent_clk "+arc.clk.pin+" "+arc.clk.pin+"_i 0");
out.print("Xdut ");
for (String port : dutSubckt.getPorts()) {
if (port.equalsIgnoreCase(arc.input.pin) || port.equalsIgnoreCase(arc.output.pin) ||
port.equalsIgnoreCase(arc.clk.pin))
out.print(port+"_i ");
else
out.print(port+" ");
}
out.print(topCellName);
out.println(" "+topCellParams);
// write load
writeLoad(arc.output, loadSubckt);
writeIC(arc.output);
for (PinEdge ic : arc.initialConditions) {
writeIC(new PinEdge("Xdut."+ic.pin, ic.stableVoltage));
}
out.println();
writeCommentHeader("Measure statements");
String inputSlew = arc.input.pin+"_slew";
String clkslew = arc.clk.pin+"_slew";
String outputSlew = arc.output.pin+"_slew";
String inputCap = arc.input.pin+"_cap";
String clkCap = arc.clk.pin+"_cap";
String outputCap = arc.output.pin+"_cap";
String pushout = "pushout";
writeMeasSlew(clkslew, arc.clk, settings.inputLow, settings.inputHigh);
writeMeasSlew(inputSlew, arc.input, settings.inputLow, settings.inputHigh);
writeMeasSlew(outputSlew, arc.output, settings.outputLow, settings.outputHigh);
writeMeasCap(clkCap, arc.clk, "Vcurrent_clk");
writeMeasCap(inputCap, arc.input, "Vcurrent_in");
writeMeasCap(outputCap, arc.output, "Vcurrent_out");
writeMeasDelay(clk2q, arc.clk, arc.output);
writeMeasDelay(setupTimeName, arc.input, arc.clk);
writeMeasPushout(pushout, arc.output, settings.clk2qpushout);
out.println();
sweeps.clear();
writeCommentHeader("Transient simulation");
out.println("*.tran "+settings.simResolutionPS+"ps "+settings.simTimePS+
"ps SWEEP "+setupTimeSweep+" LIN 20 "+tmsetupminPS+"ps "+tmsetupmaxPS+"ps");
out.println(".param "+setupTimeSweep+"=optrange("+tmsetupguessPS+"ps, "+
tmsetupminPS+"ps, "+tmsetupmaxPS+"ps)");
// use spice's passfail bisection optimization to find best clk2q, then push that out by "clk2qpushout" ps
out.println(".model optmod OPT method=passfail itropt="+maxSpiceIterations+" relin=0.0001 relout=0.001");
out.println(".tran "+settings.simResolutionPS+"ps "+settings.simTimePS+
"ps SWEEP OPTIMIZE=optrange RESULTS="+pushout+" MODEL=optmod");
// transient simulation
out.println();
out.println(".END");
out.close();
if (verbose) {
msg.println(" Finished writing netlist.");
}
runSpice(outputFileName, verbose);
File mt0file = new File(outputDir, outputFileName+".mt0");
if (verbose) {
msg.println("Reading measurements file "+mt0file.getName()+"...");
}
return TableData.readSpiceMeasResults(mt0file.getPath());
}
/**
* Finds the hold time by starting at a point that is clearly past the trigger
* point, then moving backwards until it passes through the trigger point and
* violates the hold time. Violation is defined as a glitch on the node after
* the first pass gate (usually denoted as the master node).
* @param outputFileName the output spice file name (no extension)
* @param arc the arc to characterize
* @param inputStr the input buffer strength
* @param clkStr the clock buffer strength
* @param outputLoad output load strength
* @param tmholdminPS min possible value of setup time
* @param tmholdmaxPS max possible value of setup time
* @param tmholdguessPS starting point of setup time
* @param verbose true to print messages
* @param maxSpiceIterations max number of spice iterations for the
* second optimization phase
* @return the resulting measure data
* @throws SCTimingException timing exception
*/
private TableData runHoldGlitchTimeSpiceTest(String outputFileName, Arc arc,
String inputStr, String clkStr,
String outputLoad,
double tmholdminPS, double tmholdmaxPS, double tmholdguessPS,
boolean verbose, int maxSpiceIterations
) throws SCTimingException {
int optphase = 0;
String arcDesc = arc.toString();
if (verbose) {
msg.println();
msg.println("Writing spice netlist to:");
msg.println(" '"+outputFileName+".sp'...");
msg.println(" in directory: "+outputDir);
}
try {
out = new PrintWriter(new FileOutputStream(new File(outputDir, outputFileName+".sp")));
} catch (java.io.FileNotFoundException e) {
throw new SCTimingException(e.getMessage());
}
out.println("* "+arc.toString()+" *");
writeHeader(out);
out.println(lineComment);
out.println("* Hold Time measurement");
out.print("* "); out.println(arcDesc);
out.println(lineComment);
out.println();
writeCommentHeader("Parameters");
out.println(".param "+inbufStr+"="+inputStr);
out.println(".param "+outloadStr+"="+outputLoad);
out.println(".param "+clkbufStr+"="+clkStr);
out.println(".param "+holdTimeSweep+"=0ps");
out.println();
writeCommentHeader("Test Bench");
// write stable voltage sources
for (PinEdge stable : arc.stableInputs) {
writeVoltSource(stable);
}
// input and clk buffers
writeBuffer(arc.input, bufferSubckt);
writeClkBuffer(arc.clk, clkbufSubckt);
writeVoltSource(arc.input, holdTimeSweep);
writeVoltSource(arc.clk);
if (arc.clkFalse != null) {
writeClkBuffer(arc.clkFalse, clkbufSubckt);
writeVoltSource(arc.clkFalse);
}
// device under test
out.println("Vcurrent_in "+arc.input.pin+" "+arc.input.pin+"_i 0");
out.println("Vcurrent_out "+arc.output.pin+" "+arc.output.pin+"_i 0");
out.println("Vcurrent_clk "+arc.clk.pin+" "+arc.clk.pin+"_i 0");
out.print("Xdut ");
for (String port : dutSubckt.getPorts()) {
if (port.equalsIgnoreCase(arc.input.pin) || port.equalsIgnoreCase(arc.output.pin) ||
port.equalsIgnoreCase(arc.clk.pin))
out.print(port+"_i ");
else
out.print(port+" ");
}
out.print(topCellName);
out.println(" "+topCellParams);
// write load
writeLoad(arc.output, loadSubckt);
writeIC(new PinEdge("Xdut."+arc.glitchNode.pin, arc.glitchNode.getInitialState().transition));
out.println();
writeCommentHeader("Measure statements");
String inputSlew = arc.input.pin+"_slew";
String clkslew = arc.clk.pin+"_slew";
String outputSlew = arc.output.pin+"_slew";
String inputCap = arc.input.pin+"_cap";
String clkCap = arc.clk.pin+"_cap";
String outputCap = arc.output.pin+"_cap";
String glitch = "glitch";
writeMeasSlew(clkslew, arc.clk, settings.inputLow, settings.inputHigh);
writeMeasSlew(inputSlew, arc.input, settings.inputLow, settings.inputHigh);
writeMeasSlew(outputSlew, arc.output.getOpposite(), settings.outputLow, settings.outputHigh);
writeMeasCap(clkCap, arc.clk, "Vcurrent_clk");
writeMeasCap(inputCap, arc.input, "Vcurrent_in");
writeMeasCap(outputCap, arc.output.getOpposite(), "Vcurrent_out");
writeMeasDelay(clk2q, arc.clk, arc.output, "0");
writeMeasDelay(holdTimeName, arc.clk, arc.input);
writeMeasGlitch(glitch, arc.glitchNode);
out.println();
sweeps.clear();
writeCommentHeader("Transient simulation");
out.println("*.tran "+settings.simResolutionPS+"ps "+settings.simTimePS+
"ps SWEEP "+holdTimeSweep+" LIN 20 "+tmholdminPS+"ps "+tmholdmaxPS+"ps");
out.println(".param "+holdTimeSweep+"=optrange("+tmholdguessPS+"ps, "+
tmholdminPS+"ps, "+tmholdmaxPS+"ps)");
// use spice's passfail bisection optimization to find time when signal glitches
out.println(".model optmod OPT method=passfail itropt="+maxSpiceIterations+" relin=0.0001 relout=0.001");
out.println(".tran "+settings.simResolutionPS+"ps "+settings.simTimePS+
"ps SWEEP OPTIMIZE=optrange RESULTS="+glitch+" MODEL=optmod");
// transient simulation
out.println();
out.println(".END");
out.close();
if (verbose) {
msg.println(" Finished writing netlist.");
}
runSpice(outputFileName, verbose);
File mt0file = new File(outputDir, outputFileName+".mt0");
if (verbose) {
msg.println("Reading measurements file "+mt0file.getName()+"...");
}
return TableData.readSpiceMeasResults(mt0file.getPath());
}
private static class SpiceResultChecker extends OutputStream {
private byte [] buf = new byte[256];
private int count;
private boolean failed = false;
private OutputStream out;
public SpiceResultChecker(OutputStream out) {
this.out = out;
}
public void write(int b) throws IOException {
if (b == '\n' || b == Character.LINE_SEPARATOR) {
checkLine();
count = 0;
} else {
if (count > buf.length) count = 0;
buf[count] = (byte)b;
count++;
}
if (out != null)
out.write(b);
}
private void checkLine() {
String line = new String(buf, 0, count);
if (line.indexOf("***** hspice job aborted") != -1) {
failed = true;
}
}
private boolean getFailed() { return failed; }
public void close() throws IOException { if (out != null) out.close(); }
public void flush() throws IOException { if (out != null) out.flush(); }
}
/**
* Runs an external spice job on the given file name,
* returns when the external spice job has finished.
* @param outputFileName the spice file (no path, no extension)
* @param verbose true to print messages
* @throws SCTimingException
*/
private void runSpice(String outputFileName, boolean verbose) throws SCTimingException {
String command = settings.simulator+ " "+outputFileName+".sp";
if (verbose) {
msg.println();
msg.println("Running spice: "+command);
msg.println(" In directory "+outputDir);
msg.println(" Logging output to "+outputFileName+".out");
msg.println(" "+new Date(System.currentTimeMillis()));
msg.println();
}
OutputStream outlog = null;
try {
outlog = new BufferedOutputStream(new FileOutputStream(new File(outputDir, outputFileName+".out")));
} catch (java.io.FileNotFoundException e) {
throw new SCTimingException(e.getMessage());
}
SpiceResultChecker checker = new SpiceResultChecker(verbose ? System.out : null);
Exec exec = new Exec(command, null, new File(outputDir), outlog, checker);
exec.run();
try {
outlog.close();
} catch (java.io.IOException e) {
throw new SCTimingException(e.getMessage());
}
if (exec.getExitVal() != 0 || checker.getFailed()) {
msg.println();
msg.println("Spice job Aborted");
msg.println(" "+new Date(System.currentTimeMillis()));
throw new SCTimingException("Spice run failed, please check output file");
}
if (verbose) {
msg.println();
msg.println("Spice job completed");
msg.println(" "+new Date(System.currentTimeMillis()));
msg.println();
}
}
private void writeCommentHeader(String msg) {
out.println(lineComment);
out.print("* "); out.println(msg);
out.println(lineComment);
}
private void writeVoltSource(PinEdge edge) {
writeVoltSource(edge, null);
}
private void writeVoltSource(PinEdge edge, String timeStartParam) {
int numPeriods = 1;
double vhigh = settings.vdd;
double vlow = 0;
switch(edge.transition) {
case STABLE0: {
out.println("V"+edge.pin+" "+edge.pin+" gnd 0");
break;
}
case STABLE1: {
out.println("V"+edge.pin+" "+edge.pin+" gnd vsupply");
break;
}
case STABLEV: {
out.println("V"+edge.pin+" "+edge.pin+" gnd "+edge.stableVoltage);
break;
}
case RISE: {
double temp = vlow;
vlow = vhigh;
vhigh = temp;
// fall through to FALL case
}
case FALL: {
out.println("V"+edge.pin+"_pre "+edge.pin+"_pre gnd ");
out.print("+ PWL ");
out.print(0.0+" "+vhigh+" ");
double timeStart = settings.timeStartPS;
if (timeStart + settings.tmsetupMinGuessPS < 0) // initial point would be at negative time
timeStart = -1.0*settings.tmsetupMinGuessPS;
for (int i=0; i<numPeriods; i++) {
double t = timeStart + i*settings.periodPS;
if (timeStartParam != null) {
out.print("'"+timeStartParam+"+"+t+"ps' "+vhigh+" ");
out.print("'"+timeStartParam+"+"+(t+settings.inputRampTimePS)+"ps' "+vlow+" ");
//out.print("'"+timeStartParam+"+"+(t+periodPS/2)+"ps' "+vlow+" ");
//out.print("'"+timeStartParam+"+"+(t+periodPS/2+inputRampTimePS)+"ps' "+vhigh+" ");
} else {
out.print(t+"ps "+vhigh+" ");
out.print((t+settings.inputRampTimePS)+"ps "+vlow+" ");
//out.print((t+periodPS/2)+"ps "+vlow+" ");
//out.print((t+periodPS/2+inputRampTimePS)+"ps "+vhigh+" ");
}
}
out.print((settings.periodPS*numPeriods+settings.simTimePS)+"ps "+vlow);
out.println();
break;
}
}
}
private void writeIC(PinEdge edge) {
out.print(".ic V("+edge.pin+") = ");
switch(edge.transition) {
case STABLE0:
case RISE:
out.println(0);
break;
case STABLE1:
case FALL:
out.println("vsupply");
break;
case STABLEV:
out.println(edge.stableVoltage);
}
}
private void writeBuffer(PinEdge edge, SpiceSubckt bufferSubckt) {
if (isStable(edge.transition)) {
return; // already tied to gnd or vdd
}
out.print("Xbuf"+edge.pin);
out.print(" ");
for (String port : bufferSubckt.getPorts()) {
if (port.equalsIgnoreCase(settings.bufferCellInputPort)) {
out.print(edge.pin+"_pre ");
}
else if (port.equalsIgnoreCase(settings.bufferCellOutputPort)) {
out.print(edge.pin+" ");
}
else {
// unknown port, just tie to gnd
out.print("0 ");
}
}
out.print(settings.bufferCell+" ");
out.println(settings.bufferCellStrengthParam+"="+inbufStr);
}
private void writeClkBuffer(PinEdge edge, SpiceSubckt clkbufSubckt) {
if (isStable(edge.transition)) {
return; // already tied to gnd or vdd
}
out.print("Xbuf"+edge.pin);
out.print(" ");
for (String port : clkbufSubckt.getPorts()) {
if (port.equalsIgnoreCase(settings.clkBufferCellInputPort)) {
out.print(edge.pin+"_pre ");
}
else if (port.equalsIgnoreCase(settings.clkBufferCellOutputPort)) {
out.print(edge.pin+" ");
}
else {
// unknown port, just tie to gnd
out.print("0 ");
}
}
out.print(settings.clkBufferCell+" ");
out.println(settings.clkBufferCellStrengthParam+"="+clkbufStr);
}
private void writeLoad(PinEdge edge, SpiceSubckt loadSubckt) {
out.print("Xload ");
for (String port : loadSubckt.getPorts()) {
if (port.equalsIgnoreCase(settings.loadCellPort)) {
out.print(edge.pin+" ");
}
else {
// unknown port, just tie to gnd
out.print("0 ");
}
}
out.print(settings.loadCell+" ");
out.println(settings.loadCellStrengthParam+"="+outloadStr);
}
private void writeSweepData() {
out.println(".DATA DATA_TIM");
Stack<String> pvals = new Stack<String>();
out.print("+");
for (int i=0; i<sweeps.size(); i++) {
SweepParam sp = sweeps.get(i);
out.print(" "+sp.param);
}
out.println();
iterateList(sweeps, 0, pvals);
out.println(".ENDDATA");
}
private void iterateList(List<SweepParam> list, int index, Stack<String> stack) {
if (index >= list.size()) {
index = 0;
}
SweepParam sp = list.get(index);
for (int i=0; i<sp.sweep.length; i++) {
stack.push(sp.sweep[i]);
if (list.size()-1 == index) {
// last one, print
out.print("+ ");
for (String s : stack) out.print(s+" ");
out.println();
}
else {
iterateList(list, index+1, stack);
}
stack.pop();
}
}
private void writeMeasDelay(String measname, PinEdge in, PinEdge output) {
writeMeasDelay(measname, in, output, null);
}
private void writeMeasDelay(String measname, PinEdge in, PinEdge output, String goal) {
if (isStable(in.transition))
return;
out.println(".meas TRAN "+measname);
out.println("+ TRIG V("+in.pin+") VAL='"+settings.inputDelayThresh+"*vsupply' CROSS=1");
out.println("+ TARG V("+output.pin+") VAL='"+settings.outputDelayThresh+"*vsupply' CROSS=1");
if (goal != null)
out.println("+ goal="+goal);
}
private void writeMeasSlew(String measname, PinEdge edge, double low, double high) {
if (isStable(edge.transition))
return;
double start = low;
double end = high;
if (edge.transition == PinEdge.Transition.FALL) {
start = high;
end = low;
}
out.println(".meas TRAN "+measname);
out.println("+ TRIG V("+edge.pin+") VAL='"+start+"*vsupply' CROSS=1");
out.println("+ TARG V("+edge.pin+") VAL='"+end+"*vsupply' CROSS=1");
}
private void writeMeasCap(String measname, PinEdge edge, String voltageSource) {
if (isStable(edge.transition)) return;
double start = settings.edgePercentForCapStart;
double end = settings.edgePercentForCapEnd;
if (edge.transition == PinEdge.Transition.FALL) {
start = 1-settings.edgePercentForCapStart;
end = 1-settings.edgePercentForCapEnd;
}
String vstart = measname+"vstart";
String vend = measname+"vend";
String tstart = measname+"tstart";
String tend = measname+"tend";
out.println(".param "+vstart+"='"+start+"*vsupply'");
out.println(".param "+vend+"='"+end+"*vsupply'");
out.println(".meas TRAN "+tstart+" WHEN V("+edge.pin+") = '"+vstart+"'");
out.println(".meas TRAN "+tend+" WHEN V("+edge.pin+") = '"+vend+"'");
out.println(".meas TRAN "+measname+"_avgi");
out.println("+ AVG i("+voltageSource+") FROM='"+tstart+"' TO='"+tend+"'");
out.println(".meas TRAN "+measname);
out.println("+ PARAM='abs("+measname+"_avgi*("+tend+"-"+tstart+")/("+vend+"-"+vstart+"))'");
}
private void writeMeasPushout(String measname, PinEdge edge, double pushoutPS) {
if (isStable(edge.transition)) return;
out.println(".meas TRAN "+measname+" WHEN V("+edge.pin+") = '"+settings.outputDelayThresh+
"*vsupply' CROSS=1 pushout='"+pushoutPS+"ps'");
}
private void writeMeasGlitch(String measname, PinEdge edge) {
if (isStable(edge.transition)) return;
if (edge.transition == PinEdge.Transition.RISE)
out.println(".meas TRAN "+measname+" WHEN V(Xdut."+edge.pin+") = '"+settings.holdGlitchLowPercent+"*vsupply' RISE=1");
if (edge.transition == PinEdge.Transition.FALL)
out.println(".meas TRAN "+measname+" WHEN V(Xdut."+edge.pin+") = '"+settings.holdGlitchHighPercent+"*vsupply' FALL=1");
}
private void verifyPorts(Cell topCell, SpiceSubckt testCell, Arc arc, List<String> globalNets) throws SCTimingException {
// check that ports specified are present on test cell
//List<String> ports = new ArrayList<String>(testCell.getPorts());
List<String> ports = new ArrayList<String>();
Netlist netlist = topCell.getNetlist(Netlist.ShortResistors.ALL);
for (Iterator<Export> it = topCell.getExports(); it.hasNext(); ) {
Export ex = it.next();
String name = netlist.getNetwork(ex, 0).getName();
if (!ports.contains(name)) ports.add(name);
}
for (PinEdge p : arc.stableInputs) {
if (!testCell.hasPort(p.pin))
throw new SCTimingException("Pin "+p.pin+" not found on test cell "+testCell.getName()+" for arc "+arc);
ports.remove(p.pin);
}
if (!testCell.hasPort(arc.output.pin))
throw new SCTimingException("Pin "+arc.output.pin+" not found on test cell "+testCell.getName()+" for arc "+arc);
if (!testCell.hasPort(arc.input.pin))
throw new SCTimingException("Pin "+arc.input.pin+" not found on test cell "+testCell.getName()+" for arc "+arc);
if (arc.clk != null) {
if (!testCell.hasPort(arc.clk.pin))
throw new SCTimingException("Pin "+arc.clk.pin+" not found on test cell "+testCell.getName()+" for arc "+arc);
if (arc.clk.transition != PinEdge.Transition.RISE && arc.clk.transition != PinEdge.Transition.FALL) {
throw new SCTimingException("Clock pin "+arc.clk.pin+" transition must be RISE or FALL for arc "+arc);
}
ports.remove(arc.clk.pin);
}
if (arc.clkFalse != null) {
if (!testCell.hasPort(arc.clkFalse.pin))
throw new SCTimingException("Pin "+arc.clkFalse.pin+" not found on test cell "+testCell.getName()+" for arc "+arc);
if (arc.clkFalse.transition != PinEdge.Transition.RISE && arc.clkFalse.transition != PinEdge.Transition.FALL) {
throw new SCTimingException("Clock pin "+arc.clkFalse.pin+" transition must be RISE or FALL for arc "+arc);
}
ports.remove(arc.clkFalse.pin);
}
ports.remove(arc.output.pin);
ports.remove(arc.input.pin);
for (String s : globalNets) ports.remove(s);
for (String s : arc.unusedOutputs) ports.remove(s);
if (ports.size() > 0) {
StringBuffer pins = new StringBuffer();
for (String s : ports) pins.append(s+" ");
throw new SCTimingException("Pins "+pins+" on test cell "+testCell.getName()+" not specified on arc "+arc);
}
// check that input, output, and stable transitions are specified correctly
if (arc.input.transition != PinEdge.Transition.RISE && arc.input.transition != PinEdge.Transition.FALL) {
throw new SCTimingException("Input pin "+arc.input.pin+" transition must be RISE or FALL for arc "+arc);
}
if (arc.output.transition != PinEdge.Transition.RISE && arc.output.transition != PinEdge.Transition.FALL) {
throw new SCTimingException("Output pin "+arc.output.pin+" transition must be RISE or FALL for arc "+arc);
}
for (PinEdge p : arc.stableInputs) {
if (!isStable(p.transition)) {
throw new SCTimingException("Stable Input pin "+p.pin+" transition must be STABLE0 or STABLE1 for arc "+arc);
}
}
}
private static void printColumnMeanStdDev(Table2D data2d, String key, PrintStream msg, double scaleFactor, String units) {
double [] colIndexVals = data2d.getColIndexVals();
for (int i=0; i<colIndexVals.length; i++) {
double [] colVals = data2d.getColumnValues(key, i);
if (colVals == null) {
System.out.println("Cannot find col vals for key: "+key);
return;
}
double avg = Table2D.getAverage(colVals) * scaleFactor;
double stddev = Table2D.getStandardDeviation(colVals) * scaleFactor;
double stddevp = stddev/avg*100;
msg.println(key+" for "+data2d.getColName()+"="+colIndexVals[i]+": mean="+
TextUtils.formatDouble(avg,4)+units+" stddev="+
TextUtils.formatDouble(stddev,4)+units+" stddev%="+
TextUtils.formatDouble(stddevp,4)+"%");
}
}
private static void printRowMeanStdDev(Table2D data2d, String key, PrintStream msg, double scaleFactor, String units) {
double [] rowIndexVals = data2d.getRowIndexVals();
for (int i=0; i<rowIndexVals.length; i++) {
double [] rowVals = data2d.getRowValues(key, i);
if (rowVals == null) {
System.out.println("Cannot find row vals for key: "+key);
return;
}
double avg = Table2D.getAverage(rowVals) * scaleFactor;
double stddev = Table2D.getStandardDeviation(rowVals) * scaleFactor;
double stddevp = stddev/avg*100;
msg.println(key+" for "+data2d.getRowName()+"="+rowIndexVals[i]+": mean="+
TextUtils.formatDouble(avg,4)+units+" stddev="+
TextUtils.formatDouble(stddev,4)+units+" stddev%="+
TextUtils.formatDouble(stddevp,4)+"%");
}
}
private static void printMeanStdDev(Table2D data2d, String key, PrintStream msg, double scaleFactor, String units) {
double [][] values = data2d.getValues(key);
if (values == null) {
System.out.println("Cannot find values for key: "+key);
return;
}
double avg = Table2D.getAverage(data2d.getValues(key)) * scaleFactor;
double stddev = Table2D.getStandardDeviation(data2d.getValues(key)) * scaleFactor;
double stddevp = stddev/avg*100;
msg.println(key+": mean="+
TextUtils.formatDouble(avg,4)+units+" stddev="+
TextUtils.formatDouble(stddev,4)+units+" stddev%="+
TextUtils.formatDouble(stddevp,4)+"%");
}
private static void print2DMeanStdDev(Table2D data2d, String key, PrintStream msg, double scaleFactor, String units) {
double [] rowIndexVals = data2d.getRowIndexVals();
double [] colIndexVals = data2d.getColIndexVals();
double [][] values = data2d.getValues(key);
double [][] stddev = data2d.getValues(key+"_stddev");
if (values == null) {
System.out.println("Cannot find values for key: "+key);
return;
}
if (stddev == null) {
System.out.println("Cannot find values for key: "+key+"_stddev");
}
for (int r=0; r<rowIndexVals.length; r++) {
for (int c=0; c<colIndexVals.length; c++) {
double avg = values[r][c] * scaleFactor;
double dev = stddev == null ? 0 : stddev[r][c] * scaleFactor;
double devp = stddev == null ? 0 : dev/avg*100;
msg.println(key+" for "+data2d.getRowName()+"="+rowIndexVals[r]+", "+
data2d.getColName()+"="+colIndexVals[c]+": mean="+
TextUtils.formatDouble(avg,4)+units+" stddev="+
TextUtils.formatDouble(dev,4)+units+" stddev%="+
TextUtils.formatDouble(devp,4)+"%");
}
}
}
static boolean isStable(PinEdge.Transition tran) {
if (tran == PinEdge.Transition.STABLE0 || tran == PinEdge.Transition.STABLE1 ||
tran == PinEdge.Transition.STABLEV)
return true;
return false;
}
void err(boolean print, String msg) throws SCTimingException {
if (print) {
//System.out.println("SCTiming: Error: "+msg);
throw new SCTimingException(msg);
}
}
/* *******************************************************
* Liberty file data
* *******************************************************/
/**
* Get the characeterized cell's data. It is added to the library group specified.
* @param library the library group to add it to
* @return the cell group data the cell group created
*/
public LibData.Group getCellLibData(LibData.Group library) {
if (characterizationFailed) return null;
String libertyCellName = topCellNameLiberty;
if (libertyCellName == null)
libertyCellName = topCellName;
List<LibData.Group> groups = library.getGroups("cell", libertyCellName);
if (groups.size() > 0) {
System.out.println("Warning, cell for "+libertyCellName+" already present in liberty data, replacing it with new data");
for (LibData.Group g : groups) {
library.removeGroup(g);
}
}
LibData.Group cell = new LibData.Group("cell", libertyCellName, library);
if (interfaceTiming) {
cell.putAttribute("timing_model_type", "abstracted");
}
// Make pin group for each pin, and each timing group for a pin+related pin combo
Map<String,LibData.Group> pinMap = new HashMap<String,LibData.Group>();
Map<String,LibData.Group> pinTimingMap = new HashMap<String,LibData.Group>();
List<String> ports = new ArrayList<String>();
Netlist netlist = topCell.getNetlist(Netlist.ShortResistors.ALL);
for (Iterator<Export> it = topCell.getExports(); it.hasNext(); ) {
Export ex = it.next();
String name = netlist.getNetwork(ex, 0).getName();
if (!ports.contains(name)) ports.add(name);
}
// make pin groups
//for (String s : dutSubckt.getPorts()) {
HashMap<String,Integer> pinTypes = new HashMap<String,Integer>();
for (String s : ports) {
if (netlistReader != null &&
netlistReader.getGlobalNets().contains(s)) continue;
LibData.Group pin = new LibData.Group("pin", s, cell);
pinMap.put(s, pin);
int type = 0; // 0 in, 1 out, 2 clk
// find type
for (Arc arc : timingArcs) {
if (arc.input.pin.equalsIgnoreCase(s)) {
break;
}
else if (arc.output.pin.equalsIgnoreCase(s)) {
type = 1; break;
}
else if (arc.clk != null && arc.clk.pin.equalsIgnoreCase(s)) {
type = 2; break;
}
}
pinTypes.put(s, type);
if (type == 1) {
pin.putAttribute("direction", "output");
String function = combinationalFunctions.get(s);
if (function != null) {
function = "\""+function+"\"";
pin.putAttribute("function", function);
}
} else {
pin.putAttribute("direction", "input");
}
if (testCell != null) {
if (testCell.isScanIn(s))
pin.putAttribute("nextstate_type", "scan_in");
if (testCell.isScanEn(s))
pin.putAttribute("nextstate_type", "scan_enable");
if (functionFlipFlop != null && testCell.isScanOut(s))
pin.putAttribute("function", "i"+functionFlipFlop.getOutputPosPin());
if (functionFlipFlopSDRtoDDR != null && functionFlipFlopSDRtoDDR.getInputFall().equals(s))
pin.putAttribute("nextstate_type", "data");
if (functionFlipFlopSDRtoDDR != null && functionFlipFlopSDRtoDDR.getInputRise().equals(s))
pin.putAttribute("nextstate_type", "data");
if (functionFlipFlopDDRtoSDR != null && functionFlipFlopDDRtoSDR.getInput().equals(s))
pin.putAttribute("nextstate_type", "data");
}
if (type == 2) {
pin.putAttribute("clock", "true");
}
// set connection class - always universal?
pin.putAttribute("connection_class", "universal");
// put capacitance if input
// for now just average
// really should be relative to input buffer (slew) strength, though
// the Liberty format does not support that?
if (type == 0 || type == 2) {
double cap = 0;
int count = 0;
for (Arc arc : timingArcs) {
int col = getColumn(arc, s+"_cap");
if (col > 0) {
// valid capacitance measure found
cap += arc.data.getAverage(col);
count++;
}
}
if (count != 0)
pin.putAttribute("capacitance", cap/count/settings.capUnit);
}
}
// Now, make pin timing groups and put in timing data
for (Arc arc : timingArcs) {
String inputPin = arc.input.pin;
String outputPin = arc.output.pin;
LibData.Group inpin = pinMap.get(inputPin);
LibData.Group outpin = pinMap.get(outputPin);
if (arc.clk == null) {
// combinational cell, write out propogation delay and slew times
// on output pin
// key for timing group is pin plus related pin
String timingkey = arc.output.pin+"_"+arc.input.pin;
if (arc.dependentStableInputs.size() > 0) {
for (PinEdge p : arc.dependentStableInputs) {
timingkey = timingkey + "_"+
(p.transition == PinEdge.Transition.STABLE0 ? "!" : "") + p.pin;
}
}
LibData.Group timing = pinTimingMap.get(timingkey);
if (timing == null) {
timing = new LibData.Group("timing", "", outpin);
pinTimingMap.put(timingkey, timing);
timing.putAttribute("related_pin", inputPin);
String sense = "negative_unate";
if (arc.input.transition == arc.output.transition)
sense = "positive_unate";
timing.putAttribute("timing_sense", sense);
}
if (arc.dependentStableInputs.size() > 0) {
StringBuffer whenPins = new StringBuffer();
StringBuffer sdf = new StringBuffer();
for (PinEdge p : arc.dependentStableInputs) {
whenPins.append(p.transition == PinEdge.Transition.STABLE0 ? "!" : "");
whenPins.append(p.pin);
whenPins.append(" & ");
sdf.append(p.pin);
sdf.append(" == ");
sdf.append(p.transition == PinEdge.Transition.STABLE0 ? "1'B0" : "1'B1");
sdf.append(" & ");
}
whenPins.setLength(whenPins.length()-2);
sdf.setLength(sdf.length()-2);
timing.putAttribute("when", "\""+whenPins.toString().trim()+"\"");
timing.putAttribute("sdf_cond", "\""+sdf.toString().trim()+"\"");
}
if (noTiming) {
timing.putAttribute("intrinsic_rise", "0.02");
timing.putAttribute("intrinsic_fall", "0.02");
timing.putAttribute("rise_resistance", "0.01");
timing.putAttribute("fall_resistance", "0.01");
continue;
}
String delay = "cell_fall";
String trans = "fall_transition";
if (arc.output.transition == PinEdge.Transition.RISE) {
delay = "cell_rise";
trans = "rise_transition";
}
Table2D data2D = arc.data2d_inbuf_outload;
// propogation delay vs input slew & output load
LibData.Group delayg = new LibData.Group(delay, settings.tableSlewVsLoads, timing);
// index 1 is input slew, which is dependent on inbufStr
double [] inslew = data2D.getAvgRowValues(inputPin+"_slew");
// index 2 is output cap, which is dependent on output load
double [] outload = data2D.getAvgColumnValues(outputPin+"_cap");
String index_1 = toStringQuote(inslew, settings.timeUnit);
String index_2 = toStringQuote(outload, settings.capUnit);
// propogation delay
double [][] propdelay = data2D.getValues("prop_delay");
delayg.putAttributeComplex("index_1", index_1);
delayg.putAttributeComplex("index_2", index_2);
List<String> delays = new ArrayList<String>();
for (int i=0; i<propdelay.length; i++)
delays.add(toStringQuote(propdelay[i], settings.timeUnit));
delayg.putAttribute("values", delays);
// output slew vs input slew & output load
LibData.Group transg = new LibData.Group(trans, settings.tableSlewVsLoads, timing);
double [][] transtime = data2D.getValues(outputPin+"_slew");
transg.putAttributeComplex("index_1", index_1);
transg.putAttributeComplex("index_2", index_2);
List<String> ttimes = new ArrayList<String>();
for (int i=0; i<transtime.length; i++)
ttimes.add(toStringQuote(transtime[i], settings.timeUnit));
transg.putAttribute("values", ttimes);
}
else {
if (noTiming) continue;
String clkPin = arc.clk.pin;
String edge = "R";
if (arc.clk.transition == PinEdge.Transition.FALL) edge = "F";
// sequential cell
// First, write out setup times on input pin
// key for timing group is pin plus related pin
String intimingkeySetup = arc.input.pin+"_"+arc.clk.pin+edge+"_Setup";
String intimingkeyHold = arc.input.pin+"_"+arc.clk.pin+edge+"_Hold";
LibData.Group intimingSetup = pinTimingMap.get(intimingkeySetup);
LibData.Group intimingHold = pinTimingMap.get(intimingkeyHold);
if (intimingSetup == null) {
intimingSetup = new LibData.Group("timing", "", inpin);
pinTimingMap.put(intimingkeySetup, intimingSetup);
intimingSetup.putAttribute("related_pin", clkPin);
if (arc.clk.transition == PinEdge.Transition.FALL) {
intimingSetup.putAttribute("timing_type", "setup_falling");
} else {
intimingSetup.putAttribute("timing_type", "setup_rising");
}
}
if (intimingHold == null) {
intimingHold = new LibData.Group("timing", "", inpin);
pinTimingMap.put(intimingkeyHold, intimingHold);
intimingHold.putAttribute("related_pin", clkPin);
if (arc.clk.transition == PinEdge.Transition.FALL) {
intimingHold.putAttribute("timing_type", "hold_falling");
} else {
intimingHold.putAttribute("timing_type", "hold_rising");
}
}
Table2D data2D_clkbuf_outload = arc.data2d_clkbuf_outload;
Table2D data2D_inbuf_clkbuf = arc.data2d_inbuf_clkbuf;
String trans = "fall_constraint";
if (arc.input.transition == PinEdge.Transition.RISE) {
trans = "rise_constraint";
}
// setup time vs (input slew and clock slew)
LibData.Group setup = new LibData.Group(trans, settings.tableSetupHold, intimingSetup);
// hold time vs (input slew and clock slew)
LibData.Group hold = new LibData.Group(trans, settings.tableSetupHold, intimingHold);
// index 1 is input slew, which is dependent on inbufStr
double [] inslew = data2D_inbuf_clkbuf.getAvgRowValues(inputPin+"_slew");
setup.putAttributeComplex("index_1", toStringQuote(inslew, settings.timeUnit));
hold.putAttributeComplex("index_1", toStringQuote(inslew, settings.timeUnit));
// index 2 is clk slew, which is dependent on clkbufStr
double [] clkslew = data2D_inbuf_clkbuf.getAvgColumnValues(clkPin+"_slew");
if (clkslew.length > 1) {
setup.putAttributeComplex("index_2", toStringQuote(clkslew, settings.timeUnit));
hold.putAttributeComplex("index_2", toStringQuote(clkslew, settings.timeUnit));
}
// setup time
double [][] setups = data2D_inbuf_clkbuf.getValues(setupTimeName);
List<String> setupVals = new ArrayList<String>();
for (int i=0; i<setups.length; i++) {
setupVals.add(toStringQuote(setups[i], settings.timeUnit));
}
if (clkslew.length > 1)
setup.putAttribute("values", setupVals);
else
setup.putAttributeComplex("values", toStringQuote(setups[0], settings.timeUnit));
// hold time
double [][] holds = data2D_inbuf_clkbuf.getValues(holdTimeName);
List<String> holdVals = new ArrayList<String>();
for (int i=0; i<holds.length; i++) {
holdVals.add(toStringQuote(holds[i], settings.timeUnit));
}
if (clkslew.length > 1)
hold.putAttribute("values", holdVals);
else
hold.putAttributeComplex("values", toStringQuote(holds[0], settings.timeUnit));
// Second, write out clk2q times on output pin
// key for timing group is pin plus related pin
String outtimingkey = arc.output.pin+"_"+arc.clk.pin+edge;
LibData.Group outtiming = pinTimingMap.get(outtimingkey);
if (outtiming == null) {
outtiming = new LibData.Group("timing", "", outpin);
pinTimingMap.put(outtimingkey, outtiming);
outtiming.putAttribute("related_pin", clkPin);
outtiming.putAttribute("timing_sense", "non_unate");
PinEdge.Transition clkTrans = arc.clk.transition;
if (functionLatch != null) clkTrans = arc.clk.getOpposite().transition;
if (clkTrans == PinEdge.Transition.FALL) {
outtiming.putAttribute("timing_type", "falling_edge");
} else {
outtiming.putAttribute("timing_type", "rising_edge");
}
}
String clk2qname = "cell_fall";
String outtrans = "fall_transition";
if (arc.output.transition == PinEdge.Transition.RISE) {
clk2qname = "cell_rise";
outtrans = "rise_transition";
}
// Clock to Q vs (clock slew and output load)
LibData.Group clk2q = new LibData.Group(clk2qname, settings.tableClk2Q, outtiming);
// index 1 is clk slew
clkslew = data2D_clkbuf_outload.getAvgRowValues(clkPin+"_slew");
if (clkslew.length > 1)
clk2q.putAttributeComplex("index_1", toStringQuote(clkslew, settings.timeUnit));
// index 2 is output load
double [] outload = data2D_clkbuf_outload.getAvgColumnValues(outputPin+"_cap");
if (clkslew.length > 1)
clk2q.putAttributeComplex("index_2", toStringQuote(outload, settings.capUnit));
else
clk2q.putAttributeComplex("index_1", toStringQuote(outload, settings.capUnit));
// clock to q values
double [][] clk2qVals = data2D_clkbuf_outload.getValues(this.clk2q);
List<String> clk2qStrings = new ArrayList<String>();
for (int i=0; i<clk2qVals.length; i++) {
clk2qStrings.add(toStringQuote(clk2qVals[i], settings.timeUnit));
}
if (clkslew.length > 1)
clk2q.putAttribute("values", clk2qStrings);
else
clk2q.putAttributeComplex("values", toStringQuote(clk2qVals[0], settings.timeUnit));
// Output slew vs (clock slew and output load)
LibData.Group outslew = new LibData.Group(outtrans, settings.tableClk2Q, outtiming);
// index 1 is clk slew
if (clkslew.length > 1)
outslew.putAttributeComplex("index_1", toStringQuote(clkslew, settings.timeUnit));
// index 2 is output load
if (clkslew.length > 1)
outslew.putAttributeComplex("index_2", toStringQuote(outload, settings.capUnit));
else
outslew.putAttributeComplex("index_1", toStringQuote(outload, settings.capUnit));
// output slew times
double [][] outslewVals = data2D_clkbuf_outload.getValues(outputPin+"_slew");
List<String> outslewStrings = new ArrayList<String>();
for (int i=0; i<outslewVals.length; i++) {
outslewStrings.add(toStringQuote(outslewVals[i], settings.timeUnit));
}
if (clkslew.length > 1)
outslew.putAttribute("values", outslewStrings);
else
outslew.putAttributeComplex("values", toStringQuote(outslewVals[0], settings.timeUnit));
// make ff group for flip flop
String ffname = "i"+outputPin+", i"+outputPin+"b";
List<LibData.Group> ffs = cell.getGroups("ff", ffname);
if (ffs.size() == 0) {
}
}
}
if (functionFlipFlop != null) {
String ffname = "i"+functionFlipFlop.getOutputPosPin()+", i"+functionFlipFlop.getOutputNegPin();
LibData.Group ff = new LibData.Group("ff", ffname, cell);
if (testCell != null) {
ff.putAttribute("next_state", "\"("+testCell.getScanEnPin()+"&"+testCell.getScanInPin()+")|(!"+
testCell.getScanEnPin()+"&"+functionFlipFlop.getInputPin()+")\" ");
} else
ff.putAttribute("next_state", functionFlipFlop.getInputPin());
ff.putAttribute("clocked_on", functionFlipFlop.getClockedOnPin());
}
if (functionFlipFlopSDRtoDDR != null) {
String ffname = "i"+functionFlipFlopSDRtoDDR.getOutputPos()+", i"+functionFlipFlopSDRtoDDR.getOutputNeg();
LibData.Group ff = new LibData.Group("ff", ffname, cell);
if (testCell != null) {
ff.putAttribute("next_state", "\"("+testCell.getScanEnPin()+"&"+testCell.getScanInPin()+")|(!"+
testCell.getScanEnPin()+"&("+functionFlipFlopSDRtoDDR.getInputRise()+"|"+
functionFlipFlopSDRtoDDR.getInputFall()+"))\" ");
} else
ff.putAttribute("next_state", "\""+ functionFlipFlopSDRtoDDR.getInputRise()+"|"+
functionFlipFlopSDRtoDDR.getInputFall()+"\" ");
ff.putAttribute("clocked_on", functionFlipFlopSDRtoDDR.getClockedOnPin());
}
if (functionFlipFlopDDRtoSDR != null) {
String ffname = "i"+functionFlipFlopDDRtoSDR.getOutputRisePos()+", i"+functionFlipFlopDDRtoSDR.getOutputRiseNeg();
LibData.Group ff = new LibData.Group("ff", ffname, cell);
if (testCell != null) {
ff.putAttribute("next_state", "\"("+testCell.getScanEnPin()+"&"+testCell.getScanInPin()+")|(!"+
testCell.getScanEnPin()+"&"+functionFlipFlopDDRtoSDR.getInput()+")\" ");
} else
ff.putAttribute("next_state", functionFlipFlopDDRtoSDR.getInput());
ff.putAttribute("clocked_on", functionFlipFlopDDRtoSDR.getClockedOnPin());
}
if (testCell != null) {
LibData.Group tcell = new LibData.Group("test_cell", "", cell);
if (functionFlipFlop != null) {
String ffname = "i"+functionFlipFlop.getOutputPosPin()+", i"+functionFlipFlop.getOutputNegPin();
LibData.Group ff = new LibData.Group("ff", ffname, tcell);
ff.putAttribute("next_state", functionFlipFlop.getInputPin());
ff.putAttribute("clocked_on", functionFlipFlop.getClockedOnPin());
}
for (String s : ports) {
if (netlistReader != null &&
netlistReader.getGlobalNets().contains(s)) continue;
LibData.Group pin = new LibData.Group("pin", s, tcell);
Integer type = pinTypes.get(s);
if (type != null) {
if (type.intValue() == 0 || type.intValue() == 2)
pin.putAttribute("direction", "input");
if (type.intValue() == 1)
pin.putAttribute("direction", "output");
}
if (testCell.isScanIn(s))
pin.putAttribute("signal_type", "test_scan_in");
if (testCell.isScanOut(s)) {
pin.putAttribute("signal_type", "test_scan_out");
pin.putAttribute("function", "i"+functionFlipFlop.getOutputPosPin());
pin.putAttribute("test_output_only", "true");
}
if (testCell.isScanEn(s))
pin.putAttribute("signal_type", "test_scan_enable");
if (functionFlipFlop != null) {
if (s.equals(functionFlipFlop.getOutputPosPin()))
pin.putAttribute("function", "i"+functionFlipFlop.getOutputPosPin());
if (s.equals(functionFlipFlop.getOutputNegPin()))
pin.putAttribute("function", "i"+functionFlipFlop.getOutputNegPin());
}
}
}
if (functionLatch != null && interfaceTiming == false) {
String ffname = "i"+functionLatch.getOutputPosPin()+", i"+functionLatch.getOutputNegPin();
LibData.Group ff = new LibData.Group("latch", ffname, cell);
ff.putAttribute("data_in", functionLatch.getInputPin());
ff.putAttribute("enable", functionLatch.getEnablePin());
}
return cell;
}
private static class FlipFlopFunction {
private String outputPos;
private String outputNeg;
private String inputPin;
private String clockedOnPin;
private boolean ddr = false;
public FlipFlopFunction(String outputPin, String outputNegPin, String inputPin, String clockedOnPin, boolean ddr) {
this.outputPos = outputPin;
this.outputNeg = outputNegPin;
this.inputPin = inputPin;
this.clockedOnPin = clockedOnPin;
this.ddr = ddr;
}
public String getOutputPosPin() { return outputPos; }
public String getOutputNegPin() { return outputNeg; }
public String getInputPin() { return inputPin; }
public String getClockedOnPin() { return clockedOnPin; }
public boolean isDDR() { return ddr; }
}
private static class FlipFlopFunctionSDRtoDDR {
private String outputPos;
private String outputNeg;
private String inputRise;
private String inputFall;
private String clockedOnPin;
public FlipFlopFunctionSDRtoDDR(String outputPos, String outputNeg,
String inputRise, String inputFall, String clockedOnPin) {
this.outputPos = outputPos;
this.outputNeg = outputNeg;
this.inputRise = inputRise;
this.inputFall = inputFall;
this.clockedOnPin = clockedOnPin;
}
public String getOutputPos() { return outputPos; }
public String getOutputNeg() { return outputNeg; }
public String getInputRise() { return inputRise; }
public String getInputFall() { return inputFall; }
public String getClockedOnPin() { return clockedOnPin; }
}
private static class FlipFlopFunctionDDRtoSDR {
private String outputRisePos;
private String outputRiseNeg;
private String outputFallPos;
private String outputFallNeg;
private String input;
private String clockedOnPin;
public FlipFlopFunctionDDRtoSDR(String outputRisePos, String outputRiseNeg, String outputFallPos, String outputFallNeg,
String input, String clockedOnPin) {
this.outputRisePos = outputRisePos;
this.outputRiseNeg = outputRiseNeg;
this.outputFallPos = outputFallPos;
this.outputFallNeg = outputFallNeg;
this.input = input;
this.clockedOnPin = clockedOnPin;
}
public FlipFlopFunctionDDRtoSDR(String outputRise, String outputFall, String input, String clockedOnPin) {
this(outputRise, outputRise+"_n", outputFall, outputFall+"_n", input, clockedOnPin);
}
public String getOutputRisePos() { return outputRisePos; }
public String getOutputRiseNeg() { return outputRiseNeg; }
public String getOutputFallPos() { return outputFallPos; }
public String getOutputFallNeg() { return outputFallNeg; }
public String getInput() { return input; }
public String getClockedOnPin() { return clockedOnPin; }
}
private static class LatchFunction {
private String outputPos;
private String outputNeg;
private String inputPin;
private String enablePin;
public LatchFunction(String outputPin, String outputNegPin, String inputPin, String enablePin) {
this.outputPos = outputPin;
this.outputNeg = outputNegPin;
this.inputPin = inputPin;
this.enablePin = enablePin;
}
public String getOutputPosPin() { return outputPos; }
public String getOutputNegPin() { return outputNeg; }
public String getInputPin() { return inputPin; }
public String getEnablePin() { return enablePin; }
}
private static class TestCell {
private String scanInPin;
private String scanOutPin;
private String scanEnPin;
public TestCell(String scanInPin, String scanOutPin, String scanEnPin) {
this.scanInPin = scanInPin;
this.scanOutPin = scanOutPin;
this.scanEnPin = scanEnPin;
}
public String getScanInPin() { return scanInPin; }
public String getScanOutPin() { return scanOutPin; }
public String getScanEnPin() { return scanEnPin; }
public boolean isScanIn(String s) { return (scanInPin != null) && s.equals(scanInPin); }
public boolean isScanOut(String s) { return (scanOutPin != null) && s.equals(scanOutPin); }
public boolean isScanEn(String s) { return (scanEnPin != null) && s.equals(scanEnPin); }
}
private String toStringQuote(double val, double scale) {
double [] a = {val};
return toStringQuote(a, scale);
}
private String toStringQuote(double [] vals, double scale) {
StringBuffer buf = new StringBuffer("\"");
for (double d : vals) {
buf.append(TextUtils.formatDouble(d/scale, 5)); buf.append(" ");
}
buf.append("\"");
return buf.toString();
}
private int getColumn(Arc arc, String name) {
if (arc.data == null) return -1;
return arc.data.getColumn(name);
}
}