/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: SpiceNetlistReader.java * * Copyright (c) 2006 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.spicenetlist; import java.io.*; import java.util.*; /** * Parse a spice netlist. Ignores comments, and * coalesces split lines into single lines. * User: gainsley * Date: Aug 3, 2006 */ public class SpiceNetlistReader { private File file; BufferedReader reader; private StringBuffer lines; private int lineno; private HashMap<String,String> options = new LinkedHashMap<String,String>(); private HashMap<String,String> globalParams = new LinkedHashMap<String,String>(); private List<SpiceInstance> topLevelInstances = new ArrayList<SpiceInstance>(); private HashMap<String,SpiceSubckt> subckts = new LinkedHashMap<String,SpiceSubckt>(); private List<String> globalNets = new ArrayList<String>(); private SpiceSubckt currentSubckt; public SpiceNetlistReader() { reader = null; lines = null; } public HashMap<String,String> getOptions() { return options; } public HashMap<String,String> getGlobalParams() { return globalParams; } public List<SpiceInstance> getTopLevelInstances() { return topLevelInstances; } public Collection<SpiceSubckt> getSubckts() { return subckts.values(); } public List<String> getGlobalNets() { return globalNets; } public SpiceSubckt getSubckt(String name) { return subckts.get(name.toLowerCase()); } // private static final boolean DEBUG = true; // ============================== Parsing ================================== // enum TType { PAR, PARVAL, WORD } public void readFile(String fileName, boolean verbose) throws FileNotFoundException { file = new File(fileName); reader = new BufferedReader(new FileReader(fileName)); lines = new StringBuffer(); currentSubckt = null; String line; lineno = 0; try { while ((line = readLine()) != null) { line = line.trim(); String [] tokens = getTokens(line); if (tokens.length == 0) continue; String keyword = tokens[0].toLowerCase(); if (keyword.equals(".include")) { if (tokens.length < 2) { prErr("No file specified for .include"); continue; } String ifile = tokens[1]; if (!ifile.startsWith("/") && !ifile.startsWith("\\")) { // relative path, add to current path File newFile = new File(file.getParent(), ifile); ifile = newFile.getPath(); } File saveFile = file; BufferedReader saveReader = reader; StringBuffer saveLines = lines; int saveLine = lineno; try { if (verbose) System.out.println("Reading include file "+ifile); readFile(ifile, verbose); } catch (FileNotFoundException e) { file = saveFile; lineno = saveLine; prErr("Include file does not exist: "+ifile); } file = saveFile; reader = saveReader; lines = saveLines; lineno = saveLine; } else if (keyword.startsWith(".opt")) { parseOptions(options, 1, tokens); } else if (keyword.equals(".param")) { parseParams(globalParams, 1, tokens); } else if (keyword.equals(".subckt")) { currentSubckt = parseSubckt(tokens); if (currentSubckt != null && subckts.containsKey(currentSubckt.getName().toLowerCase())) { prErr("Subckt "+currentSubckt.getName()+ " already defined"); continue; } subckts.put(currentSubckt.getName().toLowerCase(), currentSubckt); } else if (keyword.equals(".global")) { for (int i=1; i<tokens.length; i++) { if (!globalNets.contains(tokens[i])) globalNets.add(tokens[i]); } } else if (keyword.startsWith(".ends")) { currentSubckt = null; } else if (keyword.startsWith(".end")) { // end of file } else if (keyword.startsWith("x")) { SpiceInstance inst = parseSubcktInstance(tokens); addInstance(inst); } else if (keyword.startsWith("r")) { SpiceInstance inst = parseResistor(tokens); addInstance(inst); } else if (keyword.startsWith("c")) { SpiceInstance inst = parseCapacitor(tokens); addInstance(inst); } else if (keyword.startsWith("m")) { SpiceInstance inst = parseMosfet(tokens); addInstance(inst); } else if (keyword.equals(".protect") || keyword.equals(".unprotect")) { // ignore } else { prWarn("Parser does not recognize: "+line); } } reader.close(); } catch (IOException e) { System.out.println("Error reading file "+file.getPath()+": "+e.getMessage()); } } private void prErr(String msg) { System.out.println("Error ("+getLocation()+"): "+msg); } private void prWarn(String msg) { System.out.println("Warning ("+getLocation()+"): "+msg); } private String getLocation() { return file.getName()+":"+lineno; } /** * Get the tokens in a line. Tokens are separated by whitespace, * unless that whitespace is surrounded by single quotes, or parentheses. * When quotes are used, those quotes are removed from the string literal. * The construct <code>name=value</code> is returned as three tokens, * the second being the char '='. * @param line the line to parse * @return an array of tokens */ private String [] getTokens(String line) { List<String> tokens = new ArrayList<String>(); int start = 0; boolean inquotes = false; int inparens = 0; int i; for (i=0; i<line.length(); i++) { char c = line.charAt(i); if (inquotes) { if (c == '\'') { if (inparens > 0) continue; // end string literal tokens.add(line.substring(start, i)); start = i+1; inquotes = false; } } else if (c == '\'') { if (inparens > 0) continue; inquotes = true; if (start != i) { prErr("Improper use of open quote '"); break; } start = i+1; } // else !inquotes: else if (Character.isWhitespace(c) && inparens == 0) { // end of token (unless just more whitespace) if (start < i) tokens.add(line.substring(start, i)); start = i+1; } else if (c == '(') { inparens++; } else if (c == ')') { if (inparens == 0) { prErr("Too many ')'s"); break; } inparens--; } else if (c == '=') { if (start < i) tokens.add(line.substring(start, i)); tokens.add("="); start = i+1; } else if (c == '*') { break; // rest of line is comment } } if (start < i) { tokens.add(line.substring(start, i)); } if (inparens != 0) prErr("Unmatched parentheses"); // join {par, =, val} to {par=val} List<String> joined = new ArrayList<String>(); for (int j=0; j<tokens.size(); j++) { if (tokens.get(j).equals("=")) { if (j == 0) { prErr("No right hand side to assignment"); } else if (j == tokens.size()-1) { prErr("No left hand side to assignment"); } else { int last = joined.size() - 1; joined.set(last, joined.get(last)+"="+tokens.get(++j)); } } else { joined.add(tokens.get(j)); } } String ret [] = new String[joined.size()]; for (int k=0; k<joined.size(); k++) ret[k] = joined.get(k); return ret; } private void parseOptions(HashMap<String,String> map, int start, String [] tokens) { for (int i=start; i<tokens.length; i++) { int e = tokens[i].indexOf('='); String pname = tokens[i]; String value = "true"; if (e > 0) { pname = tokens[i].substring(0, e); value = tokens[i].substring(e+1); } if (pname == null || value == null) { prErr("Bad option value: "+tokens[i]); continue; } map.put(pname.toLowerCase(), value); } } private void parseParams(HashMap<String,String> map, int start, String [] tokens) { for (int i=start; i<tokens.length; i++) { parseParam(map, tokens[i], null); } } private void parseParam(HashMap<String,String> map, String parval, String defaultParName) { int e = parval.indexOf('='); String pname = defaultParName; String value = parval; if (e > 0) { pname = parval.substring(0, e); value = parval.substring(e+1); if (defaultParName != null && !defaultParName.equalsIgnoreCase(pname)) { prWarn("Expected param "+defaultParName+", but got "+pname); } } if (pname == null || value == null) { prErr("Badly formatted param=val: "+parval); return; } map.put(pname.toLowerCase(), value); } private SpiceSubckt parseSubckt(String [] parts) { SpiceSubckt subckt = new SpiceSubckt(parts[1]); int i=2; for (; i<parts.length; i++) { if (parts[i].indexOf('=') > 0) break; // parameter subckt.addPort(parts[i]); } parseParams(subckt.getParams(), i, parts); return subckt; } private SpiceInstance parseSubcktInstance(String [] parts) { String name = parts[0].substring(1); List<String> nets = new ArrayList<String>(); int i=1; for (; i<parts.length; i++) { if (parts[i].contains("=")) break; // parameter nets.add(parts[i]); } String subcktName = nets.remove(nets.size()-1); // last one is subckt reference SpiceSubckt subckt = subckts.get(subcktName.toLowerCase()); if (subckt == null) { prErr("Cannot find subckt for "+subcktName); return null; } SpiceInstance inst = new SpiceInstance(subckt, name); for (String net : nets) inst.addNet(net); parseParams(inst.getParams(), i, parts); // consistency check if (inst.getNets().size() != subckt.getPorts().size()) { prErr("Number of ports do not match: "+inst.getNets().size()+ " (instance "+name+") vs "+subckt.getPorts().size()+ " (subckt "+subckt.getName()+")"); } return inst; } private void addInstance(SpiceInstance inst) { if (inst == null) return; if (currentSubckt != null) currentSubckt.addInstance(inst); else topLevelInstances.add(inst); } private SpiceInstance parseResistor(String [] parts) { if (parts.length < 4) { prErr("Not enough arguments for resistor"); return null; } SpiceInstance inst = new SpiceInstance(parts[0]); for (int i=1; i<3; i++) { inst.addNet(parts[i]); } parseParam(inst.getParams(), parts[3], "r"); return inst; } private SpiceInstance parseCapacitor(String [] parts) { if (parts.length < 4) { prErr("Not enough arguments for capacitor"); return null; } SpiceInstance inst = new SpiceInstance(parts[0]); for (int i=1; i<3; i++) { inst.addNet(parts[i]); } parseParam(inst.getParams(), parts[3], "c"); return inst; } private SpiceInstance parseMosfet(String [] parts) { if (parts.length < 8) { prErr("Not enough arguments for mosfet"); return null; } SpiceInstance inst = new SpiceInstance(parts[0]); int i=1; for (; i<5; i++) { inst.addNet(parts[i]); } String model = parts[i]; inst.getParams().put("model", model); i++; parseParams(inst.getParams(), i, parts); return inst; } private void parseComment(String line) { if (currentSubckt == null) return; String [] parts = line.split("\\s+"); for (int i=0; i<parts.length; i++) { if (parts[i].equalsIgnoreCase("PORT") && i+2 < parts.length) { String dir = parts[i+1]; String port = parts[i+2]; SpiceSubckt.PortType type = SpiceSubckt.PortType.valueOf(dir); if (type != null) currentSubckt.setPortType(port, type); return; } } } /** * Read one line of the spice file. This concatenates continuation lines * that start with '+' to the first line, replacing '+' with ' '. It * also ignores comment lines (lines that start with '*'). * @return one spice line * @throws IOException */ private String readLine() throws IOException { while (true) { lineno++; String line = reader.readLine(); if (line == null) { // EOF if (lines.length() == 0) return null; return removeString(); } line = line.trim(); if (line.startsWith("*")) { // comment line parseComment(line); continue; } if (line.startsWith("+")) { // continuation line lines.append(" "); lines.append(line.substring(1)); continue; } // normal line if (lines.length() == 0) { // this is the first line read, read next line to see if continued lines.append(line); } else { // beginning of next line, save it and return completed line String ret = removeString(); lines.append(line); return ret; } } } private String removeString() { String ret = lines.toString(); lines.delete(0, lines.length()); return ret; } // ================================= Writing ==================================== public void writeFile(String fileName) { if (fileName == null) { write(System.out); } else { try { PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(fileName))); write(out); } catch (IOException e) { System.out.println(e.getMessage()); } } } public void write(PrintStream out) { out.println("* Spice netlist"); for (String key : options.keySet()) { String value = options.get(key); if (value == null) { out.println(".option "+key); } else { out.println(".option "+key+"="+options.get(key)); } } out.println(); for (String key : globalParams.keySet()) { out.println(".param "+key+"="+globalParams.get(key)); } out.println(); for (String net : globalNets) { out.println(".global "+net); } out.println(); for (String subcktName : subckts.keySet()) { SpiceSubckt subckt = subckts.get(subcktName); subckt.write(out); out.println(); } for (SpiceInstance inst : topLevelInstances) { inst.write(out); } out.println(); out.println(".end"); if (out != System.out) out.close(); } static void multiLinePrint(PrintStream out, boolean isComment, String str) { // put in line continuations, if over 78 chars long char contChar = '+'; if (isComment) contChar = '*'; int lastSpace = -1; int count = 0; boolean insideQuotes = false; int lineStart = 0; for (int pt = 0; pt < str.length(); pt++) { char chr = str.charAt(pt); // if (sim_spice_machine == SPICE2) // { // if (islower(*pt)) *pt = toupper(*pt); // } if (chr == '\n') { out.print(str.substring(lineStart, pt+1)); count = 0; lastSpace = -1; lineStart = pt+1; } else { if (chr == ' ' && !insideQuotes) lastSpace = pt; if (chr == '\'') insideQuotes = !insideQuotes; count++; if (count >= 78 && !insideQuotes && lastSpace > -1) { String partial = str.substring(lineStart, lastSpace+1); out.print(partial + "\n" + contChar); count = count - partial.length(); lineStart = lastSpace+1; lastSpace = -1; } } } if (lineStart < str.length()) { String partial = str.substring(lineStart); out.print(partial); } } // ======================== Spice Netlist Information ============================ // =================================== test ================================ public static void main(String [] args) { SpiceNetlistReader reader = new SpiceNetlistReader(); try { reader.readFile("/import/async/cad/2006/bic/jkg/bic/testSims/test_clk_regen.spi", true); } catch (FileNotFoundException e) { System.out.println(e.getMessage()); } reader.writeFile("/tmp/output.spi"); } // private static void testLineParserTests() { // SpiceNetlistReader reader = new SpiceNetlistReader(); // reader = new SpiceNetlistReader(); // reader.file = new File("/none"); // reader.lineno = 1; // testLineParser(reader, ".measure tran vmin min v( data) from=0ns to=1.25ns "); // testLineParser(reader, ".param poly_res_corner='1.0 * p' * 0.8 corner"); // testLineParser(reader, ".param poly_res_corner = '1.0 * p' * 0.8 corner"); // testLineParser(reader, ".param AVT0N = AGAUSS(0.0, '0.01 / 0.1' , 1)"); // } // // private static void testLineParser(SpiceNetlistReader reader, String line) { // System.out.println("Parsing: "+line); // String [] tokens = reader.getTokens(line); // for (int i=0; i<tokens.length; i++) { // System.out.println(i+": "+tokens[i]); // } // } }