Merge branch 'master' into virtualTestSignals

# Conflicts:
#	src/main/java/de/neemann/digital/testing/TestCaseDescription.java
This commit is contained in:
hneemann 2020-11-23 14:17:33 +01:00
commit b551fbe298
14 changed files with 214 additions and 91 deletions

View File

@ -9,6 +9,7 @@ HEAD, planned as v0.26
- If a high-z value is connected to a logic gate input, the read value - If a high-z value is connected to a logic gate input, the read value
is undefined. is undefined.
- It is now possible to use a probe as output in a test case. - It is now possible to use a probe as output in a test case.
- Adds undo to text fields
- Fixed a bug in the Demuxer Verilog template that causes problems - Fixed a bug in the Demuxer Verilog template that causes problems
when using multiple demuxers in the same circuit. when using multiple demuxers in the same circuit.
- Generic circuits are easier to debug: It is possible now to create - Generic circuits are easier to debug: It is possible now to create

View File

@ -4,7 +4,7 @@
### The Commands to Execute ### The Commands to Execute
The `.config` File describes two things. At first it defines which external The `.config` File describes two things. At first, it defines which external
executables are needed to be started to run a circuit on a specific board. executables are needed to be started to run a circuit on a specific board.
And second it creates the files which are required to run a circuit on a specific And second it creates the files which are required to run a circuit on a specific
board. board.
@ -84,7 +84,7 @@ The `filter` attribute enables the filtering of the files content. And finally t
is used to include this file file from other `.config` files. is used to include this file file from other `.config` files.
The `content` tag defines the content of the file created. The `content` tag defines the content of the file created.
In most cases the content of the file depends on the circuit. In this case a the `filter` In most cases the content of the file depends on the circuit. In this case a the `filter`
attribute needs to be set to `true`. After thät you are able to use the `<?...?>` or `{?...?}` attribute needs to be set to `true`. After that you are able to use the `<?...?>` or `{?...?}`
fragments to generate the file in a flexible way. fragments to generate the file in a flexible way.
As an example we want to create a file named `Pins_[name of circuit].pin` which contains all As an example we want to create a file named `Pins_[name of circuit].pin` which contains all
the pins used by the circuit: the pins used by the circuit:
@ -105,7 +105,7 @@ The circuit contains the following pins:
</files> </files>
``` ```
The code enclosed by the `{?...?}` characters is executed and prints the list of all The code enclosed by the `{?...?}` characters is executed and prints the list of all
used pins to the text file. The `<![CDATA[ ]]>` statemant ensures that the content used pins to the text file. The `<![CDATA[ ]]>` statement ensures that the content
is considered as character data which is not analysed by the XML parser. If this statement is considered as character data which is not analysed by the XML parser. If this statement
is not used the `i < sizeOf(...` code causes an XML parser error. is not used the `i < sizeOf(...` code causes an XML parser error.
@ -200,7 +200,7 @@ bool to it.
### File Creation ### File Creation
A template allways begins with plain text which is copied to the file to generate. A template always begins with plain text which is copied to the file to generate.
Special actions - data evaluations or control structures - are delimited Special actions - data evaluations or control structures - are delimited
by `{?` and `?}` or `<?` and `?>`. Because the `.config` files are XML files the by `{?` and `?}` or `<?` and `?>`. Because the `.config` files are XML files the
`{?` and `?}` variant is easier to use in most cases. `{?` and `?}` variant is easier to use in most cases.

View File

