diff --git a/src/main/java/de/neemann/digital/core/NodeException.java b/src/main/java/de/neemann/digital/core/NodeException.java index 37db54267..f4f1ae937 100644 --- a/src/main/java/de/neemann/digital/core/NodeException.java +++ b/src/main/java/de/neemann/digital/core/NodeException.java @@ -53,7 +53,20 @@ public class NodeException extends ExceptionWithOrigin { * @param values the values affected by this exception */ public NodeException(String message, Node node, int input, ImmutableList values) { - super(message); + this(message, node, input, values, null); + } + + /** + * Creates a new instance. + * + * @param message the message + * @param node the nod effected by tis exception + * @param input the affected nodes input + * @param values the values affected by this exception + * @param cause the cause + */ + public NodeException(String message, Node node, int input, ImmutableList values, Exception cause) { + super(message, cause); this.input = input; this.nodes = new ArrayList<>(); if (node != null) diff --git a/src/main/java/de/neemann/digital/core/element/Key.java b/src/main/java/de/neemann/digital/core/element/Key.java index 67e963fec..c246e1ad9 100644 --- a/src/main/java/de/neemann/digital/core/element/Key.java +++ b/src/main/java/de/neemann/digital/core/element/Key.java @@ -371,6 +371,9 @@ public class Key { * A special string key to flag long multi line strings. */ public static final class LongString extends Key { + private int rows = 6; + private int columns = 30; + /** * Creates a new Key * @@ -379,5 +382,42 @@ public class Key { public LongString(String key) { super(key, ""); } + + /** + * @return the rows of the editor field + */ + public int getRows() { + return rows; + } + + /** + * Sets the rows in the editor + * + * @param rows the number ow rows + * @return this for chained calls + */ + public LongString setRows(int rows) { + this.rows = rows; + return this; + } + + /** + * @return the coloums of the editor field + */ + public int getColumns() { + return columns; + } + + + /** + * Sets the columns in the editor + * + * @param columns the number ow rows + * @return this for chained calls + */ + public LongString setColumns(int columns) { + this.columns = columns; + return this; + } } } diff --git a/src/main/java/de/neemann/digital/core/element/Keys.java b/src/main/java/de/neemann/digital/core/element/Keys.java index b89d4f60c..dd068a87a 100644 --- a/src/main/java/de/neemann/digital/core/element/Keys.java +++ b/src/main/java/de/neemann/digital/core/element/Keys.java @@ -8,6 +8,7 @@ package de.neemann.digital.core.element; import de.neemann.digital.analyse.expression.format.FormatToExpression; import de.neemann.digital.core.arithmetic.BarrelShifterMode; import de.neemann.digital.core.arithmetic.LeftRightFormat; +import de.neemann.digital.core.extern.ProcessFactory; import de.neemann.digital.core.io.InValue; import de.neemann.digital.core.IntFormat; import de.neemann.digital.core.memory.DataField; @@ -509,4 +510,31 @@ public final class Keys { public static final Key ROMMANAGER = new Key<>("romContent", ROMManger.EMPTY); + + /** + * The type of the external process + */ + public static final Key.KeyEnum PROCESS_TYPE + = new Key.KeyEnum<>("processType", ProcessFactory.Type.Generic, ProcessFactory.Type.values()); + /** + * The inputs used by the external process + */ + public static final Key EXTERNAL_INPUTS + = new Key<>("externalInputs", "in"); + /** + * The outputs used by the external process + */ + public static final Key EXTERNAL_OUTPUTS + = new Key<>("externalOutputs", "out"); + /** + * The code to be executed by the external process + */ + public static final Key.LongString EXTERNAL_CODE + = new Key.LongString("Code").setRows(20).setColumns(40); + /** + * The parameters to be used by the external process + */ + public static final Key EXTERNAL_PARAMETERS + = new Key("externalParameters", ""); + } diff --git a/src/main/java/de/neemann/digital/core/extern/External.java b/src/main/java/de/neemann/digital/core/extern/External.java new file mode 100644 index 000000000..e95157273 --- /dev/null +++ b/src/main/java/de/neemann/digital/core/extern/External.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.core.extern; + +import de.neemann.digital.core.*; +import de.neemann.digital.core.element.*; +import de.neemann.digital.lang.Lang; +import de.neemann.gui.ErrorMessage; + +import javax.swing.*; +import java.io.IOException; + +/** + * The external component + */ +public class External extends Node implements Element { + + /** + * The external component description + */ + public static final ElementTypeDescription DESCRIPTION = new ElementTypeDescription(External.class) { + @Override + public PinDescriptions getInputDescription(ElementAttributes elementAttributes) { + return new PortDefinition(elementAttributes.get(Keys.EXTERNAL_INPUTS)).getPinDescriptions(PinDescription.Direction.input); + } + + @Override + public PinDescriptions getOutputDescriptions(ElementAttributes elementAttributes) { + return new PortDefinition(elementAttributes.get(Keys.EXTERNAL_OUTPUTS)).getPinDescriptions(PinDescription.Direction.output); + } + + } + .addAttribute(Keys.LABEL) + .addAttribute(Keys.EXTERNAL_INPUTS) + .addAttribute(Keys.EXTERNAL_OUTPUTS) + .addAttribute(Keys.EXTERNAL_CODE) + .addAttribute(Keys.PROCESS_TYPE) + .addAttribute(Keys.EXTERNAL_PARAMETERS); + + private final ProcessFactory.Type type; + private final PortDefinition ins; + private final PortDefinition outs; + private final ObservableValues outputs; + private final String code; + private final String params; + private ObservableValues inputs; + private ProcessHandler processHandler; + + /** + * Creates a new instance + * + * @param attr the elements attributes + */ + public External(ElementAttributes attr) { + super(true); + ins = new PortDefinition(attr.get(Keys.EXTERNAL_INPUTS)); + outs = new PortDefinition(attr.get(Keys.EXTERNAL_OUTPUTS)); + outputs = outs.createOutputs(); + type = attr.get(Keys.PROCESS_TYPE); + code = attr.get(Keys.EXTERNAL_CODE); + params = attr.get(Keys.EXTERNAL_PARAMETERS); + } + + @Override + public void setInputs(ObservableValues inputs) throws NodeException { + this.inputs = inputs; + for (int i = 0; i < inputs.size(); i++) + inputs.get(i).checkBits(ins.getPort(i).getBits(), this, i).addObserverToValue(this); + } + + + @Override + public void readInputs() throws NodeException { + try { + processHandler.writeValues(inputs); + } catch (IOException e) { + throw new NodeException(Lang.get("err_errorWritingDataToProcess"), this, -1, inputs, e); + } + } + + @Override + public void writeOutputs() throws NodeException { + try { + processHandler.readValuesTo(outputs); + } catch (IOException e) { + throw new NodeException(Lang.get("err_errorReadingDataToProcess"), this, -1, outputs, e); + } + } + + @Override + public ObservableValues getOutputs() { + return outputs; + } + + @Override + public void init(Model model) throws NodeException { + try { + processHandler = ProcessFactory.create(type, code, params); + } catch (IOException e) { + throw new NodeException(Lang.get("err_errorCreatingProcess"), this, -1, null, e); + } + + model.addObserver(event -> { + if (event.equals(ModelEvent.STOPPED)) { + try { + processHandler.close(); + } catch (IOException e) { + SwingUtilities.invokeLater(new ErrorMessage(Lang.get("msg_errorClosingExternalProcess")).addCause(e)); + } + } + }, ModelEvent.STOPPED); + } +} diff --git a/src/main/java/de/neemann/digital/core/extern/Port.java b/src/main/java/de/neemann/digital/core/extern/Port.java new file mode 100644 index 000000000..4b880aea6 --- /dev/null +++ b/src/main/java/de/neemann/digital/core/extern/Port.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.core.extern; + +import de.neemann.digital.core.Bits; + +/** + * A port for external access + */ +public class Port { + private final int bits; + private final String name; + + /** + * Creates a new port + * + * @param port the port + */ + public Port(String port) { + int p = port.indexOf(':'); + if (p < 0) { + name = port; + bits = 1; + } else { + name = port.substring(0, p); + int b = 1; + try { + b = (int) Bits.decode(port.substring(p + 1)); + } catch (Bits.NumberFormatException e) { + b = 1; + } + bits = b; + } + } + + /** + * @return the number of bits + */ + public int getBits() { + return bits; + } + + /** + * @return the name of the port + */ + public String getName() { + return name; + } +} diff --git a/src/main/java/de/neemann/digital/core/extern/PortDefinition.java b/src/main/java/de/neemann/digital/core/extern/PortDefinition.java new file mode 100644 index 000000000..c676bb617 --- /dev/null +++ b/src/main/java/de/neemann/digital/core/extern/PortDefinition.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.core.extern; + +import de.neemann.digital.core.ObservableValue; +import de.neemann.digital.core.ObservableValues; +import de.neemann.digital.core.element.PinDescription; +import de.neemann.digital.core.element.PinDescriptions; +import de.neemann.digital.core.element.PinInfo; + +import java.util.ArrayList; +import java.util.StringTokenizer; + +/** + * The list of ports used by an external component + */ +public class PortDefinition { + private final ArrayList ports; + + /** + * Creates a ew instance + * + * @param portDescription a comma separated list of port names + */ + public PortDefinition(String portDescription) { + ports = new ArrayList<>(); + StringTokenizer st = new StringTokenizer(portDescription, ","); + while (st.hasMoreTokens()) + ports.add(new Port(st.nextToken().trim())); + } + + /** + * Creates the output values to be used by the model + * + * @return the output values + */ + public ObservableValues createOutputs() { + ObservableValues.Builder builder = new ObservableValues.Builder(); + for (Port p : ports) + builder.add(new ObservableValue(p.getName(), p.getBits())); + return builder.build(); + } + + /** + * Creates the pin descriptions + * + * @param direction the direction to use for the pin descriptions + * @return the pin descriptions + */ + public PinDescriptions getPinDescriptions(PinDescription.Direction direction) { + PinInfo[] infos = new PinInfo[ports.size()]; + for (int i = 0; i < infos.length; i++) + infos[i] = new PinInfo(ports.get(i).getName(), "", direction); + return new PinDescriptions(infos); + } + + /** + * Getter for a single port + * + * @param i the port number + * @return the port + */ + public Port getPort(int i) { + return ports.get(i); + } +} diff --git a/src/main/java/de/neemann/digital/core/extern/ProcessFactory.java b/src/main/java/de/neemann/digital/core/extern/ProcessFactory.java new file mode 100644 index 000000000..ab96e392e --- /dev/null +++ b/src/main/java/de/neemann/digital/core/extern/ProcessFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.core.extern; + +import de.neemann.digital.lang.Lang; + +import java.io.IOException; + +/** + * The factory used to create external processes + */ +public final class ProcessFactory { + + + private ProcessFactory() { + } + + /** + * The process types + */ + public enum Type { + /** + * generic executable + */ + Generic, + /** + * ghdl vhdl interpreter + */ + GHDL + } + + /** + * Creates a new process + * + * @param type the type of the prosess + * @param code the code to use + * @param params the parameters to use + * @return the created process handler + * @throws IOException IOException + */ + public static ProcessHandler create(Type type, String code, String params) throws IOException { + switch (type) { + default: + throw new IOException(Lang.get("err_processType_N_notFound", type.name())); + } + } + +} diff --git a/src/main/java/de/neemann/digital/core/extern/ProcessHandler.java b/src/main/java/de/neemann/digital/core/extern/ProcessHandler.java new file mode 100644 index 000000000..cdf2cfd1b --- /dev/null +++ b/src/main/java/de/neemann/digital/core/extern/ProcessHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.core.extern; + +import de.neemann.digital.core.ObservableValues; + +import java.io.Closeable; +import java.io.IOException; + +/** + * A process handler implementation + */ +public interface ProcessHandler extends Closeable { + + /** + * Transfers the given values to the external process + * + * @param values the values to transfer + * @throws IOException IOException + */ + void writeValues(ObservableValues values) throws IOException; + + /** + * Reads values from the externalö process and writes them to the given values + * + * @param values the values to write to + * @throws IOException IOException + */ + void readValuesTo(ObservableValues values) throws IOException; + +} diff --git a/src/main/java/de/neemann/digital/core/extern/handler/StdIOProcess.java b/src/main/java/de/neemann/digital/core/extern/handler/StdIOProcess.java new file mode 100644 index 000000000..61bd248ce --- /dev/null +++ b/src/main/java/de/neemann/digital/core/extern/handler/StdIOProcess.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.core.extern.handler; + +import de.neemann.digital.core.ObservableValue; +import de.neemann.digital.core.ObservableValues; +import de.neemann.digital.core.extern.ProcessHandler; + +import java.io.*; +import java.util.StringTokenizer; + +/** + * The generic process description + */ +public abstract class StdIOProcess implements ProcessHandler { + private BufferedWriter writer; + private BufferedReader reader; + + /** + * Sets the reader and writer + * + * @param reader the reader + * @param writer the writer + */ + public void setReaderWriter(BufferedReader reader, BufferedWriter writer) { + this.reader = reader; + this.writer = writer; + } + + /** + * Sets the reader and writer + * + * @param in the input stream + * @param out the output stream + */ + public void setInputOutputStream(InputStream in, OutputStream out) { + setReaderWriter( + new BufferedReader(new InputStreamReader(in)), + new BufferedWriter(new OutputStreamWriter(out))); + } + + @Override + public void writeValues(ObservableValues values) throws IOException { + boolean first = true; + for (ObservableValue v : values) { + if (first) + first = false; + else + writer.write(","); + writer.write(v.getName()); + writer.write("="); + writer.write(Long.toHexString(v.getValue())); + } + writer.write("\n"); + writer.flush(); + } + + @Override + public void readValuesTo(ObservableValues values) throws IOException { + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("digital:")) { + StringTokenizer st = new StringTokenizer(line.substring(8), ",=", true); + for (ObservableValue v : values) { + if (!st.hasMoreTokens()) + throw new IOException("not enough values received!"); + + String name = st.nextToken().trim(); + if (name.equals(",")) + name = st.nextToken().trim(); + + if (!name.equals(v.getName())) + throw new IOException("values in wrong order: expected " + v.getName() + " but found " + name); + + if (!st.nextToken().equals("=")) + throw new IOException("= expected"); + + final String valStr = st.nextToken(); + if (valStr.equals("Z")) + v.setToHighZ(); + else + v.setValue(Long.parseLong(valStr, 16)); + + } + return; + } + } + } + +} diff --git a/src/main/java/de/neemann/digital/core/extern/handler/package-info.java b/src/main/java/de/neemann/digital/core/extern/handler/package-info.java new file mode 100644 index 000000000..8bddc8292 --- /dev/null +++ b/src/main/java/de/neemann/digital/core/extern/handler/package-info.java @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ + +/** + * The handlers which control the external processes + */ +package de.neemann.digital.core.extern.handler; diff --git a/src/main/java/de/neemann/digital/core/extern/package-info.java b/src/main/java/de/neemann/digital/core/extern/package-info.java new file mode 100644 index 000000000..fd80a58e8 --- /dev/null +++ b/src/main/java/de/neemann/digital/core/extern/package-info.java @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ + +/** + * Code to use external processes to implement the behavior of a component + */ +package de.neemann.digital.core.extern; diff --git a/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java b/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java index 642eaf008..a1e2dc229 100644 --- a/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java +++ b/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java @@ -9,6 +9,7 @@ import de.neemann.digital.core.arithmetic.*; import de.neemann.digital.core.arithmetic.Comparator; import de.neemann.digital.core.basic.*; import de.neemann.digital.core.element.*; +import de.neemann.digital.core.extern.External; import de.neemann.digital.core.flipflops.*; import de.neemann.digital.core.io.*; import de.neemann.digital.core.memory.*; @@ -186,7 +187,8 @@ public class ElementLibrary implements Iterable .add(PowerSupply.DESCRIPTION) .add(BusSplitter.DESCRIPTION) .add(Reset.DESCRIPTION) - .add(Break.DESCRIPTION)); + .add(Break.DESCRIPTION) + .add(External.DESCRIPTION)); addExternalJarComponents(jarFile); diff --git a/src/main/java/de/neemann/digital/gui/components/EditorFactory.java b/src/main/java/de/neemann/digital/gui/components/EditorFactory.java index 44f39a38b..1648e8ab4 100644 --- a/src/main/java/de/neemann/digital/gui/components/EditorFactory.java +++ b/src/main/java/de/neemann/digital/gui/components/EditorFactory.java @@ -13,6 +13,7 @@ import de.neemann.digital.core.NodeException; import de.neemann.digital.core.arithmetic.BarrelShifterMode; import de.neemann.digital.core.arithmetic.LeftRightFormat; import de.neemann.digital.core.element.*; +import de.neemann.digital.core.extern.ProcessFactory; import de.neemann.digital.core.io.InValue; import de.neemann.digital.core.memory.DataField; import de.neemann.digital.core.memory.ROM; @@ -71,6 +72,7 @@ public final class EditorFactory { add(FormatToExpression.class, FormatEditor.class); add(InverterConfig.class, InverterConfigEditor.class); add(ROMManger.class, ROMManagerEditor.class); + add(ProcessFactory.Type.class, ProcessFactoryTypeEditor.class); } private void add(Class clazz, Class> editor) { @@ -167,7 +169,8 @@ public final class EditorFactory { public StringEditor(String value, Key key) { if (key instanceof Key.LongString) { - text = new JTextArea(6, 30); + Key.LongString k = (Key.LongString) key; + text = new JTextArea(k.getRows(), k.getColumns()); compToAdd = new JScrollPane(text); setLabelAtTop(true); } else { @@ -538,6 +541,12 @@ public final class EditorFactory { } } + private static final class ProcessFactoryTypeEditor extends EnumEditor { + public ProcessFactoryTypeEditor(ProcessFactory.Type value, Key key) { + super(value, key); + } + } + private static class LanguageEditor extends LabelEditor { private JComboBox comb; diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index 5db988304..b42a585f4 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -696,6 +696,8 @@ Es kann dann ein Programm bis zum nächsten BRK-Befehl ausgeführt werden. Der schnelle Simulationslauf kann nur genutzt werden, wenn der Echtzeittakt deaktiviert ist! + Extern + Element zur Anbindung von externen Programmen zur Berechnung der Logik. Diode Echt bidirektionale Diode. @@ -838,6 +840,11 @@ Sind evtl. die Namen der Variablen nicht eindeutig? Konnte kein Element vom Typ "{0}" erzeugen! Zentral definierte ROM-Inhalte werden nicht unterstützt! + Prozesstyp {0} wurde nicht gefunden! + Es konnten keine Werte an den externen Prozess übergeben werden! + Es konnten keine Werte vom externen Prozess gelesen werden! + Der externe Prozess konnte nicht gestartet werden! + Adress-Bits Anzahl der Adress-Bits, die verwendet werden. Daten-Bits @@ -1036,6 +1043,19 @@ Sind evtl. die Namen der Variablen nicht eindeutig? ROM Inhalte Inhalt aller ROM Bausteine + Art des Prozesses + Gibt an, was für ein Prozess gestartet werden soll, um den Code auszuführen. + Generisch + GHDL + Eingänge + Die Eingänge des externen Prozesses. + Ausgänge + Die Ausgänge des externen Prozesses. + Programmcode + Der Programmcode welcher ausgeführt werden soll. + Parameter + Die Parameter, die an den externen Prozess übergeben werden sollen. + Leitung eingefügt. Aus Zwischenablage eingefügt. Wert ''{0}'' in Element ''{1}'' verändert. @@ -1336,6 +1356,8 @@ Soll dennoch exportiert werden? und analysiert werden. Aus nur einer Wahrheitstabelle kann keine Hardwarebeschreibung erzeugt werden. keine Daten + Konnte externen Prozess nicht beenden! + Ok 180° diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index e99216803..56abf7acc 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -692,6 +692,8 @@ Then you can run a simulated processor until a BRK instruction is reached. Fast forward clocking can only be used if the real-time clock is disabled. + External + Component to execute an external process to calculate the logic function. Diode Simplified bidirectional diode. It is used to implement a wired AND or a wired OR.. @@ -833,6 +835,11 @@ The names of the variables may not be unique. ROM's defined in the settings are not supported! Could not create a component of type {0}! + Could not found process type {0}! + Could not write values to the external process! + Could not write values from the external process! + Could not create the external process! + Address Bits Number of address bits used. Data Bits @@ -1027,6 +1034,19 @@ The names of the variables may not be unique. Content of ROM's Content of all used ROM's + Process Type + Defines which kind of process to use. + Generic + GHDL + Inputs + The inputs of the external process. + Outputs + The outputs of the external process. + Programcode + The programm code to use with the external process. + Parameters + Parameters to be used by the external process. + Inserted wire. Insert from clipboard. Value ''{0}'' in component ''{1}'' modified. @@ -1323,6 +1343,8 @@ Start export anyway? Included ROM's no data + Could not close external process! + Ok 180° diff --git a/src/test/java/de/neemann/digital/lang/TestElemConsistence.java b/src/test/java/de/neemann/digital/lang/TestElemConsistence.java index 619da0cc1..c1f13f550 100644 --- a/src/test/java/de/neemann/digital/lang/TestElemConsistence.java +++ b/src/test/java/de/neemann/digital/lang/TestElemConsistence.java @@ -8,6 +8,7 @@ package de.neemann.digital.lang; import de.neemann.digital.core.NodeException; import de.neemann.digital.core.basic.FanIn; import de.neemann.digital.core.element.*; +import de.neemann.digital.core.extern.External; import de.neemann.digital.core.memory.LookUpTable; import de.neemann.digital.core.wiring.*; import de.neemann.digital.draw.elements.PinException; @@ -66,6 +67,7 @@ public class TestElemConsistence extends TestCase { || e instanceof PriorityEncoder || e instanceof Splitter || e instanceof BusSplitter + || e instanceof External || e instanceof LookUpTable); }