/******************************************************************************* * This file is part of logisim-evolution. * * logisim-evolution 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. * * logisim-evolution 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 logisim-evolution. If not, see <http://www.gnu.org/licenses/>. * * Original code by Carl Burch (http://www.cburch.com), 2011. * Subsequent modifications by : * + Haute École Spécialisée Bernoise * http://www.bfh.ch * + Haute École du paysage, d'ingénierie et d'architecture de Genève * http://hepia.hesge.ch/ * + Haute École d'Ingénierie et de Gestion du Canton de Vaud * http://www.heig-vd.ch/ * The project is currently maintained by : * + REDS Institute - HEIG-VD * Yverdon-les-Bains, Switzerland * http://reds.heig-vd.ch *******************************************************************************/ package com.cburch.logisim.gui.start; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.LinkedList; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.cburch.logisim.circuit.Analyze; import com.cburch.logisim.circuit.Circuit; import com.cburch.logisim.circuit.CircuitState; import com.cburch.logisim.circuit.Propagator; import com.cburch.logisim.comp.Component; import com.cburch.logisim.data.Value; import com.cburch.logisim.file.FileStatistics; import com.cburch.logisim.file.LoadFailedException; import com.cburch.logisim.file.Loader; import com.cburch.logisim.file.LogisimFile; import com.cburch.logisim.instance.Instance; import com.cburch.logisim.instance.InstanceState; import com.cburch.logisim.proj.Project; import com.cburch.logisim.std.io.Keyboard; import com.cburch.logisim.std.io.Tty; import com.cburch.logisim.std.memory.Ram; import com.cburch.logisim.std.wiring.Pin; import com.cburch.logisim.tools.Library; import com.cburch.logisim.util.StringUtil; public class TtyInterface { // It's possible to avoid using the separate thread using // System.in.available(), // but this doesn't quite work because on some systems, the keyboard input // is not interactively echoed until System.in.read() is invoked. private static class StdinThread extends Thread { private LinkedList<char[]> queue; // of char[] public StdinThread() { queue = new LinkedList<char[]>(); } public char[] getBuffer() { synchronized (queue) { if (queue.isEmpty()) { return null; } else { return queue.removeFirst(); } } } @Override public void run() { InputStreamReader stdin = new InputStreamReader(System.in); char[] buffer = new char[32]; while (true) { try { int nbytes = stdin.read(buffer); if (nbytes > 0) { char[] add = new char[nbytes]; System.arraycopy(buffer, 0, add, 0, nbytes); synchronized (queue) { queue.addLast(add); } } } catch (IOException e) { } } } } private static int countDigits(int num) { int digits = 1; int lessThan = 10; while (num >= lessThan) { digits++; lessThan *= 10; } return digits; } private static void displaySpeed(long tickCount, long elapse) { double hertz = (double) tickCount / elapse * 1000.0; double precision; if (hertz >= 100) precision = 1.0; else if (hertz >= 10) precision = 0.1; else if (hertz >= 1) precision = 0.01; else if (hertz >= 0.01) precision = 0.0001; else precision = 0.0000001; hertz = (int) (hertz / precision) * precision; String hertzStr = hertz == (int) hertz ? "" + (int) hertz : "" + hertz; Object[] paramArray = { StringUtil.format(Strings.get("ttySpeedMsg")), hertzStr, tickCount, elapse }; logger.info("{}", paramArray); } private static void displayStatistics(LogisimFile file) { FileStatistics stats = FileStatistics.compute(file, file.getMainCircuit()); FileStatistics.Count total = stats.getTotalWithSubcircuits(); int maxName = 0; for (FileStatistics.Count count : stats.getCounts()) { int nameLength = count.getFactory().getDisplayName().length(); if (nameLength > maxName) maxName = nameLength; } String fmt = "%" + countDigits(total.getUniqueCount()) + "d\t" + "%" + countDigits(total.getRecursiveCount()) + "d\t"; String fmtNormal = fmt + "%-" + maxName + "s\t%s\n"; for (FileStatistics.Count count : stats.getCounts()) { Library lib = count.getLibrary(); String libName = lib == null ? "-" : lib.getDisplayName(); System.out.printf(fmtNormal, // OK Integer.valueOf(count.getUniqueCount()), Integer .valueOf(count.getRecursiveCount()), count .getFactory().getDisplayName(), libName); } FileStatistics.Count totalWithout = stats.getTotalWithoutSubcircuits(); System.out.printf( fmt + "%s\n", // OK Integer.valueOf(totalWithout.getUniqueCount()), Integer.valueOf(totalWithout.getRecursiveCount()), Strings.get("statsTotalWithout")); System.out.printf( fmt + "%s\n", // OK Integer.valueOf(total.getUniqueCount()), Integer.valueOf(total.getRecursiveCount()), Strings.get("statsTotalWith")); } private static void displayTableRow(ArrayList<Value> prevOutputs, ArrayList<Value> curOutputs) { boolean shouldPrint = false; if (prevOutputs == null) { shouldPrint = true; } else { for (int i = 0; i < curOutputs.size(); i++) { Value a = prevOutputs.get(i); Value b = curOutputs.get(i); if (!a.equals(b)) { shouldPrint = true; break; } } } if (shouldPrint) { for (int i = 0; i < curOutputs.size(); i++) { if (i != 0) System.out.print("\t"); // OK System.out.print(curOutputs.get(i)); // OK } System.out.println(); // OK } } private static void ensureLineTerminated() { if (!lastIsNewline) { lastIsNewline = true; System.out.print('\n'); // OK } } private static boolean loadRam(CircuitState circState, File loadFile) throws IOException { if (loadFile == null) return false; boolean found = false; for (Component comp : circState.getCircuit().getNonWires()) { if (comp.getFactory() instanceof Ram) { Ram ramFactory = (Ram) comp.getFactory(); InstanceState ramState = circState.getInstanceState(comp); ramFactory.loadImage(ramState, loadFile); found = true; } } for (CircuitState sub : circState.getSubstates()) { found |= loadRam(sub, loadFile); } return found; } private static boolean prepareForTty(CircuitState circState, ArrayList<InstanceState> keybStates) { boolean found = false; for (Component comp : circState.getCircuit().getNonWires()) { Object factory = comp.getFactory(); if (factory instanceof Tty) { Tty ttyFactory = (Tty) factory; InstanceState ttyState = circState.getInstanceState(comp); ttyFactory.sendToStdout(ttyState); found = true; } else if (factory instanceof Keyboard) { keybStates.add(circState.getInstanceState(comp)); found = true; } } for (CircuitState sub : circState.getSubstates()) { found |= prepareForTty(sub, keybStates); } return found; } public static void run(Startup args) { File fileToOpen = args.getFilesToOpen().get(0); Loader loader = new Loader(null); LogisimFile file; try { file = loader.openLogisimFile(fileToOpen, args.getSubstitutions()); } catch (LoadFailedException e) { logger.error("{}", Strings.get("ttyLoadError", fileToOpen.getName())); System.exit(-1); return; } int format = args.getTtyFormat(); if ((format & FORMAT_STATISTICS) != 0) { format &= ~FORMAT_STATISTICS; displayStatistics(file); } if (format == 0) { // no simulation remaining to perform, so just exit System.exit(0); } Project proj = new Project(file); Circuit circuit = file.getMainCircuit(); Map<Instance, String> pinNames = Analyze.getPinLabels(circuit); ArrayList<Instance> outputPins = new ArrayList<Instance>(); Instance haltPin = null; for (Map.Entry<Instance, String> entry : pinNames.entrySet()) { Instance pin = entry.getKey(); String pinName = entry.getValue(); if (!Pin.FACTORY.isInputPin(pin)) { outputPins.add(pin); if (pinName.equals("halt")) { haltPin = pin; } } } CircuitState circState = new CircuitState(proj, circuit); // we have to do our initial propagation before the simulation starts - // it's necessary to populate the circuit with substates. circState.getPropagator().propagate(); if (args.getLoadFile() != null) { try { boolean loaded = loadRam(circState, args.getLoadFile()); if (!loaded) { logger.error("{}", Strings.get("loadNoRamError")); System.exit(-1); } } catch (IOException e) { logger.error("{}: {}", Strings.get("loadIoError"), e.toString()); System.exit(-1); } } int ttyFormat = args.getTtyFormat(); int simCode = runSimulation(circState, outputPins, haltPin, ttyFormat); System.exit(simCode); } private static int runSimulation(CircuitState circState, ArrayList<Instance> outputPins, Instance haltPin, int format) { boolean showTable = (format & FORMAT_TABLE) != 0; boolean showSpeed = (format & FORMAT_SPEED) != 0; boolean showTty = (format & FORMAT_TTY) != 0; boolean showHalt = (format & FORMAT_HALT) != 0; ArrayList<InstanceState> keyboardStates = null; StdinThread stdinThread = null; if (showTty) { keyboardStates = new ArrayList<InstanceState>(); boolean ttyFound = prepareForTty(circState, keyboardStates); if (!ttyFound) { logger.error("{}", Strings.get("ttyNoTtyError")); System.exit(-1); } if (keyboardStates.isEmpty()) { keyboardStates = null; } else { stdinThread = new StdinThread(); stdinThread.start(); } } int retCode; long tickCount = 0; long start = System.currentTimeMillis(); boolean halted = false; ArrayList<Value> prevOutputs = null; Propagator prop = circState.getPropagator(); while (true) { ArrayList<Value> curOutputs = new ArrayList<Value>(); for (Instance pin : outputPins) { InstanceState pinState = circState.getInstanceState(pin); Value val = Pin.FACTORY.getValue(pinState); if (pin == haltPin) { halted |= val.equals(Value.TRUE); } else if (showTable) { curOutputs.add(val); } } if (showTable) { displayTableRow(prevOutputs, curOutputs); } if (halted) { retCode = 0; // normal exit break; } if (prop.isOscillating()) { retCode = 1; // abnormal exit break; } if (keyboardStates != null) { char[] buffer = stdinThread.getBuffer(); if (buffer != null) { for (InstanceState keyState : keyboardStates) { Keyboard.addToBuffer(keyState, buffer); } } } prevOutputs = curOutputs; tickCount++; prop.tick(); prop.propagate(); } long elapse = System.currentTimeMillis() - start; if (showTty) ensureLineTerminated(); if (showHalt || retCode != 0) { if (retCode == 0) { logger.error("{}", Strings.get("ttyHaltReasonPin")); } else if (retCode == 1) { logger.error("{}", Strings.get("ttyHaltReasonOscillation")); } } if (showSpeed) { displaySpeed(tickCount, elapse); } return retCode; } public static void sendFromTty(char c) { lastIsNewline = c == '\n'; System.out.print(c); // OK } final static Logger logger = LoggerFactory.getLogger(TtyInterface.class); public static final int FORMAT_TABLE = 1; public static final int FORMAT_SPEED = 2; public static final int FORMAT_TTY = 4; public static final int FORMAT_HALT = 8; public static final int FORMAT_STATISTICS = 16; private static boolean lastIsNewline = true; }