@ -17,7 +17,6 @@ import de.neemann.digital.draw.library.ElementNotFoundException;
import de.neemann.digital.draw.library.LibraryInterface; import de.neemann.digital.draw.library.LibraryInterface;
import de.neemann.digital.hdl.hgs.*; import de.neemann.digital.hdl.hgs.*;
import de.neemann.digital.lang.Lang; import de.neemann.digital.lang.Lang;
import de.neemann.digital.testing.TestCaseDescription;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -220,9 +219,6 @@ public class SubstituteLibrary implements LibraryInterface {
} }
private Object doImplicitTypeCasts(Class<?> expectedClass, Object val) { private Object doImplicitTypeCasts(Class<?> expectedClass, Object val) {
if (expectedClass == TestCaseDescription.class)
return new TestCaseDescription(val.toString());
if (expectedClass == Integer.class && val instanceof Long) { if (expectedClass == Integer.class && val instanceof Long) {
long l = (Long) val; long l = (Long) val;
if (l <= Integer.MAX_VALUE && l >= Integer.MIN_VALUE) if (l <= Integer.MAX_VALUE && l >= Integer.MIN_VALUE)

View File

@ -874,6 +874,6 @@ public final class Keys {
* The test data * The test data
*/ */
public static final Key<TestCaseDescription> TESTDATA = public static final Key<TestCaseDescription> TESTDATA =
new Key<>("Testdata", () -> new TestCaseDescription("")); new Key<>("Testdata", TestCaseDescription::new);
} }

View File

@ -177,7 +177,7 @@ public class AttributeDialog extends JDialog {
final AbstractAction cancel = new AbstractAction(Lang.get("cancel")) { final AbstractAction cancel = new AbstractAction(Lang.get("cancel")) {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
dispose(); tryDispose();
} }
}; };
@ -190,24 +190,7 @@ public class AttributeDialog extends JDialog {
addWindowListener(new WindowAdapter() { addWindowListener(new WindowAdapter() {
@Override @Override
public void windowClosing(WindowEvent e) { public void windowClosing(WindowEvent e) {
EditorHolder majorModification = null; tryDispose();
for (EditorHolder eh : editors)
if (eh.e.invisibleModification())
majorModification = eh;
if (majorModification == null)
dispose();
else {
int r = JOptionPane.showOptionDialog(
AttributeDialog.this,
Lang.get("msg_dataWillBeLost_n", majorModification.key.getName()),
Lang.get("msg_warning"),
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE,
null, new String[]{Lang.get("btn_discard"), Lang.get("cancel")},
Lang.get("cancel"));
if (r == JOptionPane.YES_OPTION)
dispose();
}
} }
}); });
@ -217,6 +200,27 @@ public class AttributeDialog extends JDialog {
JComponent.WHEN_IN_FOCUSED_WINDOW); JComponent.WHEN_IN_FOCUSED_WINDOW);
} }
private void tryDispose() {
EditorHolder majorModification = null;
for (EditorHolder eh : editors)
if (eh.e.invisibleModification())
majorModification = eh;
if (majorModification == null)
dispose();
else {
int r = JOptionPane.showOptionDialog(
AttributeDialog.this,
Lang.get("msg_dataWillBeLost_n", majorModification.key.getName()),
Lang.get("msg_warning"),
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE,
null, new String[]{Lang.get("btn_discard"), Lang.get("btn_editFurther")},
Lang.get("cancel"));
if (r == JOptionPane.YES_OPTION)
dispose();
}
}
private EditorPanel findPanel(ArrayList<EditorPanel> panels, String panelId) { private EditorPanel findPanel(ArrayList<EditorPanel> panels, String panelId) {
for (EditorPanel p : panels) for (EditorPanel p : panels)
if (panelId.equals(p.getPanelId())) if (panelId.equals(p.getPanelId()))

View File

@ -35,11 +35,9 @@ import de.neemann.gui.language.Language;
import javax.swing.*; import javax.swing.*;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import javax.swing.undo.UndoManager;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.*;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.*; import java.io.*;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.ArrayList; import java.util.ArrayList;
@ -177,6 +175,7 @@ public final class EditorFactory {
private final JTextComponent text; private final JTextComponent text;
private final JComponent compToAdd; private final JComponent compToAdd;
private final UndoManager undoManager;
private JPopupMenu popup; private JPopupMenu popup;
public StringEditor(String value, Key<String> key) { public StringEditor(String value, Key<String> key) {
@ -219,6 +218,8 @@ public final class EditorFactory {
compToAdd = text; compToAdd = text;
} }
text.setText(value); text.setText(value);
undoManager = createUndoManager(text);
} }
JPopupMenu getPopupMenu(String keyName) { JPopupMenu getPopupMenu(String keyName) {
@ -286,8 +287,10 @@ public final class EditorFactory {
@Override @Override
public void setValue(String value) { public void setValue(String value) {
if (!text.getText().equals(value)) if (!text.getText().equals(value)) {
text.setText(value); text.setText(value);
undoManager.discardAllEdits();
}
} }
public JTextComponent getTextComponent() { public JTextComponent getTextComponent() {
@ -1108,4 +1111,30 @@ public final class EditorFactory {
comb.setSelectedItem(value); comb.setSelectedItem(value);
} }
} }
/**
* Enables undo in the given text component.
*
* @param text the text component
* @return the undo manager
*/
public static UndoManager createUndoManager(JTextComponent text) {
final UndoManager undoManager;
undoManager = new UndoManager();
text.getDocument().addUndoableEditListener(undoManager);
text.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_Z && (e.getModifiersEx() & ToolTipAction.getCTRLMask()) != 0) {
if (undoManager.canUndo())
undoManager.undo();
} else if (e.getKeyCode() == KeyEvent.VK_Y && (e.getModifiersEx() & ToolTipAction.getCTRLMask()) != 0) {
if (undoManager.canRedo())
undoManager.redo();
}
}
});
return undoManager;
}
} }

