/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: EpicReaderProcess.java * Input/output tool: external reader for EPIC output (.out) * * Copyright (c) 2005 Sun Microsystems and Static Free Software * * Electric(tm) is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Electric(tm) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Electric(tm); see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, Mass 02111-1307, USA. */ package com.sun.electric.tool.io.input; import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Pattern; /** * This class is a launched in external JVM to read Epic output file. * It doesn't import other Electric modules. * Waveforms are stored in temporary file. * Signal names are passed to std out using the following syntax: * StdOut :== signames resolutions signalInfo* signalInfoEOF fileName * signames :== ( up | down | signal )* 'F' * up :== 'U' * down :== 'D' stringRef * signal :== ( 'V' | 'I' ) sigEpicNum stringRef * sigEpicNum ::== INT * stringRef= INT [ UTF ] * resolutions :== timeResolution voltageResolution currentResolution timeMax * timeResolution :== DOUBLE * voltageResolution :== DOUBLE * currentResolution :== DOUBLE * timeMax :== DOUBLE * signalInfo :== sigEpicNum minV maxV packedLength * minV :== INT * maxV :== INT * packedLength :== INT * signalInfoEOF :== -1 * fileName :== UDF * * Inititially current context is empty. * 'D' pushes a string to it. * 'U' pops a string. * 'V' and 'I' use the current context. * * stringRef starts with a number. If this number is a new number then it is followed by definition of this string, * otherwise it is reference of previously defined string. * Number of signalInfo is equal to number of defined signals. * fileName is a name of temporary file on local machine with packed waveform data. * length in signalInfo is a number of bytes occupied in the file by this signal. */ class EpicReaderProcess { /** Input stream with Epic data. */ private InputStream inputStream; /** File length of inputStream. */ private long fileLength; /** Number of bytes read from stream to buffer. */ private long byteCount; /** Buffer for parsing. */ private byte[] buf = new byte[65536]; /** Count of valid bytes in buffer. */ private int bufL; /** Count of parsed bytes in buffer. */ private int bufP; /** Count of parsed lines. */ private int lineNum; /** String builder to build input line. */ private StringBuilder builder = new StringBuilder(); /** Pattern used to split input line into pieces. */ private Pattern whiteSpace = Pattern.compile("[ \t]+"); /** Last value of progress indicater (percents(.*/ private byte lastProgress; /** ContextRoot */ private EpicReaderContext rootCtx = new EpicReaderContext(); /** A map from Strings to Integer ids. */ private HashMap<String,Integer> stringIds = new HashMap<String,Integer>(); /** Sparce list to access signals by their Epic indices. */ private ArrayList<EpicReaderSignal> signalsByEpicIndex = new ArrayList<EpicReaderSignal>(); /** Time resolution from input file. */ private double timeResolution; /** Voltage resolution from input file. */ private double voltageResolution; /** Current resolution from input file. */ private double currentResolution; /** Current time (in integer units). */ private int curTime = 0; /** A stack of signal context pieces. */ private ArrayList<String> contextStack = new ArrayList<String>(); /** Count of timepoints for statistics. */ private int timesC = 0; /** Count of signal events for statistics. */ private int eventsC = 0; /* DataOutputStream view of standard output. */ DataOutputStream stdOut = new DataOutputStream(System.out); /** Epic format we are able to read. */ private static final String VERSION_STRING50 = ";! output_format 5.0"; /** Epic format we are able to read. */ private static final String VERSION_STRING53 = ";! output_format 5.3"; /** Epic separator char. */ private static final char separator = '.'; /** Private constructor. */ private EpicReaderProcess() {} /** * Main program of external JVM for reading Epic files. */ public static void main(String args[]) { try { EpicReaderProcess process = new EpicReaderProcess(); try { process.readEpic(args[0]); } catch (IOException e) { System.err.println("Failed to read " + args[0]); e.printStackTrace(System.err); System.exit(1); } process.writeOut(); } catch (OutOfMemoryError e) { System.err.println("Out of memory. Increase memory limit in preferences."); e.printStackTrace(System.err); System.exit(2); } catch (Throwable e) { e.printStackTrace(System.err); System.exit(3); } } /** * Reads Epic file into memory. * Writes signal names to stdout. * @param urlName url name of Epic file. */ private void readEpic(String urlName) throws IOException { URL fileURL = new URL(urlName); long startTime = System.currentTimeMillis(); URLConnection urlCon = fileURL.openConnection(); urlCon.setConnectTimeout(10000); urlCon.setReadTimeout(1000); String contentLength = urlCon.getHeaderField("content-length"); fileLength = -1; try { fileLength = Long.parseLong(contentLength); } catch (Exception e) {} inputStream = urlCon.getInputStream(); byteCount = 0; String firstLine = getLine(); if (firstLine == null || !(firstLine.equals(VERSION_STRING50) || firstLine.equals(VERSION_STRING53))) { message("Unknown Epic Version: " + firstLine); } for (;;) { if (bufP >= bufL && readBuf()) break; int startLine = bufP; if (parseNumLineFast()) continue; bufP = startLine; String line = getLine(); assert bufP <= bufL; if (line == null) break; parseNumLine(line); } rootCtx.writeSigs(this); /* writeContext(""); */ inputStream.close(); long stopTime = System.currentTimeMillis(); int numSignals = 0; for (EpicReaderSignal s: signalsByEpicIndex) { if (s == null) continue; numSignals++; } System.err.println((stopTime - startTime)/1000.0 + " sec to read " + byteCount + " bytes " + numSignals + " signals (max " + (signalsByEpicIndex.size() - 1) + " ) "+ stringIds.size() + " strings " + timesC + " timepoints " + eventsC + " events from " + urlName); } /** * Parses line from Epic file. */ private void parseNumLine(String line) throws IOException { if (line.length() == 0) return; char ch = line.charAt(0); if (ch == '.') { String[] split = whiteSpace.split(line); if (split[0].equals(".index") && split.length == 4) { byte type; if (split[3].equals("v")) type = 'V'; else if (split[3].equals("i")) type = 'I'; else { message("Unknown waveform type: " + line); return; } String name = split[1]; int sigNum = atoi(split[2]); while (signalsByEpicIndex.size() <= sigNum) signalsByEpicIndex.add(null); EpicReaderSignal s = signalsByEpicIndex.get(sigNum); if (s == null) { s = new EpicReaderSignal(); signalsByEpicIndex.set(sigNum, s); } // name the signal if (name.startsWith("v(") && name.endsWith(")")) { name = name.substring(2, name.length() - 1); } else if (name.startsWith("i(") && name.endsWith(")")) { name = name.substring(2, name.length() - 1); } else if (name.startsWith("i1(") && name.endsWith(")")) { name = name.substring(3, name.length() - 1); } rootCtx.addSig(name, separator, type, sigNum); /* int lastSlashPos = name.lastIndexOf(separator); String contextName = ""; if (lastSlashPos > 0) { contextName = name.substring(0, lastSlashPos + 1); } name = name.substring(lastSlashPos + 1); if (type == 'I') name = "i(" + name + ")"; writeContext(contextName); stdOut.writeByte(type); stdOut.writeInt(sigNum); writeString(name); **/ } else if (split[0].equals(".vdd") && split.length == 2) { } else if (split[0].equals(".time_resolution") && split.length == 2) { timeResolution = atof(split[1]) * 1e-9; } else if (split[0].equals(".current_resolution") && split.length == 2) { currentResolution = atof(split[1]); } else if (split[0].equals(".voltage_resolution") && split.length == 2) { voltageResolution = atof(split[1]); } else if (split[0].equals(".simulation_time") && split.length == 2) { } else if (split[0].equals(".high_threshold") && split.length == 2) { } else if (split[0].equals(".low_threshold") && split.length == 2) { } else if (split[0].equals(".nnodes") && split.length == 2) { } else if (split[0].equals(".nelems") && split.length == 2) { } else if (split[0].equals(".extra_nodes") && split.length == 2) { } else if (split[0].equals(".bus_notation") && split.length == 4) { } else if (split[0].equals(".hier_separator") && split.length == 2) { } else if (split[0].equals(".case") && split.length == 2) { } else { message("Unrecognized Epic line: " + line); } } else if (ch >= '0' && ch <= '9') { String[] split = whiteSpace.split(line); int num = atoi(split[0]); if (split.length > 1) { putValue(num, atoi(split[1])); } else { putTime(num); } } else if (ch == ';' || Character.isSpaceChar(ch)) { } else { message("Unrecognized Epic line: " + line); } } /** * Writes new context as diff to previous context. * @param s string with new context. */ private void writeContext(String s) throws IOException { int matchSeps = 0; int pos = 0; matchLoop: while (matchSeps < contextStack.size()) { String si = contextStack.get(matchSeps); if (pos < s.length() && s.charAt(pos) == 'x') pos++; if (pos + si.length() >= s.length() || s.charAt(pos + si.length()) != separator) break; for (int k = 0; k < si.length(); k++) if (s.charAt(pos + k) != si.charAt(k)) break matchLoop; matchSeps++; pos += si.length() + 1; } while (contextStack.size() > matchSeps) { stdOut.writeByte('U'); contextStack.remove(contextStack.size() - 1); } assert contextStack.size() == matchSeps; while (pos < s.length()) { int indexOfSep = s.indexOf(separator, pos); assert indexOfSep >= pos; stdOut.writeByte('D'); if (pos < indexOfSep && s.charAt(pos) == 'x') pos++; String si = s.substring(pos, indexOfSep); writeString(si); contextStack.add(si); pos = indexOfSep + 1; } assert pos == s.length(); } /** * Writes string to stdOut. * It writes its chronological number. * It writes string itself, if it is a new string. * @param s string to write. */ void writeString(String s) throws IOException { if (s == null) { stdOut.writeInt(-1); return; } Integer i = stringIds.get(s); if (i != null) { stdOut.writeInt(i.intValue()); return; } stdOut.writeInt(stringIds.size()); i = new Integer(stringIds.size()); s = new String(s); // To avoid long char array of substrings stringIds.put(s, i); stdOut.writeUTF(s); } /** * Fast routine to parse a line from buffer. * It either recognizes a simple line or rejects. * @return true if this method recognized a line. */ private boolean parseNumLineFast() { final int MAX_DIGITS = 9; if (bufP + (MAX_DIGITS*2 + 4) >= bufL) return false; int ch = buf[bufP++]; if (ch < '0' || ch > '9') return false; int num1 = ch - '0'; ch = buf[bufP++]; for (int lim = bufP + (MAX_DIGITS - 1); '0' <= ch && ch <= '9' && bufP < lim; ch = buf[bufP++]) num1 = num1*10 + (ch - '0'); boolean twoNumbers = false; int num2 = 0; if (ch == ' ') { ch = buf[bufP++]; boolean sign = false; if (ch == '-') { sign = true; ch = buf[bufP++]; } if (ch < '0' || ch > '9') return false; num2 = ch - '0'; ch = buf[bufP++]; for (int lim = bufP + (MAX_DIGITS - 1); '0' <= ch && ch <= '9' && bufP < lim; ch = buf[bufP++]) num2 = num2*10 + (ch - '0'); if (sign) num2 = -num2; twoNumbers = true; } if (ch == '\n') { } else if (ch == '\r') { if (buf[bufP] == '\n') bufP++; } else { return false; } if (twoNumbers) putValue(num1, num2); else putTime(num1); lineNum++; return true; } /** * Sets new current time. * This current time will be used in PutBValue method. * @param time time as int value. */ private void putTime(int time) { timesC++; curTime = Math.max(curTime, time); } /** * Puts event into packed waveforms. * @param sigNum Epic signal index of signal. * @param value new value of signal. */ private void putValue(int sigNum, int value) { EpicReaderSignal s = signalsByEpicIndex.get(sigNum); if (s == null) { message("Signal " + sigNum + " not defined"); return; } s.putEvent(curTime, value); eventsC++; } /** * Gets new line from buffer. * @return String line read. */ private String getLine() throws IOException { builder.setLength(0); for (;;) { while (bufP < bufL) { int ch = buf[bufP++] & 0xff; if (ch == '\n') { return builder.toString(); } if (ch == '\r') { if (bufP == bufL) readBuf(); if (bufP < bufL && buf[bufP] == '\n') bufP++; return builder.toString(); } builder.append((char)ch); } if (readBuf()) { if (builder.length() == 0) return null; lineNum++; return builder.toString(); } } } /** * ASCII to double. * @param s ASCII string. * @return double value of string. */ private double atof(String s) { double value = 0; try { value = Double.parseDouble(s); } catch (NumberFormatException e) { message("Bad float format: " + s); } return value; } /** * ASCII to int. * @param s ASCII string. * @return int value of string. */ private int atoi(String s) { int value = 0; try { value = Integer.parseInt(s); } catch (NumberFormatException e) { message("Bad integer format: " + s); } return value; } /** * Reads buffer from Epic file. */ private boolean readBuf() throws IOException { assert bufP == bufL; bufP = bufL = 0; bufL = inputStream.read(buf, 0, buf.length); if (bufL <= 0) { bufL = 0; return true; } byteCount += bufL; showProgress(fileLength != 0 ? byteCount/(double)fileLength : 0); return false; } /** * Writes resolutions, signalInfo and fileName to stdOut. */ private void writeOut() throws IOException { File tempFile = null; BufferedOutputStream waveStream = null; boolean ok = false; try { long startTime = System.currentTimeMillis(); tempFile = File.createTempFile("elec", ".epic"); waveStream = new BufferedOutputStream(new FileOutputStream(tempFile)); showProgressNote("Writing " + tempFile); stdOut.writeByte('F'); stdOut.writeDouble(timeResolution); stdOut.writeDouble(voltageResolution); stdOut.writeDouble(currentResolution); stdOut.writeDouble(curTime*timeResolution); int size = 0; for (EpicReaderSignal s: signalsByEpicIndex) { if (s == null) continue; size += s.len; } int start = 0; for (int sigNum = 0; sigNum < signalsByEpicIndex.size(); sigNum++) { EpicReaderSignal s = signalsByEpicIndex.get(sigNum); if (s == null) continue; waveStream.write(s.waveform, 0, s.len); stdOut.writeInt(sigNum); stdOut.writeInt(s.minV); stdOut.writeInt(s.maxV); stdOut.writeInt(s.len); start += s.len; showProgress(size != 0 ? start/(double)size : 0); } stdOut.writeInt(-1); waveStream.close(); stdOut.writeUTF(tempFile.toString()); stdOut.close(); ok = true; long stopTime = System.currentTimeMillis(); System.err.println((stopTime - startTime)/1000.0 + " sec to write " + tempFile.length() + " bytes to " + tempFile); } finally { if (ok) return; if (waveStream != null) waveStream.close(); if (tempFile != null) tempFile.delete(); } } /** * Puts progress mark into stdErr. */ private void showProgress(double ratio) { byte progress = (byte)(ratio*100); if (progress == lastProgress) return; System.err.println("**PROGRESS " + progress); lastProgress = progress; } /** * Puts progress not mark into stdErr. */ private void showProgressNote(String note) { System.err.println("**PROGRESS !" + note); lastProgress = 0; } /** * Puts message int stdErr. */ private void message(String s) { System.err.println(s + " in line " + (lineNum + 1)); } } class EpicReaderContext { private ArrayList<EpicReaderSig> signals = new ArrayList<EpicReaderSig>(); private LinkedHashMap<String,EpicReaderContext> subs = new LinkedHashMap<String,EpicReaderContext>(); EpicReaderSig addSig(String path, char separator, byte type, int sigNum) { int indexOfSep = path.indexOf(separator); if (indexOfSep == -1) { if (type == 'I') path = "i(" + path + ")"; EpicReaderSig sig = new EpicReaderSig(type, path, sigNum); signals.add(sig); return sig; } String subName = path.substring(0, indexOfSep); if (subName.length() > 0 && subName.charAt(0) == 'x') subName = subName.substring(1); EpicReaderContext ctx = subs.get(subName); if (ctx == null) { ctx = new EpicReaderContext(); subs.put(subName, ctx); } path = path.substring(indexOfSep + 1); return ctx.addSig(path, separator, type, sigNum); } void writeSigs(EpicReaderProcess reader) throws IOException { DataOutputStream stdOut = reader.stdOut; for (EpicReaderSig sig: signals) { stdOut.writeByte(sig.type); stdOut.writeInt(sig.sigNum); reader.writeString(sig.name); } for (Map.Entry<String,EpicReaderContext> e: subs.entrySet()) { String subName = e.getKey(); EpicReaderContext sub = e.getValue(); stdOut.writeByte('D'); reader.writeString(subName); sub.writeSigs(reader); stdOut.writeByte('U'); } } } class EpicReaderSig { final byte type; final String name; final int sigNum; EpicReaderSig(byte type, String name, int sigNum) { this.type = type; this.name = name; this.sigNum = sigNum; } } /** * This class is a buffer to pack waveform of Epic signal. */ class EpicReaderSignal { /** Time of last event.*/ int lastT; /** Value of last event. */ int lastV; /** Minimal value among events. */ int minV = Integer.MAX_VALUE; /** Maximal value among events. */ int maxV = Integer.MIN_VALUE; /** Packed waveform. */ byte[] waveform = new byte[512]; /** Count of bytes used in waveform. */ int len; /** * Puts event into waveform. * @param t time of event. * @param v value of event. */ void putEvent(int t, int v) { putUnsigned(t - lastT); putSigned(v - lastV); lastT = t; lastV = v; minV = Math.min(minV, v); maxV = Math.max(maxV, v); } /** * Packes unsigned int. * @param value value to pack. */ void putUnsigned(int value) { if (value < 0xC0) { ensureCapacity(1); putByte(value); } else if (value < 0x3F00) { ensureCapacity(2); putByte((value + 0xC000) >> 8); putByte(value); } else { ensureCapacity(5); putByte(0xFF); putByte(value >> 24); putByte(value >> 16); putByte(value >> 8); putByte(value); } } /** * Packes signed int. * @param value value to pack. */ void putSigned(int value) { if (-0x60 <= value && value < 0x60) { ensureCapacity(1); putByte(value + 0x60); } else if (-0x1F00 <= value && value < 0x2000) { ensureCapacity(2); putByte((value + 0xDF00) >> 8); putByte(value); } else { ensureCapacity(5); putByte(0xFF); putByte(value >> 24); putByte(value >> 16); putByte(value >> 8); putByte(value); } } /** * Unpackes waveform. * @return array with time/value pairs. */ int[] getWaveform() { int count = 0; for (int i = 0; i < len; count++) { int l; int b = waveform[i++] & 0xff; if (b < 0xC0) l = 0; else if (b < 0xFF) l = 1; else l = 4; i += l; b = waveform[i++] & 0xff; if (b < 0xC0) l = 0; else if (b < 0xFF) l = 1; else l = 4; i += l; } int[] w = new int[count*2]; count = 0; int t = 0; int v = 0; for (int i = 0; i < len; count++) { int l; int b = waveform[i++] & 0xff; if (b < 0xC0) { l = 0; } else if (b < 0xFF) { l = 1; b -= 0xC0; } else { l = 4; } while (l > 0) { b = (b << 8) | waveform[i++] & 0xff; l--; } w[count*2] = t = t + b; b = waveform[i++] & 0xff; if (b < 0xC0) { l = 0; b -= 0x60; } else if (b < 0xFF) { l = 1; b -= 0xDF; } else { l = 4; } while (l > 0) { b = (b << 8) | waveform[i++] & 0xff; l--; } w[count*2 + 1] = v = v + b; } assert count*2 == w.length; return w; } /** * Puts byte int packed array. * @param value byte to but. */ void putByte(int value) { waveform[len++] = (byte)value; } /** * Ensures that it is possible to add specified number of bytes to packed waveform. * @param l number of bytes. */ void ensureCapacity(int l) { if (len + l <= waveform.length) return; byte[] newWaveform = new byte[waveform.length*3/2]; System.arraycopy(waveform, 0, newWaveform, 0, waveform.length); waveform = newWaveform; assert len + l <= waveform.length; } }