/******************************************************************************* * 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.lang.ref.WeakReference; import java.util.HashMap; import java.util.HashSet; import java.util.PriorityQueue; import java.util.Random; import com.cburch.logisim.comp.Component; import com.cburch.logisim.comp.ComponentDrawContext; import com.cburch.logisim.comp.EndData; import com.cburch.logisim.data.AttributeEvent; import com.cburch.logisim.data.AttributeListener; import com.cburch.logisim.data.Location; import com.cburch.logisim.data.Value; import com.cburch.logisim.file.Options; public class Propagator { private static class ComponentPoint { Component cause; Location loc; public ComponentPoint(Component cause, Location loc) { this.cause = cause; this.loc = loc; } @Override public boolean equals(Object other) { if (!(other instanceof ComponentPoint)) return false; ComponentPoint o = (ComponentPoint) other; return this.cause.equals(o.cause) && this.loc.equals(o.loc); } @Override public int hashCode() { return 31 * cause.hashCode() + loc.hashCode(); } } private static class Listener implements AttributeListener { WeakReference<Propagator> prop; public Listener(Propagator propagator) { prop = new WeakReference<Propagator>(propagator); } public void attributeListChanged(AttributeEvent e) { } public void attributeValueChanged(AttributeEvent e) { Propagator p = prop.get(); if (p == null) { e.getSource().removeAttributeListener(this); } else if (e.getAttribute().equals(Options.sim_rand_attr)) { p.updateRandomness(); } } } static class SetData implements Comparable<SetData> { int time; int serialNumber; CircuitState state; // state of circuit containing component Component cause; // component emitting the value Location loc; // the location at which value is emitted Value val; // value being emitted SetData next = null; private SetData(int time, int serialNumber, CircuitState state, Location loc, Component cause, Value val) { this.time = time; this.serialNumber = serialNumber; this.state = state; this.cause = cause; this.loc = loc; this.val = val; } public SetData cloneFor(CircuitState newState) { Propagator newProp = newState.getPropagator(); int dtime = newProp.clock - state.getPropagator().clock; SetData ret = new SetData(time + dtime, newProp.setDataSerialNumber, newState, loc, cause, val); newProp.setDataSerialNumber++; if (this.next != null) ret.next = this.next.cloneFor(newState); return ret; } public int compareTo(SetData o) { // Yes, these subtractions may overflow. This is intentional, as it // avoids potential wraparound problems as the counters increment. int ret = this.time - o.time; if (ret != 0) return ret; return this.serialNumber - o.serialNumber; } @Override public String toString() { return loc + ":" + val + "(" + cause + ")"; } } // // static methods // static Value computeValue(SetData causes) { if (causes == null) return Value.NIL; Value ret = causes.val; for (SetData n = causes.next; n != null; n = n.next) { ret = ret.combine(n.val); } return ret; } private CircuitState root; // root of state tree /** * The number of clock cycles to let pass before deciding that the circuit * is oscillating. */ private int simLimit = 1000; /** * On average, one out of every 2**simRandomShift propagations through a * component is delayed one step more than the component requests. This * noise is intended to address some circuits that would otherwise oscillate * within Logisim (though they wouldn't oscillate in practice). */ private volatile int simRandomShift; private PriorityQueue<SetData> toProcess = new PriorityQueue<SetData>(); private int clock = 0; private boolean isOscillating = false; private boolean oscAdding = false; private PropagationPoints oscPoints = new PropagationPoints(); private int ticks = 0; private Random noiseSource = new Random(); private int noiseCount = 0; private int setDataSerialNumber = 0; static int lastId = 0; int id = lastId++; public Propagator(CircuitState root) { this.root = root; Listener l = new Listener(this); root.getProject().getOptions().getAttributeSet() .addAttributeListener(l); updateRandomness(); } private SetData addCause(CircuitState state, SetData head, SetData data) { if (data.val == null) { // actually, it should be removed return removeCause(state, head, data.loc, data.cause); } HashMap<Location, SetData> causes = state.causes; // first check whether this is change of previous info. boolean replaced = false; for (SetData n = head; n != null; n = n.next) { if (n.cause == data.cause) { n.val = data.val; replaced = true; break; } } // otherwise, insert to list of causes if (!replaced) { if (head == null) { causes.put(data.loc, data); head = data; } else { data.next = head.next; head.next = data; } } return head; } // // private methods // void checkComponentEnds(CircuitState state, Component comp) { for (EndData end : comp.getEnds()) { Location loc = end.getLocation(); SetData oldHead = state.causes.get(loc); Value oldVal = computeValue(oldHead); SetData newHead = removeCause(state, oldHead, loc, comp); Value newVal = computeValue(newHead); Value wireVal = state.getValueByWire(loc); if (!newVal.equals(oldVal) || wireVal != null) { state.markPointAsDirty(loc); } if (wireVal != null) state.setValueByWire(loc, Value.NIL); } } private void clearDirtyComponents() { root.processDirtyComponents(); } private void clearDirtyPoints() { root.processDirtyPoints(); } public void drawOscillatingPoints(ComponentDrawContext context) { if (isOscillating) oscPoints.draw(context); } // // public methods // CircuitState getRootState() { return root; } public int getTickCount() { return ticks; } public boolean isOscillating() { return isOscillating; } boolean isPending() { return !toProcess.isEmpty(); } /* * TODO for the SimulatorPrototype class void step() { clock++; * * // propagate all values for this clock tick HashMap visited = new * HashMap(); // State -> set of ComponentPoints handled while * (!toProcess.isEmpty()) { SetData data; data = (SetData) toProcess.peek(); * if (data.time != clock) break; toProcess.remove(); CircuitState state = * data.state; * * // if it's already handled for this clock tick, continue HashSet handled * = (HashSet) visited.get(state); if (handled != null) { if * (!handled.add(new ComponentPoint(data.cause, data.loc))) continue; } else * { handled = new HashSet(); visited.put(state, handled); handled.add(new * ComponentPoint(data.cause, data.loc)); } * * if (oscAdding) oscPoints.add(state, data.loc); * * // change the information about value SetData oldHead = (SetData) * state.causes.get(data.loc); Value oldVal = computeValue(oldHead); SetData * newHead = addCause(state, oldHead, data); Value newVal = * computeValue(newHead); * * // if the value at point has changed, propagate it if * (!newVal.equals(oldVal)) { state.markPointAsDirty(data.loc); } } * * clearDirtyPoints(); clearDirtyComponents(); } */ void locationTouched(CircuitState state, Location loc) { if (oscAdding) oscPoints.add(state, loc); } public void propagate() { oscPoints.clear(); clearDirtyPoints(); clearDirtyComponents(); int oscThreshold = simLimit; int logThreshold = 3 * oscThreshold / 4; int iters = 0; while (!toProcess.isEmpty()) { iters++; if (iters < logThreshold) { stepInternal(null); } else if (iters < oscThreshold) { oscAdding = true; stepInternal(oscPoints); } else { isOscillating = true; oscAdding = false; return; } } isOscillating = false; oscAdding = false; oscPoints.clear(); } private SetData removeCause(CircuitState state, SetData head, Location loc, Component cause) { HashMap<Location, SetData> causes = state.causes; if (head == null) { ; } else if (head.cause == cause) { head = head.next; if (head == null) causes.remove(loc); else causes.put(loc, head); } else { SetData prev = head; SetData cur = head.next; while (cur != null) { if (cur.cause == cause) { prev.next = cur.next; break; } prev = cur; cur = cur.next; } } return head; } void reset() { toProcess.clear(); root.reset(); isOscillating = false; } // // package-protected helper methods // void setValue(CircuitState state, Location pt, Value val, Component cause, int delay) { if (cause instanceof Wire || cause instanceof Splitter) return; if (delay <= 0) { delay = 1; } int randomShift = simRandomShift; if (randomShift > 0) { // random noise is turned on // multiply the delay by 32 so that the random noise // only changes the delay by 3%. delay <<= randomShift; if (!(cause.getFactory() instanceof SubcircuitFactory)) { if (noiseCount > 0) { noiseCount--; } else { delay++; noiseCount = noiseSource.nextInt(1 << randomShift); } } } toProcess.add(new SetData(clock + delay, setDataSerialNumber, state, pt, cause, val)); /* * DEBUGGING - comment out Simulator.log(clock + ": set " + pt + " in " * + state + " to " + val + " by " + cause + " after " + delay); // */ setDataSerialNumber++; } void step(PropagationPoints changedPoints) { oscPoints.clear(); clearDirtyPoints(); clearDirtyComponents(); PropagationPoints oldOsc = oscPoints; oscAdding = changedPoints != null; oscPoints = changedPoints; stepInternal(changedPoints); oscAdding = false; oscPoints = oldOsc; } private void stepInternal(PropagationPoints changedPoints) { if (toProcess.isEmpty()) return; // update clock clock = toProcess.peek().time; // propagate all values for this clock tick HashMap<CircuitState, HashSet<ComponentPoint>> visited = new HashMap<CircuitState, HashSet<ComponentPoint>>(); while (true) { SetData data = toProcess.peek(); if (data == null || data.time != clock) break; toProcess.remove(); CircuitState state = data.state; // if it's already handled for this clock tick, continue HashSet<ComponentPoint> handled = visited.get(state); if (handled != null) { if (!handled.add(new ComponentPoint(data.cause, data.loc))) continue; } else { handled = new HashSet<ComponentPoint>(); visited.put(state, handled); handled.add(new ComponentPoint(data.cause, data.loc)); } /* * DEBUGGING - comment out Simulator.log(data.time + ": proc " + * data.loc + " in " + data.state + " to " + data.val + " by " + * data.cause); // */ if (changedPoints != null) changedPoints.add(state, data.loc); // change the information about value SetData oldHead = state.causes.get(data.loc); Value oldVal = computeValue(oldHead); SetData newHead = addCause(state, oldHead, data); Value newVal = computeValue(newHead); // if the value at point has changed, propagate it if (!newVal.equals(oldVal)) { state.markPointAsDirty(data.loc); } } clearDirtyPoints(); clearDirtyComponents(); } public boolean tick() { ticks++; return root.tick(ticks); } @Override public String toString() { return "Prop" + id; } private void updateRandomness() { Options opts = root.getProject().getOptions(); Object rand = opts.getAttributeSet().getValue(Options.sim_rand_attr); int val = ((Integer) rand).intValue(); int logVal = 0; while ((1 << logVal) < val) logVal++; simRandomShift = logVal; } }