View File

@ -24,34 +24,57 @@ import de.neemann.gui.ToolTipAction;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException; import java.io.IOException;
import static de.neemann.digital.gui.components.EditorFactory.addF1Traversal; import static de.neemann.digital.gui.components.EditorFactory.addF1Traversal;
import static de.neemann.digital.gui.components.EditorFactory.createUndoManager;
/** /**
* Dialog to show and edit the testing data source. * Dialog to show and edit the testing data source.
*/ */
public class TestCaseDescriptionDialog extends JDialog { public class TestCaseDescriptionDialog extends JDialog {
private final JTextArea text;
private final TestCaseDescription initialData;
private final VisualElement element;
private TestCaseDescription modifiedData;
private boolean circuitModified = false;
/** /**
* Creates a new data dialog * Creates a new data dialog.
* *
* @param parent the parent component * @param parent the parent component
* @param data the data to edit * @param initialData the data to edit
* @param element the element to be modified
*/ */
public TestCaseDescriptionDialog(Window parent, TestCaseDescription data, VisualElement element) { public TestCaseDescriptionDialog(Window parent, TestCaseDescription initialData) {
this(parent, initialData, null);
}
/**
* Creates a new data dialog.
* This constructor allows to open the dialog in a modeless way.
*
* @param parent the parent component
* @param initialData the data to edit
* @param element the element to be modified
*/
public TestCaseDescriptionDialog(Window parent, TestCaseDescription initialData, VisualElement element) {
super(parent, super(parent,
Lang.get("key_Testdata"), Lang.get("key_Testdata"),
element == null ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS); element == null ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
setDefaultCloseOperation(DISPOSE_ON_CLOSE); this.element = element;
this.initialData = initialData;
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
text = addF1Traversal(new JTextArea(initialData.getDataString(), 30, 50));
String initialDataString = data.getDataString();
JTextArea text = addF1Traversal(new JTextArea(data.getDataString(), 30, 50));
text.setFont(new Font(Font.MONOSPACED, Font.PLAIN, Screen.getInstance().getFontSize())); text.setFont(new Font(Font.MONOSPACED, Font.PLAIN, Screen.getInstance().getFontSize()));
createUndoManager(text);
addWindowListener(new ClosingWindowListener());
JScrollPane scrollPane = new JScrollPane(text); JScrollPane scrollPane = new JScrollPane(text);
getContentPane().add(scrollPane); getContentPane().add(scrollPane);
scrollPane.setRowHeaderView(new TextLineNumber(text, 3)); scrollPane.setRowHeaderView(new TextLineNumber(text, 3));
@ -91,7 +114,7 @@ public class TestCaseDescriptionDialog extends JDialog {
buttons.add(new ToolTipAction(Lang.get("cancel")) { buttons.add(new ToolTipAction(Lang.get("cancel")) {
@Override @Override
public void actionPerformed(ActionEvent actionEvent) { public void actionPerformed(ActionEvent actionEvent) {
dispose(); tryDispose();
} }
}.createJButton()); }.createJButton());
@ -100,10 +123,10 @@ public class TestCaseDescriptionDialog extends JDialog {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
try { try {
data.setDataString(text.getText());
if (parent instanceof Main) { if (parent instanceof Main) {
CircuitComponent cc = ((Main) parent).getCircuitComponent(); CircuitComponent cc = ((Main) parent).getCircuitComponent();
element.getElementAttributes().set(Keys.TESTDATA, data); element.getElementAttributes().set(Keys.TESTDATA, new TestCaseDescription(text.getText()));
circuitModified = true;
cc.getMain().startTests(); cc.getMain().startTests();
} }
} catch (ParserException | IOException e1) { } catch (ParserException | IOException e1) {
@ -117,12 +140,12 @@ public class TestCaseDescriptionDialog extends JDialog {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
try { try {
data.setDataString(text.getText()); modifiedData = new TestCaseDescription(text.getText());
if (element != null if (element != null
&& !initialDataString.equals(data.getDataString()) && isStateChanged()
&& parent instanceof Main) { && parent instanceof Main) {
CircuitComponent cc = ((Main) parent).getCircuitComponent(); CircuitComponent cc = ((Main) parent).getCircuitComponent();
cc.modify(new ModifyAttribute<>(element, Keys.TESTDATA, new TestCaseDescription(data))); cc.modify(new ModifyAttribute<>(element, Keys.TESTDATA, modifiedData));
} }
dispose(); dispose();
} catch (ParserException | IOException e1) { } catch (ParserException | IOException e1) {
@ -138,4 +161,48 @@ public class TestCaseDescriptionDialog extends JDialog {
setLocationRelativeTo(parent); setLocationRelativeTo(parent);
} }
/**
* Shows the dialog and returns the modified data
*
* @return the modified data or null if not modified
*/
public TestCaseDescription showDialog() {
modifiedData = null;
setVisible(true);
return modifiedData;
}
private boolean isStateChanged() {
return !initialData.getDataString().equals(text.getText());
}
private void tryDispose() {
if (isStateChanged()) {
int r = JOptionPane.showOptionDialog(
this,
Lang.get("msg_dataWillBeLost_n", Keys.TESTDATA.getName()),
Lang.get("msg_warning"),
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE,
null, new String[]{Lang.get("btn_discard"), Lang.get("btn_editFurther")},
Lang.get("cancel"));
if (r == JOptionPane.YES_OPTION)
myDispose();
} else
myDispose();
}
private void myDispose() {
dispose();
if (circuitModified)
element.getElementAttributes().set(Keys.TESTDATA, initialData);
}
private final class ClosingWindowListener extends WindowAdapter {
@Override
public void windowClosing(WindowEvent e) {
tryDispose();
}
}
} }

View File

@ -19,10 +19,12 @@ import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
/** /**
* The test case description editor
*/ */
public class TestCaseDescriptionEditor extends EditorFactory.LabelEditor<TestCaseDescription> { public class TestCaseDescriptionEditor extends EditorFactory.LabelEditor<TestCaseDescription> {
private final TestCaseDescription data; private final TestCaseDescription initialData;
private TestCaseDescription data;
/** /**
* Creates a new editor * Creates a new editor
@ -31,7 +33,8 @@ public class TestCaseDescriptionEditor extends EditorFactory.LabelEditor<TestCas
* @param key the data key * @param key the data key
*/ */
public TestCaseDescriptionEditor(TestCaseDescription data, Key<TestCaseDescription> key) { public TestCaseDescriptionEditor(TestCaseDescription data, Key<TestCaseDescription> key) {
this.data = new TestCaseDescription(data); this.data = data;
this.initialData = data;
} }
@Override @Override
@ -46,7 +49,10 @@ public class TestCaseDescriptionEditor extends EditorFactory.LabelEditor<TestCas
panel.add(new ToolTipAction(Lang.get("btn_edit")) { panel.add(new ToolTipAction(Lang.get("btn_edit")) {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
new TestCaseDescriptionDialog(SwingUtilities.getWindowAncestor(panel), data, null).setVisible(true); TestCaseDescriptionDialog tdd = new TestCaseDescriptionDialog(SwingUtilities.getWindowAncestor(panel), data);
TestCaseDescription d = tdd.showDialog();
if (d != null)
data = d;
} }
}.createJButton()); }.createJButton());
@ -73,6 +79,12 @@ public class TestCaseDescriptionEditor extends EditorFactory.LabelEditor<TestCas
} }
@Override @Override
public void setValue(TestCaseDescription value) { public boolean invisibleModification() {
return !initialData.equals(data);
}
@Override
public void setValue(TestCaseDescription data) {
this.data = data;
} }
} }

View File

@ -144,6 +144,9 @@ public class HDLCircuit implements Iterable<HDLNode>, HDLModel.BitProvider, Prin
for (HDLNet n : listOfNets) for (HDLNet n : listOfNets)
n.fixBits(); n.fixBits();
for (HDLNet n : listOfNets)
n.checkPinControlUsage();
// fix inverted inputs // fix inverted inputs
ArrayList<HDLNode> newNodes = new ArrayList<>(); ArrayList<HDLNode> newNodes = new ArrayList<>();
for (HDLNode n : nodes) { for (HDLNode n : nodes) {

View File

@ -203,6 +203,8 @@ public class HDLNet implements Printable, HasName {
* @return true if this is a clock net * @return true if this is a clock net
*/ */
public boolean isClock() { public boolean isClock() {
if (output == null)
return false;
return output.isClock(); return output.isClock();
} }
@ -231,4 +233,17 @@ public class HDLNet implements Printable, HasName {
return !inOutputs.isEmpty(); return !inOutputs.isEmpty();
} }
/**
* Checks whether a PinControl component is used correctly.
*
* @throws HDLException thrown if PinControl us used the wrong way
*/
public void checkPinControlUsage() throws HDLException {
if (output == null && inOutputs.size() > 0) {
if (inputs.size() > 1)
throw new HDLException("multiple components connected to PinControl output");
if (inputs.size() == 1 && inputs.get(0).getParent() != null)
throw new HDLException("only a single output is allowed on a PinControl component");
}
}
} }

View File

@ -18,18 +18,31 @@ import java.util.ArrayList;
* The test data. * The test data.
*/ */
public class TestCaseDescription { public class TestCaseDescription {
private String dataString; private final String dataString;
private transient LineEmitter lines; private transient LineEmitter lines;
private transient ArrayList<String> names; private transient ArrayList<String> names;
private transient ArrayList<VirtualSignal> virtualSignals; private transient ArrayList<VirtualSignal> virtualSignals;
/**
* creates a new instance
*/
public TestCaseDescription() {
this.dataString = "";
}
/** /**
* creates a new instance * creates a new instance
* *
* @param data the test case description * @param data the test case description
* @throws IOException IOException
* @throws ParserException ParserException
*/ */
public TestCaseDescription(String data) { public TestCaseDescription(String data) throws IOException, ParserException {
this.dataString = data; this.dataString = data;
Parser tdp = new Parser(data).parse();
lines = tdp.getLines();
names = tdp.getNames();
} }
/** /**
@ -38,7 +51,7 @@ public class TestCaseDescription {
* @param valueToCopy the instance to copy * @param valueToCopy the instance to copy
*/ */
public TestCaseDescription(TestCaseDescription valueToCopy) { public TestCaseDescription(TestCaseDescription valueToCopy) {
this(valueToCopy.dataString); this.dataString = valueToCopy.dataString;
} }
/** /**
@ -48,23 +61,6 @@ public class TestCaseDescription {
return dataString; return dataString;
} }
/**
* Sets the data and checks its validity
*
* @param data the data
* @throws IOException thrown if data is not valid
* @throws ParserException thrown if data is not valid
*/
public void setDataString(String data) throws IOException, ParserException {
if (!data.equals(dataString)) {
Parser tdp = new Parser(data).parse();
dataString = data;
lines = tdp.getLines();
names = tdp.getNames();
virtualSignals = tdp.getVirtualSignals();
}
}
private void check() throws TestingDataException { private void check() throws TestingDataException {
if (lines == null || names == null) { if (lines == null || names == null) {
try { try {

View File

@ -46,6 +46,7 @@ import de.neemann.digital.gui.remote.RemoteException;
import de.neemann.digital.lang.Lang; import de.neemann.digital.lang.Lang;
import de.neemann.digital.testing.TestCaseDescription; import de.neemann.digital.testing.TestCaseDescription;
import de.neemann.digital.testing.TestCaseElement; import de.neemann.digital.testing.TestCaseElement;
import de.neemann.digital.testing.parser.ParserException;
import de.neemann.gui.ErrorMessage; import de.neemann.gui.ErrorMessage;
import junit.framework.TestCase; import junit.framework.TestCase;
@ -1375,10 +1376,14 @@ public class TestInGUI extends TestCase {
@Override @Override
public void checkWindow(GuiTester gt, Main main) { public void checkWindow(GuiTester gt, Main main) {
main.getCircuitComponent().getCircuit().add( try {
new VisualElement(TestCaseElement.DESCRIPTION.getName()) main.getCircuitComponent().getCircuit().add(
.setAttribute(TESTDATA, new TestCaseDescription(testdata)) new VisualElement(TestCaseElement.DESCRIPTION.getName())
.setShapeFactory(main.getCircuitComponent().getLibrary().getShapeFactory())); .setAttribute(TESTDATA, new TestCaseDescription(testdata))
.setShapeFactory(main.getCircuitComponent().getLibrary().getShapeFactory()));
} catch (IOException | ParserException e) {
throw new RuntimeException(e);
}
} }
} }
} }

View File

@ -28,8 +28,8 @@ public class TestDataTest extends TestCase {
// try to set a non parsable string // try to set a non parsable string
try { try {
td.setDataString(DATA3); td = new TestCaseDescription(DATA3);
assertTrue(false); fail();
} catch (IOException | ParserException e) { } catch (IOException | ParserException e) {
assertTrue(true); assertTrue(true);
} }
@ -46,7 +46,7 @@ public class TestDataTest extends TestCase {
assertEquals(DATA1, td.getDataString()); assertEquals(DATA1, td.getDataString());
// try to set a parsable string // try to set a parsable string
td.setDataString(DATA2); td = new TestCaseDescription(DATA2);
// TestData has changed! // TestData has changed!
assertEquals(DATA2, td.getDataString()); assertEquals(DATA2, td.getDataString());
} }

View File

@ -27,11 +27,6 @@
<elementAttributes/> <elementAttributes/>
<pos x="520" y="20"/> <pos x="520" y="20"/>
</visualElement> </visualElement>
<visualElement>
<elementName>VDD</elementName>
<elementAttributes/>
<pos x="500" y="0"/>
</visualElement>
<visualElement> <visualElement>
<elementName>Out</elementName> <elementName>Out</elementName>
<elementAttributes> <elementAttributes>
@ -89,7 +84,7 @@
<p2 x="460" y="40"/> <p2 x="460" y="40"/>
</wire> </wire>
<wire> <wire>
<p1 x="500" y="60"/> <p1 x="460" y="60"/>
<p2 x="520" y="60"/> <p2 x="520" y="60"/>
</wire> </wire>
<wire> <wire>
@ -100,22 +95,22 @@
<p1 x="340" y="20"/> <p1 x="340" y="20"/>
<p2 x="340" y="100"/> <p2 x="340" y="100"/>
</wire> </wire>
<wire>
<p1 x="500" y="0"/>
<p2 x="500" y="60"/>
</wire>
<wire> <wire>
<p1 x="360" y="40"/> <p1 x="360" y="40"/>
<p2 x="360" y="80"/> <p2 x="360" y="80"/>
</wire> </wire>
<wire> <wire>
<p1 x="460" y="40"/> <p1 x="460" y="40"/>
<p2 x="460" y="80"/> <p2 x="460" y="60"/>
</wire> </wire>
<wire> <wire>
<p1 x="460" y="-40"/> <p1 x="460" y="-40"/>
<p2 x="460" y="20"/> <p2 x="460" y="20"/>
</wire> </wire>
<wire>
<p1 x="460" y="60"/>
<p2 x="460" y="80"/>
</wire>
</wires> </wires>
<measurementOrdering/> <measurementOrdering/>
</circuit> </circuit>