/******************************************************************************* * 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.std.hdl; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import javax.help.UnsupportedOperationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.cburch.logisim.circuit.CircuitEvent; import com.cburch.logisim.circuit.CircuitListener; import com.cburch.logisim.circuit.CircuitState; import com.cburch.logisim.comp.Component; import com.cburch.logisim.proj.Project; import com.cburch.logisim.util.SocketClient; /** * VHDL simulator allows Logisim to simulate the behavior of VHDL architectures. * It delegate this task to Questasim. Communication between Logisim and * Questasim is done by a TCL socket (TclBinder). Path to Questasim has to be * specified in Logisim's preferences. * * @author christian.mueller@heig-vd.ch * @since 2.12.0 */ public class VhdlSimulator implements CircuitListener { public enum State { DISABLED, ENABLED, STARTING, RUNNING; } public static List<Component> getVhdlComponents(CircuitState s) { LinkedList<Component> vhdlComp = new LinkedList<Component>(); /* Add current circuits comp */ for (Component comp : s.getCircuit().getNonWires()) { if (comp.getFactory().getClass().equals(VhdlEntity.class)) { vhdlComp.add(comp); } } /* Add subcircuits comp */ for (CircuitState sub : s.getSubstates()) { vhdlComp.addAll(getVhdlComponents(sub)); } return vhdlComp; } final static Logger logger = LoggerFactory.getLogger(VhdlSimulator.class); final static Charset ENCODING = StandardCharsets.UTF_8; final static String VHDL_TEMPLATES_PATH = "/resources/logisim/hdl/"; final static String SIM_RESOURCES_PATH = "/resources/logisim/sim/"; final static String SIM_PATH = System.getProperty("java.io.tmpdir") + "/logisim/sim/"; final static String SIM_SRC_PATH = SIM_PATH + "src/"; final static String SIM_COMP_PATH = SIM_PATH + "comp/"; final static String SIM_TOP_FILENAME = "top_sim.vhdl"; private VhdlSimulatorVhdlTop vhdlTop = new VhdlSimulatorVhdlTop(this); private VhdlSimulatorTclComp tclRun = new VhdlSimulatorTclComp(this); private VhdlSimulatorTclBinder tclBinder; private SocketClient socketClient = new SocketClient(); private Project project; private static ArrayList<VhdlSimulatorListener> listeners = new ArrayList<VhdlSimulatorListener>(); private State state = State.DISABLED; public VhdlSimulator(Project circuitState) { this.project = circuitState; } public void addVhdlSimStateListener(VhdlSimulatorListener l) { listeners.add(l); } /** * Circuit listener Each time the circuit changes, test if there is a VHDL * component If yes, start the VHDL simulator, and if not, stop it */ @Override public void circuitChanged(CircuitEvent event) { if (hasVhdlComponent(getProject())) start(); else stop(); } /** * Disable the simulator. If it is running, stops it first. */ public void disable() { switch (state) { case RUNNING: stop(); break; case ENABLED: break; case DISABLED: return; default: throw new UnsupportedOperationException( "Cannot disable VHDL simulator from " + state + " state"); } setState(State.DISABLED); /* Hide and empty console log */ getProject().getFrame().setVhdlSimulatorConsoleStatus(false); getProject().getFrame().getVhdlSimulatorConsole().clear(); } /** * Enable the simulator. Here we also decide to start it directly as it is * what the user generally wants. */ public void enable() { /* * Init binder with qsim, test qsim path and ask for a valid one if it * fails */ if (tclBinder == null) { tclBinder = new VhdlSimulatorTclBinder(this); } switch (state) { case RUNNING: stop(); break; case ENABLED: return; case DISABLED: break; default: throw new UnsupportedOperationException( "Cannot enable VHDL simulator from " + state + " state"); } getProject().getFrame().setVhdlSimulatorConsoleStatus(true); setState(State.ENABLED); start(); } /* At least one of the VHDL entity changed */ public void fireInvalidated() { // File dir = new File(SIM_SRC_PATH); // for(File file: dir.listFiles()) file.delete(); // vhdlTop.fireInvalidated(); // tclRun.fireInvalidated(); } private void fireVhdlSimStateChanged() { for (VhdlSimulatorListener l : listeners) { l.stateChanged(); } } public void generateFiles() { vhdlTop.fireInvalidated(); tclRun.fireInvalidated(); new File(SIM_PATH).mkdirs(); new File(SIM_SRC_PATH).mkdirs(); new File(SIM_COMP_PATH).mkdirs(); try { Files.copy( this.getClass().getResourceAsStream( SIM_RESOURCES_PATH + "questasim_binder.tcl"), Paths.get(SIM_PATH + "questasim_binder.tcl"), StandardCopyOption.REPLACE_EXISTING); Files.copy( this.getClass().getResourceAsStream( SIM_RESOURCES_PATH + "run.tcl"), Paths.get(SIM_PATH + "run.tcl"), StandardCopyOption.REPLACE_EXISTING); Files.copy( this.getClass().getResourceAsStream( SIM_RESOURCES_PATH + "modelsim.ini"), Paths.get(SIM_COMP_PATH + "modelsim.ini"), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { logger.error("Cannot copy simulation files: {}", e.getMessage()); e.printStackTrace(); } vhdlTop.generate(); tclRun.generate(); /* Generate each component's file */ for (Component comp : getVhdlComponents(project.getCircuitState())) { ((VhdlEntity) comp.getFactory()).saveFile(comp.getAttributeSet()); } } public Project getProject() { return project; } public SocketClient getSocketClient() { return socketClient; } public State getState() { return state; } private boolean hasVhdlComponent(CircuitState s) { /* Test current circuit */ for (Component comp : s.getCircuit().getNonWires()) { if (comp.getFactory().getClass().equals(VhdlEntity.class)) { return true; } } /* Test subcircuits */ for (CircuitState sub : s.getSubstates()) { if (hasVhdlComponent(sub)) return true; } return false; } /** * Test if a project has a VHDL component * * @param p * @return */ private boolean hasVhdlComponent(Project p) { return hasVhdlComponent(p.getCircuitState()); } public boolean isEnabled() { return state != State.DISABLED; } public boolean isRunning() { return state == State.RUNNING; } /** * Receive a message from the VHDL simulator * * @return */ public String receive() { if (!isRunning()) throw new UnsupportedOperationException(); return socketClient.receive(); } public void removeVhdlSimStateListener(VhdlSimulatorListener l) { listeners.remove(l); } public void reset() { if (isEnabled()) socketClient.send("restart"); } /** * Stop and restart. If not running, just start */ public void restart() { switch (state) { case DISABLED: case STARTING: case ENABLED: start(); return; case RUNNING: stop(); start(); break; default: throw new UnsupportedOperationException( "Cannot restart VHDL simulator from " + state + " state"); } } /** * Send a message to the VHDL simulator * * @param message */ public void send(String message) { if (!isRunning()) throw new UnsupportedOperationException(); socketClient.send(message); } public void setEnabled(Boolean enable) { if (enable) enable(); else disable(); } public void setState(State newState) { state = newState; fireVhdlSimStateChanged(); } /** * Start both TCL binder and socket server */ public void start() { if (!hasVhdlComponent(getProject())) return; switch (state) { case DISABLED: case STARTING: case RUNNING: return; case ENABLED: setState(State.STARTING); generateFiles(); tclBinder.start(); break; default: throw new UnsupportedOperationException( "Cannot start VHDL simulator from " + state + " state"); } } /** * Stop the simulator, both TCL binder and socket server */ public void stop() { switch (state) { case DISABLED: case ENABLED: return; case RUNNING: break; case STARTING: // FIXME : start a thread that waits for the simulator to finish // starting and then stop it break; default: throw new UnsupportedOperationException( "Cannot stop VHDL simulator from " + state + " state"); } tclBinder.stop(); socketClient.stop(); setState(State.ENABLED); } public void tclStartCallback() { socketClient.start(); setState(State.RUNNING); } }