/******************************************************************************* * 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.circuit; import java.awt.Graphics; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.WeakHashMap; import javax.swing.JOptionPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.bfh.logisim.designrulecheck.Netlist; import com.bfh.logisim.fpgagui.FPGAReport; import com.cburch.logisim.circuit.appear.CircuitAppearance; import com.cburch.logisim.comp.Component; import com.cburch.logisim.comp.ComponentDrawContext; import com.cburch.logisim.comp.ComponentEvent; import com.cburch.logisim.comp.ComponentFactory; import com.cburch.logisim.comp.ComponentListener; import com.cburch.logisim.comp.EndData; import com.cburch.logisim.data.Attribute; import com.cburch.logisim.data.AttributeEvent; import com.cburch.logisim.data.AttributeSet; import com.cburch.logisim.data.BitWidth; import com.cburch.logisim.data.Bounds; import com.cburch.logisim.data.FailException; import com.cburch.logisim.data.Location; import com.cburch.logisim.data.TestException; import com.cburch.logisim.data.Value; import com.cburch.logisim.file.LogisimFile; import com.cburch.logisim.instance.Instance; import com.cburch.logisim.instance.InstanceState; import com.cburch.logisim.instance.StdAttr; import com.cburch.logisim.prefs.AppPreferences; import com.cburch.logisim.proj.Project; import com.cburch.logisim.std.wiring.Clock; import com.cburch.logisim.std.wiring.Pin; import com.cburch.logisim.std.wiring.Tunnel; import com.cburch.logisim.tools.SetAttributeAction; import com.cburch.logisim.tools.Strings; import com.cburch.logisim.util.AutoLabel; import com.cburch.logisim.util.CollectionUtil; import com.cburch.logisim.util.EventSourceWeakSupport; public class Circuit { private class EndChangedTransaction extends CircuitTransaction { private Component comp; private Map<Location, EndData> toRemove; private Map<Location, EndData> toAdd; EndChangedTransaction(Component comp, Map<Location, EndData> toRemove, Map<Location, EndData> toAdd) { this.comp = comp; this.toRemove = toRemove; this.toAdd = toAdd; } @Override protected Map<Circuit, Integer> getAccessedCircuits() { return Collections.singletonMap(Circuit.this, READ_WRITE); } @Override protected void run(CircuitMutator mutator) { for (Location loc : toRemove.keySet()) { EndData removed = toRemove.get(loc); EndData replaced = toAdd.remove(loc); if (replaced == null) { wires.remove(comp, removed); } else if (!replaced.equals(removed)) { wires.replace(comp, removed, replaced); } } for (EndData end : toAdd.values()) { wires.add(comp, end); } ((CircuitMutatorImpl) mutator).markModified(Circuit.this); } } private class MyComponentListener implements ComponentListener { public void componentInvalidated(ComponentEvent e) { fireEvent(CircuitEvent.ACTION_INVALIDATE, e.getSource()); } public void endChanged(ComponentEvent e) { locker.checkForWritePermission("ends changed"); Annotated = false; MyNetList.clear(); Component comp = e.getSource(); HashMap<Location, EndData> toRemove = toMap(e.getOldData()); HashMap<Location, EndData> toAdd = toMap(e.getData()); EndChangedTransaction xn = new EndChangedTransaction(comp, toRemove, toAdd); locker.execute(xn); fireEvent(CircuitEvent.ACTION_INVALIDATE, comp); } private HashMap<Location, EndData> toMap(Object val) { HashMap<Location, EndData> map = new HashMap<Location, EndData>(); if (val instanceof List) { @SuppressWarnings("unchecked") List<EndData> valList = (List<EndData>) val; for (EndData end : valList) { if (end != null) { map.put(end.getLocation(), end); } } } else if (val instanceof EndData) { EndData end = (EndData) val; map.put(end.getLocation(), end); } return map; } public void LabelChanged(ComponentEvent e) { AttributeEvent attre = (AttributeEvent) e.getData(); if (attre.getSource()==null|| attre.getValue()==null) { return; } String newLabel = (String) attre.getValue(); String oldLabel = attre.getOldValue() != null ? (String) attre.getOldValue() : ""; @SuppressWarnings("unchecked") Attribute<String> lattr = (Attribute<String>) attre.getAttribute(); if (!IsCorrectLabel(newLabel,comps,attre.getSource(),e.getSource().getFactory(),true)) attre.getSource().setValue(lattr, oldLabel); } } public static boolean IsCorrectLabel(String Name, Set<Component> components, AttributeSet me, ComponentFactory myFactory, Boolean ShowDialog) { if (myFactory instanceof Tunnel) return true; return !(IsExistingLabel(Name,me,components,ShowDialog)||IsComponentName(Name,components,ShowDialog)); } private static boolean IsComponentName(String Name, Set<Component> comps, Boolean ShowDialog) { if (Name.isEmpty()) return false; for (Component comp : comps) { if (comp.getFactory().getName().toUpperCase().equals(Name.toUpperCase())) { if (ShowDialog) { String msg = com.cburch.logisim.circuit.Strings.get("ComponentLabelNameError"); JOptionPane.showMessageDialog(null, "\""+Name+"\" : "+msg); } return true; } } /* we do not have to check the wires as (1) Wire is a reserved keyword, and (2) they cannot have a label */ return false; } private static boolean IsExistingLabel(String Name, AttributeSet me, Set<Component> comps, Boolean ShowDialog) { if (Name.isEmpty()) return false; for (Component comp : comps) { if (!comp.getAttributeSet().equals(me)&&!(comp.getFactory() instanceof Tunnel)) { String Label = (comp.getAttributeSet().containsAttribute(StdAttr.LABEL)) ? comp.getAttributeSet().getValue(StdAttr.LABEL) : ""; if (Label.toUpperCase().equals(Name.toUpperCase())) { if (ShowDialog) { String msg = com.cburch.logisim.circuit.Strings.get("UsedLabelNameError"); JOptionPane.showMessageDialog(null, "\""+Name+"\" : "+msg); } return true; } } } /* we do not have to check the wires as (1) Wire is a reserved keyword, and (2) they cannot have a label */ return false; } // // helper methods for other classes in package // public static boolean isInput(Component comp) { return comp.getEnd(0).getType() != EndData.INPUT_ONLY; } private MyComponentListener myComponentListener = new MyComponentListener(); private CircuitAppearance appearance; private AttributeSet staticAttrs; private SubcircuitFactory subcircuitFactory; private EventSourceWeakSupport<CircuitListener> listeners = new EventSourceWeakSupport<CircuitListener>(); private HashSet<Component> comps = new HashSet<Component>(); // doesn't // include // wires CircuitWires wires = new CircuitWires(); private ArrayList<Component> clocks = new ArrayList<Component>(); private CircuitLocker locker; final static Logger logger = LoggerFactory.getLogger(Circuit.class); private WeakHashMap<Component, Circuit> circuitsUsingThis; private Netlist MyNetList; private boolean Annotated; private Project proj; private LogisimFile logiFile; public Circuit(String name, LogisimFile file, Project proj) { staticAttrs = CircuitAttributes.createBaseAttrs(this, name); appearance = new CircuitAppearance(this); subcircuitFactory = new SubcircuitFactory(this); locker = new CircuitLocker(); circuitsUsingThis = new WeakHashMap<Component, Circuit>(); MyNetList = new Netlist(this); addCircuitListener(MyNetList); Annotated = false; logiFile = file; staticAttrs.setValue(CircuitAttributes.NAMED_CIRCUIT_BOX, AppPreferences.NAMED_CIRCUIT_BOXES.getBoolean()); this.proj = proj; } public void SetProject(Project proj) { this.proj = proj; } public Graphics GetGraphics() { return (proj==null) ? null : proj.getFrame().getGraphics(); } // // Listener methods // public void addCircuitListener(CircuitListener what) { listeners.add(what); } private class AnnotateComparator implements Comparator<Component> { @Override public int compare(Component o1, Component o2) { if (o1==o2) return 0; Location l1 = o1.getLocation(); Location l2 = o2.getLocation(); if (l2.getY() != l1.getY()) return l1.getY()-l2.getY(); if (l2.getX() != l1.getX()) return l1.getX()-l2.getX(); return -1; } } private static String GetAnnotationName(Component comp) { String ComponentName; /* Pins are treated specially */ if (comp.getFactory() instanceof Pin) { if (comp.getEnd(0).isOutput()) { if (comp.getEnd(0).getWidth().getWidth() > 1) { ComponentName = "Input_bus"; } else { ComponentName = "Input"; } } else { if (comp.getEnd(0).getWidth().getWidth() > 1) { ComponentName = "Output_bus"; } else { ComponentName = "Output"; } } } else { ComponentName = comp.getFactory().getHDLName( comp.getAttributeSet()); } return ComponentName; } public void Annotate(boolean ClearExistingLabels, FPGAReport reporter) { /* If I am already completely annotated, return */ if (Annotated) { reporter.AddInfo("Nothing to do !"); return; } SortedSet<Component> comps = new TreeSet<Component>(new AnnotateComparator()); HashMap<String,AutoLabel> lablers = new HashMap<String,AutoLabel>(); Set<String> LabelNames = new HashSet<String>(); for (Component comp:getNonWires()) { if (comp.getFactory() instanceof Tunnel) continue; /* we are directly going to remove duplicated labels */ AttributeSet attrs = comp.getAttributeSet(); if (attrs.containsAttribute(StdAttr.LABEL)) { String label = attrs.getValue(StdAttr.LABEL); if (!label.isEmpty()) { if (LabelNames.contains(label.toUpperCase())) { SetAttributeAction act = new SetAttributeAction(this,Strings.getter("changeComponentAttributesAction")); act.set(comp, StdAttr.LABEL, ""); proj.doAction(act); reporter.AddSevereWarning("Removed duplicated label "+this.getName()+"/"+label); } else { LabelNames.add(label.toUpperCase()); } } } /* now we only process those that require a label */ if (comp.getFactory().RequiresNonZeroLabel()) { if (ClearExistingLabels) { /* in case of label cleaning, we clear first the old label */ reporter.AddInfo("Cleared " + this.getName() + "/" + comp.getAttributeSet().getValue(StdAttr.LABEL)); comp.getAttributeSet().setValue(StdAttr.LABEL, ""); } if (comp.getAttributeSet().getValue(StdAttr.LABEL).isEmpty()) { comps.add(comp); String ComponentName = GetAnnotationName(comp); if (!lablers.containsKey(ComponentName)) { lablers.put(ComponentName, new AutoLabel(ComponentName+"_0",this)); } } } /* if the current component is a sub-circuit, recurse into it */ if (comp.getFactory() instanceof SubcircuitFactory) { SubcircuitFactory sub = (SubcircuitFactory) comp.getFactory(); sub.getSubcircuit().Annotate(ClearExistingLabels, reporter); } } /* Now Annotate */ for (Component comp : comps) { String ComponentName = GetAnnotationName(comp); if (!lablers.containsKey(ComponentName)|| !lablers.get(ComponentName).hasNext(this)) { /* This should never happen! */ reporter.AddFatalError("Annotate internal Error: Either there exists duplicate labels or the label syntax is incorrect!\nPlease try annotation on labeled components also\n"); return; } else { String NewLabel = lablers.get(ComponentName).GetNext(this,comp.getFactory()); SetAttributeAction act = new SetAttributeAction(this,Strings.getter("changeComponentAttributesAction")); act.set(comp, StdAttr.LABEL, NewLabel); proj.doAction(act); reporter.AddInfo("Labeled " + this.getName() + "/" + NewLabel); } } Annotated = true; } // // Annotation module for all components that require a non-zero-length label public void ClearAnnotationLevel() { Annotated = false; MyNetList.clear(); for (Component comp : this.getNonWires()) { if (comp.getFactory() instanceof SubcircuitFactory) { SubcircuitFactory sub = (SubcircuitFactory) comp.getFactory(); sub.getSubcircuit().ClearAnnotationLevel(); } } } public boolean contains(Component c) { return comps.contains(c) || wires.getWires().contains(c); } /** * Code taken from Cornell's version of Logisim: * http://www.cs.cornell.edu/courses/cs3410/2015sp/ */ public void doTestVector(Project project, Instance pin[], Value[] val) throws TestException { CircuitState state = project.getCircuitState(); state.reset(); for (int i = 0; i < pin.length; ++i) { if (Pin.FACTORY.isInputPin(pin[i])) { InstanceState pinState = state.getInstanceState(pin[i]); Pin.FACTORY.setValue(pinState, val[i]); } } Propagator prop = state.getPropagator(); try { prop.propagate(); } catch (Throwable thr) { thr.printStackTrace(); } if (prop.isOscillating()) throw new TestException("oscilation detected"); FailException err = null; for (int i = 0; i < pin.length; i++) { InstanceState pinState = state.getInstanceState(pin[i]); if (Pin.FACTORY.isInputPin(pin[i])) continue; Value v = Pin.FACTORY.getValue(pinState); if (!val[i].compatible(v)) { if (err == null) err = new FailException(i, pinState.getAttributeValue(StdAttr.LABEL), val[i], v); else err.add(new FailException(i, pinState .getAttributeValue(StdAttr.LABEL), val[i], v)); } } if (err != null) { throw err; } } // // Graphics methods // public void draw(ComponentDrawContext context, Collection<Component> hidden) { Graphics g = context.getGraphics(); Graphics g_copy = g.create(); context.setGraphics(g_copy); wires.draw(context, hidden); if (hidden == null || hidden.size() == 0) { for (Component c : comps) { Graphics g_new = g.create(); context.setGraphics(g_new); g_copy.dispose(); g_copy = g_new; c.draw(context); } } else { for (Component c : comps) { if (!hidden.contains(c)) { Graphics g_new = g.create(); context.setGraphics(g_new); g_copy.dispose(); g_copy = g_new; try { c.draw(context); } catch (RuntimeException e) { // this is a JAR developer error - display it and move // on e.printStackTrace(); } } } } context.setGraphics(g); g_copy.dispose(); } private void fireEvent(CircuitEvent event) { for (CircuitListener l : listeners) { l.circuitChanged(event); } } void fireEvent(int action, Object data) { fireEvent(new CircuitEvent(action, this, data)); } public Collection<Component> getAllContaining(Location pt) { HashSet<Component> ret = new HashSet<Component>(); for (Component comp : getComponents()) { if (comp.contains(pt)) ret.add(comp); } return ret; } public Collection<Component> getAllContaining(Location pt, Graphics g) { HashSet<Component> ret = new HashSet<Component>(); for (Component comp : getComponents()) { if (comp.contains(pt, g)) ret.add(comp); } return ret; } public Collection<Component> getAllWithin(Bounds bds) { HashSet<Component> ret = new HashSet<Component>(); for (Component comp : getComponents()) { if (bds.contains(comp.getBounds())) ret.add(comp); } return ret; } public Collection<Component> getAllWithin(Bounds bds, Graphics g) { HashSet<Component> ret = new HashSet<Component>(); for (Component comp : getComponents()) { if (bds.contains(comp.getBounds(g))) ret.add(comp); } return ret; } public CircuitAppearance getAppearance() { return appearance; } public Bounds getBounds() { Bounds wireBounds = wires.getWireBounds(); Iterator<Component> it = comps.iterator(); if (!it.hasNext()) return wireBounds; Component first = it.next(); Bounds firstBounds = first.getBounds(); int xMin = firstBounds.getX(); int yMin = firstBounds.getY(); int xMax = xMin + firstBounds.getWidth(); int yMax = yMin + firstBounds.getHeight(); while (it.hasNext()) { Component c = it.next(); Bounds bds = c.getBounds(); int x0 = bds.getX(); int x1 = x0 + bds.getWidth(); int y0 = bds.getY(); int y1 = y0 + bds.getHeight(); if (x0 < xMin) xMin = x0; if (x1 > xMax) xMax = x1; if (y0 < yMin) yMin = y0; if (y1 > yMax) yMax = y1; } Bounds compBounds = Bounds.create(xMin, yMin, xMax - xMin, yMax - yMin); if (wireBounds.getWidth() == 0 || wireBounds.getHeight() == 0) { return compBounds; } else { return compBounds.add(wireBounds); } } public Bounds getBounds(Graphics g) { Bounds ret = wires.getWireBounds(); int xMin = ret.getX(); int yMin = ret.getY(); int xMax = xMin + ret.getWidth(); int yMax = yMin + ret.getHeight(); if (ret == Bounds.EMPTY_BOUNDS) { xMin = Integer.MAX_VALUE; yMin = Integer.MAX_VALUE; xMax = Integer.MIN_VALUE; yMax = Integer.MIN_VALUE; } for (Component c : comps) { Bounds bds = c.getBounds(g); if (bds != null && bds != Bounds.EMPTY_BOUNDS) { int x0 = bds.getX(); int x1 = x0 + bds.getWidth(); int y0 = bds.getY(); int y1 = y0 + bds.getHeight(); if (x0 < xMin) xMin = x0; if (x1 > xMax) xMax = x1; if (y0 < yMin) yMin = y0; if (y1 > yMax) yMax = y1; } } if (xMin > xMax || yMin > yMax) return Bounds.EMPTY_BOUNDS; return Bounds.create(xMin, yMin, xMax - xMin, yMax - yMin); } public Collection<Circuit> getCircuitsUsingThis() { return circuitsUsingThis.values(); } public ArrayList<Component> getClocks() { return clocks; } private Set<Component> getComponents() { return CollectionUtil.createUnmodifiableSetUnion(comps, wires.getWires()); } public Collection<? extends Component> getComponents(Location loc) { return wires.points.getComponents(loc); } public Component getExclusive(Location loc) { return wires.points.getExclusive(loc); } CircuitLocker getLocker() { return locker; } // // access methods // public String getName() { return staticAttrs.getValue(CircuitAttributes.NAME_ATTR); } public Netlist getNetList() { return MyNetList; } public Set<Component> getNonWires() { return comps; } public Collection<? extends Component> getNonWires(Location loc) { return wires.points.getNonWires(loc); } public String getProjName() { return logiFile == null ? "" : logiFile.getName(); } public Collection<? extends Component> getSplitCauses(Location loc) { return wires.points.getSplitCauses(loc); } public Set<Location> getSplitLocations() { return wires.points.getSplitLocations(); } public AttributeSet getStaticAttributes() { return staticAttrs; } public SubcircuitFactory getSubcircuitFactory() { return subcircuitFactory; } public BitWidth getWidth(Location p) { return wires.getWidth(p); } public Location getWidthDeterminant(Location p) { return wires.getWidthDeterminant(p); } public Set<WidthIncompatibilityData> getWidthIncompatibilityData() { return wires.getWidthIncompatibilityData(); } public Set<Wire> getWires() { return wires.getWires(); } public Collection<Wire> getWires(Location loc) { return wires.points.getWires(loc); } public WireSet getWireSet(Wire start) { return wires.getWireSet(start); } public boolean hasConflict(Component comp) { return wires.points.hasConflict(comp); } public boolean isConnected(Location loc, Component ignore) { for (Component o : wires.points.getComponents(loc)) { if (o != ignore) return true; } return false; } void mutatorAdd(Component c) { // logger.debug("mutatorAdd: {}", c); locker.checkForWritePermission("add"); Annotated = false; MyNetList.clear(); if (c instanceof Wire) { Wire w = (Wire) c; if (w.getEnd0().equals(w.getEnd1())) return; boolean added = wires.add(w); if (!added) return; } else { // add it into the circuit boolean added = comps.add(c); if (!added) return; wires.add(c); ComponentFactory factory = c.getFactory(); if (factory instanceof Clock) { clocks.add(c); } else if (factory instanceof SubcircuitFactory) { SubcircuitFactory subcirc = (SubcircuitFactory) factory; subcirc.getSubcircuit().circuitsUsingThis.put(c, this); } c.addComponentListener(myComponentListener); } RemoveWrongLabels(c.getFactory().getName()); fireEvent(CircuitEvent.ACTION_ADD, c); } public void mutatorClear() { locker.checkForWritePermission("clear"); Set<Component> oldComps = comps; comps = new HashSet<Component>(); wires = new CircuitWires(); clocks.clear(); MyNetList.clear(); Annotated = false; for (Component comp : oldComps) { if (comp.getFactory() instanceof SubcircuitFactory) { SubcircuitFactory sub = (SubcircuitFactory) comp.getFactory(); sub.getSubcircuit().circuitsUsingThis.remove(comp); } } fireEvent(CircuitEvent.ACTION_CLEAR, oldComps); } void mutatorRemove(Component c) { //logger.debug("mutatorRemove: {}", c); locker.checkForWritePermission("remove"); Annotated = false; MyNetList.clear(); if (c instanceof Wire) { wires.remove(c); } else { wires.remove(c); comps.remove(c); ComponentFactory factory = c.getFactory(); if (factory instanceof Clock) { clocks.remove(c); } else if (factory instanceof SubcircuitFactory) { SubcircuitFactory subcirc = (SubcircuitFactory) factory; subcirc.getSubcircuit().circuitsUsingThis.remove(c); } c.removeComponentListener(myComponentListener); } fireEvent(CircuitEvent.ACTION_REMOVE, c); } private void RemoveWrongLabels(String Label) { boolean HaveAChange = false; for (Component comp : comps) { AttributeSet attrs = comp.getAttributeSet(); if (attrs.containsAttribute(StdAttr.LABEL)) { String CompLabel = attrs.getValue(StdAttr.LABEL); if (Label.toUpperCase().equals(CompLabel.toUpperCase())) { attrs.setValue(StdAttr.LABEL, ""); HaveAChange = true; } } } /* we do not have to check the wires as (1) Wire is a reserved keyword, and (2) they cannot have a label */ if (HaveAChange) JOptionPane.showMessageDialog(null, "\""+Label+"\" : "+Strings.get("ComponentLabelCollisionError")); } public void removeCircuitListener(CircuitListener what) { listeners.remove(what); } // // action methods // public void setName(String name) { staticAttrs.setValue(CircuitAttributes.NAME_ATTR, name); } @Override public String toString() { return staticAttrs.getValue(CircuitAttributes.NAME_ATTR); } }