diff --git a/src/main/java/de/neemann/digital/core/element/AttributeKey.java b/src/main/java/de/neemann/digital/core/element/AttributeKey.java index 4714a00fe..df61a45cb 100644 --- a/src/main/java/de/neemann/digital/core/element/AttributeKey.java +++ b/src/main/java/de/neemann/digital/core/element/AttributeKey.java @@ -11,6 +11,8 @@ public class AttributeKey { public static final AttributeKey Value = new AttributeKey<>("Value", 1); public static final AttributeKey Default = new AttributeKey<>("Default", 0); public static final AttributeKey Color = new AttributeKey<>("Color", java.awt.Color.RED); + public static final AttributeKey InputSplit = new AttributeKey<>("Input Splitting", ""); + public static final AttributeKey OutputSplit = new AttributeKey<>("Output Splitting", ""); private final String name; private final VALUE def; diff --git a/src/main/java/de/neemann/digital/core/element/ElementAttributes.java b/src/main/java/de/neemann/digital/core/element/ElementAttributes.java index 1951aaf23..f705ba242 100644 --- a/src/main/java/de/neemann/digital/core/element/ElementAttributes.java +++ b/src/main/java/de/neemann/digital/core/element/ElementAttributes.java @@ -36,7 +36,7 @@ public class ElementAttributes { } } - public void set(AttributeKey key, VALUE value) { + public ElementAttributes set(AttributeKey key, VALUE value) { if (value != get(key)) { if (value.equals(key.getDefault())) { if (attributes != null) @@ -48,6 +48,7 @@ public class ElementAttributes { } fireValueChanged(key); } + return this; } private void fireValueChanged(AttributeKey key) { diff --git a/src/main/java/de/neemann/digital/core/wiring/Splitter.java b/src/main/java/de/neemann/digital/core/wiring/Splitter.java new file mode 100644 index 000000000..46569e4c9 --- /dev/null +++ b/src/main/java/de/neemann/digital/core/wiring/Splitter.java @@ -0,0 +1,183 @@ +package de.neemann.digital.core.wiring; + +import de.neemann.digital.core.*; +import de.neemann.digital.core.element.AttributeKey; +import de.neemann.digital.core.element.Element; +import de.neemann.digital.core.element.ElementAttributes; +import de.neemann.digital.core.element.ElementTypeDescription; +import de.neemann.digital.gui.draw.elements.PinException; + +import java.util.ArrayList; +import java.util.StringTokenizer; + +/** + * @author hneemann + */ +public class Splitter implements Element { + + public static final ElementTypeDescription DESCRIPTION + = new SplitterTypeDescription() + .addAttribute(AttributeKey.InputSplit) + .addAttribute(AttributeKey.OutputSplit) + .setShortName(""); + + private final ObservableValue[] outputs; + private final Ports inPorts; + private final Ports outPorts; + private ObservableValue[] inputs; + + + private static class SplitterTypeDescription extends ElementTypeDescription { + public SplitterTypeDescription() { + super(Splitter.class); + } + + @Override + public String[] getInputNames(ElementAttributes elementAttributes) { + Ports p = new Ports(elementAttributes.get(AttributeKey.InputSplit)); + return p.getNames(); + } + + } + + public Splitter(ElementAttributes attributes) throws PinException { + outPorts = new Ports(attributes.get(AttributeKey.OutputSplit)); + outputs = outPorts.getOutputs(); + inPorts = new Ports(attributes.get(AttributeKey.InputSplit)); + if (inPorts.getBits() != outPorts.getBits()) + throw new PinException("splitterPortMismatch"); + } + + @Override + public void setInputs(ObservableValue... inputs) throws NodeException { + this.inputs = inputs; + for (int i = 0; i < inputs.length; i++) { + if (inPorts.getPort(i).getBits() != inputs[i].getBits()) + throw new BitsException("splitterBitsMismatch", inputs[i]); + inputs[i].addObserver(createObserverForInput(i)); + } + } + + private Observer createObserverForInput(int i) throws NodeException { + Observer observer = outPorts.getSimpleTargetObserver(inPorts.getPort(i), inputs, outputs); + if (observer == null) + throw new NodeException("splitterMismatchError"); + return observer; + } + + @Override + public ObservableValue[] getOutputs() { + return outputs; + } + + @Override + public void registerNodes(Model model) { + } + + public static final class Ports { + private final ArrayList ports; + private int bits; + + public Ports(String definition) { + StringTokenizer st = new StringTokenizer(definition, ",", false); + ports = new ArrayList<>(); + bits = 0; + while (st.hasMoreTokens()) { + Port p = new Port(Integer.decode(st.nextToken().trim()), bits, ports.size()); + ports.add(p); + bits += p.getBits(); + } + if (ports.isEmpty()) { + ports.add(new Port(1, 0, 0)); + bits = 1; + } + + } + + public int getBits() { + return bits; + } + + public String[] getNames() { + String[] name = new String[ports.size()]; + for (int i = 0; i < name.length; i++) + name[i] = ports.get(i).getName(); + + return name; + } + + public ObservableValue[] getOutputs() { + ObservableValue[] outputs = new ObservableValue[ports.size()]; + for (int i = 0; i < ports.size(); i++) { + Port p = ports.get(i); + outputs[i] = new ObservableValue(p.getName(), p.getBits()); + } + return outputs; + } + + public Port getPort(int i) { + return ports.get(i); + } + + /** + * Checks if there is a single out target port for the input port + * + * @param inPort + * @param inputs + * @param outputs + */ + public Observer getSimpleTargetObserver(Port inPort, ObservableValue[] inputs, ObservableValue[] outputs) { + int pos = inPort.getPos(); + int bits = inPort.getBits(); + + for (Port outPort : ports) { + if (outPort.getPos() <= pos && pos + bits <= outPort.getPos() + outPort.getBits()) { + final int bitPos = pos - outPort.getPos(); + final int mask = ~(((1 << inPort.bits) - 1) << bitPos); + final ObservableValue inValue = inputs[inPort.number]; + final ObservableValue outValue = outputs[outPort.number]; + return new Observer() { + @Override + public void hasChanged() { + long in = inValue.getValue(); + long out = outValue.getValue(); + outValue.setValue((out & mask) | (in << bitPos)); + } + }; + } + } + return null; + } + + } + + private static final class Port { + + private final String name; + private final int bits; + private final int pos; + private final int number; + + public Port(int bits, int pos, int number) { + this.bits = bits; + this.pos = pos; + this.number = number; + if (bits == 1) + name = "" + pos; + else + name = "" + pos + "-" + (pos + bits - 1); + } + + public int getBits() { + return bits; + } + + public int getPos() { + return pos; + } + + public String getName() { + return name; + } + } +} diff --git a/src/main/java/de/neemann/digital/gui/draw/elements/VisualElement.java b/src/main/java/de/neemann/digital/gui/draw/elements/VisualElement.java index 3a41f4967..55f3d2fe4 100644 --- a/src/main/java/de/neemann/digital/gui/draw/elements/VisualElement.java +++ b/src/main/java/de/neemann/digital/gui/draw/elements/VisualElement.java @@ -101,7 +101,8 @@ public class VisualElement implements Drawable, Moveable, AttributeListener { shape.drawTo(gr); for (Pin p : shape.getPins()) - gr.drawCircle(p.getPos().add(-PIN, -PIN), p.getPos().add(PIN, PIN), p.getDirection() == Pin.Direction.input ? Style.NORMAL : Style.WIRE); + gr.drawCircle(p.getPos().add(-PIN, -PIN), p.getPos().add(PIN, PIN) + , p.getDirection() == Pin.Direction.input ? Style.NORMAL : Style.WIRE_OUT); if (highLight && minMax != null) { Vector delta = minMax.getMax().sub(minMax.getMin()); diff --git a/src/main/java/de/neemann/digital/gui/draw/graphics/Style.java b/src/main/java/de/neemann/digital/gui/draw/graphics/Style.java index 930cb0eb2..38e7b72bd 100644 --- a/src/main/java/de/neemann/digital/gui/draw/graphics/Style.java +++ b/src/main/java/de/neemann/digital/gui/draw/graphics/Style.java @@ -10,6 +10,7 @@ public class Style { public static final Style WIRE = new Style(2, true, Color.BLUE.darker()); public static final Style WIRE_LOW = new Style(3, true, new Color(0, 112, 0)); public static final Style WIRE_HIGH = new Style(3, true, new Color(102, 255, 102)); + public static final Style WIRE_OUT = new Style(2, true, Color.RED.darker()); public static final Style FILLED = new Style(2, true, Color.BLACK); public static final Style THIN = new Style(1, false, Color.BLACK); public static final Style DASH = new Style(1, false, Color.BLACK); diff --git a/src/main/java/de/neemann/digital/gui/draw/library/PartLibrary.java b/src/main/java/de/neemann/digital/gui/draw/library/PartLibrary.java index 43506156b..cec776053 100644 --- a/src/main/java/de/neemann/digital/gui/draw/library/PartLibrary.java +++ b/src/main/java/de/neemann/digital/gui/draw/library/PartLibrary.java @@ -13,6 +13,7 @@ import de.neemann.digital.core.io.Const; import de.neemann.digital.core.io.In; import de.neemann.digital.core.io.Out; import de.neemann.digital.core.wiring.Delay; +import de.neemann.digital.core.wiring.Splitter; import java.util.ArrayList; import java.util.HashMap; @@ -42,6 +43,8 @@ public class PartLibrary implements Iterable { add(Out.LEDDESCRIPTION, "IO"); add(Out.PROBEDESCRIPTION, "IO"); + add(Splitter.DESCRIPTION, "Wires"); + add(RS_FF.DESCRIPTION, "FlipFlops"); add(JK_FF.DESCRIPTION, "FlipFlops"); add(D_FF.DESCRIPTION, "FlipFlops"); diff --git a/src/main/java/de/neemann/digital/gui/draw/model/ModelEntry.java b/src/main/java/de/neemann/digital/gui/draw/model/ModelEntry.java index a8cff1185..87bc24811 100644 --- a/src/main/java/de/neemann/digital/gui/draw/model/ModelEntry.java +++ b/src/main/java/de/neemann/digital/gui/draw/model/ModelEntry.java @@ -26,7 +26,7 @@ public class ModelEntry { } /** - * Sets the Inputs of the element contained in thes entry + * Sets the Inputs of the element contained in this entry * * @throws PinException * @throws NodeException diff --git a/src/main/java/de/neemann/digital/gui/draw/shapes/ShapeFactory.java b/src/main/java/de/neemann/digital/gui/draw/shapes/ShapeFactory.java index cd0ae2c1e..5c9e02186 100644 --- a/src/main/java/de/neemann/digital/gui/draw/shapes/ShapeFactory.java +++ b/src/main/java/de/neemann/digital/gui/draw/shapes/ShapeFactory.java @@ -12,6 +12,7 @@ import de.neemann.digital.core.io.Const; import de.neemann.digital.core.io.In; import de.neemann.digital.core.io.Out; import de.neemann.digital.core.wiring.Delay; +import de.neemann.digital.core.wiring.Splitter; import de.neemann.digital.gui.draw.library.PartLibrary; import java.util.HashMap; @@ -53,6 +54,9 @@ public final class ShapeFactory { map.put(Out.DESCRIPTION.getName(), attr -> new OutputShape(attr.get(AttributeKey.Label))); map.put(Out.LEDDESCRIPTION.getName(), attr -> new LEDShape(attr.get(AttributeKey.Label), attr.get(AttributeKey.Color))); map.put(Out.PROBEDESCRIPTION.getName(), attr -> new ProbeShape(attr.get(AttributeKey.Label))); + + map.put(Splitter.DESCRIPTION.getName(), attr -> new SplitterShape(attr.get(AttributeKey.InputSplit), attr.get(AttributeKey.OutputSplit))); + } public PartLibrary setLibrary(PartLibrary library) { diff --git a/src/main/java/de/neemann/digital/gui/draw/shapes/SplitterShape.java b/src/main/java/de/neemann/digital/gui/draw/shapes/SplitterShape.java new file mode 100644 index 000000000..6fd1852a3 --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/draw/shapes/SplitterShape.java @@ -0,0 +1,62 @@ +package de.neemann.digital.gui.draw.shapes; + +import de.neemann.digital.core.Observer; +import de.neemann.digital.core.wiring.Splitter; +import de.neemann.digital.gui.draw.elements.IOState; +import de.neemann.digital.gui.draw.elements.Pin; +import de.neemann.digital.gui.draw.elements.Pins; +import de.neemann.digital.gui.draw.graphics.*; + +import static de.neemann.digital.gui.draw.shapes.GenericShape.SIZE; + +/** + * @author hneemann + */ +public class SplitterShape implements Shape { + + private final String[] inputs; + private final String[] outputs; + private final int length; + private Pins pins; + + public SplitterShape(String inputDef, String outputDef) { + inputs = new Splitter.Ports(inputDef).getNames(); + outputs = new Splitter.Ports(outputDef).getNames(); + length = (Math.max(inputs.length, outputs.length) - 1) * SIZE + 2; + } + + @Override + public Pins getPins() { + if (pins == null) { + pins = new Pins(); + for (int i = 0; i < inputs.length; i++) + pins.add(new Pin(new Vector(0, i * SIZE), inputs[i], Pin.Direction.input)); + for (int i = 0; i < outputs.length; i++) + pins.add(new Pin(new Vector(SIZE, i * SIZE), outputs[i], Pin.Direction.output)); + } + return pins; + } + + @Override + public Interactor applyStateMonitor(IOState ioState, Observer guiObserver) { + return null; + } + + @Override + public void drawTo(Graphic graphic) { + for (int i = 0; i < inputs.length; i++) { + Vector pos = new Vector(-2, i * SIZE - 2); + graphic.drawText(pos, pos.add(2, 0), inputs[i], Orientation.RIGHTBOTTOM, Style.SHAPE_PIN); + } + for (int i = 0; i < outputs.length; i++) { + Vector pos = new Vector(SIZE + 2, i * SIZE - 2); + graphic.drawText(pos, pos.add(2, 0), outputs[i], Orientation.LEFTBOTTOM, Style.SHAPE_PIN); + } + + graphic.drawPolygon(new Polygon(true) + .add(2, 0) + .add(SIZE - 2, 0) + .add(SIZE - 2, length) + .add(2, length), Style.NORMAL); + } +} diff --git a/src/test/java/de/neemann/digital/TestExecuter.java b/src/test/java/de/neemann/digital/TestExecuter.java index 743affc59..040e8f81e 100644 --- a/src/test/java/de/neemann/digital/TestExecuter.java +++ b/src/test/java/de/neemann/digital/TestExecuter.java @@ -3,6 +3,7 @@ package de.neemann.digital; import de.neemann.digital.core.Model; import de.neemann.digital.core.NodeException; import de.neemann.digital.core.ObservableValue; +import de.neemann.digital.core.element.Element; import static org.junit.Assert.assertEquals; @@ -15,13 +16,17 @@ public class TestExecuter { private ObservableValue[] inputs; private ObservableValue[] outputs; + public TestExecuter() throws NodeException { + this(null); + } public TestExecuter(Model model) throws NodeException { this(model, false); } public TestExecuter(Model model, boolean noise) throws NodeException { this.model = model; - model.init(noise); + if (model != null) + model.init(noise); } public TestExecuter setInputs(ObservableValue... values) { @@ -34,11 +39,17 @@ public class TestExecuter { return this; } + public TestExecuter setOutputsOf(Element element) { + outputs = element.getOutputs(); + return this; + } + public void check(int... val) throws NodeException { for (int i = 0; i < inputs.length; i++) { inputs[i].setValue(val[i]); } - model.doStep(); + if (model != null) + model.doStep(); for (int i = 0; i < outputs.length; i++) { int should = val[i + inputs.length]; diff --git a/src/test/java/de/neemann/digital/core/wiring/SplitterTest.java b/src/test/java/de/neemann/digital/core/wiring/SplitterTest.java new file mode 100644 index 000000000..e49aa5217 --- /dev/null +++ b/src/test/java/de/neemann/digital/core/wiring/SplitterTest.java @@ -0,0 +1,102 @@ +package de.neemann.digital.core.wiring; + +import de.neemann.digital.TestExecuter; +import de.neemann.digital.core.ObservableValue; +import de.neemann.digital.core.element.AttributeKey; +import de.neemann.digital.core.element.ElementAttributes; +import junit.framework.TestCase; + +/** + * @author hneemann + */ +public class SplitterTest extends TestCase { + + public void testBits() throws Exception { + ObservableValue a = new ObservableValue("a", 1); + ObservableValue b = new ObservableValue("b", 1); + ObservableValue c = new ObservableValue("c", 1); + ObservableValue d = new ObservableValue("d", 1); + + Splitter splitter = new Splitter(new ElementAttributes() + .set(AttributeKey.InputSplit, "1,1,1,1") + .set(AttributeKey.OutputSplit, "4")); + + splitter.setInputs(a, b, c, d); + + ObservableValue[] outputs = splitter.getOutputs(); + assertEquals(1, outputs.length); + + TestExecuter sc = new TestExecuter().setInputs(a, b, c, d).setOutputsOf(splitter); + sc.check(0, 0, 0, 0, 0); + sc.check(1, 0, 0, 0, 1); + sc.check(0, 1, 0, 0, 2); + sc.check(0, 0, 1, 0, 4); + sc.check(0, 0, 0, 1, 8); + sc.check(1, 1, 0, 0, 3); + sc.check(1, 1, 1, 0, 7); + sc.check(1, 1, 1, 1, 15); + sc.check(0, 0, 1, 1, 12); + sc.check(0, 1, 1, 1, 14); + } + + public void testMoreBits() throws Exception { + ObservableValue a = new ObservableValue("a", 4); + ObservableValue b = new ObservableValue("b", 4); + ObservableValue c = new ObservableValue("c", 4); + ObservableValue d = new ObservableValue("d", 4); + + Splitter splitter = new Splitter(new ElementAttributes() + .set(AttributeKey.InputSplit, "4,4,4,4") + .set(AttributeKey.OutputSplit, "16")); + + splitter.setInputs(a, b, c, d); + + ObservableValue[] outputs = splitter.getOutputs(); + assertEquals(1, outputs.length); + + TestExecuter sc = new TestExecuter().setInputs(d, c, b, a).setOutputsOf(splitter); + sc.check(0, 0, 0, 0, 0x0000); + sc.check(0, 0, 0, 1, 0x0001); + sc.check(0, 0, 1, 0, 0x0010); + sc.check(0, 1, 0, 0, 0x0100); + sc.check(1, 0, 0, 0, 0x1000); + sc.check(0, 0, 0, 15, 0x000F); + sc.check(0, 0, 15, 0, 0x00F0); + sc.check(0, 15, 0, 0, 0x0F00); + sc.check(15, 0, 0, 0, 0xF000); + } + + public void testMoreBits2() throws Exception { + ObservableValue a = new ObservableValue("a", 4); + ObservableValue b = new ObservableValue("b", 4); + ObservableValue c = new ObservableValue("c", 4); + ObservableValue d = new ObservableValue("d", 4); + + Splitter splitter = new Splitter(new ElementAttributes() + .set(AttributeKey.InputSplit, "4,4,4,4") + .set(AttributeKey.OutputSplit, "8,8")); + + splitter.setInputs(a, b, c, d); + + ObservableValue[] outputs = splitter.getOutputs(); + assertEquals(2, outputs.length); + + TestExecuter sc = new TestExecuter().setInputs(b, a, d, c).setOutputsOf(splitter); + sc.check(0x0, 0x0, 0x0, 0x0, 0x00, 0x00); + sc.check(0x1, 0x0, 0x0, 0x0, 0x10, 0x00); + sc.check(0x0, 0x1, 0x0, 0x0, 0x01, 0x00); + sc.check(0x0, 0x0, 0x1, 0x0, 0x00, 0x10); + sc.check(0x0, 0x0, 0x0, 0x1, 0x00, 0x01); + sc.check(0xf, 0x0, 0x0, 0x0, 0xf0, 0x00); + sc.check(0x0, 0xf, 0x0, 0x0, 0x0f, 0x00); + sc.check(0x0, 0x0, 0xf, 0x0, 0x00, 0xf0); + sc.check(0x0, 0x0, 0x0, 0xf, 0x00, 0x0f); + sc.check(0x1, 0x1, 0x0, 0x0, 0x11, 0x00); + sc.check(0x0, 0x0, 0x1, 0x1, 0x00, 0x11); + sc.check(0xf, 0xf, 0x0, 0x0, 0xff, 0x00); + sc.check(0x0, 0x0, 0xf, 0xf, 0x00, 0xff); + sc.check(0xf, 0xf, 0xf, 0xf, 0xff, 0xff); + } + + +} \ No newline at end of file