diff --git a/distribution/ReleaseNotes.txt b/distribution/ReleaseNotes.txt
index a494497c6..b95fe04cf 100644
--- a/distribution/ReleaseNotes.txt
+++ b/distribution/ReleaseNotes.txt
@@ -1,6 +1,7 @@
Release Notes
HEAD, planned as v0.31
+- Added a run command to the cli to run circuit headless
- Main open dialog is able to open FSM and Truth Tables
- FSM editor highlights the current transition
- Adds drivers with inverted output
diff --git a/src/main/java/de/neemann/digital/cli/Main.java b/src/main/java/de/neemann/digital/cli/Main.java
index dc17ff211..fb447ee5b 100644
--- a/src/main/java/de/neemann/digital/cli/Main.java
+++ b/src/main/java/de/neemann/digital/cli/Main.java
@@ -20,6 +20,7 @@ public class Main extends Muxer {
addCommand(new CommandLineTester.TestCommand());
addCommand(new SVGExport());
addCommand(new StatsExport());
+ addCommand(new Runner());
}
/**
diff --git a/src/main/java/de/neemann/digital/cli/Runner.java b/src/main/java/de/neemann/digital/cli/Runner.java
new file mode 100644
index 000000000..77035da4f
--- /dev/null
+++ b/src/main/java/de/neemann/digital/cli/Runner.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2024 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.cli;
+
+import de.neemann.digital.FileLocator;
+import de.neemann.digital.cli.cli.Argument;
+import de.neemann.digital.cli.cli.BasicCommand;
+import de.neemann.digital.cli.cli.CLIException;
+import de.neemann.digital.core.Model;
+import de.neemann.digital.core.ModelEventType;
+import de.neemann.digital.core.element.ElementAttributes;
+import de.neemann.digital.core.element.Keys;
+import de.neemann.digital.core.wiring.Clock;
+import de.neemann.digital.draw.elements.Circuit;
+import de.neemann.digital.draw.model.ModelCreator;
+import de.neemann.digital.draw.model.RealTimeClock;
+import de.neemann.digital.gui.ProgramMemoryLoader;
+import de.neemann.digital.lang.Lang;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+/**
+ * Is used to run a circuit headless
+ */
+public class Runner extends BasicCommand {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Runner.class);
+ private final Argument digFile;
+
+ /**
+ * Creates the run command
+ */
+ public Runner() {
+ super("run");
+ digFile = addArgument(new Argument<>("dig", "", false));
+ }
+
+ @Override
+ protected void execute() throws CLIException {
+ try {
+ final CircuitLoader circuitLoader = new CircuitLoader(digFile.get(), false);
+ final Circuit circuit = circuitLoader.getCircuit();
+
+ long time = System.currentTimeMillis();
+ ModelCreator modelCreator = new ModelCreator(circuit, circuitLoader.getLibrary());
+ Model model = modelCreator.createModel(true);
+
+ time = System.currentTimeMillis() - time;
+ LOGGER.debug("model creation: " + time + " ms, " + model.getNodes().size() + " nodes");
+
+ ArrayList clocks = model.getClocks();
+ if (clocks.size() == 0)
+ throw new CLIException(Lang.get("cli_run_noClock"), null);
+
+ ScheduledThreadPoolExecutor timerExecutor = new ScheduledThreadPoolExecutor(1);
+
+ int threadRunnerCount = 0;
+ boolean realTimeClockRunning = false;
+ for (Clock c : clocks) {
+ int frequency = c.getFrequency();
+ if (frequency > 0) {
+ final RealTimeClock realTimeClock = new RealTimeClock(model, c, timerExecutor, null);
+ if (realTimeClock.isThreadRunner()) threadRunnerCount++;
+ realTimeClockRunning = true;
+ }
+ }
+ if (threadRunnerCount > 1)
+ throw new CLIException(Lang.get("err_moreThanOneFastClock"), null);
+
+ if (!realTimeClockRunning) {
+ throw new CLIException(Lang.get("cli_run_noClock"), null);
+ }
+
+ ElementAttributes settings = circuit.getAttributes();
+ if (settings.get(Keys.PRELOAD_PROGRAM)) {
+ File romHex = new FileLocator(settings.get(Keys.PROGRAM_TO_PRELOAD))
+ .setBaseFile(new File(digFile.get()))
+ .locate();
+ new ProgramMemoryLoader(romHex, settings.get(Keys.BIG_ENDIAN_SETTING))
+ .preInit(model);
+ }
+
+ model.addObserver(event -> {
+ if (event.getType() == ModelEventType.POSTCLOSED) {
+ timerExecutor.shutdownNow();
+ }
+ }, ModelEventType.POSTCLOSED);
+
+ model.init();
+ } catch (Exception e) {
+ throw new CLIException(Lang.get("cli_errorRunningCircuit"), e);
+ }
+ }
+}
diff --git a/src/main/java/de/neemann/digital/draw/model/RealTimeClock.java b/src/main/java/de/neemann/digital/draw/model/RealTimeClock.java
index 582047aa1..4790b6d5f 100644
--- a/src/main/java/de/neemann/digital/draw/model/RealTimeClock.java
+++ b/src/main/java/de/neemann/digital/draw/model/RealTimeClock.java
@@ -92,7 +92,7 @@ public class RealTimeClock implements ModelStateObserverTyped {
RealTimeRunner(int delay) {
FrequencyCalculator frequencyCalculator;
- if (frequency > 2000)
+ if (frequency > 2000 && status != null)
frequencyCalculator = new FrequencyCalculator(status, frequency);
else
frequencyCalculator = null;
@@ -120,10 +120,13 @@ public class RealTimeClock implements ModelStateObserverTyped {
ThreadRunner() {
thread = new Thread(() -> {
LOGGER.debug("thread start");
- FrequencyCalculator frequencyCalculator = new FrequencyCalculator(status, frequency);
+ FrequencyCalculator frequencyCalculator = null;
+ if (status != null)
+ frequencyCalculator = new FrequencyCalculator(status, frequency);
while (!Thread.interrupted()) {
model.modify(() -> output.setValue(1 - output.getValue()));
- frequencyCalculator.calc();
+ if (frequencyCalculator != null)
+ frequencyCalculator.calc();
}
});
thread.setDaemon(true);
diff --git a/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java b/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java
index ac4ef9402..13347c970 100644
--- a/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java
+++ b/src/main/java/de/neemann/digital/gui/ProgramMemoryLoader.java
@@ -31,9 +31,10 @@ public class ProgramMemoryLoader implements ModelModifier {
/**
* Creates a new rom modifier
*
- * @param romHex the file to load
+ * @param romHex the file to load
+ * @param bigEndian reads the file in big endian mode
*/
- ProgramMemoryLoader(File romHex, boolean bigEndian) {
+ public ProgramMemoryLoader(File romHex, boolean bigEndian) {
this.romHex = romHex;
this.bigEndian = bigEndian;
}
diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml
index feeb9d0e5..911f20365 100644
--- a/src/main/resources/lang/lang_de.xml
+++ b/src/main/resources/lang/lang_de.xml
@@ -1813,6 +1813,11 @@ Sind evtl. die Namen der Variablen nicht eindeutig?
Fehler bei der Erzeugung der CSV Datei!
+ Kein aktivierter Takt in der Schaltung gefunden!
+ Fehler beim Starten der Schaltung!
+ Start eine Schaltung headless.
+ Der Dateiname der Schaltung.
+
Fenster
Über Digital
Analyse
diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml
index 22cf3e448..3d136ef8a 100644
--- a/src/main/resources/lang/lang_en.xml
+++ b/src/main/resources/lang/lang_en.xml
@@ -1793,6 +1793,11 @@
Error while creating the stats file!
+ No running clock found in circuit!
+ Error running the circuit!
+ Runs a circuit headless.
+ File name of the circuit.
+
Windows
About
Analysis