/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: SimulationModel.java * Written by Jonathan Gainsley, Sun Microsystems. * * Copyright (c) 2004 Sun Microsystems and Static Free Software * * Electric(tm) is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Electric(tm) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Electric(tm); see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, Mass 02111-1307, USA. */ package com.sun.electric.tool.simulation.test; import java.util.List; import java.util.ArrayList; import java.util.Iterator; import java.io.*; /** * Defines a model that replaces the actual chip (device under test). This software * model can simulate the behavior of the actual chip. * <P> * For the most part, this allows you to create devices that replace the * measurement devices that would normally probe a Device Under Test. Instead * these devices will now probe the software model. * <P> * Most of the process control code has been moved into this Class, because * it is fairly similar between the various simulators. */ public abstract class SimulationModel implements ChipModel { /** if using simulation model, disable all GPIB devices */ private static boolean inUse = false; private static List models = new ArrayList(); /** The simulator name */ protected final String simulatorName; /** The simulator's interactive quit command (used when processing output) */ protected final String quitCommand; /** The simulator's error string on the interactive prompt when an error occurs */ protected final String errorFlag; /** The simulator's interactive prompt (used when processing output) */ private String outputPrompt; /** The simulator's interactive Error message */ /** The Verilog Process. Null if not started, or terminated due to error */ private ExecProcess process; /** The output log file */ private PrintWriter logfile; private String logfileName = null; /** A pipe connected to the output of the process that allows us to read the output */ private BufferedReader processReader; /** Stores the output from Verilog for the last command issued */ private StringBuffer lastCommandOutput; /** Current interactive command number */ private int currentCommand; /** see now long simulation took */ private long startTime; /** any additional command line arguments */ private String additionalCommandLineArgs = ""; // optimized reads and writes for bypassScanning mode private boolean optimizedDirectReadsWrites = false; private boolean suppressErrorMsgs = false; private int numIssueCommandErrors = 0; /** true if any assertions failed */ private boolean assertionFailed = false; /** * Enable or disable bypassing the scan chain. If true, instead of * scanning in bits serially and applying the data on "write", * data is applied directly to the data bits, without a need * for scanning in the bits. Note that this means no * data was scanned in, so no data was scanned out. I.e., * if you need to "read", you should not use this mode. * <P> * Read and write are not applied in this mode. The jtag controller * is not used in this mode. Only bits who have their "dataNet" * property set in the XML file will have data applied. * <P> * The state of this flag must not change during simulation, * otherwise undesirable behavior will occur. */ private boolean bypassScanning = false; /** * Default constructor. Should be called from extending classes. * Sets flag to disable GPIB devices. * @param simulatorName the name of this simulator (Verilog, Nanosim, etc) * @param quitCommand the command used to quit the simulator ($finish;, quit, etc) * @param errorFlag the error String found in the simulator's output on error * @param outputPrompt the prompt from the interactive process. If this contains %%, * then %% will be replaced with the current command count. This can also be set * later with setPrompt() */ public SimulationModel(String simulatorName, String quitCommand, String errorFlag, String outputPrompt) { this.simulatorName = simulatorName; this.quitCommand = quitCommand; this.errorFlag = errorFlag; this.outputPrompt = outputPrompt; process = null; logfile = null; logfileName = null; processReader = null; currentCommand = 1; lastCommandOutput = null; setInUse(true); models.add(this); startTime = System.currentTimeMillis(); } /** * Create a {@link JtagTester} that can be used to drive the JtagController on the * Software model of the chip. The arguments specify the port names specific * to the software model that correspond to the Jtag Controller ports. * @param tckName name of the input port for TCK * @param tmsName name of the input port for TMS * @param trstbName name of the input port for TRSTb * @param tdiName name of the input port for TDI * @param tdobName name of the input port for TDOb * @return a JtagTester than can be used to test the chip */ public abstract JtagTester createJtagTester(String tckName, String tmsName, String trstbName, String tdiName, String tdobName); /** * Create a {@link JtagTester} that can be used to drive the JtagController on the * Software model of the chip. This uses the default port names of * TCK, TMS, TRSTb, TDI, and TDOb. * @return a JtagTester than can be used to test the chip */ public JtagTester createJtagTester() { return createJtagTester("TCK", "TMS", "TRSTb", "TDI", "TDOb"); } /** * Create a subchain tester based on the 8- or 9-wire jtag interface. * jtag[8:0] = {scan_data_return, phi2_return, phi1_return, rd, wr, phi1, phi2, sin, mc*} * Note that mc is not present on older designs, so they are jtag[8:1]. * @param jtagInBus the name of the 9-bit wide input bus, i.e. "jtagIn" or "jtagIn[8:0]" * @param jtagOutBus the name of the 9-bit wide output bus, i.e. "jtagOut" or "jtagOut[8:0]" */ public abstract JtagTester createJtagSubchainTester(String jtagInBus, String jtagOutBus); /** * Create a subchain tester based on the 5-wire jtag interface. * @param phi2 name of the phi2 signal * @param phi1 name of the phi1 signal * @param write name of the write signal * @param read name of the read signal * @param sin name of the scan data in signal * @param sout name of the scan data out signal */ public abstract JtagTester createJtagSubchainTester(String phi2, String phi1, String write, String read, String sin, String sout); /** * Create a {@link LogicSettable} that can be used to control a port on * the Software model of the chip. * @param portName the name of the port to control. * @return a LogicSettable tied to the given port. */ public abstract LogicSettable createLogicSettable(String portName); /** * Create a {@link LogicSettable} that can be used to control a set of ports on * the Software model of the chip. The ports then act as if they have * been tied together. * @param portNames a list of Strings of port names to be controlled. * @return a LogicSettable tied to the given port. */ public abstract LogicSettable createLogicSettable(List portNames); /** * Force node to a state. Note that 1 is high and 0 is low. * The node is * case-insensitive, and may be a hierarchical spice name, such as 'Xtop.Xfoo.net@12'. * It should match the name from the spice file that nanosim is simulating. * @param node the hierarchical spice node name * @param state the state to set to, must be 1 or 0. */ public abstract void setNodeState(String node, int state); /** * Get the state of a node. Returns 1 or 0. * The node is * case-insensitive, and may be a hierarchical spice name, such as 'Xtop.Xfoo.net@12'. * It should match the name from the spice file that nanosim is simulating. * May return -1 if not a valid state. * @param node the hierarchical spice node name */ public abstract int getNodeState(String node); /** * Release any nodes being forced to a value using set node state * @param nodes a list of node names (strings) */ public abstract void releaseNodes(List nodes); /** * Get the voltage value for vdd * @return the voltage value for vdd */ protected abstract double getVdd(); /** * Get the current simulation time, used for error reporting * @return the current simulation time as tracked by the underlying simulation model */ protected abstract double getSimulationTime(); /** * Set a bus of nodes to a state. See setNodeState(String node, int state). * @param bus the bus * @param state the state to set the bus to */ public void setNodeState(BussedIO bus, BitVector state) { for (int i=0; i<bus.getWidth(); i++) { setNodeState(bus.getSignal(i), state.get(i)?1:0); } } /** * Get the state of a bus. Set getNodeState(String node). * @param bus the bus * @return the state of the bus. */ public BitVector getNodeState(BussedIO bus) { BitVector state = new BitVector(bus.getWidth(), bus.getName()); for (int i=0; i<bus.getWidth(); i++) { int b = getNodeState(bus.getSignal(i)); if (b == 1) state.set(i); if (b == 0) state.clear(i); } return state; } /** * Assert that a node is at a given state. If not, * an error message will be printed, and the * simulation will continue on. The exit value * of the process will be non-zero if any assertions fail. * @param node the hierarchical node name * @param expectedState the expected state, 1 is high and 0 is low. */ public void assertNodeState(String node, int expectedState) { assertNodeState(node, expectedState, ""); } /** * Assert that a node is at a given state. If not, * an error message will be printed, and the * simulation will continue on. The exit value * of the process will be non-zero if any assertions fail. * @param node the hierarchical node name * @param expectedState the expected state, 1 is high and 0 is low. * @param errMsg additional error message */ public void assertNodeState(String node, int expectedState, String errMsg) { int value = getNodeState(node); if (value != expectedState) { System.out.println("Assertion failed for node \""+node+"\", state was "+ value+" (expected: "+expectedState+") "+errMsg); assertionFailed = true; } } /** * Set any additional command line arguments that will be appended * to the simulation command when run * @param args the args to append */ public void setAdditionalCommandLineArgs(String args) { additionalCommandLineArgs = args; } /** * Start the Chip Model simulation, and have it ready to accept * commands from the test software. * @param command the command to start the simulation. * @param simFile the file to simulate. * @param recordSim the level of simulation recording used. */ public void start(String command, String simFile, int recordSim) { start(command, simFile, recordSim, false); } /** * Start the Chip Model simulation, and have it ready to accept * commands from the test software. * @param command the command to start the simulation. * @param simFile the file to simulate. * @param recordSim the level of simulation recording used. * @param bypassScanning true to bypass scanning in of data. See {@link #bypassScanning}. */ public void start(String command, String simFile, int recordSim, boolean bypassScanning) { this.bypassScanning = bypassScanning; if (!start_(command, simFile, recordSim)) Infrastructure.exit(1); } abstract boolean start_(String command, String simFile, int recordSim); /** * Tell the Chip Model simulation to continue simulating for the * time specified * @param seconds number of seconds to continue simulating */ public abstract void wait(float seconds); public abstract void waitNS(double nanoseconds); public abstract void waitPS(double picoseconds); /** * Return the current simulation time in nanoseconds * @return current simulation time in nanoseconds */ public abstract double getTimeNS(); /** * If supported, disable a node (forces it to 0) * @param node the name of the node */ public abstract void disableNode(String node); /** * If supported, enable a node (allows it to be driven) * @param node the name of the node */ public abstract void enableNode(String node); public void setBypassScanning(boolean enabled) { bypassScanning = enabled; } /** * Enable optimized direct reads and writes. This only has effect * if bypass scanning mode is enabled. With this enabled, the test * code keeps track of the state of shadow registers in the * scan chain elements, and does not perform a read if it already * knows what will be read, and does not perform a write if it * knows the current state matches the state to write. * @param enabled true to enable optimized direct reads and writes */ public void setOptimizedDirectReadsWrites(boolean enabled) { optimizedDirectReadsWrites = enabled; } /** * See {@link #setOptimizedDirectReadsWrites(boolean)} */ public boolean getOptimizedDirectReadsWrites() { return optimizedDirectReadsWrites; } /** * Check the state of bypass scanning. * * See {@link SimulationModel#bypassScanning}. * @return true if enabled, false if not. */ public boolean isBypassScanning() { return bypassScanning; } /** * Tell any Chip Model simulators currently running to wait * for the specified time. If any simulators are running, * return true. * @param seconds the time to wait. * @return true if any simulators were active. */ public static boolean waitSeconds(float seconds) { if (!isInUse()) return false; for (Iterator it = models.iterator(); it.hasNext(); ) { SimulationModel cm = (SimulationModel)it.next(); cm.wait(seconds); } return true; } /** * Tell the Chip Model simulation to end, and stop accepting * input from the test software. */ public void finish() { long time = System.currentTimeMillis() - startTime; System.out.println("Simulation took "+getElapsedTime(time)); issueCommand(quitCommand); if (assertionFailed) Infrastructure.exit(1); } /** * Terminate all models. Used when program encounters error * and needs to exit. */ public static void finishAll() { for (Iterator it = models.iterator(); it.hasNext(); ) { SimulationModel cm = (SimulationModel)it.next(); cm.finish(); } } /** * Set whether the chip model is in use. * @param e */ private static synchronized void setInUse(boolean e) { inUse = e; } /** * Get whether or not a chip model is in use. * @return if the chip model is in use */ static synchronized boolean isInUse() { return inUse; } /** * Method to describe a time value as a String. * @param milliseconds the time span in milli-seconds. * @return a String describing the time span with the * format: days : hours : minutes : seconds */ public static String getElapsedTime(long milliseconds) { if (milliseconds < 60000) { // less than a minute: show fractions of a second return (milliseconds / 1000.0) + " secs"; } StringBuffer buf = new StringBuffer(); int seconds = (int)milliseconds/1000; if (seconds < 0) seconds = 0; int days = seconds/86400; if (days > 0) buf.append(days + " days, "); seconds = seconds - (days*86400); int hours = seconds/3600; if (hours > 0) buf.append(hours + " hrs, "); seconds = seconds - (hours*3600); int minutes = seconds/60; if (minutes > 0) buf.append(minutes + " mins, "); seconds = seconds - (minutes*60); buf.append(seconds + " secs"); return buf.toString(); } // ==================================================================== // // Process Control // // ==================================================================== private static final boolean DEBUGPROCESS = false; // general debugging messages private static final boolean DEBUGOUTPUT = false; // output parsing debug messages /** * Start a simulator's process. * @param command the command to start the process * @param envVars environment variables, or null if none * @param dir working dir, null for current * @param logfileName name of log file of interaction with process * @return true if process started, false otherwise */ protected boolean startProcess(String command, String [] envVars, File dir, String logfileName) { // setup the log file FileOutputStream fout = null; try { fout = new FileOutputStream(logfileName, false); logfile = new PrintWriter(fout); this.logfileName = logfileName; } catch (FileNotFoundException e) { System.out.println(e.getMessage()); suppressErrorMsgs = true; return false; } // setup the reader which will read the output of the process PipedOutputStream ostream = new PipedOutputStream(); try { PipedInputStream istream = new PipedInputStream(ostream); processReader = new BufferedReader(new InputStreamReader(istream)); } catch (IOException e) { System.out.println("Unable to create pipe to process output: "+e.getMessage()); suppressErrorMsgs = true; return false; } // setup and start the process if (additionalCommandLineArgs.length() > 0) command = command + " " + additionalCommandLineArgs; process = new ExecProcess(command, null, null, ostream, ostream); System.out.println(" Starting process: "+command); if (process == null) { suppressErrorMsgs = true; return false; } long startTime = System.currentTimeMillis(); process.start(); // wait for process to start before returning if (!readProcessOutputUntilReady()) { process = null; return false; } long duration = System.currentTimeMillis() - startTime; if (duration > 60000) System.out.println(" external process is ready, took "+Infrastructure.getElapsedTime(duration)); return true; } /** * For children of this class to see if the process is currently running * @return true if it is, false if it is not running. */ protected boolean isProcessRunning() { return (process == null) ? false : true; } /** * Sets the simulator's interactive prompt. If there is a %% in the * String, it will be replaced by the current command number. * @param prompt the prompt */ protected void setPrompt(String prompt) { outputPrompt = prompt; } /** * Issues a command to the process. This method will block until the * process has finished processing the command, hit a $stop or breakpoint, and is * waiting for another interactive command. It will return if the process exits. * @param command the command to send to the process. */ protected void issueCommand(String command) { issueCommand(command, true); } /** * Issues a command to the process. This method will block until the * process has finished processing the command, hit a stop or breakpoint, and is * waiting for another interactive command. It will return if the process exits. * @param command the command to send to the process. * @param incrPrompt true if this command will increment the interactive prompt */ protected void issueCommand(String command, boolean incrPrompt) { if (!incrPrompt) currentCommand--; if (process == null) { if (!suppressErrorMsgs) { System.out.println("Error: command "+command+" issue when no "+simulatorName+" process running!"); numIssueCommandErrors++; if (numIssueCommandErrors > 5) { System.out.println("Warning: too many command issue errors, suppressing all remaining errors"); suppressErrorMsgs = true; } } return; } if (command == null) return; if (DEBUGPROCESS) System.out.println("Running command "+command); //if (command.indexOf("get_node_info") == -1) // System.out.println("Running command "+command); process.writeln(command); if (logfile != null) { logfile.println(command); logfile.flush(); } // don't worry about readProcessOutputUntilReady returning false on '$finish', // as process will just exit and the method will return false. if (command.equals(quitCommand)) { readProcessOutputUntilReady(); process = null; return; } // read output until simulator is ready. If there was an error, end simulation. if (!readProcessOutputUntilReady()) { process.writeln(quitCommand); readProcessOutputUntilReady(); Infrastructure.exit(1); } } /** * Get the output of Verilog caused by the last command issued to verilog. * @return a StringBuffer containing the output from the last command. */ StringBuffer getLastCommandOutput() { StringBuffer buf = new StringBuffer(); buf.append(lastCommandOutput); return buf; } /** * Read the output of the Verilog process until it is ready to receive more input. * This only works when running verilog in interactive mode, because this waits * for the next prompt. This blocks until the Verilog process is ready to read more input. * @return true if ready, false on error */ private final int bufsize = 256 * 1024 * 4; private char [] cbuf = new char[bufsize]; private boolean readProcessOutputUntilReady() { /** * Note this function is quite complicated because the prompt is not followed by a new line. * Therefore we cannot use readLine(), but must instead just use read(). Because the prompt * we are trying to match, "C# > " may be split across two reads, we need to concatenate bits * from both the previous and current read and check that as well. */ if (processReader == null) { return false; } boolean ready = true; // this will hold the some of the chars we read from the verilog process' stdout int offset = 0; int len = bufsize; int read = 0; StringBuffer lastString = new StringBuffer(); // make the entire set of chars sent to stdout from the command // available for devices to parse lastCommandOutput = new StringBuffer(); try { while ((read = processReader.read(cbuf, offset, len)) > 0) { if (logfile != null) { logfile.write(cbuf, offset, read); logfile.flush(); } lastCommandOutput.append(cbuf, offset, read); // grab the last line in the char buffer - we need to save this for the next read if (read > 0) { int i; for (i=read-1; i>=0; i--) { if (cbuf[i] == '\n') { lastString = new StringBuffer(); i++; // move past \n lastString.append(cbuf, i, read-i); if (DEBUGOUTPUT) System.out.println("Setting last line to: "+lastString); break; } } if (i < 0) { // no \n found, append this to last known line if (DEBUGOUTPUT) { StringBuffer sb = new StringBuffer(); sb.append(cbuf, 0, read); System.out.println("No end-line found, appending "+sb); } lastString.append(cbuf, 0, read); } } // error checking StringBuffer errbuf = new StringBuffer(); errbuf.append(cbuf, 0, read); if (errbuf.indexOf(errorFlag) != -1 || lastString.indexOf(errorFlag) != -1) { System.out.println("Error in "+simulatorName+" simulation, aborting. Please check log file "+logfileName); ready = false; // command # is not incremented on errors, so subtract. Unless this is an error // before the first command, in which case the command number remains 1. if (currentCommand > 1) currentCommand--; } // check if lastString matches input prompt String prompt = getPrompt(); if (lastString.toString().indexOf(prompt) != -1) { currentCommand++; if (DEBUGOUTPUT) System.out.println("Matched: "+lastString); return ready; } // sometimes process writes out info after writing // out the prompt (looks like threading issue), in // this case there is no last line (chars not ending in \n), // so we should check the entire thing for a prompt // this happens for nanosim sometimes //if (lastString.length() == 0) { if (lastCommandOutput.toString().indexOf(prompt) != -1) { currentCommand++; if (DEBUGOUTPUT) System.out.println("Matched: "+lastCommandOutput); return ready; } //} if (DEBUGOUTPUT) System.out.println("Last line read from process: "+lastString); } } catch (IOException e) { System.out.println(e.getMessage()); } return false; } private String getPrompt() { return outputPrompt.replaceAll("%%", String.valueOf(currentCommand)); } }