integrated sync branch

This commit is contained in:
hneemann 2016-07-10 10:27:21 +02:00
commit 07b730a87b
33 changed files with 755 additions and 366 deletions

View File

@ -31,7 +31,7 @@ This are the main features of Digital:
- You can test your design by creating test cases and execute them.
- Many examples: From a transmission gate D-flipflop to a complete (simple) MIPS-like processor.
- Fast-run mode to perform a simulation without updating the HMI.
A simple processor can be clocked at 3MHz.
A simple processor can be clocked at 100kHz.
- Displaying a LST file when executing assembler programs within such a processor.
- Simple remote TCP interface to allow e.g. an assembler IDE to control the simulator.
- SVG export of circuits, including a LaTeX-compatible SVG version (see [ctan](https://www.ctan.org/tex-archive/info/svg-inkscape))
@ -105,7 +105,7 @@ Logisim works somewhat different, which sometimes leads to surprises like unexpe
If a complete processors is simulated, it is possible to calculate the simulation without an update of the
graphical representation.
A simple processor (see example) can so simulated with a roughly 3MHz clock (Intel® Core ™ i5-3230M CPU @ 2.60GHz)
A simple processor (see example) can so simulated with a roughly 100kHz clock (Intel® Core ™ i5-3230M CPU @ 2.60GHz)
which is suitable also for more complex exercises.
There is a break gate having a single input. If this input changes from low to high this quick run is stopped.
In this way, an assembler instruction BRK can be implemented, which then can be used to insert break points

View File

@ -26,7 +26,7 @@ Folgende Features zeichnen Digital aus:
- Analyse und Synthese von kombinatorischen Schaltungen und Schaltwerken.
- Viele Beispiele: Vom Transmision-Gate D-FF bis zum kompletten MIPS-ähnlichem Prozessor.
- Fast-Run-Mode um eine Simulation ohne Aktualisierung des HMI durchzuführen.
Ein einfacher Prozessor kann mit 3MHz getaktet werden.
Ein einfacher Prozessor kann mit 100kHz getaktet werden.
- Anzeige von LST-Files bei der Ausführung von Assembler-Programmen.
- Einfache Remote TCP-Schnittstelle um z.B. mit einer Assembler-IDE den Simulator zu steuern.
- SVG-Export von Schaltungen, incl. einer LaTeX-tauglichen SVG-Variante (siehe [ctan](ftp://ftp.fau.de/ctan/info/svg-inkscape/InkscapePDFLaTeX.pdf))
@ -97,7 +97,7 @@ Logisim arbeitet hier etwas anders, was gelegentlich zu Überraschungen führt,
### Performance ###
Werden komplette Prozessoren simuliert, ist es möglich die Simulation zu berechnen ohne die grafische Anzeige zu aktualisieren.
Ein einfacher Prozessor (siehe Beispiel) lässt sich so mit etwa 3MHz takten (Intel® Core™ i5-3230M CPU @ 2.60GHz) was auch für
Ein einfacher Prozessor (siehe Beispiel) lässt sich so mit etwa 100kHz takten (Intel® Core™ i5-3230M CPU @ 2.60GHz) was auch für
komplexere Übungen ausreichend ist.
Es gibt ein Break-Gatter welches einen einzelnen Eingang hat. Wechselt dieser Eingang von low auf high wird dieser
schnelle Lauf beendet. Auf diese Weise lässt sich eine Assembler-Anweisung BRK implementieren, womit sich dann Break-Points

View File

@ -14,7 +14,7 @@
<int>16</int>
</entry>
</elementAttributes>
<pos x="180" y="220"/>
<pos x="200" y="220"/>
</visualElement>
<visualElement>
<elementName>In</elementName>
@ -24,7 +24,7 @@
<string>R</string>
</entry>
</elementAttributes>
<pos x="180" y="460"/>
<pos x="200" y="460"/>
</visualElement>
<visualElement>
<elementName>Comparator</elementName>
@ -101,7 +101,7 @@
<string>C</string>
</entry>
</elementAttributes>
<pos x="180" y="540"/>
<pos x="200" y="540"/>
</visualElement>
<visualElement>
<elementName>And</elementName>
@ -160,6 +160,10 @@
<visualElement>
<elementName>D_FF</elementName>
<elementAttributes>
<entry>
<string>valueIsProbe</string>
<boolean>true</boolean>
</entry>
<entry>
<string>Label</string>
<string>Bank-FF</string>
@ -175,7 +179,7 @@
<string>W</string>
</entry>
</elementAttributes>
<pos x="180" y="380"/>
<pos x="200" y="380"/>
</visualElement>
<visualElement>
<elementName>Splitter</elementName>
@ -256,7 +260,7 @@
<p2 x="460" y="520"/>
</wire>
<wire>
<p1 x="180" y="460"/>
<p1 x="200" y="460"/>
<p2 x="580" y="460"/>
</wire>
<wire>
@ -264,7 +268,7 @@
<p2 x="460" y="300"/>
</wire>
<wire>
<p1 x="220" y="300"/>
<p1 x="240" y="300"/>
<p2 x="360" y="300"/>
</wire>
<wire>
@ -292,7 +296,7 @@
<p2 x="680" y="180"/>
</wire>
<wire>
<p1 x="220" y="180"/>
<p1 x="240" y="180"/>
<p2 x="580" y="180"/>
</wire>
<wire>
@ -324,11 +328,11 @@
<p2 x="580" y="220"/>
</wire>
<wire>
<p1 x="180" y="220"/>
<p2 x="220" y="220"/>
<p1 x="200" y="220"/>
<p2 x="240" y="220"/>
</wire>
<wire>
<p1 x="220" y="220"/>
<p1 x="240" y="220"/>
<p2 x="360" y="220"/>
</wire>
<wire>
@ -336,7 +340,7 @@
<p2 x="720" y="380"/>
</wire>
<wire>
<p1 x="180" y="380"/>
<p1 x="200" y="380"/>
<p2 x="400" y="380"/>
</wire>
<wire>
@ -348,7 +352,7 @@
<p2 x="580" y="540"/>
</wire>
<wire>
<p1 x="180" y="540"/>
<p1 x="200" y="540"/>
<p2 x="340" y="540"/>
</wire>
<wire>
@ -367,6 +371,14 @@
<p1 x="400" y="380"/>
<p2 x="400" y="560"/>
</wire>
<wire>
<p1 x="240" y="180"/>
<p2 x="240" y="220"/>
</wire>
<wire>
<p1 x="240" y="220"/>
<p2 x="240" y="300"/>
</wire>
<wire>
<p1 x="820" y="400"/>
<p2 x="820" y="600"/>
@ -395,14 +407,6 @@
<p1 x="680" y="180"/>
<p2 x="680" y="360"/>
</wire>
<wire>
<p1 x="220" y="180"/>
<p2 x="220" y="220"/>
</wire>
<wire>
<p1 x="220" y="220"/>
<p2 x="220" y="300"/>
</wire>
<wire>
<p1 x="540" y="520"/>
<p2 x="540" y="600"/>

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ public class TruthTableTableModel implements TableModel {
public static final String[] STATENAMES = new String[]{"0", "1", "x"};
private final TruthTable truthTable;
private ArrayList<TableModelListener> listeners = new ArrayList<>();
private final ArrayList<TableModelListener> listeners = new ArrayList<>();
/**
* Creates a new instance

View File

@ -49,6 +49,7 @@ public class SpeedTest {
for (int i = 0; i < LOOPCOUNTER; i++) {
state = 1 - state;
clockValue.setValue(state);
model.doStep();
}
loops++;
aktTime = System.currentTimeMillis();

View File

@ -77,7 +77,7 @@ public final class Keys {
*/
public static final Key<Integer> FREQUENCY
= new Key.KeyInteger("Frequency", 1)
.setComboBoxValues(new Integer[]{1, 2, 5, 10, 20, 50, 100, 200, 500})
.setComboBoxValues(new Integer[]{1, 2, 5, 10, 20, 50, 100, 200, 500, 5000, 50000, 500000})
.setMin(1);
/**
* the bit count of a muxer or decoder

View File

@ -20,7 +20,7 @@ public class DataField {
private long[] data;
private final int bits;
private transient ArrayList<DataListener> listeners;
private final transient ArrayList<DataListener> listeners = new ArrayList<>();
/**
* Creates a new DataField
@ -158,9 +158,9 @@ public class DataField {
* @param l the listener
*/
public void addListener(DataListener l) {
if (listeners == null)
listeners = new ArrayList<>();
listeners.add(l);
synchronized (listeners) {
listeners.add(l);
}
}
/**
@ -169,12 +169,9 @@ public class DataField {
* @param l the listener to remove
*/
public void removeListener(DataListener l) {
if (listeners == null)
return;
listeners.remove(l);
if (listeners.isEmpty())
listeners = null;
synchronized (listeners) {
listeners.remove(l);
}
}
/**
@ -183,11 +180,10 @@ public class DataField {
* @param addr the address which value has changed
*/
public void fireChanged(int addr) {
if (listeners == null)
return;
for (DataListener l : listeners)
l.valueChanged(addr);
synchronized (listeners) {
for (DataListener l : listeners)
l.valueChanged(addr);
}
}
/**

View File

@ -6,6 +6,7 @@ import de.neemann.digital.draw.graphics.*;
import de.neemann.digital.draw.shapes.*;
import de.neemann.digital.draw.shapes.Shape;
import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.sync.Sync;
import javax.swing.*;
import java.awt.*;
@ -261,9 +262,9 @@ public class VisualElement implements Drawable, Moveable, AttributeListener {
* @param pos the position
* @return true if model is changed
*/
public boolean elementClicked(CircuitComponent cc, Point pos) {
public boolean elementClicked(CircuitComponent cc, Point pos, Sync modelSync) {
if (interactor != null)
return interactor.clicked(cc, pos, ioState, element);
return interactor.clicked(cc, pos, ioState, element, modelSync);
else
return false;
}
@ -276,9 +277,9 @@ public class VisualElement implements Drawable, Moveable, AttributeListener {
* @param pos the position
* @return true if model is changed
*/
public boolean elementPressed(CircuitComponent cc, Point pos) {
public boolean elementPressed(CircuitComponent cc, Point pos, Sync modelSync) {
if (interactor != null)
return interactor.pressed(cc, pos, ioState, element);
return interactor.pressed(cc, pos, ioState, element, modelSync);
else
return false;
}
@ -291,9 +292,9 @@ public class VisualElement implements Drawable, Moveable, AttributeListener {
* @param pos the position
* @return true if model is changed
*/
public boolean elementReleased(CircuitComponent cc, Point pos) {
public boolean elementReleased(CircuitComponent cc, Point pos, Sync modelSync) {
if (interactor != null)
return interactor.released(cc, pos, ioState, element);
return interactor.released(cc, pos, ioState, element, modelSync);
else
return false;
}

View File

@ -4,24 +4,28 @@ import de.neemann.digital.core.*;
import de.neemann.digital.core.wiring.Clock;
import de.neemann.digital.gui.ErrorStopper;
import de.neemann.digital.gui.GuiModelObserver;
import de.neemann.digital.gui.StatusInterface;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang;
import javax.swing.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* The real time clock which is used to fire the models clocks with realtime signals
*
* @author hneemann
*/
public class RealTimeClock implements ModelStateObserver {
private final Model model;
private final ScheduledThreadPoolExecutor executor;
private final ErrorStopper stopper;
private final Sync modelSync;
private final StatusInterface status;
private final int frequency;
private final ObservableValue output;
private ScheduledFuture<?> timer;
private Runner runner;
/**
* Creates a new real time clock
@ -29,11 +33,14 @@ public class RealTimeClock implements ModelStateObserver {
* @param model the model
* @param clock the clock element which is modify
* @param executor the executor used to schedule the update
* @param status allows sending messages to the status line
*/
public RealTimeClock(Model model, Clock clock, ScheduledThreadPoolExecutor executor, ErrorStopper stopper) {
public RealTimeClock(Model model, Clock clock, ScheduledThreadPoolExecutor executor, ErrorStopper stopper, Sync modelSync, StatusInterface status) {
this.model = model;
this.executor = executor;
this.stopper = stopper;
this.modelSync = modelSync;
this.status = status;
int f = clock.getFrequency();
if (f < 1) f = 1;
this.frequency = f;
@ -44,27 +51,96 @@ public class RealTimeClock implements ModelStateObserver {
public void handleEvent(ModelEvent event) {
switch (event) {
case STARTED:
if (frequency > 50) // if frequency is high it is not necessary to update the GUI at every clock
output.removeObserver(GuiModelObserver.class);
if (frequency > 50) // if frequency is high it is not necessary to update the GUI at every clock change
modelSync.access(() -> output.removeObserver(GuiModelObserver.class));
int delay = 500 / frequency;
if (delay < 1) delay = 1;
int delay = 500000 / frequency;
if (delay < 10)
runner = new ThreadRunner();
else
runner = new RealTimeRunner(delay);
break;
case STOPPED:
if (runner != null)
runner.stop();
break;
}
}
timer = executor.scheduleAtFixedRate(() -> SwingUtilities.invokeLater(() -> {
output.setValue(1 - output.getValue());
interface Runner {
void stop();
}
/**
* runs with defined rate
*/
private class RealTimeRunner implements Runner {
private final ScheduledFuture<?> timer;
RealTimeRunner(int delay) {
timer = executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
model.doStep();
modelSync.accessNEx(() -> {
output.setValue(1 - output.getValue());
model.doStep();
});
} catch (NodeException e1) {
stopper.showErrorAndStopModel(Lang.get("msg_clockError"), e1);
timer.cancel(false);
}
}), delay, delay, TimeUnit.MILLISECONDS);
}
}, delay, delay, TimeUnit.MICROSECONDS);
}
break;
case STOPPED:
if (timer != null)
timer.cancel(false);
break;
@Override
public void stop() {
if (timer != null)
timer.cancel(false);
}
}
/**
* runs at fast as possible!
*/
private class ThreadRunner implements Runner {
private final Thread thread;
ThreadRunner() {
thread = new Thread() {
@Override
public void run() {
System.out.println("thread start");
long time = System.currentTimeMillis();
long counter = 0;
try {
while (!interrupted()) {
modelSync.accessNEx(() -> {
output.setValue(1 - output.getValue());
model.doStep();
});
counter++;
}
} catch (NodeException e1) {
stopper.showErrorAndStopModel(Lang.get("msg_clockError"), e1);
}
time = System.currentTimeMillis() - time;
status.setStatus(counter / time / 2 + "kHz");
System.out.println("thread end");
}
};
thread.setDaemon(true);
thread.start();
}
@Override
public void stop() {
thread.interrupt();
}
}
}

View File

@ -11,6 +11,7 @@ import de.neemann.digital.draw.elements.Pins;
import de.neemann.digital.draw.graphics.*;
import de.neemann.digital.draw.graphics.Polygon;
import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*;
@ -52,21 +53,25 @@ public class ButtonShape implements Shape {
ioState.getOutput(0).addObserverToValue(guiObserver);
return new InteractorInterface() {
@Override
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) {
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
return false;
}
@Override
public boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element) {
ObservableValue value = ioState.getOutput(0);
value.setValue(1);
public boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
modelSync.access(() -> {
ObservableValue value = ioState.getOutput(0);
value.setValue(1);
});
return true;
}
@Override
public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element) {
ObservableValue value = ioState.getOutput(0);
value.setValue(0);
public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
modelSync.access(() -> {
ObservableValue value = ioState.getOutput(0);
value.setValue(0);
});
return true;
}
};

View File

@ -11,6 +11,7 @@ import de.neemann.digital.draw.elements.Pins;
import de.neemann.digital.draw.graphics.*;
import de.neemann.digital.draw.graphics.Polygon;
import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*;
@ -18,6 +19,7 @@ import static de.neemann.digital.draw.shapes.OutputShape.SIZE;
/**
* The Clock shape
*
* @author hneemann
*/
public class ClockShape implements Shape {
@ -49,10 +51,12 @@ public class ClockShape implements Shape {
ioState.getOutput(0).addObserverToValue(guiObserver); // necessary to replot wires also if component itself does not depend on state
return new Interactor() {
@Override
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) {
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
ObservableValue value = ioState.getOutput(0);
if (value.getBits() == 1) {
value.setValue(1 - value.getValue());
modelSync.access(() -> {
value.setValue(1 - value.getValue());
});
return true;
}
return false;

View File

@ -16,6 +16,7 @@ import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.components.OrderMerger;
import de.neemann.digital.gui.components.data.DataSet;
import de.neemann.digital.gui.components.data.DataSetObserver;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*;
import java.util.ArrayList;
@ -52,7 +53,7 @@ public class DataShape implements Shape {
public Interactor applyStateMonitor(IOState ioState, Observer guiObserver) {
return new Interactor() {
@Override
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) {
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
dataSet.clear();
return false;
}

View File

@ -12,6 +12,7 @@ import de.neemann.digital.draw.graphics.*;
import de.neemann.digital.draw.graphics.Polygon;
import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.components.SingleValueDialog;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*;
@ -52,17 +53,19 @@ public class InputShape implements Shape {
ioState.getOutput(0).addObserverToValue(guiObserver);
return new Interactor() {
@Override
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) {
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
ObservableValue value = ioState.getOutput(0);
if (value.getBits() == 1) {
if (value.supportsHighZ()) {
if (value.isHighZ()) value.set(0, false);
else if (value.getValue() == 0) value.setValue(1);
else value.set(0, true);
} else
value.setValue(1 - value.getValue());
modelSync.access(() -> {
if (value.supportsHighZ()) {
if (value.isHighZ()) value.set(0, false);
else if (value.getValue() == 0) value.setValue(1);
else value.set(0, true);
} else
value.setValue(1 - value.getValue());
});
} else {
SingleValueDialog.editValue(pos, value);
SingleValueDialog.editValue(pos, value, modelSync);
}
return true;
}

View File

@ -3,6 +3,7 @@ package de.neemann.digital.draw.shapes;
import de.neemann.digital.core.element.Element;
import de.neemann.digital.draw.elements.IOState;
import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*;
@ -17,12 +18,12 @@ import java.awt.*;
public abstract class Interactor implements InteractorInterface {
@Override
public boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element) {
public boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
return false;
}
@Override
public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element) {
public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
return false;
}
}

View File

@ -3,6 +3,7 @@ package de.neemann.digital.draw.shapes;
import de.neemann.digital.core.element.Element;
import de.neemann.digital.draw.elements.IOState;
import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*;
@ -23,7 +24,7 @@ public interface InteractorInterface {
* @param ioState the state of the element
* @return true if model is changed
*/
boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element);
boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync);
/**
* Called mouse is pressed on running model
@ -33,7 +34,7 @@ public interface InteractorInterface {
* @param ioState the state of the element
* @return true if model is changed
*/
boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element);
boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync);
/**
* Called mouse is released on running model
@ -43,5 +44,5 @@ public interface InteractorInterface {
* @param ioState the state of the element
* @return true if model is changed
*/
boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element);
boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync);
}

View File

@ -8,6 +8,7 @@ import de.neemann.digital.core.memory.RAMInterface;
import de.neemann.digital.draw.elements.IOState;
import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.components.DataEditor;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*;
@ -32,10 +33,10 @@ public class RAMShape extends GenericShape {
public Interactor applyStateMonitor(IOState ioState, Observer guiObserver) {
return new Interactor() {
@Override
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) {
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
if (element instanceof RAMInterface) {
DataField dataField = ((RAMInterface) element).getMemory();
new DataEditor(cc, dataField).showDialog();
new DataEditor(cc, dataField, modelSync).showDialog();
}
return false;
}

View File

@ -5,6 +5,8 @@ import de.neemann.digital.core.ModelStateObserver;
import de.neemann.digital.core.Observer;
import de.neemann.digital.gui.components.CircuitComponent;
import javax.swing.*;
/**
* This observer is added to the model if real time timers are started.
* This observer paints the CircuitComponent after a step is calculated.
@ -18,6 +20,7 @@ public class GuiModelObserver implements Observer, ModelStateObserver {
private final CircuitComponent component;
private final ModelEvent type;
private boolean changed = false;
private volatile boolean paintPending;
/**
* Creates a new instance.
@ -38,7 +41,13 @@ public class GuiModelObserver implements Observer, ModelStateObserver {
@Override
public void handleEvent(ModelEvent event) {
if (changed && event == type) {
component.paintImmediately(0, 0, component.getWidth(), component.getHeight());
if (!paintPending) {
paintPending = true;
SwingUtilities.invokeLater(() -> {
paintPending = false;
component.paintImmediately(0, 0, component.getWidth(), component.getHeight());
});
}
changed = false;
}
}

View File

@ -34,6 +34,9 @@ import de.neemann.digital.gui.remote.DigitalHandler;
import de.neemann.digital.gui.remote.RemoteSever;
import de.neemann.digital.gui.state.State;
import de.neemann.digital.gui.state.StateManager;
import de.neemann.digital.gui.sync.LockSync;
import de.neemann.digital.gui.sync.NoSync;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang;
import de.neemann.gui.*;
@ -56,7 +59,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
*
* @author hneemann
*/
public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, ErrorStopper, FileHistory.OpenInterface, DigitalRemoteInterface {
public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, ErrorStopper, FileHistory.OpenInterface, DigitalRemoteInterface, StatusInterface {
private static final ArrayList<Key> ATTR_LIST = new ArrayList<>();
private static boolean experimental;
@ -106,7 +109,9 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
private File filename;
private FileHistory fileHistory;
private Sync modelSync;
private Model model;
private ModelCreator modelCreator;
private boolean realtimeClockRunning;
@ -615,7 +620,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
public void enter() {
super.enter();
clearModelDescription();
circuitComponent.setModeAndReset(false);
circuitComponent.setModeAndReset(false, NoSync.INST);
doStep.setEnabled(false);
runToBreakAction.setEnabled(false);
}
@ -662,7 +667,6 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
private boolean createAndStartModel(boolean globalRunClock, ModelEvent updateEvent) {
try {
circuitComponent.removeHighLighted();
circuitComponent.setModeAndReset(true);
modelCreator = new ModelCreator(circuitComponent.getCircuit(), library);
@ -674,12 +678,19 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
statusLabel.setText(Lang.get("msg_N_nodes", model.size()));
realtimeClockRunning = false;
modelSync = null;
if (globalRunClock)
for (Clock c : model.getClocks())
if (c.getFrequency() > 0) {
model.addObserver(new RealTimeClock(model, c, timerExecuter, this));
if (modelSync == null)
modelSync = new LockSync();
model.addObserver(new RealTimeClock(model, c, timerExecuter, this, modelSync, this));
realtimeClockRunning = true;
}
if (modelSync == null)
modelSync = NoSync.INST;
circuitComponent.setModeAndReset(true, modelSync);
if (realtimeClockRunning) {
// if clock is running, enable automatic update of gui
@ -695,12 +706,12 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
List<String> ordering = circuitComponent.getCircuit().getMeasurementOrdering();
if (settings.get(Keys.SHOW_DATA_TABLE))
windowPosManager.register("probe", new ProbeDialog(this, model, updateEvent, ordering)).setVisible(true);
windowPosManager.register("probe", new ProbeDialog(this, model, updateEvent, ordering, modelSync)).setVisible(true);
if (settings.get(Keys.SHOW_DATA_GRAPH))
windowPosManager.register("dataset", new DataSetDialog(this, model, updateEvent == ModelEvent.MICROSTEP, ordering)).setVisible(true);
windowPosManager.register("dataset", new DataSetDialog(this, model, updateEvent == ModelEvent.MICROSTEP, ordering, modelSync)).setVisible(true);
if (settings.get(Keys.SHOW_DATA_GRAPH_MICRO))
windowPosManager.register("datasetMicro", new DataSetDialog(this, model, true, ordering)).setVisible(true);
windowPosManager.register("datasetMicro", new DataSetDialog(this, model, true, ordering, modelSync)).setVisible(true);
int i = 0;
for (ROM rom : model.findNode(ROM.class))
@ -830,6 +841,11 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
return windowPosManager;
}
@Override
public void setStatus(String message) {
SwingUtilities.invokeLater(() -> statusLabel.setText(message));
}
private class FullStepObserver implements Observer {
private final Model model;
@ -840,8 +856,10 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
@Override
public void hasChanged() {
try {
model.fireManualChangeEvent();
model.doStep();
modelSync.accessNEx(() -> {
model.fireManualChangeEvent();
model.doStep();
});
circuitComponent.repaint();
} catch (NodeException | RuntimeException e) {
showErrorAndStopModel(Lang.get("msg_errorCalculatingStep"), e);

View File

@ -0,0 +1,15 @@
package de.neemann.digital.gui;
/**
* Interface to acess the status line
*
* @author hneemann
*/
public interface StatusInterface {
/**
* Set message to the status line
*
* @param message the message
*/
void setStatus(String message);
}

View File

@ -13,6 +13,8 @@ import de.neemann.digital.draw.shapes.ShapeFactory;
import de.neemann.digital.gui.LibrarySelector;
import de.neemann.digital.gui.Main;
import de.neemann.digital.gui.SavedListener;
import de.neemann.digital.gui.sync.NoSync;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang;
import de.neemann.gui.IconCreator;
import de.neemann.gui.ToolTipAction;
@ -73,6 +75,7 @@ public class CircuitComponent extends JComponent {
private AffineTransform transform = new AffineTransform();
private Observer manualChangeObserver;
private Vector lastMousePos;
private Sync modelSync;
/**
@ -248,13 +251,15 @@ public class CircuitComponent extends JComponent {
*
* @param runMode true if running, false if editing
*/
public void setModeAndReset(boolean runMode) {
public void setModeAndReset(boolean runMode, Sync modelSync) {
this.modelSync = modelSync;
if (runMode)
mouseRun.activate();
else
else {
mouseNormal.activate();
circuit.clearState();
}
requestFocusInWindow();
circuit.clearState();
}
/**
@ -372,7 +377,7 @@ public class CircuitComponent extends JComponent {
public void setCircuit(Circuit circuit) {
this.circuit = circuit;
fitCircuit();
setModeAndReset(false);
setModeAndReset(false, NoSync.INST);
}
/**
@ -982,7 +987,7 @@ public class CircuitComponent extends JComponent {
private interface Actor {
boolean interact(CircuitComponent cc, Point p);
boolean interact(CircuitComponent cc, Point p, Sync modelSync);
}
private final class MouseControllerRun extends MouseController {
@ -1016,7 +1021,7 @@ public class CircuitComponent extends JComponent {
private void interact(MouseEvent e, Actor actor) {
Point p = new Point(e.getX(), e.getY());
SwingUtilities.convertPointToScreen(p, CircuitComponent.this);
boolean modelHasChanged = actor.interact(CircuitComponent.this, p);
boolean modelHasChanged = actor.interact(CircuitComponent.this, p, modelSync);
if (modelHasChanged) {
if (manualChangeObserver != null)
manualChangeObserver.hasChanged();

View File

@ -3,6 +3,7 @@ package de.neemann.digital.gui.components;
import de.neemann.digital.core.element.ElementAttributes;
import de.neemann.digital.core.element.Keys;
import de.neemann.digital.core.memory.DataField;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang;
import javax.swing.*;
@ -32,8 +33,8 @@ public class DataEditor extends JDialog {
* @param parent the parent
* @param dataField the data to edit
*/
public DataEditor(JComponent parent, DataField dataField) {
this(parent, dataField, null);
public DataEditor(JComponent parent, DataField dataField, Sync modelSync) {
this(parent, dataField, null, modelSync);
}
/**
@ -43,7 +44,7 @@ public class DataEditor extends JDialog {
* @param dataField the data to edit
* @param attr uset to get bit sizes
*/
public DataEditor(JComponent parent, DataField dataField, ElementAttributes attr) {
public DataEditor(JComponent parent, DataField dataField, ElementAttributes attr, Sync modelSync) {
super(SwingUtilities.windowForComponent(parent), Lang.get("key_Data"), attr == null ? ModalityType.MODELESS : ModalityType.APPLICATION_MODAL);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
@ -70,7 +71,7 @@ public class DataEditor extends JDialog {
if (size <= 16) cols = 1;
else if (size <= 128) cols = 8;
MyTableModel dm = new MyTableModel(this.dataField, cols);
MyTableModel dm = new MyTableModel(this.dataField, cols, modelSync);
JTable table = new JTable(dm);
table.setDefaultRenderer(MyLong.class, new MyLongRenderer(bits));
getContentPane().add(new JScrollPane(table));
@ -122,12 +123,14 @@ public class DataEditor extends JDialog {
private final static class MyTableModel implements TableModel, DataField.DataListener {
private final DataField dataField;
private final int cols;
private final Sync modelSync;
private final int rows;
private ArrayList<TableModelListener> listener = new ArrayList<>();
private MyTableModel(DataField dataField, int cols) {
private MyTableModel(DataField dataField, int cols, Sync modelSync) {
this.dataField = dataField;
this.cols = cols;
this.modelSync = modelSync;
rows = (dataField.size() - 1) / cols + 1;
}
@ -171,7 +174,9 @@ public class DataEditor extends JDialog {
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
dataField.setData(rowIndex * cols + (columnIndex - 1), ((MyLong) aValue).getValue());
modelSync.access(()->{
dataField.setData(rowIndex * cols + (columnIndex - 1), ((MyLong) aValue).getValue());
});
}
private void fireEvent(TableModelEvent e) {

View File

@ -8,6 +8,7 @@ import de.neemann.digital.core.memory.DataField;
import de.neemann.digital.core.memory.ROM;
import de.neemann.digital.gui.components.test.TestData;
import de.neemann.digital.gui.components.test.TestDataEditor;
import de.neemann.digital.gui.sync.NoSync;
import de.neemann.digital.lang.Lang;
import de.neemann.gui.ErrorMessage;
import de.neemann.gui.ToolTipAction;
@ -15,6 +16,7 @@ import de.neemann.gui.language.Bundle;
import de.neemann.gui.language.Language;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.IOException;
@ -234,7 +236,7 @@ public final class EditorFactory {
panel.add(new ToolTipAction(Lang.get("btn_edit")) {
@Override
public void actionPerformed(ActionEvent e) {
DataEditor de = new DataEditor(panel, data, attr);
DataEditor de = new DataEditor(panel, data, attr, NoSync.INST);
if (de.showDialog()) {
data = de.getDataField();
}
@ -245,6 +247,7 @@ public final class EditorFactory {
public void actionPerformed(ActionEvent e) {
JFileChooser fc = new JFileChooser();
fc.setSelectedFile(attr.getFile(ROM.LAST_DATA_FILE_KEY));
fc.setFileFilter(new FileNameExtensionFilter("hex", "hex"));
if (fc.showOpenDialog(panel) == JFileChooser.APPROVE_OPTION) {
attr.setFile(ROM.LAST_DATA_FILE_KEY, fc.getSelectedFile());
try {

View File

@ -4,6 +4,7 @@ import de.neemann.digital.core.Model;
import de.neemann.digital.core.ModelEvent;
import de.neemann.digital.core.ModelStateObserver;
import de.neemann.digital.core.Signal;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang;
import javax.swing.*;
@ -27,12 +28,13 @@ public class ProbeDialog extends JDialog implements ModelStateObserver {
/**
* Creates a new instance
*
* @param owner the owner
* @param model the model to run
* @param type the event type which fires a dialog repaint
* @param ordering the names list used to order the measurement values
* @param owner the owner
* @param model the model to run
* @param type the event type which fires a dialog repaint
* @param ordering the names list used to order the measurement values
* @param modelSync
*/
public ProbeDialog(Frame owner, Model model, ModelEvent type, List<String> ordering) {
public ProbeDialog(Frame owner, Model model, ModelEvent type, List<String> ordering, Sync modelSync) {
super(owner, Lang.get("win_measures"), false);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.type = type;
@ -53,12 +55,12 @@ public class ProbeDialog extends JDialog implements ModelStateObserver {
addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
model.addObserver(ProbeDialog.this);
modelSync.access(() -> model.addObserver(ProbeDialog.this));
}
@Override
public void windowClosed(WindowEvent e) {
model.removeObserver(ProbeDialog.this);
modelSync.access(() -> model.removeObserver(ProbeDialog.this));
}
});
@ -71,7 +73,7 @@ public class ProbeDialog extends JDialog implements ModelStateObserver {
@Override
public void handleEvent(ModelEvent event) {
if (event == type || event == ModelEvent.MANUALCHANGE) {
tableModel.fireChanged();
SwingUtilities.invokeLater(tableModel::fireChanged);
}
}

View File

@ -1,6 +1,7 @@
package de.neemann.digital.gui.components;
import de.neemann.digital.core.ObservableValue;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang;
import javax.swing.*;
@ -60,16 +61,20 @@ public final class SingleValueDialog extends JDialog {
* @param pos the position to pop up the dialog
* @param value the value to edit
*/
public static void editValue(Point pos, ObservableValue value) {
public static void editValue(Point pos, ObservableValue value, Sync modelSync) {
String ret = new SingleValueDialog(pos, value.getValueString()).showDialog();
if (ret != null) {
ret = ret.trim();
if (ret.equals("?") && value.supportsHighZ()) {
value.setHighZ(true);
modelSync.access(() -> {
value.setHighZ(true);
});
} else {
try {
long l = Long.decode(ret);
value.set(l, false);
modelSync.access(() -> {
value.set(l, false);
});
} catch (NumberFormatException e) {
}

View File

@ -75,7 +75,7 @@ public class DataSet implements Iterable<DataSample>, Drawable {
*
* @param sample the DataSample
*/
public void add(DataSample sample) {
synchronized void add(DataSample sample) {
while (samples.size() >= maxSize)
samples.remove(0);
@ -154,7 +154,7 @@ public class DataSet implements Iterable<DataSample>, Drawable {
@Override
public void drawTo(Graphic g, boolean highLight) {
synchronized public void drawTo(Graphic g, boolean highLight) {
int x = getTextBorder();
int yOffs = SIZE / 2;

View File

@ -5,6 +5,7 @@ import de.neemann.digital.core.ModelEvent;
import de.neemann.digital.core.ModelStateObserver;
import de.neemann.digital.core.Signal;
import de.neemann.digital.gui.components.OrderMerger;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang;
import de.neemann.gui.ErrorMessage;
import de.neemann.gui.ToolTipAction;
@ -29,6 +30,7 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
private static final int MAX_SAMPLE_SIZE = 1000;
private final DataSetComponent dsc;
private final JScrollPane scrollPane;
private final Sync modelSync;
private DataSet dataSet;
private DataSetObserver dataSetObserver;
@ -40,8 +42,9 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
* @param microStep true the event type which triggers a new DataSample
* @param ordering the ordering of the measurement values
*/
public DataSetDialog(Frame owner, Model model, boolean microStep, List<String> ordering) {
public DataSetDialog(Frame owner, Model model, boolean microStep, List<String> ordering, Sync modelSync) {
super(owner, createTitle(microStep), false);
this.modelSync = modelSync;
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setAlwaysOnTop(true);
@ -64,12 +67,12 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
model.addObserver(DataSetDialog.this);
modelSync.access(() -> model.addObserver(DataSetDialog.this));
}
@Override
public void windowClosed(WindowEvent e) {
model.removeObserver(DataSetDialog.this);
modelSync.access(() -> model.removeObserver(DataSetDialog.this));
}
});
@ -111,10 +114,14 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
@Override
public void handleEvent(ModelEvent event) {
dataSetObserver.handleEvent(event);
dsc.revalidate();
dsc.repaint();
JScrollBar bar = scrollPane.getHorizontalScrollBar();
SwingUtilities.invokeLater(() -> bar.setValue(bar.getMaximum()));
modelSync.access(() -> {
dataSetObserver.handleEvent(event);
});
SwingUtilities.invokeLater(() -> {
dsc.revalidate();
dsc.repaint();
JScrollBar bar = scrollPane.getHorizontalScrollBar();
bar.setValue(bar.getMaximum());
});
}
}

View File

@ -17,6 +17,10 @@ import javax.swing.*;
import static de.neemann.digital.core.element.PinInfo.input;
/**
* Graphic card.
* Mostly a RAM module with an additional input bit which selects the visible bank.
* So you can use double buffering.
*
* @author hneemann
*/
public class GraphicCard extends Node implements Element, RAMInterface {

View File

@ -83,7 +83,6 @@ public class ROMListingDialog extends JDialog implements Observer {
list.setSelectedIndex(line);
});
}
lastAddr = addr;
}
}

View File

@ -0,0 +1,41 @@
package de.neemann.digital.gui.sync;
import de.neemann.digital.core.NodeException;
import java.util.concurrent.locks.ReentrantLock;
/**
* Calls the runnables under a reentrant lock.
*
* @author hneemann
*/
public class LockSync implements Sync {
private final ReentrantLock lock;
/**
* Creates a new instance
*/
public LockSync() {
lock = new ReentrantLock();
}
@Override
public void access(Runnable run) {
lock.lock();
try {
run.run();
} finally {
lock.unlock();
}
}
@Override
public void accessNEx(Sync.ModelRun run) throws NodeException {
lock.lock();
try {
run.run();
} finally {
lock.unlock();
}
}
}

View File

@ -0,0 +1,29 @@
package de.neemann.digital.gui.sync;
import de.neemann.digital.core.NodeException;
/**
* Implementation which is used in runtim clock does not run.
* Does no synchronisation at all.
*
* @author hneemann
*/
public final class NoSync implements Sync {
/**
* The single instance
*/
public static final Sync INST = new NoSync();
private NoSync() {
}
@Override
public void access(Runnable run) {
run.run();
}
@Override
public void accessNEx(Sync.ModelRun run) throws NodeException {
run.run();
}
}

View File

@ -0,0 +1,34 @@
package de.neemann.digital.gui.sync;
import de.neemann.digital.core.NodeException;
/**
* Simple sync interface
*
* @author hneemann
*/
public interface Sync {
/**
* Calls the given runnable
*
* @param run the runnable to execute
*/
void access(Runnable run);
/**
* Same as access, but catches an exception
*
* @param run the runnable to execute
* @throws NodeException NodeException
*/
void accessNEx(ModelRun run) throws NodeException;
/**
* Like runnable but throws an exception
*/
interface ModelRun {
void run() throws NodeException;
}
}

View File

@ -0,0 +1,10 @@
/**
* Classes to allow a simple synchronisation of model access.
* The problem is, that every modification of a {@link de.neemann.digital.core.ObservableValue}
* is a model access and needs to be synchronized. Synchronisation is necessary only if
* the runtime clock is running activated. If the runtime clock does not run, all modifications
* on the model are done by the GUI thread.
*
* @author hneemann
*/
package de.neemann.digital.gui.sync;